From ecb294cc6654b76ea059c35ee22506675aaa17c9 Mon Sep 17 00:00:00 2001
From: CrimsonShrike <29737699+CrimsonShrike@users.noreply.github.com>
Date: Tue, 26 Sep 2023 14:56:20 +0300
Subject: [PATCH] [MIRROR] Lighting
---
baystation12.dme | 6 +-
code/__defines/MC.dm | 6 +
code/__defines/__dview.dm | 14 +
code/__defines/_renderer.dm | 7 -
code/__defines/lighting.dm | 73 ++-
code/__defines/misc.dm | 2 +
code/__defines/species.dm | 8 +-
code/__defines/subsystem-priority.dm | 1 +
code/__defines/subsystems.dm | 9 +-
code/_helpers/game.dm | 3 +-
code/_helpers/time.dm | 16 +
code/_helpers/unsorted.dm | 13 +-
code/controllers/configuration.dm | 4 +-
.../subsystems/ambient_lighting.dm | 244 ++++++++
code/controllers/subsystems/lighting.dm | 225 ++++---
code/controllers/subsystems/mobs.dm | 5 -
code/controllers/subsystems/zcopy.dm | 13 +-
code/datums/observation/sight_set.dm | 6 +
.../proximity_trigger/proximity_trigger.dm | 5 +-
code/datums/security_state.dm | 25 +-
code/game/atoms.dm | 17 +-
code/game/atoms_movable.dm | 18 +-
code/game/gamemodes/cult/cult_structures.dm | 8 +-
code/game/gamemodes/cult/narsie.dm | 4 +-
code/game/machinery/alarm.dm | 6 +-
code/game/machinery/bluespace_drive.dm | 2 +-
code/game/machinery/computer/computer.dm | 7 +-
code/game/machinery/doors/airlock.dm | 8 +-
code/game/machinery/doors/firedoor.dm | 2 +-
code/game/machinery/doors/simple.dm | 2 +-
code/game/machinery/floodlight.dm | 18 +-
code/game/machinery/floor_light.dm | 13 +-
code/game/machinery/hologram.dm | 4 +-
code/game/machinery/holosign.dm | 2 +-
code/game/machinery/lightswitch.dm | 2 +-
code/game/machinery/newscaster.dm | 2 +-
code/game/machinery/requests_console.dm | 2 +-
code/game/machinery/rotating_alarm.dm | 2 +-
code/game/machinery/spaceheater.dm | 6 +-
code/game/machinery/status_display.dm | 6 +-
code/game/machinery/supplybeacon.dm | 2 +-
code/game/machinery/teleporter/pad.dm | 2 +-
code/game/machinery/turret_control.dm | 6 +-
code/game/machinery/vending/_vending.dm | 3 +-
code/game/objects/auras/radiant_aura.dm | 2 +-
.../objects/effects/decals/Cleanable/misc.dm | 2 +-
code/game/objects/effects/effect_system.dm | 2 +-
code/game/objects/effects/fake_fire.dm | 2 +-
code/game/objects/effects/fire/fire.dm | 8 +-
code/game/objects/items/devices/flashlight.dm | 91 ++-
code/game/objects/items/devices/oxycandle.dm | 4 +-
code/game/objects/items/devices/powersink.dm | 2 +-
.../objects/items/devices/slide_projector.dm | 4 +-
code/game/objects/items/toys.dm | 2 +-
.../objects/items/weapons/candle/candle.dm | 8 +-
.../objects/items/weapons/candle/incense.dm | 5 +-
code/game/objects/items/weapons/ecigs.dm | 2 +-
.../objects/items/weapons/flamethrower.dm | 2 +-
.../weapons/grenades/anti_photon_grenade.dm | 4 +-
.../objects/items/weapons/grenades/light.dm | 2 +-
code/game/objects/items/weapons/lighter.dm | 4 +-
.../objects/items/weapons/melee/energy.dm | 2 +-
code/game/objects/items/weapons/shields.dm | 2 +-
code/game/objects/items/weapons/stunbaton.dm | 4 +-
.../items/weapons/tools/weldingtool.dm | 4 +-
code/game/objects/structures/flora.dm | 4 +-
code/game/objects/structures/fountain.dm | 2 +-
code/game/objects/structures/seaweed.dm | 4 +-
code/game/turfs/flooring/flooring_premade.dm | 16 +-
code/game/turfs/simulated/wall_attacks.dm | 2 +-
code/game/turfs/space/space.dm | 23 +-
code/game/turfs/turf.dm | 22 +-
code/game/turfs/turf_changing.dm | 29 +-
code/modules/ZAS/Fire.dm | 8 +-
code/modules/admin/buildmode/light_maker.dm | 14 +-
code/modules/admin/verbs/debug.dm | 2 +-
.../admin/view_variables/vv_set_handlers.dm | 10 +-
code/modules/ai/ai_holder_targeting.dm | 2 +-
code/modules/blob/blob.dm | 2 +-
code/modules/clothing/_clothing.dm | 2 +-
code/modules/clothing/glasses/glasses.dm | 1 -
.../clothing/spacesuits/rig/modules/ninja.dm | 12 +-
.../modules/clothing/spacesuits/spacesuits.dm | 2 +-
code/modules/detectivework/tools/rag.dm | 2 +-
code/modules/detectivework/tools/uvlight.dm | 2 +-
code/modules/holodeck/HolodeckObjects.dm | 3 +-
code/modules/holomap/ship_holomap.dm | 2 +-
code/modules/hydroponics/seed.dm | 4 +-
.../hydroponics/spreading/spreading.dm | 2 +-
.../hydroponics/trays/tray_update_icons.dm | 2 +-
.../integrated_electronics/subtypes/output.dm | 2 +-
code/modules/lighting/__lighting_docs.dm | 101 ++-
code/modules/lighting/_lighting_defs.dm | 43 ++
code/modules/lighting/darksight.dm | 25 +
code/modules/lighting/lighting_area.dm | 31 +-
code/modules/lighting/lighting_atom.dm | 157 +++--
code/modules/lighting/lighting_corner.dm | 393 ++++++++++--
code/modules/lighting/lighting_overlay.dm | 214 ++++---
code/modules/lighting/lighting_planemaster.dm | 25 -
code/modules/lighting/lighting_source.dm | 587 +++++++++++-------
code/modules/lighting/lighting_turf.dm | 296 +++++++--
code/modules/mechs/equipment/engineering.dm | 2 +-
code/modules/mechs/equipment/utility.dm | 9 +-
code/modules/mining/mine_items.dm | 2 +-
code/modules/mob/dview.dm | 27 +
code/modules/mob/living/bot/bot.dm | 2 +-
.../living/carbon/alien/diona/nymph_life.dm | 2 +-
code/modules/mob/living/carbon/human/human.dm | 34 +
code/modules/mob/living/carbon/human/life.dm | 4 +-
code/modules/mob/living/life.dm | 51 +-
code/modules/mob/living/living_defense.dm | 2 +-
code/modules/mob/living/silicon/ai/ai.dm | 12 +-
code/modules/mob/living/silicon/pai/pai.dm | 7 +-
.../mob/living/silicon/robot/drone/drone.dm | 2 +-
.../modules/mob/living/silicon/robot/robot.dm | 6 +-
.../mob/living/silicon/robot/robot_items.dm | 2 +-
.../simple_animal/constructs/constructs.dm | 2 +-
.../living/simple_animal/hostile/bluespace.dm | 4 +-
.../hostile/giant_spider/nurse.dm | 2 +-
.../living/simple_animal/hostile/vagrant.dm | 2 +-
code/modules/mob/login.dm | 11 +-
code/modules/mob/logout.dm | 5 +-
.../computers/modular_computer/core.dm | 2 +-
.../computers/subtypes/dev_console.dm | 2 +-
code/modules/multiz/zmimic/mimic_movable.dm | 53 +-
code/modules/multiz/zmimic/mimic_turf.dm | 17 +
code/modules/overmap/exoplanets/_exoplanet.dm | 121 +++-
.../planet_themes/radiation_bombing.dm | 2 +-
.../exoplanets/planet_themes/ruined_city.dm | 3 -
.../exoplanets/planet_types/chlorine.dm | 8 +-
.../overmap/exoplanets/planet_types/desert.dm | 2 +-
.../overmap/exoplanets/planet_types/grass.dm | 11 +-
.../exoplanets/planet_types/shrouded.dm | 2 +-
.../exoplanets/planet_types/volcanic.dm | 10 +-
code/modules/overmap/exoplanets/turfs.dm | 4 +-
code/modules/overmap/ships/computers/helm.dm | 2 +-
.../overmap/ships/engines/gas_thruster.dm | 2 +-
code/modules/power/apc.dm | 4 +-
code/modules/power/fusion/core/core_field.dm | 6 +-
code/modules/power/lighting.dm | 45 +-
code/modules/power/port_gen.dm | 2 +-
.../power/singularity/containment_field.dm | 2 +-
code/modules/power/singularity/singularity.dm | 2 +-
code/modules/projectiles/effects.dm | 22 +-
code/modules/projectiles/projectile/energy.dm | 4 +-
.../psionics/equipment/cerebro_enhancers.dm | 2 +-
code/modules/random_map/drop/droppod.dm | 2 +-
.../heat_sources/thermal_regulator.dm | 2 +-
.../reagent_containers/food/drinks/bottle.dm | 2 +-
code/modules/research/message_server.dm | 2 +-
code/modules/shieldgen/shieldwallgen.dm | 2 +-
code/modules/shuttles/landmarks.dm | 2 +-
.../spells/aoe_turf/conjure/construct.dm | 2 +-
code/modules/spells/racial_wizard.dm | 2 +-
code/modules/supermatter/supermatter.dm | 6 +-
.../effects/hellportal/portals.dm | 4 +-
config/example/config.txt | 4 +-
icons/effects/lighting_overlay.dmi | Bin 6319 -> 6268 bytes
icons/effects/lighting_overlay.png | Bin 1480 -> 0 bytes
icons/mob/darksight.dmi | Bin 0 -> 143537 bytes
maps/away/blueriver/blueriver.dm | 4 +-
maps/away/magshield/magshield.dm | 10 +-
.../skrellscoutship_machines.dm | 2 +-
maps/event/placeholders/placeholders.dm | 6 +-
.../exoplanet_ruins/icarus/icarus.dm | 2 +-
.../exoplanet_ruins/monoliths/monoliths.dm | 2 +-
maps/torch/torch_security_state.dm | 30 +-
maps/using.dm | 10 +
test/check-paths.sh | 4 +-
169 files changed, 2451 insertions(+), 1204 deletions(-)
create mode 100644 code/__defines/__dview.dm
create mode 100644 code/controllers/subsystems/ambient_lighting.dm
create mode 100644 code/modules/lighting/_lighting_defs.dm
create mode 100644 code/modules/lighting/darksight.dm
delete mode 100644 code/modules/lighting/lighting_planemaster.dm
create mode 100644 code/modules/mob/dview.dm
delete mode 100644 icons/effects/lighting_overlay.png
create mode 100644 icons/mob/darksight.dmi
diff --git a/baystation12.dme b/baystation12.dme
index f8c41a625641e..5b8551a5f016b 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -20,6 +20,7 @@
#include "code\__datastructures\priority_queue.dm"
#include "code\__datastructures\stack.dm"
#include "code\__defines\__compile_options.dm"
+#include "code\__defines\__dview.dm"
#include "code\__defines\__initialization.dm"
#include "code\__defines\__renderer.dm"
#include "code\__defines\_excom.dm"
@@ -188,6 +189,7 @@
#include "code\controllers\subsystems\air.dm"
#include "code\controllers\subsystems\airflow.dm"
#include "code\controllers\subsystems\alarm.dm"
+#include "code\controllers\subsystems\ambient_lighting.dm"
#include "code\controllers\subsystems\ao.dm"
#include "code\controllers\subsystems\atoms.dm"
#include "code\controllers\subsystems\chat.dm"
@@ -1991,11 +1993,12 @@
#include "code\modules\library\manuals\medical.dm"
#include "code\modules\library\manuals\nanotrasen.dm"
#include "code\modules\library\manuals\union.dm"
+#include "code\modules\lighting\_lighting_defs.dm"
+#include "code\modules\lighting\darksight.dm"
#include "code\modules\lighting\lighting_area.dm"
#include "code\modules\lighting\lighting_atom.dm"
#include "code\modules\lighting\lighting_corner.dm"
#include "code\modules\lighting\lighting_overlay.dm"
-#include "code\modules\lighting\lighting_planemaster.dm"
#include "code\modules\lighting\lighting_setup.dm"
#include "code\modules\lighting\lighting_source.dm"
#include "code\modules\lighting\lighting_turf.dm"
@@ -2070,6 +2073,7 @@
#include "code\modules\mining\machinery\mineral_unloader.dm"
#include "code\modules\mob\animations.dm"
#include "code\modules\mob\death.dm"
+#include "code\modules\mob\dview.dm"
#include "code\modules\mob\examinations.dm"
#include "code\modules\mob\gender.dm"
#include "code\modules\mob\hear_say.dm"
diff --git a/code/__defines/MC.dm b/code/__defines/MC.dm
index 8e6659624730f..edd52aa3fb13d 100644
--- a/code/__defines/MC.dm
+++ b/code/__defines/MC.dm
@@ -169,3 +169,9 @@ if(Datum.is_processing) {\
****/
#define addtimer(args...) _addtimer(args, source ="[__FILE__]#[__LINE__]")
+
+/****
+ * Helper for waits
+ ****/
+
+#define UNTIL(X) while(!(X)) stoplag()
diff --git a/code/__defines/__dview.dm b/code/__defines/__dview.dm
new file mode 100644
index 0000000000000..52553ef99784a
--- /dev/null
+++ b/code/__defines/__dview.dm
@@ -0,0 +1,14 @@
+//DVIEW defines
+
+#define FOR_DVIEW(type, range, center, invis_flags) \
+ global.dview_mob.loc = center; \
+ global.dview_mob.see_invisible = invis_flags; \
+ for(type in view(range, dview_mob))
+
+#define END_FOR_DVIEW dview_mob.loc = null
+
+#define DVIEW(output, range, center, invis_flags) \
+ global.dview_mob.loc = center; \
+ global.dview_mob.see_invisible = invis_flags; \
+ output = view(range, dview_mob); \
+ global.dview_mob.loc = null;
diff --git a/code/__defines/_renderer.dm b/code/__defines/_renderer.dm
index 90ff79719959c..c46d78b2060a0 100644
--- a/code/__defines/_renderer.dm
+++ b/code/__defines/_renderer.dm
@@ -190,13 +190,6 @@ GLOBAL_LIST_EMPTY(zmimic_renderers)
plane = LIGHTING_PLANE
appearance_flags = PLANE_MASTER | NO_CLIENT_COLOR
relay_blend_mode = BLEND_MULTIPLY
- color = list(
- -1, 0, 0, 0, // R
- 0, -1, 0, 0, // G
- 0, 0, -1, 0, // B
- 0, 0, 0, 0, // A
- 1, 1, 1, 1 // Mapping
- )
mouse_opacity = MOUSE_OPACITY_UNCLICKABLE
diff --git a/code/__defines/lighting.dm b/code/__defines/lighting.dm
index 5e35bad8636b1..8a5820f42579d 100644
--- a/code/__defines/lighting.dm
+++ b/code/__defines/lighting.dm
@@ -1,29 +1,42 @@
-#define FOR_DVIEW(type, range, center, invis_flags) \
- GLOB.dview_mob.loc = center; \
- GLOB.dview_mob.see_invisible = invis_flags; \
- for(type in view(range, GLOB.dview_mob))
+#define LIGHTING_INTERVAL 1 // Frequency, in 1/10ths of a second, of the lighting process.
-#define END_FOR_DVIEW GLOB.dview_mob.loc = null
+#define LIGHTING_HEIGHT 1 // height off the ground of light sources on the pseudo-z-axis, you should probably leave this alone
+#define LIGHTING_Z_FACTOR 10 // Z diff is multiplied by this and LIGHTING_HEIGHT to get the final height of a light source. Affects how much darker A Z light gets with each level transitioned.
+#define LIGHTING_ROUND_VALUE (1 / 200) //Value used to round lumcounts, values smaller than 1/255 don't matter (if they do, thanks sinking points), greater values will make lighting less precise, but in turn increase performance, VERY SLIGHTLY.
#define LIGHTING_ICON 'icons/effects/lighting_overlay.dmi' // icon used for lighting shading effects
-#define LIGHTING_ICON_STATE_DARK "dark" // Change between "soft_dark" and "dark" to swap soft darkvision
+#define LIGHTING_BASE_ICON_STATE "matrix" // icon_state used for normal color-matrix based lighting overlays.
+#define LIGHTING_STATION_ICON_STATE "tubedefault" // icon_state used for lighting overlays that are just displaying standard station lighting.
+#define LIGHTING_DARKNESS_ICON_STATE "black" // icon_state used for lighting overlays with no luminosity.
+#define LIGHTING_TRANSPARENT_ICON_STATE "blank"
-#define LIGHTING_ROUND_VALUE (1 / 64) // Value used to round lumcounts, values smaller than 1/69 don't matter (if they do, thanks sinking points), greater values will make lighting less precise, but in turn increase performance, VERY SLIGHTLY.
+#define LIGHTING_BLOCKED_FACTOR 0.5 // How much the range of a directional light will be reduced while facing a wall.
-#define LIGHTING_SOFT_THRESHOLD 0 // If the max of the lighting lumcounts of each spectrum drops below this, disable luminosity on the lighting overlays. This also should be the transparancy of the "soft_dark" icon state.
+// If defined, instant updates will be used whenever server load permits. Otherwise queued updates are always used.
+#define USE_INTELLIGENT_LIGHTING_UPDATES
+
+/// Maximum light_range before forced to always queue instead of using sync updates. Setting this too high will cause server stutter with moving large lights.
+#define LIGHTING_MAXIMUM_INSTANT_RANGE 8
+
+// mostly identical to below, but doesn't make sure T is valid first. Should only be used by lighting code.
+#define TURF_IS_DYNAMICALLY_LIT_UNSAFE(T) ((T:dynamic_lighting && T:loc:dynamic_lighting))
+#define TURF_IS_DYNAMICALLY_LIT(T) (isturf(T) && TURF_IS_DYNAMICALLY_LIT_UNSAFE(T))
+
+// Note: this does not imply the above, a turf can have ambient light without being dynamically lit.
+#define TURF_IS_AMBIENT_LIT_UNSAFE(T) (T:ambient_active)
+#define TURF_IS_AMBIENT_LIT(T) (isturf(T) && TURF_IS_AMBIENT_LIT_UNSAFE(T))
-#define LIGHTING_MULT_FACTOR 0.9
// If I were you I'd leave this alone.
#define LIGHTING_BASE_MATRIX \
- list \
- ( \
- LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, 0, \
- LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, 0, \
- LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, 0, \
- LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, 0, \
- 0, 0, 0, 1 \
- )
+ list \
+ ( \
+ 1, 1, 1, 0, \
+ 1, 1, 1, 0, \
+ 1, 1, 1, 0, \
+ 1, 1, 1, 0, \
+ 0, 0, 0, 1 \
+ ) \
// Helpers so we can (more easily) control the colour matrices.
#define CL_MATRIX_RR 1
@@ -47,12 +60,25 @@
#define CL_MATRIX_CB 19
#define CL_MATRIX_CA 20
+// Higher numbers override lower.
+#define LIGHTING_NO_UPDATE 0
+#define LIGHTING_VIS_UPDATE 1
+#define LIGHTING_CHECK_UPDATE 2
+#define LIGHTING_FORCE_UPDATE 3
+
// Lightbulb statuses
#define LIGHT_OK 0 // A light bulb is installed and functioning.
#define LIGHT_EMPTY 1 // There is no light bulb installed.
#define LIGHT_BROKEN 2 // The light bulb is broken/shattered.
#define LIGHT_BURNED 3 // The light bulb is burned out.
+// This color of overlay is very common - most of the station is this color when lit fully.
+// Tube lights are a bluish-white, so we can't just assume 1-1-1 is full-illumination.
+// -- If you want to change these, find them *by checking in-game*, just converting tubes' RGB color into floats will not work!
+#define LIGHTING_DEFAULT_TUBE_R 0.96
+#define LIGHTING_DEFAULT_TUBE_G 1
+#define LIGHTING_DEFAULT_TUBE_B 1
+
// Lighting color presets
#define LIGHT_COLOUR_WHITE "#fefefe" // Clinical white light bulbs
#define LIGHT_COLOUR_WARM "#fffee0" // Warm yellowish light bulbs
@@ -84,3 +110,16 @@
#define AREA_LIGHTING_WARM "warm"
#define AREA_LIGHTING_COOL "cool"
#define AREA_LIGHTING_DEFAULT "default" // For light replacers, defaults to whatever the area is set to. For areas, uses the initial lighting value from the light bulb itself.
+
+// Some angle presets for directional lighting.
+#define LIGHT_OMNI null
+#define LIGHT_SEMI 180
+#define LIGHT_VERY_WIDE 135
+#define LIGHT_WIDE 90
+#define LIGHT_NARROW 45
+
+#define DARKSIGHT_GRADIENT_SIZE 480
+// Max number of ambient groups, amount over this value will simply not be created
+#define AMBIENT_GROUP_MAX_BITS 24
+// Ambient group used for exterior turfs not on planets - Could also replace Space turf legacy starlight implementation
+#define SPACE_AMBIENT_GROUP 1
diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm
index 0d86253502bdd..c8da27040f9d9 100644
--- a/code/__defines/misc.dm
+++ b/code/__defines/misc.dm
@@ -338,3 +338,5 @@
#define SANITY_CHECK_TOPIC_PHYSICALLY_INTERACT FLAG(6)
#define SANITY_CHECK_DEFAULT (SANITY_CHECK_TOOL_IN_HAND | SANITY_CHECK_BOTH_ADJACENT)
+
+#define Z_ALL_TURFS(Z) block(locate(1, 1, Z), locate(world.maxx, world.maxy, Z))
diff --git a/code/__defines/species.dm b/code/__defines/species.dm
index 58dbcc6e359cf..31b74723209b8 100644
--- a/code/__defines/species.dm
+++ b/code/__defines/species.dm
@@ -42,7 +42,7 @@
#define SKIN_THREAT FLAG(0)
-// Darkvision Levels. Inverted - white is darkest, black is full vision
-#define DARKTINT_NONE "#ffffff"
-#define DARKTINT_MODERATE "#f9f9f5"
-#define DARKTINT_GOOD "#ebebe6"
+// Darkvision Levels. White is brightest, darker tints affect vision negatively
+#define DARKTINT_GOOD "#ffffff"
+#define DARKTINT_MODERATE "#f9f9f5"
+#define DARKTINT_NONE "#ebebe6"
diff --git a/code/__defines/subsystem-priority.dm b/code/__defines/subsystem-priority.dm
index 9e3e1e818a456..add0b6b88c821 100644
--- a/code/__defines/subsystem-priority.dm
+++ b/code/__defines/subsystem-priority.dm
@@ -20,6 +20,7 @@
#define SS_PRIORITY_AIR 80 // ZAS processing.
#define SS_PRIORITY_THROWING 75 // Throwing calculation and constant checks
#define SS_PRIORITY_CHEMISTRY 60 // Multi-tick chemical reactions.
+#define SS_PRIORITY_LIGHTING 50 // Queued lighting engine updates.
#define SS_PRIORITY_SPACEDRIFT 45 // Drifting things
#define SS_PRIORITY_CHAT 40 // Chat
#define SS_PRIORITY_ALARM 20 // Alarm processing.
diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm
index addaa201bbee6..3257785cc6fef 100644
--- a/code/__defines/subsystems.dm
+++ b/code/__defines/subsystems.dm
@@ -43,10 +43,11 @@
#define SS_INIT_SHUTTLE -5
#define SS_INIT_GOALS -5
#define SS_INIT_LIGHTING -6
-#define SS_INIT_ZCOPY -7
-#define SS_INIT_HOLOMAP -8
-#define SS_INIT_OVERLAYS -9
-#define SS_INIT_XENOARCH -10
+#define SS_INIT_AMBIENT_LIGHT -7
+#define SS_INIT_ZCOPY -8
+#define SS_INIT_HOLOMAP -9
+#define SS_INIT_OVERLAYS -10
+#define SS_INIT_XENOARCH -11
#define SS_INIT_BAY_LEGACY -12
#define SS_INIT_TICKER -20
#define SS_INIT_AI -21
diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm
index 739ca2d00e8f8..fd8df21c08558 100644
--- a/code/_helpers/game.dm
+++ b/code/_helpers/game.dm
@@ -262,7 +262,8 @@
/proc/get_mobs_and_objs_in_view_fast(turf/T, range, list/mobs, list/objs, checkghosts = null)
- var/list/hear = dview(range,T,INVISIBILITY_MAXIMUM)
+ var/list/hear = list()
+ DVIEW(hear, range, T, INVISIBILITY_MAXIMUM)
var/list/hearturfs = list()
for(var/atom/movable/AM in hear)
diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm
index 9b2fb78d22b49..7b539933c0584 100644
--- a/code/_helpers/time.dm
+++ b/code/_helpers/time.dm
@@ -207,3 +207,19 @@ var/global/round_start_time = 0
var/time_string = time2text(world.realtime, "MM-DD")
var/time_list = splittext(time_string, "-")
return list(text2num(time_list[1]), text2num(time_list[2]))
+
+
+#define MIDNIGHT_ROLLOVER 864000 //number of deciseconds in a day
+
+var/global/midnight_rollovers = 0
+var/global/rollovercheck_last_timeofday = 0
+/proc/update_midnight_rollover()
+ if (world.timeofday < global.rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS!
+ global.midnight_rollovers += 1
+ global.rollovercheck_last_timeofday = world.timeofday
+ return global.midnight_rollovers
+
+//time of day but automatically adjusts to the server going into the next day within the same round.
+//for when you need a reliable time number that doesn't depend on byond time.
+#define REALTIMEOFDAY (world.timeofday + (MIDNIGHT_ROLLOVER * MIDNIGHT_ROLLOVER_CHECK))
+#define MIDNIGHT_ROLLOVER_CHECK ( global.rollovercheck_last_timeofday != world.timeofday ? update_midnight_rollover() : global.midnight_rollovers )
diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm
index a642fae1cf57d..7a19e3db3d74d 100644
--- a/code/_helpers/unsorted.dm
+++ b/code/_helpers/unsorted.dm
@@ -1036,17 +1036,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
. = view(range, GLOB.dview_mob)
GLOB.dview_mob.loc = null
-/mob/dview
- invisibility = INVISIBILITY_ABSTRACT
- density = FALSE
-
- anchored = TRUE
- simulated = FALSE
-
- see_in_dark = 1e6
-
- virtual_mob = null
-
/mob/dview/Destroy()
SHOULD_CALL_PARENT(FALSE)
return QDEL_HINT_LETMELIVE
@@ -1062,7 +1051,7 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
/atom/proc/get_light_and_color(atom/origin)
if(origin)
color = origin.color
- set_light(origin.light_max_bright, origin.light_inner_range, origin.light_outer_range, origin.light_falloff_curve)
+ set_light(origin.light_range, origin.light_power)
// call to generate a stack trace and print to runtime logs
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index 3100d3345740b..e11f37874629f 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -335,8 +335,8 @@
var/static/aooc_allowed = TRUE
- /// Whether space turfs have ambient light or not
- var/static/starlight = 0
+ /// Whether space turfs and some exterior turfs have ambient light or not default, 0.5, values over 1 may overpower dynamic lights
+ var/static/starlight = 0.5
var/static/list/ert_species = list(SPECIES_HUMAN)
diff --git a/code/controllers/subsystems/ambient_lighting.dm b/code/controllers/subsystems/ambient_lighting.dm
new file mode 100644
index 0000000000000..a234899708402
--- /dev/null
+++ b/code/controllers/subsystems/ambient_lighting.dm
@@ -0,0 +1,244 @@
+SUBSYSTEM_DEF(ambient_lighting) //A simple SS that handles updating ambient lights of away sites and such places
+ name = "Ambient Lighting"
+ wait = 1
+ priority = SS_PRIORITY_LIGHTING
+ init_order = SS_INIT_AMBIENT_LIGHT
+ runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
+
+ /// List of turfs queued for ambient light evaluation
+ var/list/queued = list()
+
+ /// A bitmap of free ambience group indexes.
+ var/ambient_group_free_bitmap = ~0
+ /// map of ambiient groups
+ var/list/ambient_groups[AMBIENT_GROUP_MAX_BITS]
+
+/datum/ambient_group
+ /// Index in SSambient_lighting map
+ var/global_index
+ var/list/member_turfs_by_z = list()
+ /// Color data, do NOT modify manually
+ var/apparent_r
+ var/apparent_g
+ var/apparent_b
+ /// Prevent modification of member turfs or colour while an operation is taking place
+ var/busy = FALSE
+
+/datum/ambient_group/New(ncolor, nmultiplier, nindex)
+ . = ..()
+ set_color(ncolor, nmultiplier)
+ global_index = nindex
+
+/datum/ambient_group/Destroy()
+ SSambient_lighting.ambient_groups[global_index] = null
+ SSambient_lighting.ambient_group_free_bitmap |= FLAG(global_index)
+ return ..()
+
+/datum/ambient_group/proc/set_color(color, multiplier)
+ var/list/new_parts = rgb2num(color)
+ //Calculate delta from current to desired location
+ var/dr = (new_parts[1] / 255) * multiplier - apparent_r
+ var/dg = (new_parts[2] / 255) * multiplier - apparent_g
+ var/db = (new_parts[3] / 255) * multiplier - apparent_b
+
+ if (round(dr/4, LIGHTING_ROUND_VALUE) == 0 && round(dg/4, LIGHTING_ROUND_VALUE) == 0 && round(db/4, LIGHTING_ROUND_VALUE) == 0)
+ // no-op
+ return
+
+ busy = TRUE
+
+ // Doing it ordered by zlev should ensure that it looks vaguely coherent mid-update regardless of turf insertion order.
+ for (var/zlev in 1 to length(member_turfs_by_z))
+ for (var/turf/T as anything in member_turfs_by_z[zlev])
+ T.add_ambient_light_raw(dr, dg, db)
+ CHECK_TICK
+
+ apparent_r += dr
+ apparent_g += dg
+ apparent_b += db
+
+ busy = FALSE
+
+/**
+ * Adds group ambient light to a turf
+ *
+ * **Parameters**:
+ * - `T` turf - Turf to modify
+ *
+ */
+/datum/ambient_group/proc/set_ambient_light(turf/T)
+ set waitfor = FALSE
+
+ UNTIL(!busy)
+ T.add_ambient_light_raw(apparent_r, apparent_g, apparent_b)
+
+/**
+ * Removes group ambient light from turf
+ *
+ * **Parameters**:
+ * - `T` turf - Turf to modify
+ *
+ */
+/datum/ambient_group/proc/remove_ambient_light(turf/T)
+ set waitfor = FALSE
+
+ UNTIL(!busy)
+ T.add_ambient_light_raw(-apparent_r, -apparent_g, -apparent_b)
+
+/**
+ * Adds turf to ambient group, will set bitflags and set current ambient light
+ *
+ * **Parameters**:
+ * - `T` turf - Turf to add and track
+ *
+ */
+/datum/ambient_group/proc/add_turf(turf/T)
+ set waitfor = FALSE
+
+ UNTIL(!busy)
+ //Already existing
+ if(T.ambient_bitflag & FLAG(global_index))
+ return
+
+ if (T.z > length(member_turfs_by_z))
+ member_turfs_by_z.len = T.z
+
+ LAZYADD(member_turfs_by_z[T.z], T)
+ T.ambient_bitflag |= FLAG(global_index)
+ set_ambient_light(T)
+
+/**
+ * Removes turf from ambient group if it is part of it. Removes group's ambient light and flag from turf
+ *
+ * **Parameters**:
+ * - `T` turf - Turf to remove
+ *
+ */
+/datum/ambient_group/proc/remove_turf(turf/T)
+ set waitfor = FALSE
+
+ UNTIL(!busy)
+ if(!(T.ambient_bitflag & FLAG(global_index)))
+ return
+
+ if (T.z > length(member_turfs_by_z))
+ CRASH("Attempt to remove member turf with Z greater than local max -- this turf is not a member")
+
+ remove_ambient_light(T)
+ T.ambient_bitflag &= ~FLAG(global_index)
+ member_turfs_by_z[T.z] -= T
+
+/**
+ * Find a valid index in the ambient group map for a new group
+ *
+ * Returns index or -1 if no indices are left
+ */
+/datum/controller/subsystem/ambient_lighting/proc/allocate_index()
+ if (ambient_group_free_bitmap == 0)
+ return -1 //Out of indices, no ambient light for you
+
+ // Find the first free index in the bitmap.
+ var/index = 1
+ while (!(ambient_group_free_bitmap & FLAG(index)) && index < AMBIENT_GROUP_MAX_BITS)
+ index += 1
+
+ ambient_group_free_bitmap &= ~FLAG(index)
+
+ return index
+/**
+ * Adds the space ambient group if it doesn't currently exist
+ *
+ */
+/datum/controller/subsystem/ambient_lighting/proc/add_space_ambient_group()
+ var/index = allocate_index() //It will always be 1, but we want to make sure bitmap is in a valid state
+
+ ASSERT(index == SPACE_AMBIENT_GROUP)
+
+ ambient_groups[index] = new /datum/ambient_group(SSskybox.background_color, config.starlight, index )
+
+/**
+ * Removes turf from ambient group if it is part of it. Removes group's ambient light and flag from turf
+ *
+ * **Parameters**:
+ * - `color` color - Initial color
+ * - `multiplier` float - Initial multiplier of light strength
+ *
+ * Returns index or -1 if no indices are left
+ */
+/datum/controller/subsystem/ambient_lighting/proc/create_ambient_group(color, multiplier)
+
+ if(ambient_groups[SPACE_AMBIENT_GROUP] == null) //Something (probably a planet) wants to add an ambient group, add space first
+ add_space_ambient_group()
+
+ // Find the first free index in the bitmap.
+ var/index = allocate_index()
+
+ if(index <= 0)
+ return index
+
+ ambient_groups[index] = new /datum/ambient_group(color, multiplier, index)
+
+ return index
+
+/**
+ * Removes turf from all ambient groups it is part of (if any)
+ *
+ * **Parameters**:
+ * - `target` turf - Turf to remove
+ */
+/datum/controller/subsystem/ambient_lighting/proc/clean_turf(turf/target)
+ if(target.ambient_bitflag != 0)
+ for(var/datum/ambient_group/A in ambient_groups)
+ if(target.ambient_bitflag & FLAG(A.global_index))
+ A.remove_turf(target)
+ if(!target.ambient_bitflag)
+ return //Return early if flag is already clear
+
+/datum/controller/subsystem/ambient_lighting/Initialize(start_timeofday)
+ //Create space ambient group if nothing created it until now.
+ if(ambient_groups[SPACE_AMBIENT_GROUP] == null)
+ add_space_ambient_group()
+
+ fire(FALSE, TRUE)
+ return ..()
+
+/// Go over turfs in queue, add them to space or planet ambient groups if valid, else remove them from all ambient groups
+/datum/controller/subsystem/ambient_lighting/fire(resumed = FALSE, no_mc_tick = FALSE)
+ var/list/curr = queued
+ var/starlight_enabled = config.starlight
+
+ var/needs_ambience
+ while (length(curr))
+ var/turf/target = curr[length(curr)]
+ LIST_DEC(curr)
+
+ if(target && target.is_outside())
+ needs_ambience = TURF_IS_DYNAMICALLY_LIT_UNSAFE(target)
+ if (!needs_ambience)
+ for (var/turf/T in RANGE_TURFS(target, 1))
+ if(TURF_IS_DYNAMICALLY_LIT_UNSAFE(T))
+ needs_ambience = TRUE
+ break
+
+ if (needs_ambience)
+ var/obj/effect/overmap/visitable/sector/exoplanet/E = map_sectors["[target.z]"]
+ if (istype(E))
+ if(E.ambient_group_index > 0)
+ var/datum/ambient_group/A = ambient_groups[E.ambient_group_index]
+ A.add_turf(target)
+ else
+ if (starlight_enabled) //Assume we can light up exterior with space light generally
+ var/datum/ambient_group/A = ambient_groups[SPACE_AMBIENT_GROUP]
+ A.add_turf(target)
+ else if (TURF_IS_AMBIENT_LIT_UNSAFE(target))
+ //Remove from all groups
+ if(target.ambient_bitflag != 0)
+ for(var/datum/ambient_group/A in ambient_groups)
+ A.remove_turf(target)
+ if(!target.ambient_bitflag)
+ break
+
+ if (no_mc_tick)
+ CHECK_TICK
+ else if (MC_TICK_CHECK)
+ return
diff --git a/code/controllers/subsystems/lighting.dm b/code/controllers/subsystems/lighting.dm
index 775bb83b47ba5..971adfeac50b7 100644
--- a/code/controllers/subsystems/lighting.dm
+++ b/code/controllers/subsystems/lighting.dm
@@ -1,112 +1,140 @@
-var/global/lighting_overlays_initialised = FALSE
-
SUBSYSTEM_DEF(lighting)
name = "Lighting"
- wait = 1
+ wait = LIGHTING_INTERVAL
+ priority = SS_PRIORITY_LIGHTING
init_order = SS_INIT_LIGHTING
+ runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
- // Queues of update counts, waiting to be rolled into stats lists
- var/list/stats_queues = list(
- "Source" = list(),
- "Corner" = list(),
- "Overlay" = list()
- )
- // Stats lists
- var/list/stats_lists = list(
- "Source" = list(),
- "Corner" = list(),
- "Overlay" = list()
- )
- var/update_stats_every = 1 SECOND
- var/next_stats_update = 0
- var/stat_updates_to_keep = 5
+ var/total_lighting_overlays = 0
+ var/total_lighting_sources = 0
+ var/total_ambient_turfs = 0
+ var/total_lighting_corners = 0
- var/list/light_queue = list() // lighting sources queued for update.
+ /// lighting sources queued for update.
+ var/list/light_queue = list()
var/lq_idex = 1
- var/list/corner_queue = list() // lighting corners queued for update.
+ /// lighting corners queued for update.
+ var/list/corner_queue = list()
var/cq_idex = 1
- var/list/overlay_queue = list() // lighting overlays queued for update.
+ /// lighting overlays queued for update.
+ var/list/overlay_queue = list()
var/oq_idex = 1
+ // - Performance and analytics data
var/processed_lights = 0
var/processed_corners = 0
var/processed_overlays = 0
+ var/total_ss_updates = 0
+ var/total_instant_updates = 0
+
+#ifdef USE_INTELLIGENT_LIGHTING_UPDATES
+ var/force_queued = TRUE
+ var/force_override = FALSE // For admins.
+#endif
+
/datum/controller/subsystem/lighting/UpdateStat(time)
- if (PreventUpdateStat(time))
- return ..()
- ..({"\
- Queues: \
- Source [length(light_queue)] \
- Corner [length(corner_queue)] \
- Overlay [length(overlay_queue)]\n\
- Source Updates [length(stats_lists["Source"])]\n\
- Corner Updates [length(stats_lists["Corner"])]\n\
- Overlay Updates [length(stats_lists["Overlay"])]\
- "})
-
-
-/datum/controller/subsystem/lighting/Initialize(start_uptime)
- InitializeTurfs()
- lighting_overlays_initialised = TRUE
+ var/list/out = list(
+#ifdef USE_INTELLIGENT_LIGHTING_UPDATES
+ "IUR: [total_ss_updates ? round(total_instant_updates/(total_instant_updates+total_ss_updates)*100, 0.1) : "NaN"]%\n",
+#endif
+ "\tT:{L:[total_lighting_sources] C:[total_lighting_corners] O:[total_lighting_overlays] A:[total_ambient_turfs]}\n",
+ "\tP:{L:[length(light_queue) - (lq_idex - 1)]|C:[length(corner_queue) - (cq_idex - 1)]|O:[length(overlay_queue) - (oq_idex - 1)]}\n",
+ "\tL:{L:[processed_lights]|C:[processed_corners]|O:[processed_overlays]}\n"
+ )
+ ..(out.Join())
+
+#ifdef USE_INTELLIGENT_LIGHTING_UPDATES
+
+/hook/roundstart/proc/lighting_init_roundstart()
+ SSlighting.handle_roundstart()
+ return TRUE
+
+/datum/controller/subsystem/lighting/proc/handle_roundstart()
+ force_queued = FALSE
+ total_ss_updates = 0
+ total_instant_updates = 0
+
+#endif
+/// Generate overlays for all Zlevels and then fire normally
+/datum/controller/subsystem/lighting/Initialize(timeofday)
+ var/overlaycount = 0
+ var/starttime = REALTIMEOFDAY
+
+ // Generate overlays.
+ for (var/zlevel = 1 to world.maxz)
+ overlaycount += InitializeZlev(zlevel)
+
+ admin_notice(SPAN_DANGER("Created [overlaycount] lighting overlays in [(REALTIMEOFDAY - starttime)/10] seconds."), R_DEBUG)
+
+ starttime = REALTIMEOFDAY
+ // Tick once to clear most lights.
fire(FALSE, TRUE)
+ admin_notice(SPAN_DANGER("Processed [processed_lights] light sources."), R_DEBUG)
+ admin_notice(SPAN_DANGER("Processed [processed_corners] light corners."), R_DEBUG)
+ admin_notice(SPAN_DANGER("Processed [processed_overlays] light overlays."), R_DEBUG)
+ admin_notice(SPAN_DANGER("Lighting pre-bake completed in [(REALTIMEOFDAY - starttime)/10] seconds."), R_DEBUG)
+
+ log_ss("lighting", "NOv:[overlaycount] L:[processed_lights] C:[processed_corners] O:[processed_overlays]")
+
+ ..()
+
+/**
+ * Go over turfs thay may be dynamically lit and add a lighting overlay if they don't have one. Then do the same for turfs that may be ambient lit.
+ *
+ * **Parameters**:
+ * - `zlev` int - z-level index
+ */
+/datum/controller/subsystem/lighting/proc/InitializeZlev(zlev)
+ for (var/thing in Z_ALL_TURFS(zlev))
+ var/turf/T = thing
+ if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(T) && !T.lighting_overlay) // Can't assume that one hasn't already been created on bay/neb.
+ new /atom/movable/lighting_overlay(T)
+ . += 1
+ if(TURF_IS_AMBIENT_LIT_UNSAFE(T))
+ T.generate_missing_corners() // Forcibly generate corners.
-// It's safe to pass a list of non-turfs to this list - it'll only check turfs.
+ CHECK_TICK
+
+/// Initialize a set of turfs (for example as part of loading a map template) It's safe to pass a list of non-turfs to this list - it'll only check turfs.
/datum/controller/subsystem/lighting/proc/InitializeTurfs(list/targets)
for (var/turf/T in (targets || world))
- if (T.dynamic_lighting && T.loc:dynamic_lighting)
+ if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(T))
T.lighting_build_overlay()
// If this isn't here, BYOND will set-background us.
CHECK_TICK
+/**
+ * Go over light queue and update corners as needed
+ * Go over light corner queue and update overlays as needed
+ * Go over overlay queue and update as needed
+ */
/datum/controller/subsystem/lighting/fire(resumed = FALSE, no_mc_tick = FALSE)
if (!resumed)
- stats_queues["Source"] += processed_lights
- stats_queues["Corner"] += processed_corners
- stats_queues["Overlay"] += processed_overlays
-
processed_lights = 0
processed_corners = 0
processed_overlays = 0
- if(next_stats_update <= world.time)
- next_stats_update = world.time + update_stats_every
- for(var/stat_name in stats_queues)
- var/stat_sum = 0
- var/list/stats_queue = stats_queues[stat_name]
- for(var/count in stats_queue)
- stat_sum += count
- stats_queue.Cut()
-
- var/list/stats_list = stats_lists[stat_name]
- stats_list.Insert(1, stat_sum)
- if(length(stats_list) > stat_updates_to_keep)
- stats_list.Cut(length(stats_list))
-
MC_SPLIT_TICK_INIT(3)
if (!no_mc_tick)
MC_SPLIT_TICK
- // Sources.
- while (lq_idex <= length(light_queue))
- var/datum/light_source/L = light_queue[lq_idex]
- lq_idex += 1
+ var/list/curr_lights = light_queue
+ var/list/curr_corners = corner_queue
+ var/list/curr_overlays = overlay_queue
- if(L.check() || L.destroyed || L.force_update)
- L.remove_lum()
- if(!L.destroyed)
- L.apply_lum()
+ while (lq_idex <= length(curr_lights))
+ var/datum/light_source/L = curr_lights[lq_idex++]
- else if(L.vis_update) //We smartly update only tiles that became (in) visible to use.
- L.smart_vis_update()
+ if (L.needs_update != LIGHTING_NO_UPDATE)
+ total_ss_updates += 1
+ L.update_corners()
- L.vis_update = FALSE
- L.force_update = FALSE
- L.needs_update = FALSE
+ L.needs_update = LIGHTING_NO_UPDATE
- processed_lights += 1
+ processed_lights++
if (no_mc_tick)
CHECK_TICK
@@ -114,22 +142,21 @@ SUBSYSTEM_DEF(lighting)
break
if (lq_idex > 1)
- light_queue.Cut(1, lq_idex)
+ curr_lights.Cut(1, lq_idex)
lq_idex = 1
if (!no_mc_tick)
MC_SPLIT_TICK
- // Corners.
- while (cq_idex <= length(corner_queue))
- var/datum/lighting_corner/C = corner_queue[cq_idex]
- cq_idex += 1
+ while (cq_idex <= length(curr_corners))
+ var/datum/lighting_corner/C = curr_corners[cq_idex++]
- C.update_overlays()
+ if (C.needs_update)
+ C.update_overlays()
- C.needs_update = FALSE
+ C.needs_update = FALSE
- processed_corners += 1
+ processed_corners++
if (no_mc_tick)
CHECK_TICK
@@ -137,27 +164,51 @@ SUBSYSTEM_DEF(lighting)
break
if (cq_idex > 1)
- corner_queue.Cut(1, cq_idex)
+ curr_corners.Cut(1, cq_idex)
cq_idex = 1
if (!no_mc_tick)
MC_SPLIT_TICK
- // Objects.
- while (oq_idex <= length(overlay_queue))
- var/atom/movable/lighting_overlay/O = overlay_queue[oq_idex]
- oq_idex += 1
+ while (oq_idex <= length(curr_overlays))
+ var/atom/movable/lighting_overlay/O = curr_overlays[oq_idex++]
- O.update_overlay()
- O.needs_update = 0
+ if (!QDELETED(O) && O.needs_update)
+ O.update_overlay()
+ O.needs_update = FALSE
- processed_overlays += 1
+ processed_overlays++
if (no_mc_tick)
CHECK_TICK
else if (MC_TICK_CHECK)
break
+ if (oq_idex > 1)
+ curr_overlays.Cut(1, oq_idex)
+ oq_idex = 1
+
+/datum/controller/subsystem/lighting/Recover()
+ total_lighting_corners = SSlighting.total_lighting_corners
+ total_lighting_overlays = SSlighting.total_lighting_overlays
+ total_lighting_sources = SSlighting.total_lighting_sources
+
+ light_queue = SSlighting.light_queue
+ corner_queue = SSlighting.corner_queue
+ overlay_queue = SSlighting.overlay_queue
+
+ lq_idex = SSlighting.lq_idex
+ cq_idex = SSlighting.cq_idex
+ oq_idex = SSlighting.oq_idex
+
+ if (lq_idex > 1)
+ light_queue.Cut(1, lq_idex)
+ lq_idex = 1
+
+ if (cq_idex > 1)
+ corner_queue.Cut(1, cq_idex)
+ cq_idex = 1
+
if (oq_idex > 1)
overlay_queue.Cut(1, oq_idex)
oq_idex = 1
diff --git a/code/controllers/subsystems/mobs.dm b/code/controllers/subsystems/mobs.dm
index 42fd7330a381a..7c6378b83a61e 100644
--- a/code/controllers/subsystems/mobs.dm
+++ b/code/controllers/subsystems/mobs.dm
@@ -61,8 +61,3 @@ if(MOB.is_processing == SSmobs) {\
else if (MOB.is_processing) {\
crash_with("Failed to stop processing mob. Being processed by [MOB.is_processing] instead.")\
}
-
-
-/mob/dview/Initialize()
- . = ..()
- STOP_PROCESSING_MOB(src)
diff --git a/code/controllers/subsystems/zcopy.dm b/code/controllers/subsystems/zcopy.dm
index 6c8bc6a8fe523..a5f21140f0832 100644
--- a/code/controllers/subsystems/zcopy.dm
+++ b/code/controllers/subsystems/zcopy.dm
@@ -1,5 +1,6 @@
-#define SHADOWER_DARKENING_FACTOR 0.6 // The multiplication factor for openturf shadower darkness. Lighting will be multiplied by this.
-#define SHADOWER_DARKENING_COLOR "#999999" // The above, but as an RGB string for lighting-less turfs.
+#define SHADOWER_DARKENING_FACTOR 0.8 // The multiplication factor for openturf shadower darkness. Lighting will be multiplied by this.
+#define SHADOWER_DARKENING_COLOR "#00000033" // The above, but as an RGB string for lighting-less turfs.
+//Bay can't do multiplicative lighting for zmimic currently so we change alpha, this does mean full lit turfs need a different colour. TODO: Take another look at zmimic render setup
SUBSYSTEM_DEF(zcopy)
name = "Z-Copy"
@@ -181,6 +182,9 @@ SUBSYSTEM_DEF(zcopy)
T.z_generation += 1
T.z_queued -= 1
+ if (T.above)
+ T.above.update_mimic()
+
if (no_mc_tick)
CHECK_TICK
else if (MC_TICK_CHECK)
@@ -268,7 +272,7 @@ SUBSYSTEM_DEF(zcopy)
var/atom/movable/openspace/turf_mimic/DC = T.below.mimic_above_copy
DC.appearance = T.below
DC.mouse_opacity = initial(DC.mouse_opacity)
- DC.plane = OPENTURF_MAX_PLANE
+ DC.plane = OPENTURF_MAX_PLANE - turf_depth - 1
else if (T.below.mimic_above_copy)
QDEL_NULL(T.below.mimic_above_copy)
@@ -284,7 +288,7 @@ SUBSYSTEM_DEF(zcopy)
// Special case: these are merged into the shadower to reduce memory usage.
if (object.type == /atom/movable/lighting_overlay)
- //T.shadower.copy_lighting(object)
+ T.shadower.copy_lighting(object)
continue
if (!object.bound_overlay) // Generate a new overlay if the atom doesn't already have one.
@@ -297,6 +301,7 @@ SUBSYSTEM_DEF(zcopy)
var/have_performed_fixup = FALSE
switch (object.type)
+ // Layering for recursive mimic needs to be inherited.
if (/atom/movable/openspace/mimic)
var/atom/movable/openspace/mimic/OOO = object
original_type = OOO.mimiced_type
diff --git a/code/datums/observation/sight_set.dm b/code/datums/observation/sight_set.dm
index 664ad33b88c80..c2db96c382d3c 100644
--- a/code/datums/observation/sight_set.dm
+++ b/code/datums/observation/sight_set.dm
@@ -18,8 +18,14 @@ GLOBAL_DATUM_INIT(sight_set_event, /singleton/observ/sight_set, new)
* Sight Set Handling *
*********************/
+/mob/var/see_black = FALSE
+
/mob/proc/set_sight(new_sight)
var/old_sight = sight
+ if(see_black)
+ new_sight |= SEE_BLACKNESS // Avoids pixel bleed from atoms overlapping completely dark turfs, but conflicts with other flags.
+ else
+ new_sight &= ~SEE_BLACKNESS
if(old_sight != new_sight)
sight = new_sight
GLOB.sight_set_event.raise_event(src, old_sight, new_sight)
diff --git a/code/datums/proximity_trigger/proximity_trigger.dm b/code/datums/proximity_trigger/proximity_trigger.dm
index af67ffe616097..a4ffa41bbf795 100644
--- a/code/datums/proximity_trigger/proximity_trigger.dm
+++ b/code/datums/proximity_trigger/proximity_trigger.dm
@@ -140,9 +140,10 @@ var/global/const/PROXIMITY_EXCLUDE_HOLDER_TURF = 1 // When acquiring turfs to mo
if(!center)
return
- for(var/T in dview(range_, center))
- if(T in turfs_in_range)
+ FOR_DVIEW(var/T, range_, center, 0)
+ if (T in turfs_in_range) // This is awful, but I don't want to refactor this to be assoc.
. += T
+ END_FOR_DVIEW
/datum/proximity_trigger/proc/acquire_relevant_turfs()
. = turf_selection.get_turfs(holder, range_)
diff --git a/code/datums/security_state.dm b/code/datums/security_state.dm
index 08669a6280070..65e5bc8ddc909 100644
--- a/code/datums/security_state.dm
+++ b/code/datums/security_state.dm
@@ -141,9 +141,8 @@
var/alarm_level = "off"
// These values are primarily for station alarms and status displays, and which light colors and overlays to use
- var/light_max_bright = 0.5
- var/light_inner_range = 0.1
- var/light_outer_range = 1
+ var/light_range
+ var/light_power
var/light_color_alarm
var/light_color_status_display
@@ -205,9 +204,8 @@
/singleton/security_level/default/code_green
name = "code green"
- light_max_bright = 0.25
- light_inner_range = 0.1
- light_outer_range = 1
+ light_range = 2
+ light_power = 1
light_color_alarm = COLOR_GREEN
light_color_status_display = COLOR_GREEN
@@ -222,9 +220,8 @@
name = "code blue"
alarm_level = "on"
- light_max_bright = 0.5
- light_inner_range = 0.1
- light_outer_range = 2
+ light_range = 2
+ light_power = 1
light_color_alarm = COLOR_BLUE
light_color_status_display = COLOR_BLUE
@@ -241,9 +238,8 @@
name = "code red"
alarm_level = "on"
- light_max_bright = 0.5
- light_inner_range = 0.1
- light_outer_range = 2
+ light_range = 4
+ light_power = 2
light_color_alarm = COLOR_RED
light_color_status_display = COLOR_RED
@@ -260,9 +256,8 @@
name = "code delta"
alarm_level = "on"
- light_max_bright = 0.75
- light_inner_range = 0.1
- light_outer_range = 3
+ light_range = 4
+ light_power = 2
light_color_alarm = COLOR_RED
light_color_status_display = COLOR_NAVY_BLUE
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 5968e31fa6adc..12e0b991923bf 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -83,14 +83,14 @@
log_debug("Abstract atom [type] created!")
return INITIALIZE_HINT_QDEL
- if(light_max_bright && light_outer_range)
+ if(light_power && light_range)
update_light()
if(opacity)
updateVisibility(src)
var/turf/T = loc
if(istype(T))
- T.RecalculateOpacity()
+ T.recalc_atom_opacity()
if (health_max)
health_current = health_max
@@ -114,6 +114,7 @@
/atom/Destroy()
QDEL_NULL(reagents)
+ QDEL_NULL(light)
. = ..()
/**
@@ -422,6 +423,18 @@
return FALSE
dir = new_dir
GLOB.dir_set_event.raise_event(src, old_dir, dir)
+
+ //Lighting
+ if(light_source_solo)
+ if(light_source_solo.light_angle)
+ light_source_solo.source_atom.update_light()
+ else if(light_source_multi)
+ var/datum/light_source/L
+ for(var/thing in light_source_multi)
+ L = thing
+ if(L.light_angle)
+ L.source_atom.update_light()
+
return TRUE
/**
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 86cf810c8b69c..29c70a42c096c 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -217,8 +217,13 @@
updateVisibility(src)
// lighting
- if (light_sources) // Yes, I know you can for-null safely, but this is slightly faster. Hell knows why.
- for (var/datum/light_source/L in light_sources)
+ if (light_source_solo)
+ light_source_solo.source_atom.update_light()
+ else if (light_source_multi)
+ var/datum/light_source/L
+ var/thing
+ for (thing in light_source_multi)
+ L = thing
L.source_atom.update_light()
/atom/movable/Move(...)
@@ -233,8 +238,13 @@
updateVisibility(src)
// lighting
- if (light_sources) // Yes, I know you can for-null safely, this is slightly faster. Hell knows why.
- for (var/datum/light_source/L in light_sources)
+ if (light_source_solo)
+ light_source_solo.source_atom.update_light()
+ else if (light_source_multi)
+ var/datum/light_source/L
+ var/thing
+ for (thing in light_source_multi)
+ L = thing
L.source_atom.update_light()
//called when src is thrown into hit_atom
diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm
index 360a6bcd78296..cc96c3f22d01c 100644
--- a/code/game/gamemodes/cult/cult_structures.dm
+++ b/code/game/gamemodes/cult/cult_structures.dm
@@ -19,9 +19,8 @@
desc = "A floating crystal that hums with an unearthly energy."
icon = 'icons/obj/structures/pylon.dmi'
icon_state = "pylon"
- light_max_bright = 0.5
- light_inner_range = 1
- light_outer_range = 13
+ light_power = 0.5
+ light_range = 13
light_color = "#3e0000"
health_max = 20
health_min_damage = 4
@@ -73,7 +72,7 @@
var/spawnable = null
/obj/effect/gateway/active
- light_outer_range=5
+ light_range=5
light_color="#ff0000"
spawnable=list(
/mob/living/simple_animal/hostile/scarybat,
@@ -82,7 +81,6 @@
)
/obj/effect/gateway/active/cult
- light_outer_range=5
light_color="#ff0000"
spawnable=list(
/mob/living/simple_animal/hostile/scarybat/cult,
diff --git a/code/game/gamemodes/cult/narsie.dm b/code/game/gamemodes/cult/narsie.dm
index 9a84d4566df41..fa2bfb1c41fe7 100644
--- a/code/game/gamemodes/cult/narsie.dm
+++ b/code/game/gamemodes/cult/narsie.dm
@@ -32,7 +32,7 @@ var/global/list/narsie_list = list()
// Pixel stuff centers Narsie.
pixel_x = -236
pixel_y = -256
- light_outer_range = 1
+ light_range = 1
light_color = "#3e0000"
current_size = 6
@@ -154,7 +154,7 @@ var/global/list/narsie_list = list()
T.icon_state = "cult-narsie"
T.set_opacity(0)
T.set_density(0)
- set_light(1)
+ set_light(1, 1)
/obj/singularity/narsie/large/consume(atom/A) //Has its own consume proc because it doesn't need energy and I don't want BoHs to explode it. --NEO
//NEW BEHAVIOUR
diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm
index b5bd691adfb74..3d0589477743e 100644
--- a/code/game/machinery/alarm.dm
+++ b/code/game/machinery/alarm.dm
@@ -370,7 +370,7 @@
else if(dir == EAST)
pixel_x = -21
- set_light(0.25, 0.1, 1, 2, new_color)
+ set_light(2, 0.25, new_color)
/obj/machinery/alarm/receive_signal(datum/signal/signal)
if(!signal || signal.encryption)
@@ -964,12 +964,12 @@ FIRE ALARM
else
if(!detecting)
AddOverlays(get_cached_overlay("fire1"))
- set_light(0.25, 0.1, 1, 2, COLOR_RED)
+ set_light(2, 0.25, COLOR_RED)
else if(z in GLOB.using_map.contact_levels)
var/singleton/security_state/security_state = GET_SINGLETON(GLOB.using_map.security_state)
var/singleton/security_level/sl = security_state.current_security_level
- set_light(sl.light_max_bright, sl.light_inner_range, sl.light_outer_range, 2, sl.light_color_alarm)
+ set_light(sl.light_power, sl.light_range, sl.light_color_alarm)
AddOverlays(image(sl.icon, sl.overlay_alarm))
else
AddOverlays(get_cached_overlay("fire0"))
diff --git a/code/game/machinery/bluespace_drive.dm b/code/game/machinery/bluespace_drive.dm
index 1e84f0d607d94..c95f810717ead 100644
--- a/code/game/machinery/bluespace_drive.dm
+++ b/code/game/machinery/bluespace_drive.dm
@@ -38,7 +38,7 @@
. = ..()
drive_sound = GLOB.sound_player.PlayLoopingSound(src, "\ref[src]", 'sound/machines/BSD_idle.ogg', 50, 7)
AddParticles(/particles/torus/bluespace)
- set_light(1, 5, 15, 10, COLOR_CYAN)
+ set_light(15, 1, COLOR_CYAN)
update_icon()
diff --git a/code/game/machinery/computer/computer.dm b/code/game/machinery/computer/computer.dm
index f3251db715e88..6ac304b64da2c 100644
--- a/code/game/machinery/computer/computer.dm
+++ b/code/game/machinery/computer/computer.dm
@@ -17,9 +17,8 @@
var/icon_keyboard = "generic_key"
var/icon_screen = "generic"
- var/light_max_bright_on = 0.2
- var/light_inner_range_on = 0.1
- var/light_outer_range_on = 2
+ var/light_power_on = 1
+ var/light_range_on = 2
var/overlay_layer
atom_flags = ATOM_FLAG_NO_TEMP_CHANGE | ATOM_FLAG_CLIMBABLE
clicksound = "keyboard"
@@ -109,7 +108,7 @@
*/
/obj/machinery/computer/proc/update_glow()
if (operable())
- set_light(light_max_bright_on, light_inner_range_on, light_outer_range_on, 2, light_color)
+ set_light(light_range_on, light_power_on, light_color)
return TRUE
else
set_light(0)
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index cabbad0bbacc4..864a515e82ec2 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -705,21 +705,21 @@ About the new airlock wires panel:
if(AIRLOCK_CLOSED)
if(lights && locked)
new_overlays += overlay_image(bolts_file, plane = EFFECTS_ABOVE_LIGHTING_PLANE, layer = ABOVE_LIGHTING_LAYER)
- set_light(0.25, 0.1, 1, 2, COLOR_RED_LIGHT)
+ set_light(2, 0.75, COLOR_RED_LIGHT)
if(AIRLOCK_DENY)
if(lights)
new_overlays += overlay_image(deny_file, plane = EFFECTS_ABOVE_LIGHTING_PLANE, layer = ABOVE_LIGHTING_LAYER)
- set_light(0.25, 0.1, 1, 2, COLOR_RED_LIGHT)
+ set_light(2, 0.75, COLOR_RED_LIGHT)
if(AIRLOCK_EMAG)
new_overlays += overlay_image(emag_file, plane = EFFECTS_ABOVE_LIGHTING_PLANE, layer = ABOVE_LIGHTING_LAYER)
if(AIRLOCK_CLOSING)
if(lights)
new_overlays += overlay_image(lights_file, plane = EFFECTS_ABOVE_LIGHTING_PLANE, layer = ABOVE_LIGHTING_LAYER)
- set_light(0.25, 0.1, 1, 2, COLOR_LIME)
+ set_light(2, 0.75, COLOR_LIME)
if(AIRLOCK_OPENING)
if(lights)
new_overlays += overlay_image(lights_file, plane = EFFECTS_ABOVE_LIGHTING_PLANE, layer = ABOVE_LIGHTING_LAYER)
- set_light(0.25, 0.1, 1, 2, COLOR_LIME)
+ set_light(2, 0.75, COLOR_LIME)
if(MACHINE_IS_BROKEN(src))
new_overlays += overlay_image(sparks_broken_file, plane = EFFECTS_ABOVE_LIGHTING_PLANE, layer = ABOVE_LIGHTING_LAYER)
else if (get_damage_percentage() >= 25)
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index 7369ae505838d..d89a8e1b00d1a 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -521,7 +521,7 @@
weld_overlay = welded_file
if(do_set_light)
- set_light(0.25, 0.1, 1, 2, COLOR_SUN)
+ set_light(2, 0.25, COLOR_SUN)
AddOverlays(panel_overlay)
AddOverlays(weld_overlay)
diff --git a/code/game/machinery/doors/simple.dm b/code/game/machinery/doors/simple.dm
index d9064599205d8..196c9b9b19a65 100644
--- a/code/game/machinery/doors/simple.dm
+++ b/code/game/machinery/doors/simple.dm
@@ -29,7 +29,7 @@
if(locked)
lock = new(src,locked)
if(material.luminescence)
- set_light(0.5, 1, material.luminescence, l_color = material.icon_colour)
+ set_light(material.luminescence, 0.5, l_color = material.icon_colour)
if(material.opacity < 0.5)
glass = TRUE
diff --git a/code/game/machinery/floodlight.dm b/code/game/machinery/floodlight.dm
index 62a481a074d26..ce38f148a0522 100644
--- a/code/game/machinery/floodlight.dm
+++ b/code/game/machinery/floodlight.dm
@@ -17,9 +17,8 @@
machine_desc = "A portable, battery-powered LED flood lamp used to illuminate large areas."
//better laser, increased brightness & power consumption
- var/l_max_bright = 0.8 //brightness of light when on, can be negative
- var/l_inner_range = 0 //inner range of light when on, can be negative
- var/l_outer_range = 4.5 //outer range of light when on, can be negative
+ var/l_power = 2.5 //brightness of light when on, can be negative
+ var/l_range = 7 //outer range of light when on, can be negative
/obj/machinery/floodlight/on_update_icon()
icon_state = "flood[panel_open ? "o" : ""][panel_open && get_cell() ? "b" : ""]0[use_power == POWER_USE_ACTIVE]"
@@ -34,17 +33,17 @@
// If the cell is almost empty rarely "flicker" the light. Aesthetic only.
if(prob(30))
- set_light(l_max_bright / 2, l_inner_range, l_outer_range)
+ set_light(l_range, l_power / 2, angle = LIGHT_WIDE)
spawn(20)
if(use_power)
- set_light(l_max_bright, l_inner_range, l_outer_range)
+ set_light(l_range, l_power, angle = LIGHT_WIDE)
// Returns 0 on failure and 1 on success
/obj/machinery/floodlight/proc/turn_on(loud = 0)
if(!is_powered())
return 0
- set_light(l_max_bright, l_inner_range, l_outer_range)
+ set_light(l_range, l_power / 2, angle = LIGHT_WIDE)
update_use_power(POWER_USE_ACTIVE)
use_power_oneoff(active_power_usage)//so we drain cell if they keep trying to use it
update_icon()
@@ -77,9 +76,8 @@
/obj/machinery/floodlight/RefreshParts()//if they're insane enough to modify a floodlight, let them
..()
var/light_mod = clamp(total_component_rating_of_type(/obj/item/stock_parts/capacitor), 0, 10)
- l_max_bright = light_mod? light_mod*0.01 + initial(l_max_bright) : initial(l_max_bright)/2 //gives us between 0.8-0.9 with capacitor, or 0.4 without one
- l_inner_range = light_mod + initial(l_inner_range)
- l_outer_range = light_mod*1.5 + initial(l_outer_range)
+ l_power = light_mod? light_mod*0.01 + initial(l_power) : initial(l_power)/2 //gives us between 0.8-0.9 with capacitor, or 0.4 without one
+ l_range = light_mod*1.5 + initial(l_range)
change_power_consumption(initial(active_power_usage) * light_mod , POWER_USE_ACTIVE)
if(use_power)
- set_light(l_max_bright, l_inner_range, l_outer_range)
+ set_light(l_range, l_power, angle = LIGHT_WIDE)
diff --git a/code/game/machinery/floor_light.dm b/code/game/machinery/floor_light.dm
index e058d23f3e911..81939b750a7c6 100644
--- a/code/game/machinery/floor_light.dm
+++ b/code/game/machinery/floor_light.dm
@@ -14,9 +14,8 @@ var/global/list/floor_light_cache = list()
matter = list(MATERIAL_STEEL = 250, MATERIAL_GLASS = 250)
var/damaged
- var/default_light_max_bright = 0.75
- var/default_light_inner_range = 1
- var/default_light_outer_range = 3
+ var/default_light_power = 0.75
+ var/default_light_range = 3
var/default_light_colour = "#ffffff"
@@ -105,11 +104,11 @@ var/global/list/floor_light_cache = list()
/obj/machinery/floor_light/proc/update_brightness()
if((use_power == POWER_USE_ACTIVE) && operable())
- if(light_outer_range != default_light_outer_range || light_max_bright != default_light_max_bright || light_color != default_light_colour)
- set_light(default_light_max_bright, default_light_inner_range, default_light_outer_range, l_color = default_light_colour)
- change_power_consumption((light_outer_range + light_max_bright) * 20, POWER_USE_ACTIVE)
+ if(light_range != default_light_range || light_power != default_light_power || light_color != default_light_colour)
+ set_light(default_light_range, default_light_power, default_light_colour)
+ change_power_consumption((light_range + light_power) * 20, POWER_USE_ACTIVE)
else
- if(light_outer_range || light_max_bright)
+ if(light_range || light_power)
set_light(0)
/obj/machinery/floor_light/on_update_icon()
diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm
index 8e5cc24e5b90c..5f4d088ed140c 100644
--- a/code/game/machinery/hologram.dm
+++ b/code/game/machinery/hologram.dm
@@ -356,9 +356,9 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
hologram.SetName("[A.name] (Hologram)") //If someone decides to right click.
A.holo = src
masters[A] = hologram
- hologram.set_light(1, 0.1, 2) //hologram lighting
+ hologram.set_light(2, 0.1) //hologram lighting
hologram.color = color //painted holopad gives coloured holograms
- set_light(1, 0.1, 2) //pad lighting
+ set_light(2, 0.1) //pad lighting
icon_state = "[base_icon]1"
return 1
diff --git a/code/game/machinery/holosign.dm b/code/game/machinery/holosign.dm
index 2a6c00e93ec22..a0920d6fab780 100644
--- a/code/game/machinery/holosign.dm
+++ b/code/game/machinery/holosign.dm
@@ -36,7 +36,7 @@
set_light(0)
else
icon_state = on_icon
- set_light(0.5, 0.5, 1, l_color = COLOR_CYAN_BLUE)
+ set_light(1, 0.5, COLOR_CYAN_BLUE)
/singleton/public_access/public_variable/holosign_on
expected_type = /obj/machinery/holosign
diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm
index c121ac2efc611..0e48e0a77e2a2 100644
--- a/code/game/machinery/lightswitch.dm
+++ b/code/game/machinery/lightswitch.dm
@@ -41,7 +41,7 @@
icon_state = "light[on]"
overlay.icon_state = "light[on]-overlay"
AddOverlays(overlay)
- set_light(0.1, 0.1, 1, 2, on ? "#82ff4c" : "#f86060")
+ set_light(2, 0.25, on ? "#82ff4c" : "#f86060")
/obj/machinery/light_switch/examine(mob/user, distance)
. = ..()
diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm
index 889addcde312d..62c5f3cb24e9a 100644
--- a/code/game/machinery/newscaster.dm
+++ b/code/game/machinery/newscaster.dm
@@ -153,7 +153,7 @@ var/global/list/obj/machinery/newscaster/allCasters = list() //Global list that
var/hitstaken = 0 //Death at 3 hits from an item with force>=15
var/datum/feed_channel/viewing_channel = null
var/datum/feed_network/connected_group
- light_outer_range = 0
+ light_range = 0
anchored = TRUE
layer = ABOVE_WINDOW_LAYER
diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm
index 7d0c25c78b187..36f789cc7fa83 100644
--- a/code/game/machinery/requests_console.dm
+++ b/code/game/machinery/requests_console.dm
@@ -50,7 +50,7 @@ var/global/list/obj/machinery/requests_console/allConsoles = list()
var/message = "";
var/recipient = ""; //the department which will be receiving the message
var/priority = -1 ; //Priority of the message being sent
- light_outer_range = 0
+ light_range = 0
var/datum/announcement/announcement = new
/obj/machinery/requests_console/on_update_icon()
diff --git a/code/game/machinery/rotating_alarm.dm b/code/game/machinery/rotating_alarm.dm
index 4dc1650968955..5d9e7b20a067e 100644
--- a/code/game/machinery/rotating_alarm.dm
+++ b/code/game/machinery/rotating_alarm.dm
@@ -96,7 +96,7 @@
/obj/machinery/rotating_alarm/proc/set_on()
vis_contents += spin_effect
- set_light(1, 0.5, 2, 0.3, alarm_light_color)
+ set_light(2, 0.5, alarm_light_color)
on = TRUE
low_alarm = FALSE
diff --git a/code/game/machinery/spaceheater.dm b/code/game/machinery/spaceheater.dm
index 4c9e6641b6bc5..71107f83a46b4 100644
--- a/code/game/machinery/spaceheater.dm
+++ b/code/game/machinery/spaceheater.dm
@@ -14,6 +14,8 @@
atom_flags = ATOM_FLAG_NO_TEMP_CHANGE | ATOM_FLAG_CLIMBABLE
clicksound = "switch"
+ light_power = 0.5
+
/obj/machinery/space_heater/New()
..()
@@ -26,10 +28,10 @@
set_light(0)
else if(active > 0)
icon_state = "sheater-heat"
- set_light(0.7, 1, 2, 3, COLOR_SEDONA)
+ set_light(3, COLOR_SEDONA)
else if(active < 0)
icon_state = "sheater-cool"
- set_light(0.7, 1, 2, 3, COLOR_DEEP_SKY_BLUE)
+ set_light(3, l_color = COLOR_DEEP_SKY_BLUE)
else
icon_state = "sheater-standby"
set_light(0)
diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm
index dd86c05886db2..c909f8d85fa88 100644
--- a/code/game/machinery/status_display.dm
+++ b/code/game/machinery/status_display.dm
@@ -191,7 +191,7 @@
var/image/alert = overlay_image(sl.icon, sl.overlay_status_display, plane = EFFECTS_ABOVE_LIGHTING_PLANE, layer = ABOVE_LIGHTING_LAYER)
- set_light(sl.light_max_bright, sl.light_inner_range, sl.light_outer_range, 2, sl.light_color_alarm)
+ set_light(sl.light_range, sl.light_power, sl.light_color_alarm)
AddOverlays(alert)
/obj/machinery/status_display/proc/set_picture(state)
@@ -200,13 +200,13 @@
picture_state = state
picture = image('icons/obj/machines/status_display.dmi', icon_state=picture_state)
AddOverlays(picture)
- set_light(0.5, 0.1, 1, 2, COLOR_WHITE)
+ set_light(2, 0.5, COLOR_WHITE)
/obj/machinery/status_display/proc/update_display(line1, line2)
var/new_text = {"
[line1]
[line2]
"}
if(maptext != new_text)
maptext = new_text
- set_light(0.5, 0.1, 1, 2, COLOR_WHITE)
+ set_light(2, 0.5, COLOR_WHITE)
/obj/machinery/status_display/proc/get_shuttle_timer()
var/timeleft = evacuation_controller.get_eta()
diff --git a/code/game/machinery/supplybeacon.dm b/code/game/machinery/supplybeacon.dm
index b4e90fdac91cc..806d04e3e4e3e 100644
--- a/code/game/machinery/supplybeacon.dm
+++ b/code/game/machinery/supplybeacon.dm
@@ -76,7 +76,7 @@
if(surplus() < 500)
if(user) to_chat(user, SPAN_NOTICE("The connected wire doesn't have enough current."))
return
- set_light(1, 0.5, 2, 2, "#00ccaa")
+ set_light(3, 3, "#00ccaa")
icon_state = "beacon_active"
update_use_power(POWER_USE_IDLE)
admin_attacker_log(user, "has activated \a [src] at [get_area(src)]")
diff --git a/code/game/machinery/teleporter/pad.dm b/code/game/machinery/teleporter/pad.dm
index c8044cbe88ec9..27ea156fc52df 100644
--- a/code/game/machinery/teleporter/pad.dm
+++ b/code/game/machinery/teleporter/pad.dm
@@ -81,7 +81,7 @@
I.plane = EFFECTS_ABOVE_LIGHTING_PLANE
I.layer = ABOVE_LIGHTING_LAYER
AddOverlays(I)
- set_light(0.4, 1.2, 4, 10)
+ set_light(4, 0.4)
if (interference && prob(20))
visible_message(SPAN_WARNING("The teleporter sparks ominously!"))
diff --git a/code/game/machinery/turret_control.dm b/code/game/machinery/turret_control.dm
index f703976b19e7c..bdaeadccc00ad 100644
--- a/code/game/machinery/turret_control.dm
+++ b/code/game/machinery/turret_control.dm
@@ -199,13 +199,13 @@
else if (enabled)
if (lethal)
icon_state = "control_kill"
- set_light(1, 0.5, 2, 2, "#990000")
+ set_light(1.5, 1,"#990000")
else
icon_state = "control_stun"
- set_light(1, 0.5, 2, 2, "#ff9900")
+ set_light(1.5, 1,"#ff9900")
else
icon_state = "control_standby"
- set_light(1, 0.5, 2, 2, "#003300")
+ set_light(1.5, 1,"#003300")
/obj/machinery/turretid/emp_act(severity)
if(enabled)
diff --git a/code/game/machinery/vending/_vending.dm b/code/game/machinery/vending/_vending.dm
index 77a8554ebda67..295352e7335aa 100644
--- a/code/game/machinery/vending/_vending.dm
+++ b/code/game/machinery/vending/_vending.dm
@@ -91,7 +91,6 @@
var/scan_id = TRUE
var/obj/item/material/coin/coin
var/light_max_bright_on = 0.2
- var/light_inner_range_on = 1
var/light_outer_range_on = 2
@@ -148,7 +147,7 @@
if (!is_powered() || MACHINE_IS_BROKEN(src))
set_light(0)
else
- set_light(light_max_bright_on, light_inner_range_on, light_outer_range_on, 2, light_color)
+ set_light(light_outer_range_on, light_max_bright_on, light_color)
/obj/machinery/vending/on_update_icon()
diff --git a/code/game/objects/auras/radiant_aura.dm b/code/game/objects/auras/radiant_aura.dm
index fb62c8646758d..9b5a4a68a68b9 100644
--- a/code/game/objects/auras/radiant_aura.dm
+++ b/code/game/objects/auras/radiant_aura.dm
@@ -7,7 +7,7 @@
/obj/aura/radiant_aura/added_to(mob/living/L)
..()
to_chat(L,SPAN_NOTICE("A bubble of light appears around you, exuding protection and warmth."))
- set_light(0.6, 1, 6, 2, "#e09d37")
+ set_light(6, 6, "#e09d37")
/obj/aura/radiant_aura/removed()
to_chat(user, SPAN_WARNING("Your protective aura dissipates, leaving you feeling cold and unsafe."))
diff --git a/code/game/objects/effects/decals/Cleanable/misc.dm b/code/game/objects/effects/decals/Cleanable/misc.dm
index 33c4dc193b1c7..6e8b2043b2bba 100644
--- a/code/game/objects/effects/decals/Cleanable/misc.dm
+++ b/code/game/objects/effects/decals/Cleanable/misc.dm
@@ -44,7 +44,7 @@
name = "glowing goo"
desc = "Jeez. I hope that's not for lunch."
gender = PLURAL
- light_outer_range = 1
+ light_range = 1
icon = 'icons/effects/effects.dmi'
icon_state = "greenglow"
persistent = TRUE
diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm
index b74dc451a7e9c..6ab22462b7b18 100644
--- a/code/game/objects/effects/effect_system.dm
+++ b/code/game/objects/effects/effect_system.dm
@@ -240,7 +240,7 @@ would spawn and follow the beaker, even if it is carried or thrown.
icon_state = "sparks"
/obj/effect/effect/smoke/illumination/New(newloc, lifetime=10, range=null, power=null, color=null)
- set_light(power, 0.1, range, 2, color)
+ set_light(range, power, color)
time_to_live=lifetime
..()
diff --git a/code/game/objects/effects/fake_fire.dm b/code/game/objects/effects/fake_fire.dm
index ad6f2ded48d57..4dfabc3bdd78a 100644
--- a/code/game/objects/effects/fake_fire.dm
+++ b/code/game/objects/effects/fake_fire.dm
@@ -11,7 +11,7 @@
/obj/effect/fake_fire/New()
..()
- set_light(0.5, 1, 3, l_color = color)
+ set_light(3, 0.5, color)
START_PROCESSING(SSobj,src)
if(lifetime)
QDEL_IN(src,lifetime)
diff --git a/code/game/objects/effects/fire/fire.dm b/code/game/objects/effects/fire/fire.dm
index da455a9545c3e..1e5511b4fc228 100644
--- a/code/game/objects/effects/fire/fire.dm
+++ b/code/game/objects/effects/fire/fire.dm
@@ -197,16 +197,16 @@
switch(current_fire_state)
if(TURF_FIRE_STATE_SMALL)
icon_state = "small"
- set_light(0.5, 1, 1.5)
+ set_light(1.5, 0.5)
if(TURF_FIRE_STATE_MEDIUM)
icon_state = "medium"
- set_light(0.5, 1, 2,)
+ set_light(2, 0.5)
if(TURF_FIRE_STATE_LARGE)
icon_state = "big"
- set_light(0.5, 1.5, 2,)
+ set_light(2, 0.6)
if(TURF_FIRE_STATE_MAX)
icon_state = "max"
- set_light(0.7, 1.6, 3)
+ set_light(3, 0.7)
#undef TURF_FIRE_REQUIRED_TEMP
#undef TURF_FIRE_TEMP_BASE
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 1ec7f3cb3394f..b87544ada4656 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -19,11 +19,13 @@
action_button_name = "Toggle Flashlight"
var/on = FALSE
var/activation_sound = 'sound/effects/flashlight.ogg'
- var/flashlight_max_bright = 0.5 //brightness of light when on, must be no greater than 1.
- var/flashlight_inner_range = 1 //inner range of light when on, can be negative
- var/flashlight_outer_range = 3 //outer range of light when on, can be negative
+ var/flashlight_power = 1 //brightness of light when on
+ var/flashlight_range = 4 //outer range of light when on, can be negative
+ light_wedge = LIGHT_VERY_WIDE
var/flashlight_flags = EMPTY_BITFIELD // FLASHLIGHT_ bitflags
+ var/spawn_dir // a way for mappers to force which way a flashlight faces upon spawning
+
/obj/item/device/flashlight/Initialize()
. = ..()
@@ -61,11 +63,34 @@
return 1
/obj/item/device/flashlight/proc/set_flashlight()
+ if(light_wedge)
+ set_dir(pick(NORTH, SOUTH, EAST, WEST))
+ if(spawn_dir)
+ set_dir(spawn_dir)
+
if (on)
- set_light(flashlight_max_bright, flashlight_inner_range, flashlight_outer_range, 2, light_color)
+ set_light(flashlight_range, flashlight_power, light_color)
else
set_light(0)
+/obj/item/device/flashlight/examine(mob/user, distance)
+ . = ..()
+ if(light_wedge && isturf(loc))
+ to_chat(user, FONT_SMALL(SPAN_NOTICE("\The [src] is facing [dir2text(dir)].")))
+
+/obj/item/device/flashlight/dropped(mob/user)
+ . = ..()
+ if(light_wedge)
+ set_dir(user.dir)
+ update_light()
+
+/obj/item/device/flashlight/throw_at()
+ . = ..()
+ if(light_wedge)
+ var/new_dir = pick(NORTH, SOUTH, EAST, WEST)
+ set_dir(new_dir)
+ update_light()
+
/obj/item/device/flashlight/attack(mob/living/M as mob, mob/living/user as mob)
. = FALSE
if (istype(M) && on && user.zone_sel.selecting == BP_EYES)
@@ -138,8 +163,8 @@
desc = "An energy efficient flashlight."
icon_state = "biglight"
item_state = "biglight"
- flashlight_max_bright = 0.75
- flashlight_outer_range = 4
+ flashlight_power = 3
+ flashlight_range = 6
/obj/item/device/flashlight/flashdark
name = "flashdark"
@@ -147,9 +172,8 @@
icon_state = "flashdark"
item_state = "flashdark"
w_class = ITEM_SIZE_NORMAL
- flashlight_max_bright = -1
- flashlight_outer_range = 4
- flashlight_inner_range = 1
+ flashlight_power = -6
+ flashlight_range = 6
flashlight_flags = FLASHLIGHT_CANNOT_BLIND
/obj/item/device/flashlight/pen
@@ -160,9 +184,8 @@
obj_flags = OBJ_FLAG_CONDUCTIBLE
slot_flags = SLOT_EARS
w_class = ITEM_SIZE_TINY
- flashlight_max_bright = 0.25
- flashlight_inner_range = 0.1
- flashlight_outer_range = 2
+ light_wedge = LIGHT_OMNI
+ flashlight_range = 2
/obj/item/device/flashlight/maglight
name = "maglight"
@@ -174,8 +197,8 @@
attack_verb = list ("smacked", "thwacked", "thunked")
matter = list(MATERIAL_ALUMINIUM = 200, MATERIAL_GLASS = 50)
hitsound = "swing_hit"
- flashlight_max_bright = 0.5
- flashlight_outer_range = 5
+ light_wedge = LIGHT_NARROW
+ flashlight_range = 8
/******************************Lantern*******************************/
/obj/item/device/flashlight/lantern
@@ -189,7 +212,10 @@
obj_flags = OBJ_FLAG_CONDUCTIBLE
slot_flags = SLOT_BELT
matter = list(MATERIAL_STEEL = 200,MATERIAL_GLASS = 100)
- flashlight_outer_range = 5
+ flashlight_range = 3
+ light_wedge = LIGHT_OMNI
+ light_color = COLOR_ORANGE
+ flashlight_power = 1
/obj/item/device/flashlight/lantern/on_update_icon()
..()
@@ -207,9 +233,7 @@
item_state = ""
obj_flags = OBJ_FLAG_CONDUCTIBLE
w_class = ITEM_SIZE_TINY
- flashlight_max_bright = 0.25
- flashlight_inner_range = 0.1
- flashlight_outer_range = 2
+ flashlight_range = 2
flashlight_flags = FLASHLIGHT_CANNOT_BLIND
@@ -222,9 +246,8 @@
item_state = "lamp"
w_class = ITEM_SIZE_LARGE
obj_flags = OBJ_FLAG_CONDUCTIBLE
- flashlight_max_bright = 0.3
- flashlight_inner_range = 2
- flashlight_outer_range = 5
+ flashlight_range = 5
+ light_wedge = LIGHT_OMNI
on = 1
@@ -260,9 +283,9 @@
activation_sound = 'sound/effects/flare.ogg'
flashlight_flags = FLASHLIGHT_SINGLE_USE
- flashlight_max_bright = 0.8
- flashlight_inner_range = 0.1
- flashlight_outer_range = 5
+ flashlight_power = 3
+ flashlight_range = 5
+ light_wedge = LIGHT_OMNI
/obj/item/device/flashlight/flare/Initialize()
. = ..()
@@ -336,9 +359,9 @@
produce_heat = 0
activation_sound = 'sound/effects/glowstick.ogg'
- flashlight_max_bright = 0.6
- flashlight_inner_range = 0.1
- flashlight_outer_range = 3
+ flashlight_range = 4
+ flashlight_power = 1.5
+ light_wedge = LIGHT_OMNI
flashlight_flags = FLASHLIGHT_SINGLE_USE | FLASHLIGHT_CANNOT_BLIND
@@ -407,9 +430,9 @@
on = TRUE //Bio-luminesence has one setting, on.
flashlight_flags = FLASHLIGHT_ALWAYS_ON | FLASHLIGHT_CANNOT_BLIND
- flashlight_max_bright = 1
- flashlight_inner_range = 0.1
- flashlight_outer_range = 5
+ flashlight_power = 0.8
+ flashlight_range = 5
+ light_wedge = LIGHT_OMNI
/obj/item/device/flashlight/slime/New()
..()
@@ -426,9 +449,9 @@
w_class = ITEM_SIZE_LARGE
obj_flags = OBJ_FLAG_CONDUCTIBLE | OBJ_FLAG_ROTATABLE
- flashlight_max_bright = 0.8
- flashlight_inner_range = 1
- flashlight_outer_range = 5
+ flashlight_power = 1
+ flashlight_range = 7
+ light_wedge = LIGHT_WIDE
/obj/item/device/flashlight/lamp/floodlamp/green
icon_state = "greenfloodlamp"
@@ -440,7 +463,7 @@
icon_state = "lavalamp"
on = 0
action_button_name = "Toggle lamp"
- flashlight_outer_range = 3 //range of light when on
+ flashlight_range = 3 //range of light when on
matter = list(MATERIAL_ALUMINIUM = 250, MATERIAL_GLASS = 200)
flashlight_flags = FLASHLIGHT_CANNOT_BLIND
diff --git a/code/game/objects/items/devices/oxycandle.dm b/code/game/objects/items/devices/oxycandle.dm
index 72705d5b6373a..174df6eca7708 100644
--- a/code/game/objects/items/devices/oxycandle.dm
+++ b/code/game/objects/items/devices/oxycandle.dm
@@ -11,8 +11,8 @@
var/on = 0
var/activation_sound = 'sound/effects/flare.ogg'
light_color = "#e58775"
- light_outer_range = 2
- light_max_bright = 1
+ light_range = 2
+ light_power = 1
var/brightness_on = 1 // Moderate-low bright.
action_button_name = null
diff --git a/code/game/objects/items/devices/powersink.dm b/code/game/objects/items/devices/powersink.dm
index ba96d722b9f4e..2afe8bf32716a 100644
--- a/code/game/objects/items/devices/powersink.dm
+++ b/code/game/objects/items/devices/powersink.dm
@@ -131,7 +131,7 @@
var/datum/powernet/PN = attached.powernet
var/drained = 0
if(PN)
- set_light(0.5, 0.1, 12)
+ set_light(12)
PN.trigger_warning()
// found a powernet, so drain up to max power from it
drained = PN.draw_power(drain_rate)
diff --git a/code/game/objects/items/devices/slide_projector.dm b/code/game/objects/items/devices/slide_projector.dm
index ab69838fd8e1d..9191355377619 100644
--- a/code/game/objects/items/devices/slide_projector.dm
+++ b/code/game/objects/items/devices/slide_projector.dm
@@ -93,7 +93,7 @@
projection = new projection_type(target)
projection.set_source(current_slide)
GLOB.moved_event.register(src, src, .proc/check_projections)
- set_light(0.1, 0.1, 1, 2, COLOR_WHITE) //Bit of light
+ set_light(1, 0.1, COLOR_WHITE) //Bit of light
update_icon()
/obj/item/storage/slide_projector/attack_self(mob/user)
@@ -155,7 +155,7 @@
/obj/effect/projection/Initialize()
. = ..()
- set_light(0.1, 0.1, 1, 2, COLOR_WHITE) //Makes turning off the lights not invalidate projection
+ set_light(1, 0.1, COLOR_WHITE) //Makes turning off the lights not invalidate projection
/obj/effect/projection/on_update_icon()
filters = filter(type="drop_shadow", color = COLOR_WHITE, size = 4, offset = 1,x = 0, y = 0)
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index c68602b4db467..9b64e41714689 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -889,7 +889,7 @@
attack_verb = list("attacked", "whacked", "jabbed", "poked", "marshalled")
/obj/item/marshalling_wand/Initialize()
- set_light(0.6, 0.5, 2, 2, "#ff0000")
+ set_light(1.5, 1.5, "#ff0000")
return ..()
/obj/item/marshalling_wand/attack_self(mob/living/user as mob)
diff --git a/code/game/objects/items/weapons/candle/candle.dm b/code/game/objects/items/weapons/candle/candle.dm
index d7f1c22b76b41..4b83b86b5e982 100644
--- a/code/game/objects/items/weapons/candle/candle.dm
+++ b/code/game/objects/items/weapons/candle/candle.dm
@@ -11,10 +11,8 @@
var/wax
var/last_lit
var/icon_set = "candle"
- var/candle_max_bright = 0.3
- var/candle_inner_range = 0.1
- var/candle_outer_range = 4
- var/candle_falloff = 2
+ var/candle_range = CANDLE_LUM
+ var/candle_power
/obj/item/flame/candle/Initialize()
wax = rand(27 MINUTES, 33 MINUTES) / SSobj.wait // Enough for 27-33 minutes. 30 minutes on average, adjusted for subsystem tickrate.
@@ -52,7 +50,7 @@
if(!lit)
lit = 1
visible_message(SPAN_NOTICE("\The [user] lights the [name]."))
- set_light(candle_max_bright, candle_inner_range, candle_outer_range, candle_falloff)
+ set_light(candle_range, candle_power)
START_PROCESSING(SSobj, src)
/obj/item/flame/candle/Process()
diff --git a/code/game/objects/items/weapons/candle/incense.dm b/code/game/objects/items/weapons/candle/incense.dm
index 6c7246377cbd1..75eb59076e8af 100644
--- a/code/game/objects/items/weapons/candle/incense.dm
+++ b/code/game/objects/items/weapons/candle/incense.dm
@@ -5,10 +5,7 @@
available_colours = null
icon_set = "incense"
- candle_max_bright = 0.1
- candle_inner_range = 0.1
- candle_outer_range = 1
- candle_falloff = 2
+ candle_range = 1
scent_types = list(/singleton/scent_type/rose,
/singleton/scent_type/citrus,
diff --git a/code/game/objects/items/weapons/ecigs.dm b/code/game/objects/items/weapons/ecigs.dm
index dd47f843c66ea..b44380dd2d7c1 100644
--- a/code/game/objects/items/weapons/ecigs.dm
+++ b/code/game/objects/items/weapons/ecigs.dm
@@ -131,7 +131,7 @@
if (active)
item_state = icon_on
icon_state = icon_on
- set_light(0.6, 0.5, brightness_on)
+ set_light(brightness_on)
else if (ec_cartridge)
set_light(0)
item_state = icon_off
diff --git a/code/game/objects/items/weapons/flamethrower.dm b/code/game/objects/items/weapons/flamethrower.dm
index e93d54d22dff2..f636b6dd3a8ae 100644
--- a/code/game/objects/items/weapons/flamethrower.dm
+++ b/code/game/objects/items/weapons/flamethrower.dm
@@ -140,7 +140,7 @@
playsound(loc, 'sound/items/welderdeactivate.ogg', 50, TRUE)
STOP_PROCESSING(SSobj,src)
if(lit)
- set_light(0.7, 1, 2.5, l_color = COLOR_ORANGE)
+ set_light(2.5, 0.7, l_color = COLOR_ORANGE)
else
set_light(0)
diff --git a/code/game/objects/items/weapons/grenades/anti_photon_grenade.dm b/code/game/objects/items/weapons/grenades/anti_photon_grenade.dm
index d428534046f34..dc385553ee0d2 100644
--- a/code/game/objects/items/weapons/grenades/anti_photon_grenade.dm
+++ b/code/game/objects/items/weapons/grenades/anti_photon_grenade.dm
@@ -8,11 +8,11 @@
/obj/item/grenade/anti_photon/detonate(mob/living/user)
playsound(src.loc, 'sound/effects/phasein.ogg', 50, 1, 5)
- set_light(-1, 6, 10, 2, "#ffffff")
+ set_light(10, -10, "#ffffff")
addtimer(new Callback(src, .proc/finish), rand(20 SECONDS, 29 SECONDS))
/obj/item/grenade/anti_photon/proc/finish()
- set_light(1, 1, 10, 2, "#[num2hex(rand(64,255))][num2hex(rand(64,255))][num2hex(rand(64,255))]")
+ set_light(10, 10, "#[num2hex(rand(64,255))][num2hex(rand(64,255))][num2hex(rand(64,255))]")
playsound(loc, 'sound/effects/bang.ogg', 50, 1, 5)
sleep(1 SECOND)
qdel(src)
diff --git a/code/game/objects/items/weapons/grenades/light.dm b/code/game/objects/items/weapons/grenades/light.dm
index f6c947468e1a1..08b8568ab62b4 100644
--- a/code/game/objects/items/weapons/grenades/light.dm
+++ b/code/game/objects/items/weapons/grenades/light.dm
@@ -12,5 +12,5 @@
playsound(src, 'sound/effects/snap.ogg', 80, 1)
audible_message(SPAN_WARNING("\The [src] detonates with a sharp crack!"))
- set_light(1, 1, 12, 2, light_colour)
+ set_light(12, 1, light_colour)
QDEL_IN(src, lifetime)
diff --git a/code/game/objects/items/weapons/lighter.dm b/code/game/objects/items/weapons/lighter.dm
index ed78d113f49a1..8d2d56bc003c3 100644
--- a/code/game/objects/items/weapons/lighter.dm
+++ b/code/game/objects/items/weapons/lighter.dm
@@ -30,7 +30,7 @@
lit = 1
update_icon()
light_effects(user)
- set_light(0.6, 0.5, 2, l_color = COLOR_PALE_ORANGE)
+ set_light(2, l_color = COLOR_PALE_ORANGE)
START_PROCESSING(SSobj, src)
/obj/item/flame/lighter/proc/light_effects(mob/living/carbon/user)
@@ -95,7 +95,7 @@
if(ismob(loc) && prob(10) && reagents.get_reagent_amount(/datum/reagent/fuel) < 1)
to_chat(loc, SPAN_WARNING("\The [src]'s flame flickers."))
set_light(0)
- addtimer(new Callback(src, .atom/proc/set_light, 0.6, 0.5, 2), 4)
+ addtimer(new Callback(src, .atom/proc/set_light, 2), 4)
reagents.remove_reagent(/datum/reagent/fuel, 0.05)
else
extinguish()
diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm
index 5c37dc6390848..c5e640901247a 100644
--- a/code/game/objects/items/weapons/melee/energy.dm
+++ b/code/game/objects/items/weapons/melee/energy.dm
@@ -56,7 +56,7 @@
if (user)
playsound(user, 'sound/weapons/saberon.ogg', 50, 1)
to_chat(user, SPAN_NOTICE("\The [src] is now energised."))
- set_light(0.8, 1, 2, 4, lighting_color)
+ set_light(2, 0.8, lighting_color)
if (istype(user,/mob/living/carbon/human))
var/mob/living/carbon/human/H = user
diff --git a/code/game/objects/items/weapons/shields.dm b/code/game/objects/items/weapons/shields.dm
index 5e12c02dd694b..659b31bc11eaf 100644
--- a/code/game/objects/items/weapons/shields.dm
+++ b/code/game/objects/items/weapons/shields.dm
@@ -185,7 +185,7 @@
/obj/item/shield/energy/on_update_icon()
icon_state = "eshield[active]"
if (active)
- set_light(0.6, 0.1, 2, 1, "#006aff")
+ set_light(1.5, 1.5, "#006aff")
else
set_light(0)
diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm
index 9dd976773a32a..7e688821c1e8a 100644
--- a/code/game/objects/items/weapons/stunbaton.dm
+++ b/code/game/objects/items/weapons/stunbaton.dm
@@ -63,7 +63,7 @@
icon_state = "[initial(name)]"
if(icon_state == "[initial(name)]_active")
- set_light(0.4, 0.1, 1, 2, "#ff6a00")
+ set_light(1.5, 2, "#ff6a00")
else
set_light(0)
@@ -241,7 +241,7 @@
/obj/item/melee/baton/robot/electrified_arm/on_update_icon()
if(status)
icon_state = "electrified_arm_active"
- set_light(0.4, 0.1, 1, 2, "#006aff")
+ set_light(1.5, 2, "#006aff")
else
icon_state = "electrified_arm"
set_light(0)
diff --git a/code/game/objects/items/weapons/tools/weldingtool.dm b/code/game/objects/items/weapons/tools/weldingtool.dm
index 48a66a1674c5e..4e3d3d0f39ef7 100644
--- a/code/game/objects/items/weapons/tools/weldingtool.dm
+++ b/code/game/objects/items/weapons/tools/weldingtool.dm
@@ -179,7 +179,7 @@
burn_fuel(amount)
if(M)
M.welding_eyecheck()//located in mob_helpers.dm
- set_light(0.7, 2, 5, l_color = COLOR_LIGHT_CYAN)
+ set_light(5, 0.7, COLOR_LIGHT_CYAN)
addtimer(new Callback(src, /atom/proc/update_icon), 5)
return 1
@@ -222,7 +222,7 @@
AddOverlays(image('icons/obj/tools/welder.dmi', "welder_[tank.icon_state]"))
if(welding)
AddOverlays(image('icons/obj/tools/welder.dmi', "welder_on"))
- set_light(0.6, 0.5, 2.5, l_color =COLOR_PALE_ORANGE)
+ set_light(2.5, 0.6, l_color =COLOR_PALE_ORANGE)
else
set_light(0)
item_state = welding ? "welder1" : "welder"
diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm
index 1c9484231b39c..919f6b743e8e1 100644
--- a/code/game/objects/structures/flora.dm
+++ b/code/game/objects/structures/flora.dm
@@ -244,7 +244,7 @@
/obj/structure/flora/pottedplant/unusual/Initialize()
. = ..()
- set_light(0.4, 0.1, 2, 2, "#007fff")
+ set_light(2, 0.4, "#007fff")
/obj/structure/flora/pottedplant/orientaltree
name = "potted oriental tree"
@@ -298,7 +298,7 @@
/obj/structure/flora/pottedplant/subterranean/Initialize()
. = ..()
- set_light(0.4, 0.1, 2, 2, "#ff6633")
+ set_light(2, 0.4, "#ff6633")
/obj/structure/flora/pottedplant/minitree
name = "potted tree"
diff --git a/code/game/objects/structures/fountain.dm b/code/game/objects/structures/fountain.dm
index 9e6285f326c7a..69df5092771b0 100644
--- a/code/game/objects/structures/fountain.dm
+++ b/code/game/objects/structures/fountain.dm
@@ -19,7 +19,7 @@
/obj/structure/fountain/strange/Initialize()
. = ..()
light_color = get_random_colour(lower = 190)
- set_light(0.6, 3, 5, 2, light_color)
+ set_light(5, 0.6, light_color)
/obj/structure/fountain/strange/attack_hand(mob/living/user)
diff --git a/code/game/objects/structures/seaweed.dm b/code/game/objects/structures/seaweed.dm
index d3bc8722ffc64..8853a7b5ce01a 100644
--- a/code/game/objects/structures/seaweed.dm
+++ b/code/game/objects/structures/seaweed.dm
@@ -20,11 +20,11 @@
/obj/structure/flora/seaweed/glow/Initialize()
. = ..()
- set_light(0.6, 0.1, 4, 3, "#00fff4")
+ set_light(4, 0.6, "#00fff4")
icon_state = "glowweed[rand(1,3)]"
/obj/effect/decal/cleanable/lichen
name = "lichen"
desc = "Damp and mossy plant life."
icon_state = "lichen"
- icon = 'icons/obj/structures/plants.dmi'
\ No newline at end of file
+ icon = 'icons/obj/structures/plants.dmi'
diff --git a/code/game/turfs/flooring/flooring_premade.dm b/code/game/turfs/flooring/flooring_premade.dm
index 2c8ce0cf64573..0a0c8c7c5ac51 100644
--- a/code/game/turfs/flooring/flooring_premade.dm
+++ b/code/game/turfs/flooring/flooring_premade.dm
@@ -10,8 +10,8 @@
icon = 'icons/turf/flooring/circuit.dmi'
icon_state = "bcircuit"
initial_flooring = /singleton/flooring/reinforced/circuit
- light_outer_range = 2
- light_max_bright = 1
+ light_range = 2
+ light_power = 1
light_color = COLOR_BLUE
/turf/simulated/floor/greengrid
@@ -19,8 +19,8 @@
icon = 'icons/turf/flooring/circuit.dmi'
icon_state = "gcircuit"
initial_flooring = /singleton/flooring/reinforced/circuit/green
- light_outer_range = 2
- light_max_bright = 3
+ light_range = 2
+ light_power = 3
light_color = COLOR_GREEN
/turf/simulated/floor/redgrid
@@ -28,8 +28,8 @@
icon = 'icons/turf/flooring/circuit.dmi'
icon_state = "rcircuit"
initial_flooring = /singleton/flooring/reinforced/circuit/red
- light_outer_range = 2
- light_max_bright = 2
+ light_range = 2
+ light_power = 2
light_color = COLOR_RED
/turf/simulated/floor/selfestructgrid
@@ -37,8 +37,8 @@
icon = 'icons/turf/flooring/circuit.dmi'
icon_state = "rcircuit_off"
initial_flooring = /singleton/flooring/reinforced/circuit/selfdestruct
- light_outer_range = 2
- light_max_bright = 2
+ light_range = 2
+ light_power = 2
light_color = COLOR_BLACK
/turf/simulated/floor/wood
diff --git a/code/game/turfs/simulated/wall_attacks.dm b/code/game/turfs/simulated/wall_attacks.dm
index bfa606fd3f5b0..6a7401b9ea926 100644
--- a/code/game/turfs/simulated/wall_attacks.dm
+++ b/code/game/turfs/simulated/wall_attacks.dm
@@ -29,7 +29,7 @@
update_icon()
update_air()
sleep(15)
- set_light(0.4, 0.1, 1)
+ set_light(1, 0.4)
src.blocks_air = 1
set_opacity(1)
for(var/turf/simulated/turf in loc)
diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm
index 885c13f82d01a..016e0d2e26ad0 100644
--- a/code/game/turfs/space/space.dm
+++ b/code/game/turfs/space/space.dm
@@ -11,6 +11,7 @@
turf_flags = TURF_DISALLOW_BLOB
z_eventually_space = TRUE
+ var/starlit = FALSE
/turf/space/Initialize()
. = ..()
@@ -32,6 +33,7 @@
return INITIALIZE_HINT_LATELOAD // oh no! we need to switch to being a different kind of turf!
/turf/space/Destroy()
+ remove_starlight()
// Cleanup cached z_eventually_space values above us.
if (above)
var/turf/T = src
@@ -53,13 +55,26 @@
/turf/space/is_solid_structure()
return locate(/obj/structure/lattice, src) || locate(/obj/structure/catwalk, src) //counts as solid structure if it has a lattice or catwalk
+/turf/space/proc/remove_starlight()
+ if(starlit)
+ replace_ambient_light(SSskybox.background_color, null, config.starlight, 0)
+ starlit = FALSE
+
/turf/space/proc/update_starlight()
if(!config.starlight)
return
- if(locate(/turf/simulated) in orange(src,1)) //Let's make sure not to break everything if people use a crazy setting.
- set_light(min(0.1*config.starlight, 1), 1, 3, l_color = SSskybox.background_color)
- else
- set_light(0)
+
+ //We only need starlight on turfs adjacent to dynamically lit turfs, for example space near bulkhead
+ for (var/turf/T in RANGE_TURFS(src, 1))
+ if (!isloc(T.loc) || !TURF_IS_DYNAMICALLY_LIT_UNSAFE(T))
+ continue
+
+ add_ambient_light(SSskybox.background_color, config.starlight)
+ starlit = TRUE
+ return
+
+ if(TURF_IS_AMBIENT_LIT_UNSAFE(src))
+ remove_starlight()
/turf/space/attackby(obj/item/C as obj, mob/user as mob)
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index ffd975cddea93..9aee9a3e69669 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -52,7 +52,14 @@
else
luminosity = 1
- RecalculateOpacity()
+ if (light_power && light_range)
+ update_light()
+
+ if (!mapload || (!istype(src, /turf/space) && is_outside()))
+ SSambient_lighting.queued += src
+
+ if (opacity)
+ has_opaque_atom = TRUE
if (mapload && permit_ao)
queue_ao()
@@ -431,3 +438,16 @@ var/global/const/enterloopsanity = 100
/turf/proc/IgniteTurf(power, fire_colour)
return
+
+//Maybe we want to make this stateful at some point
+/turf/proc/is_outside()
+
+ //For the purposes of light, dense turfs should not be considered to be outside
+ if(density)
+ return FALSE
+
+ var/area/A = get_area(src)
+ if(A.area_flags & AREA_FLAG_EXTERNAL)
+ return TRUE
+
+ //TODO: CitRP has some concept of outside based on turfs above. We don't really have any use cases right now, revisit this function if this changes
diff --git a/code/game/turfs/turf_changing.dm b/code/game/turfs/turf_changing.dm
index 2cb0c880a4431..14226552f07a8 100644
--- a/code/game/turfs/turf_changing.dm
+++ b/code/game/turfs/turf_changing.dm
@@ -35,13 +35,14 @@
var/old_hotspot = hotspot
var/old_turf_fire = null
var/old_opacity = opacity
- var/old_dynamic_lighting = dynamic_lighting
+ var/old_dynamic_lighting = TURF_IS_DYNAMICALLY_LIT_UNSAFE(src)
var/old_affecting_lights = affecting_lights
var/old_lighting_overlay = lighting_overlay
var/old_corners = corners
var/old_ao_neighbors = ao_neighbors
var/old_above = above
var/old_permit_ao = permit_ao
+ var/old_zflags = z_flags
if(isspaceturf(N) || isopenspace(N))
QDEL_NULL(turf_fire)
@@ -63,18 +64,16 @@
var/turf/simulated/S = src
if(S.zone) S.zone.rebuild()
+ if(ambient_bitflag) //Should remove everything about current bitflag, let it be recalculated by SS later
+ SSambient_lighting.clean_turf(src)
+
// Run the Destroy() chain.
qdel(src)
-
- var/old_opaque_counter = opaque_counter
var/turf/simulated/W = new N(src)
if (permit_ao)
regenerate_ao()
- W.opaque_counter = old_opaque_counter
- W.RecalculateOpacity()
-
if (keep_air)
W.air = old_air
@@ -101,18 +100,28 @@
. = W
W.ao_neighbors = old_ao_neighbors
- if(lighting_overlays_initialised)
+ // lighting stuff
+
+ if(SSlighting.initialized)
+ recalc_atom_opacity()
lighting_overlay = old_lighting_overlay
affecting_lights = old_affecting_lights
corners = old_corners
- if((old_opacity != opacity) || (dynamic_lighting != old_dynamic_lighting))
+ if (old_opacity != opacity || dynamic_lighting != old_dynamic_lighting || force_lighting_update)
reconsider_lights()
- if(dynamic_lighting != old_dynamic_lighting)
- if(dynamic_lighting)
+ updateVisibility(src)
+
+ if (dynamic_lighting != old_dynamic_lighting)
+ if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(src))
lighting_build_overlay()
else
lighting_clear_overlay()
+ W.setup_local_ambient()
+ if(z_flags != old_zflags)
+ W.rebuild_zbleed()
+ // end of lighting stuff
+
for(var/turf/T in RANGE_TURFS(src, 1))
T.update_icon()
diff --git a/code/modules/ZAS/Fire.dm b/code/modules/ZAS/Fire.dm
index cfe59cf749212..3141430ade5fa 100644
--- a/code/modules/ZAS/Fire.dm
+++ b/code/modules/ZAS/Fire.dm
@@ -112,13 +112,13 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin
if(firelevel > 6)
icon_state = "3"
- set_light(1, 2, 7)
+ set_light(7, 1)
else if(firelevel > 2.5)
icon_state = "2"
- set_light(0.7, 2, 5)
+ set_light(5, 0.7)
else
icon_state = "1"
- set_light(0.5, 1, 3)
+ set_light(3, 0.5)
for(var/mob/living/L in loc)
L.FireBurn(firelevel, air_contents.temperature, air_contents.return_pressure()) //Burn the mobs!
@@ -168,7 +168,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin
var/datum/gas_mixture/air_contents = loc.return_air()
color = fire_color(air_contents.temperature)
- set_light(0.5, 1, 3, l_color = color)
+ set_light(3, 0.5, l_color = color)
firelevel = fl
SSair.active_hotspots.Add(src)
diff --git a/code/modules/admin/buildmode/light_maker.dm b/code/modules/admin/buildmode/light_maker.dm
index 552624cb87838..6af18301838c7 100644
--- a/code/modules/admin/buildmode/light_maker.dm
+++ b/code/modules/admin/buildmode/light_maker.dm
@@ -2,8 +2,8 @@
name = "Light Maker"
icon_state = "buildmode8"
- var/light_outer_range = 3
- var/light_max_bright = 3
+ var/light_range = 3
+ var/light_power = 3
var/light_color = COLOR_WHITE
/datum/build_mode/light_maker/Help()
@@ -17,14 +17,14 @@
var/choice = alert("Change the new light range, power, or color?", "Light Maker", "Range", "Power", "Color", "Cancel")
switch(choice)
if("Range")
- var/input = input("New light range.", name, light_outer_range) as null|num
+ var/input = input("New light range.", name, light_range) as null|num
if(input)
- light_outer_range = input
+ light_range = input
if("Power")
- var/input = input("New light power, from 0.1 to 1 in decimal increments.", name, light_max_bright) as null|num
+ var/input = input("New light power, from 0.1 to 1 in decimal increments.", name, light_power) as null|num
if(input)
input = clamp(input, 0.1, 1)
- light_max_bright = input
+ light_power = input
if("Color")
var/input = input("New light color.", name, light_color) as null|color
if(input)
@@ -33,7 +33,7 @@
/datum/build_mode/light_maker/OnClick(atom/A, list/parameters)
if(parameters["left"])
if(A)
- A.set_light(light_max_bright, 0.1, light_outer_range, l_color = light_color)
+ A.set_light(light_range, light_power, l_color = light_color)
if(parameters["right"])
if(A)
A.set_light(0, 0, 0, l_color = COLOR_WHITE)
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index b3a50edc990f1..19de2e0d3aa29 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -482,7 +482,7 @@
var/obj/effect/overmap/visitable/sector/exoplanet/new_planet = new exoplanet_type(null, world.maxx, world.maxy)
new_planet.features_budget = budget
new_planet.themes = list(new theme)
- new_planet.lightlevel = rand(5, 10)/10
+ new_planet.sun_brightness_modifier = rand(0.1, 0.6)
log_and_message_admins("is spawning [new_planet] at [new_planet.start_x],[new_planet.start_y], containing Z [english_list(new_planet.map_z)]")
new_planet.build_level()
diff --git a/code/modules/admin/view_variables/vv_set_handlers.dm b/code/modules/admin/view_variables/vv_set_handlers.dm
index 57e0faedab68f..8e68ebeb00ba8 100644
--- a/code/modules/admin/view_variables/vv_set_handlers.dm
+++ b/code/modules/admin/view_variables/vv_set_handlers.dm
@@ -118,7 +118,7 @@
/singleton/vv_set_handler/light_handler
handled_type = /atom
- handled_vars = list("light_max_bright","light_inner_range","light_outer_range","light_falloff_curve")
+ handled_vars = list("light_power","light_range")
/singleton/vv_set_handler/light_handler/handle_set_var(atom/A, variable, var_value, client)
var_value = text2num(var_value)
@@ -126,12 +126,10 @@
return
// More sanity checks
- var/new_max = variable == "light_max_bright" ? var_value : A.light_max_bright
- var/new_inner = variable == "light_inner_range" ? var_value : A.light_inner_range
- var/new_outer = variable == "light_outer_range" ? var_value : A.light_outer_range
- var/new_falloff = variable == "light_falloff_curve" ? var_value : A.light_falloff_curve
+ var/new_max = variable == "light_power" ? var_value : A.light_power
+ var/new_range = variable == "light_range" ? var_value : A.light_range
- A.set_light(new_max, new_inner, new_outer, new_falloff)
+ A.set_light(new_range, new_max)
/singleton/vv_set_handler/health_value_handler
handled_type = /atom
diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm
index 1115e4dcae8e2..7fdf701698144 100644
--- a/code/modules/ai/ai_holder_targeting.dm
+++ b/code/modules/ai/ai_holder_targeting.dm
@@ -26,7 +26,7 @@
/// Step 1, find out what we can see.
/datum/ai_holder/proc/list_targets()
. = ohearers(vision_range, holder)
- . -= GLOB.dview_mob // Not the dview mob!
+ . -= global.dview_mob // Not the dview mob!
var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /mob/living/exosuit, /obj/effect/blob))
diff --git a/code/modules/blob/blob.dm b/code/modules/blob/blob.dm
index 268caaf4920e7..6a3e9977347e8 100644
--- a/code/modules/blob/blob.dm
+++ b/code/modules/blob/blob.dm
@@ -10,7 +10,7 @@
desc = "A pulsating mass of interwoven tendrils."
icon = 'icons/mob/blob.dmi'
icon_state = "blob"
- light_outer_range = 2
+ light_range = 2
light_color = BLOB_COLOR_PULS
density = TRUE
opacity = 1
diff --git a/code/modules/clothing/_clothing.dm b/code/modules/clothing/_clothing.dm
index 6dfad07833ae1..3d2b1cab59232 100644
--- a/code/modules/clothing/_clothing.dm
+++ b/code/modules/clothing/_clothing.dm
@@ -456,7 +456,7 @@ BLIND // can't see anything
/obj/item/clothing/head/proc/update_flashlight(mob/user = null)
if(on && !light_applied)
- set_light(brightness_on, 1, 3)
+ set_light(3, brightness_on, angle = LIGHT_WIDE)
light_applied = 1
else if(!on && light_applied)
set_light(0)
diff --git a/code/modules/clothing/glasses/glasses.dm b/code/modules/clothing/glasses/glasses.dm
index 549a244c00942..157030d0bc7d1 100644
--- a/code/modules/clothing/glasses/glasses.dm
+++ b/code/modules/clothing/glasses/glasses.dm
@@ -173,7 +173,6 @@
darkness_view = 5
action_button_name = "Toggle Goggles"
toggleable = TRUE
- see_invisible = SEE_INVISIBLE_NOLIGHTING
siemens_coefficient = 0.6
electric = TRUE
diff --git a/code/modules/clothing/spacesuits/rig/modules/ninja.dm b/code/modules/clothing/spacesuits/rig/modules/ninja.dm
index fa8857967191f..5fb896c503db9 100644
--- a/code/modules/clothing/spacesuits/rig/modules/ninja.dm
+++ b/code/modules/clothing/spacesuits/rig/modules/ninja.dm
@@ -220,7 +220,7 @@
holder.visible_message(SPAN_DANGER("\The [src.holder] emits a shrill tone!"),SPAN_DANGER(" You hear a shrill tone!"))
sleep(blink_solid_time)
src.blink_mode = 0
- src.holder.set_light(0, 0, 0, 2, "#000000")
+ src.holder.set_light(2, 0, "#000000")
explosion(get_turf(src), explosion_radius, explosion_max_power)
if(holder && holder.wearer)
@@ -244,17 +244,17 @@
if(0)
return
if(1)
- src.holder.set_light(1, 1, 8.5, 2, "#ff0a00")
+ src.holder.set_light(8.5, 1, "#ff0a00")
sleep(6)
- src.holder.set_light(0, 0, 0, 2, "#000000")
+ src.holder.set_light(0)
spawn(6) .()
if(2)
- src.holder.set_light(1, 1, 8.5, 2, "#ff0a00")
+ src.holder.set_light(8.5, 1, "#ff0a00")
sleep(2)
- src.holder.set_light(0, 0, 0, 2, "#000000")
+ src.holder.set_light(0)
spawn(2) .()
if(3)
- src.holder.set_light(1, 1, 8.5, 2, "#ff0a00")
+ src.holder.set_light(8.5, 1, "#ff0a00")
/obj/item/rig_module/grenade_launcher/ninja
suit_overlay = null
diff --git a/code/modules/clothing/spacesuits/spacesuits.dm b/code/modules/clothing/spacesuits/spacesuits.dm
index efb52b798ca84..9eab5276cf639 100644
--- a/code/modules/clothing/spacesuits/spacesuits.dm
+++ b/code/modules/clothing/spacesuits/spacesuits.dm
@@ -33,7 +33,7 @@
action_button_name = "Toggle Helmet Light"
light_overlay = "helmet_light"
- brightness_on = 0.5
+ brightness_on = 1
on = 0
var/tinted = null //Set to non-null for toggleable tint helmets
diff --git a/code/modules/detectivework/tools/rag.dm b/code/modules/detectivework/tools/rag.dm
index 56383bbcfbccf..a95facf8eaae4 100644
--- a/code/modules/detectivework/tools/rag.dm
+++ b/code/modules/detectivework/tools/rag.dm
@@ -238,7 +238,7 @@
return
START_PROCESSING(SSobj, src)
- set_light(0.5, 0.1, 2, 2, "#e38f46")
+ set_light(2, 0.5, "#e38f46")
on_fire = 1
update_name()
update_icon()
diff --git a/code/modules/detectivework/tools/uvlight.dm b/code/modules/detectivework/tools/uvlight.dm
index 1d828ffc8df6b..1b90195421e0c 100644
--- a/code/modules/detectivework/tools/uvlight.dm
+++ b/code/modules/detectivework/tools/uvlight.dm
@@ -21,7 +21,7 @@
/obj/item/device/uv_light/attack_self(mob/user)
on = !on
if(on)
- set_light(0.5, 0.1, range, 2, "#007fff")
+ set_light(range, 0.5, "#007fff")
START_PROCESSING(SSobj, src)
icon_state = "uv_on"
else
diff --git a/code/modules/holodeck/HolodeckObjects.dm b/code/modules/holodeck/HolodeckObjects.dm
index 43509ea0cd995..f4982e39cdfaa 100644
--- a/code/modules/holodeck/HolodeckObjects.dm
+++ b/code/modules/holodeck/HolodeckObjects.dm
@@ -419,10 +419,9 @@
/mob/living/simple_animal/hostile/carp/holodeck/on_update_icon()
return
-
/mob/living/simple_animal/hostile/carp/holodeck/Initialize(mapload, ...)
. = ..()
- set_light(0.5, 0.1, 2) //hologram lighting
+ set_light(2, 0.5) //hologram lighting
/mob/living/simple_animal/hostile/carp/holodeck/proc/set_safety(safe)
diff --git a/code/modules/holomap/ship_holomap.dm b/code/modules/holomap/ship_holomap.dm
index a55b4f1cf64a0..0fc732082952d 100644
--- a/code/modules/holomap/ship_holomap.dm
+++ b/code/modules/holomap/ship_holomap.dm
@@ -160,7 +160,7 @@
set_light(0)
else
icon_state = "station_map"
- set_light(0.8, 0.1, 2, 2, "#1dbe17")
+ set_light(2, 0.8, "#1dbe17")
// Put the little "map" overlay down where it looks nice
if(small_station_map)
diff --git a/code/modules/hydroponics/seed.dm b/code/modules/hydroponics/seed.dm
index 70d4d9811f8bb..fdef5fce2ffb2 100644
--- a/code/modules/hydroponics/seed.dm
+++ b/code/modules/hydroponics/seed.dm
@@ -204,7 +204,7 @@
var/clr
if(get_trait(TRAIT_BIOLUM_COLOUR))
clr = get_trait(TRAIT_BIOLUM_COLOUR)
- splat.set_light(0.5, 0.1, 3, l_color = clr)
+ splat.set_light(3, 0.5, l_color = clr)
var/flesh_colour = get_trait(TRAIT_FLESH_COLOUR)
if(!flesh_colour) flesh_colour = get_trait(TRAIT_PRODUCT_COLOUR)
if(flesh_colour) splat.color = get_trait(TRAIT_PRODUCT_COLOUR)
@@ -787,7 +787,7 @@
var/clr
if(get_trait(TRAIT_BIOLUM_COLOUR))
clr = get_trait(TRAIT_BIOLUM_COLOUR)
- product.set_light(0.5, 0.1, 3, l_color = clr)
+ product.set_light(3, 0.5, l_color = clr)
//Handle spawning in living, mobile products (like dionaea).
if(istype(product,/mob/living))
diff --git a/code/modules/hydroponics/spreading/spreading.dm b/code/modules/hydroponics/spreading/spreading.dm
index 540bf2a8e8e4b..55cad3d73e222 100644
--- a/code/modules/hydroponics/spreading/spreading.dm
+++ b/code/modules/hydroponics/spreading/spreading.dm
@@ -157,7 +157,7 @@
// Apply colour and light from seed datum.
if(seed.get_trait(TRAIT_BIOLUM))
- set_light(0.5, 0.1, 3, l_color = seed.get_trait(TRAIT_BIOLUM_COLOUR))
+ set_light(3, 0.5, l_color = seed.get_trait(TRAIT_BIOLUM_COLOUR))
else
set_light(0)
diff --git a/code/modules/hydroponics/trays/tray_update_icons.dm b/code/modules/hydroponics/trays/tray_update_icons.dm
index 1344d374a5749..60fb1f53159ea 100644
--- a/code/modules/hydroponics/trays/tray_update_icons.dm
+++ b/code/modules/hydroponics/trays/tray_update_icons.dm
@@ -71,7 +71,7 @@
// Update bioluminescence.
if(seed && seed.get_trait(TRAIT_BIOLUM))
- set_light(0.5, 0.1, 3, l_color = seed.get_trait(TRAIT_BIOLUM_COLOUR))
+ set_light(3, 0.5, l_color = seed.get_trait(TRAIT_BIOLUM_COLOUR))
else
set_light(0)
diff --git a/code/modules/integrated_electronics/subtypes/output.dm b/code/modules/integrated_electronics/subtypes/output.dm
index d8b1e3e639552..e4b03e969f841 100644
--- a/code/modules/integrated_electronics/subtypes/output.dm
+++ b/code/modules/integrated_electronics/subtypes/output.dm
@@ -83,7 +83,7 @@
/obj/item/integrated_circuit/output/light/proc/update_lighting()
if(light_toggled)
if(assembly)
- assembly.set_light(light_brightness, 1, 4, 2, light_rgb)
+ assembly.set_light(4, light_brightness, light_rgb)
else
if(assembly)
assembly.set_light(0)
diff --git a/code/modules/lighting/__lighting_docs.dm b/code/modules/lighting/__lighting_docs.dm
index 3816595f3c2d0..a742e1a98adb5 100644
--- a/code/modules/lighting/__lighting_docs.dm
+++ b/code/modules/lighting/__lighting_docs.dm
@@ -1,67 +1,46 @@
/*
-BS12 object based lighting system
-*/
+ -- O7 lighting system --
-/*
-Changes from tg DAL:
- - Lighting is done using objects instead of subareas.
- - Animated transitions. (newer tg DAL has this)
- - Full colours with mixing.
- - Support for lights on shuttles.
+ Documentation is present in most of the code files.
+ lighting_atom.dm -> procs/vars for tracking/managing lights attached to objects.
+ lighting_turf.dm -> procs/vars for managing lighting overlays bound to turfs, tracking lights affecting said turf, and getting information about the turf's light level.
+ lighting_corner.dm -> contains code for tracking per-corner lighting data.
+ lighting_source.dm -> contains actual light emitter datum & core lighting calculations. Directional lights and Z-lights are implemented here.
+ lighting_area.dm -> contains area vars/procs for managing an area's dynamic lighting state.
- - Code:
- - Instead of one flat luminosity var, light is represented by 3 atom vars:
- - light_range; diameter in tiles of the light, used for calculating falloff, Cannot be 1.
- - light_power; multiplier for the brightness of lights,
- - light_color; hex string representing the RGB colour of the light.
- - setLuminousity() is now set_light() and takes the three variables above.
- - Variables can be left as null to not update them.
- - set_opacity() is now set_opacity().
- - Areas have luminosity set to 1 permanently, no hard-lighting.
- - Objects inside other objects can have lights and they properly affect the turf. (flashlights)
- - area/master and area/list/related have been eviscerated since subareas aren't needed.
*/
/*
-Relevant vars/procs:
-
-atom: (lighting_atom.dm)
- - var/light_range; range in tiles of the light, used for calculating falloff
- - var/light_power; multiplier for the brightness of lights
- - var/light_color; hex string representing the RGB colour of the light
-
- - var/datum/light_source/light; light source datum for this atom, only present if light_range && light_power
- - var/list/light_sources; light sources in contents that are shining through this object, including this object
-
- - proc/set_light(l_max_bright, l_inner_range, l_outer_range, l_falloff_curve, l_color):
- - Sets light_range/power/color to non-null args and calls update_light()
- - proc/set_opacity(new_opacity):
- - Sets opacity to new_opacity.
- - If opacity has changed, call turf.reconsider_lights() to fix light occlusion
- - proc/update_light():
- - Updates the light var on this atom, deleting or creating as needed and calling .update()
-
-
-turf: (lighting_turf.dm)
- - var/list/affecting_lights; list of light sources that are shining onto this turf
-
- - proc/reconsider_lights():
- - Force all light sources shining onto this turf to update
-
- - proc/lighting_clear_overlays():
- - Delete (manual GC) all light overlays on this turf, used when changing turf to space
- - proc/lighting_build_overlays():
- - Create lighting overlays for this turf
-
-
-/atom/movable/lighting_overlay: (lighting_overlay.dm)
- - var/lum_r, var/lum_g, var/lum_b; lumcounts of each colour
- - var/needs_update; set on update_lumcount, checked by lighting process
-
- - var/xoffset, var/yoffset; (only present when using sub-tile overlays) fractional offset of this overlay in the tile
-
- - proc/update_lumcount(delta_r, delta_g, delta_b):
- - Change the lumcount vars and queue the overlay for update
- - proc/update_overlay()
- - Called by the lighting process to update the color of the overlay
-*/
\ No newline at end of file
+ Useful procs when using lights:
+
+/atom/proc/set_light(range, power, color, angle, no_update)
+ desc: Sets an atom's light emission. `set_light(FALSE)` will disable the light.
+ args:
+ range -> the range of the light. 1.4 is the lowest possible value here.
+ power -> the power (intensity) of the light. Generally should be 1 or lower. Optional.
+ color -> The hex string (#FFFFFF) color of the light. Optional.
+ angle -> The angle of the cone that the light should shine at (directional lighting). Behavior of lights over 180 degrees is undefined. Best to stick to using the LIGHT_ defines for this. Optional.
+ no_update -> if TRUE, the light will not be updated. Useful for when making several of these calls to the same object. Optional.
+
+/atom/proc/set_opacity(new_opacity)
+ desc: Sets an atom's opacity, updating affecting lights' visibility.
+ args:
+ new_opacity -> the new opacity value.
+
+/turf/proc/reconsider_lights()
+ desc: Cause all lights affecting this turf to recalculate visibility.
+ args: none
+
+/turf/proc/force_update_lights()
+ desc: Force all lights affecting this turf to regenerate. Slow, use reconsider_lights instead when possible.
+ args: none
+
+/turf/proc/get_avg_color()
+ desc: Gets the average color of this tile as a hexadecimal color string. Used by cameras.
+
+/turf/proc/get_lumcount(minlum = 0, maxlum = 1)
+ desc: Gets the brightness of this tile. If not dynamically lit, always returns 0.5, otherwise returns the average brightness of all 4 corners, scaled between minlum and maxlum.
+ args:
+ minlum -> the low-bound of the scalar.
+ maxlum -> the high-bound of the scalar.
+*/
diff --git a/code/modules/lighting/_lighting_defs.dm b/code/modules/lighting/_lighting_defs.dm
new file mode 100644
index 0000000000000..f43ef762a6b3e
--- /dev/null
+++ b/code/modules/lighting/_lighting_defs.dm
@@ -0,0 +1,43 @@
+// This is the define used to calculate falloff.
+#define LUM_FALLOFF(Cx,Cy,Tx,Ty,HEIGHT) (1 - CLAMP01(sqrt(((Cx) - (Tx)) ** 2 + ((Cy) - (Ty)) ** 2 + HEIGHT) / max(1, actual_range)))
+
+// Macro that applies light to a new corner.
+// It is a macro in the interest of speed, yet not having to copy paste it.
+// If you're wondering what's with the backslashes, the backslashes cause BYOND to not automatically end the line.
+// As such this all gets counted as a single line.
+// The braces and semicolons are there to be able to do this on a single line.
+
+#define APPLY_CORNER(C,now,Tx,Ty,hdiff) \
+ . = LUM_FALLOFF(C.x, C.y, Tx, Ty, hdiff) * light_power; \
+ var/OLD = effect_str[C]; \
+ effect_str[C] = .; \
+ C.update_lumcount \
+ ( \
+ (. * lum_r) - (OLD * applied_lum_r), \
+ (. * lum_g) - (OLD * applied_lum_g), \
+ (. * lum_b) - (OLD * applied_lum_b), \
+ now \
+ );
+
+// I don't need to explain what this does, do I?
+#define REMOVE_CORNER(C,now) \
+ . = -effect_str[C]; \
+ C.update_lumcount \
+ ( \
+ . * applied_lum_r, \
+ . * applied_lum_g, \
+ . * applied_lum_b, \
+ now \
+ );
+
+// Converts two Z levels into a height value for LUM_FALLOFF or HEIGHT_FALLOFF.
+#define CALCULATE_CORNER_HEIGHT(ZA,ZB) (((max(ZA,ZB) - min(ZA,ZB)) + 1) * LIGHTING_HEIGHT * LIGHTING_Z_FACTOR)
+
+#define APPLY_CORNER_BY_HEIGHT(now) \
+ if (C.z != Sz) { \
+ corner_height = CALCULATE_CORNER_HEIGHT(C.z, Sz); \
+ } \
+ else { \
+ corner_height = LIGHTING_HEIGHT; \
+ } \
+ APPLY_CORNER(C, now, Sx, Sy, corner_height);
diff --git a/code/modules/lighting/darksight.dm b/code/modules/lighting/darksight.dm
new file mode 100644
index 0000000000000..a3fcd2747abeb
--- /dev/null
+++ b/code/modules/lighting/darksight.dm
@@ -0,0 +1,25 @@
+/obj/darksight
+ plane = LIGHTING_PLANE
+
+ icon = 'icons/mob/darksight.dmi'
+
+ screen_loc = "CENTER-7,CENTER-7"
+
+ blend_mode = BLEND_ADD
+ invisibility = INVISIBILITY_LIGHTING
+ alpha = 0 //By default make it transparent so mobs that don't update it also don't get it
+
+/obj/darksight/Initialize()
+ . = ..()
+ SetTransform((world.icon_size/DARKSIGHT_GRADIENT_SIZE) * 0.9)
+
+/obj/darksight/proc/sync(new_colour)
+ color = new_colour
+
+/mob
+ var/obj/darksight/darksight = null
+
+
+/mob/proc/change_light_colour(new_colour)
+ if(darksight)
+ darksight.sync(new_colour)
diff --git a/code/modules/lighting/lighting_area.dm b/code/modules/lighting/lighting_area.dm
index 003d7bba6fef2..cdcd2c9c10a21 100644
--- a/code/modules/lighting/lighting_area.dm
+++ b/code/modules/lighting/lighting_area.dm
@@ -1,10 +1,27 @@
-/area/luminosity = TRUE
-/// Boolean. Whether or not the area should process dynamic lighting. Affects the value of `luminosity` during init and the lighting effects on turfs in the area.
+/area/luminosity = TRUE
/area/var/dynamic_lighting = TRUE
-/// String (One of `AREA_LIGHTING_*`). The light tone selection mode used for the area. See `code\__defines\lighting.dm` for possible values.
-/area/var/lighting_tone = AREA_LIGHTING_DEFAULT
+/area/var/lighting_tone = AREA_LIGHTING_DEFAULT
-/area/New()
- ..()
- if(dynamic_lighting)
+/area/Initialize()
+ . = ..()
+
+ if (dynamic_lighting)
luminosity = FALSE
+
+/area/proc/set_dynamic_lighting(new_dynamic_lighting = TRUE)
+ if (new_dynamic_lighting == dynamic_lighting)
+ return FALSE
+
+ dynamic_lighting = new_dynamic_lighting
+
+ if (new_dynamic_lighting)
+ for (var/turf/T in src)
+ if (T.dynamic_lighting)
+ T.lighting_build_overlay()
+
+ else
+ for (var/turf/T in src)
+ if (T.lighting_overlay)
+ T.lighting_clear_overlay()
+
+ return TRUE
diff --git a/code/modules/lighting/lighting_atom.dm b/code/modules/lighting/lighting_atom.dm
index 52b63428bea91..d33781ed60799 100644
--- a/code/modules/lighting/lighting_atom.dm
+++ b/code/modules/lighting/lighting_atom.dm
@@ -1,98 +1,117 @@
-/// Float. Intensity of the light within the full brightness range. Value between 0 and 1. Do not modify directly. See `set_light()`.
-/atom/var/light_max_bright = 1.0
-/// Integer. Range, in tiles, the light is at full brightness. Do not modify directly. See `set_light()`.
-/atom/var/light_inner_range = 1
-/// Integer. Range, in tiles, where the light becomes darkness. Do not modify directly. See `set_light()`.
-/atom/var/light_outer_range = 0
-/// Integer. Adjusts curve for falloff gradient. Must be greater than 0. Do not modify directly. See `set_light()`.
-/atom/var/light_falloff_curve = 2
-/// String (Hexadecimal color code). The color of the light. Do not modify directly. See `set_light()`.
-/atom/var/light_color
+#define MINIMUM_USEFUL_LIGHT_RANGE 1.4
-/// The light source datum handling rendering of the light defined in the `light_*` vars on this atom. See `set_light()` and `update_light()`.
+ /// Intensity of the light.
+/atom/var/light_power = 1
+/// Range in tiles of the light.
+/atom/var/light_range = 0
+/// Hexadecimal RGB string representing the colour of the light.
+/atom/var/light_color
+/// The angle that the light's emission should be restricted to. null for omnidirectional.
+/atom/var/light_wedge
+/** These two vars let you override the default light offset detection (pixel_x/y).
+ * Useful for objects like light fixtures that aren't visually in the middle of the turf, but aren't offset either.
+ */
+/atom/var/light_offset_x
+/atom/var/light_offset_y
+/// Our light source. Don't fuck with this directly unless you have a good reason!
/atom/var/datum/light_source/light
-/// LAZYLIST of all light sources contained within the atom and its contents. Used to propagate updates whenever somehting, i.e. position, changes.
-/atom/var/list/light_sources
+/// Any light sources that are "inside" of us, for example, if src here was a mob that's carrying a flashlight, that flashlight's light source would be part of this list.
+/atom/var/list/light_source_multi
+/// Same as above - this is a shortcut to avoid allocating the above list if we can
+/atom/var/datum/light_source/light_source_solo
-// Nonsensical value for l_color default, so we can detect if it gets set to null.
+/// Nonsensical value for l_color default, so we can detect if it gets set to null.
#define NONSENSICAL_VALUE -99999
-#define DEFAULT_FALLOFF_CURVE (2)
/**
- * Sets the atom's light values and color. Calls `update_light()`.
+ * Sets the atom's light values and color. May call `update_light()`.
*
* **Parameters**:
- * - `l_max_bright` float (0 to 1) - Intensity of the light within the full brightness range. Value between 0 and 1. Applied to `light_max_bright`.
- * - `l_inner_range` integer - Range, in tiles, the light is at full brightness. Applied to `light_inner_range`.
- * - `l_outer_range` integer - Range, in tiles, where the light becomes darkness. Do not modify directly. Applied to `light_outer_range`.
- * - `l_falloff_curbe` integer (Default `NONSENSICAL_VALUE`) - Adjusts curve for falloff gradient. Must be greater than 0. Do not modify directly. Applied to `light_falloff_curve`.
- * - `l_color` color (Default `NONSENSICAL_VALUE`) - The color of the light. Applied to `light_color`.
+ * - `l_range` integer - Range in tiles of the light (Must be above `MINIMUM_USEFUL_LIGHT_RANGE`), light brightness will decay to 0 at this range. Applied to `light_range`. WARNING: Values over 32 are bound to cause lag
+ * - `l_power` float - The power (intensity) of the light. Generally should be 1 or lower but may be higher. Applied to `light_power`. Optional
+ * - `l_color` color (Default `NONSENSICAL_VALUE`) - The color of the light. Applied to `light_color`. Optional
+ * - `angle` integer (Default `NONSENSICAL_VALUE`) - The angle of the cone that the light should shine at (directional lighting). Behavior of lights over 180 degrees is undefined. Best to stick to using the LIGHT_ defines for this. Optional.
+ * - `no_update` boolean (Default `FALSE`) -if TRUE, `update_light()` will not be called. Useful for when making several of these calls to the same object. Optional.
*
- * Returns boolean. Whether or not the light was actually changed.
+ * Returns null
*/
-/atom/proc/set_light(l_max_bright, l_inner_range, l_outer_range, l_falloff_curve = NONSENSICAL_VALUE, l_color = NONSENSICAL_VALUE)
- . = 0 //make it less costly if nothing's changed
-
- if(l_max_bright != null && l_max_bright != light_max_bright)
- light_max_bright = l_max_bright
- . = 1
- if(l_outer_range != null && l_outer_range != light_outer_range)
- light_outer_range = l_outer_range
- . = 1
- if(l_inner_range != null && l_inner_range != light_inner_range)
- if(light_inner_range >= light_outer_range)
- light_inner_range = light_outer_range / 4
- else
- light_inner_range = l_inner_range
- . = 1
- if(l_falloff_curve != NONSENSICAL_VALUE)
- if(!l_falloff_curve || l_falloff_curve <= 0)
- light_falloff_curve = DEFAULT_FALLOFF_CURVE
- if(l_falloff_curve != light_falloff_curve)
- light_falloff_curve = l_falloff_curve
- . = 1
- if(l_color != NONSENSICAL_VALUE && l_color != light_color)
+/atom/proc/set_light(l_range, l_power, l_color = NONSENSICAL_VALUE, angle = NONSENSICAL_VALUE, no_update = FALSE)
+ if(l_range > 0 && l_range < MINIMUM_USEFUL_LIGHT_RANGE)
+ l_range = MINIMUM_USEFUL_LIGHT_RANGE //Brings the range up to 1.4
+ if (l_power != null)
+ light_power = l_power
+
+ if (l_range != null)
+ light_range = l_range
+
+ if (l_color != NONSENSICAL_VALUE)
light_color = l_color
- . = 1
- if(.) update_light()
+ if (angle != NONSENSICAL_VALUE)
+ light_wedge = angle
+
+ if (no_update)
+ return
+
+ update_light()
#undef NONSENSICAL_VALUE
-#undef DEFAULT_FALLOFF_CURVE
/**
- * Updates the atom's light source datum. This is automatically called by `set_light()`.
+ * Will update the light (duh).
+ *
+ * Creates or destroys it if needed, makes it update values, makes sure it's got the correct source turf...
*/
/atom/proc/update_light()
- set waitfor = FALSE
-
- if(!light_max_bright || !light_outer_range || light_max_bright > 1)
- if(light)
- light.destroy()
- light = null
- if(light_max_bright > 1)
- light_max_bright = 1
- CRASH("Attempted to call update_light() on atom [src] \ref[src] with a light_max_bright value greater than one")
+ if (QDELING(src))
+ return
+
+ if (!light_power || !light_range) // We won't emit light anyways, destroy the light source.
+ QDEL_NULL(light)
else
- if(!istype(loc, /atom/movable))
+ if (!istype(loc, /atom/movable)) // We choose what atom should be the top atom of the light here.
. = src
else
. = loc
- if(light)
+ if (light)
light.update(.)
else
light = new /datum/light_source(src, .)
-/atom/Destroy()
- if(light)
- light.destroy()
- light = null
- return ..()
-/atom/set_opacity()
+// Should always be used to change the opacity of an atom.
+// It notifies (potentially) affected light sources so they can update (if needed).
+/atom/set_opacity(new_opacity)
+ . = ..()
+ if (!.)
+ return
+
+ opacity = new_opacity
+ var/turf/T = loc
+ if (!isturf(T))
+ return
+
+ if (new_opacity == TRUE)
+ T.has_opaque_atom = TRUE
+ T.reconsider_lights()
+ #ifdef AO_USE_LIGHTING_OPACITY
+ T.regenerate_ao()
+ #endif
+ else
+ var/old_has_opaque_atom = T.has_opaque_atom
+ T.recalc_atom_opacity()
+ if (old_has_opaque_atom != T.has_opaque_atom)
+ T.reconsider_lights()
+
+/atom/movable/forceMove()
. = ..()
- if(.)
- var/turf/T = loc
- if(istype(T))
- T.RecalculateOpacity()
+
+ if (light_source_solo)
+ light_source_solo.source_atom.update_light()
+ else if (light_source_multi)
+ var/datum/light_source/L
+ var/thing
+ for (thing in light_source_multi)
+ L = thing
+ L.source_atom.update_light()
diff --git a/code/modules/lighting/lighting_corner.dm b/code/modules/lighting/lighting_corner.dm
index 225b9d4a560eb..bd2ace158a9c0 100644
--- a/code/modules/lighting/lighting_corner.dm
+++ b/code/modules/lighting/lighting_corner.dm
@@ -1,4 +1,3 @@
-var/global/total_lighting_corners = 0
var/global/datum/lighting_corner/dummy/dummy_lighting_corner = new
// Because we can control each corner of every lighting overlay.
// And corners get shared between multiple turfs (unless you're on the corners of the map, then 1 corner doesn't).
@@ -7,35 +6,75 @@ var/global/datum/lighting_corner/dummy/dummy_lighting_corner = new
// This list is what the code that assigns corners listens to, the order in this list is the order in which corners are added to the /turf/corners list.
var/global/list/LIGHTING_CORNER_DIAGONAL = list(NORTHEAST, SOUTHEAST, SOUTHWEST, NORTHWEST)
+// This is the reverse of the above - the position in the array is a dir. Update this if the above changes.
+var/global/list/REVERSE_LIGHTING_CORNER_DIAGONAL = list(0, 0, 0, 0, 3, 4, 0, 0, 2, 1)
+
/datum/lighting_corner
- var/list/turf/masters = list()
- var/list/datum/light_source/affecting = list() // Light sources affecting us.
+ // t1 through t4 are our masters, in no particular order.
+ // They are split into vars like this in the interest of reducing memory usage.
+ // tX is the turf itself, tXi is the index of this corner in that turf's corners list.
+ var/turf/t1
+ var/t1i
+ var/turf/t2
+ var/t2i
+ var/turf/t3
+ var/t3i
+ var/turf/t4
+ var/t4i
+
+ var/list/datum/light_source/affecting // Light sources affecting us.
var/active = FALSE // TRUE if one of our masters has dynamic lighting.
- var/x = 0
- var/y = 0
- var/z = 0
+ var/x = 0
+ var/y = 0
+ var/z = 0
+
+ // Our own intensity, from lights directly shining on us.
+ var/self_r = 0
+ var/self_g = 0
+ var/self_b = 0
+
+ // The intensity we're inheriting from the turf below us, if we're a Z-turf.
+ var/below_r = 0
+ var/below_g = 0
+ var/below_b = 0
+
+ // Ambient turf lighting that's not inherited from a light source. These are updated as absolute values.
+ var/ambient_r = 0
+ var/ambient_g = 0
+ var/ambient_b = 0
- var/lum_r = 0
- var/lum_g = 0
- var/lum_b = 0
+ // The turf above us' ambient
+ var/above_ambient_r = 0
+ var/above_ambient_g = 0
+ var/above_ambient_b = 0
+
+ // The final intensity, all things considered.
+ var/apparent_r = 0
+ var/apparent_g = 0
+ var/apparent_b = 0
var/needs_update = FALSE
- var/cache_r = LIGHTING_SOFT_THRESHOLD
- var/cache_g = LIGHTING_SOFT_THRESHOLD
- var/cache_b = LIGHTING_SOFT_THRESHOLD
+ var/cache_r = 0
+ var/cache_g = 0
+ var/cache_b = 0
var/cache_mx = 0
+ /// Used for planet lighting. Probably needs a better system to prevent over-updating when not needed at some point.
var/update_gen = 0
-/datum/lighting_corner/New(turf/new_turf, diagonal)
- . = ..()
+/datum/lighting_corner/New(turf/new_turf, diagonal, oi)
+ SSlighting.total_lighting_corners += 1
- total_lighting_corners++
+ var/has_ambience = FALSE
- masters[new_turf] = turn(diagonal, 180)
+ t1 = new_turf
z = new_turf.z
+ t1i = oi
+
+ if (TURF_IS_AMBIENT_LIT_UNSAFE(new_turf))
+ has_ambience = TRUE
var/vertical = diagonal & ~(diagonal - 1) // The horizontal directions (4 and 8) are bigger than the vertical ones (1 and 2), so we can reliably say the lsb is the horizontal direction.
var/horizontal = diagonal & ~vertical // Now that we know the horizontal one we can get the vertical one.
@@ -47,88 +86,320 @@ var/global/list/LIGHTING_CORNER_DIAGONAL = list(NORTHEAST, SOUTHEAST, SOUTHWEST,
// Issue being that the only way I could think of doing it was very messy, slow and honestly overengineered.
// So we'll have this hardcode instead.
var/turf/T
- var/i
+
// Diagonal one is easy.
T = get_step(new_turf, diagonal)
if (T) // In case we're on the map's border.
if (!T.corners)
- T.corners = list(null, null, null, null)
+ T.corners = new(4)
- masters[T] = diagonal
- i = LIGHTING_CORNER_DIAGONAL.Find(turn(diagonal, 180))
- T.corners[i] = src
+ t2 = T
+ t2i = REVERSE_LIGHTING_CORNER_DIAGONAL[diagonal]
+ T.corners[t2i] = src
+ if (TURF_IS_AMBIENT_LIT_UNSAFE(T))
+ has_ambience = TRUE
// Now the horizontal one.
T = get_step(new_turf, horizontal)
if (T) // Ditto.
if (!T.corners)
- T.corners = list(null, null, null, null)
+ T.corners = new(4)
- masters[T] = ((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH) // Get the dir based on coordinates.
- i = LIGHTING_CORNER_DIAGONAL.Find(turn(masters[T], 180))
- T.corners[i] = src
+ t3 = T
+ t3i = REVERSE_LIGHTING_CORNER_DIAGONAL[((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH)] // Get the dir based on coordinates.
+ T.corners[t3i] = src
+ if (TURF_IS_AMBIENT_LIT_UNSAFE(T))
+ has_ambience = TRUE
// And finally the vertical one.
T = get_step(new_turf, vertical)
if (T)
if (!T.corners)
- T.corners = list(null, null, null, null)
+ T.corners = new(4)
- masters[T] = ((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH) // Get the dir based on coordinates.
- i = LIGHTING_CORNER_DIAGONAL.Find(turn(masters[T], 180))
- T.corners[i] = src
+ t4 = T
+ t4i = REVERSE_LIGHTING_CORNER_DIAGONAL[((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH)] // Get the dir based on coordinates.
+ T.corners[t4i] = src
+ if (TURF_IS_AMBIENT_LIT_UNSAFE(T))
+ has_ambience = TRUE
update_active()
+ if (has_ambience)
+ init_ambient()
+
+#define OVERLAY_PRESENT(T) (T && T.lighting_overlay)
/datum/lighting_corner/proc/update_active()
active = FALSE
- for (var/turf/T in masters)
- if (T.lighting_overlay)
- active = TRUE
+
+ if (OVERLAY_PRESENT(t1) || OVERLAY_PRESENT(t2) || OVERLAY_PRESENT(t3) || OVERLAY_PRESENT(t4))
+ active = TRUE
+
+#undef OVERLAY_PRESENT
+
+#define GET_ABOVE(T) (HasAbove(T:z) ? get_step(T, UP) : null)
+#define GET_BELOW(T) (HasBelow(T:z) ? get_step(T, DOWN) : null)
+
+#define UPDATE_APPARENT(T, CH) T.apparent_##CH = T.self_##CH + T.below_##CH + T.ambient_##CH + T.above_ambient_##CH
+
+/datum/lighting_corner/proc/init_ambient()
+ var/sum_r = 0
+ var/sum_g = 0
+ var/sum_b = 0
+
+ var/turf/T
+ for (var/i in 1 to 4)
+ // this is ugly as fuck, but it's still more legible than doing this with a macro
+ switch (i)
+ if (1) T = t1
+ if (2) T = t2
+ if (3) T = t3
+ if (4) T = t4
+
+ if (!T || !T.ambient_light)
+ continue
+
+ var/list/parts = rgb2num(T.ambient_light)
+
+ sum_r += (parts[1] / 255) * T.ambient_light_multiplier
+ sum_g += (parts[2] / 255) * T.ambient_light_multiplier
+ sum_b += (parts[3] / 255) * T.ambient_light_multiplier
+
+ sum_r /= 4
+ sum_g /= 4
+ sum_b /= 4
+
+ update_ambient_lumcount(sum_r, sum_g, sum_b)
// God that was a mess, now to do the rest of the corner code! Hooray!
-/datum/lighting_corner/proc/update_lumcount(delta_r, delta_g, delta_b)
- lum_r += delta_r
- lum_g += delta_g
- lum_b += delta_b
+/datum/lighting_corner/proc/update_lumcount(delta_r, delta_g, delta_b, now = FALSE)
+ if (!(delta_r + delta_g + delta_b))
+ return
+
+ self_r += delta_r
+ self_g += delta_g
+ self_b += delta_b
+
+ UPDATE_APPARENT(src, r)
+ UPDATE_APPARENT(src, g)
+ UPDATE_APPARENT(src, b)
+
+ var/turf/T
+ var/Ti
+ // Grab the first master that's a Z-turf, if one exists.
+ if ((T = t1?.above) && (T.z_flags & ZM_ALLOW_LIGHTING))
+ Ti = t1i
+ else if ((T = t2?.above) && (T.z_flags & ZM_ALLOW_LIGHTING))
+ Ti = t2i
+ else if ((T = t3?.above) && (T.z_flags & ZM_ALLOW_LIGHTING))
+ Ti = t3i
+ else if ((T = t4?.above) && (T.z_flags & ZM_ALLOW_LIGHTING))
+ Ti = t4i
+ else // Nothing above us that cares about below light.
+ T = null
+
+ if (TURF_IS_DYNAMICALLY_LIT(T))
+ if (!T.corners || !T.corners[Ti])
+ T.generate_missing_corners()
+ var/datum/lighting_corner/above = T.corners[Ti]
+ above.update_below_lumcount(delta_r, delta_g, delta_b, now)
+
+ // This needs to be down here instead of the above if so the lum values are properly updated.
+ if (needs_update)
+ return
- if (!needs_update)
+ if (now)
+ update_overlays(TRUE)
+ else
needs_update = TRUE
SSlighting.corner_queue += src
-/datum/lighting_corner/proc/update_overlays()
- // Cache these values a head of time so 4 individual lighting overlays don't all calculate them individually.
- var/lum_r = src.lum_r > 0 ? LIGHTING_MULT_FACTOR * sqrt(src.lum_r) : src.lum_r
- var/lum_g = src.lum_g > 0 ? LIGHTING_MULT_FACTOR * sqrt(src.lum_g) : src.lum_g
- var/lum_b = src.lum_b > 0 ? LIGHTING_MULT_FACTOR * sqrt(src.lum_b) : src.lum_b
+/datum/lighting_corner/proc/update_below_lumcount(delta_r, delta_g, delta_b, now = FALSE)
+ if (!(delta_r + delta_g + delta_b))
+ return
+
+ below_r += delta_r
+ below_g += delta_g
+ below_b += delta_b
+
+ UPDATE_APPARENT(src, r)
+ UPDATE_APPARENT(src, g)
+ UPDATE_APPARENT(src, b)
+
+ // This needs to be down here instead of the above if so the lum values are properly updated.
+ if (needs_update)
+ return
+
+ if (!now)
+ needs_update = TRUE
+ SSlighting.corner_queue += src
+ else
+ update_overlays(TRUE)
+
+//So, a turf with 4 corners needs to reset all 4 of those to 0, then we need to take turf below and tell its corners to rebuild
+// Probably better ways to do this
+/datum/lighting_corner/proc/clear_below_lumcount()
- var/mx = max(lum_r, lum_g, lum_b) // Scale it so 1 is the strongest lum, if it is above 1.
+ if(!(below_r || below_b || below_g))
+ return
+
+ below_r = 0
+ below_g = 0
+ below_b = 0
+
+ UPDATE_APPARENT(src, r)
+ UPDATE_APPARENT(src, g)
+ UPDATE_APPARENT(src, b)
+
+ if (needs_update)
+ return
+
+ needs_update = TRUE
+ SSlighting.corner_queue += src
+
+/datum/lighting_corner/proc/set_below_lumcount(_r, _g, _b)
+
+ below_r = _r
+ below_g = _g
+ below_b = _b
+
+ UPDATE_APPARENT(src, r)
+ UPDATE_APPARENT(src, g)
+ UPDATE_APPARENT(src, b)
+
+ if (needs_update)
+ return
+
+ needs_update = TRUE
+ SSlighting.corner_queue += src
+
+/datum/lighting_corner/proc/rebuild_above_below_lumcount()
+ //Destroy current state and rebuild it!
+ var/turf/T
+ var/Ti
+ // Grab the first master that's a Z-turf, if one exists.
+ if ((T = t1?.above) && (T.z_flags & ZM_ALLOW_LIGHTING))
+ Ti = t1i
+ else if ((T = t2?.above) && (T.z_flags & ZM_ALLOW_LIGHTING))
+ Ti = t2i
+ else if ((T = t3?.above) && (T.z_flags & ZM_ALLOW_LIGHTING))
+ Ti = t3i
+ else if ((T = t4?.above) && (T.z_flags & ZM_ALLOW_LIGHTING))
+ Ti = t4i
+ else // Nothing above us that cares about below light.
+ T = null
+
+ if (TURF_IS_DYNAMICALLY_LIT(T))
+ if (!T.corners || !T.corners[Ti])
+ T.generate_missing_corners()
+ var/datum/lighting_corner/above = T.corners[Ti]
+ above.set_below_lumcount(self_r, self_g, self_b)
+
+/datum/lighting_corner/proc/update_ambient_lumcount(delta_r, delta_g, delta_b, skip_update = FALSE)
+ ambient_r += delta_r
+ ambient_g += delta_g
+ ambient_b += delta_b
+
+ UPDATE_APPARENT(src, r)
+ UPDATE_APPARENT(src, g)
+ UPDATE_APPARENT(src, b)
+
+ var/turf/T
+ var/Ti
+
+ if (t1)
+ T = t1
+ Ti = t1i
+ else if (t2)
+ T = t2
+ Ti = t2i
+ else if (t3)
+ T = t3
+ Ti = t3i
+ else if (t4)
+ T = t4
+ Ti = t4i
+ else
+ // This should be impossible to reach -- how do we exist without at least one master turf?
+ CRASH("Corner has no masters!")
+
+ var/datum/lighting_corner/below = src
+
+ var/turf/lasT
+
+ // We init before Z-Mimic, cannot rely on above/below.
+ while ((lasT = T) && (T = T.below || GET_BELOW(T)) && (lasT.z_flags & ZM_ALLOW_LIGHTING) && TURF_IS_DYNAMICALLY_LIT_UNSAFE(T))
+ T.ambient_has_indirect = TRUE
+
+ if (!T.corners || !T.corners[Ti])
+ T.generate_missing_corners()
+
+ ASSERT(length(T.corners))
+
+ below = T.corners[Ti]
+ below.above_ambient_r += delta_r
+ below.above_ambient_g += delta_g
+ below.above_ambient_b += delta_b
+
+ UPDATE_APPARENT(below, r)
+ UPDATE_APPARENT(below, g)
+ UPDATE_APPARENT(below, b)
+
+ if (!skip_update && !below.needs_update)
+ below.needs_update = TRUE
+ SSlighting.corner_queue += below
+
+ if (needs_update || skip_update)
+ return
+
+ // Always queue for this, not important enough to hit the synchronous path.
+ needs_update = TRUE
+ SSlighting.corner_queue += src
+
+#undef UPDATE_APPARENT
+
+/datum/lighting_corner/proc/update_overlays(now = FALSE)
+ var/lr = apparent_r
+ var/lg = apparent_g
+ var/lb = apparent_b
+
+ // Cache these values a head of time so 4 individual lighting overlays don't all calculate them individually.
+ var/mx = max(lr, lg, lb) // Scale it so 1 is the strongest lum, if it is above 1.
. = 1 // factor
if (mx > 1)
. = 1 / mx
- #if LIGHTING_SOFT_THRESHOLD != 0
- else if (mx < LIGHTING_SOFT_THRESHOLD)
- . = 0 // 0 means soft lighting.
-
- cache_r = round(lum_r * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
- cache_g = round(lum_g * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
- cache_b = round(lum_b * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
- #else
- cache_r = round(lum_r * ., LIGHTING_ROUND_VALUE)
- cache_g = round(lum_g * ., LIGHTING_ROUND_VALUE)
- cache_b = round(lum_b * ., LIGHTING_ROUND_VALUE)
- #endif
+ cache_r = round(lr * ., LIGHTING_ROUND_VALUE)
+ cache_g = round(lg * ., LIGHTING_ROUND_VALUE)
+ cache_b = round(lb * ., LIGHTING_ROUND_VALUE)
+
cache_mx = round(mx, LIGHTING_ROUND_VALUE)
- for (var/TT in masters)
- var/turf/T = TT
- if (T.lighting_overlay)
- if (!T.lighting_overlay.needs_update)
- T.lighting_overlay.needs_update = TRUE
- SSlighting.overlay_queue += T.lighting_overlay
+ var/turf/T
+ for (var/i in 1 to 4)
+ // this is ugly as fuck, but it's still more legible than doing this with a macro
+ switch (i)
+ if (1) T = t1
+ if (2) T = t2
+ if (3) T = t3
+ if (4) T = t4
+
+ var/atom/movable/lighting_overlay/Ov
+ if (T && (Ov = T.lighting_overlay))
+ if (now)
+ Ov.update_overlay()
+ else if (!Ov.needs_update)
+ Ov.needs_update = TRUE
+ SSlighting.overlay_queue += Ov
+
+/datum/lighting_corner/Destroy(force = FALSE)
+ //PRINT_STACK_TRACE("Someone [force ? "force-" : ""]deleted a lighting corner.")
+ if (!force)
+ return QDEL_HINT_LETMELIVE
+ SSlighting.total_lighting_corners -= 1
+ return ..()
/datum/lighting_corner/dummy/New()
return
diff --git a/code/modules/lighting/lighting_overlay.dm b/code/modules/lighting/lighting_overlay.dm
index ec09a9930ac94..55b3837145f77 100644
--- a/code/modules/lighting/lighting_overlay.dm
+++ b/code/modules/lighting/lighting_overlay.dm
@@ -1,71 +1,84 @@
-var/global/total_lighting_overlays = 0
/atom/movable/lighting_overlay
- name = ""
+ name = ""
+ anchored = TRUE
+ icon = LIGHTING_ICON
+ icon_state = LIGHTING_BASE_ICON_STATE
+ color = LIGHTING_BASE_MATRIX
mouse_opacity = 0
- simulated = FALSE
- anchored = TRUE
- icon = LIGHTING_ICON
- plane = LIGHTING_PLANE
- layer = LIGHTING_LAYER
- invisibility = INVISIBILITY_LIGHTING
- color = LIGHTING_BASE_MATRIX
- icon_state = "light1"
- blend_mode = BLEND_OVERLAY
-
- appearance_flags = DEFAULT_APPEARANCE_FLAGS
-
- var/lum_r = 0
- var/lum_g = 0
- var/lum_b = 0
+ layer = LIGHTING_LAYER
+ plane = LIGHTING_PLANE
+ invisibility = INVISIBILITY_LIGHTING
+ simulated = FALSE
+ blend_mode = BLEND_OVERLAY
var/needs_update = FALSE
-/atom/movable/lighting_overlay/Initialize(mapload, no_update)
- var/turf/turf = loc
- if (!turf.dynamic_lighting)
- atom_flags |= ATOM_FLAG_INITIALIZED
- return INITIALIZE_HINT_QDEL
+ #if WORLD_ICON_SIZE != 32
+ transform = matrix(WORLD_ICON_SIZE / 32, 0, (WORLD_ICON_SIZE - 32) / 2, 0, WORLD_ICON_SIZE / 32, (WORLD_ICON_SIZE - 32) / 2)
+ #endif
+
+/atom/movable/lighting_overlay/Initialize(mapload, update_now = FALSE)
. = ..()
- verbs.Cut()
- total_lighting_overlays++
- turf.lighting_overlay = src
- turf.luminosity = 0
- if (no_update)
- return
- update_overlay()
+ atom_flags |= ATOM_FLAG_INITIALIZED
+ SSlighting.total_lighting_overlays += 1
+
+ var/turf/T = loc // If this runtimes atleast we'll know what's creating overlays in things that aren't turfs.
+ T.lighting_overlay = src
+ T.luminosity = 0
+
+ if (T.corners && length(T.corners))
+ for (var/datum/lighting_corner/C in T.corners)
+ C.active = TRUE
+
+ if (update_now)
+ update_overlay()
+ needs_update = FALSE
+ else
+ needs_update = TRUE
+ SSlighting.overlay_queue += src
+
+/atom/movable/lighting_overlay/Destroy(force = FALSE)
+ if (!force)
+ return QDEL_HINT_LETMELIVE // STOP DELETING ME
+ SSlighting.total_lighting_overlays -= 1
+
+ var/turf/T = loc
+ if (istype(T))
+ T.lighting_overlay = null
+ T.luminosity = 1
+
+ return ..()
+
+// This is a macro PURELY so that the if below is actually readable.
+#define ALL_EQUAL ((rr == gr && gr == br && br == ar) && (rg == gg && gg == bg && bg == ag) && (rb == gb && gb == bb && bb == ab))
/atom/movable/lighting_overlay/proc/update_overlay()
- set waitfor = FALSE
var/turf/T = loc
+ if (!isturf(T)) // Erm...
+ if (loc)
+ warning("A lighting overlay realised its loc was NOT a turf (actual loc: [loc], [loc.type]) in update_overlay() and got deleted!")
- if(!istype(T))
- if(loc)
- log_debug("A lighting overlay realised its loc was NOT a turf (actual loc: [loc][loc ? ", " + loc.type : "null"]) in update_overlay() and got qdel'ed!")
else
- log_debug("A lighting overlay realised it was in nullspace in update_overlay() and got pooled!")
- qdel(src)
- return
- if(!T.dynamic_lighting)
- qdel(src)
- return
+ warning("A lighting overlay realised it was in nullspace in update_overlay() and got deleted!")
- // To the future coder who sees this and thinks
- // "Why didn't he just use a loop?"
- // Well my man, it's because the loop performed like shit.
- // And there's no way to improve it because
- // without a loop you can make the list all at once which is the fastest you're gonna get.
- // Oh it's also shorter line wise.
- // Including with these comments.
+ qdel(src, TRUE)
+ return
// See LIGHTING_CORNER_DIAGONAL in lighting_corner.dm for why these values are what they are.
- // No I seriously cannot think of a more efficient method, fuck off Comic.
- var/datum/lighting_corner/cr = T.corners[3] || dummy_lighting_corner
- var/datum/lighting_corner/cg = T.corners[2] || dummy_lighting_corner
- var/datum/lighting_corner/cb = T.corners[4] || dummy_lighting_corner
- var/datum/lighting_corner/ca = T.corners[1] || dummy_lighting_corner
+ var/list/corners = T.corners
+ var/datum/lighting_corner/cr = dummy_lighting_corner
+ var/datum/lighting_corner/cg = dummy_lighting_corner
+ var/datum/lighting_corner/cb = dummy_lighting_corner
+ var/datum/lighting_corner/ca = dummy_lighting_corner
+ if (corners)
+ cr = corners[3] || dummy_lighting_corner
+ cg = corners[2] || dummy_lighting_corner
+ cb = corners[4] || dummy_lighting_corner
+ ca = corners[1] || dummy_lighting_corner
var/max = max(cr.cache_mx, cg.cache_mx, cb.cache_mx, ca.cache_mx)
+ luminosity = max > 0
var/rr = cr.cache_r
var/rg = cr.cache_g
@@ -83,61 +96,70 @@ var/global/total_lighting_overlays = 0
var/ag = ca.cache_g
var/ab = ca.cache_b
- #if LIGHTING_SOFT_THRESHOLD != 0
- var/set_luminosity = max > LIGHTING_SOFT_THRESHOLD
- #else
- // Because of floating points, it won't even be a flat 0.
- // This number is mostly arbitrary.
- var/set_luminosity = max > 1e-6
- #endif
-
- if((rr & gr & br & ar) && (rg + gg + bg + ag + rb + gb + bb + ab == 8))
- //anything that passes the first case is very likely to pass the second, and addition is a little faster in this case
- icon_state = "transparent"
+ if(rr + rg + rb + gr + gg + gb + br + bg + bb + ar + ag + ab >= 12)
+ icon_state = LIGHTING_TRANSPARENT_ICON_STATE
color = null
- else if(!set_luminosity)
- icon_state = LIGHTING_ICON_STATE_DARK
+ else if (!luminosity)
+ icon_state = LIGHTING_DARKNESS_ICON_STATE
+ color = null
+ else if (rr == LIGHTING_DEFAULT_TUBE_R && rg == LIGHTING_DEFAULT_TUBE_G && rb == LIGHTING_DEFAULT_TUBE_B && ALL_EQUAL)
+ icon_state = LIGHTING_STATION_ICON_STATE
color = null
else
- icon_state = null
- color = list(
- -rr, -rg, -rb, 00,
- -gr, -gg, -gb, 00,
- -br, -bg, -bb, 00,
- -ar, -ag, -ab, 00,
- 01, 01, 01, 01
- )
-
- luminosity = set_luminosity
- // if (T.above && T.above.shadower)
- // T.above.shadower.copy_lighting(src)
+ icon_state = LIGHTING_BASE_ICON_STATE
+ if (islist(color))
+ // Does this even save a list alloc?
+ var/list/c_list = color
+ c_list[CL_MATRIX_RR] = rr
+ c_list[CL_MATRIX_RG] = rg
+ c_list[CL_MATRIX_RB] = rb
+ c_list[CL_MATRIX_GR] = gr
+ c_list[CL_MATRIX_GG] = gg
+ c_list[CL_MATRIX_GB] = gb
+ c_list[CL_MATRIX_BR] = br
+ c_list[CL_MATRIX_BG] = bg
+ c_list[CL_MATRIX_BB] = bb
+ c_list[CL_MATRIX_AR] = ar
+ c_list[CL_MATRIX_AG] = ag
+ c_list[CL_MATRIX_AB] = ab
+ color = c_list
+ else
+ color = list(
+ rr, rg, rb, 0,
+ gr, gg, gb, 0,
+ br, bg, bb, 0,
+ ar, ag, ab, 0,
+ 0, 0, 0, 1
+ )
+
+ // If there's a Z-turf above us, update its shadower.
+ if (T.above)
+ if (T.above.shadower)
+ T.above.shadower.copy_lighting(src)
+ else
+ T.above.update_mimic()
+
+#undef ALL_EQUAL
// Variety of overrides so the overlays don't get affected by weird things.
-/atom/movable/lighting_overlay/ex_act()
+
+/atom/movable/lighting_overlay/ex_act(severity)
+ SHOULD_CALL_PARENT(FALSE)
return
-/atom/movable/lighting_overlay/singularity_pull()
+/atom/movable/lighting_overlay/singularity_act()
return
-/atom/movable/lighting_overlay/Destroy()
- total_lighting_overlays--
- SSlighting.overlay_queue -= src
+/atom/movable/lighting_overlay/singularity_pull()
+ return
- var/turf/T = loc
- if(istype(T))
- T.lighting_overlay = null
+/atom/movable/lighting_overlay/singuloCanEat()
+ return FALSE
- . = ..()
+/atom/movable/lighting_overlay/can_fall()
+ return FALSE
-/atom/movable/lighting_overlay/forceMove()
- //should never move
- //In theory... except when getting deleted :C
+// Override here to prevent things accidentally moving around overlays.
+/atom/movable/lighting_overlay/forceMove(atom/destination, harderforce = FALSE)
if(QDELING(src))
- return ..()
- return 0
-
-/atom/movable/lighting_overlay/Move()
- return 0
-
-/atom/movable/lighting_overlay/throw_at()
- return 0
+ . = ..()
diff --git a/code/modules/lighting/lighting_planemaster.dm b/code/modules/lighting/lighting_planemaster.dm
deleted file mode 100644
index 7d82972cbfc42..0000000000000
--- a/code/modules/lighting/lighting_planemaster.dm
+++ /dev/null
@@ -1,25 +0,0 @@
-/obj/lighting_general
- plane = LIGHTING_PLANE
- screen_loc = "8,8"
-
- icon = LIGHTING_ICON
- icon_state = LIGHTING_ICON_STATE_DARK
-
- color = "#ffffff"
-
- blend_mode = BLEND_MULTIPLY
-
-/obj/lighting_general/Initialize()
- . = ..()
- SetTransform(scale = world.view * 2.2)
-
-/obj/lighting_general/proc/sync(new_colour)
- color = new_colour
-
-/mob
- var/obj/lighting_general/l_general
-
-
-/mob/proc/change_light_colour(new_colour)
- if(l_general)
- l_general.sync(new_colour)
diff --git a/code/modules/lighting/lighting_source.dm b/code/modules/lighting/lighting_source.dm
index 0f9cdeafa6d56..48fcd6b5ae284 100644
--- a/code/modules/lighting/lighting_source.dm
+++ b/code/modules/lighting/lighting_source.dm
@@ -1,17 +1,16 @@
-var/global/total_lighting_sources = 0
// This is where the fun begins.
// These are the main datums that emit light.
/datum/light_source
- var/atom/top_atom // The atom we're emitting light from(for example a mob if we're from a flashlight that's being held).
+ var/atom/top_atom // The atom we're emitting light from (for example a mob if we're from a flashlight that's being held).
var/atom/source_atom // The atom that we belong to.
- var/turf/source_turf // The turf under the above.
- var/light_max_bright = 1 // intensity of the light within the full brightness range. Value between 0 and 1
- var/light_inner_range = 0 // range, in tiles, the light is at full brightness
- var/light_outer_range = 0 // range, in tiles, where the light becomes darkness
- var/light_falloff_curve // adjusts curve for falloff gradient
- var/light_color // The colour of the light, string, decomposed by parse_light_color()
+ var/turf/source_turf // The turf under the above.
+ var/turf/pixel_turf // The turf the top_atom _appears_ to be on
+ var/light_power // Intensity of the emitter light.
+ var/light_range // The range of the emitted light.
+ var/light_color // The colour of the light, string, decomposed by parse_light_color()
+ var/light_angle // The light's emission angle, in degrees.
// Variables for keeping track of the colour.
var/lum_r
@@ -23,310 +22,430 @@ var/global/total_lighting_sources = 0
var/applied_lum_g
var/applied_lum_b
+ // Variables used to keep track of the atom's angle.
+ var/limit_a_x // The first test point's X coord for the cone.
+ var/limit_a_y // The first test point's Y coord for the cone.
+ var/limit_b_x // The second test point's X coord for the cone.
+ var/limit_b_y // The second test point's Y coord for the cone.
+ var/cached_origin_x // The last known X coord of the origin.
+ var/cached_origin_y // The last known Y coord of the origin.
+ var/old_direction // The last known direction of the origin.
+ var/test_x_offset // How much the X coord should be offset due to direction.
+ var/test_y_offset // How much the Y coord should be offset due to direction.
+ var/facing_opaque = FALSE
+
var/list/datum/lighting_corner/effect_str // List used to store how much we're affecting corners.
var/list/turf/affecting_turfs
var/applied = FALSE // Whether we have applied our light yet or not.
- var/vis_update // Whether we should smartly recalculate visibility. and then only update tiles that became(in)visible to us.
- var/needs_update // Whether we are queued for an update.
- var/destroyed // Whether we are destroyed and need to stop emitting light.
- var/force_update
+ var/needs_update = LIGHTING_NO_UPDATE
+
+// This macro will only offset up to 1 tile, but anything with a greater offset is an outlier and probably should handle its own lighting offsets.
+// Anything pixelshifted 16px or more will be considered on the next tile.
+#define GET_APPROXIMATE_PIXEL_DIR(PX, PY) ((!(PX) ? 0 : (((PX) >= 16 ? EAST : ((PX) <= -16 ? WEST : 0)))) | (!(PY) ? 0 : ((PY) >= 16 ? NORTH : ((PY) <= -16 ? SOUTH : 0))))
+#define UPDATE_APPROXIMATE_PIXEL_TURF var/px = top_atom.light_offset_x || top_atom.pixel_x; var/py = top_atom.light_offset_y || top_atom.pixel_y; var/_dir = GET_APPROXIMATE_PIXEL_DIR(px, py); pixel_turf = _dir ? (get_step(source_turf, _dir) || source_turf) : source_turf
+
+// These macros are for dealing with the multi/solo split.
+#define ADD_SOURCE(TARGET) if (!TARGET.light_source_multi && !TARGET.light_source_solo) { TARGET.light_source_solo = src; } else if (TARGET.light_source_solo) { TARGET.light_source_multi = list(TARGET.light_source_solo, src); TARGET.light_source_solo = null; } else { TARGET.light_source_multi += src }
+#define REMOVE_SOURCE(TARGET) if (TARGET.light_source_solo == src) { TARGET.light_source_solo = null } else if (TARGET.light_source_multi) { TARGET.light_source_multi -= src; if (length(TARGET.light_source_multi) == 1) { TARGET.light_source_solo = TARGET.light_source_multi[1]; TARGET.light_source_multi = null; } }
/datum/light_source/New(atom/owner, atom/top)
- total_lighting_sources++
+ SSlighting.total_lighting_sources += 1
source_atom = owner // Set our new owner.
- if(!source_atom.light_sources)
- source_atom.light_sources = list()
- source_atom.light_sources += src // Add us to the lights of our owner.
- top_atom = top
- if(top_atom != source_atom)
- if(!top.light_sources)
- top.light_sources = list()
+ ADD_SOURCE(source_atom)
- top_atom.light_sources += src
+ top_atom = top
+ if (top_atom != source_atom)
+ ADD_SOURCE(top_atom)
source_turf = top_atom
- light_max_bright = source_atom.light_max_bright
- light_inner_range = source_atom.light_inner_range
- light_outer_range = source_atom.light_outer_range
- light_falloff_curve = source_atom.light_falloff_curve
+ UPDATE_APPROXIMATE_PIXEL_TURF
+ light_power = source_atom.light_power
+ light_range = source_atom.light_range
light_color = source_atom.light_color
+ light_angle = source_atom.light_wedge
parse_light_color()
- effect_str = list()
- affecting_turfs = list()
-
update()
-
- return ..()
-
-/* lighting debugging verb
-/mob/verb/self_light()
- set name = "set self light"
- set category = "Light"
- var/v1 = input(usr, "Enter max bright", "max bright", 1) as num|null
- var/v2 = input(usr, "Enter inner range", "inner range", 0.1) as num|null
- var/v3 = input(usr, "Enter outer range", "outer range", 4) as num|null
- var/v4 = input(usr, "Enter curve", "curve", 2) as num|null
- set_light(v1, v2, v3, v4, "#0066ff")
-*/
-
// Kill ourselves.
-/datum/light_source/proc/destroy()
- total_lighting_sources--
- destroyed = TRUE
- force_update()
- if(source_atom && source_atom.light_sources)
- source_atom.light_sources -= src
-
- if(top_atom && top_atom.light_sources)
- top_atom.light_sources -= src
-
-// Call it dirty, I don't care.
-// This is here so there's no performance loss on non-instant updates from the fact that the engine can also do instant updates.
-// If you're wondering what's with the "BYOND" argument: BYOND won't let me have a() macro that has no arguments :|.
-#define effect_update(BYOND) \
- if(!needs_update) \
- { \
- SSlighting.light_queue += src; \
- needs_update = TRUE; \
+/datum/light_source/Destroy(force)
+ SSlighting.total_lighting_sources -= 1
+
+ remove_lum()
+ if (source_atom)
+ REMOVE_SOURCE(source_atom)
+
+ if (top_atom)
+ REMOVE_SOURCE(top_atom)
+
+ . = ..()
+ if (!force)
+ return QDEL_HINT_IWILLGC
+
+#ifdef USE_INTELLIGENT_LIGHTING_UPDATES
+// Picks either scheduled or instant updates based on current server load.
+#define INTELLIGENT_UPDATE(level) \
+ var/_should_update = needs_update == LIGHTING_NO_UPDATE; \
+ if (needs_update < level) { \
+ needs_update = level; \
+ } \
+ if (_should_update) { \
+ if (world.tick_usage > (Master.current_ticklimit/2) || light_range > LIGHTING_MAXIMUM_INSTANT_RANGE || SSlighting.force_queued) { \
+ SSlighting.light_queue += src; \
+ } \
+ else { \
+ SSlighting.total_instant_updates += 1; \
+ update_corners(TRUE); \
+ needs_update = LIGHTING_NO_UPDATE; \
+ } \
}
+#else
+#define INTELLIGENT_UPDATE(level) \
+ if (needs_update == LIGHTING_NO_UPDATE) \
+ SSlighting.light_queue += src; \
+ if (needs_update < level) \
+ needs_update = level;
+#endif
// This proc will cause the light source to update the top atom, and add itself to the update queue.
/datum/light_source/proc/update(atom/new_top_atom)
// This top atom is different.
- if(new_top_atom && new_top_atom != top_atom)
+ if (new_top_atom && new_top_atom != top_atom)
if(top_atom != source_atom) // Remove ourselves from the light sources of that top atom.
- top_atom.light_sources -= src
+ REMOVE_SOURCE(top_atom)
top_atom = new_top_atom
- if(top_atom != source_atom)
- if(!top_atom.light_sources)
- top_atom.light_sources = list()
+ if (top_atom != source_atom)
+ ADD_SOURCE(top_atom) // Add ourselves to the light sources of our new top atom.
- top_atom.light_sources += src // Add ourselves to the light sources of our new top atom.
-
- effect_update(null)
+ INTELLIGENT_UPDATE(LIGHTING_CHECK_UPDATE)
// Will force an update without checking if it's actually needed.
/datum/light_source/proc/force_update()
- force_update = 1
-
- effect_update(null)
+ INTELLIGENT_UPDATE(LIGHTING_FORCE_UPDATE)
// Will cause the light source to recalculate turfs that were removed or added to visibility only.
/datum/light_source/proc/vis_update()
- vis_update = 1
+ INTELLIGENT_UPDATE(LIGHTING_VIS_UPDATE)
- effect_update(null)
+// Decompile the hexadecimal colour into lumcounts of each perspective.
+/datum/light_source/proc/parse_light_color()
+ if (light_color)
+ var/list/parts = rgb2num(light_color)
+ ASSERT(length(parts) == 3)
+ lum_r = parts[1] / 255
+ lum_g = parts[2] / 255
+ lum_b = parts[3] / 255
+ else
+ lum_r = 1
+ lum_g = 1
+ lum_b = 1
-// Will check if we actually need to update, and update any variables that may need to be updated.
-/datum/light_source/proc/check()
- if(!source_atom || !light_outer_range || !light_max_bright)
- destroy()
- return 1
+#define POLAR_TO_CART_X(R,T) ((R) * cos(T))
+#define POLAR_TO_CART_Y(R,T) ((R) * sin(T))
+#define DETERMINANT(A_X,A_Y,B_X,B_Y) ((A_X)*(B_Y) - (A_Y)*(B_X))
+#define MINMAX(NUM) ((NUM) < 0 ? -round(-(NUM)) : round(NUM))
+#define ARBITRARY_NUMBER 10
+
+/datum/light_source/proc/regenerate_angle(ndir)
+ old_direction = ndir
+
+ var/turf/front = get_step(source_turf, old_direction)
+ facing_opaque = (front && front.has_opaque_atom)
+
+ cached_origin_x = test_x_offset = source_turf.x
+ cached_origin_y = test_y_offset = source_turf.y
+
+ if (facing_opaque)
+ return
+
+ var/limit_a_t
+ var/limit_b_t
+
+ var/angle = light_angle * 0.5
+ switch (old_direction)
+ if (NORTH)
+ limit_a_t = angle + 90
+ limit_b_t = -(angle) + 90
+ test_y_offset += 1
+
+ if (SOUTH)
+ limit_a_t = (angle) - 90
+ limit_b_t = -(angle) - 90
+ test_y_offset -= 1
+
+ if (EAST)
+ limit_a_t = angle
+ limit_b_t = -(angle)
+ test_x_offset += 1
+
+ if (WEST)
+ limit_a_t = angle + 180
+ limit_b_t = -(angle) - 180
+ test_x_offset -= 1
+
+ // Convert our angle + range into a vector.
+ limit_a_x = POLAR_TO_CART_X(light_range + ARBITRARY_NUMBER, limit_a_t)
+ limit_a_x = MINMAX(limit_a_x)
+ limit_a_y = POLAR_TO_CART_Y(light_range + ARBITRARY_NUMBER, limit_a_t)
+ limit_a_y = MINMAX(limit_a_y)
+ limit_b_x = POLAR_TO_CART_X(light_range + ARBITRARY_NUMBER, limit_b_t)
+ limit_b_x = MINMAX(limit_b_x)
+ limit_b_y = POLAR_TO_CART_Y(light_range + ARBITRARY_NUMBER, limit_b_t)
+ limit_b_y = MINMAX(limit_b_y)
+
+#undef ARBITRARY_NUMBER
+#undef POLAR_TO_CART_X
+#undef POLAR_TO_CART_Y
+#undef MINMAX
+
+/datum/light_source/proc/remove_lum(now = FALSE)
+ applied = FALSE
- if(!top_atom)
- top_atom = source_atom
- . = 1
+ var/thing
+ for (thing in affecting_turfs)
+ var/turf/T = thing
+ LAZYREMOVE(T.affecting_lights, src)
- if(isturf(top_atom))
- if(source_turf != top_atom)
- source_turf = top_atom
- . = 1
- else if(top_atom.loc != source_turf)
- source_turf = top_atom.loc
- . = 1
+ affecting_turfs = null
- if(source_atom.light_max_bright != light_max_bright)
- light_max_bright = source_atom.light_max_bright
- . = 1
+ for (thing in effect_str)
+ var/datum/lighting_corner/C = thing
+ REMOVE_CORNER(C,now)
- if(source_atom.light_inner_range != light_inner_range)
- light_inner_range = source_atom.light_inner_range
- . = 1
+ LAZYREMOVE(C.affecting, src)
- if(source_atom.light_outer_range != light_outer_range)
- light_outer_range = source_atom.light_outer_range
- . = 1
+ effect_str = null
- if(source_atom.light_falloff_curve != light_falloff_curve)
- light_falloff_curve = source_atom.light_falloff_curve
- . = 1
+/datum/light_source/proc/recalc_corner(datum/lighting_corner/C, now = FALSE)
+ LAZYINITLIST(effect_str)
+ if (effect_str[C]) // Already have one.
+ REMOVE_CORNER(C,now)
+ effect_str[C] = 0
- if(light_max_bright && light_outer_range && !applied)
- . = 1
+ var/actual_range = light_range
- if(source_atom.light_color != light_color)
- light_color = source_atom.light_color
- parse_light_color()
- . = 1
+ var/Sx = pixel_turf.x
+ var/Sy = pixel_turf.y
+ var/Sz = pixel_turf.z
-// Decompile the hexadecimal colour into lumcounts of each perspective.
-/datum/light_source/proc/parse_light_color()
- if(light_color)
- lum_r = GetRedPart (light_color) / 255
- lum_g = GetGreenPart(light_color) / 255
- lum_b = GetBluePart (light_color) / 255
- else
- lum_r = 1
- lum_g = 1
- lum_b = 1
+ var/height = C.z == Sz ? LIGHTING_HEIGHT : CALCULATE_CORNER_HEIGHT(C.z, Sz)
+ APPLY_CORNER(C, now, Sx, Sy, height)
-// Macro that applies light to a new corner.
-// It is a macro in the interest of speed, yet not having to copy paste it.
-// If you're wondering what's with the backslashes, the backslashes cause BYOND to not automatically end the line.
-// As such this all gets counted as a single line.
-// The braces and semicolons are there to be able to do this on a single line.
-
-#define APPLY_CORNER(C) \
- . = LUM_FALLOFF(C, source_turf); \
- . *= (light_max_bright ** 2); \
- . *= light_max_bright < 0 ? -1:1;\
- effect_str[C] = .; \
- C.update_lumcount \
- ( \
- . * applied_lum_r, \
- . * applied_lum_g, \
- . * applied_lum_b \
- );
-
-// I don't need to explain what this does, do I?
-#define REMOVE_CORNER(C) \
- . = -effect_str[C]; \
- C.update_lumcount \
- ( \
- . * applied_lum_r, \
- . * applied_lum_g, \
- . * applied_lum_b \
- );
-
-// This is the define used to calculate falloff.
-// Assuming a brightness of 1 at range 1, formula should be (brightness = 1 / distance^2)
-// However, due to the weird range factor, brightness = (-(distance - full_dark_start) / (full_dark_start - full_light_end)) ^ light_max_bright
-
-#define LUM_FALLOFF(C, T)(CLAMP01(-((((C.x - T.x) ** 2 +(C.y - T.y) ** 2) ** 0.5 - light_outer_range) / max(light_outer_range - light_inner_range, 1))) ** light_falloff_curve)
-
-
-/datum/light_source/proc/apply_lum()
- var/static/update_gen = 1
- applied = 1
-
- // Keep track of the last applied lum values so that the lighting can be reversed
- applied_lum_r = lum_r
- applied_lum_g = lum_g
- applied_lum_b = lum_b
+ UNSETEMPTY(effect_str)
- FOR_DVIEW(var/turf/T, light_outer_range, source_turf, INVISIBILITY_LIGHTING)
- check_t:
- if (!T)
- continue
- if(!T.lighting_corners_initialised)
- T.generate_missing_corners()
+/datum/light_source/proc/update_corners(now = FALSE)
+ var/update = FALSE
- for(var/datum/lighting_corner/C in T.get_corners())
- if(C.update_gen == update_gen)
- continue
+ if (QDELETED(source_atom))
+ qdel(src)
+ return
- C.update_gen = update_gen
- C.affecting += src
+ if (source_atom.light_power != light_power)
+ light_power = source_atom.light_power
+ update = TRUE
- if(!C.active)
- effect_str[C] = 0
- continue
+ if (source_atom.light_range != light_range)
+ light_range = source_atom.light_range
+ update = TRUE
- APPLY_CORNER(C)
+ if (!top_atom)
+ top_atom = source_atom
+ update = TRUE
- LAZYADD(T.affecting_lights, src)
- affecting_turfs += T
+ if (top_atom.loc != source_turf)
+ source_turf = top_atom.loc
+ UPDATE_APPROXIMATE_PIXEL_TURF
+ update = TRUE
- if (T.z_flags & ZM_ALLOW_LIGHTING)
- T = T.below
- goto check_t
+ if (!light_range || !light_power)
+ qdel(src)
+ return
- END_FOR_DVIEW
+ if (isturf(top_atom))
+ if (source_turf != top_atom)
+ source_turf = top_atom
+ UPDATE_APPROXIMATE_PIXEL_TURF
+ update = TRUE
+ else if (top_atom.loc != source_turf)
+ source_turf = top_atom.loc
+ UPDATE_APPROXIMATE_PIXEL_TURF
+ update = TRUE
- update_gen++
+ if (!source_turf)
+ return // Somehow we've got a light in nullspace, no-op.
-/datum/light_source/proc/remove_lum()
- applied = FALSE
+ if (light_range && light_power && !applied)
+ update = TRUE
- for(var/turf/T in affecting_turfs)
- LAZYREMOVE(T.affecting_lights, src)
+ if (source_atom.light_color != light_color)
+ light_color = source_atom.light_color
+ parse_light_color()
+ update = TRUE
+
+ else if (applied_lum_r != lum_r || applied_lum_g != lum_g || applied_lum_b != lum_b)
+ update = TRUE
+
+ if (source_atom.light_wedge != light_angle)
+ light_angle = source_atom.light_wedge
+ update = TRUE
+
+ if (light_angle)
+ var/ndir
+ if (istype(top_atom, /mob) && top_atom:facing_dir)
+ ndir = top_atom:facing_dir
+ else
+ ndir = top_atom.dir
+
+ if (old_direction != ndir) // If our direction has changed, we need to regenerate all the angle info.
+ regenerate_angle(ndir)
+ update = TRUE
+ else // Check if it was just a x/y translation, and update our vars without an regenerate_angle() call if it is.
+ var/co_updated = FALSE
+ if (source_turf.x != cached_origin_x)
+ test_x_offset += source_turf.x - cached_origin_x
+ cached_origin_x = source_turf.x
+
+ co_updated = TRUE
+
+ if (source_turf.y != cached_origin_y)
+ test_y_offset += source_turf.y - cached_origin_y
+ cached_origin_y = source_turf.y
+
+ co_updated = TRUE
+
+ if (co_updated)
+ // We might be facing a wall now.
+ var/turf/front = get_step(source_turf, old_direction)
+ var/new_fo = (front && front.has_opaque_atom)
+ if (new_fo != facing_opaque)
+ facing_opaque = new_fo
+ regenerate_angle(ndir)
+
+ update = TRUE
+
+ if (update)
+ needs_update = LIGHTING_CHECK_UPDATE
+ else if (needs_update == LIGHTING_CHECK_UPDATE)
+ return // No change.
- affecting_turfs.Cut()
+ var/list/datum/lighting_corner/corners = list()
+ var/list/turf/turfs = list()
+ var/thing
+ var/datum/lighting_corner/C
+ var/turf/T
+ var/list/Tcorners
+ var/Sx = pixel_turf.x // these are used by APPLY_CORNER_BY_HEIGHT
+ var/Sy = pixel_turf.y
+ var/Sz = pixel_turf.z
+ var/corner_height = LIGHTING_HEIGHT
+ var/actual_range = (light_angle && facing_opaque) ? light_range * LIGHTING_BLOCKED_FACTOR : light_range
+ var/test_x
+ var/test_y
+
+ var/should_do_wedge = light_angle && !facing_opaque
+
+ FOR_DVIEW(T, Ceilm(actual_range, 1), source_turf, 0) do
+ if (should_do_wedge) // Directional lighting coordinate filter.
+ test_x = T.x - test_x_offset
+ test_y = T.y - test_y_offset
+
+ // If the signs of these are the same, then the point is within the cone.
+ if ((DETERMINANT(limit_a_x, limit_a_y, test_x, test_y) > 0) || DETERMINANT(test_x, test_y, limit_b_x, limit_b_y) > 0)
+ continue
- for(var/datum/lighting_corner/C in effect_str)
- REMOVE_CORNER(C)
+ if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(T) || T.light_source_solo || T.light_source_multi)
+ Tcorners = T.corners
+ if (!T.lighting_corners_initialised)
+ T.lighting_corners_initialised = TRUE
- C.affecting -= src
+ if (!Tcorners)
+ T.corners = list(null, null, null, null)
+ Tcorners = T.corners
- effect_str.Cut()
+ for (var/i = 1 to 4)
+ if (Tcorners[i])
+ continue
-/datum/light_source/proc/recalc_corner(datum/lighting_corner/C)
- if(effect_str.Find(C)) // Already have one.
- REMOVE_CORNER(C)
+ Tcorners[i] = new /datum/lighting_corner(T, LIGHTING_CORNER_DIAGONAL[i], i)
- APPLY_CORNER(C)
+ if (!T.has_opaque_atom)
+ for (var/v in 1 to 4)
+ var/val = Tcorners[v]
+ if (val)
+ corners[val] = 0
-/datum/light_source/proc/smart_vis_update()
- var/list/datum/lighting_corner/corners = list()
- var/list/turf/turfs = list()
- FOR_DVIEW(var/turf/T, light_outer_range, source_turf, 0)
- if (!T)
- continue
- if(!T.lighting_corners_initialised)
- T.generate_missing_corners()
- corners |= T.get_corners()
- turfs += T
-
- var/turf/simulated/open/O = T
- if(istype(O) && O.below)
- // Consider the turf below us as well. (Z-lights)
- for(T = O.below; !isnull(T); T = update_the_turf(T,corners, turfs));
+ turfs += T
+
+ // Upwards lights are handled at the corner level, so only search down.
+ // This is a do-while associated with the FOR_DVIEW above.
+ while (T && (T.z_flags & ZM_ALLOW_LIGHTING) && (T = T.below))
END_FOR_DVIEW
+ LAZYINITLIST(affecting_turfs)
+
var/list/L = turfs - affecting_turfs // New turfs, add us to the affecting lights of them.
affecting_turfs += L
- for(var/turf/T in L)
+ for (thing in L)
+ T = thing
LAZYADD(T.affecting_lights, src)
L = affecting_turfs - turfs // Now-gone turfs, remove us from the affecting lights.
affecting_turfs -= L
- for(var/turf/T in L)
+ for (thing in L)
+ T = thing
LAZYREMOVE(T.affecting_lights, src)
- for(var/datum/lighting_corner/C in corners - effect_str) // New corners
- C.affecting += src
- if(!C.active)
- effect_str[C] = 0
- continue
+ LAZYINITLIST(effect_str)
+ if (needs_update == LIGHTING_VIS_UPDATE)
+ for (thing in corners - effect_str)
+ C = thing
+ LAZYADD(C.affecting, src)
+ if (!C.active)
+ effect_str[C] = 0
+ continue
+
+ APPLY_CORNER_BY_HEIGHT(now)
+ else
+ L = corners - effect_str
+ for (thing in L)
+ C = thing
+ LAZYADD(C.affecting, src)
+ if (!C.active)
+ effect_str[C] = 0
+ continue
+
+ APPLY_CORNER_BY_HEIGHT(now)
+
+ for (thing in corners - L)
+ C = thing
+ if (!C.active)
+ effect_str[C] = 0
+ continue
- APPLY_CORNER(C)
+ APPLY_CORNER_BY_HEIGHT(now)
- for(var/datum/lighting_corner/C in effect_str - corners) // Old, now gone, corners.
- REMOVE_CORNER(C)
- C.affecting -= src
- effect_str -= C
+ L = effect_str - corners
+ for (thing in L)
+ C = thing
+ REMOVE_CORNER(C, now)
+ LAZYREMOVE(C.affecting, src)
+ effect_str -= L
-/datum/light_source/proc/update_the_turf(turf/T, list/datum/lighting_corner/corners, list/turf/turfs)
- if(!T.lighting_corners_initialised)
- T.generate_missing_corners()
- corners |= T.get_corners()
- turfs += T
+ applied_lum_r = lum_r
+ applied_lum_g = lum_g
+ applied_lum_b = lum_b
- var/turf/simulated/open/O = T
- if(istype(O) && O.below)
- return O.below
- return null
+ UNSETEMPTY(effect_str)
+ UNSETEMPTY(affecting_turfs)
-#undef effect_update
-#undef LUM_FALLOFF
-#undef REMOVE_CORNER
-#undef APPLY_CORNER
+#undef INTELLIGENT_UPDATE
+#undef DETERMINANT
+#undef GET_APPROXIMATE_PIXEL_DIR
+#undef UPDATE_APPROXIMATE_PIXEL_TURF
diff --git a/code/modules/lighting/lighting_turf.dm b/code/modules/lighting/lighting_turf.dm
index 8071ca8d4796f..eea95935de39e 100644
--- a/code/modules/lighting/lighting_turf.dm
+++ b/code/modules/lighting/lighting_turf.dm
@@ -1,113 +1,273 @@
-/// Does the turf use dynamic lighting?
-/turf/var/dynamic_lighting = TRUE
-/turf/luminosity = 1
+/turf
+ var/dynamic_lighting = TRUE
+ var/ambient_light // If non-null, a hex RGB light color that should be applied to this turf.
+ var/ambient_light_multiplier = 0.3 // The power of the above is multiplied by this. Setting too high may drown out normal lights on the same turf.
+ luminosity = 1
-/turf/var/lighting_corners_initialised = FALSE
+ var/lighting_corners_initialised = FALSE
-/// List of light sources affecting this turf.
-/turf/var/list/datum/light_source/affecting_lights
-/// Our lighting overlay.
-/turf/var/atom/movable/lighting_overlay/lighting_overlay
-/turf/var/list/datum/lighting_corner/corners
-/turf/var/opaque_counter
+ var/list/datum/light_source/affecting_lights // List of light sources affecting this turf.
+ var/atom/movable/lighting_overlay/lighting_overlay // Our lighting overlay.
+ var/list/datum/lighting_corner/corners
-/turf/set_opacity(new_opacity)
+ var/ambient_has_indirect = FALSE // If this is TRUE, an above turf's ambient light is affecting this turf.
+
+ // Record-keeping, do not touch -- that means you, admins.
+ var/ambient_active = FALSE //! Do we have non-zero ambient light? Use [TURF_IS_AMBIENT_LIT] instead of reading this directly.
+ var/ambient_light_old_r = 0
+ var/ambient_light_old_g = 0
+ var/ambient_light_old_b = 0
+
+ var/ambient_bitflag = 0
+
+//Done on init if mapload, done post copying corners if changeturf
+/turf/proc/setup_local_ambient()
+ return
+
+/turf/Initialize(mapload, ...)
. = ..()
- if(opacity == new_opacity)
- return FALSE
+ if(mapload)
+ setup_local_ambient()
- opacity = new_opacity
- return RecalculateOpacity()
+/turf/proc/set_ambient_light(color, multiplier)
+ if (color == ambient_light && multiplier == ambient_light_multiplier)
+ return
-/turf/proc/RecalculateOpacity()
- var/old_opaque_counter = opaque_counter
+ ambient_light = color || ambient_light
+ ambient_light_multiplier = multiplier || ambient_light_multiplier
+ if (!ambient_light_multiplier)
+ ambient_light_multiplier = initial(ambient_light_multiplier)
- opaque_counter = opacity
- for(var/a in src)
- var/atom/A = a
- opaque_counter += A.opacity
+ update_ambient_light()
- // If the counter changed and was or became 0 then lift event/reconsider lights
- if(opaque_counter != old_opaque_counter && (!opaque_counter || !old_opaque_counter))
- GLOB.opacity_set_event.raise_event(src, !opaque_counter, !!opaque_counter)
- reconsider_lights()
- return TRUE
- return FALSE
+/turf/proc/replace_ambient_light(old_color, new_color, old_multiplier, new_multiplier = 0)
+ if (!TURF_IS_AMBIENT_LIT_UNSAFE(src))
+ add_ambient_light(new_color, new_multiplier)
+ return
+
+ ASSERT(old_multiplier) // omitting new_multiplier is allowed for removing light nondestructively
+
+ old_color ||= COLOR_WHITE
+ new_color ||= COLOR_WHITE
+
+ var/list/old_parts = rgb2num(old_color)
+ var/list/new_parts = rgb2num(new_color)
+
+ var/dr = (new_parts[1] / 255) * new_multiplier - (old_parts[1] / 255) * old_multiplier
+ var/dg = (new_parts[2] / 255) * new_multiplier - (old_parts[2] / 255) * old_multiplier
+ var/db = (new_parts[3] / 255) * new_multiplier - (old_parts[3] / 255) * old_multiplier
+
+ if (!dr && !dg && !db)
+ return
+
+ add_ambient_light_raw(dr, dg, db)
+
+/turf/proc/add_ambient_light(color, multiplier, update = TRUE)
+ if (!color)
+ return
+
+ multiplier ||= ambient_light_multiplier
+
+ var/list/ambient_parts = rgb2num(color)
+
+ var/ambient_r = (ambient_parts[1] / 255) * multiplier
+ var/ambient_g = (ambient_parts[2] / 255) * multiplier
+ var/ambient_b = (ambient_parts[3] / 255) * multiplier
+
+ add_ambient_light_raw(ambient_r, ambient_g, ambient_b, update)
+
+/turf/proc/add_ambient_light_raw(lr, lg, lb, update = TRUE)
+ if (!lr && !lg && !lb)
+ if (!ambient_light_old_r || !ambient_light_old_g || !ambient_light_old_b)
+ ambient_active = FALSE
+ SSlighting.total_ambient_turfs -= 1
+ return
+
+ if (!ambient_active)
+ SSlighting.total_ambient_turfs += 1
+ ambient_active = TRUE
+
+ // There are four corners per (lit) turf, we don't want to apply our light 4 times -- compensate by dividing by 4.
+ lr /= 4
+ lg /= 4
+ lb /= 4
+
+ lr = round(lr, LIGHTING_ROUND_VALUE)
+ lg = round(lg, LIGHTING_ROUND_VALUE)
+ lb = round(lb, LIGHTING_ROUND_VALUE)
+
+ ambient_light_old_r += lr
+ ambient_light_old_g += lg
+ ambient_light_old_b += lb
+
+ if (!corners || !lighting_corners_initialised)
+ generate_missing_corners()
+
+ // This list can contain nulls on things like space turfs -- they only have their neighbors' corners.
+ for (var/datum/lighting_corner/C in corners)
+ C.update_ambient_lumcount(lr, lg, lb, !update)
+
+/turf/proc/clear_ambient_light()
+ if (ambient_light == null)
+ return
+
+ ambient_light = null
+ update_ambient_light()
+
+/turf/proc/update_ambient_light(no_corner_update = FALSE)
+ // These are deltas.
+ var/ambient_r = 0
+ var/ambient_g = 0
+ var/ambient_b = 0
+
+ if (ambient_light)
+ var/list/parts = rgb2num(ambient_light)
+ ambient_r = ((parts[1] / 255) * ambient_light_multiplier) - ambient_light_old_r
+ ambient_g = ((parts[2] / 255) * ambient_light_multiplier) - ambient_light_old_g
+ ambient_b = ((parts[3] / 255) * ambient_light_multiplier) - ambient_light_old_b
+ else
+ ambient_r = -ambient_light_old_r
+ ambient_g = -ambient_light_old_g
+ ambient_b = -ambient_light_old_b
+
+ add_ambient_light_raw(ambient_r, ambient_g, ambient_b, !no_corner_update)
// Causes any affecting light sources to be queued for a visibility update, for example a door got opened.
/turf/proc/reconsider_lights()
- for(var/datum/light_source/L in affecting_lights)
+ var/datum/light_source/L
+ for (var/thing in affecting_lights)
+ L = thing
L.vis_update()
+// Forces a lighting update. Reconsider lights is preferred when possible.
+/turf/proc/force_update_lights()
+ var/datum/light_source/L
+ for (var/thing in affecting_lights)
+ L = thing
+ L.force_update()
+
/turf/proc/lighting_clear_overlay()
- if(lighting_overlay)
- qdel(lighting_overlay)
+ if (lighting_overlay)
+ if (lighting_overlay.loc != src)
+ stack_trace("Lighting overlay variable on turf [log_info_line(src)] is insane, lighting overlay actually located on [log_info_line(lighting_overlay.loc)]!")
+
+ qdel(lighting_overlay, TRUE)
+ lighting_overlay = null
- for(var/datum/lighting_corner/C in corners)
+ for (var/datum/lighting_corner/C in corners)
C.update_active()
// Builds a lighting overlay for us, but only if our area is dynamic.
-/turf/proc/lighting_build_overlay()
- if(lighting_overlay)
- return
+/turf/proc/lighting_build_overlay(now = FALSE)
+ if (lighting_overlay)
+ return //In Cit this wont happen, bay has a slightly different init so just returning is fine
+ //CRASH("Attempted to create lighting_overlay on tile that already had one.")
- var/area/A = loc
- if(A.dynamic_lighting && dynamic_lighting)
- if(!lighting_corners_initialised)
+ if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(src))
+ if (!lighting_corners_initialised || !corners)
generate_missing_corners()
- new /atom/movable/lighting_overlay(src)
+ new /atom/movable/lighting_overlay(src, now)
- for(var/datum/lighting_corner/C in corners)
- if(!C.active) // We would activate the corner, calculate the lighting for it.
- for(var/L in C.affecting)
+ for (var/datum/lighting_corner/C in corners)
+ if (!C.active) // We would activate the corner, calculate the lighting for it.
+ for (var/L in C.affecting)
var/datum/light_source/S = L
- S.recalc_corner(C)
+ S.recalc_corner(C, TRUE)
C.active = TRUE
+// Returns the average color of this tile. Roughly corresponds to the color of a single old-style lighting overlay.
+/turf/proc/get_avg_color()
+ if (!lighting_overlay)
+ return null
+
+ var/lum_r
+ var/lum_g
+ var/lum_b
+
+ for (var/datum/lighting_corner/L in corners)
+ lum_r += L.apparent_r
+ lum_g += L.apparent_g
+ lum_b += L.apparent_b
+
+ lum_r = CLAMP01(lum_r / 4) * 255
+ lum_g = CLAMP01(lum_g / 4) * 255
+ lum_b = CLAMP01(lum_b / 4) * 255
+
+ return "#[num2hex(lum_r)][num2hex(lum_g)][num2hex(lum_b)]"
+
+#define SCALE(targ,min,max) (targ - min) / (max - min)
+
// Used to get a scaled lumcount.
/turf/proc/get_lumcount(minlum = 0, maxlum = 1)
- if(!lighting_overlay)
- var/area/A = loc
- if(A.dynamic_lighting && dynamic_lighting)
- var/atom/movable/lighting_overlay/O = new /atom/movable/lighting_overlay(src)
- lighting_overlay = O
+ if (!lighting_overlay)
+ return 0.5
var/totallums = 0
- for(var/datum/lighting_corner/L in corners)
- totallums += max(L.lum_r, L.lum_g, L.lum_b)
+ for (var/datum/lighting_corner/L in corners)
+ totallums += L.apparent_r + L.apparent_b + L.apparent_g
- totallums /= 4 // 4 corners, max channel selected, return the average
+ totallums /= 12 // 4 corners, each with 3 channels, get the average.
- totallums =(totallums - minlum) /(maxlum - minlum)
+ totallums = SCALE(totallums, minlum, maxlum)
return CLAMP01(totallums)
-// If an opaque movable atom moves around we need to potentially update visibility.
-/turf/Entered(atom/movable/AM, atom/OldLoc)
- . = ..()
- if(AM?.opacity)
- RecalculateOpacity()
+#undef SCALE
+
+// Can't think of a good name, this proc will recalculate the has_opaque_atom variable.
+/turf/proc/recalc_atom_opacity()
+#ifdef AO_USE_LIGHTING_OPACITY
+ var/old = has_opaque_atom
+#endif
+
+ has_opaque_atom = FALSE
+ if (opacity)
+ has_opaque_atom = TRUE
+ else
+ for (var/thing in src) // Loop through every movable atom on our tile
+ var/atom/movable/A = thing
+ if (A.opacity)
+ has_opaque_atom = TRUE
+ break // No need to continue if we find something opaque.
+
+#ifdef AO_USE_LIGHTING_OPACITY
+ if (old != has_opaque_atom)
+ regenerate_ao()
+#endif
-/turf/Exited(atom/movable/AM, atom/newloc)
+/turf/Exited(atom/movable/Obj, atom/newloc)
. = ..()
- if(AM?.opacity)
- RecalculateOpacity()
-/turf/proc/get_corners()
- if(opaque_counter)
- return null // Since this proc gets used in a for loop, null won't be looped though.
+ if (!Obj)
+ return
+
+ if (Obj.opacity)
+ recalc_atom_opacity() // Make sure to do this before reconsider_lights(), incase we're on instant updates.
+ reconsider_lights()
- return corners
+// This block isn't needed now, but it's here if supporting area dyn lighting changes is needed later.
+// /turf/change_area(area/old_area, area/new_area)
+// if (new_area.dynamic_lighting != old_area.dynamic_lighting)
+// if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(src))
+// lighting_build_overlay()
+// else
+// lighting_clear_overlay()
+
+// This is inlined in lighting_source.dm.
+// Update it too if you change this.
/turf/proc/generate_missing_corners()
+ if (!TURF_IS_DYNAMICALLY_LIT_UNSAFE(src) && !light_source_solo && !light_source_multi && !(z_flags & ZM_ALLOW_LIGHTING) && !ambient_light && !ambient_has_indirect)
+ return
+
lighting_corners_initialised = TRUE
- if(!corners)
- corners = list(null, null, null, null)
+ if (!corners)
+ corners = new(4)
- for(var/i = 1 to 4)
- if(corners[i]) // Already have a corner on this direction.
+ for (var/i = 1 to 4)
+ if (corners[i]) // Already have a corner on this direction.
continue
- corners[i] = new /datum/lighting_corner(src, LIGHTING_CORNER_DIAGONAL[i])
+ corners[i] = new/datum/lighting_corner(src, LIGHTING_CORNER_DIAGONAL[i], i)
diff --git a/code/modules/mechs/equipment/engineering.dm b/code/modules/mechs/equipment/engineering.dm
index e2649221cef61..2e71a23d79db0 100644
--- a/code/modules/mechs/equipment/engineering.dm
+++ b/code/modules/mechs/equipment/engineering.dm
@@ -73,7 +73,7 @@
/obj/effect/mech_shield/Initialize()
. = ..()
- set_light(0.8, 0.1, 1, 2, COLOR_SABER_BLUE)
+ set_light(1, 0.8, COLOR_SABER_BLUE)
update_nearby_tiles(need_rebuild=1)
/obj/effect/mech_shield/Destroy()
diff --git a/code/modules/mechs/equipment/utility.dm b/code/modules/mechs/equipment/utility.dm
index 13ac4c7b41e25..312c0eca24110 100644
--- a/code/modules/mechs/equipment/utility.dm
+++ b/code/modules/mechs/equipment/utility.dm
@@ -274,9 +274,8 @@
var/on = 0
- var/l_max_bright = 0.9
- var/l_inner_range = 1
- var/l_outer_range = 6
+ var/l_power = 2
+ var/l_range = 6
origin_tech = list(TECH_MATERIAL = 1, TECH_ENGINEERING = 1)
/obj/item/mech_equipment/light/installed(mob/living/exosuit/_owner)
@@ -304,7 +303,7 @@
/obj/item/mech_equipment/light/on_update_icon()
if(on)
icon_state = "[initial(icon_state)]-on"
- set_light(l_max_bright, l_inner_range, l_outer_range)
+ set_light(l_range, l_power, angle = LIGHT_WIDE)
else
icon_state = "[initial(icon_state)]"
set_light(0, 0)
@@ -793,7 +792,7 @@
. = ..()
if (active)
icon_state = "mech_jet_on"
- set_light(1, 1, 1, l_color = COLOR_LIGHT_CYAN)
+ set_light(1, 1, l_color = COLOR_LIGHT_CYAN)
else
icon_state = "mech_jet_off"
set_light(0)
diff --git a/code/modules/mining/mine_items.dm b/code/modules/mining/mine_items.dm
index 4c0e2409c949a..1e4385d32b21c 100644
--- a/code/modules/mining/mine_items.dm
+++ b/code/modules/mining/mine_items.dm
@@ -241,7 +241,7 @@
addon.layer = ABOVE_LIGHTING_LAYER
addon.plane = EFFECTS_ABOVE_LIGHTING_PLANE
AddOverlays(addon)
- set_light(0.5, 0.5, 3)
+ set_light(3, 0.5)
else
pixel_x = rand(-randpixel, randpixel)
pixel_y = rand(-randpixel, randpixel)
diff --git a/code/modules/mob/dview.dm b/code/modules/mob/dview.dm
new file mode 100644
index 0000000000000..816c757082cce
--- /dev/null
+++ b/code/modules/mob/dview.dm
@@ -0,0 +1,27 @@
+//DVIEW is a hack that uses a mob with darksight in order to find lists of viewable stuff while ignoring darkness
+// Defines for dview are elsewhere.
+
+var/global/mob/dview/dview_mob = new
+
+/mob/dview
+ anchored = TRUE
+ density = FALSE
+ invisibility = INVISIBILITY_ABSTRACT
+ see_in_dark = 1e6
+ simulated = FALSE
+ virtual_mob = null
+
+/mob/dview/Destroy(force = FALSE)
+ SHOULD_CALL_PARENT(FALSE)
+ if (!force)
+ return QDEL_HINT_LETMELIVE
+
+ crash_with("Forced deletion of dview mob, this should not happen! : [log_info_line(src)]")
+
+ dview_mob = new
+ return QDEL_HINT_QUEUE
+
+/mob/dview/Initialize()
+ . = ..()
+ // We don't want to be in any mob lists; we're a dummy not a mob.
+ STOP_PROCESSING_MOB(src)
diff --git a/code/modules/mob/living/bot/bot.dm b/code/modules/mob/living/bot/bot.dm
index 6893de9bdcee0..c1a8d5373fc1d 100644
--- a/code/modules/mob/living/bot/bot.dm
+++ b/code/modules/mob/living/bot/bot.dm
@@ -374,7 +374,7 @@
if(stat)
return 0
on = 1
- set_light(0.5, 0.1, light_strength)
+ set_light(light_strength, 0.5)
update_icons()
resetTarget()
patrol_path = list()
diff --git a/code/modules/mob/living/carbon/alien/diona/nymph_life.dm b/code/modules/mob/living/carbon/alien/diona/nymph_life.dm
index f33a947b6f3b1..0404aa3482044 100644
--- a/code/modules/mob/living/carbon/alien/diona/nymph_life.dm
+++ b/code/modules/mob/living/carbon/alien/diona/nymph_life.dm
@@ -17,7 +17,7 @@
else
var/mult = clamp(radiation/200, 0.5, 1)
if(last_glow != mult)
- set_light(mult, 0.5, (5*mult), 2, "#55ff55")
+ set_light((5*mult), mult, "#55ff55")
last_glow = mult
set_nutrition(clamp(nutrition + floor(radiation/100) + light_amount, 0, 500))
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 2e97b3286c5b0..7c1d7309a6e3a 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1114,6 +1114,40 @@
to_chat(src, SPAN_NOTICE("You look up."))
reset_view(z_eye)
return
+
+ var/turf/T= get_turf(src)
+
+ if(T.is_outside())// They're outside and hopefully on a planet.
+ var/obj/effect/overmap/visitable/sector/exoplanet/E = map_sectors["[T.z]"]
+ if (!istype(E))
+ to_chat(usr, SPAN_NOTICE("You see... things, it's hard to put into words what you're seeing specifically."))
+ return
+
+ //Weather hook here when it is a thing
+
+ // Sun-related output.
+ //Calculate time of day
+ var/time_of_day = E.sun_last_process % E.daycycle
+ var/afternoon = time_of_day > (E.daycycle / 2)
+ var/star_name = GLOB.using_map.system_name
+
+ var/sun_message = null
+ switch(E.sun_position)
+ if(0 to 0.4) // Night
+ sun_message = "It is night time, [star_name] is not visible."
+ if(0.4 to 0.5) // Twilight
+ sun_message = "The sky is in twilight, however [star_name] is not visible."
+ if(0.5 to 0.7) // Sunrise/set.
+ sun_message = "[star_name] is slowly [!afternoon ? "rising from" : "setting on"] the horizon."
+ if(0.7 to 0.9) // Morning/evening
+ sun_message = "[star_name]'s position implies it is currently [!afternoon ? "early" : "late"] in the day."
+ if(0.9 to 1.0) // Noon
+ sun_message = "It's high noon. [star_name] hangs directly above you."
+
+ to_chat(usr, SPAN_NOTICE(sun_message))
+ return
+
+
to_chat(src, SPAN_NOTICE("You can see \the [above ? above : "ceiling"]."))
else
to_chat(src, SPAN_NOTICE("You can't look up right now."))
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index b2dc3683bdaff..fe83411edc3a6 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -231,7 +231,7 @@
set_light(0)
else
if(species.appearance_flags & SPECIES_APPEARANCE_RADIATION_GLOWS)
- set_light(0.3, 0.1, max(1,min(20,radiation/20)), 2, species.get_flesh_colour(src))
+ set_light(max(1,min(20,radiation/20)), 0.3, species.get_flesh_colour(src))
// END DOGSHIT SNOWFLAKE
var/obj/item/organ/internal/diona/nutrients/rad_organ = locate() in internal_organs
@@ -885,7 +885,7 @@
//0.1% chance of playing a scary sound to someone who's in complete darkness
if(isturf(loc) && rand(1,1000) == 1)
var/turf/T = loc
- if(T.get_lumcount() <= LIGHTING_SOFT_THRESHOLD)
+ if(T.get_lumcount() <= 0)
playsound_local(src,pick(GLOB.scarySounds),50, 1, -1)
var/area/A = get_area(src)
diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm
index 8151bd3f7e2f2..a1e459d93a871 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -193,8 +193,6 @@
reset_view(null)
/mob/living/proc/update_sight()
- set_sight(0)
- set_see_in_dark(0)
if(stat == DEAD || eyeobj)
update_dead_sight()
else
@@ -226,3 +224,52 @@
/mob/living/proc/handle_hud_icons_health()
return
+
+//Adaptative darksight
+//Ideally this would run instantly as mob updates are a bit too slow for this (noticeable when moving fast), but set_see_in_dark is called several timees.
+//For the time being it's instant and called whenever see in dark changes. Replace with a single call at end of updates once code is not spaghetti
+/mob/living/proc/handle_darksight()
+ if(!darksight)
+ return
+
+ //For testing purposes
+ var/darksightedness = min(see_in_dark/world.view,1.0) //A ratio of how good your darksight is, from 'nada' to 'really darn good'
+ var/current = darksight.alpha/255 //Our current adjustedness
+ var/adjusted_diameter = (0.5 + (see_in_dark - 1)) * 2
+ var/newScale = min((adjusted_diameter) * (world.icon_size/DARKSIGHT_GRADIENT_SIZE), 1)*0.9 //Scale the darksight gradient
+
+ var/brightness = 0.0 //We'll assume it's superdark if we can't find something else.
+
+
+ //Currently we're going to assume that only thing that matter is your turf
+ //This is not necessarily correct.
+ //We may want to blind people inside exosuits and such. At some point we could try moving lumcount to atom, default to turf and then we can make those override
+
+ var/turf/my_turf = get_turf(src)
+ if(isturf(my_turf))
+ brightness = my_turf.get_lumcount()
+
+ brightness = min((brightness + brightness*brightness), 1) //Increase apparent brightness so it's not that obvious. TODO: Make this a curve
+
+ var/darkness = 1-brightness //Silly, I know, but 'alpha' and 'darkness' go the same direction on a number line
+ newScale *= darkness // you see further in the dark, in fully lit areas you don't get a bonus
+ var/adjust_to = min(darkness,darksightedness)//Capped by how darksighted they are
+ var/distance = abs(current-adjust_to) //Used for how long to animate for
+ var/negative = current > adjust_to //Unfortunately due to a visual issue this must be instant if we go down 1 level of darksight
+
+ if((distance < 0.001) && (abs(darksight.transform.a - newScale) < 0.01)) return //We're already all set
+
+ if(negative)
+ distance = 0 //Make it instant
+
+ //TODO:.
+ //FIX VISION CODE! There is no correct place to update darksight as it keeps being reset and enabled several times a frame (even placing it on Life doesnt work because overrides set it in wrong function)
+ // Time = 0 means instant change, avoids some issues of animation resetting several times a frame
+ distance = 0
+
+ animate(darksight, alpha = (adjust_to*255), transform = matrix().Update(scale_x = newScale, scale_y = newScale), time = (distance*1 SECOND), flags = ANIMATION_LINEAR_TRANSFORM)
+
+//Need to update every time we set see in dark as it can be called at many different points and waiting for next frame causes visual artifacts
+/mob/living/set_see_in_dark(new_see_in_dark)
+ . = ..()
+ handle_darksight()
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 85170b9e950a6..22927c7925d5f 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -256,7 +256,7 @@
/mob/living/proc/IgniteMob()
if(fire_stacks > 0 && !on_fire)
on_fire = 1
- set_light(0.6, 0.1, 4, l_color = COLOR_ORANGE)
+ set_light(4, 0.6, l_color = COLOR_ORANGE)
update_fire()
/mob/living/proc/ExtinguishMob()
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 88c994145126f..017f90e34b8d3 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -449,7 +449,7 @@ var/global/list/ai_verbs_default = list(
camera = A
..()
if(istype(A,/obj/machinery/camera))
- if(camera_light_on) A.set_light(0.5, 0.1, AI_CAMERA_LUMINOSITY)
+ if(camera_light_on) A.set_light(AI_CAMERA_LUMINOSITY, 0.5)
else A.set_light(0)
@@ -599,7 +599,7 @@ var/global/list/ai_verbs_default = list(
src.camera.set_light(0)
if(!camera.light_disabled)
src.camera = camera
- src.camera.set_light(0.5, 0.1, AI_CAMERA_LUMINOSITY)
+ src.camera.set_light(AI_CAMERA_LUMINOSITY, 0.5)
else
src.camera = null
else if(isnull(camera))
@@ -609,7 +609,7 @@ var/global/list/ai_verbs_default = list(
var/obj/machinery/camera/camera = near_range_camera(src.eyeobj)
if(camera && !camera.light_disabled)
src.camera = camera
- src.camera.set_light(0.5, 0.1, AI_CAMERA_LUMINOSITY)
+ src.camera.set_light(AI_CAMERA_LUMINOSITY, 0.5)
camera_light_on = world.timeofday + 1 * 20 // Update the light every 2 seconds.
@@ -709,13 +709,13 @@ var/global/list/ai_verbs_default = list(
icon = selected_sprite.icon
if(stat == DEAD)
icon_state = selected_sprite.dead_icon
- set_light(0.7, 0.1, 1, 2, selected_sprite.dead_light)
+ set_light(1, 0.7, selected_sprite.dead_light)
else if(!has_power())
icon_state = selected_sprite.nopower_icon
- set_light(0.4, 0.1, 1, 2, selected_sprite.nopower_light)
+ set_light(1, 0.4, selected_sprite.nopower_light)
else
icon_state = selected_sprite.alive_icon
- set_light(0.4, 0.1, 1, 2, selected_sprite.alive_light)
+ set_light(1, 0.4, selected_sprite.alive_light)
// Pass lying down or getting up to our pet human, if we're in a rig.
/mob/living/silicon/ai/lay_down()
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 7a5809f98aa20..7a976e2d8aac7 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -73,9 +73,8 @@ GLOBAL_LIST_INIT(possible_say_verbs, list(
var/translator_on = 0 // keeps track of the translator module
- var/flashlight_max_bright = 0.5 //brightness of light when on, must be no greater than 1.
- var/flashlight_inner_range = 1 //inner range of light when on, can be negative
- var/flashlight_outer_range = 3 //outer range of light when on, can be negative
+ var/flashlight_power = 0.5 //brightness of light when on, must be no greater than 1.
+ var/flashlight_range = 3 //outer range of light when on, can be negative
var/light_on = FALSE
hud_type = /datum/hud/pai
@@ -325,7 +324,7 @@ GLOBAL_LIST_INIT(possible_say_verbs, list(
/mob/living/silicon/pai/proc/toggle_integrated_light()
if(!light_on)
- set_light(flashlight_max_bright, flashlight_inner_range, flashlight_outer_range, 2)
+ set_light(flashlight_range, flashlight_power, 2)
to_chat(src, SPAN_NOTICE("You enable your integrated light."))
light_on = TRUE
else
diff --git a/code/modules/mob/living/silicon/robot/drone/drone.dm b/code/modules/mob/living/silicon/robot/drone/drone.dm
index f1aeb74cd0945..1c5da33484c4a 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone.dm
@@ -36,7 +36,7 @@ var/global/list/mob_hat_cache = list()
lawupdate = FALSE
density = TRUE
req_access = list(access_engine, access_robotics)
- integrated_light_max_bright = 0.5
+ integrated_light_power = 0.5
local_transmit = 1
possession_candidate = 1
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index a73a3eff911c9..3ba5e50d0ea5e 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -23,7 +23,7 @@
var/custom_sprite = FALSE
var/crisis //Admin-settable for combat module use.
var/crisis_override = FALSE
- var/integrated_light_max_bright = 0.75
+ var/integrated_light_power = 0.75
var/datum/wires/robot/wires
var/module_category = ROBOT_MODULE_TYPE_GROUNDED
var/dismantle_type = /obj/item/robot_parts/robot_suit
@@ -408,9 +408,9 @@
/mob/living/silicon/robot/proc/update_robot_light()
if(lights_on)
if(intenselight)
- set_light(1, 2, 6)
+ set_light(6, 1)
else
- set_light(0.75, 1, 4)
+ set_light(4, 0.75)
else
set_light(0)
diff --git a/code/modules/mob/living/silicon/robot/robot_items.dm b/code/modules/mob/living/silicon/robot/robot_items.dm
index 73d169f1e8afb..7482a8aae5647 100644
--- a/code/modules/mob/living/silicon/robot/robot_items.dm
+++ b/code/modules/mob/living/silicon/robot/robot_items.dm
@@ -114,7 +114,7 @@
/obj/item/party_light/on_update_icon()
if (activated)
icon_state = "partylight-on"
- set_light(1, 1, 7)
+ set_light(7, 1)
else
icon_state = "partylight_off"
set_light(0)
diff --git a/code/modules/mob/living/simple_animal/constructs/constructs.dm b/code/modules/mob/living/simple_animal/constructs/constructs.dm
index 7b8df30f8f80e..9f06592e52fc0 100644
--- a/code/modules/mob/living/simple_animal/constructs/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs/constructs.dm
@@ -280,7 +280,7 @@
eye_glow.plane = EFFECTS_ABOVE_LIGHTING_PLANE
eye_glow.layer = EYE_GLOW_LAYER
AddOverlays(eye_glow)
- set_light(-2, 0.1, 1.5, l_color = "#ffffff")
+ set_light(1.5, -2, l_color = "#ffffff")
z_flags |= ZMM_MANGLE_PLANES
////////////////HUD//////////////////////
diff --git a/code/modules/mob/living/simple_animal/hostile/bluespace.dm b/code/modules/mob/living/simple_animal/hostile/bluespace.dm
index 0fb7387aa20b4..3b7a343c0e93c 100644
--- a/code/modules/mob/living/simple_animal/hostile/bluespace.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bluespace.dm
@@ -16,8 +16,8 @@
say_list = /datum/say_list/bluespace
natural_weapon = /obj/item/natural_weapon/bluespace
light_color = "#4da6ff"
- light_outer_range = 2
- light_max_bright = 1
+ light_range = 2
+ light_power = 1
bleed_colour = "#0000ff"
/mob/living/simple_animal/hostile/bluespace/Process_Spacemove()
diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider/nurse.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider/nurse.dm
index f5ad5d0b47713..631de88335e2c 100644
--- a/code/modules/mob/living/simple_animal/hostile/giant_spider/nurse.dm
+++ b/code/modules/mob/living/simple_animal/hostile/giant_spider/nurse.dm
@@ -209,7 +209,7 @@
/obj/effect/spider/stickyweb/dark/Initialize()
. = ..()
- set_light(-1, 0.5, 1, 1, l_color = "#ffffff")
+ set_light(1, -1, l_color = "#ffffff")
// The AI for nurse spiders. Wraps things in webs by 'attacking' them.
/datum/ai_holder/simple_animal/melee/nurse_spider
diff --git a/code/modules/mob/living/simple_animal/hostile/vagrant.dm b/code/modules/mob/living/simple_animal/hostile/vagrant.dm
index 0aad8485596ec..d0b94a707501d 100644
--- a/code/modules/mob/living/simple_animal/hostile/vagrant.dm
+++ b/code/modules/mob/living/simple_animal/hostile/vagrant.dm
@@ -111,7 +111,7 @@
else //It's fight time
alpha = 255
icon_state = "vagrant_glowing"
- set_light(0.2, 0.1, 3)
+ set_light(3, 0.2)
move_to_delay = 2
/mob/living/simple_animal/hostile/vagrant/swarm/Initialize()
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index 2de61bafa0d8c..496e6e9c2a982 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -92,8 +92,8 @@
if(eyeobj)
eyeobj.possess(src)
- l_general = new()
- client.screen += l_general
+ darksight = new()
+ client.screen += darksight
CreateRenderers()
@@ -118,3 +118,10 @@
. = ..()
if(internals && internal)
internals.icon_state = "internal1"
+
+/mob/observer/ghost/Login()
+ . = ..()
+ if(darksight)
+ darksight.icon_state = "ghost"
+ darksight.alpha = 127
+ darksight.SetTransform(2) //Max darksight
diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm
index 05d4206efcb14..1bf481795d7c5 100644
--- a/code/modules/mob/logout.dm
+++ b/code/modules/mob/logout.dm
@@ -4,11 +4,10 @@
log_access("Logout: [key_name(src)]")
handle_admin_logout()
if(my_client)
- my_client.screen -= l_general
-
+ my_client.screen -= darksight
RemoveRenderers()
- QDEL_NULL(l_general)
+ QDEL_NULL(darksight)
hide_client_images()
..()
diff --git a/code/modules/modular_computers/computers/modular_computer/core.dm b/code/modules/modular_computers/computers/modular_computer/core.dm
index de518efca29bc..71907054b189e 100644
--- a/code/modules/modular_computers/computers/modular_computer/core.dm
+++ b/code/modules/modular_computers/computers/modular_computer/core.dm
@@ -86,7 +86,7 @@
AddOverlays(os.get_keyboard_overlay())
if(enabled)
- set_light(0.2, 0.1, light_strength)
+ set_light(light_strength, 0.2)
else
set_light(0)
diff --git a/code/modules/modular_computers/computers/subtypes/dev_console.dm b/code/modules/modular_computers/computers/subtypes/dev_console.dm
index 1cdd458090d66..93dc31bab52e8 100644
--- a/code/modules/modular_computers/computers/subtypes/dev_console.dm
+++ b/code/modules/modular_computers/computers/subtypes/dev_console.dm
@@ -48,7 +48,7 @@
var/datum/extension/interactive/ntos/os = get_extension(src, /datum/extension/interactive/ntos)
if(os)
if(os.on)
- set_light(light_max_bright_on, light_inner_range_on, light_outer_range_on, 2, light_color)
+ set_light(light_range_on, light_power_on, light_color)
else
set_light(0)
diff --git a/code/modules/multiz/zmimic/mimic_movable.dm b/code/modules/multiz/zmimic/mimic_movable.dm
index 81ed570387bc9..fe780df31e673 100644
--- a/code/modules/multiz/zmimic/mimic_movable.dm
+++ b/code/modules/multiz/zmimic/mimic_movable.dm
@@ -79,10 +79,11 @@
name = "openspace multiplier"
desc = "You shouldn't see this."
icon = 'icons/effects/lighting_overlay.dmi'
- icon_state = "dark"
+ icon_state = LIGHTING_TRANSPARENT_ICON_STATE
plane = OPENTURF_MAX_PLANE
layer = MIMICED_LIGHTING_LAYER
- color = "#0000004b"
+ color = SHADOWER_DARKENING_COLOR
+ //blend_mode = BLEND_MULTIPLY
/atom/movable/openspace/multiplier/Destroy()
var/turf/myturf = loc
@@ -96,38 +97,36 @@
layer = MIMICED_LIGHTING_LAYER
plane = OPENTURF_MAX_PLANE
invisibility = 0
- blend_mode = BLEND_MULTIPLY
- if (icon_state == null)
+
+ if (icon_state == LIGHTING_BASE_ICON_STATE)
+ blend_mode = BLEND_MULTIPLY
// We're using a color matrix, so just darken the colors across the board.
- // Bay stores lights as inverted so the lighting PM can invert it for darksight, but
- // we don't have a plane master, so invert it again.
var/list/c_list = color
- c_list[CL_MATRIX_RR] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_RG] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_RB] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_GR] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_GG] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_GB] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_BR] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_BG] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_BB] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_AR] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_AG] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_AB] *= -SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_RR] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_RG] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_RB] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_GR] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_GG] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_GB] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_BR] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_BG] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_BB] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_AR] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_AG] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_AB] *= SHADOWER_DARKENING_FACTOR
color = c_list
+
+ //Hackfix until I look into planes a bit more, we copy the plane of turf
+ var/turf/myturf = loc
+ if (istype(myturf))
+ plane = myturf.plane
else
- // Not a color matrix, so we just ignore the lighting values.
- icon_state = "dark" // this is actually just a white sprite, which is what this blending needs
- color = list(
- SHADOWER_DARKENING_FACTOR, 0, 0,
- 0, SHADOWER_DARKENING_FACTOR, 0,
- 0, 0, SHADOWER_DARKENING_FACTOR
- )
+ // Not a color matrix, so we can just use the color var ourselves.
+ color = (icon_state == LIGHTING_DARKNESS_ICON_STATE) ? COLOR_WHITE : SHADOWER_DARKENING_COLOR
var/turf/parent = loc
ASSERT(isturf(parent))
- if (LAZYLEN(parent.ao_overlays_mimic))
- AddOverlays(parent.ao_overlays_mimic)
+ UpdateOverlays()
if (bound_overlay)
update_above()
diff --git a/code/modules/multiz/zmimic/mimic_turf.dm b/code/modules/multiz/zmimic/mimic_turf.dm
index f708119d10c84..400170e114378 100644
--- a/code/modules/multiz/zmimic/mimic_turf.dm
+++ b/code/modules/multiz/zmimic/mimic_turf.dm
@@ -70,6 +70,23 @@
update_mimic(!mapload) // Only recursively update if the map isn't loading.
+ //Update lights if mapload, else if we're changing turf this will be overriden by corner copy step
+ if(mapload)
+ rebuild_zbleed()
+
+//Force reconsider zbleed
+/turf/proc/rebuild_zbleed()
+ //Only relevant if dynamically lit
+ var/turf/under = GetBelow(src)
+ if(TURF_IS_DYNAMICALLY_LIT_UNSAFE(src) && under)
+ //We need to force recalculation of corners regardless, clear first
+ if(corners && length(corners))
+ for (var/datum/lighting_corner/C in corners)
+ C.clear_below_lumcount()
+ if (under.corners && length(under.corners))
+ for (var/datum/lighting_corner/C in under.corners)
+ C.rebuild_above_below_lumcount()
+
/// Cleans up Z-mimic objects for this turf. You shouldn't call this directly 99% of the time.
/turf/proc/cleanup_zmimic()
SSzcopy.openspace_turfs -= 1
diff --git a/code/modules/overmap/exoplanets/_exoplanet.dm b/code/modules/overmap/exoplanets/_exoplanet.dm
index 4b22bddd8ce97..3deb2b495cbcc 100644
--- a/code/modules/overmap/exoplanets/_exoplanet.dm
+++ b/code/modules/overmap/exoplanets/_exoplanet.dm
@@ -15,11 +15,20 @@ GLOBAL_VAR(planet_repopulation_disabled)
var/list/breathgas = list() //list of gases animals/plants require to survive
var/badgas //id of gas that is toxic to life here
- var/lightlevel = 0 //This default makes turfs not generate light. Adjust to have exoplanents be lit.
- var/night = TRUE
- var/daycycle //How often do we change day and night
- var/daycolumn = 0 //Which column's light needs to be updated next?
- var/daycycle_column_delay = 10 SECONDS
+
+ //DAY/NIGHT CYCLE
+ var/daycycle_range = list(15 MINUTES, 30 MINUTES)
+ var/daycycle = 0//How often do we change day and night, at first list, to determine min and max day length
+ var/sun_process_interval = 1.5 MINUTES //How often we update planetary sunlight
+ var/sun_last_process = null // world.time
+
+ /// 0 means midnight, 1 means noon.
+ var/sun_position = 0
+ /// This a multiplier used to apply to the brightness of ambient lighting. 0.3 means 30% of the brightness of the sun.
+ var/sun_brightness_modifier = 0.5
+
+ /// Sun control
+ var/ambient_group_index = -1
var/maxx
var/maxy
@@ -181,22 +190,84 @@ GLOBAL_VAR(planet_repopulation_disabled)
daddy.group_multiplier = Z.air.group_multiplier
Z.air.equalize(daddy)
- if (daycycle)
- if (tick % round(daycycle / wait) == 0)
- night = !night
- daycolumn = 1
- if (daycolumn && tick % round(daycycle_column_delay / wait) == 0)
- update_daynight()
-
-/obj/effect/overmap/visitable/sector/exoplanet/proc/update_daynight()
- var/light = 0.1
- if (!night)
- light = lightlevel
- for (var/turf/simulated/floor/exoplanet/T in block(locate(daycolumn,1,min(map_z)),locate(daycolumn,maxy,max(map_z))))
- T.set_light(light, 0.1, 2)
- daycolumn++
- if (daycolumn > maxx)
- daycolumn = 0
+ if(sun_last_process <= (world.time - sun_process_interval))
+ update_sun()
+
+/obj/effect/overmap/visitable/sector/exoplanet/proc/generate_daycycle()
+ daycycle = rand(daycycle_range[1], daycycle_range[2])
+ update_sun()
+
+// This changes the position of the sun on the planet.
+/obj/effect/overmap/visitable/sector/exoplanet/proc/update_sun()
+ if(sun_last_process == world.time) //For now, calling it several times in same frame is not valid. Add a parameter to ignore this if weather is added
+ return
+ sun_last_process = world.time
+
+ var/time_of_day = (world.time % daycycle) / daycycle //0 to 1 range.
+
+ var/distance_from_noon = abs(time_of_day - 0.5)
+ sun_position = distance_from_noon / 0.5 // -1 to 1 range
+ sun_position = abs(sun_position - 1)
+
+ var/low_brightness = null
+ var/high_brightness = null
+
+ var/low_color = null
+ var/high_color = null
+ var/min = 0
+ var/max = 0
+
+ //Now, each planet type may want to do its own thing for light, if so move most of this code into its own function and override it.
+ switch(sun_position)
+ if(0 to 0.40) // Night
+ low_brightness = 0.01
+ low_color = "#000066"
+
+ high_brightness = 0.2
+ high_color = "#66004d"
+ min = 0
+ max = 0.4
+
+ if(0.40 to 0.50) // Twilight
+ low_brightness = 0.2
+ low_color = "#66004d"
+
+ high_brightness = 0.5
+ high_color = "#cc3300"
+ min = 0.40
+ max = 0.50
+
+ if(0.50 to 0.70) // Sunrise/set
+ low_brightness = 0.5
+ low_color = "#cc3300"
+
+ high_brightness = 0.8
+ high_color = "#ff9933"
+ min = 0.50
+ max = 0.70
+
+ if(0.70 to 1.00) // Noon
+ low_brightness = 0.8
+ low_color = "#dddddd"
+
+ high_brightness = 1.0
+ high_color = "#ffffff"
+ min = 0.70
+ max = 1.0
+
+ //var/interpolate_weight = (abs(min - sun_position)) * 4 Cit interpolation, not sure
+ var/interpolate_weight = (sun_position - min) / (max - min)
+
+ var/new_brightness = (Interpolate(low_brightness, high_brightness, interpolate_weight) ) * sun_brightness_modifier
+
+ //We do a gradient instead of linear interpolation because linear interpolations of colours are unintuitive
+ var/new_color = UNLINT(gradient(low_color, high_color, space = COLORSPACE_HSV, index=interpolate_weight))
+
+ if(ambient_group_index > 0)
+ var/datum/ambient_group/A = SSambient_lighting.ambient_groups[ambient_group_index]
+ A.set_color(new_color, new_brightness)
+ else
+ ambient_group_index = SSambient_lighting.create_ambient_group(new_color, new_brightness)
/obj/effect/overmap/visitable/sector/exoplanet/proc/generate_map()
var/list/grasscolors = plant_colors.Copy()
@@ -231,14 +302,6 @@ GLOBAL_VAR(planet_repopulation_disabled)
for (var/mob/living/simple_animal/A in animals)
adapt_animal(A)
-/obj/effect/overmap/visitable/sector/exoplanet/proc/generate_daycycle()
- if (lightlevel)
- night = FALSE //we start with a day if we have light.
-
- //When you set daycycle ensure that the minimum is larger than [maxx * daycycle_column_delay].
- //Otherwise the right side of the exoplanet can get stuck in a forever day.
- daycycle = rand(10 MINUTES, 40 MINUTES)
-
/obj/effect/landmark/exoplanet_spawn/Initialize()
..()
return INITIALIZE_HINT_LATELOAD
diff --git a/code/modules/overmap/exoplanets/planet_themes/radiation_bombing.dm b/code/modules/overmap/exoplanets/planet_themes/radiation_bombing.dm
index c6323a782aa0c..9cfbfe94d4cb5 100644
--- a/code/modules/overmap/exoplanets/planet_themes/radiation_bombing.dm
+++ b/code/modules/overmap/exoplanets/planet_themes/radiation_bombing.dm
@@ -21,7 +21,7 @@
var/datum/radiation_source/S = new(T, radiation_power, FALSE)
S.range = 4
SSradiation.add_source(S)
- T.set_light(0.4, 1, 2, l_color = PIPE_COLOR_GREEN)
+ T.set_light(2, 0.4, l_color = PIPE_COLOR_GREEN)
for (var/turf/simulated/floor/exoplanet/crater in circlerangeturfs(T, 3))
if (prob(10))
new/obj/item/remains/xeno/charred(crater)
diff --git a/code/modules/overmap/exoplanets/planet_themes/ruined_city.dm b/code/modules/overmap/exoplanets/planet_themes/ruined_city.dm
index 841effa9c5963..964d752068cd0 100644
--- a/code/modules/overmap/exoplanets/planet_themes/ruined_city.dm
+++ b/code/modules/overmap/exoplanets/planet_themes/ruined_city.dm
@@ -22,9 +22,6 @@
for (var/zlevel in E.map_z)
new /datum/random_map/city(null,1,1,zlevel,E.maxx,E.maxy,0,1,1, E.planetary_area)
- if (prob(50))
- E.lightlevel = rand(5,10)/10 //deserts are usually :lit:
-
if (prob(50))
var/datum/exoplanet_theme/robotic_guardians/T = new /datum/exoplanet_theme/robotic_guardians
E.themes += T
diff --git a/code/modules/overmap/exoplanets/planet_types/chlorine.dm b/code/modules/overmap/exoplanets/planet_types/chlorine.dm
index 4b15ab2077924..c641ccc76b428 100644
--- a/code/modules/overmap/exoplanets/planet_types/chlorine.dm
+++ b/code/modules/overmap/exoplanets/planet_types/chlorine.dm
@@ -14,17 +14,11 @@
flora_diversity = 5
fauna_types = list(/mob/living/simple_animal/thinbug, /mob/living/simple_animal/hostile/retaliate/beast/samak/alt, /mob/living/simple_animal/yithian, /mob/living/simple_animal/tindalos, /mob/living/simple_animal/hostile/retaliate/jelly)
megafauna_types = list(/mob/living/simple_animal/hostile/retaliate/jelly/mega)
+ sun_brightness_modifier = 0.5 //The dense atmosphere makes it all dark
/obj/effect/overmap/visitable/sector/exoplanet/chlorine/get_atmosphere_color()
return "#e5f2bd"
-/obj/effect/overmap/visitable/sector/exoplanet/chlorine/generate_map()
- if(prob(50))
- lightlevel = rand(7,10)/10 //It could be night.
- else
- lightlevel = 0.1
- ..()
-
/obj/effect/overmap/visitable/sector/exoplanet/chlorine/generate_atmosphere()
..()
if(atmosphere)
diff --git a/code/modules/overmap/exoplanets/planet_types/desert.dm b/code/modules/overmap/exoplanets/planet_types/desert.dm
index 22dbcbe9c6600..8571f108e1538 100644
--- a/code/modules/overmap/exoplanets/planet_types/desert.dm
+++ b/code/modules/overmap/exoplanets/planet_types/desert.dm
@@ -16,7 +16,7 @@
/obj/effect/overmap/visitable/sector/exoplanet/desert/generate_map()
if(prob(70))
- lightlevel = rand(5,10)/10 //deserts are usually :lit:
+ sun_brightness_modifier = rand(4,8)/10 //deserts are usually :lit:
..()
/obj/effect/overmap/visitable/sector/exoplanet/desert/generate_atmosphere()
diff --git a/code/modules/overmap/exoplanets/planet_types/grass.dm b/code/modules/overmap/exoplanets/planet_types/grass.dm
index a548a8089a8d4..0d35671999305 100644
--- a/code/modules/overmap/exoplanets/planet_types/grass.dm
+++ b/code/modules/overmap/exoplanets/planet_types/grass.dm
@@ -12,11 +12,6 @@
fauna_types = list(/mob/living/simple_animal/yithian, /mob/living/simple_animal/tindalos, /mob/living/simple_animal/hostile/retaliate/jelly)
megafauna_types = list(/mob/living/simple_animal/hostile/retaliate/parrot/space/megafauna, /mob/living/simple_animal/hostile/retaliate/goose/dire)
-/obj/effect/overmap/visitable/sector/exoplanet/grass/generate_map()
- if(prob(40))
- lightlevel = rand(1,7)/10 //give a chance of twilight jungle
- ..()
-
/obj/effect/overmap/visitable/sector/exoplanet/grass/generate_atmosphere()
..()
if(atmosphere)
@@ -76,7 +71,7 @@
rock_colors = list(COLOR_ASTEROID_ROCK, COLOR_GRAY80, COLOR_BROWN)
plant_colors = list("#2f573e","#24574e","#6e9280","#9eab88","#868b58", "#84be7c", "RANDOM")
map_generators = list(/datum/random_map/noise/exoplanet/grass/terraformed)
- lightlevel = 0.5
+ sun_brightness_modifier = 0.8 //Fairly bright
has_trees = TRUE
flora_diversity = 8
fauna_types = list(/mob/living/simple_animal/passive/cat, /mob/living/simple_animal/passive/chicken, /mob/living/simple_animal/passive/mouse, /mob/living/simple_animal/passive/opossum, /mob/living/simple_animal/hostile/retaliate/goat, /mob/living/simple_animal/hostile/retaliate/goose, /mob/living/simple_animal/passive/cow)
@@ -100,10 +95,6 @@
atmosphere.temperature = T0C + rand(0, 50)
atmosphere.update_values()
-/obj/effect/overmap/visitable/sector/exoplanet/grass/generate_map()
- lightlevel = rand(0.7,0.9)/10
- ..()
-
/datum/random_map/noise/exoplanet/grass/terraformed
descriptor = "terraformed grass exoplanet"
flora_prob = 15
diff --git a/code/modules/overmap/exoplanets/planet_types/shrouded.dm b/code/modules/overmap/exoplanets/planet_types/shrouded.dm
index 7c1b939fbeb32..e91c5aaccf2c0 100644
--- a/code/modules/overmap/exoplanets/planet_types/shrouded.dm
+++ b/code/modules/overmap/exoplanets/planet_types/shrouded.dm
@@ -7,7 +7,7 @@
plant_colors = list("#3c5434", "#2f6655", "#0e703f", "#495139", "#394c66", "#1a3b77", "#3e3166", "#52457c", "#402d56", "#580d6d")
map_generators = list(/datum/random_map/noise/exoplanet/shrouded, /datum/random_map/noise/ore/poor)
ruin_tags_blacklist = RUIN_HABITAT
- lightlevel = -0.15
+ sun_brightness_modifier = -0.5
surface_color = "#3e3960"
water_color = "#2b2840"
has_trees = TRUE
diff --git a/code/modules/overmap/exoplanets/planet_types/volcanic.dm b/code/modules/overmap/exoplanets/planet_types/volcanic.dm
index a2adbb188812d..2de90133a7671 100644
--- a/code/modules/overmap/exoplanets/planet_types/volcanic.dm
+++ b/code/modules/overmap/exoplanets/planet_types/volcanic.dm
@@ -94,16 +94,18 @@
turf_flags = TURF_DISALLOW_BLOB
var/list/victims
+ ambient_light_multiplier = 1
+
+/turf/simulated/floor/exoplanet/lava/setup_local_ambient()
+ set_ambient_light(COLOR_ORANGE, 1)
+
/turf/simulated/floor/exoplanet/lava/on_update_icon()
return
-/turf/simulated/floor/exoplanet/lava/Initialize()
- . = ..()
- set_light(0.95, 0.5, 2, l_color = COLOR_ORANGE)
-
/turf/simulated/floor/exoplanet/lava/Destroy()
STOP_PROCESSING(SSobj, src)
. = ..()
+ clear_ambient_light()
/turf/simulated/floor/exoplanet/lava/Entered(atom/movable/AM)
..()
diff --git a/code/modules/overmap/exoplanets/turfs.dm b/code/modules/overmap/exoplanets/turfs.dm
index 1e0b9401c808d..d0abaff9f9f4c 100644
--- a/code/modules/overmap/exoplanets/turfs.dm
+++ b/code/modules/overmap/exoplanets/turfs.dm
@@ -20,8 +20,7 @@
else
initial_gas = list()
temperature = T0C
- //Must be done here, as light data is not fully carried over by ChangeTurf (but overlays are).
- set_light(E.lightlevel, 0.1, 2)
+
if(E.planetary_area && istype(loc, world.area))
ChangeArea(src, E.planetary_area)
..()
@@ -216,6 +215,7 @@
dynamic_lighting = FALSE
icon = null
icon_state = null
+ permit_ao = FALSE
/turf/simulated/planet_edge/Initialize()
. = ..()
diff --git a/code/modules/overmap/ships/computers/helm.dm b/code/modules/overmap/ships/computers/helm.dm
index 3424e05539bdc..a0251cc7effa5 100644
--- a/code/modules/overmap/ships/computers/helm.dm
+++ b/code/modules/overmap/ships/computers/helm.dm
@@ -391,4 +391,4 @@ GLOBAL_LIST_EMPTY(overmap_helm_computers)
set_light(0)
else
icon_state = "tele_nav"
- set_light(light_max_bright_on, light_inner_range_on, light_outer_range_on, 2, light_color)
+ set_light(light_range_on, light_power_on, light_color)
diff --git a/code/modules/overmap/ships/engines/gas_thruster.dm b/code/modules/overmap/ships/engines/gas_thruster.dm
index 1e30a9d949c80..ad2c9a43d8966 100644
--- a/code/modules/overmap/ships/engines/gas_thruster.dm
+++ b/code/modules/overmap/ships/engines/gas_thruster.dm
@@ -206,7 +206,7 @@
/obj/effect/engine_exhaust/New(turf/nloc, ndir)
..(nloc)
nloc.hotspot_expose(1000,125)
- set_light(0.5, 1, 4)
+ set_light(4, 0.5)
set_dir(ndir)
spawn(20)
qdel(src)
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index da4d04bc331df..e3d07abcb582f 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -345,7 +345,7 @@
if(update_state & (UPDATE_OPENED1|UPDATE_OPENED2|UPDATE_BROKE))
set_light(0)
else if(update_state & UPDATE_BLUESCREEN)
- set_light(0.8, 0.1, 1, 2, "#00ecff")
+ set_light(1, 0.8, "#00ecff")
else if(!MACHINE_IS_BROKEN(src) && !GET_FLAGS(stat, MACHINE_STAT_MAINT) && update_state & UPDATE_ALLGOOD)
var/color
switch(charging)
@@ -355,7 +355,7 @@
color = "#a8b0f8"
if(2)
color = "#82ff4c"
- set_light(0.8, 0.1, 1, l_color = color)
+ set_light(1, 0.8, l_color = color)
else
set_light(0)
diff --git a/code/modules/power/fusion/core/core_field.dm b/code/modules/power/fusion/core/core_field.dm
index b280a56e904cf..ef4ba06c051ea 100644
--- a/code/modules/power/fusion/core/core_field.dm
+++ b/code/modules/power/fusion/core/core_field.dm
@@ -68,7 +68,7 @@
, filter(type="outline", size = 2, color = COLOR_RED)
, filter(type="bloom", size=3, offset = 0.5, alpha = 235))
- set_light(light_min_power, light_min_range / 10, light_min_range)
+ set_light(light_min_range, light_min_power)
last_range = light_min_range
last_power = light_min_power
@@ -147,7 +147,7 @@
alpha = 200
if (last_range != use_range || last_power != use_power || color != light_color)
- set_light(min(use_power, 1), use_range / 6, use_range) //cap first arg at 1 to avoid breaking lighting stuff.
+ set_light(use_range, min(use_power, 1)) //cap first arg at 1 to avoid breaking lighting stuff.
last_range = use_range
last_power = use_power
//Temperature based color
@@ -286,7 +286,7 @@
/obj/effect/fusion_em_field/proc/Rupture()
visible_message(SPAN_DANGER("\The [src] shudders like a dying animal before flaring to eye-searing brightness and rupturing!"))
- set_light(1, 0.1, 15, 2, "#ccccff")
+ set_light(15, 1, "#ccccff")
empulse(get_turf(src), ceil(plasma_temperature/1000), ceil(plasma_temperature/300))
sleep(5)
RadiateAll()
diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm
index 69663b349c9e1..10d7ea0bc8637 100644
--- a/code/modules/power/lighting.dm
+++ b/code/modules/power/lighting.dm
@@ -238,6 +238,16 @@
on = powered()
update_icon(FALSE)
+ switch (dir)
+ if(NORTH)
+ light_offset_y = WORLD_ICON_SIZE * 0.5
+ if(SOUTH)
+ light_offset_y = WORLD_ICON_SIZE * -0.5
+ if(EAST)
+ light_offset_x = WORLD_ICON_SIZE * 0.5
+ if(WEST)
+ light_offset_x = WORLD_ICON_SIZE * -0.5
+
/// Fetches the light's color based on area flags. Used for Init and for smartly installing new bulbs during runtime (See light replacers).
/obj/machinery/light/proc/get_color_from_area()
var/light_color = null
@@ -301,14 +311,14 @@
if(current_mode && (current_mode in lightbulb.lighting_modes))
changed = set_light(arglist(lightbulb.lighting_modes[current_mode]))
else
- changed = set_light(lightbulb.b_max_bright, lightbulb.b_inner_range, lightbulb.b_outer_range, lightbulb.b_curve, lightbulb.b_colour)
+ changed = set_light(lightbulb.b_range, lightbulb.b_power, lightbulb.b_colour)
if(trigger && changed && get_status() == LIGHT_OK)
switch_check()
else
update_use_power(POWER_USE_OFF)
set_light(0)
- change_power_consumption((light_outer_range * light_max_bright) * LIGHTING_POWER_FACTOR, POWER_USE_ACTIVE)
+ change_power_consumption((light_range * light_power) * LIGHTING_POWER_FACTOR, POWER_USE_ACTIVE)
/// Returns `lightbulb.status`.
/obj/machinery/light/proc/get_status()
@@ -639,21 +649,15 @@
var/broken_chance = 2
atom_flags = ATOM_FLAG_NO_TEMP_CHANGE | ATOM_FLAG_CAN_BE_PAINTED
- /// Lighting `max_bright` value when turned on.
- var/b_max_bright = 0.9
- /// Lighting `inner_range` value when turned on.
- var/b_inner_range = 1
- /// Lighting `outer_range` value when turned on
- var/b_outer_range = 5
- /// Lighting `curve` value when turned on.
- var/b_curve = 2
+ var/b_power = 0.7
+ var/b_range = 5
/// Lighting `colour` value when turned on.
var/b_colour = LIGHT_COLOUR_WARM
/**
* List of lists. Alternative lighting modes the bulb supports. Entry index should be the `LIGHTMODE_*` type supported, and the value should be a list of `l_*` lighting values to be applied when the mode is enabled.
*
- * Example: `LIGHTMODE_EMERGENCY = list(l_outer_range = 4, l_max_bright = 1, l_color = LIGHT_COLOUR_E_RED)`
+ * Example: `LIGHTMODE_EMERGENCY = list(l_range = 4, l_power = 1, l_color = LIGHT_COLOUR_E_RED)`
*/
var/list/lighting_modes = list()
@@ -691,9 +695,9 @@
item_state = "c_tube"
matter = list(MATERIAL_GLASS = 100, MATERIAL_ALUMINIUM = 20)
- b_outer_range = 5
+ b_range = 5
lighting_modes = list(
- LIGHTMODE_EMERGENCY = list(l_outer_range = 4, l_max_bright = 1, l_color = LIGHT_COLOUR_E_RED),
+ LIGHTMODE_EMERGENCY = list(l_range = 4, l_power = 1, l_color = LIGHT_COLOUR_E_RED),
)
sound_on = 'sound/machines/lightson.ogg'
@@ -719,10 +723,7 @@
/obj/item/light/tube/large
w_class = ITEM_SIZE_SMALL
name = "large light tube"
- b_max_bright = 0.95
- b_inner_range = 2
- b_outer_range = 8
- b_curve = 2.5
+ b_range = 8
/obj/item/light/tube/large/warm
name = "large light tube (warm)"
@@ -752,12 +753,10 @@
broken_chance = 3
matter = list(MATERIAL_GLASS = 100)
- b_max_bright = 0.6
- b_inner_range = 0.1
- b_outer_range = 4
- b_curve = 3
+ b_power = 0.7
+ b_range = 4
lighting_modes = list(
- LIGHTMODE_EMERGENCY = list(l_outer_range = 3, l_max_bright = 1, l_color = LIGHT_COLOUR_E_RED)
+ LIGHTMODE_EMERGENCY = list(l_range = 3, l_power = 1, l_color = LIGHT_COLOUR_E_RED)
)
/obj/item/light/bulb/warm
@@ -782,7 +781,7 @@
/obj/item/light/bulb/red/readylight
lighting_modes = list(
- LIGHTMODE_READY = list(l_outer_range = 5, l_max_bright = 1, l_color = LIGHT_COLOUR_READY)
+ LIGHTMODE_READY = list(l_range = 5, l_power = 1, l_color = LIGHT_COLOUR_READY)
)
/obj/item/light/throw_impact(atom/hit_atom)
diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm
index 4a665e65efe14..e327da37f9d70 100644
--- a/code/modules/power/port_gen.dm
+++ b/code/modules/power/port_gen.dm
@@ -447,7 +447,7 @@
I.blend_mode = BLEND_ADD
I.alpha = round(255*power_output/max_power_output)
AddOverlays(I)
- set_light(0.7, 0.1, rad_power + power_output - max_safe_output, 2, "#3b97ca")
+ set_light(rad_power + power_output - max_safe_output, 0.7, "#3b97ca")
else
set_light(0)
diff --git a/code/modules/power/singularity/containment_field.dm b/code/modules/power/singularity/containment_field.dm
index 506789e4ef7a1..679fcbb0feb8e 100644
--- a/code/modules/power/singularity/containment_field.dm
+++ b/code/modules/power/singularity/containment_field.dm
@@ -10,7 +10,7 @@
unacidable = TRUE
use_power = POWER_USE_OFF
uncreated_component_parts = null
- light_outer_range = 4
+ light_range = 4
movable_flags = MOVABLE_FLAG_PROXMOVE
var/obj/machinery/field_generator/FG1 = null
var/obj/machinery/field_generator/FG2 = null
diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm
index 4b9e7b6e9539e..d5481cb497a30 100644
--- a/code/modules/power/singularity/singularity.dm
+++ b/code/modules/power/singularity/singularity.dm
@@ -8,7 +8,7 @@
anchored = TRUE
density = TRUE
layer = SINGULARITY_LAYER
- light_outer_range = 6
+ light_range = 6
unacidable = TRUE
var/current_size = 1
diff --git a/code/modules/projectiles/effects.dm b/code/modules/projectiles/effects.dm
index 63f271a6247d3..3c50c83c253b3 100644
--- a/code/modules/projectiles/effects.dm
+++ b/code/modules/projectiles/effects.dm
@@ -4,8 +4,8 @@
plane = EFFECTS_ABOVE_LIGHTING_PLANE
layer = BEAM_PROJECTILE_LAYER //Muzzle flashes would be above the lighting plane anyways.
//Standard compiletime light vars aren't working here, so we've made some of our own.
- light_outer_range = 2
- light_max_bright = 1
+ light_range = 2
+ light_power = 1
light_color = "#ff00dc"
mouse_opacity = 0
@@ -75,7 +75,7 @@
// Heavy laser beam
//----------------------------
/obj/effect/projectile/laser/heavy
- light_max_bright = 1
+ light_power = 1
/obj/effect/projectile/laser/heavy/tracer
icon_state = "beam_heavy"
@@ -90,7 +90,7 @@
// Pulse laser beam
//----------------------------
/obj/effect/projectile/laser/pulse
- light_max_bright = 1
+ light_power = 1
light_color = COLOR_DEEP_SKY_BLUE
/obj/effect/projectile/laser/pulse/tracer
@@ -107,7 +107,7 @@
// Skrell laser beam
//----------------------------
/obj/effect/projectile/laser/pulse/skrell
- light_max_bright = 1
+ light_power = 1
light_color = "#4c00ff"
/obj/effect/projectile/laser/pulse/skrell/tracer
@@ -124,7 +124,7 @@
//----------------------------
/obj/effect/projectile/pulse/muzzle
icon_state = "muzzle_pulse"
- light_max_bright = 1
+ light_power = 1
light_color = COLOR_DEEP_SKY_BLUE
//----------------------------
@@ -146,7 +146,7 @@
// Emitter beam
//----------------------------
/obj/effect/projectile/laser/emitter
- light_max_bright = 1
+ light_power = 1
light_color = "#00cc00"
/obj/effect/projectile/laser/emitter/tracer
@@ -178,8 +178,8 @@
//----------------------------
/obj/effect/projectile/bullet/muzzle
icon_state = "muzzle_bullet"
- light_outer_range = 5
- light_max_bright = 1
+ light_range = 5
+ light_power = 1
light_color = COLOR_MUZZLE_FLASH
//----------------------------
@@ -247,7 +247,7 @@
//----------------------------
/obj/effect/projectile/pointdefense
light_color = COLOR_GOLD
- light_max_bright = 1
+ light_power = 1
/obj/effect/projectile/pointdefense/tracer
icon_state = "beam_pointdef_d"
@@ -271,4 +271,4 @@
icon_state = "muzzle_incen"
/obj/effect/projectile/incen/impact
- icon_state = "impact_incen"
\ No newline at end of file
+ icon_state = "impact_incen"
diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/energy.dm
index e3694c1e7ba2e..249e3591e8c05 100644
--- a/code/modules/projectiles/projectile/energy.dm
+++ b/code/modules/projectiles/projectile/energy.dm
@@ -40,7 +40,7 @@
sparks.start()
new /obj/effect/decal/cleanable/ash(src.loc) //always use src.loc so that ash doesn't end up inside windows
- new /obj/effect/effect/smoke/illumination(T, 5, 4, 1, light_colour)
+ new /obj/effect/effect/smoke/illumination(T, 5, 4, 2, light_colour)
//blinds people like the flash round, but in a larger area and can also be used for temporary illumination
/obj/item/projectile/energy/flash/flare
@@ -60,7 +60,7 @@
/obj/item/projectile/energy/flash/flare/on_impact(atom/A)
light_colour = pick("#e58775", "#ffffff", "#faa159", "#e34e0e")
- set_light(1, 2, 6, 1, light_colour)
+ set_light(6, 1, light_colour)
..() //initial flash
//residual illumination
diff --git a/code/modules/psionics/equipment/cerebro_enhancers.dm b/code/modules/psionics/equipment/cerebro_enhancers.dm
index d018984511d28..3ab8a5c9fb92f 100644
--- a/code/modules/psionics/equipment/cerebro_enhancers.dm
+++ b/code/modules/psionics/equipment/cerebro_enhancers.dm
@@ -164,4 +164,4 @@
action_button_name = "Remove Psionic Amplifier"
H.update_action_buttons()
- set_light(0.5, 0.1, 3, 2, l_color = "#880000")
+ set_light(3, 0.5, l_color = "#880000")
diff --git a/code/modules/random_map/drop/droppod.dm b/code/modules/random_map/drop/droppod.dm
index bebfc40d4a308..8bf4cb500d3dd 100644
--- a/code/modules/random_map/drop/droppod.dm
+++ b/code/modules/random_map/drop/droppod.dm
@@ -123,7 +123,7 @@
/datum/random_map/droppod/proc/get_spawned_drop(turf/T)
var/obj/structure/bed/chair/C = new(T)
- C.set_light(0.5, 0.1, 3, 2, l_color = "#cc0000")
+ C.set_light(3, 0.5, l_color = "#cc0000")
var/mob/living/drop
// This proc expects a list of mobs to be passed to the spawner.
// Use the supply pod if you don't want to drop mobs.
diff --git a/code/modules/reagents/heat_sources/thermal_regulator.dm b/code/modules/reagents/heat_sources/thermal_regulator.dm
index 68c9af8bea044..41fcb821023b3 100644
--- a/code/modules/reagents/heat_sources/thermal_regulator.dm
+++ b/code/modules/reagents/heat_sources/thermal_regulator.dm
@@ -129,7 +129,7 @@
AddOverlays(emissive_appearance(icon, "[icon_state]_lights_cold"))
glow_icon.alpha = clamp(temperature - MINIMUM_GLOW_TEMPERATURE, MINIMUM_GLOW_VALUE, MAXIMUM_GLOW_VALUE)
LAZYADD(adding_overlays, glow_icon)
- set_light(0.2, 0.1, 1, l_color = COLOR_GREEN)
+ set_light(1, 0.2, l_color = COLOR_GREEN)
else
set_light(0)
else
diff --git a/code/modules/reagents/reagent_containers/food/drinks/bottle.dm b/code/modules/reagents/reagent_containers/food/drinks/bottle.dm
index 5d695680b18b8..0ef2ef51f7d88 100644
--- a/code/modules/reagents/reagent_containers/food/drinks/bottle.dm
+++ b/code/modules/reagents/reagent_containers/food/drinks/bottle.dm
@@ -119,7 +119,7 @@
if (rag)
var/underlay_image = image(icon='icons/obj/food/drinks.dmi', icon_state=rag.on_fire? "[rag_underlay]_lit" : rag_underlay)
underlays += underlay_image
- set_light(rag.light_max_bright, 0.1, rag.light_outer_range, 2, rag.light_color)
+ set_light(rag.light_range, rag.light_power, rag.light_color)
else
set_light(0)
diff --git a/code/modules/research/message_server.dm b/code/modules/research/message_server.dm
index fc58040f63ca5..c21b9f949f458 100644
--- a/code/modules/research/message_server.dm
+++ b/code/modules/research/message_server.dm
@@ -105,7 +105,7 @@ var/global/list/obj/machinery/message_server/message_servers = list()
playsound(Console.loc, 'sound/machines/twobeep.ogg', 50, 1)
Console.audible_message("[icon2html(Console, viewers(get_turf(Console)))][SPAN_NOTICE("\The [Console] announces: 'Message received from [sender].'")]", hearing_distance = 5)
Console.message_log += "Message from [sender]
[authmsg]"
- Console.set_light(0.3, 0.1, 2)
+ Console.set_light(2, 0.5)
/obj/machinery/message_server/interface_interact(mob/user)
diff --git a/code/modules/shieldgen/shieldwallgen.dm b/code/modules/shieldgen/shieldwallgen.dm
index 3832a7c76b0f3..26cc56bde2575 100644
--- a/code/modules/shieldgen/shieldwallgen.dm
+++ b/code/modules/shieldgen/shieldwallgen.dm
@@ -261,7 +261,7 @@
anchored = TRUE
density = TRUE
unacidable = TRUE
- light_outer_range = 3
+ light_range = 3
var/needs_power = 0
var/active = 1
var/delay = 5
diff --git a/code/modules/shuttles/landmarks.dm b/code/modules/shuttles/landmarks.dm
index 1c2f74004f32f..05c45e096055d 100644
--- a/code/modules/shuttles/landmarks.dm
+++ b/code/modules/shuttles/landmarks.dm
@@ -221,7 +221,7 @@
AddOverlays(image)
pixel_x = rand(-6, 6)
pixel_y = rand(-6, 6)
- set_light(0.7, 0.1, 7, 2, "#85d1ff")
+ set_light(7, 0.7, "#85d1ff")
else
icon_state = initial(icon_state)
ClearOverlays()
diff --git a/code/modules/spells/aoe_turf/conjure/construct.dm b/code/modules/spells/aoe_turf/conjure/construct.dm
index 7cbafc56e200f..703b542954bfb 100644
--- a/code/modules/spells/aoe_turf/conjure/construct.dm
+++ b/code/modules/spells/aoe_turf/conjure/construct.dm
@@ -117,4 +117,4 @@
icon = 'icons/effects/effects.dmi'
icon_state = "m_shield_cult"
light_color = "#b40000"
- light_outer_range = 2
+ light_range = 2
diff --git a/code/modules/spells/racial_wizard.dm b/code/modules/spells/racial_wizard.dm
index d787c2123b45d..2e3008986150c 100644
--- a/code/modules/spells/racial_wizard.dm
+++ b/code/modules/spells/racial_wizard.dm
@@ -59,7 +59,7 @@
return
var/obj/O = new /obj(T)
- O.set_light(-10, 0.1, 10, 2, "#ffffff")
+ O.set_light(10, -10, "#ffffff")
spawn(duration)
qdel(O)
diff --git a/code/modules/supermatter/supermatter.dm b/code/modules/supermatter/supermatter.dm
index 2e7fd46063330..0caebad6954c4 100644
--- a/code/modules/supermatter/supermatter.dm
+++ b/code/modules/supermatter/supermatter.dm
@@ -31,7 +31,7 @@
icon_state = "supermatter"
density = TRUE
anchored = FALSE
- light_outer_range = 4
+ light_range = 4
layer = ABOVE_HUMAN_LAYER
@@ -275,8 +275,8 @@
//Changes color and luminosity of the light to these values if they were not already set
/obj/machinery/power/supermatter/proc/shift_light(lum, clr)
- if(lum != light_outer_range || clr != light_color)
- set_light(1, 0.1, lum, l_color = clr)
+ if(lum != light_range || clr != light_color)
+ set_light(lum, 1, l_color = clr)
/obj/machinery/power/supermatter/proc/get_integrity()
var/integrity = damage / explosion_point
diff --git a/code/modules/xenoarcheaology/effects/hellportal/portals.dm b/code/modules/xenoarcheaology/effects/hellportal/portals.dm
index 92b29c9c9c799..c9f1cef06da13 100644
--- a/code/modules/xenoarcheaology/effects/hellportal/portals.dm
+++ b/code/modules/xenoarcheaology/effects/hellportal/portals.dm
@@ -1,7 +1,7 @@
/obj/effect/gateway/artifact
name = "reality tear"
desc = "A piercing pain strikes your mind as you peer into the tear, witnessing horrors and suffering beyond comprehension."
- light_outer_range=5
+ light_range=5
light_color="#ff0000"
spawnable = list(
/mob/living/simple_animal/hostile/meat/abomination = 5,
@@ -45,7 +45,7 @@
/obj/effect/gateway/artifact/big
name = "interdimensional gateway"
desc = "A huge hole in reality with a strange, pulsing heartbeat. Faint, agonized screams can be heard from inside it..."
- light_outer_range = 10
+ light_range = 10
///Ticks down every so often until portal vanishes.
var/health = 15
///How many mobs we've spawned.
diff --git a/config/example/config.txt b/config/example/config.txt
index 66e295bc4b9dd..4f22546e84923 100644
--- a/config/example/config.txt
+++ b/config/example/config.txt
@@ -327,8 +327,8 @@ EVENT_CUSTOM_START_MAJOR 80;100
## The delay in minutes before an observer that has returned to the main menu may rejoin the game.
#RESPAWN_MENU_DELAY 0
-## Strength of ambient star light. Set to 0 or less to turn off. A value of 1 is unlikely to have a noticeable effect in most lighting systems.
-STARLIGHT 0
+## Enables and disables starlight. This will make space turfs and some turfs considered to be in exterior areas to be lit based on the colour of the background parallax.
+STARLIGHT 1
## Defines which races are allowed to join as ERT, in singular form. If unset, defaults to only human. Casing matters, separate using ;
## Example races include: Human, Tajara, Skrell, Unathi
diff --git a/icons/effects/lighting_overlay.dmi b/icons/effects/lighting_overlay.dmi
index e5e4573b5845ea79bd44d7e465d3800e09fecaa1..b2c14395dd2e718ef4b64797f2ea05bd5c81873b 100644
GIT binary patch
literal 6268
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
E0nOb5(EtDd
literal 6319
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
Ir`F;B19jan82|tP
diff --git a/icons/effects/lighting_overlay.png b/icons/effects/lighting_overlay.png
deleted file mode 100644
index b317268b702d50c33c0099a63d0a0ea94a607744..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1480
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
4T)v{04(X9ii@W)VA;aZy)##zs
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