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 zcmcgx^;;9}*WO0A1L+PyBu3{b1q1;}rCUZwN=goC5$Taq;>gh<>5x!Lx}Gg2D5rROBq=004kWTT9*OF5CUzBPG6zf3gGR?ox8F ziJ70eldt0&S8qR8FHZmT{gF7!=>5W9i zT$))VN3HC}-ADABmStu_O&fx#p_2-Ow*Lg$@zB}7#1u=4*TfkiN{pLN=g9y7#&T_S zHItzH{m|hzd~F}A6;(B#4PV`=yp?q{A|}HLt7^tpZqF}_d>E)sb8?BDo*5+#R0fie z#iQp)uwjk0G?=U7QV23$FTyz!rx- zcPhl)9CnQTTfP3b8ut64>yk>iKp-wSpc`B4rM0TcoT~p&zvx3eupcnAyDc9n;r85) z5Th2P$^j?AXq!Do0O;(bQ7fTcfNrj^C%9N_DOP{;Jj;E3be>*ZngMm!2YZTV4q+{1{aOJkIV2T4@F`}D#rk0<-xl4Bb8-| zzVCd=#YI6(M4@0bMG(2!YFJprDbboXhHOXsZ?!_9fpP-0SS~@XI1#Sq0UB6F(=nuI z0LS>U?^W$o0;g31Y0xF?`l_qxCr&E@D&!>9LdA8`}Nan{WpM@p$=inF{$LaX}h~nFcsikm8+oPPyLWQy{3s?+zuP z5_hO^hrd#ef?&SxC(2J+RH7g`=>;ho^VWiuBljis#M5j zxEvp+mUzQRxld=SHQmGu&4kcq1XRLRcu{UX>Wh>K53*qZVx^4ayj+I%Nsb zf2KdY%tx7M#jDy2qi!d*?tLF!B;@`oh1|%#v%qP@^dE%wl?5-C&*M;@3zH)&jEZpp z?8xf$XA`&qJSgten?5y#97GEAj(l%G3y5TQzEt(3bs@rE-t@^T`|#99s#N_db1FbW zb9^keiX6-a#QN=i)kn4l$Jkk9i)SMcl)S#u`GgE)X@+8?(>$0eMDWVMr^?gm!I83< z=dc0>d0{CgdG{p1aXu`!DIdo0D%KpCstsewB%Dl146jjsi09%u%m`v23xF+FG&%E+ z>9Ptc@_MM-oaPVKlalIGfzgb(*BZLt3C$kq6{(fb27s}Vz6XkKm~Gcd*Bt#}xg2@- zRJ-?63^;_Zw``uwC=)_=ohY0s1(Cu$$z@KiS%Dr?0;M>MoGZ+I4O!%fFl>&A^`M14 zKW599AAJvKq;wo`Qz8#7Rda3kouJ7>2$KyuZkgXCaACO=bB`!IkjY7x0cxO&Ihrh1~PTt7TW_ zUf$RrYzUcUhoBK2IHi6pcuJP+-xPbA_Ks05J=w=-@ihpjPM=wR_Kk`g&xhQA%G0Ud zxKSog8R(l?$uh|@@m<1aTe~0)^=8rZFFHiw4VVixz;mJ>4aWAoQ<@_C;V@i07QEFv zGC*0V$(gRqtPH{S%Hn`Q&kBqpA?<0RJPZG1St^Dn*r+2Wh%6PQ*d)Le|ul4zfh0+zz z5H+={J`){EKZ1wTqJ0*Y`ONOVE7hL2EU8IPb)i&$QefdfOcq4dBhpqxhywrW$>e!G zyCHAUN7=73!IUq?Y8L{``s(U-Ltwogzm*Fc!D~xJ-VZIM+b>sw!TcnIeVxsoy=5yF&XLJfH=foz6{p04j5BFik5+IIdVIrReY`2B2c+b_al*XS zi?qf-#$|DIE)Z;245nD`af|Nph`RI? z)mY0ifuI;_F7Og-8u6oOrj&m|#r>@o_?mD;yV>xn%EH5q@IDUa>j`4HI2I9&%P#5Z zjr?k1iY#+V%pjS0$$6?O@|}*3=w^CDYkRsW)l;%8sk}cR`l6|=$1CbYyNRp?z(D54 zEsQ~ZnU)xRfyp9bXQzscMKdZ7<@6s_Q?~Pe{wOzd<(s>SWj?(~Z%qBX5`24uZ&B_j z4vrAQZX(>6-E^gi^CM#u?xZ9i5BjEswfA4PMzwX4NeVC zz}1G}2;-vSVw9kZgWGrM@rGqfNdYAiC8VAut1@+At?Q43(~E4Xc;Zq1{swo%gm9zz z?6zcs`1JUTy;fG>c>Z|)aF5}~kNHz);X>ihgP(t#If__m7q$czic}y>EW5s)=89P? z5n6rniu95U1A56!g~qPHYr$d=>;#0il)!# zFO;Lq+H8BtmlAaAp6kN*2GXKn1C${IG*nOJY-g5gC*#E6YQs0qyCZqFoU9akN$6Q* z`YA_g$`Te3L;u%aFP;5#lUQ{u-`Qd3H`=Gfr(}M% zyy&@x4GO${>dTrKVN4X3xw$pDIl=<7iu|-^IL$~_pwt^Iqqy;sPp*SsF+UIP6*`SP zq3+I!G1p|@n-k4zwLUBGKlEMP84@P7I2qa7SrGBdcK#Nn53yVtzeHPbC<*@m*_V4Q`-O+PtX~%sT#V=+%#POk_1l?Is-dMmj4DAlMA2`8w34rn3~y9 zkegZC^=7XHbJ4i;Pd>`MH?q=zn*E24@bJ6MK9}OYuH?6$dd-#nSmRKFCWBOH8a3emii)T1=-OQJqlaB*37Di8 zM$}{*C;YU!KC4weB0c4)y5G4Rv>}fk{F0FExj!#8NjNP$LtGX4wa0m8siH;yE4Dn! zNoSa0s9;N}ksi3E#PiWlaWbgXGHa$WpM)~*Kf+M1mw(%}>|lCIIh~>Jf8H6(L4$RR z*3UgxqR6Lb*uOT{2k{hm=!oCA_qe~6Qqe46j9uHNINwxiZPPsXQ>`o*=^nC|^K|TG z*tpWic<9KHH%xYF4H0MfG_tmr0a%E8C)p#hBHXHTapV^40IYSdShPfS%~~8?YaR2B zexy`%02U6cvj7JO21qDLC~a}78U|UlO~UD_^PJ%t-+mzwdMz!v;rm}h%s4pSMsM0P z#F=vhM1G?pp`u~=*hTzGBLzaiuk*s&E3B(0*;o@MW1F`!;^vXm^^Yo_74H$mPD*0E!&LWTm{;oEd$I?bZb-XX44Z18=t^vJU~C=+z6p2grVz>@@0Z6 zC1vzOcE7EM7=cS&b#T*U=_Q%rU|14Bk{uSjOd6D;U{}du@ESd7@Y6<3S42TPNY1k(}I{`RbSm#tIlXWRnOO>cSBc(2=icaSQ1AF(YNznw(TR)C;aEz$xu3dmWQ zDoH?<^%pbZtY4_Sf~K8mtv~7GUMJ||0IuRxzjHOja~Tu!kuhhBT49#n6@3{@zsmm1 zFv5ZCHNT>k^><{zjYJ`6K66O1i$TBI79qJp$jscxjbgyuwuY$Ts)}5|vIMKK|01b> z3siX8HDqT_4T2pE&EAZfwjb|}CplyR3a_MSMe4plTbWn#*E=w~){3Se^m6n*AQ-Ga zh|ZGUgJ5YUsPDR^BvhS^i-W8CFd^Di5SBsT{foZOk;vGNHT11T@Rq`|WlMFV6U{J( zNnmB(3h_1Zw>y-A;c|9C)fzY>s)RrEYBF3&LK%)jZq`S`ZK-Yy3v=SNd9~Mw9kH?3 zBO+MNYA}||?_SWn1pzru2UV2!j@!#Ve{-DSv*2g6^NfSk!d?ldQ$QmtZi66eji8TF z8EZ7^K8n@=4I6E0>+gb8+B$`iG8J1yH-}~W`jFlgBhsXllUl%RNT2mUU;0(no|SjG(uF_xiDvxPn~Ae7jtS0ioS# z_yl#2433kB1H|zN)O}b?yJJUJOa-+uG;u$hU;u9hf3djNg)$VY*G9jd?Jo#Bj3pV(lp$JiKJT7&KGj34+QB>jahe|LlsG$@6q?A68^9qz254D^bIUKc zL8qCxR6$Ldo`{eh%*TZ^#rk(gx6^g0{;1z2slWUUDnKW}Cr8#EEWJZ`YtVNt`cW`@INIQ)r!wo_klQ zLpS`KppKx9#DxeN1Zc*QQ{XuXh3o&&exwcIegv|)!errF*xEOZ_kbswC&cV@a%^(> zK};ugxgN=S0H69>9y_%(8-0d`hZm&egX#y=T!AmiNI2LW^$T%gbRyDzx@D4qOTrTv>3CpvT{Yh%m~G}H8&rx@riT>AMsWn<)Kj4?m z*cJLBFzf8va3^SNh`g$kDPin^fr@0SK`O6fB7IiqYdQn)hFGK74W3<3Yl8OSQ_BYr zng1*M@U63x^rw)tSHfg-1HrQg?K36Yw2yxIz4Tys1k@oC_bO(~Fb?tB&E$DFm7G4X zYApi{-Ac}Y7UX1RH%0F_?v(z}6H!<xCrKzVM-T+Z4sGdwGvY z)&q;O=Ifj;`I5CXiL!*LK3dY_II#14?FAAAnaK1Hg^gu?Xg}s=#2sfAh}$xQlm`Ra z9um<&4julZjC{{4L6<=~}#GkmX-MPEt zztSFtaaDn5`jmBW=MM$`K}w;06rS6N6XG07 zu~u{6!!@`fHd}AxJr{58cvzfoI4gWrG!Za^qWL$JwBwpd@C{{j#2?_f*F?WA5) zfJW-=(PXzu&51?qfn`Jk+v|}B&yy8iMRLRoaa~C&7NH|{N-ecBM|Hls*kBs{g|GYa z;E4e4Jqgdq(4%dtdl4|u-7w~bao$R;mzvsAa*g|IeIh#Y1S{G}2C(5R@(em&^4F2R z_O|T+D|Xmql69)@dW>D9wrR`pl*C=7u66OX|32ke1E2`PGp<)QdARg6K>ZuUDy^oz zku*AkcQe-O)R3NL4wU*OQTTn_$>#Yj$Y45u{=NNBeE3UenBvoqm&F?pwd>mj&j*X`k}PEYn^*pw$^~LKbv-c4^H$>-yD%tSd-* zv6`NiFMXYoOj~tq_{>nK_)i{O1Fb`2vBM1zk|*S!;4>eZ{RG+Rm=juPn5@ftr?m12 z+^i+{s``YoBXJti{P#0q;+;rr3+McE6@@Se!ITU$+3P*l2zAp^p}%c#Q!`t}d-s7F zk8KmQZ+R5qoEqO(tryRKq!Ay4=mokBv@hso^W2;a zu$7EJvo1~@$aP>ABai~`Ukuv`h?XX=Vg1ckwe9s5-|kmxU((A6%#!E`z8xa2!e^&( zLlW?L@H6Yh`8@TT_NBnVTpf>&y$4|hSRIyI{kQnTIA}ND5hK_%pl=m5q7hjWan*XW zy}DK_6452R(K?(5w-xmQg|~H*;Wm%>F 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 zcmV;(1vmPMP);Uvh8NW&Hj9u{oJD8zyJ38|NQOa z|M-u;{hjmky?xmq8}>77#ec@IUtGUa@plf}ajk^;9)r~m<`{nLsNF2OdGx_rY3A96 zWwR9@KGZP&dj?k!_@MxP`89$47XoiqfPb^<8{l_T-46&HRgVq^@fTWi|u4@}5K=9BMP`7XFfwUSsJI_mEWW(PCeh02& zNn|@X0Zjqdt-wdi7E1uvIVX_Dg_zyjxLsR=04I?4)&vMoe@{5NZ|_WYuexV8bdMw_ z&@_QA0ZbqPZ4k(UySG5!=(=I=@@j&;6`0+l<|&{l_`4(sME8`S0Kk+83glVQJ(ocO zgw_Q7g1fb#04kw5DB*&Sq{JmqVAgR4YfH&VwI zYSle5C?OFFa1Vs-P4 zXaf5TCA8^OIRy$ZfTMH3Id(`;0M{oRQ6a0nnn6DWZw0pHIYOQO{TB!C=9 zTLKlqmF*74#$&b#?*-3-r*GJ505g{WM~@J`AxAr~0ShX4KLp0_3qGL>e&9&E;C;iX z-D50?+%U|ZnOjNN23#K{z)g7YNAQCZ*;xVZ5IMN!+HC9|Y74&Y60i*?vEU^!2%q4_ zD)=aPh21}FL*Lt+z;XWUgYXr~0IqNl3VK2`+F=;5gy-I3)xhkE$9y^Qd}G-~rq=C~%w;0NlD`C$y3P7zx59flY=S zvDRK|e!?$kRjfClT@`z|psa^u_)hdgOK@lp?6n~^f#y*H9t87rV-1~fTxZ_rA8US0 zXw`fKps}s4QM*4<0_eK48YZA!@W6iM1lakE;JctYTC>19WTNhEopcxcHuhB5wIn*O z0|HxnVpU6`Yy1HFhXA^7E0FL?@=R~*eDBIUV7Sxc-{H^D_=E6gPB5ncI7HBW<*6y0 z0@gHvJOyxc=6bLY>hpLVXZLBJYaTNh@x-Z(3B*$q=oEoKqk!_jbMR@r8QUdbf}1;^ zTodk9*F$BUa5`kV?(vqJI&U3aZzX{1fP}z+ati22*AyUpTmsyL=$zn!U&-}IJ)dfl z5SX_Zg7*M+FOlrp`2aDE-9KZ31z!_rl<=3PP!nMHt_h4w01ps=^XwkaaUHAce80dA zw^|FrYZ47(_bGv(``1_k?ypf3P`w1uJ>L@-9Ot#`Io<%z^+;V$HNo*JnsvXoz`OCr zO4x!wMpeNv%onEcLZ1|Q0d=2%Q-E(ulz>3JFb;J;_+m{c-}G2{u6K5>^W6gz2*8+{ z0Q?sm9V-()!|ov>3D+Jd@cFhnZf~$DfhEyZ0Ua3y_o_-e1g^AOZg6M!EA|C}FMX8o zCDkqouD8JEHvvn2a%}qk@#-F*A*>I48$GxTgVjpFX7`e?w%}hty};lUZUXohQ}<=U z?)l+>7YR!A`nCy4SbeJjr|xgm*~ z0>LZw1qdj!X#?W(6zLP(pGLUAt-yv`&ua6rd%;WKTOX!&&jPP++HrpG-lDE=>5qS= iJ#V4J*JlhL?0*5J6wAQ~73C2C0000%9~Ax$kqnDu0p%VpC!R007_zxpyi607@GGfV7H<{^t|RE!3btFV3E- z8qV)boQxbT?42#_YyklGlrJEAN0;l%FZO5fO;!aC){&vnFBNLsawOs6P@ zMCU<=SjStm0buUUD55POpa%fZ0Y1EwRCP}|Og(@u3PhGjmR~$ZS*Oq%qB2NWE{N); zZs8aX*YhwxL#5xcg&=dWG4RM_k8@>^+z7%T-@@dmwb?+%#`_JXR;Bv>x4BU=tuZ`1 zvVxQmf4-Fue|ogRkjWO;{8nydVqBIr)dx98n#0gYmri7x_8ee&H0?rZA*|$KnDHF> z;r0v43o7aIWyI`i(hc4UrnEx25=n^q^E>8mC?g&v6!pyCAYRrmfNbR;eV4ZmrQc%@ z1fh~dWRF_?G3KSV)sP%ZNfH}E8GU25UD$?d%h4z$`uOU{SUD)9w8 zk9c$h0{+iqUwRDx+TAUmidKp(ju;L)Kyyn^|GwjcW6ikk--kEEXK|e`fT$#UUqXo1 ze~c|olwwnGey5`4tROxvVd7qjgE~JbSgiw2SX*}RSbglh-5K9ingN{t@9&gJ|6}Zy z6VkhM?h0+Mq*==^>IOs&d^@8BaELiX+7CBxF)i5m`kUgTgkOAf5W$-1(+JOQN$n%} zv?9T_QR>yCO#5WB6Ws-iUtAa2t=QeK)8)iDoa!ClwHy0;gosV#qq0RFsH#D}qEtj( z_tPs@Kkw-C+l#fm;O?BZkz$}Y=}qzUaoN%((bfoq4^uU;G|r>rBgn2Ee%om%(=I(m9@kd1+_WRw*}d z=)2T(3N{Ice?8X_)TE0&bK{2o_*uXfUhj(MQS zU3gF~=}Zs0Tvl($MP7(ESAPdS@ApUIzwY>B_<%Wrh3&y6w7KN!*2~PTZARU1k+w93 zLZFh(XEJpV_sWsA_pS~2v@$IO`09Y)Ycp5$-J{H|ny7bZc3wi($va3jER2x;^u88` zFGJ_EC-v)_z9yRr_^E(%FDI;-VtHO+$_GwL!1MoQ(C$T2Z_oG%vm@n2I#BxcDBNuO zN-W*4T2hV_6UMG(681fFgwO!zUHgRkr+ZnNIGNE;I{39+hidpvrrl1VNOMQuwZ_7^ zM}D6u)#EFhyuF+X%<*ZJ-4!t(F54-KpI|R9V{UTb8cMGjKgmQ|{Xdx|R{qD};u`Je zL}5-s{VB+(5h&t6`jw2Xm+3sLQh8lphCX$>RNo0??esTTsJ8Z+b~? zg={t$^sh(3%C!w+2yEr3?>4dfyehPE#~H+EaRUkp^|qQ~^&WH$W2< z0z^hOzCzP9&F8hnj28B=awJQxAbiauS~i}Qd!6-@5k12` z?oV3ii0%{y$iDvudk`+mr@)GtkOSnSvBS$PB&u@Y??A36f}E=+pkS}aTKbK`xgT7{ z#;czWZ3MfPr#4RZg$}4{I$ZO8k<`5V>FwlO#P$E`(f{1N!>8n*?faSMdpt$2&@~I% zcjjz!Q-C*NcSRkK06W2DM=zR7)MwIuj0({v&l7EMA=WknQzy2&ws2iP2#jV3y$4-tmcWJBnp!q&y9Os z^l~MUWSvOJ-1Ydb^xx3?V?aO(p))WuEhCN>uOBw&^+T(okDB>WmA&cJJ_H%I>WM!w zZ%trU^_)zzrDUYVaPJox!0}LCf!g8n5?+uy=RTV_3}KFo+{$;!@pT4mScid_v=Zk$C*B~ zZWL*7`ZcCH$KiJ~lbhq=63X+E&Y1EN?NBwZ7F%0k$KDB<;5FY@l1<|mtd*}F$iEAgib-wqWvC-EaSQqQlY zHS!30NWpojM?@>K^&&z&8_AZ26@92BVVP=} z%tNmRlzy-~$fbGJr;`?^2kOx5>fS+B>*)&q?6G!_?cbfIa7kc76f(n8{Bg=wc@{+YHaG(f)?>wdw z`C4~g`bycw{-!H8@HE4!zGL)nX_V>!u~h=sU|gt>{T+gx4E{sd*{ zIB=0~lW%%YvQ}qIo}xjQ^*#6GJ?Mmusdes4(hFant%hNR?VB~kds}Hx!w^La4?2Dg zykyTeV>_G!8zkvIn!&Kqm_y6$Mbm_P?NVGqmNd$2CR97{ylwDE#O6S+qg2d`Bn>is zAWFcC5Vt%|{SOk9in%cs9^be})JwA+a4q!KMMo&;_bznW>u+Cl`GDJJL)4S00-BTO zOE}h+&R+{KsjneOendeN@+0^CI>@No0u%tttW;~A-AD1PLl$(4dekvZQz!G$g;4&Y zJQGaUj5L>n-gVZ+q>5NFDg*kJ9p-J>f#e%o*i{J!@|XN*esT;8LG{6Pp13SzC(S))c~dBFZ-JB3i_z48B`ynk%cd7ArQ50j`d> zp7O|MskR>Sq^vbf*72o4(yRMnX}V&FP?B^T!R=#fLP+SgG?n?JAxCN5hY^piI-A1Z zPn3pHYxz8$&6#45!P4#a83k~ZOw_+u1sBdKc5p0kGtF^1!i zPrmayKF1#j1P73}tF0U(ZXEICbuHmmMR z=ARBu16W8a2lgh@L?wf(mVNsZiOo|V&E0Ym8azvmR(Km^n{%eLGAR;OUPxYxn3R5* z$8>?pvOJCYR@Gjq6Ixc9fL@4s&?hb_lJ+P>(GGdVc3A;mO~+%|i3RYe-%brXDrCjM zYY)q$3e5atHSQ>C`p5_&_57i(3fj1nCdEt#Pv*{+|8(QOnFtCsH6&Im>&6G;WIZSz z*{`JWX`VM5ha1E*GcU-};djJ3`4u^p%ysqI^~ zJpp^KF4q-5&l;DJ0aDGl;nc{sxuo0bzGS+1cy|yX!So26SmZhjp6Hbs#%4z zDZFlu-V&?%Sq<-280V%i^D}b3{N?Au^lhuPad=!CMjEbDX==3lDQ`5~&y!K2qUACW ztZpxE=%5>NiY;N}&V>N9*-2O7hv5!+w@5dca@a{u(mu$y_8caSUEADc327&)*wQd} z7Em}@sJ0^-d&`%xRX&8iOny7Hwkol`tfv}SSDNr#MwF~);&!q{^yYrOtQFI*6n!MS z^}kO0g8Hg1yi;+5smJFSFctSphm7bBj2k?CNZ;2qYx03AmQB}FzrB2*h5={xsQ4h| z0?`=Y6sR|4vc?rb*tZKM@Y+%hVtH%vx-D?&q=fVJAGkw!`9QlLr^6Tom7elMa zhDa)JH_#fDcE$<4z6|$sAkdY=3zu`?fO0|eeuVcwEALLsBz+1Iz%Fm zs9NvLk|TsTexl^x3fj6WlZIZtnc<%VZ!gR9<~1gfcv_y3wSnf%>D9K zi@mx@sqHFI+bG#vfe@elEu@y{78hjDv2jS^Z*t^1fT_X6;2V>RYy`!6E3p~PwsUq! zyWT6!=t>eWTnL-Ajz{cX;bu9`nOlQ??m#y~h zccZR$BJmBQ_+sj9$EV75vV0j026$8_Gg)((Hc!HcR~MCD!EkgQj=*6u61)-kx`FbI z-Q-`U2RL2H>0V^O2iy>oS1OKJx*+l1+wP3u?sxhq6m&$C>PbE{GHXcNY0T z(asrR-1U-f7GkID>R%;>>D9;6B|&%XaISb_GY-=&zc6dlsZx^spTXSt(V>Xd~fb_Fcj(xw1%Te2aUM42UdCVsbuw@kQjv*05Hb637-t$ELinE!;9k9eWv#DmT61mIgzrnp$bKTM6pxi4!|v-x zJZ4*N*n*;U^<n7Hw= z;|tCp^~9bjC%KgsRCZ0WnYYZ(5Ms#TlKq3|&v{#(zOqp39Ig6f`jHgb0Vv79xcq@< zTQn##F_i<~-}Njz?k{c3yJ;kK(Cgp`=AZ5_ud%XtTw6^v|A;3veON#78(+z682QV6 zeM{m{K9{hZ6X_F%mE6|G`cHl2*4OBdQ?Dn4e1`C z<_iN@nyNJxbCcnv4^h*Fyh%%nP0qC;PR)7NE>yY2_|TFXQwR8z9FIgq@U=?|-~_13 z*pO5=O3Qio>Y)bEs4z~}mGu2I{eRT*fAYisIHtIA-A7Kzb7X%ZJN1M8>RS{ZGL5DH z?zWDJl=VP%rYW92bMf`t)i2*@ILm3GeyGKh@Cnqtn_nD_Gwl>_yVN7i7U9ti}KbhQ!{ z(B<}AI#i@?oj&Xy8m2~izX7X$o})2tx;tW1yB9+y@o;ZrV&n0kZ=Zfy#Fe9_Z)@9l zJXqcduOO$(yZ;GR|5v%X!T(wA*Nod^{kkCL zyp06ee4vtKwljvC+5iWbnz#Y-2Xt4QyPqRf4_RLdNe-tAWvNOAHLq&U>U35Px?$qRLQbX8!2z=m0HJ8B1BbACY5r1PSKu_sm)UE z#Z|aR)8XWP{?DVOqyCgF%P&Npy|`O#M|wMK8?Ja%<@yNkmG^R*G|f?Y_so)*1;Kb| z_|#pp9!_JQ1l2%S6OrZI<`D$;B*HsVgL4e<;7iKF;)Pus$)bVivVlL^#;d?!AOX%J zmDz%*3Ky!aa=S9$pg>1U28leqBB z#s4J3iYnkEMjf(h+o)i?n)tQ#(MW)8vLnwxQwR z2_uSyllBZvrKc2T`OIN+hdzWIEVSGN`m85x)MQon`ZuJq6tjJJI;WMm zU?SA&K2n0-Nxe`qm_ua>C*fuIl97GsZ>3g0_xaq3`)oV6u4}SaGw-=32Eh@(VT`0P zpl1?`XZ>rXoY92MyZ+)o2fY6ByC@b zwZ^q*aM3&3yl@TTUQrD4-oxOUDLjX@wfXSOGm~$x836MJUp_x0c}`X%4mKafI(D*m zO7E;y0s95=$!7-I25v(KC6?UptnSbvZfWdz+9O{gl#sV2B^ z2*yb|b*K7h>8f=7S$8K*_F3R{aLsIdr>~8NHetao7dAU>m^EKR4(Pj2EEVQZVy#iE zo-y_hialGS8A#(6N^Wu9AJHoGpna_75jk%f$?U|JI&!P_FxTJ29t_eii)R+&LRjZ} zO!`B=FDqAEvD(7PDJ34DXR(UG2ELVJMP*gp9w(u`2MKI{A$r3sUz zC5{`&LUiJ%OyaCY*R#YdH8l9Uza0FAe@#7&5KmYQKhDQ{s^xD5Y?W`Wz0whDsDc*~ z)wq}nFD$9FebZ9r2%JfzdZSy1g&jrPa1$P!bST;l`NX5WEZzTwbVbVI1m2Kue+#l{ z#CNA>e2J8FFrn*bUqvoGaHR>gwu-wE+;Y}AA*@2UpqxEtgZ0|Wn$_-twg0*YrL8p! z^AiV6|I;d1SmQmH-=QpabeFN|!J6V!8 z{;O#Q-DkWe*I`;`H|WKFbegZ4NCl&hzQ=UUZ{6*Of*@Dyt@$i(!{R!dR-idrcwGy!&3@g zBJvjARlF0E@!WQuTRnVq)f{wDSxM!f+^Qd`jR{KIO`$E0Fb)}E_z3`=a?Wg80 zJu5ABdoi8$%yX>lSBSA)yi=DYXP85GIgG4d9ny92)yA~@i8OxSY+k{Cygd6K^Z|cJ z*0FkE8Py^kOl{iRALs+L_d)S1{Hf#$W2KXaIJ2zN9k6+kgRL34!~(vKw)o8j;w^q_ zQLvkGyP2LVyispjR6PB`zPhE&t}C}7Ox(BBt%Yu!#N47HTvapf6J)49Xvn2imM~;( z?1HY%?C;Ae*hChC&3AVp?i(OfMl?q{_G;m&CZ1rnlk}pAZM(&zy;DI4VW>CZt+tZt zgMxFJZ5A9JEBxX3Nso>K#3S!S4G>o)2j42z4Lv1X&^lpJ@5XS4no>YVs2F&WOeWM zQt*v#yQ0DImHNlfmi+Y`O3YBnnozH|9M_bC17>nBj|rP_SISxfaz|~#curV76!dMX z?rdz~ksk)Gbr5S_r^^)BkIes8`SQW*7RbT@G{JRa&GM$Mri}VUg-oR~SX2{Qk0tEf z-pK4R&G@0vnI>q}h@}i~3a{7^ulK6@3@U3c*V2P{4507BLQh)Slqmyr=~C|J9jHFN z?+*twVCrq>YCpN{b>FU=;kiZ(;2OHvnA%oK)y#`kMNDw)oY~`A!w3)?vHd@gILK@z z79w*}S)R~X2VGmf?~>l7fUO1_V!5qEW5^fx7k>USRjQW(YvAs**n;T}_V~r*R%D-w z{_UyX6Ec%fGfIUrxzUM)U~^H6+K{cT&t3B~2~oAsGrqcEb7p;k6Q?zpu{RYpp12PL z{7P&KADt(Grz)p-TqzE)o4&M4vwU;X7)7$N5i7-V`@4B#e&-5GoG=YO|HTF>@f8eG zpC7Yk0Dl@=D(P;Msw(5yYG$|^=&px3fa&BR2g>0@!o)(dhzBk#eNOV3yN-{-yzJSS z0`ajXAGVQq@lwR_5h499;9dUrd|Y>_UFnT0pOKbWr(>+LxUe;3QcS#2*u)enVz(e! zyi`U4=1`N83-4DMozt|+k%KPe5c$8Q@?!U*ybviJiMpy8Ipth``TFW8QJcEARmYhX zG>2E0tvz#j3)^X4Qg}g{Xn;01O`E*{oW*8qrsyyB!}Xx5$P9G)$ni`iy<(vQ2APTV zDc3Z)Guf4&j7J*PZ$s3}q7HiNcJ76~vovnh$YDcPHhVeJ<68jwv|1X{xA8teiawC0 zPMky^b@TZD?E;v_rYZqbRbZqB!k%?%ii-+LPacaNpXvpopv{L=_C~fYa)S%-L1a*d z+_kDWfjFA&m0C{=)ksxyxzOc#n%wdXO+ge%1r_>1_|I>}&Y!48<=l_?a2+I*M6<6^s4fPI#k@JejMO}LcJ z0aK# zWIJ7}=O_5?0S}^+=+Rlv>8bW%!)b*5c(nJ3IfKlMv6KomXnXXJHfyjm5J?IOz43Id z_#{-k4}jxEUja$V!oR$)nJT180F9UM6+=7wRLxeQ!YR(9#!XC)6@Ko(1-qkG-CyHR z@BRiWN*nF7Oa_oW#3U!4*!8x>L`t*7gvmya6I$?@+%=59-k`_X3l*EcSG&iqkogE4 zXsT&m%t&uwXv1xpVbxjQ!uCu-QoJQRUZ$VANUE5p2BVBsDgU~98E4UdflP&UKoqp? zQV=vE{EUC4mebxzfs+`rn%gC*5w~8M?ZhLL!g|%}?%rJr(R%$7V=>1oOLvoO-tVl3Dw(d6ZXj7-tWzhf$GR@wUcUIZI zYMb=CL6(v=t-mW#No_n< zLp~(_oUxGVesr|et8QV=th9_{v6AbwnSyvs5N_Ywb;YbtEIO%jj$u!6JzWduZ|l~U zc1a7Dg2d9JyB~X9nqKg)TorCCP-}91xx*5^3z9OCKl~A^CjHje-XHh77bBPSwL4B?`v^LEBO8kS-$_i3!iE2LVrnH zb!Bm2!>p;Ge=lp5q+{T>^HSuh+pzall#i?hWI0~F%?Erzq!l@H+xziS+0st$Fi9{r zvhtgJo1RtQlv8YimW^(`o0=WzsP_kd2z&bG>TZDnHHX&eQlr1V^tIL3Rq{kgi`*@G0(`;X5~_R`VP zt)`H5xvd=SQQxd_Qo=x1UF$=uzV3?6K+zc>g1_WFhoy=3)c}TGQ;F8fwJ**t>(z8F zl$#jIxqr#%(9O0yNX$Ki#AW=rs6KPgUK*0g-h6*rks{}dYliprrLI7B@kSNzeb(vw zIBL1_r6*nl#=ik8KE_ck?~dE9fF_lSgUhJW#4O0APvrsXSSo9T54w6aq@TkPv^NgC$^zQ6o4&L48yr&-*0-W1bKW65~d zmS5@{1K`MAnW!3UWa@O4KWNN%1;PSz5`eM`K~M2lrC_cdU#X>L zhu0M-+KQMO7r|4yPc+jupnFpp3 zSs-mCo`t>XY z+UQbZ3Br`EgQpH}UvyG8aVNQ@j-PyiKg#2Bg#BAl^IQ`cfFnet03i;kzWFE~5VCyHVrfSjG|; zCI4J;+@RCdWSQZ#W2l99WJ`N$c)DOsAdhrp^2Yjx$C}lC=MhhT)SZ-#XeFw<^s1d` zWYApKjbBAH<{D>T4myHg|G=9h5c$8Q$4iet++{{+2eFUjtzPgstP2J9(g??wXgkuN z1??kTQ_k!?3-P%&V}Xm19UZfx!q(d#ajqioz7=ny9&ujNj6Rf{+nl|Z8n{wgj@PBm zC>r^|jpS~d*=-xo0lLK&Zvn2k4qnmYnHDC;iZSw27bxiO`>2YrXV0i-BF<-yb0910 zg-CN#Wdt!XgL);ko*05RKfMdHE<%CV*NUFDjKomgpLK_jEs77a7cp)5vJHXer*}wc zEkmCPxePXQQ5HCIY^~>^RRfcp+ltq-P;&Mw-Kwq6k=%IAH5cMEPo*zH{thypA~=6g z5OQ41{mJD1Xw8ra^ZQjoTx&m-vEd&jYfy#c(^Ve9xX>~jc|i%H7_9Gi!gs4}1K;f~ zQuWKN3klS9*_4D-aexa@+0w|#NH}|%R+>S170!qAll9Kx+kP&J4I@ z#CP|_3r{B%9(~ueWQntO#%^zQqTy$C={w$K_g95~Dw+adIhithh5LSDL1Py+d#jFx zx={%mrnQ~Zl4N?0gKI`2u>8xqxW?gE4OBTlqx#S@9jHuYKN0uK?)s``cpaHvNJRS^ z8QC)Fxu+MQy@gi8)M|qy|HGd7Vut#BuL2f(J+T1#td(>%5-phLQe9kAbn2*i4vjpw z*?a7LO3evIms)ku7q1L1_tu89*&(i`%NUeH>@kY5$%K@-Tnlw>eny1+*wT|(+2J+D zIj4`~A6MyOwE*WpZ?7>%`_>=BK&^9=KF^QxNXe2vbVAmOdq3g(U{`PO_ScQT#lAlp z{maK`-_^g?ck)3tcNEUht<1pLIpc1?%ppvTkK#*2tFSXkkZ03i%NkJm*dCEbjU&kOMoPD*qupPbh@Q@ig2)hisgoZO0DFbXtmc ztpy`Rn&8NWvISdAyH^FIMkT*`8_QjO)HaZn&PvA}>R9ii$$&!)+zRUaOUSg*mUqFD zI3aj5owrz3c*a{%CM)duhPMSCV|Q#O`5hMGcH zE?$2ZFfX7_jmw6kx0tk4LSE`!b;n~K9M$I@k0R9`;03sfok-}IIBTcXR4siuGj5Il zG2|L#6MWYtk<}3;UpTdO5cNfqoQr3d!V_Y;=Tq?bj5%2pf`5))KCG8zSO1#iGUiB7 zm5dC3OJ#e0Uv#OW&VOb+_S1mhwRI?_3-AdmdOmbBJjZr%XKtS@0CJKjO8wh)lf0vo z3~xE*S}~(OKzdwwSRzll-JtBb)>Pll zI4hani=t_71I36!8^5afadzW}IwTOde3-bF1rp zU7w**<=9Yts2sXw-7I5wpeFULGOPlv;@r@wcYgKf6xG+01Y_e`Yh7uO7m9P&Pk(-W z^ml`!VLQxaCb9@P@`%_>J!A#AH4t_}?=AXNeg6L=!&rnI?T!37f>)C{bVZy&-BO>& zUySX0&uzX&QL>7w8 zh^U947i6pZKmiTgVpqrGwwAs^aEt>z;Os%rc6^S{l+rc|)=x1mD$M%j-Mg1AAe&7T zmARSH?GmvS?^YwtAA88;JUkcs2Dmg$wrs^V@ z0qGp9AOK^-Bxf9_39R>xqt~|$sF#6S#a!=eHTm0!=`LcvU%hBwFw~lA#aRrg(0UV3 zKoDSd5Za}k&n`^4x(cbUCeuX5IeqK>n$xu3mJ2nFYM1=6={8aJGTw58r;VqEG}J+D zVju2NOGb*T_>-*9<;3?iABDeRz?quPtIf!)2JpKBG`FSf^vKB>F1Y0%XYvT(^Z)#{ z1!y4qM#V5m1#FlDNO62!O|=dG;W1$?^|Kl%D|6V@8gOwW`F3_aALGr7LrGH5^-x)z z+6?2y-Gwkdm$=Zslq43j{7&+xW=BM1f^QRyLQ(W^t<#$J%lpi_CEM7Zw4JqpK0HHf zl=RL_lHR_Ync)0SeLjy${g_S?%o|=xlAYmg*{{hD$=6d!0$B_5K!KC|PmK(urn*V5 zy;|fiC9vN0ga<9ZF&b>~-k5N;>Y&4O0|;ZcecdfKXY;Q7h}!;hs4uf_;cmHPX)ljPLMnz(ym6j+wS3Ck{ zR95+L2T}AfvoF|3@gJ`@+##c+Mt;Yd9|kZr0>_89=L$A|Li+fwGUAMuQ>WE6lAdB4 z{#i)7QQ+wKW(sE8dm*s^m?bNkzz6zZ&1DjQ)-Y1e%gE!e(Y=5@7FPuE6G4f&OLNCE zhq}2G=I3B^fgj>YX`wi3Bm&Tta8leBsX@(>0OelS4VBl?P`EKh2ku0Ujn$So&e2J% zL-)$VSUDH9Gx^>i_et?%nQc|OZlSbQG7erEdc^sKZ?+7d5+&LZW1s&`G zKcF963AEBi)|$w_ zc%$K=MW=?c>4Lgn$+D2Azx?oK#{U6z#+vTJ>SwX#mx(r>N)xO8w(xAQ3%0tmFs{W(9_E(u@K)nt%#JfiXynTKdUI|ux#Ry z=Mavmk-DF%%d>+J5)n8)`}R#aZab4`Up-&QEPGD z#IG60)oiy+^MB~Jk!cLkJ=GrX$>c|UGxc4%rL#PszSFHX7iMxvQi-%*M*%I~)eam; zKs1H0dJT~;zdp8iC({!USy}9Pjfra>g0>7^ zQp}GW9?zE?U5Kv$ZNsbTD!HaNZ7}B5SL}!azW`v8&v%5_oGhD#_xAIrr;o^w2nVdj zivo2tmo2XRZm4!MXb}*#IiWPYy#{5q>S3p28<$p>J~)ifTCzM~3i&kFWq6>~^Rqt3 zi!3#qtXJ$&|B2ctZCQ*&*dIR=#4x}z)UxG}OpFJ)joe7oVwci%bu&JsZhUhsibw`u zHZ zi(vNO4`!O1M*F>@%B3LjOnp+^XqV8U>@j?3sez2E%=l0ZHd(aHlfyc?sqhi=1EvMV zZr))n`d2rUX(1{MIugXpEi!vU%xCV#RrPl%L#d(ZD||KR=}b)rw9=`(Lq^mu(!APt z@!~6uQe|6$4Yn!QY5i^|Wob!=zq6H>rd^AAN6`;XHe+`mA4q;fuWV^Z3_1vLasUd*6^b`JgqI>h=;79w6rltVGeFfd_V! zy{HY+pRR(jao&;3PL=x25ze0jx?Of(KfqEosJM zTbVKtq2CYKA;4QPLSFigyggus>?n6>f8`d;3&O zMEHNDxCbgqK%~H`I&VAF(C&?L2{Lk1nzR$Qm;v;#<8U6Jb&Au3@CIvcNt9NorSHAd zp8cOb-DJDb_lc2zk79F28B+czD|uE&LOt?5jjJZ$qfJTB_u z&cJiU4WRjMRuH43L-`Wx32j^dpo5(I+U zD8SZD5m_&BEm^4&sc5yD&em76#U17BQ_J|8-eHxVE;!ie+y`iaik{UZ(%J_eDP?udU)W3+WF1n zZgB2}|DMDUP;be!g}u+-?c0l$P}^|)Bk#N`WLTH%4?9q%T5;F#q-9X7F>nCLJh_^$ObtCA${0#V_L=7G z)2(cG*UbPV?5S3A9325M^8_9Qf0UbKYfQt*X8B_IE zpyIJ;MzKN$H7%#TP8pI(F6unb$v#mlf76v3Y%w{i^Seo^@Q`VE%C<^8rNuNPGpFr- z`Z53g3hO(Q5bzTK{xRp>ZUhDKrvBUNQ}C~0OG6qPKKGA{UI+6qv#+@)uPPSrVx~UP zIEC4n4Z`%0p9?GGUn+{L-!03FuQ04VeDttkFu|%#+0i*bnyokiUEBS~6Tmoa5OXJv zuC8bNy}w`os-aHcEqsSof3!fn1Y!{5Y4X_U`<4x?47<{<@HC4#i&%kyxQ$v{{zayjz}M@8y+8 z{b+E&;DnE4)LLEl{LkQRjGi{+2vYFq2A;%S^HFkP3tJ{vY2A4>Niu!AM^pMF@E0#1 zQUM>KIv9%q{J`0H?j3E@7-{v_OxINsu~D8fFOKi^=<<9>RzWImBo)tpXabJ7Q|`+$ zJmy*IMb}BGm@tz|9q8_bL_Ceg_4#iW(8<<8bw|pF9G0!4Jt9O0P@0R8i<(NLXPvp4 z&xV{m`W=q!RZV$tw~ctE*@#$(9G||Wca0tq@i;fPzK(G{x&ln{cM0T$$(PAWAb0_ z44*-&7(UzdUJ66*My6#WiY3&cQgNxMv^OXAzy#GGLos>jnfFD$9Vl;ovceV#KRnc& zRkObY?!?&XzZaP0m?Wev1G*Z;!L_*=OsVwEd$6z`1ORe7d>dO*?W;U@LWdwQ4-@j} z<*ALm)9*3IE)@kr_74BLTY3Y}0iAW~yR8q!j+ceK$1lWZ6y#-Y7$BkoQuxaaS89j5A24Bk6JG|JEKG&zDGj5uMNds*$my>>p@5iL#b*CqS; zxu<+3{r8I_huQ6?b8)i`wC$HaZYwO>%r3fDYprSJN)l%b-y#c$hn9IZ+c-QwKocvc zP(3mmWtRJYy8sLo1*KU>G(reH7P* zm25tHZml}WQ`D>1xdwH4(hoS2?mY9GMoSBh_%dID>!_7SS)k6{e8b&3Dp1lf#*SwY?Jy4Q zE;LZ01IMOGe(a4QeYN)wcbRivHlba!!xmYmrmJ2p^Y=uVR$~*YTrx6uLAlu}z)yq# zCflRE$-}JP@oXHd7Sp*eXnnPT((e#LsF;O_YC9a$w+=7-2zXrMr7+ z5Tv`7UP`*VLt46PiO=_b-{-l%e{dah%v^KM%sJ;o>G?bzfJ;q(sGsnTyc6gYA$-k- z0QlVgX=`4js(xtZjWTu{`VyFluEKpg*h>eYpxbUUFMyVH76o?GKBT9QOQE$wg$A@1 z`yoaLK4wGPh$L?~ZL`8gKHrCeQ$ll+4DGMUA=1YGk zo#h7_hBTD)Gpzi7;ItOE#k_gFtGeXiGL?hGoUbBWbK{OY>*Sx5Yo zMJGz+29tLTFZIAv#EES(=PXrE{9I__)@q^UY;4r_jKQZC1W5>aT@!zZcGpw7%OI}* zbxwC1BdV?>!Zc_Mgr#p~{}Nsg=Y7R@gzpPn1d~)?OV}zG`=aw4Rwfy)?T(%p&vmQg z2<602F|%xaR+_0Oxbg)|%@GNGz3UH_UVKZpH}J$mm!&ECXM23McE5js$tYJzB!^+IMDd&;Z< zD1a&ts_Vd(!p~wpus2=|oOM^5nuJ4>WfK=H!ueos69i?b!`U12zbEyjOb)$%y(+}J zeuMCx&liJsUi5gS=ljl~3lg{SN|khx>Pp??q9a4!mM>{cNf;Mw~>-K z#K9-Mz!1mIo|`vZl|xKBkzsHgunVE|-Jy@djHUmq=SK)J&2jS^5tw@v>YA{UY~gP2 z6M0l$GI%Oq*Fac>j*d+V$b0%WYM@M=foqr?w)UeiF-~Zng4@x*zOb5p48*y6%s!@< zLjTF|6i$e@E-}>7u_}5m`}t_6iNLvGkB7Q>70Wm8-+0mlR2ufXTdsCO30%Dg??<+febmA4e{l=Lz}*xj!Bk%@5gX ze+6YRrq@3aoB`L->s@})&WCGqnQ+&8<6V(xE=a93)Vy!>XmrcP=}<^xvE4B>2OuHC<6x8c2!B(>!1zi zR!vNWFs|pm>i<286Cx=M@H_BhT~f={ii?^?5*t+L2c}Qm_9# zGQX3<>%PLqdguo=noa!qtmkW%`=X@ur~>EyN93WZT=cacFz#-gi{gr3xg3g#8?Gv@ zc4$`DmDae!jjOvQoDH0@&KRGj0ppglMv^O_;Udk4Ewe8daPaNs47mYVHXPsudvrG( zPixkGO@Jwsk=JbHW?td;hgO2qTaoT_qK*<(`|#Ip5p6N@Ayr7%0mZD85&B|&hx5xZ zQqD0OA@SZEe$O8k1o>e1DN7=u;-U`Sx@u;BIvz1{U59!#(>;XhdPlA<-Zgcxw^*Vb z+kj}A$M}UFKE!g_;$bhlbX&ttyZ7tVQ=0ob9dz^GM)C0Z4*#O6B$)Z)a$Yj#fX9U? z853s{ivI4qhMwi~V#Fj1&+T!_%-3j^r9rBImUoLbvLNKg4uliYe+4*vDFBGo5ys?V z+Pk&v)_;0D;bY|_V&~spSTag*mmqnt`++$PJqok9SD9-?7r^o3HSDKvF%e;5cK(v# z-ZCG_Z=Q$mvyqS4FiZPNEfFI6Qb?jdx&slD`xASSXE@waa(Sa37FRyI>^~UDgBDJJyW6LG zQ`N51a?uLWf9B&)*bjAp)T4Yq&9I*oMG|3@K01ZYh9W1WsFqABeqNm)$tSdWM0B-( zw1|09$MSsC*sY-QLPiu4xC_rf3B4N6Bb;OD%)-`5c_u0ef7uWCWJ`|5+*3};rKgUK zOOW1N!Cci`{G^rqqlKXQZoPn~eg#W$cAeSx)a)42Uqv>J_HEX34-miMlHToYlM>IP zq(FQARnQ`g7rgu=keDu;gD)|QuYyXE&mm7_`o{!M9T&w8hJIy9v%aCe*IVmz)T(@h zKXQfjS<9A51r+jG+Fw`g>oY{+oRG)sM(eOxaSpQM>ZN$7>hZRQ+1({q{ow1PzP7c! z2j|3F#TrF&V4|a#O14I#WVExvXsmx_gS*$0RHiQ6Vr4YO?=-& zE9sqRyvX=h*bIR3onsMQ8K#IouXeyFCmxcLZ3)-eh^T30GMamP_VZQGL;HUjT6 z4-cZ{9C3mTYtu#+cB zr^}jw=AJ+Ebz%co()!P~IY^+SXX_{eDYE?PT$qIK-@!tE!ynms?54wtTE9-0^K$DQ z+H~X=#I}4P8Suqu3=j}mc*?{9G>d! zC%DbNd^W55JzqmaKsqe_8bN<03wSgedsL2=KlyVV>~wLQ37mVKUXgH`+(*fc@*-!J zqWTHnu4*WtAmw=vKj&dR0CJ|%|7@hKAr~w%me`g$gcqIc4Z9lX9Gu5Y{8-Lp(`Dmk z%m%Z%OjfWG%{5wFjMTwI44S|T*Z)s;y2X>ei}YhG+BydySD;?ayw||L zDS2p)O3}`DJw%!*^^E{UA;#V}eJP>&*NYROW5`pr`nX6H1b4z4@5Rhde%^{UBs~=9 zb-C$X3l#`4@5OZN+JkDV-j1)LerKwnBXg%_#lnZ;rao93F!9WQ3anl~u3 z{)v4PEB7M!-*MJqBdWppW98FP1fea^_PuVJfwwe}`k8r$ag^Mpa8U;jD*5f|IP@TJ zT=H1V4QO`vnXk0U-96UsLd)A?WaoV0#RUFW&O?)}SHN|b(pf_|6FzZO|12{@A%mq) zuK>?+@;UB91}gU(iS2m~e%S)H!VBjPA5u^?Rzw+z<<;$xL9ss1`EN+(fjRRLpl>x3 zPB~dfOeD>Q1t-CWd=`&Xn6Cxxk$tWDkbYL;(!Az7JeKV(@=cda9`^d^NHsJI&zidz zIch;XHs;z17Q3I_o4bLaVMosL6e7;(cbPT<>VaJP#XqIc0kI+jXC7uHjU6RyQm~A# zy3|dW6Jt@7aBBameu?4Y976Q(^KYKK{x!`l%7*aA1E}^&7^gbk!{qJ`*rWe~xp4 zQ|wvqJ)ln=*O?4sRo6WrX*T{?Cfj*(`xW9KSI*SA;}yDKxeRjt`7}Lte(AOrDL&A_ zje_$ZMZ3RSG}cjK?kZxF%x`f#`O6Mn_%PGoAy?kuuG_5=eGwq#o@8Hpv$~Wpf1{6D%!#sxsTJ&DEq==Apfbqrq!Wk!oBD%GjCt(KlAA zUmIXSSChg%R1u`Sr^$UAnM7?*NvOx#z?D`R@~3HJd(uF(*DPF>6vEYbxFWNu6D*P% z^w5y46W#30BJlVyNo=; z-U#s=I5iF*5aO&K$QXUU`DK301Q2nnwwbId_CkPIH`;*B<*zIIPkS>{Jk~mL{#$qW z=m28=ikppOT|3lc_gK+=pnfB`%^$rQBCXLJkYp@KS2KzG5Hy4x47qVZb3~oQ?s?MW z{z0Sp#Aq*I_^sp=dN*B~uo-wIy^q<`OWC$HP{|l@^?OA+O5E1um-e8mU|(~?ynJ>4 zElSOPWXxSOTE0cFolug~%huVC`Ek0ZRmgNR8W$7T5%w$hXgE(vgEUyink;H_ld6dX z>UM~q@3e1~Y}J!6isffXvSP^t#jT5>2Xr(@i!t!E_iq|Pqf5~>_d@`A0$}EC7Xazf zoFk`+JMY-x7x((58qQ6x&ogp~0_%Tk4*Nfxt}xw+;1XMj`81mO1x6BS(*lgYQ*p|n z6$1($@Hu-p$9R>&?!33O7sPO`BmQ3-Ei;5l0y>>K#=9yU5eA1D`#EuKI#JG?jeg^r z(RP#;g|e|9T`O4dc35m8Nm#Kd+zA8Qj%o#-F55$Xt9;QhA+KO_V0!-@Y{~B37&wwC zab0&%-5&}gW2V#b>5^&n_QV4I21H1nH7$Cq{5b=OQktj^N3D~0zf|o0!KjUWf4{Qq z7f>W2r%!`RMu<4I{CgI40=E!(1NJDFE8&|zl+U7AW|Sk3+Uw4Jhjofxz2iI_w&CqsC5EALRNBu zO@H0cWuA?4{xN7KdTXeF6Q#Q#)A%zFzi~ms=oq+HzJ_^crC8F4z$An@3PP65UEvaM2c=G zp7FW|fQKz&Ag0|}cSJu}y?$HY8mTYi-3LcUcIl97v=bmH?Gw(?mNhkAb?H8Ti8GwL z5r*%|*xcC{lcG*eZ;>AjHLta{o5ViZPi6xeO`{0QCrceaJRNqHDHZP#C;&sJ zAXuSt<<6ZA@|Dm@&QuZNI~D~N7!`>jGd0nP;gJsS4@h=Al)1sfbgWviq4+x|F{p_m{0xBA-*zE|`L)uTxBTt0$goQA`yejh5catjgbqmkN)mOyk$>PXov*d~XN{`6{-d3AlMXq> zEAJXvuQapq5Ib|n>&k$wh!SZlz;U95g;xpebU+*&^g0{`sU7K z>g4B=j{0fK`{ZB0G{mGd$Yr{J4Go-jS-Eb!<0?Ne3xp<7y&*dC;#cPR#<_gf*v`2z zv5viUuonXjpzne{PKr_XM2;3Fsdc`!$mVJA?Z3tDWO{Sa+Ugw6?@x6d<0`bQvvC;r zBKR-bZbaC~Zg}fWR5R-kAn%_|``WO+%SuSAq1({kI*Z8)#^)~Z^I%RG?Cym1H0nBb z6v#CL9pe{KH??($oYk$6Q4M?R3aG|%UOJ@;z4W@);fQ`AQrY4byR9FUtvo6-w`U5B zx|@>=#|12pUMTc`x@1h?1>UlNaxjd%h%ha^1f;_NGCVHO0ljD4j7YWk(?|x1P7J&Lv)269%%)i}V z6Zeszc&;1QXrX-0!m`K!D7J8LOJW<%qaR|zjVbX^eRb{c%j&(vU28y=rq#K2m?B;M&Hw%dn=_6*=rxzFP66XD=GtHw%G+m5Nw~&N`+M zPlh5@nryY|lQLtglJzh@)c2iqrV1c zR|fnQ@H$>`f_=K=1(pdYlj`}m1@!lB?VWwEXVav2U_6}=^_+sDFa~1mFYM0cb-(}% z^kw0Eeh}--dXP>uN8G#Zb?m{>_*&!vY_KxIkm)v>Yf#J0f3+6k zsguHS&0m9_w@MtS_=_bkdG)EnzV>Ckr;F!?yW91vnbOn5jTeRW`v$QZ=Y#q>9?YUu z@eT~lXI7v8-J4+e(+DsK)R^jOM74F+KS>xz`jm+gDwnyVv`N13yUW`HJcAE?kALV_ zZ;Gs=o(x96)f>K;X-VSmAWse8ZK!%*ro{N(Y9g5PATfZ>CKZBRM(i%tA?y>4Vu-Q$ z&EK6Bd#>z{j~Sn8CPIJ^}&+}1=4N%~CB4U%|)lMgrI)w`;slCt840KPdC znqW_$>I}Rq)nd!0Go*6=de&8zxVY^_Z4qY3S`xr$&>~T*yN*cot>WSLvi`4~aw?Xv zgrR3bb!U>(cuCu=6k-fg^)RqQ98m|-JLJG)AHLem0ml4!@j|2Lpv{|v{18SskxY{r zx2`@dQAcCOIH7c(n0K+&?2_H>0kG?7sVEbSfG1jqann)pFB(Afi~Ze&hu(`0MP?r! z4iYm$tgYG|Ad6}s*g{AB60m;L{7e^T<2`@C!r#{m*>3WrXZ5;K-!#oEdhRSDM89jv zXI=tlyRqTQddUg4$Nu&+Pp5ZAnXqi6wOYvB23Y8YXm3%W@k`aO;_UN8wHO7XntSor z2H@O0iNuv6kimP!I$)vO42|r%f;Wh5ZK}$v_6DmGwh_|s|ppiQ;mS&830 z$|Q;uEVk-o8NSd{cj}bA?v47C4J9*Ky;l0*5C2xn z*#Wsk=?eCd!=R{aU#=~u^ERf)<6P)2-7>WFRNg z-XMSA*Ci#!*drgw3r8u^*axHYEX?p3Bz-1Jy@=9h8+{{Jm;Q8IUCv~6Xj<*atq<3D zrfv))`9Im}^@&E`Wq;~d24r|C*H<_RhJ>lTTK%~@I0{CL8Vxl(Z56mQL<1~g2D1I^Cy^cfq1Z5Ge=YIL1#yYgK2iDGO!8MoCQa z9%B_1?reYZ@2JQpb_8Vjw*0P9)+~@Oe|WIWeEQ3KWVehj5MjOiWQ+Uo>qovbxm(1{ ztc7+*N>Z}B6w>ayEyv~dl5^QtaoJWkhdkPXbeTA*`IIrvXUfQjI?cf?VaHR?vMbKy zjBW*$kZ4!Unns#3uRj@gndQ}w$TF=ubO%)W{-T~u^V`IaZLeyVG%0oMkgk$#Pw?oU z-MXusT9>!oUU%Vrm>UzRoM*GoxF9wbu4JD*Uzulk4QA<>p?*v*-<|PoSLeRi`K|io ze)dOTU*2rLJuMUi3aXqRH`%4T)v{04(X9ii@W)VA;aZy)##zs zUv7b--fFgpNgfiZbAQ*9s|5us-q02%yu}>*Be}p#r|~3QSDwH^aGC7#tG!}z?4-ES zgoj;pfwQ5a4XHDs+J=gK{EEL{)~;QnpJK0w`VXphPD0jMdC^@SvHXz8Q$;!tMALh& zbvzWe(I=6O8iNvx3*;CIE+2E=kl4a5`{j$`K#u;k3tGHDZhkDvwFruVX)r`8y~bDLbpb$ z(K8R!DpH1=OehFZ>^hGYXNvfwSlN!7OhMfGo3BnY^r(tp-#G?f*VZr)rAV43n%0?r z$^rfHtgS4*t=J@JI~niZ^cZZToGu9;GMeU=^9?m)#wA&F+ES&;VHLz; z)!r6OSmu+n)aOd=%fzWoeYCblY(nGo&pumxu_*P^YeoZ!!jkMO%ud%o&+LbhYJ)v? zUhG@=3u9*@5?9{b+nz@)rv4sg=4Y^AQ=Q{3AHqjTAVRbS3wQ|H{vARDL6!K$v~&5! zn@Z%gEZf~ek)vsu84mx!ohU-BwH@X}%i0`bJwX*_4C*mi$7)khX@^2qpGCqObq3K_ zJiI}YCCjl}k^?0NmLaYBn*?v);FI9lbZB0_ zTYIK3+5fQKq3PrbNLuM2);nGxI{QZaGd^hisfjr$7$f@VqQ*d7hWu_!b-&V2vS44@ zbeH9_l&H@7_Bi3A$Y6};JA$V#0#{S&Igu;Gqn1xsj@py?dB?6gt78cl-*?LJ2Mvz5 z(x%M6t37pcCmD|y47Om`T4?p^KL03duBto5$zm$QY2)?zikpr3pZg4KsGS1~oOs2F zw*WTuZpV=!J7W`pdu(k_sp~Iue+vsKku)gt-G}{hbKO-dVob|Ure~4RGOQMf1(z>8 zGbZyqe3t6FQ>Pb@7qnl%rBWOp{X;~6LB-8A;)#abshr#o?Cm;$tKQB?)wADpbBp98i_bF zL3BN2*1Piad&@57K(3^IVpAR(&Gjl`+$*zbg*$|Ac$AE8Z!d%j&(zp4d}rsKeluB> zV2w)ZVr`dm(kK$8C5SAsOl^D<6wg@STCtR+5V_s%6`tHon^i=p0Y2UcCuUs{znU77 zJF3SKDA(4=G1IG~wJEFWi2FAee*mC36T~5es0+K`S0Z@}V)`=Sp72n99D{Dim@ck$ zPWz`-@NPugyYt{ObgI2zRZ0MTdJ}G9SitHYBnUy2GRwq!P{NyTLqj$l|NAS*uw>;DgwzQG=zsWLf4<6SSm~%`kGq08%!PNhM1LU`51`X% z3{nw6JSlWUy7^Yy=0HvuYP8z53IMmi`zsHGTKq@)5fosU-an* z&i!Wj#Y@?3?RWTs%b8=iPR!1I7S(BQ6)V4NBy*@|lf?D`ED6Msodn&kZuVb(-!mrs zrsicS@#)m@NHZe63c=4~=LNZ%o1J0p#OqV2iuwJ8m?DPtjFIw}=HyQeN+PzVUsZ6X1=BLU2e{fD4sL*AKg(}n8$FK779CW#>x zU8;LN&h4fF7;k?&)#`FbqUb<%{BH(iZdVJxH_i=PRPo|( zUFR0Bi+mQVSK8yYw#j%)-`Cjt>3`oEVUaRL5)8@|tB@arp{M?xv`*hJspHc7nhB*H z>9KjhMq8uJ5<>MAe-)a>4YgQ}r_=D9saG@RAY7bw3>|B{K%T#>kjF^iUe)gSKUl>#(C2{T z9-p(C+aey5@tbWVVlEa>WVCwStGc~CvUvEYSW(EWXW^_(b$p$$f6j%TUZKBJDw{El zSh1g--yyD+))kk}`{L9;5(ShaLqxcRrqczyW4T|F9H2S+(Q26Quiq6PvVP41OEg5$ zMPid+n>itO(v30K%`R6T>2bd-vQZ(Oui8PMKbjsLRyrhbpYB$!Mv>8Wtv|pJRdWI6)9-!P z0!-~T4DC!Ghf%x^oIfe~_6#4B@RMX*v1-s^LLT`fRsig8lLcCP28_56bVF9Ayu`l%EZPWYR~ zP(DvF%RNzR<(rb z5d}8$kjcA<<6Mf6PS;5d&uQ^1=Yf!L2E+0yHlG{V7)-k!7h>W8w-gSH|ixC%(+R0+4*BP69Z``Us<=9rj6Or_MOZmzb(;$}w#V%WiZn&|T8}?yJyi%`E5Syws16!dOxKuJI+)+7 zjYoy;VNZ;`nZG=*_4EPPSH%pRg=@)s6oX9JmFjdqN{@p-d+B(N=3fiCrir)L)@d60c?xD8bf5A-Z8OPbmm+IIXRhIr zT8$+)?(Q?Lr*guS>`gL5o25A9pHuj6Q!T12tUq$4NP8|9$B%s z?e58syn|>_=E-8D9uvAIsr5;eHjVWjY&pLTlG^xh`^A^g?|z@fTZ*Js%?oZ0z^6hf zJ9!mHYidi=*sgi#r=h8VD^%OAL35OLv<3}a5jd!F6+#LMuOdt&XFo~NU@_O7LxRB;NE&#y_&TnMB{+F(rCfARx2u*fFvkt7hG75qcft$U0T!YRc zD~eHG*Bca*?28q`O=bA3!Np*}q&mdfghkWY*YjKcBPFdD zlH$CvQsc?LM_}IcZq{OW;9TGA{Tk78Cff7pS{%|ta{P2=!6K@Yp%WSl6u(3#^Nk{f)PN-g4mKv%rb zf;L)GGJbfO&q4j}?^C`#hhu~(V=q^IMfp0cc^;SynvrjRj93)~6hEex{-O%drE#xQ z*P$|%xfMVQ30$15`gO3Rzv(c(_q2P%mZyHfI*oeJ@VnWR4{lC`-)VU~q`5Inq>_NO zn>hyywd-O9X06+4$s1KI5DgUMW6JNZZ1q&(zR*k6f0%%`eZ-w^Qb_qecO-bE3*gJs zhk4I0g*gq?>wb?!~_USLe{x zjGm#VLih3%t~U!kC{&wQ6rgVCIP0TbWFivDS>xt210DDvA~7eB+pZ&o_K%^{CRy51 zkL!5hh6Z8Om;$`)2p0md&ehKl`l?Iu-?3_~n?vH>9vLfzb_aFtl70HRQ7yw6M{9d$ zwm1}!kMJ$W{2{5t{TU7BQfXb!*Tu70HBh|X^Kj$D2`!vmK zbVzC+1LN+^7}Q;>kQAosU&p3ixU^ApYIrNwF?S$1beLys4iG8&_xHo|qxRdc?vku{ z7WJ}gHQ|rtJQnX2d69rEiZTx{)p(g@-|zqNkZ^XM)rJNEXo)|(z5m=tIfD`8nwu!Li7JNTI69Bifq+Y?s#TmgJ>wUn0!^tzDqzvb!4z!s?}Blb8FBQ%uP+Qz zn<2X~Gl6!6od%dg{ZHN2;4fzn7{A10f!wbI`{aruSOUGfb}aq!m<1qDwo7F=WCUfR z9r0C1+ugg(JfVDG;LYS6X495p%4M*8;N@7OhU*f?LBiKtYUrL(mD#>{@9Fm5-%d4q zc2B2B3Lmv>@+`898q^JCUEG=c&VO$VKxMcnU}$n_;V!sIm1}~k&@dw|)Bh1EX4Lvr z$3^y4<&nv@ipGQdcTPCKHpuh&3F{YxrFuK<;rMB6#QyeuDQ@wa3;wkTyX}>Tp{G(b z%Nh^+@}yZoI9~Ye3j2sm#>iXrgWQVA>YZRrv&eNBVgRVc`Oj2=Tu06`6JP-{Dk63R zG(D9+zQki!{PQi4jqxmrc;RRx$3R((5KEvleWykgdzY-~=kS>~>p86qGGHxjSH%4w zEB;;Q1NVgdbX6;t^liO>ENdma%J1Edq+a{*+#&K|>DART>!tn$%M(_f?Y&{^g z^-v&-D)^i4jX0FTV0GPz91PlcGb$-#a$*h}$TAn8D3IH~uu zkTK$1`;q!UnbdgEFB1nkv)94NIkPQRv6WALdYTOCbNtmK&2ND~m*I!IZcrC^k@M5? zQ%_&pI&`U)#bb2LIOq@$c)dMK}DTKztHg#`y06v!m9T__B$@sOe8? z9EsA1l@Sf+T1_ss2j{>8-7z*Fpi*0k9CZO0r=qij(v{o#Wi8l<>~OMCa?u& z*WfSvO2IG>1xqx+`RB75l)IU&(f_B(`8n^VN51*+QB zTsIUAnn}e!3dnsE743|*UE(c+E}rp?Hb&c8X3V%1Y^+w0oRM_`c2TDtj$`0*6#+fLwl ze&~hBsFN2p&-wjM=IHQ)PH{;Gjh}ZBvak8H=(~@(y(Wbaxu|E*hUbzco-Z3sJK*kb z{Qh;sho`Fypnag<-oj7R`LkI`+6lp(x~hZLt!iKe2vt>UcfwC`EM=|!Xa$j#f&jk` zuli(XOtAH6zuy_aK1g)c@KZqjNvf^kXyYu_Z(fr^%a^QEDR$n+a%i5W+D6l?u3bor z#~8d?y=ms_R}}CVs!=82vJBPrXJ)TkP>es;KzT!3c8UGD&rm?#B^5;q>WZ?^&>~3gRZZvCVqzy0q`cLxtBHKj>q=I; z18b5J0m#c-{jefF!-IMbcAnOT8qOMg2Z>N(_kFpL8A)LLq(879lU@n>ait5YjXgFz zZ2o)*eNioc4lu+-V!MCu_QRJ^ckF%=RV!PF>pLBh9^EK~vAK529`+?c0>zA)b>2tdW+9-i{WEC8cS6C}POEIw&uobB z{YzKbbvZy3^~V;lY%JkqA%k0cPK7enqC??O_NId3{I}!;!^GY!sG_`TI;_E)qjx%OIsm`rl`mz?#HpD@T; zOT@@~o~A{yVGaCSROzpW09B+%VGIA* z4wlq*JhKx??1Qfd@cy410%3jfd9VR2)Rv%#1fpTzog%)q#r-IDMZDn|Cplk?I@oK8 zI?&l^#;d*LbRa}KjbaNjaQ3?iJ(;1sgsUmajSu)IEC1eb@>|83z~;8wKwB1ZFdg&x zEi=rY19j+i_CCrp9Wj0QarkD_9Lzuh#lTi-y^l8q8XDjGm`;27ax51UpPA&rx)14n z&<2_nUblrTuCUr|lpPomaH(Iv%(7T54C0zU8HM-ZzdF0Q)BsLwrP^9_Qw;#nb51@f3Ga8;?~2N1wYp zgHXAE%%p&Hza^l;Nh)HlO|ItU(a0Pg3{%N%2dnsOu@fB4hgd1?O($s zLhbv;AV@_I?>Xjj_sW_UFvz`n1~|^Pu4VQcKT7mTI1vv` z*B8wf|K0Zts8BEGD|j~4C*6%p7A%dAMHJn(W!_Em761IJD(B@!07!h-SN=oBK8xM~ zMx`zZw{MG#wwIRhxns)5*r7i{$ASz+Gx$!v%~k4-&N`%Nb@9STxrP%2Y=@TO9HieleEQ}6koNaMk3*+X z3dg)NMWRqP)7X*|7mjF;HPYuJd>|9Yw3gKr$CPmk*Dw9Fd|Wii2F54S=h{|oj{>?RC^brn@3OaY=O zzb)wqqLy8b1V_>S>SR*VEU%E#s?A{s|0P1SLmxXVw!&VEI_Ogd4+3n7j~z(`Q*G7+ z7m~^D5gy7zYp3 z^eIQ4qPCr+ar2w~pgOlhW|=4~H*4y{@ur(FihlaVzl93tvUDOyvZQ4GRlmXHgXnHo zt4sW>7q(Qyq)n|SK`WPAxz46R?9cB#b#d?RitlP5ysg+v!4V$rDt&`LQ*xV3BjK)+ zR}lyh>SM^kNx`mnyJFX_4y2ud=kUmJRm;IlzCdjK2C};BXlUasG`#RN5M*Mg zA|y|I+*_bWPtW@6d)c$+HK?PzcoeP?N3DPV);vMiw}C3b>&s)|$kFJ(Fp@dwX`GwJ zK$e+v*bRunWdsNUiXn<>`Yi9H`~Iru=ETi4`<(r&(F6!+fVNEF+ta=DB+sH6)5`H# zf6zcZd3bz<_Ce(u9uL7Tme2!vd7+xTdW&)EGplu@is<@v@wH#ibr!fJ9Gm7xtZQnp z9fiDTn1#+XQ9s>6XjWc;wbG&~AMcR+p_w|_J~v%vL2gd!%=Qy<@#+o+2#_*`zRld6 zH5lNO14uYps%*#rV&$fC9%gki4|DEy_j^nLvJcljF;T&(SG7|i-T=j8VkAcu8=%z8 z>Ut4@SZ-DxrigCHlPB-l*@9d)T%C^>yLAw8A+~oxVjMrds7n!iWa52n z8!C??Qhm(jcymxi^1?LxV)fW=_PzOLDW-Q-7vrsH;YB00(+3xpEDI+lCyGo|{U1p3 zz3`WF(b)guq8H=`762ctlT4I%z%qBn#W!A-A~yr?GlQ$q6+$Bl4-f{WSQ8-uo1|+& zHq=PI&^Xc9gJGJ>-gz zKccCbI^aaJG6u!jKu}%d6zU9<>btdd>MXhKm%}k9A4+dIv&k^XT>~GMwo6g;rVD%! zTdMEKpJN+9bXV})SCa(1J3X-Dw{%){5plzo^S4{W`Su?!fL_%-fQ%Sv;7Be6YIeJ{n!S?wiHrzbaD^)UHUAbBUD@?feC;bE^X5_tW} z^%T=?Sm2i4Sh12%#+14kq`#3&sC+HXz& zxhM8z)ERj84=8H6g8vciKy?Rdc=JQTWbF62ddL&O@~fC^`Y7eQxoIQ7iX~KmR!8Ke zQahlf3$7q1n)6oxn30F1v_v`!(+j~5Z-nGy>?9A)Km-H16&%wFQJFLd8i{D%PPv~! zI4y2t&|G`Cio-(n4$o}R1x0};gfWC#gId0ijPLsM!$D;Iv$e<*n-5K91`$!PNZ~x{ z-Kf6h$-|-VQkP*nS?(ffT-8S%ELOcb1`&?)T`qnPg@=VA>iv=-!mIU^oyQxgx2W&f z`eJu5KdLT-uiJjU$0XQd42QZX3aJh3^)5$Gg6lFaF^-q%n{ldKlNZ?pLRK*OQ}|<+ zq%O|=GcVg{BWJP~(2jR(SM%W|8^kt(ZYFc5w&~}*8y}S=Or=k18n+|EJZ$^o`}!CE z@M0boDJc9Aj$ec+1ovZbt+zBNt?qc6mHW$OU^y6Bdq(pW5fe z>53Y;Q?WBP(GKpJO?n%)KhU#vGUgY8NP^@{m8nU0jN z`FnP(s5Fs(4Fmq(564e@{!n$$sZpU1`o7Zx+r=IL3qjf5RLQzDYc0b^mS6Ee07Ndo zyb!)w&%90b_Uytxv81wx0nz8Y{wh&4&JGii-*H>Ded-#q%`!-UNcKnKF5Nupz82&5 zK+BOOwlJ2s?T}T9c*piRHwgkl8)&>i>^oc{xYJ&Yp&^VP{qhcZM;A#{ml4n>&0i(w zo3SUa0NN@%`gl?VyXf{rA!OKoZ(gh~^(%1|#a*P1gS_XIkUPsCI@sKCO+w#g$S<<*DB@JWe!Puxt|PPdHzGk3v@ z%|k43Bb;rjdV6KIT-f(Cmybb77NG_$@`t8rK(=aOFzr@M#5-$STD-H}2N4jN??+UF z*Ji^g?K$L#`0vh+I@6BQn*B1V(#9c=lIeN4QH+uG!>kjy6MsHN#Cf=wpdJ{BE{@=l z*wZ;7_#zm-WLbQHx&daCgp&)hE&0tDNrQ!tNiHXLj|5To`zLbN0Kq(zhv7>xG z1>6^epnn;$F(bE_IxCnFo9Jx?bwLNs@k1@~Q$2A~#DuIj{P|nQwXM?px?}fuJ}a4N zxMXZDZ_T61EpR^#3{TjUsc@U({-9EG9uLv^79JMQWXLlQ;Q5MZ(fmK#7ssRcJ3`zC zW-Z-uG=%qZBh(VFe@fd*-PM~;m&#nQ8%@~Fdj zu%`$x`R1MNr$Xd@Wm0#e=x-&s))5{kI{=vTV_j?KKGIp@)%r>+Xj$8R;gApYCH3f+ zgZH6@i(t;5fD>TmfK9oh|I97HmEKfq7jE~e?fn}b=>n#`%5`QL+p7+X66s&fl{)74 z?$>LUkMvT*lClBcX^Au9R_<+}lvh`cZ8ZAPi6%6jFJ9v-gN35&ANQyF&4aF;<;?G? zb=$7N*D-#x3Gdi_&pcku?Dl}YkM6%mxDeGITnpSlGezCw?0h(n56AKr2Qu+@Anfyn zqhEe;D)R5@Ft*Bjou!IDO9wEXP_O>)thk;CKmpJgG288E5A|uc04V&o5seCd0;S~a z`uWiTpw29kCXSZ`Ox-|f1VPk}cmx}O@D2CJZ8Cv)&+7<8t`vjttl1ln7IQg-R~G8l za*}k=h*{XjDn)W0s=GtNsD8K!vucEB9JjJTNlNRzkT7{tbYNOVqczw0)M+vs;+M-{ zTKduHTJy=!8NVP6w#1RCEC5xw;YF$CUP%-}_P1qnC`Ra)7ZKh8d)#YvWKXh}4kEx+ zE=hBi8N1ADK=~jQO8SYCSSx%Lr%5TUYM=8IaN7M1QM^(4Bbd><#SSC(JNzj#CyA`N z$U(Qti(&rc{BiIW{9v3 za^40lrhf@_Nuw6EnEh(tk9aF?&)W^=JFw*41%Pe#Y(?q6#o&dn<59?R>Q#6SXx-37 z?G8&({_FzVeYO50n=qoXASJt#aFG&eYK^DB^~7PL>Rm7`_Lp(SXN-?k4zILRZ$B5x z0Xz}ck;EYyM^i)gyt@So!j33^aZW+S5e4ayWQ0ByzyY>AZ_B2?vyRkk(y?>rxeGfV z?Z{%hflOkoD|Kn=N#Q$YwYuFHhe9~WBVI|1b!Tv@yYqm~OHB=IF-5CK z%J#fXt0T^!TUzYtI3=BaHyS$P1Es#hlLSo3`UN7?D#;Ce zear53e0JKf5_ycnyfxqv!SS_%NZWwoja&+8a8UvO8ANA&!~vjieM}}^1AfQTQ~1`9 z-Q|FJC5y&1MD!C0Tis*O6Es^std(m6gD`BOr;_r)DVTa{noSQFUNe)f7`q&W}MRc7?rBv_dS(-`#Fuu*TSTQ#MT*?(r!q;8+up@8tN~Fs(<+ zn3!5|*WPu@Ve;bw@fag8+5#{WC9^^a?Z?^nLisGNu2VrkMjaljl!~u(V^^s{(GYD? zqcC%}$D8V&7|QJiSph}MPJfil@k$D*A1cW=oUA9yv83{Q$}@j7jWu`@7oNB|OqySp z305SUpX^ zkj_tx7Ri}bWhBeo?l`+D*~mOIaEHE%laL8RMFg3j6OsF)^Jy_}@fC}_sc+GKWFy>4 zwBf8%MD4no1h#AEH+iN-*QlT_((xRKfFkn_*Jrt;=f=%3uR=Yj?N>gXphr$if{NBF zsu_12sIGovbshWL8CT8B2l+Wf`2n9Rn(WgNigO~;xX~N28apnjyC>S(Qev%j*>)vX zZi)R=PJLJGpX;}ZP~?%O_eqUHvXoA0lxPuy!o$qoVPb=%AQVTK5!m}=Rx7?J55S1K zOx0w~Z?=`t+g=-=V%rZH3rIGz_1Lz~<+~T2C6?cu25u^^-8OB=t&r2Q?J0*hemh)g z>{QOpmJRVL6p=oUCDwf%qhXi0d`W177l!XG@%xst3OMUBg9BPo8%L#0Grg~-lH3MN zn(AI*dp@7Hf^JKf+n!an^b!63qiSR(@5{Lv1gT05%D{M@AP%!t%6@mi&bAc$#j^mxthyY zF!yC;55cYw5i{ut$Se37=Xoj0=T%h|zTi#iL9?;FfI1+(6!Zf>w#FWXx1y$KU9?GT z_ZXEXu-|RYoI+1=dfse$ALUq@DF&fj&LNPG+^E9juZ_$#mS>r)UHR@qE){8(!Wq7n zMQyD>?ZJ09FRG&!*iOzh=^HmAazIz@6qP~86W>T025L-`%G}#5_$`P2>lef}<^G`? z+tIdf!+hFUmtJbeHKv(7%?dy`%^h%G!aR}*tY(nAmTvL&4ZhAaCkwd8%?;%IKMIGA z;mN~s0k*cv1;lB3dQiS+S8ej#M#OOf4QAWz`wp+ILs9YYxt?AxJiLYGcWi{#E$kHx$tclNuv{RG;$(U4aV6S1<6F@GFx%g9bnRrK36MmJ)g0u-eT8d zoz{eUEPVJR06PMkYu8UCFR3pxbNY)JJP9@Ze^j@F7?SOdFPz|$QJq>Qo+2t1`3-x5 zz3Mc8;qM{B{0-7)hgjybjK3hGCqR9Kg!hT+ z)VYzGqjYeL;uk(vmBen^H zcnq6~>ghw$h38aJM8&krO(b0Uw0q)g=`b@&zk-p6cMmYmd0e#>xy>t{oJRm5W{$|C z-=3T<%p*Vd%6y*Aa$*kKW-2PR!GXlg+0a3M3e{N;XN^8sJJ!_wY(ZzbF|0BRN@i9> zjGxLOxA4UtQ?}gnlT3eu{CGkT`$HdGS1=$Y&Y|kuE1CCeFgL_yX0xIgVG=YQ$J7IN)G!Q#;)4ga zfPO?;!qM4&!GRF8|Fq{53X9Uun9(FE@K}B*Ca>w_D*7YH*gVBGX zhRpkdHA8N3eA^DLzXn~b*qNEC+Ly?#X^#yK*n2RQ*B~-*MQ@m`%<$|bP_>Mr`dpz} zXl&A~PPuyz8WI@$eu5p0X`9ai?Z!O^XLxlLfA5c7Y`A!C0y4*(yEgQc-;{GD~d24AM%d7mIZQ*8VP zUqb~l0$wc5w?x4#QZzHK7=pb><%_$*^;g&>x5+Q)DT0ofK zWck%09OG^PSx;?tYtwD1kI$^t0!`m`aK7)Hruv#C&1-x}y6dm)57@YSvT0KXwg{yB5|t}8WUzsi``q;BQONUt;!^X?a-}AzvnFQ&QgCHPA5q+XuonF)g%xbC~G(G zH(ugm!hZN}=bKrNW-Da``deToM7yHmJNJIb;r_aI%Q=I(AQT2!;gS`9T>l?!tm(2% zHp<|Mz4S)8huJ_Hpb%>cQwjVT`o3{Ld$`m_dD=vRRB*6&D-};p+(6=s$=S&VNkz^D4^*-1#L1cu1o2oc$q~j`G|Um{9hkN% zTf@o2{P5Io3{D;&PcvZ+9d{cg!xI7n7HZ-i>eZ-SMD#vGGmXtSbO8Jg;(Ke+*hu6qryGC z5}d`?zU5^7{z#bWgDgZgcn#!9LX0j*ib$zGMSAq)J@QB{R=Lz-EXd+9lHO`Is5bA+ z*>yRl=nswP*NUdw4&LAG=TnKNtxl=`&=K>ty|wFZhSmqF1}}E)XxYmktrUs6k1JLC zz2m4*_$adQ&HvY_)#6>=Ey^L$fW>bwl&aQPi1tYmvag?zRTplR5k8;$B065CSZGCDaEsXnS3{;uOtqiKTD}#u}3fy%b3mhQ*dc{b1mYnuX1Aiy?jI$lH{GmbZ*? z_LfI-Wz|jh@#RTE3Trbw-Jn00FnINO<@^yzZ8jc@fap`X9XAuvx|o@z2;vuCj#OpT zGoST}Bg^YHMPB~cD4wy9@a<`px@RNzf9eG^`@-_s(5woPapWn~FTjKZSqK;tYdvX+ zav{2zUryIM#4MPeWs7`Al7(yKi~12;1zTqog%eeUA4AZ zu;V5QbqzCCNY+>&(v_BROVCWudT-TN0d<{|s9!}&G9V{VH4Xn{N3>R(s=036&`fPe;l>sdes9yiZ>)955MjGySByTgt zDfp9M3Y^PWk@R?onQ^Q;C>l?qg%8u*SZ%iQ$-vPO(r!X^*ng}dXN}j`+_`%5C3#K6 zon@r=ugAr4spWt|#?vS3eLeepE$zwjx{@Z#mX>MqpUhH+>prI?;c{!fO@#pGZe@0?47V92{HV zn8g8DH?4kjUA})k?M?_9xct~5mumdF$D$BCuq&U=%b30b{G8Ixw^HFg2+RwB1XStm zCmh((mV(4rxt%A!(DR*{$y&Ea9bLzUVvnJRC&N7ko_`-`KK}+ni@Rei^;+++HclB) zH#zku2(}|OizeA8tS!T;dBCLB5Mfixxi}ND)06m1p{D+Y6iWLMx#`Lsv#dTtu((OR zA|o_#W2{NgF?!j@_xSUX#Vg!mwS_^rCX+D-pDA(9AUkdGvlS|UV9kCl-%hVi19P3BrnnNqVlyd-+m;889vnnI?cd{mnM3Uk7BujjU zVt_?T>Xi*brHQ4P_{ZU3nG$3XTCNbP`eqfobi!D^-aW*98x(Th`%(U&L}QOjgd7uP12!Cy!di4< zJ-@#5x34MvxDs{Ve=Yj`DZ7o*JRq56Un_-Y|4_#g-b#%?e0Df6*>cQ695-a3T{>J^ zTh$AbVSv~17jS4}j+`m{yng$qM&yqjih0;b7t5qQm1cfDwfMYt-;%PM@rTbEj=Y*?v~I_fpJ|3#hNSl?Oy>=JNp?3_S!(@lNIkdz>QIC8 zsl2tG$*i+p(fYav$5id?M~rb>HIac2gucP!HV*nk@C;hQ`akKza0Q5UYLo3*X6qGn zqEAZ@_)a-@cx$la<~H>DFNh$WNZB&C4)(Pesau2F=j3l(I3t_UJ7rf8%O`J;+8oG( zkbMKCjRULF`QXB=KS9Z-YzAqB5C^1dO-ld(BP$1DAmfY~qVVv%KHupp#+lu_UY4-_ zX^>uO=W8x+e*Ot^wa9EmjAxn8o5UgEFCLr}r>Bfsxb4v6e($EOgv*lNQX8i7w)*s+aAiy|>wmF(o;a$dN&+)+ZtC6J zT@_POz8k=LOYg^hW==&&ClPRLFWwcBc621$&1>Ve>aE&H<>U+!X3Tz2JpbKF^{1Ox z*uIe6+C?XLrm$Q@T9^=R>7leE&OWp5)&TB(m1v%$P24(UsS8Cm0)OwT^{-9VdgXKC zfr@+ogTB&fzHm5YAIlXP(9A~iuz2peaG16bVSCOK z`g13-%#y7uV7x{k_ceXnWB?~6d7L;|f@e3c`l*oFr?7&TgxS--y|YIE9dQht)CM;K`2=G=DO#b*$(K#d@-CYn>laR4ktV z@S~JWq2`Vs=S~TcKhc!pZHcna9!u+RdE0-4&}bVP#Ho4Q%zJ#D81u?UMVD)GG{N^P zyK=+tJ}EMRQl3LwDol2)U!{hee{JzY(HYAb<3&cf6XkeDn3TS?D@sD&Hnn5h^5ONq zFGiLq%$fzs5XToe$@r3T1qPe1y*Ma02BBo8EhdJum7VglEyDLdGM1c%3~A42Edf2z zdTPSGL!(B;Uq6D&QC>ri?bIZTP}0}WP}b}ey=e^1r#Tr_@$Fz=?qZaHk!&}8X?@b4 z(U(NGb405ubN#x6ojLF_=NwGKXkuFIb>#eK0k68l=hJTbqHb=LZh>|a?x|lM@qFL{GoHrp5|0k- zz2H9G1y3Dw53HR#>xXgEK7ES?|D*2ZDLnIBav~V9FLY&t=1@(<0^bFv;?S~f@4itU z!%PWBHBSQqxd}SmB2l#tO+^`na!As_00Du_gJ&K z;c?i`$*$(BD_CqXV_AYgR08wz1D-|~Pqh>r17c5p)k^g5#x7NwrYG+XcjsVHXqw<1 z9sh*0yF+>8Um#?h&i$sTD$Glz1A6*trcy+RXU;*d?RG!HAHCNZcBS|Gil-4GSnm-0 z6V#anC(cJRCyi+0EYmnR`$acu+x+h}@7ooPYRARjmbGf!o{?B^0I!m_TalVl8luQ)GdO?7#nP?%wBj{t&;=RxcOgqh#GLaH+f$X8JWyr*it?!Z;F-$UH4Q90;mC&nVQyJNZR<^fy6-c z5L4DSgVBPbp7yG@82zHbKdtId^X&ZW-J9!!mg3tav*7bN^{&P5RwH0{x0AP7C_R=c zUo0zj7$#$`gOs!U5KD+%w|bIMz=S{Pdvm9hRvsR#AYC>$HcIk13nYPFs0$0j=*LJw zeQ;Hd4g^!2#9`*DsPhUfy2m6 zZW&=tqvyWkMRPrsQ ztOT<@tMet_GA#EKc@!{dZBC(b$|Xy6eAcVvS=12!kN5DsD?q478h@>)g7^u^A|n?K zVj?F_$P4nZ8<=nus=ize$uaZ$!E>946k^_bA8z6+bTe{BDB7u|<88ualTtT+#xY3!jd`s^KC z=h4%V9|`lkWEp@fKD33VxSz|fwr#3;`5cW*YL1Gg_Ms1&A_OHN9Eh;g|2fq%xeLFG zx{)i>GdIL?%S%rAK&{<&>&(VX`Mn3lTuOmdWe2&>;itcXfZa zs5E5jIf%dq??$m0q^IdYBZp#;O$ScD1teLc)2G?&O?U zPF&Me^O->@L@Bx7*T5e7;3~R#hdG2%`V(|Qe9VKa;?8PX!wgr`#M0h14F#CN#5m2OJ9A`A|Un=DjgXme(@J^8yzb z{-*SmqPPgn$bw`giyp#8ailEUde4Guw#t2bv z{A~mUt$z?FW)8*WZ)Cr0N2h{UNaVt8p53SqPYm9+owx4*Ep&sj(kEl5)|LAs8 z#H)xAY~&jmn0W!-iAO3uIGs`UwG^HfdjE@To&KsU*OL`Asu+a}XR3553b0d@1x5hUja`XFBIs)H{Y$3#k8Cw7Iv2hUSxA1% zz#KQzcT{cRQ<)Ny(;2&C^OK*Vm!63$mQHO#Oc7J8wi}J z?|nUcMov$ocE6xfd2OnVIt=Vk>R~x2eP;d14?8?DPolaTk{&x!Eh`g8sDc@o4WoD5 z?*Phv;7~S55#Fo~e|JR`g$Y9ZG@z=A?t39pN7h=*~?jkn=uO zayHj44A*!>#02zso2NuC8ZLS8mc&$9V9u{S=-)YqT1@N1y0|9)GZxHUjvyG7Vz`W6 z8>DTwFX9lAM}l;}!HP|nzql7qzN7XZRa5#|iWXYQ93=K3o_dIAKsgrbkZTK>Cvk*g zxvpO|P-UA$QUhflC>vuWnH=Bcnul*TH@Ny)eP#5&`x+by+U7BJLq20h%b>l#9i001 z6%Hgnv7qJ3Yw0X-X%ZXBPJ+QaZgz=?<5R9+N=jLy||Efrb9mwK zx@NT3z`i0rTClVUZv`YN5RGNb?5xn2;fMiJjjxXTCZpc<-_#=LSm= zrp-CzDza*nJ%N&)k{j23rU|_a=5V<&pgv6i=kIYIGArNl8e;DPB7TS^7rfg^`jd)* z00TQU6TbydM$;~Z55>P`z>>_U{PgpK|Gtt#b$NyyyRYsL z#_2G^L_F5M)LOOTCgHitZD7j@Ka2CC=A1fAeL78@5)2HcG!&1CY1b57o(#G9hv}a@ zcA+ak(MknQbXl$>3|G%yB28@Z)G#q~=!JaZLs|zBGN@YR5D7l=X*7xB_vO9d$4847 zYF$arBK{E@lS9NxSGC7*3)KEOl**Z~Jf@&%Q^1db>Xgp)f+$TMvH5Gn*0#Os4NbPh zxddr_obYAhk?|us3A^g&U6It!vd>hzvIDpHX8iPK?qn&^d8x%BsSFma-v~+ADPg7p z0eX{nuABn`w0=zDAV!w~&51BXtMj~>T-k&g2O$&)p_aQe-au7v@eCexrOZ-!!eKr4#1)eYU$ygD|@MWaUC3p_bL=1TqB@C=uA%;>!4+8NX&32- zS9)CCG#-DRePPz#C}cGYzk2(|#l|(%N4dOO@ ztlmvAR}K7f#>W+9>yN)PN>JCMZk>Q#Ar=vZlBXcq`Zk0RXn0cY`|?)>S6O4s;$LW2 zJedg075RN84KnIPrP@WbfB&(1j?EM~B9#PC!!BJju!b~?s@1{2tGiGl%=ilJzC_ok zyV`J~+3_$x%IIP>HxT)7X&U98WvNNoG773_r>kL)zkZ^YJp^82-P4@pr88~d$o4=O zc+w}~;p{2PxBMmbtNf#!7S3|DitIPE5i$-wp)XGHtHi1)h#NkQG;A*&P~Uc@=%2lc zwa8n&k6tXG6Az^2jF?Jjm!1QHBT|6Nj|sFmY|Lu&O@mkaf4sNHC@$>e6;DH$7<5U> zmTzVcRC)Gx2^n*|ZM&T!&Me?maq8mHimi)tecNa%_a!@z(_Iz8WM9l^J zNz?#R#!zULisK}0d;0+ZB}R|lpCEN-$JCT9(*C!%!8H>eZlh^sPP@PQ=?k!@jg z>UgzUc26X;Un^o1uE>$Hyk5zO47aL>u#auTGcddnzlXkrH`*B($6(ixql&iS>`Oh5 zlHSD`TYcxS+o%SaMk-dEfAvSlTC{9*SRr8weW<-FA?uA_6caYSh=5c_Hi zpCjVi$<-ED$~W|Z`qJbh3w^Th=pZ9q7ZW6hFN-hjY8U z%0A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
ym$LNEARa?wh;`t@^$^AiHawf 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 literal 0 HcmV?d00001 diff --git a/maps/away/blueriver/blueriver.dm b/maps/away/blueriver/blueriver.dm index 61e01002cc432..bb17447f1056a 100644 --- a/maps/away/blueriver/blueriver.dm +++ b/maps/away/blueriver/blueriver.dm @@ -142,7 +142,7 @@ /turf/simulated/floor/away/blueriver/alienfloor/Initialize() .=..() - set_light(0.7, 1, 5, l_color = "#0066ff") + set_light(5, 0.7, l_color = "#0066ff") /turf/unsimulated/wall/away/blueriver/livingwall name = "alien wall" @@ -173,7 +173,7 @@ .=..() icon_state = "bluespacecrystal[rand(1,3)]" - set_light(0.7, 1, 5, l_color = "#0066ff") + set_light(5, 1, l_color = "#0066ff") /turf/unsimulated/wall/supermatter/no_spread/Process() return PROCESS_KILL diff --git a/maps/away/magshield/magshield.dm b/maps/away/magshield/magshield.dm index b51502d381102..5947485744ee9 100644 --- a/maps/away/magshield/magshield.dm +++ b/maps/away/magshield/magshield.dm @@ -49,8 +49,8 @@ icon_state = "maggen" anchored = TRUE density = TRUE - light_outer_range = 3 - light_max_bright = 1 + light_range = 3 + light_power = 1 light_color = "#ffea61" var/heavy_range = 10 var/lighter_range = 20 @@ -130,13 +130,13 @@ icon_state = "nav_light_green" anchored = TRUE density = TRUE - light_outer_range = 10 - light_max_bright = 1 + light_range = 10 + light_power = 1 light_color = "#00ee00" /obj/structure/magshield/nav_light/New()//try make flashing through the process ..() - set_light(light_max_bright, light_outer_range / 6, light_outer_range, 2, light_color) + set_light(light_range, light_power, light_color) /obj/structure/magshield/nav_light/red desc = "Large and bright light regularly emitting red flashes." diff --git a/maps/away/skrellscoutship/skrellscoutship_machines.dm b/maps/away/skrellscoutship/skrellscoutship_machines.dm index e0413efe4ea9e..485a0e9a33338 100644 --- a/maps/away/skrellscoutship/skrellscoutship_machines.dm +++ b/maps/away/skrellscoutship/skrellscoutship_machines.dm @@ -41,7 +41,7 @@ if(on) AddOverlays(field_image) - set_light(0.8, 1, 6, l_color = COLOR_CYAN) + set_light(6, 0.8, l_color = COLOR_CYAN) icon_state = "core1" else CutOverlays(field_image) diff --git a/maps/event/placeholders/placeholders.dm b/maps/event/placeholders/placeholders.dm index 106e6944f0cd4..6ec350ba50644 100644 --- a/maps/event/placeholders/placeholders.dm +++ b/maps/event/placeholders/placeholders.dm @@ -40,16 +40,16 @@ Middle-Click / Ctrl-Click - Jump a placeholder to a point and deselect it if (isnull(option)) return selected.color = option -/* else if (option == "Sensor") + else if (option == "Sensor") option = alert(user, "Sensor Range", null, "Off", "Short", "Far") if (isnull(option)) return else if (option == "Off") selected.set_light(0) else if (option == "Short") - selected.set_light(1, 2, 3) + selected.set_light(3, 1) else if (option == "Far") - selected.set_light(1, 6, 7)*/ + selected.set_light(7, 1) else if (option == "Scan") var/scantext = "" option = input(user, "Placeholder Scan Description", null, scantext) as null | text diff --git a/maps/random_ruins/exoplanet_ruins/icarus/icarus.dm b/maps/random_ruins/exoplanet_ruins/icarus/icarus.dm index f556f1a546625..be9e05912afc9 100644 --- a/maps/random_ruins/exoplanet_ruins/icarus/icarus.dm +++ b/maps/random_ruins/exoplanet_ruins/icarus/icarus.dm @@ -38,7 +38,7 @@ S.update_rad_power(radiation_power) SSradiation.add_source(S) - loc.set_light(0.4, 1, req_range, l_color = COLOR_LIME) //The goo doesn't last, so this is another indicator + loc.set_light(req_range, 0.4, l_color = COLOR_LIME) //The goo doesn't last, so this is another indicator /obj/effect/icarus_irradiate/Destroy() . = ..() diff --git a/maps/random_ruins/exoplanet_ruins/monoliths/monoliths.dm b/maps/random_ruins/exoplanet_ruins/monoliths/monoliths.dm index c6556b1e5d070..1a467c6476abf 100644 --- a/maps/random_ruins/exoplanet_ruins/monoliths/monoliths.dm +++ b/maps/random_ruins/exoplanet_ruins/monoliths/monoliths.dm @@ -38,7 +38,7 @@ I.layer = ABOVE_LIGHTING_LAYER I.plane = EFFECTS_ABOVE_LIGHTING_PLANE AddOverlays(I) - set_light(0.3, 0.1, 2, l_color = I.color) + set_light(2, 0.3, l_color = I.color) var/turf/simulated/floor/exoplanet/T = get_turf(src) if(istype(T)) diff --git a/maps/torch/torch_security_state.dm b/maps/torch/torch_security_state.dm index 997a09050ba39..3d8ede4fd2279 100644 --- a/maps/torch/torch_security_state.dm +++ b/maps/torch/torch_security_state.dm @@ -29,9 +29,8 @@ icon = 'icons/misc/security_state.dmi' alarm_level = "off" - 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 @@ -49,9 +48,8 @@ name = "code violet" alarm_level = "on" - light_max_bright = 0.5 - light_inner_range = 1 - light_outer_range = 2 + light_range = 2 + light_power = 1 light_color_alarm = COLOR_VIOLET light_color_status_display = COLOR_VIOLET @@ -68,9 +66,8 @@ name = "code orange" alarm_level = "on" - light_max_bright = 0.5 - light_inner_range = 1 - light_outer_range = 2 + light_range = 2 + light_power = 1 light_color_alarm = COLOR_ORANGE light_color_status_display = COLOR_ORANGE overlay_alarm = "alarm_orange" @@ -88,9 +85,8 @@ icon = 'icons/misc/security_state.dmi' alarm_level = "on" - light_max_bright = 0.5 - light_inner_range = 1 - light_outer_range = 2 + light_range = 2 + light_power = 1 light_color_alarm = COLOR_BLUE light_color_status_display = COLOR_BLUE overlay_alarm = "alarm_blue" @@ -107,9 +103,8 @@ icon = 'icons/misc/security_state.dmi' alarm_level = "on" - light_max_bright = 0.75 - light_inner_range = 1 - light_outer_range = 3 + light_range = 4 + light_power = 2 light_color_alarm = COLOR_RED light_color_status_display = COLOR_RED overlay_alarm = "alarm_red" @@ -135,9 +130,8 @@ icon = 'icons/misc/security_state.dmi' 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/maps/using.dm b/maps/using.dm index 926908a4411b1..76dcb3d2e26c0 100644 --- a/maps/using.dm +++ b/maps/using.dm @@ -1,5 +1,15 @@ //Easily change which map to build by uncommenting ONE below. +<<<<<<< ours +<<<<<<< ours //#include "example\map.dm" //#include "torch\map.dm" #include "sierra\map.dm" +======= +#include "example\map.dm" +//#include "torch\map.dm" +>>>>>>> theirs +======= +//#include "example\map.dm" +#include "torch\map.dm" +>>>>>>> theirs diff --git a/test/check-paths.sh b/test/check-paths.sh index a419f41432cac..203196b86495c 100755 --- a/test/check-paths.sh +++ b/test/check-paths.sh @@ -46,7 +46,7 @@ exactly 2 ">> uses" '(?)>>(?!>)' -P exactly 0 "incorrect indentations" '^( {4,})' -P exactly 24 "text2path uses" 'text2path' exactly 4 "update_icon() override" '/update_icon\((.*)\)' -P -exactly 5 "goto use" 'goto ' +exactly 4 "goto use" 'goto ' exactly 1 "NOOP match" 'NOOP' exactly 342 "spawn uses" '^\s*spawn\s*\(\s*(-\s*)?\d*\s*\)' -P exactly 0 "tag uses" '\stag = ' -P '**/*.dmm' @@ -56,7 +56,7 @@ exactly 0 "emagged = 0/1" 'emagged\s*=\s*\d' -P exactly 0 "simulated = 0/1" 'simulated\s*=\s*\d' -P exactly 2 "var/ in proc arguments" '(^/[^/].+/.+?\(.*?)var/' -P exactly 0 "tmp/ vars" 'var.*/tmp/' -P -exactly 6 "uses of .len" '\.len\b' -P +exactly 7 "uses of .len" '\.len\b' -P exactly 388 "attackby() override" '\/attackby\((.*)\)' -P exactly 15 "uses of examine()" '[.|\s]examine\(' -P # If this fails it's likely because you used '/atom/proc/examine(mob)' instead of '/proc/examinate(mob, atom)' - Exception: An examine()-proc may call other examine()-procs exactly 7 "direct modifications of overlays list" '\boverlays((\s*[|^=+&-])|(\.(Cut)|(Add)|(Copy)|(Remove)|(Remove)))' -P