From e8af4afc88103672be4119c8e6bf06e5f47cce04 Mon Sep 17 00:00:00 2001 From: Leshana Date: Tue, 11 Apr 2017 22:42:24 -0400 Subject: [PATCH] Port tg/paradise/good soft-edge lighting Ports https://github.com/ParadiseSS13/Paradise/pull/6161 Its merged in nearly verbatim as far as the lighting module goes. Changes outside the lighting module are merged based on our codebase. --- code/__defines/lighting.dm | 44 +++ code/_helpers/unsorted.dm | 2 +- code/controllers/Processes/lighting.dm | 98 ++++++ code/game/area/Space Station 13 areas.dm | 32 +- code/game/area/areas.dm | 2 +- code/game/area/asteroid_areas.dm | 2 +- code/game/atoms_movable.dm | 6 + code/game/gamemodes/cult/hell_universe.dm | 2 +- .../endgame/supermatter_cascade/universe.dm | 2 +- code/game/turfs/turf.dm | 1 - code/game/turfs/turf_changing.dm | 28 +- code/modules/lighting/__lighting_docs.dm | 8 +- code/modules/lighting/_lighting_defs.dm | 9 - code/modules/lighting/light_source.dm | 300 ------------------ code/modules/lighting/lighting_area.dm | 9 + code/modules/lighting/lighting_atom.dm | 47 ++- code/modules/lighting/lighting_corner.dm | 125 ++++++++ code/modules/lighting/lighting_overlay.dm | 127 ++++---- code/modules/lighting/lighting_process.dm | 35 -- code/modules/lighting/lighting_setup.dm | 16 + code/modules/lighting/lighting_source.dm | 290 +++++++++++++++++ code/modules/lighting/lighting_system.dm | 25 -- code/modules/lighting/lighting_turf.dm | 100 +++++- code/modules/lighting/~lighting_undefs.dm | 6 +- code/modules/mining/mine_turfs.dm | 1 + .../reagent_containers/food/drinks/bottle.dm | 2 +- icons/effects/lighting_overlay.dmi | Bin 260 -> 0 bytes icons/effects/lighting_overlay.png | Bin 0 -> 1480 bytes maps/RandomZLevels/zresearchlabs.dmm | 118 +++---- maps/overmap/bearcat/bearcat.dm | 2 +- polaris.dme | 9 +- 31 files changed, 876 insertions(+), 572 deletions(-) create mode 100644 code/controllers/Processes/lighting.dm delete mode 100644 code/modules/lighting/_lighting_defs.dm delete mode 100644 code/modules/lighting/light_source.dm create mode 100644 code/modules/lighting/lighting_area.dm create mode 100644 code/modules/lighting/lighting_corner.dm delete mode 100644 code/modules/lighting/lighting_process.dm create mode 100644 code/modules/lighting/lighting_setup.dm create mode 100644 code/modules/lighting/lighting_source.dm delete mode 100644 code/modules/lighting/lighting_system.dm delete mode 100644 icons/effects/lighting_overlay.dmi create mode 100644 icons/effects/lighting_overlay.png diff --git a/code/__defines/lighting.dm b/code/__defines/lighting.dm index 56c95535fe..58f2e96b14 100644 --- a/code/__defines/lighting.dm +++ b/code/__defines/lighting.dm @@ -4,3 +4,47 @@ for(type in view(range, dview_mob)) #define END_FOR_DVIEW dview_mob.loc = null + +#define LIGHTING_FALLOFF 1 // type of falloff to use for lighting; 1 for circular, 2 for square +#define LIGHTING_LAMBERTIAN 0 // use lambertian shading for light sources +#define LIGHTING_HEIGHT 1 // height off the ground of light sources on the pseudo-z-axis, you should probably leave this alone + +#define LIGHTING_LAYER 10 // drawing layer for lighting overlays +#define LIGHTING_ICON 'icons/effects/lighting_overlay.png' // icon used for lighting shading effects + +#define LIGHTING_ROUND_VALUE (1 / 128) //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_SOFT_THRESHOLD 0.05 // If the max of the lighting lumcounts of each spectrum drops below this, disable luminosity on the lighting overlays. + +// 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 \ + ) \ + +// Helpers so we can (more easily) control the colour matrices. +#define CL_MATRIX_RR 1 +#define CL_MATRIX_RG 2 +#define CL_MATRIX_RB 3 +#define CL_MATRIX_RA 4 +#define CL_MATRIX_GR 5 +#define CL_MATRIX_GG 6 +#define CL_MATRIX_GB 7 +#define CL_MATRIX_GA 8 +#define CL_MATRIX_BR 9 +#define CL_MATRIX_BG 10 +#define CL_MATRIX_BB 11 +#define CL_MATRIX_BA 12 +#define CL_MATRIX_AR 13 +#define CL_MATRIX_AG 14 +#define CL_MATRIX_AB 15 +#define CL_MATRIX_AA 16 +#define CL_MATRIX_CR 17 +#define CL_MATRIX_CG 18 +#define CL_MATRIX_CB 19 +#define CL_MATRIX_CA 20 diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm index d244916ec2..0fa1b9033b 100644 --- a/code/_helpers/unsorted.dm +++ b/code/_helpers/unsorted.dm @@ -952,7 +952,7 @@ proc/DuplicateObject(obj/original, var/perfectcopy = 0 , var/sameloc = 0) // var/area/AR = X.loc -// if(AR.lighting_use_dynamic) +// if(AR.dynamic_lighting) // X.opacity = !X.opacity // X.sd_SetOpacity(!X.opacity) //TODO: rewrite this code so it's not messed by lighting ~Carn diff --git a/code/controllers/Processes/lighting.dm b/code/controllers/Processes/lighting.dm new file mode 100644 index 0000000000..d46cab1d19 --- /dev/null +++ b/code/controllers/Processes/lighting.dm @@ -0,0 +1,98 @@ +/var/lighting_overlays_initialised = FALSE + +/var/list/lighting_update_lights = list() // List of lighting sources queued for update. +/var/list/lighting_update_corners = list() // List of lighting corners queued for update. +/var/list/lighting_update_overlays = list() // List of lighting overlays queued for update. + +/var/list/lighting_update_lights_old = list() // List of lighting sources currently being updated. +/var/list/lighting_update_corners_old = list() // List of lighting corners currently being updated. +/var/list/lighting_update_overlays_old = list() // List of lighting overlays currently being updated. + + +/datum/controller/process/lighting + // 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 SECONDS) + var/next_stats_update = 0 + var/stat_updates_to_keep = 5 + +/datum/controller/process/lighting/setup() + name = "lighting" + + schedule_interval = 0 // run as fast as you possibly can + sleep_interval = 10 // Yield every 10% of a tick + defer_usage = 80 // Defer at 80% of a tick + create_all_lighting_overlays() + lighting_overlays_initialised = TRUE + + // Pre-process lighting once before the round starts. Wait 30 seconds so the away mission has time to load. + spawn(300) + doWork(1) + +/datum/controller/process/lighting/doWork(roundstart) + + lighting_update_lights_old = lighting_update_lights //We use a different list so any additions to the update lists during a delay from scheck() don't cause things to be cut from the list without being updated. + lighting_update_lights = list() + for(var/datum/light_source/L in lighting_update_lights_old) + + if(L.check() || L.destroyed || L.force_update) + L.remove_lum() + if(!L.destroyed) + L.apply_lum() + + else if(L.vis_update) //We smartly update only tiles that became (in) visible to use. + L.smart_vis_update() + + L.vis_update = FALSE + L.force_update = FALSE + L.needs_update = FALSE + + SCHECK + + lighting_update_corners_old = lighting_update_corners //Same as above. + lighting_update_corners = list() + for(var/A in lighting_update_corners_old) + var/datum/lighting_corner/C = A + + C.update_overlays() + + C.needs_update = FALSE + + SCHECK + + lighting_update_overlays_old = lighting_update_overlays //Same as above. + lighting_update_overlays = list() + + for(var/A in lighting_update_overlays_old) + var/atom/movable/lighting_overlay/O = A + O.update_overlay() + O.needs_update = 0 + SCHECK + + stats_queues["Source"] += lighting_update_lights_old.len + stats_queues["Corner"] += lighting_update_corners_old.len + stats_queues["Overlay"] += lighting_update_overlays_old.len + + 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(stats_list.len > stat_updates_to_keep) + stats_list.Cut(stats_list.len) + +/datum/controller/process/lighting/statProcess() + ..() + stat(null, "[total_lighting_sources] sources, [total_lighting_corners] corners, [total_lighting_overlays] overlays") + for(var/stat_type in stats_lists) + stat(null, "[stat_type] updates: [jointext(stats_lists[stat_type], " | ")]") diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm index 0c8d6ee052..01e95e5716 100755 --- a/code/game/area/Space Station 13 areas.dm +++ b/code/game/area/Space Station 13 areas.dm @@ -100,7 +100,7 @@ var/list/ghostteleportlocs = list() icon_state = "space" requires_power = 1 always_unpowered = 1 - lighting_use_dynamic = 1 + dynamic_lighting = 1 power_light = 0 power_equip = 0 power_environ = 0 @@ -293,7 +293,7 @@ area/space/atmosalert() /area/shuttle/mining name = "\improper Mining Elevator" music = "music/escape.ogg" - lighting_use_dynamic = 0 + dynamic_lighting = 0 base_turf = /turf/simulated/mineral/floor/ignore_mapgen /area/shuttle/mining/station @@ -392,7 +392,7 @@ area/space/atmosalert() /area/shuttle/research name = "\improper Research Elevator" music = "music/escape.ogg" - lighting_use_dynamic = 0 + dynamic_lighting = 0 base_turf = /turf/simulated/mineral/floor/ignore_mapgen /area/shuttle/research/station @@ -418,7 +418,7 @@ area/space/atmosalert() name = "\improper CentCom" icon_state = "centcom" requires_power = 0 - lighting_use_dynamic = 0 + dynamic_lighting = 0 /area/centcom/control name = "\improper CentCom Control" @@ -491,7 +491,7 @@ area/space/atmosalert() name = "\improper Mercenary Base" icon_state = "syndie-ship" requires_power = 0 - lighting_use_dynamic = 0 + dynamic_lighting = 0 /area/syndicate_mothership/control name = "\improper Mercenary Control Room" @@ -543,7 +543,7 @@ area/space/atmosalert() name = "\improper Thunderdome" icon_state = "thunder" requires_power = 0 - lighting_use_dynamic = 0 + dynamic_lighting = 0 sound_env = ARENA /area/tdome/tdome1 @@ -624,7 +624,7 @@ area/space/atmosalert() name = "\improper Wizard's Den" icon_state = "yellow" requires_power = 0 - lighting_use_dynamic = 0 + dynamic_lighting = 0 /area/skipjack_station name = "\improper Skipjack" @@ -1485,7 +1485,7 @@ area/space/atmosalert() /area/holodeck name = "\improper Holodeck" icon_state = "Holodeck" - lighting_use_dynamic = 0 + dynamic_lighting = 0 sound_env = LARGE_ENCLOSED /area/holodeck/alphadeck @@ -1638,7 +1638,7 @@ area/space/atmosalert() /area/solar requires_power = 1 always_unpowered = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 base_turf = /turf/space auxport @@ -2336,7 +2336,7 @@ area/space/atmosalert() /area/shuttle/constructionsite name = "\improper Construction Site Shuttle" icon_state = "yellow" - lighting_use_dynamic = 0 + dynamic_lighting = 0 base_turf = /turf/simulated/mineral/floor/ignore_mapgen /area/shuttle/constructionsite/station @@ -2493,25 +2493,25 @@ area/space/atmosalert() name = "\improper AI Sat Ext" icon_state = "storage" luminosity = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 /area/AIsatextFS name = "\improper AI Sat Ext" icon_state = "storage" luminosity = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 /area/AIsatextAS name = "\improper AI Sat Ext" icon_state = "storage" luminosity = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 /area/AIsatextAP name = "\improper AI Sat Ext" icon_state = "storage" luminosity = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 /area/NewAIMain name = "\improper AI Main New" @@ -2685,7 +2685,7 @@ area/space/atmosalert() name = "Beach" icon_state = "null" luminosity = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 requires_power = 0 ambience = list() var/sound/mysound = null @@ -2807,7 +2807,7 @@ var/list/the_station_areas = list ( name = "Keelin's private beach" icon_state = "null" luminosity = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 requires_power = 0 var/sound/mysound = null /* diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index e118e6fa4d..f152050990 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -18,7 +18,7 @@ power_equip = 0 power_environ = 0 - if(lighting_use_dynamic) + if(dynamic_lighting) luminosity = 0 else luminosity = 1 diff --git a/code/game/area/asteroid_areas.dm b/code/game/area/asteroid_areas.dm index 68f9807ead..66a7e86913 100644 --- a/code/game/area/asteroid_areas.dm +++ b/code/game/area/asteroid_areas.dm @@ -126,7 +126,7 @@ /area/outpost/engineering/solarsoutside requires_power = 1 always_unpowered = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 aft name = "\improper Engineering Outpost Solar Array" diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index b76620fa95..b511c9befd 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -40,7 +40,13 @@ reagents = null for(var/atom/movable/AM in contents) qdel(AM) + var/turf/un_opaque + if(opacity && isturf(loc)) + un_opaque = loc + loc = null + if(un_opaque) + un_opaque.recalc_atom_opacity() if (pulledby) if (pulledby.pulling == src) pulledby.pulling = null diff --git a/code/game/gamemodes/cult/hell_universe.dm b/code/game/gamemodes/cult/hell_universe.dm index 496af98ef8..75c97d5cee 100644 --- a/code/game/gamemodes/cult/hell_universe.dm +++ b/code/game/gamemodes/cult/hell_universe.dm @@ -62,7 +62,7 @@ In short: /datum/universal_state/hell/OverlayAndAmbientSet() spawn(0) - for(var/atom/movable/lighting_overlay/L in world) + for(var/datum/lighting_corner/L in world) L.update_lumcount(1, 0, 0) for(var/turf/space/T in turfs) diff --git a/code/game/gamemodes/endgame/supermatter_cascade/universe.dm b/code/game/gamemodes/endgame/supermatter_cascade/universe.dm index 11c61cfed6..6f549d15f4 100644 --- a/code/game/gamemodes/endgame/supermatter_cascade/universe.dm +++ b/code/game/gamemodes/endgame/supermatter_cascade/universe.dm @@ -93,7 +93,7 @@ The access requirements on the Asteroid Shuttles' consoles have now been revoked /datum/universal_state/supermatter_cascade/OverlayAndAmbientSet() spawn(0) - for(var/atom/movable/lighting_overlay/L in world) + for(var/datum/lighting_corner/L in world) if(L.z in using_map.admin_levels) L.update_lumcount(1,1,1) else diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 1074af1b90..c13b4606d3 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -21,7 +21,6 @@ var/icon_old = null var/pathweight = 1 // How much does it cost to pathfind over this turf? var/blessed = 0 // Has the turf been blessed? - var/dynamic_lighting = 1 // Does the turf use dynamic lighting? var/list/decals diff --git a/code/game/turfs/turf_changing.dm b/code/game/turfs/turf_changing.dm index 7379d2c80b..78946ff832 100644 --- a/code/game/turfs/turf_changing.dm +++ b/code/game/turfs/turf_changing.dm @@ -30,9 +30,10 @@ var/obj/fire/old_fire = fire var/old_opacity = opacity var/old_dynamic_lighting = dynamic_lighting - var/list/old_affecting_lights = affecting_lights + var/old_affecting_lights = affecting_lights var/old_lighting_overlay = lighting_overlay var/old_weather_overlay = weather_overlay + var/old_corners = corners //world << "Replacing [src.type] with [N]" @@ -94,15 +95,16 @@ W.post_change() . = W - lighting_overlay = old_lighting_overlay - affecting_lights = old_affecting_lights - if((old_opacity != opacity) || (dynamic_lighting != old_dynamic_lighting) || force_lighting_update) - reconsider_lights() - if(dynamic_lighting != old_dynamic_lighting) - if(dynamic_lighting) - lighting_build_overlays() - else - lighting_clear_overlays() - else - if(lighting_overlay) - lighting_overlay.update_overlay() \ No newline at end of file + recalc_atom_opacity() + + if(lighting_overlays_initialised) + lighting_overlay = old_lighting_overlay + affecting_lights = old_affecting_lights + corners = old_corners + if((old_opacity != opacity) || (dynamic_lighting != old_dynamic_lighting)) + reconsider_lights() + if(dynamic_lighting != old_dynamic_lighting) + if(dynamic_lighting) + lighting_build_overlay() + else + lighting_clear_overlay() diff --git a/code/modules/lighting/__lighting_docs.dm b/code/modules/lighting/__lighting_docs.dm index 5ac5897eb5..8ef93935e9 100644 --- a/code/modules/lighting/__lighting_docs.dm +++ b/code/modules/lighting/__lighting_docs.dm @@ -14,9 +14,9 @@ Changes from tg DAL: - light_range; range in tiles of the light, used for calculating falloff, - light_power; multiplier for the brightness of lights, - light_color; hex string representing the RGB colour of the light. - - SetLuminosity() is now set_light() and takes the three variables above. + - setLuminousity() is now set_light() and takes the three variables above. - Variables can be left as null to not update them. - - SetOpacity() is now set_opacity(). + - 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. @@ -51,7 +51,7 @@ turf: (lighting_turf.dm) - 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. Called by ChangeTurf in case the turf is being changed to use dynamic lighting. + - Create lighting overlays for this turf atom/movable/lighting_overlay: (lighting_overlay.dm) @@ -64,4 +64,4 @@ atom/movable/lighting_overlay: (lighting_overlay.dm) - 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 diff --git a/code/modules/lighting/_lighting_defs.dm b/code/modules/lighting/_lighting_defs.dm deleted file mode 100644 index 21fdafdaeb..0000000000 --- a/code/modules/lighting/_lighting_defs.dm +++ /dev/null @@ -1,9 +0,0 @@ -#define LIGHTING_INTERVAL 5 // frequency, in 1/10ths of a second, of the lighting process - -#define LIGHTING_FALLOFF 1 // type of falloff to use for lighting; 1 for circular, 2 for square -#define LIGHTING_LAMBERTIAN 1 // use lambertian shading for light sources -#define LIGHTING_HEIGHT 1 // height off the ground of light sources on the pseudo-z-axis, you should probably leave this alone -#define LIGHTING_ROUND_VALUE (1 / 128) //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_LAYER 10 // drawing layer for lighting overlays -#define LIGHTING_ICON 'icons/effects/lighting_overlay.dmi' // icon used for lighting shading effects diff --git a/code/modules/lighting/light_source.dm b/code/modules/lighting/light_source.dm deleted file mode 100644 index 3675cd945c..0000000000 --- a/code/modules/lighting/light_source.dm +++ /dev/null @@ -1,300 +0,0 @@ -//So much copypasta in this file, supposedly in the name of performance. If you change anything make sure to consider other places where the code may have been copied. - -/datum/light_source - var/atom/top_atom - var/atom/source_atom - - var/turf/source_turf - var/light_power - var/light_range - var/light_color // string, decomposed by parse_light_color() - - var/lum_r - var/lum_g - var/lum_b - - //hold onto the actual applied lum values so we can undo them when the lighting changes - var/tmp/applied_lum_r - var/tmp/applied_lum_g - var/tmp/applied_lum_b - - var/list/effect_str - var/list/effect_turf - - var/applied - - var/vis_update //Whetever we should smartly recalculate visibility. and then only update tiles that became (in) visible to us - var/needs_update - var/destroyed - var/force_update - -/datum/light_source/New(atom/owner, atom/top) - source_atom = owner - if(!source_atom.light_sources) source_atom.light_sources = list() - source_atom.light_sources += src - top_atom = top - if(top_atom != source_atom) - if(!top.light_sources) top.light_sources = list() - top_atom.light_sources += src - - source_turf = top_atom - light_power = source_atom.light_power - light_range = source_atom.light_range - light_color = source_atom.light_color - - parse_light_color() - - effect_str = list() - effect_turf = list() - - update() - - return ..() - -/datum/light_source/proc/destroy() - destroyed = 1 - 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 - -/datum/light_source/proc/update(atom/new_top_atom) - if(new_top_atom && new_top_atom != top_atom) - if(top_atom != source_atom) top_atom.light_sources -= src - top_atom = new_top_atom - if(top_atom != source_atom) - if(!top_atom.light_sources) top_atom.light_sources = list() - top_atom.light_sources += src - - if(!needs_update) //Incase we're already updating either way. - lighting_update_lights += src - needs_update = 1 - -/datum/light_source/proc/force_update() - force_update = 1 - if(!needs_update) //Incase we're already updating either way. - needs_update = 1 - lighting_update_lights += src - -/datum/light_source/proc/vis_update() - if(!needs_update) - needs_update = 1 - lighting_update_lights += src - - vis_update = 1 - -/datum/light_source/proc/check() - if(!source_atom || !light_range || !light_power) - destroy() - return 1 - - if(!top_atom) - top_atom = source_atom - . = 1 - - if(istype(top_atom, /turf)) - if(source_turf != top_atom) - source_turf = top_atom - . = 1 - else if(top_atom.loc != source_turf) - source_turf = top_atom.loc - . = 1 - - if(source_atom.light_power != light_power) - light_power = source_atom.light_power - . = 1 - - if(source_atom.light_range != light_range) - light_range = source_atom.light_range - . = 1 - - if(light_range && light_power && !applied) - . = 1 - - if(source_atom.light_color != light_color) - light_color = source_atom.light_color - parse_light_color() - . = 1 - -/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 - -#if LIGHTING_FALLOFF == 1 //circular - #define LUM_DISTANCE(swapvar, O, T) swapvar = (O.x - T.x)**2 + (O.y - T.y)**2 + LIGHTING_HEIGHT - #if LIGHTING_LAMBERTIAN == 1 - #define LUM_ATTENUATION(swapvar) swapvar = CLAMP01((1 - CLAMP01(sqrt(swapvar) / max(1,light_range))) * (1 / sqrt(swapvar + 1))) - #else - #define LUM_ATTENUATION(swapvar) swapvar = 1 - CLAMP01(sqrt(swapvar) / max(1,light_range)) - #endif -#elif LIGHTING_FALLOFF == 2 //square - #define LUM_DISTANCE(swapvar, O, T) swapvar = abs(O.x - T.x) + abs(O.y - T.y) + LIGHTING_HEIGHT - #if LIGHTING_LAMBERTIAN == 1 - #define LUM_ATTENUATION(swapvar) swapvar = CLAMP01((1 - CLAMP01(swapvar / max(1,light_range))) * (1 / sqrt(swapvar**2 + 1))) - #else - #define LUM_ATTENUATION(swapvar) swapvar = CLAMP01(swapvar / max(1,light_range)) - #endif -#endif - -#define LUM_FALLOFF(swapvar, O, T) \ - LUM_DISTANCE(swapvar, O, T); \ - LUM_ATTENUATION(swapvar); - -/datum/light_source/proc/apply_lum() - 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 - - if(istype(source_turf)) - FOR_DVIEW(var/turf/T, light_range, source_turf, INVISIBILITY_LIGHTING) - if(T.lighting_overlay) - var/strength - LUM_FALLOFF(strength, T, source_turf) - strength *= light_power - - if(!strength) //Don't add turfs that aren't affected to the affected turfs. - continue - - strength = round(strength, LIGHTING_ROUND_VALUE) //Screw sinking points. - - effect_str += strength - - T.lighting_overlay.update_lumcount( - applied_lum_r * strength, - applied_lum_g * strength, - applied_lum_b * strength - ) - - else - effect_str += 0 - - if(!T.affecting_lights) - T.affecting_lights = list() - - T.affecting_lights += src - effect_turf += T - END_FOR_DVIEW - -/datum/light_source/proc/remove_lum() - applied = 0 - var/i = 1 - for(var/turf/T in effect_turf) - if(T.affecting_lights) - T.affecting_lights -= src - - if(T.lighting_overlay) - var/str = effect_str[i] - T.lighting_overlay.update_lumcount( - -str * applied_lum_r, - -str * applied_lum_g, - -str * applied_lum_b - ) - - i++ - - effect_str.Cut() - effect_turf.Cut() - -//Smartly updates the lighting, only removes lum from and adds lum to turfs that actually got changed. -//This is for lights that need to reconsider due to nearby opacity changes. -//Stupid dumb copy pasta because BYOND and speed. -/datum/light_source/proc/smart_vis_update() - var/list/view[0] - FOR_DVIEW(var/turf/T, light_range, source_turf, INVISIBILITY_LIGHTING) - view += T //Filter out turfs. - END_FOR_DVIEW - //This is the part where we calculate new turfs (if any) - var/list/new_turfs = view - effect_turf //This will result with all the tiles that are added. - for(var/turf/T in new_turfs) - if(T.lighting_overlay) - LUM_FALLOFF(., T, source_turf) - . *= light_power - - if(!.) //Don't add turfs that aren't affected to the affected turfs. - continue - - . = round(., LIGHTING_ROUND_VALUE) - - effect_str += . - - T.lighting_overlay.update_lumcount( - applied_lum_r * ., - applied_lum_g * ., - applied_lum_b * . - ) - - else - effect_str += 0 - - if(!T.affecting_lights) - T.affecting_lights = list() - - T.affecting_lights += src - effect_turf += T - - var/list/old_turfs = effect_turf - view - for(var/turf/T in old_turfs) - //Insert not-so-huge copy paste from remove_lum(). - var/idx = effect_turf.Find(T) //Get the index, luckily Find() is cheap in small lists like this. (with small I mean under a couple thousand len) - if(T.affecting_lights) - T.affecting_lights -= src - - if(T.lighting_overlay) - var/str = effect_str[idx] - T.lighting_overlay.update_lumcount(-str * applied_lum_r, -str * applied_lum_g, -str * applied_lum_b) - - effect_turf.Cut(idx, idx + 1) - effect_str.Cut(idx, idx + 1) - -//Whoop yet not another copy pasta because speed ~~~~BYOND. -//Calculates and applies lighting for a single turf. This is intended for when a turf switches to -//using dynamic lighting when it was not doing so previously (when constructing a floor on space, for example). -//Assumes the turf is visible and such. -//For the love of god don't call this proc when it's not needed! Lighting artifacts WILL happen! -/datum/light_source/proc/calc_turf(var/turf/T) - var/idx = effect_turf.Find(T) - if(!idx) - return //WHY. - - if(T.lighting_overlay) - #if LIGHTING_FALLOFF == 1 // circular - . = (T.lighting_overlay.x - source_turf.x)**2 + (T.lighting_overlay.y - source_turf.y)**2 + LIGHTING_HEIGHT - #if LIGHTING_LAMBERTIAN == 1 - . = CLAMP01((1 - CLAMP01(sqrt(.) / light_range)) * (1 / (sqrt(. + 1)))) - #else - . = 1 - CLAMP01(sqrt(.) / light_range) - #endif - - #elif LIGHTING_FALLOFF == 2 // square - . = abs(T.lighting_overlay.x - source_turf.x) + abs(T.lighting_overlay.y - source_turf.y) + LIGHTING_HEIGHT - #if LIGHTING_LAMBERTIAN == 1 - . = CLAMP01((1 - CLAMP01(. / light_range)) * (1 / (sqrt(.)**2 + ))) - #else - . = 1 - CLAMP01(. / light_range) - #endif - #endif - . *= light_power - - . = round(., LIGHTING_ROUND_VALUE) - - effect_str[idx] = . - - //Since the applied_lum values are what are (later) removed by remove_lum. - //Anything we apply to the lighting overlays HAS to match what remove_lum uses. - T.lighting_overlay.update_lumcount( - applied_lum_r * ., - applied_lum_g * ., - applied_lum_b * . - ) - -#undef LUM_FALLOFF -#undef LUM_DISTANCE -#undef LUM_ATTENUATION diff --git a/code/modules/lighting/lighting_area.dm b/code/modules/lighting/lighting_area.dm new file mode 100644 index 0000000000..92923e6b0d --- /dev/null +++ b/code/modules/lighting/lighting_area.dm @@ -0,0 +1,9 @@ +/area + luminosity = TRUE + var/dynamic_lighting = TRUE + +/area/New() + . = ..() + + if(dynamic_lighting) + luminosity = FALSE \ No newline at end of file diff --git a/code/modules/lighting/lighting_atom.dm b/code/modules/lighting/lighting_atom.dm index c900f17bf8..6ec1e6ecd7 100644 --- a/code/modules/lighting/lighting_atom.dm +++ b/code/modules/lighting/lighting_atom.dm @@ -1,12 +1,14 @@ /atom var/light_power = 1 // intensity of the light var/light_range = 0 // range in tiles of the light - var/light_color // RGB string representing the colour of the light + var/light_color // Hexadecimal RGB string representing the colour of the light var/datum/light_source/light var/list/light_sources -/atom/proc/set_light(l_range, l_power, l_color) +// Nonsensical value for l_color default, so we can detect if it gets set to null. +#define NONSENSICAL_VALUE -99999 +/atom/proc/set_light(l_range, l_power, l_color = NONSENSICAL_VALUE) . = 0 //make it less costly if nothing's changed if(l_power != null && l_power != light_power) @@ -15,16 +17,17 @@ if(l_range != null && l_range != light_range) light_range = l_range . = 1 - if(l_color != null && l_color != light_color) + if(l_color != NONSENSICAL_VALUE && l_color != light_color) light_color = l_color . = 1 if(.) update_light() -/atom/proc/copy_light(atom/A) - set_light(A.light_range, A.light_power, A.light_color) +#undef NONSENSICAL_VALUE /atom/proc/update_light() + set waitfor = FALSE + if(!light_power || !light_range) if(light) light.destroy() @@ -42,9 +45,14 @@ /atom/New() . = ..() + if(light_power && light_range) update_light() + if(opacity && isturf(loc)) + var/turf/T = loc + T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guranteed to be on afterwards anyways. + /atom/Destroy() if(light) light.destroy() @@ -57,19 +65,38 @@ T.reconsider_lights() return ..() -/atom/Entered(atom/movable/obj, atom/prev_loc) +/atom/movable/Move() + var/turf/old_loc = loc . = ..() - if(obj && prev_loc != src) - for(var/datum/light_source/L in obj.light_sources) + if(loc != old_loc) + for(var/datum/light_source/L in light_sources) L.source_atom.update_light() + var/turf/new_loc = loc + if(istype(old_loc) && opacity) + old_loc.reconsider_lights() + + if(istype(new_loc) && opacity) + new_loc.reconsider_lights() + /atom/proc/set_opacity(new_opacity) - var/old_opacity = opacity + if(new_opacity == opacity) + return + opacity = new_opacity var/turf/T = loc - if(old_opacity != new_opacity && istype(T)) + if(!isturf(T)) + return + + if(new_opacity == TRUE) + T.has_opaque_atom = TRUE T.reconsider_lights() + 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() /obj/item/equipped() . = ..() diff --git a/code/modules/lighting/lighting_corner.dm b/code/modules/lighting/lighting_corner.dm new file mode 100644 index 0000000000..907e7aab8b --- /dev/null +++ b/code/modules/lighting/lighting_corner.dm @@ -0,0 +1,125 @@ +/var/total_lighting_corners = 0 +/var/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). +// For the record: these should never ever ever be deleted, even if the turf doesn't have dynamic lighting. + +// 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/list/LIGHTING_CORNER_DIAGONAL = list(NORTHEAST, SOUTHEAST, SOUTHWEST, NORTHWEST) + +/datum/lighting_corner + var/list/turf/masters = list() + var/list/datum/light_source/affecting = list() // 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/lum_r = 0 + var/lum_g = 0 + var/lum_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_mx = 0 + + var/update_gen = 0 + +/datum/lighting_corner/New(var/turf/new_turf, var/diagonal) + . = ..() + + total_lighting_corners++ + + masters[new_turf] = turn(diagonal, 180) + z = new_turf.z + + 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. + + x = new_turf.x + (horizontal == EAST ? 0.5 : -0.5) + y = new_turf.y + (vertical == NORTH ? 0.5 : -0.5) + + // My initial plan was to make this loop through a list of all the dirs (horizontal, vertical, diagonal). + // 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) + + masters[T] = diagonal + i = LIGHTING_CORNER_DIAGONAL.Find(turn(diagonal, 180)) + T.corners[i] = src + + // Now the horizontal one. + T = get_step(new_turf, horizontal) + if (T) // Ditto. + if (!T.corners) + T.corners = list(null, null, null, null) + + 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 + + // And finally the vertical one. + T = get_step(new_turf, vertical) + if (T) + if (!T.corners) + T.corners = list(null, null, null, null) + + 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 + + update_active() + +/datum/lighting_corner/proc/update_active() + active = FALSE + for (var/turf/T in masters) + if (T.lighting_overlay) + active = TRUE + +// God that was a mess, now to do the rest of the corner code! Hooray! +/datum/lighting_corner/proc/update_lumcount(var/delta_r, var/delta_g, var/delta_b) + lum_r += delta_r + lum_g += delta_g + lum_b += delta_b + + if (!needs_update) + needs_update = TRUE + lighting_update_corners += 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/mx = max(lum_r, lum_g, lum_b) // Scale it so 1 is the strongest lum, if it is above 1. + . = 1 // factor + if (mx > 1) + . = 1 / mx + + else if (mx < LIGHTING_SOFT_THRESHOLD) + . = 0 // 0 means soft lighting. + + cache_r = lum_r * . || LIGHTING_SOFT_THRESHOLD + cache_g = lum_g * . || LIGHTING_SOFT_THRESHOLD + cache_b = lum_b * . || LIGHTING_SOFT_THRESHOLD + cache_mx = mx + + 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 + lighting_update_overlays += T.lighting_overlay + + +/datum/lighting_corner/dummy/New() + return diff --git a/code/modules/lighting/lighting_overlay.dm b/code/modules/lighting/lighting_overlay.dm index 2dd3889e4e..4679d1fafc 100644 --- a/code/modules/lighting/lighting_overlay.dm +++ b/code/modules/lighting/lighting_overlay.dm @@ -1,102 +1,89 @@ +/var/total_lighting_overlays = 0 /atom/movable/lighting_overlay name = "" mouse_opacity = 0 simulated = 0 anchored = 1 - icon = LIGHTING_ICON - icon_state = "light1" layer = LIGHTING_LAYER invisibility = INVISIBILITY_LIGHTING - color = "#000000" + color = LIGHTING_BASE_MATRIX + icon_state = "light1" + auto_init = 0 // doesn't need special init + blend_mode = BLEND_MULTIPLY - var/lum_r - var/lum_g - var/lum_b + var/lum_r = 0 + var/lum_g = 0 + var/lum_b = 0 - var/needs_update + var/needs_update = FALSE -/atom/movable/lighting_overlay/New() +/atom/movable/lighting_overlay/New(var/atom/loc, var/no_update = FALSE) . = ..() verbs.Cut() + total_lighting_overlays++ - var/turf/T = loc //If this runtimes atleast we'll know what's creating overlays in things that aren't turfs. + var/turf/T = loc //If this runtimes atleast we'll know what's creating overlays outside of turfs. + T.lighting_overlay = src T.luminosity = 0 - -/atom/movable/lighting_overlay/proc/update_lumcount(delta_r, delta_g, delta_b) - if(!delta_r && !delta_g && !delta_b) //Nothing is being changed all together. + if(no_update) return - - var/should_update = 0 - - if(!needs_update) //If this isn't true, we're already updating anyways. - if(max(lum_r, lum_g, lum_b) < 1) //Any change that could happen WILL change appearance. - should_update = 1 - - else if(max(lum_r + delta_r, lum_g + delta_g, lum_b + delta_b) < 1) //The change would bring us under 1 max lum, again, guaranteed to change appearance. - should_update = 1 - - else //We need to make sure that the colour ratios won't change in this code block. - var/mx1 = max(lum_r, lum_g, lum_b) - var/mx2 = max(lum_r + delta_r, lum_g + delta_g, lum_b + delta_b) - - if(lum_r / mx1 != (lum_r + delta_r) / mx2 || lum_g / mx1 != (lum_g + delta_g) / mx2 || lum_b / mx1 != (lum_b + delta_b) / mx2) //Stuff would change. - should_update = 1 - - lum_r += delta_r - lum_g += delta_g - lum_b += delta_b - - if(!needs_update && should_update) - needs_update = 1 - lighting_update_overlays += src + update_overlay() /atom/movable/lighting_overlay/proc/update_overlay() + set waitfor = FALSE var/turf/T = loc - if(istype(T)) //Incase we're not on a turf, pool ourselves, something happened. - if(lum_r == lum_g && lum_r == lum_b) //greyscale - blend_mode = BLEND_OVERLAY - if(lum_r <= 0) - T.luminosity = 0 - color = "#000000" - alpha = 255 - else - T.luminosity = 1 - color = "#000000" - alpha = (1 - min(lum_r, 1)) * 255 + 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 - alpha = 255 - var/mx = max(lum_r, lum_g, lum_b) - . = 1 // factor - if(mx > 1) - . = 1/mx - blend_mode = BLEND_MULTIPLY - color = rgb(lum_r * 255 * ., lum_g * 255 * ., lum_b * 255 * .) - if(color != "#000000") - T.luminosity = 1 - else //No light, set the turf's luminosity to 0 to remove it from view() - T.luminosity = 0 - else - warning("A lighting overlay realised its loc was NOT a turf (actual loc: [loc][loc ? ", " + loc.type : ""]) in update_overlay() and got pooled!") + log_debug("A lighting overlay realised it was in nullspace in update_overlay() and got pooled!") qdel(src) + return -/atom/movable/lighting_overlay/ResetVars() - loc = null + // 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. - lum_r = 0 - lum_g = 0 - lum_b = 0 + // 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 - color = "#000000" + var/max = max(cr.cache_mx, cg.cache_mx, cb.cache_mx, ca.cache_mx) - needs_update = 0 + color = list( + cr.cache_r, cr.cache_g, cr.cache_b, 0, + cg.cache_r, cg.cache_g, cg.cache_b, 0, + cb.cache_r, cb.cache_g, cb.cache_b, 0, + ca.cache_r, ca.cache_g, ca.cache_b, 0, + 0, 0, 0, 1 + ) + luminosity = max > LIGHTING_SOFT_THRESHOLD + + + +/atom/movable/lighting_overlay/singularity_act() + return + +/atom/movable/lighting_overlay/singularity_pull() + return /atom/movable/lighting_overlay/Destroy() - lighting_update_overlays -= src + total_lighting_overlays-- + global.lighting_update_overlays -= src + global.lighting_update_overlays_old -= src var/turf/T = loc if(istype(T)) T.lighting_overlay = null - - ..() + T.luminosity = 1 + + return ..() diff --git a/code/modules/lighting/lighting_process.dm b/code/modules/lighting/lighting_process.dm deleted file mode 100644 index 055e366337..0000000000 --- a/code/modules/lighting/lighting_process.dm +++ /dev/null @@ -1,35 +0,0 @@ -/datum/controller/process/lighting/setup() - name = "lighting" - schedule_interval = LIGHTING_INTERVAL - - create_lighting_overlays() - -/datum/controller/process/lighting/doWork() - var/list/lighting_update_lights_old = lighting_update_lights //We use a different list so any additions to the update lists during a delay from scheck() don't cause things to be cut from the list without being updated. - lighting_update_lights = null //Nulling it first because of http://www.byond.com/forum/?post=1854520 - lighting_update_lights = list() - - for(var/datum/light_source/L in lighting_update_lights_old) - if(L.destroyed || L.check() || L.force_update) - L.remove_lum() - if(!L.destroyed) - L.apply_lum() - - else if(L.vis_update) //We smartly update only tiles that became (in) visible to use. - L.smart_vis_update() - - L.vis_update = 0 - L.force_update = 0 - L.needs_update = 0 - - SCHECK - - var/list/lighting_update_overlays_old = lighting_update_overlays //Same as above. - lighting_update_overlays = null //Same as above - lighting_update_overlays = list() - - for(var/atom/movable/lighting_overlay/O in lighting_update_overlays_old) - O.update_overlay() - O.needs_update = 0 - - SCHECK diff --git a/code/modules/lighting/lighting_setup.dm b/code/modules/lighting/lighting_setup.dm new file mode 100644 index 0000000000..981de65864 --- /dev/null +++ b/code/modules/lighting/lighting_setup.dm @@ -0,0 +1,16 @@ +/proc/create_all_lighting_overlays() + for(var/zlevel = 1 to world.maxz) + create_lighting_overlays_zlevel(zlevel) + +/proc/create_lighting_overlays_zlevel(var/zlevel) + ASSERT(zlevel) + + for(var/turf/T in block(locate(1, 1, zlevel), locate(world.maxx, world.maxy, zlevel))) + if(!T.dynamic_lighting) + continue + + var/area/A = T.loc + if(!A.dynamic_lighting) + continue + + new /atom/movable/lighting_overlay(T, TRUE) diff --git a/code/modules/lighting/lighting_source.dm b/code/modules/lighting/lighting_source.dm new file mode 100644 index 0000000000..d878d8c4b6 --- /dev/null +++ b/code/modules/lighting/lighting_source.dm @@ -0,0 +1,290 @@ +/var/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/source_atom // The atom that we belong to. + + var/turf/source_turf // The turf under the above. + 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() + + // Variables for keeping track of the colour. + var/lum_r + var/lum_g + var/lum_b + + // The lumcount values used to apply the light. + var/tmp/applied_lum_r + var/tmp/applied_lum_g + var/tmp/applied_lum_b + + 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 + +/datum/light_source/New(var/atom/owner, var/atom/top) + total_lighting_sources++ + 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() + + top_atom.light_sources += src + + source_turf = top_atom + light_power = source_atom.light_power + light_range = source_atom.light_range + light_color = source_atom.light_color + + parse_light_color() + + effect_str = list() + affecting_turfs = list() + + update() + + + return ..() + +// Kill ourselves. +/datum/light_source/proc/destroy() + total_lighting_sources-- + destroyed = TRUE + force_update() + if(source_atom) + if(!source_atom.light_sources) + log_runtime(EXCEPTION("Atom [source_atom] was a light source, but lacked a light source list!\n"), source_atom) + else + source_atom.light_sources -= src + + if(top_atom) + 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) \ + { \ + lighting_update_lights += src; \ + needs_update = TRUE; \ + } + +// This proc will cause the light source to update the top atom, and add itself to the update queue. +/datum/light_source/proc/update(var/atom/new_top_atom) + // This top atom is different. + 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 + + top_atom = new_top_atom + + if(top_atom != source_atom) + if(!top_atom.light_sources) + top_atom.light_sources = list() + + top_atom.light_sources += src // Add ourselves to the light sources of our new top atom. + + effect_update(null) + +// Will force an update without checking if it's actually needed. +/datum/light_source/proc/force_update() + force_update = 1 + + effect_update(null) + +// 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 + + effect_update(null) + +// 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_range || !light_power) + destroy() + return 1 + + if(!top_atom) + top_atom = source_atom + . = 1 + + 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 + + if(source_atom.light_power != light_power) + light_power = source_atom.light_power + . = 1 + + if(source_atom.light_range != light_range) + light_range = source_atom.light_range + . = 1 + + if(light_range && light_power && !applied) + . = 1 + + if(source_atom.light_color != light_color) + light_color = source_atom.light_color + parse_light_color() + . = 1 + +// 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 + +// 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_power; \ + \ + 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. +#define LUM_FALLOFF(C, T)(1 - CLAMP01(sqrt((C.x - T.x) ** 2 +(C.y - T.y) ** 2 + LIGHTING_HEIGHT) / max(1, light_range))) + +/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 + + FOR_DVIEW(var/turf/T, light_range, source_turf, INVISIBILITY_LIGHTING) + if(!T.lighting_corners_initialised) + T.generate_missing_corners() + + for(var/datum/lighting_corner/C in T.get_corners()) + if(C.update_gen == update_gen) + continue + + C.update_gen = update_gen + C.affecting += src + + if(!C.active) + effect_str[C] = 0 + continue + + APPLY_CORNER(C) + + if(!T.affecting_lights) + T.affecting_lights = list() + + T.affecting_lights += src + affecting_turfs += T + + update_gen++ + +/datum/light_source/proc/remove_lum() + applied = FALSE + + for(var/turf/T in affecting_turfs) + if(!T.affecting_lights) + T.affecting_lights = list() + else + T.affecting_lights -= src + + affecting_turfs.Cut() + + for(var/datum/lighting_corner/C in effect_str) + REMOVE_CORNER(C) + + C.affecting -= src + + effect_str.Cut() + +/datum/light_source/proc/recalc_corner(var/datum/lighting_corner/C) + if(effect_str.Find(C)) // Already have one. + REMOVE_CORNER(C) + + APPLY_CORNER(C) + +/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_range, source_turf, 0) + if(!T.lighting_corners_initialised) + T.generate_missing_corners() + corners |= T.get_corners() + turfs += T + + 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) + if(!T.affecting_lights) + T.affecting_lights = list(src) + else + 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) + 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 + + APPLY_CORNER(C) + + for(var/datum/lighting_corner/C in effect_str - corners) // Old, now gone, corners. + REMOVE_CORNER(C) + C.affecting -= src + effect_str -= C + +#undef effect_update +#undef LUM_FALLOFF +#undef REMOVE_CORNER +#undef APPLY_CORNER diff --git a/code/modules/lighting/lighting_system.dm b/code/modules/lighting/lighting_system.dm deleted file mode 100644 index 0c84294f35..0000000000 --- a/code/modules/lighting/lighting_system.dm +++ /dev/null @@ -1,25 +0,0 @@ -/var/list/lighting_update_lights = list() -/var/list/lighting_update_overlays = list() - -/area/var/lighting_use_dynamic = 1 - -// duplicates lots of code, but this proc needs to be as fast as possible. -/proc/create_lighting_overlays(zlevel = 0) - var/area/A - if(zlevel == 0) // populate all zlevels - for(var/turf/T in world) - if(T.dynamic_lighting) - A = T.loc - if(A.lighting_use_dynamic) - var/atom/movable/lighting_overlay/O = PoolOrNew(/atom/movable/lighting_overlay, T) - T.lighting_overlay = O - - else - for(var/x = 1; x <= world.maxx; x++) - for(var/y = 1; y <= world.maxy; y++) - var/turf/T = locate(x, y, zlevel) - if(T.dynamic_lighting) - A = T.loc - if(A.lighting_use_dynamic) - var/atom/movable/lighting_overlay/O = PoolOrNew(/atom/movable/lighting_overlay, T) - T.lighting_overlay = O diff --git a/code/modules/lighting/lighting_turf.dm b/code/modules/lighting/lighting_turf.dm index f8f0a5ab10..cc9c678b97 100644 --- a/code/modules/lighting/lighting_turf.dm +++ b/code/modules/lighting/lighting_turf.dm @@ -1,32 +1,102 @@ /turf - var/list/affecting_lights - var/atom/movable/lighting_overlay/lighting_overlay + var/dynamic_lighting = TRUE // Does the turf use dynamic lighting? + luminosity = 1 + var/tmp/lighting_corners_initialised = FALSE + + var/tmp/list/datum/light_source/affecting_lights // List of light sources affecting this turf. + var/tmp/atom/movable/lighting_overlay/lighting_overlay // Our lighting overlay. + var/tmp/list/datum/lighting_corner/corners + var/tmp/has_opaque_atom = FALSE // Not to be confused with opacity, this will be TRUE if there's any opaque atom on the tile. + +/turf/New() + . = ..() + + if(opacity) + has_opaque_atom = TRUE + +// 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) L.vis_update() -/turf/proc/lighting_clear_overlays() +/turf/proc/lighting_clear_overlay() if(lighting_overlay) qdel(lighting_overlay) -/turf/proc/lighting_build_overlays() + 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 + + var/area/A = loc + if(A.dynamic_lighting) + if(!lighting_corners_initialised) + generate_missing_corners() + + new /atom/movable/lighting_overlay(src) + + 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) + + C.active = TRUE + +// Used to get a scaled lumcount. +/turf/proc/get_lumcount(var/minlum = 0, var/maxlum = 1) if(!lighting_overlay) - var/area/A = loc - if(A.lighting_use_dynamic) - var/atom/movable/lighting_overlay/O = PoolOrNew(/atom/movable/lighting_overlay, src) - lighting_overlay = O + return 1 - //Make the light sources recalculate us so the lighting overlay updates immediately - for(var/datum/light_source/L in affecting_lights) - L.calc_turf(src) + var/totallums = 0 + for(var/datum/lighting_corner/L in corners) + totallums += max(L.lum_r, L.lum_g, L.lum_b) -/turf/Entered(atom/movable/obj) + totallums /= 4 // 4 corners, max channel selected, return the average + + totallums =(totallums - minlum) /(maxlum - minlum) + + return CLAMP01(totallums) + +// Can't think of a good name, this proc will recalculate the has_opaque_atom variable. +/turf/proc/recalc_atom_opacity() + has_opaque_atom = FALSE + for(var/atom/A in src.contents + src) // Loop through every movable atom on our tile PLUS ourselves (we matter too...) + if(A.opacity) + has_opaque_atom = TRUE + +// If an opaque movable atom moves around we need to potentially update visibility. +/turf/Entered(var/atom/movable/Obj, var/atom/OldLoc) . = ..() - if(obj && obj.opacity) + + if(Obj && Obj.opacity) + has_opaque_atom = TRUE // Make sure to do this before reconsider_lights(), incase we're on instant updates. Guaranteed to be on in this case. reconsider_lights() -/turf/Exited(atom/movable/obj) +/turf/Exited(var/atom/movable/Obj, var/atom/newloc) . = ..() - if(obj && obj.opacity) + + if(Obj && Obj.opacity) + recalc_atom_opacity() // Make sure to do this before reconsider_lights(), incase we're on instant updates. reconsider_lights() + +/turf/proc/get_corners() + if(has_opaque_atom) + return null // Since this proc gets used in a for loop, null won't be looped though. + + return corners + +/turf/proc/generate_missing_corners() + lighting_corners_initialised = TRUE + if(!corners) + corners = list(null, null, null, null) + + 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]) diff --git a/code/modules/lighting/~lighting_undefs.dm b/code/modules/lighting/~lighting_undefs.dm index 772f70557a..2f863b73d5 100644 --- a/code/modules/lighting/~lighting_undefs.dm +++ b/code/modules/lighting/~lighting_undefs.dm @@ -1,9 +1,7 @@ -#undef LIGHTING_INTERVAL - #undef LIGHTING_FALLOFF #undef LIGHTING_LAMBERTIAN #undef LIGHTING_HEIGHT -#undef LIGHTING_RESOLUTION -#undef LIGHTING_LAYER #undef LIGHTING_ICON + +#undef LIGHTING_BASE_MATRIX diff --git a/code/modules/mining/mine_turfs.dm b/code/modules/mining/mine_turfs.dm index f46d384f86..cc7c8c40bb 100644 --- a/code/modules/mining/mine_turfs.dm +++ b/code/modules/mining/mine_turfs.dm @@ -82,6 +82,7 @@ var/list/mining_overlay_cache = list() /turf/simulated/mineral/proc/update_general() update_icon(1) if(ticker && ticker.current_state == GAME_STATE_PLAYING) + // TODO - Look into this reconsider_lights() if(air_master) air_master.mark_for_update(src) diff --git a/code/modules/reagents/reagent_containers/food/drinks/bottle.dm b/code/modules/reagents/reagent_containers/food/drinks/bottle.dm index 0045769d74..3300eb91d6 100644 --- a/code/modules/reagents/reagent_containers/food/drinks/bottle.dm +++ b/code/modules/reagents/reagent_containers/food/drinks/bottle.dm @@ -134,7 +134,7 @@ if(rag) var/underlay_image = image(icon='icons/obj/drinks.dmi', icon_state=rag.on_fire? "[rag_underlay]_lit" : rag_underlay) underlays += underlay_image - copy_light(rag) + set_light(rag.light_range, rag.light_power, rag.light_color) else set_light(0) diff --git a/icons/effects/lighting_overlay.dmi b/icons/effects/lighting_overlay.dmi deleted file mode 100644 index 1ff16d2b78b6a89fd525856cfee87830095ec9c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^2|(1VBG)! diff --git a/icons/effects/lighting_overlay.png b/icons/effects/lighting_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..b317268b702d50c33c0099a63d0a0ea94a607744 GIT binary patch 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