From e4a3abdd70da0697a06b59725f2d2f1b2aac0eba Mon Sep 17 00:00:00 2001 From: MrPerson Date: Thu, 26 Mar 2015 12:32:11 -0700 Subject: [PATCH 1/4] Object based lighting system Uses actual objects on each non-space, dynamically lit turf. Light levels are switched back and forth via animate() and the object's alpha. Supporting colors shouldn't be too hard. Some hacky efficiency improvements means it isn't that much more expensive than current (I think, needs testing). Most of the lighting ss's cost is in checking all the lights and doing big loops, not anything actually in the loops themselves. Start PDA flashlights on. This was to speed up testing but frankly I think it's a good change in general. Added a Moved() proc. Called after a successful move. In the future I hope to move off the luminosity var entirely but that was too slow in testing for me. That's what all that "for(area in sortedAreas) area.luminosity = 1" stuff in the lighting ss is, tests on removing luminosity outright. --- code/__HELPERS/unsorted.dm | 2 - code/controllers/_DynamicAreaLighting_TG.dm | 199 +++++++++++--------- code/controllers/subsystem/lighting.dm | 55 +++--- code/game/area/Space Station 13 areas.dm | 3 +- code/game/area/areas.dm | 10 +- code/game/atoms_movable.dm | 12 +- code/game/gamemodes/blob/blob_report.dm | 25 +-- code/game/machinery/doors/brigdoors.dm | 6 +- code/game/machinery/requests_console.dm | 6 +- code/game/objects/items/blueprints.dm | 1 - code/game/objects/items/devices/PDA/PDA.dm | 9 +- code/modules/events/spacevine.dm | 2 +- code/modules/power/lighting.dm | 6 +- code/modules/shuttle/shuttle.dm | 6 +- icons/effects/alphacolors.dmi | Bin 395 -> 650 bytes 15 files changed, 189 insertions(+), 153 deletions(-) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 0b5b128540a..c9d2237a3bf 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -820,8 +820,6 @@ Turf and target are seperate in case you want to teleport some distance from a t //else populates the list first before returning it /proc/SortAreas() for(var/area/A in world) - if(A.lighting_subarea) - continue sortedAreas.Add(A) sortTim(sortedAreas, /proc/cmp_name_asc) diff --git a/code/controllers/_DynamicAreaLighting_TG.dm b/code/controllers/_DynamicAreaLighting_TG.dm index ea1b76b1ce2..b72494d2fd5 100644 --- a/code/controllers/_DynamicAreaLighting_TG.dm +++ b/code/controllers/_DynamicAreaLighting_TG.dm @@ -30,21 +30,29 @@ No directional lighting support. (prototype looked ugly) */ -#define USE_CIRCULAR_LIGHTING //comment this out to use old square lighting effects. +#define LIGHTING_CIRCULAR 1 //comment this out to use old square lighting effects. +#define LIGHTING_LAYER 15 //Drawing layer for lighting +#define LIGHTING_CAP 10 //The lumcount level at which alpha is 0 and we're fully lit. +#define LIGHTING_CAP_FRAC (255/LIGHTING_CAP) //A precal'd variable we'll use in turf/redraw_lighting() +#define LIGHTING_ICON 'icons/effects/alphacolors.dmi' +#define LIGHTING_ICON_STATE "white" +#define LIGHTING_ALPHA_CHANGE_PER_SECOND 75 //Alpha change that will occur each second +#define LIGHTING_DARKEST_VISIBLE_ALPHA 230 //Anything darker than this is so dark, we'll just consider the whole tile unlit /datum/light_source var/atom/owner + var/strength = 0 var/changed = 1 var/list/effect = list() var/__x = 0 //x coordinate at last update var/__y = 0 //y coordinate at last update - /datum/light_source/New(atom/A) if(!istype(A)) CRASH("The first argument to the light object's constructor must be the atom that is the light source. Expected atom, received '[A]' instead.") ..() owner = A + strength = A.luminosity __x = owner.x __y = owner.y // the lighting object maintains a list of all light sources @@ -57,12 +65,6 @@ remove_effect() return 1 //causes it to be removed from our list of lights. The garbage collector will then destroy it. - // check to see if we've moved since last update - if(owner.x != __x || owner.y != __y) - __x = owner.x - __y = owner.y - changed = 1 - if(changed) changed = 0 remove_effect() @@ -84,10 +86,10 @@ /datum/light_source/proc/add_effect() // only do this if the light is turned on and is on the map - if(owner.loc && owner.luminosity > 0) + if(owner.loc && strength > 0) effect = list() var/turf/To = get_turf(owner) - var/range = owner.get_light_range() + var/range = owner.get_light_range(strength) for(var/turf/T in view(range, To)) var/delta_lumcount = T.lumen(src) @@ -106,7 +108,7 @@ //longer referenced by the queue /turf/proc/lumen(datum/light_source/L) - . = L.owner.luminosity + . = L.strength #ifdef USE_CIRCULAR_LIGHTING . -= cheap_hypotenuse(x, y, L.__x, L.__y) #else @@ -129,6 +131,7 @@ if(luminosity) if(light) WARNING("[type] - Don't set lights up manually during New(), We do it automatically.") light = new(src) +// luminosity = 0 //Movable atoms with opacity when they are constructed will trigger nearby lights to update //Movable atoms with luminosity when they are constructed will create a light_source automatically @@ -139,6 +142,7 @@ if(luminosity) if(light) WARNING("[type] - Don't set lights up manually during New(), We do it automatically.") light = new(src) +// luminosity = 0 //Objects with opacity will trigger nearby lights to update at next lighting process. /atom/movable/Destroy() @@ -154,16 +158,24 @@ /atom/proc/SetLuminosity(new_luminosity) if(new_luminosity < 0) new_luminosity = 0 - if(light) - if(luminosity != new_luminosity) //non-luminous lights are removed from the lights list in add_effect() - light.changed = 1 + + if(!light) + if(!new_luminosity) + return + light = new(src) else - if(new_luminosity) - light = new(src) + if(light.strength == new_luminosity) + return + light.remove_effect() // we need to remove the effect before changing strength + light.strength = new_luminosity luminosity = new_luminosity + light.changed = 1 /atom/proc/AddLuminosity(delta_luminosity) - SetLuminosity(luminosity + delta_luminosity) + if(light) + SetLuminosity(light.strength + delta_luminosity) + else + SetLuminosity(delta_luminosity) /area/SetLuminosity(new_luminosity) //we don't want dynamic lighting for areas luminosity = !!new_luminosity @@ -179,13 +191,42 @@ UpdateAffectingLights() +/atom/movable/SetOpacity(new_opacity) + if(..()==1) //only bother if opacity changed + if(isturf(loc)) //only bother with an update if we're on a turf + var/turf/T = loc + if(T.lighting_lumcount) //only bother with an update if our turf is currently affected by a light + UpdateAffectingLights() + +/atom/movable/light + icon = LIGHTING_ICON + icon_state = LIGHTING_ICON_STATE + layer = LIGHTING_LAYER + mouse_opacity = 0 + blend_mode = BLEND_MULTIPLY + invisibility = INVISIBILITY_LIGHTING + color = "#000" + luminosity = 0 + infra_luminosity = 1 + anchored = 1 + +/atom/movable/light/Destroy() + return 1 + +/atom/movable/light/Move() + return 0 + /turf var/lighting_lumcount = 0 var/lighting_changed = 0 + var/atom/movable/light/lighting_object //Will be null for space turfs and anything in a static lighting area var/list/affecting_lights //not initialised until used (even empty lists reserve a fair bit of memory) -/turf/space - lighting_lumcount = 4 //starlight +/turf/New() + lighting_object = locate() in src + if(!lighting_object && SSlighting) // Don't init_lighting() for map objects, basically + init_lighting() + return ..() /turf/proc/update_lumcount(amount) lighting_lumcount += amount @@ -193,78 +234,62 @@ SSlighting.changed_turfs += src lighting_changed = 1 -/area/proc/lighting_tag(level) - return tagbase + "sd_L[level]" +/turf/space/update_lumcount(amount) //Keep track in case the turf becomes a floor at some point, but don't process. + lighting_lumcount += amount -/area/proc/build_lighting_area(tag, level) - var/area/A = locate(tag) // find an appropriate area - if(A) - return A - A = new type() // create area if it wasn't found - // replicate vars - for(var/V in vars) - switch(V) - if("contents","last_light","overlays") continue - else - if(issaved(vars[V])) A.vars[V] = vars[V] +/turf/proc/init_lighting() + var/area/A = loc + if(!A.lighting_use_dynamic) + lighting_changed = 0 + else + if(!lighting_object) + lighting_object = new (src) + redraw_lighting() - A.tag = tag - A.lighting_subarea = 1 - A.lighting_space = 0 // in case it was copied from a space subarea - A.SetLightLevel(level) +/turf/space/init_lighting() + if(config.starlight) + update_starlight() - related += A - return A +/turf/proc/redraw_lighting() + if(lighting_object) + var/newalpha + if(lighting_lumcount <= 0) + newalpha = 255 + else + lighting_object.luminosity = 1 + if(lighting_lumcount < LIGHTING_CAP) + var/num = round(Clamp(lighting_lumcount * LIGHTING_CAP_FRAC, 0, 255), 1) + newalpha = 255-num + else //if(lighting_lumcount >= LIGHTING_CAP) + newalpha = 0 + + if(lighting_object.alpha != newalpha) + var/change_time = (abs(newalpha - lighting_object.alpha)) / LIGHTING_ALPHA_CHANGE_PER_SECOND + animate(lighting_object, alpha = newalpha, time = change_time) + if(newalpha >= LIGHTING_DARKEST_VISIBLE_ALPHA) //Doesn't actually make it darker or anything, just tells byond you can't see the tile + animate(luminosity = 0, time = 0) -/turf/proc/shift_to_subarea() lighting_changed = 0 - var/area/Area = loc - - if(!istype(Area) || !Area.lighting_use_dynamic) return - - var/level = min(max(round(lighting_lumcount,1),0),SSlighting.lighting_images.len) - var/new_tag = Area.lighting_tag(level) - if(Area.tag!=new_tag) //skip if already in this area - var/area/A = Area.build_lighting_area(new_tag,level) - A.contents += src // move the turf into the area /area var/lighting_use_dynamic = 1 //Turn this flag off to prevent sd_DynamicAreaLighting from affecting this area - var/last_light //tracks the last light level set for this area (used for removing previously applied lighting overlays) - var/lighting_subarea = 0 //tracks whether we're a lighting sub-area - var/lighting_space = 0 // true for space-only lighting subareas - var/tagbase - -/area/proc/SetLightLevel(light) - if(!src) return - if(light <= 1) - light = 1 - luminosity = 0 - else - if(light > SSlighting.lighting_images.len) - light = SSlighting.lighting_images.len - luminosity = 1 - - if(last_light != light) - if(last_light) - overlays -= SSlighting.lighting_images[last_light] - overlays += SSlighting.lighting_images[light] - last_light = light /area/proc/SetDynamicLighting() lighting_use_dynamic = 1 - for(var/turf/T in contents) + luminosity = 0 + for(var/turf/T in src.contents) + T.init_lighting() T.update_lumcount(0) -/area/proc/InitializeLighting() //TODO: could probably improve this bit ~Carn - tagbase = "[type]" - if(!tag) tag = tagbase - if(!lighting_use_dynamic) - if(!lighting_subarea) // see if this is a lighting subarea already - //show the dark overlay so areas, not yet in a lighting subarea, won't be bright as day and look silly. - SetLightLevel(4) +#undef LIGHTING_LAYER +#undef LIGHTING_CIRCULAR +#undef LIGHTING_ICON +#undef LIGHTING_ICON_STATE +#undef LIGHTING_TIME +#undef LIGHTING_CAP +#undef LIGHTING_CAP_FRAC +#undef LIGHTING_DARKEST_VISIBLE_ALPHA -#undef USE_CIRCULAR_LIGHTING //set the changed status of all lights which could have possibly lit this atom. //We don't need to worry about lights which lit us but moved away, since they will have change status set already @@ -287,20 +312,20 @@ #define LIGHTING_MAX_LUMINOSITY_TURF 1 //turfs have a severely shortened range to protect from inevitable floor-lighttile spam. //caps luminosity effects max-range based on what type the light's owner is. -/atom/proc/get_light_range() - return min(luminosity, LIGHTING_MAX_LUMINOSITY_STATIC) +/atom/proc/get_light_range(strength) + return min(strength, LIGHTING_MAX_LUMINOSITY_STATIC) -/atom/movable/get_light_range() - return min(luminosity, LIGHTING_MAX_LUMINOSITY_MOBILE) +/atom/movable/get_light_range(strength) + return min(strength, LIGHTING_MAX_LUMINOSITY_MOBILE) -/mob/get_light_range() - return min(luminosity, LIGHTING_MAX_LUMINOSITY_MOB) +/mob/get_light_range(strength) + return min(strength, LIGHTING_MAX_LUMINOSITY_MOB) -/obj/machinery/light/get_light_range() - return min(luminosity, LIGHTING_MAX_LUMINOSITY_STATIC) +/obj/machinery/light/get_light_range(strength) + return min(strength, LIGHTING_MAX_LUMINOSITY_STATIC) -/turf/get_light_range() - return min(luminosity, LIGHTING_MAX_LUMINOSITY_TURF) +/turf/get_light_range(strength) + return min(strength, LIGHTING_MAX_LUMINOSITY_TURF) #undef LIGHTING_MAX_LUMINOSITY_STATIC #undef LIGHTING_MAX_LUMINOSITY_MOBILE diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm index 0754b284c67..0fc70de34b4 100644 --- a/code/controllers/subsystem/lighting.dm +++ b/code/controllers/subsystem/lighting.dm @@ -1,15 +1,12 @@ var/datum/subsystem/lighting/SSlighting #define MC_AVERAGE(average, current) (0.8*(average) + 0.2*(current)) -#define LIGHTING_ICON 'icons/effects/ss13_dark_alpha6.dmi' -#define LIGHTING_LAYER 10 //Drawing layer for lighting overlays /datum/subsystem/lighting name = "Lighting" wait = 5 priority = 1 - var/list/lighting_images = list() //replaces lighting_states (use lighting_images.len) ~carn var/list/lights = list() //list of all datum/light_source var/lights_workload = 0 //stats on the largest number of lights (max lights.len) var/list/changed_turfs = list() //list of all turfs which need moving to a new lighting subarea @@ -19,11 +16,6 @@ var/datum/subsystem/lighting/SSlighting /datum/subsystem/lighting/New() NEW_SS_GLOBAL(SSlighting) - //cache lighting images - if(!lighting_images.len) - for(var/icon_state in icon_states(LIGHTING_ICON)) - lighting_images += image(LIGHTING_ICON, null, icon_state, LIGHTING_LAYER) - return ..() @@ -38,19 +30,26 @@ var/datum/subsystem/lighting/SSlighting //By using queues we are ensuring we don't perform more updates than are necessary /datum/subsystem/lighting/fire() lights_workload = MC_AVERAGE(lights_workload, lights.len) + +// for(var/area/A in sortedAreas) +// A.luminosity = 1 + var/i=1 for(var/thing in lights) if(thing && !thing:check()) //yes, cry that I'm using the : operator, it's much faster looping like this. And this gets called a lot. Dealwithit. ++i continue + qdel(lights[i]) lights.Cut(i, i+1) changed_turfs_workload = MC_AVERAGE(changed_turfs_workload, changed_turfs.len) for(var/thing in changed_turfs) if(thing && thing:lighting_changed) - thing:shift_to_subarea() + thing:redraw_lighting() changed_turfs.Cut() +// for(var/area/A in sortedAreas) +// A.luminosity = !A.lighting_use_dynamic //same as above except it attempts to shift ALL turfs in the world regardless of lighting_changed status //Does not loop. Should be run prior to process() being called for the first time. @@ -58,11 +57,15 @@ var/datum/subsystem/lighting/SSlighting //z-levels with the z_level argument /datum/subsystem/lighting/Initialize(timeofday, z_level) +// for(var/area/A in sortedAreas) +// A.luminosity = 1 + var/i=1 for(var/thing in lights) if(thing && !thing:check()) ++i continue + qdel(lights[i]) lights.Cut(i, i+1) var/z_start = 1 @@ -72,12 +75,10 @@ var/datum/subsystem/lighting/SSlighting z_start = z_level z_finish = z_level - for(var/z=z_start, z<=z_finish, ++z) - for(var/x=1, x<=world.maxx, ++x) - for(var/y=1, y<=world.maxy, ++y) - var/turf/T = locate(x,y,z) - if(T) - T.shift_to_subarea() + var/list/turfs_to_init = block(locate(1, 1, z_start), locate(world.maxx, world.maxy, z_finish)) + + for(var/T in turfs_to_init) + T:init_lighting() if(z_level) //we need to loop through to clear only shifted turfs from the list. or we will cause errors @@ -90,10 +91,8 @@ var/datum/subsystem/lighting/SSlighting else changed_turfs.Cut() - if(config.starlight) - set background = 1 - for(var/turf/space/S in world) - S.update_starlight() +// for(var/area/A in sortedAreas) +// A.luminosity = !A.lighting_use_dynamic ..() @@ -106,18 +105,18 @@ var/datum/subsystem/lighting/SSlighting if(!istype(SSlighting.lights)) SSlighting.lights = list() - if(istype(SSlighting.lighting_images)) - lighting_images = SSlighting.lighting_images +// for(var/area/A in sortedAreas) +// A.luminosity = 1 - for(var/datum/light_source/L in SSlighting.lights) + for(var/L in SSlighting.lights) spawn(-1) //so we don't crash the loop (inefficient) - L.check() + L:check() lights += L //If we didn't runtime then this will get transferred over - for(var/turf/T in changed_turfs) - if(T.lighting_changed) + for(var/T in changed_turfs) + if(T:lighting_changed) spawn(-1) - T.shift_to_subarea() + T:redraw_lighting() var/msg = "## DEBUG: [time2text(world.timeofday)] [name] subsystem restarted. Reports:\n" for(var/varname in SSlighting.vars) @@ -132,5 +131,5 @@ var/datum/subsystem/lighting/SSlighting msg += "\t [varname] = [varval1] -> [varval2]\n" world.log << msg -#undef LIGHTING_ICON -#undef LIGHTING_LAYER \ No newline at end of file +// for(var/area/A in sortedAreas) +// A.luminosity = !A.lighting_use_dynamic \ No newline at end of file diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm index fb9b8f856d3..d9b12400f19 100644 --- a/code/game/area/Space Station 13 areas.dm +++ b/code/game/area/Space Station 13 areas.dm @@ -194,8 +194,7 @@ proc/process_ghost_teleport_locs() /area/asteroid/artifactroom/New() ..() - lighting_use_dynamic = 1 - InitializeLighting() + SetDynamicLighting() /area/planet/clown name = "\improper Clown Planet" diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index 86520169a27..f33d876c73b 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -43,7 +43,6 @@ ..() power_change() // all machines set to current power level, also updates lighting icon - InitializeLighting() blend_mode = BLEND_MULTIPLY // Putting this in the constructure so that it stops the icons being screwed up in the map editor. @@ -223,7 +222,7 @@ return /area/proc/updateicon() - if ((fire || eject || party) && (!requires_power||power_environ) && !lighting_space)//If it doesn't require power, can still activate this proc. + if ((fire || eject || party) && (!requires_power||power_environ))//If it doesn't require power, can still activate this proc. if(fire && !eject && !party) icon_state = "blue" /*else if(atmosalm && !fire && !eject && !party) @@ -238,6 +237,8 @@ // new lighting behaviour with obj lights icon_state = null +/area/space/updateicon() + icon_state = null /* #define EQUIP 1 @@ -251,8 +252,6 @@ return 1 if(master.always_unpowered) return 0 - if(src.lighting_space) - return 0 // Nope sorry switch(chan) if(EQUIP) return master.power_equip @@ -263,6 +262,9 @@ return 0 +/area/space/powered(chan) //Nope.avi + return 0 + // called when power status changes /area/proc/power_change() diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index a0eaf523e16..e5a1bffce8d 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -46,11 +46,13 @@ else if (step(src, WEST)) . = step(src, SOUTH) - if(!loc || (loc == oldloc && oldloc != newloc)) last_move = 0 return + if(.) + Moved(oldloc, direct) + last_move = direct spawn(5) // Causes space drifting. /tg/station has no concept of speed, we just use 5 @@ -58,6 +60,14 @@ if(loc == newloc) //Remove this check and people can accelerate. Not opening that can of worms just yet. newtonian_move(last_move) +//Called after a successful Move(). By this point, we've already moved +/atom/movable/proc/Moved(atom/OldLoc, Dir) + if(light) + light.changed = 1 + light.__x = x + light.__y = y + return 1 + /atom/movable/Del() if(isnull(gc_destroyed) && loc) testing("GC: -- [type] was deleted via del() rather than qdel() --") diff --git a/code/game/gamemodes/blob/blob_report.dm b/code/game/gamemodes/blob/blob_report.dm index 736fa2e7df9..9d6de20b142 100644 --- a/code/game/gamemodes/blob/blob_report.dm +++ b/code/game/gamemodes/blob/blob_report.dm @@ -57,9 +57,7 @@ /datum/station_state/proc/count() - for(var/turf/T in world) - if(T.z != ZLEVEL_STATION) - continue + for(var/turf/T in block(locate(1,1,1), locate(world.maxx,world.maxy,1))) if(istype(T,/turf/simulated/floor)) if(!(T:burnt)) @@ -79,19 +77,16 @@ else src.r_wall += 1 - for(var/obj/O in world) - if(O.z != ZLEVEL_STATION) - continue - if(istype(O, /obj/structure/window)) - src.window += 1 - else if(istype(O, /obj/structure/grille) && (!O:destroyed)) - src.grille += 1 - else if(istype(O, /obj/machinery/door)) - src.door += 1 - else if(istype(O, /obj/machinery)) - src.mach += 1 - return + for(var/obj/O in T.contents) + if(istype(O, /obj/structure/window)) + src.window += 1 + else if(istype(O, /obj/structure/grille) && (!O:destroyed)) + src.grille += 1 + else if(istype(O, /obj/machinery/door)) + src.door += 1 + else if(istype(O, /obj/machinery)) + src.mach += 1 /datum/station_state/proc/score(var/datum/station_state/result) diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm index f91fb5a6ddf..378e7e67dea 100644 --- a/code/game/machinery/doors/brigdoors.dm +++ b/code/game/machinery/doors/brigdoors.dm @@ -42,15 +42,15 @@ pixel_y = ((src.dir & 3)? (src.dir ==1 ? 24 : -32) : (0)) spawn(20) - for(var/obj/machinery/door/window/brigdoor/M in world) + for(var/obj/machinery/door/window/brigdoor/M in range(20, src)) if (M.id == src.id) targets += M - for(var/obj/machinery/flasher/F in world) + for(var/obj/machinery/flasher/F in range(20, src)) if(F.id == src.id) targets += F - for(var/obj/structure/closet/secure_closet/brig/C in world) + for(var/obj/structure/closet/secure_closet/brig/C in range(20, src)) if(C.id == src.id) targets += C diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm index 00d896cc6c0..1ba38ab9140 100644 --- a/code/game/machinery/requests_console.dm +++ b/code/game/machinery/requests_console.dm @@ -181,7 +181,7 @@ var/list/obj/machinery/requests_console/allConsoles = list() if (Console.department == department) Console.newmessagepriority = 0 Console.update_icon() - Console.luminosity = 1 + Console.SetLuminosity(1) newmessagepriority = 0 update_icon() var/messageComposite = "" @@ -317,7 +317,7 @@ var/list/obj/machinery/requests_console/allConsoles = list() else // Normal priority Console.createmessage(src, "Message from [department]", sending, 1, 1) screen = 6 - Console.luminosity = 2 + Console.SetLuminosity(2) switch(priority) if(2) @@ -431,7 +431,7 @@ var/list/obj/machinery/requests_console/allConsoles = list() var/obj/item/weapon/paper/slip = new /obj/item/weapon/paper(src.loc) slip.info = "From: [unlinkedsender]
[message]" slip.name = "Message - [unlinkedsender]" - src.luminosity = 2 + SetLuminosity(2) /obj/machinery/requests_console/attackby(var/obj/item/weapon/O as obj, var/mob/user as mob, params) if (istype(O, /obj/item/weapon/crowbar)) diff --git a/code/game/objects/items/blueprints.dm b/code/game/objects/items/blueprints.dm index 0c5002a60f5..d8389f36327 100644 --- a/code/game/objects/items/blueprints.dm +++ b/code/game/objects/items/blueprints.dm @@ -148,7 +148,6 @@ return var/area/A = new A.name = str - A.tagbase="[A.type]_[md5(str)]" // without this dynamic light system ruin everithing //var/ma //ma = A.master ? "[A.master]" : "(null)" //world << "DEBUG: create_area:
A.name=[A.name]
A.tag=[A.tag]
A.master=[ma]" diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 5bd70bf4391..c31e1ae61f6 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -21,7 +21,7 @@ var/global/list/obj/item/device/pda/PDAs = list() //Secondary variables var/scanmode = 0 //1 is medical scanner, 2 is forensics, 3 is reagent scanner. - var/fon = 0 //Is the flashlight function on? + var/fon = 1 //Is the flashlight function on? var/f_lum = 3 //Luminosity for the flashlight function var/silent = 0 //To beep or not to beep, that is the question var/toff = 0 //If 1, messenger disabled @@ -195,6 +195,7 @@ var/global/list/obj/item/device/pda/PDAs = list() /obj/item/device/pda/ai icon_state = "NONE" ttone = "data" + fon = 0 mode = 5 noreturn = 1 detonate = 0 @@ -223,6 +224,12 @@ var/global/list/obj/item/device/pda/PDAs = list() /obj/item/device/pda/New() ..() + if(fon) + if(!isturf(loc)) + loc.AddLuminosity(f_lum) + SetLuminosity(0) + else + SetLuminosity(f_lum) PDAs += src if(default_cartridge) cartridge = new default_cartridge(src) diff --git a/code/modules/events/spacevine.dm b/code/modules/events/spacevine.dm index 525d66debca..9583533d98e 100644 --- a/code/modules/events/spacevine.dm +++ b/code/modules/events/spacevine.dm @@ -137,7 +137,7 @@ /datum/spacevine_mutation/light/on_grow(obj/effect/spacevine/holder) if(prob(10*severity)) - holder.luminosity = 4 + holder.SetLuminosity(4) /datum/spacevine_mutation/toxicity name = "toxic" diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index 31e2944aa96..d6b26c14127 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -277,7 +277,7 @@ update_icon() if(on) - if(luminosity != brightness) + if(!light || light.strength != brightness) switchcount++ if(rigged) if(status == LIGHT_OK && trigger) @@ -295,11 +295,11 @@ use_power = 1 SetLuminosity(0) - active_power_usage = (luminosity * 10) + active_power_usage = (brightness * 10) if(on != on_gs) on_gs = on if(on) - static_power_used = luminosity * 20 //20W per unit luminosity + static_power_used = brightness * 20 //20W per unit luminosity addStaticPower(static_power_used, STATIC_LIGHT) else removeStaticPower(static_power_used, STATIC_LIGHT) diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index b41d28366d8..bf1a6d022aa 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -321,6 +321,8 @@ for(var/obj/O in T0) if(O.invisibility >= 101) continue + if(O == T0.lighting_object) + continue O.loc = T1 //close open doors @@ -350,13 +352,13 @@ //air system updates for(var/turf/T1 in L1) - T1.shift_to_subarea() + T1.redraw_lighting() SSair.remove_from_active(T1) T1.CalculateAdjacentTurfs() SSair.add_to_active(T1,1) for(var/turf/T0 in L0) - T0.shift_to_subarea() + T0.redraw_lighting() SSair.remove_from_active(T0) T0.CalculateAdjacentTurfs() SSair.add_to_active(T0,1) diff --git a/icons/effects/alphacolors.dmi b/icons/effects/alphacolors.dmi index 02316fbf64de161a7da31202f1c857995c09211a..74452b58dcf073900a7fd20b935e0806811b574e 100644 GIT binary patch literal 650 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSYpX&cN?cNllZ!G7N;32F7#J$% zOzl6&)#M=Jdfhm_@Z#E*S-R=MUOO(-&zLdYI6y2vuCxCBB3IVVEqe;yJ-jFK``LwW zD|Uz3cO>uVGgx7^Gc35^QQ`*Ot)E2L^w$d<kUBDL|^HDkP%BCAB!YD6^m>Ge3`k zp<>R|K1Z%Y1|qE2e=e`sa$CW-^K_1i>!ytN0w=?nx3nKGX}bRhYfu` zBrjn1KKYu7efR4XS0W_&&Svf_cGgPOEHGGdPAEU-L4v31yvJ}P@Ru0Ln~5zWLSAehk5!NBaoBB7vkfI*Ovt%JkB!KHywiF#6h Y7y?dm%)Gkt9mp39p00i_>zopr049f!RsaA1 From d6be5a2e1b390ac6ff27c50ff9f145d0215f3eee Mon Sep 17 00:00:00 2001 From: MrPerson Date: Mon, 30 Mar 2015 01:58:01 -0700 Subject: [PATCH 2/4] Move time for lighting changes from a scale to a fixed length PDA's start off again, but the code is still there. forceMove() now calls Moved() and has some other minor tidying up. This means teleporting will update the lights around you right away. --- code/controllers/_DynamicAreaLighting_TG.dm | 5 ++--- code/game/atoms_movable.dm | 11 +++++++---- code/game/objects/items/devices/PDA/PDA.dm | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/code/controllers/_DynamicAreaLighting_TG.dm b/code/controllers/_DynamicAreaLighting_TG.dm index b72494d2fd5..a1353e0df44 100644 --- a/code/controllers/_DynamicAreaLighting_TG.dm +++ b/code/controllers/_DynamicAreaLighting_TG.dm @@ -36,7 +36,7 @@ #define LIGHTING_CAP_FRAC (255/LIGHTING_CAP) //A precal'd variable we'll use in turf/redraw_lighting() #define LIGHTING_ICON 'icons/effects/alphacolors.dmi' #define LIGHTING_ICON_STATE "white" -#define LIGHTING_ALPHA_CHANGE_PER_SECOND 75 //Alpha change that will occur each second +#define LIGHTING_TIME 1.2 //Time to do any lighting change. Actual number pulled out of my ass #define LIGHTING_DARKEST_VISIBLE_ALPHA 230 //Anything darker than this is so dark, we'll just consider the whole tile unlit /datum/light_source @@ -166,7 +166,6 @@ else if(light.strength == new_luminosity) return - light.remove_effect() // we need to remove the effect before changing strength light.strength = new_luminosity luminosity = new_luminosity light.changed = 1 @@ -264,7 +263,7 @@ newalpha = 0 if(lighting_object.alpha != newalpha) - var/change_time = (abs(newalpha - lighting_object.alpha)) / LIGHTING_ALPHA_CHANGE_PER_SECOND + var/change_time = LIGHTING_TIME animate(lighting_object, alpha = newalpha, time = change_time) if(newalpha >= LIGHTING_DARKEST_VISIBLE_ALPHA) //Doesn't actually make it darker or anything, just tells byond you can't see the tile animate(luminosity = 0, time = 0) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index e5a1bffce8d..47b73bc05ad 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -106,12 +106,15 @@ /atom/movable/proc/forceMove(atom/destination) if(destination) - if(loc) - loc.Exited(src) + var/atom/oldloc = loc + if(oldloc) + oldloc.Exited(src, destination) loc = destination - loc.Entered(src) - for(var/atom/movable/AM in loc) + destination.Entered(src, oldloc) + for(var/atom/movable/AM in destination) + if(AM == src) continue AM.Crossed(src) + Moved(oldloc, 0) return 1 return 0 diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index c31e1ae61f6..4776dc54969 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -21,7 +21,7 @@ var/global/list/obj/item/device/pda/PDAs = list() //Secondary variables var/scanmode = 0 //1 is medical scanner, 2 is forensics, 3 is reagent scanner. - var/fon = 1 //Is the flashlight function on? + var/fon = 0 //Is the flashlight function on? var/f_lum = 3 //Luminosity for the flashlight function var/silent = 0 //To beep or not to beep, that is the question var/toff = 0 //If 1, messenger disabled From 907e20c94d48f3ae4788533aa778767ed74528aa Mon Sep 17 00:00:00 2001 From: MrPerson Date: Tue, 31 Mar 2015 04:33:32 -0700 Subject: [PATCH 3/4] Lights are now told when to update instead of constantly asking if they need to. The lighting SS is a lot faster as a result and no longer has :'s everywhere atom/movable/Destroy() in atoms_movable.dm now calls ..() to fix #8063 Light strength and light radius are no longer a single concept, although right now all lights are max strength for their radius Updated the comment intro for _DynamicAreaLighting_TG.dm to account for modern fact and not talk so much about the old system(s) And a changelog for all this lighting shit, not that anybody could possibly miss it --- code/controllers/_DynamicAreaLighting_TG.dm | 109 +++++++++++--------- code/controllers/subsystem/garbage.dm | 3 +- code/controllers/subsystem/lighting.dm | 68 ++++++------ code/game/atoms_movable.dm | 7 +- code/modules/power/lighting.dm | 2 +- html/changelogs/MrPerson.yml | 3 +- 6 files changed, 98 insertions(+), 94 deletions(-) diff --git a/code/controllers/_DynamicAreaLighting_TG.dm b/code/controllers/_DynamicAreaLighting_TG.dm index a1353e0df44..d039e51b25b 100644 --- a/code/controllers/_DynamicAreaLighting_TG.dm +++ b/code/controllers/_DynamicAreaLighting_TG.dm @@ -1,33 +1,29 @@ /* - Modified DynamicAreaLighting for TGstation - Coded by Carnwennan + This is /tg/'s 'newer' lighting system. It's basically a combination of Forum_Account's and ShadowDarke's + respective lighting libraries heavily modified by Carnwennan for /tg/station with further edits by + MrPerson. Credits, where due, to them. - This is TG's 'new' lighting system. It's basically a heavily modified combination of Forum_Account's and - ShadowDarke's respective lighting libraries. Credits, where due, to them. + Originally, like all other lighting libraries on BYOND, we used areas to render different hard-coded light levels. + The idea was that this was cheaper than using objects. Well as it turns out, the cost of the system is primarily from the + massive loops the system has to run, not from the areas or objects actually doing any work. Thus the newer system uses objects + so we can have more lighting states and smooth transitions between them. - Like sd_DAL (what we used to use), it changes the shading overlays of areas by splitting each type of area into sub-areas - by using the var/tag variable and moving turfs into the contents list of the correct sub-area. This method is - much less costly than using overlays or objects. + This is a queueing system. Everytime we call a change to opacity or luminosity throwgh SetOpacity() or SetLuminosity(), + we are simply updating variables and scheduling certain lights/turfs for an update. Actual updates are handled + periodically by the SSlighting subsystem. Specifically, it runs check() on every changed light datum and deletes any that + return 1. Then it runs redraw_lighting() on every turf with lighting_changed = 1. - Unlike sd_DAL however it uses a queueing system. Everytime we call a change to opacity or luminosity - (through SetOpacity() or SetLuminosity()) we are simply updating variables and scheduling certain lights/turfs for an - update. Actual updates are handled periodically by the SSlighting subsystem. This carries additional overheads, however it - means that each thing is changed only once per SSlighting.wait deciseconds. Allowing for greater control - over how much priority we'd like lighting updates to have. - - UPDATE: we no longer postpone lighting updates by editting variables, there is now a subsystem/proc/postpone() procedure. - So you would do SSlighting.postpone() - - Unlike our old system there are hardcoded maximum luminositys (different for certain atoms). + Unlike our older system, there are hardcoded maximum luminosities (different for certain atoms). This is to cap the cost of creating lighting effects. (without this, an atom with luminosity of 20 would have to update 41^2 turfs!) :s - Also, in order for the queueing system to work, each light remembers the effect it casts on each turf. This is going to - have larger memory requirements than our previous system but it's easily worth the hassle for the greater control we - gain. It also reduces cost of removing lighting effects by a lot! + Each light remembers the effect it casts on each turf. It reduces cost of removing lighting effects by a lot! Known Issues/TODO: - Shuttles still do not have support for dynamic lighting (I hope to fix this at some point) + Shuttles still do not have support for dynamic lighting (I hope to fix this at some point) -probably trivial now No directional lighting support. (prototype looked ugly) + Allow lights to be weaker than 'cap' radius + Colored lights */ #define LIGHTING_CIRCULAR 1 //comment this out to use old square lighting effects. @@ -41,7 +37,7 @@ /datum/light_source var/atom/owner - var/strength = 0 + var/radius = 0 var/changed = 1 var/list/effect = list() var/__x = 0 //x coordinate at last update @@ -52,18 +48,21 @@ CRASH("The first argument to the light object's constructor must be the atom that is the light source. Expected atom, received '[A]' instead.") ..() owner = A - strength = A.luminosity + radius = A.luminosity __x = owner.x __y = owner.y - // the lighting object maintains a list of all light sources - SSlighting.lights += src + SSlighting.changed_lights += src +/datum/light_source/Destroy() + if(owner && owner.light == src) + owner.light = null + return ..() //Check a light to see if its effect needs reprocessing. If it does, remove any old effect and create a new one /datum/light_source/proc/check() if(!owner) remove_effect() - return 1 //causes it to be removed from our list of lights. The garbage collector will then destroy it. + return 1 //causes it to be deleted if(changed) changed = 0 @@ -72,6 +71,14 @@ return 0 +/datum/light_source/proc/changed() + changed = 1 + if(owner) + __x = owner.x + __y = owner.y + if(SSlighting) + SSlighting.changed_lights |= src + /datum/light_source/proc/remove_effect() // before we apply the effect we remove the light's current effect. for(var/turf/T in effect) // negate the effect of this light source @@ -86,10 +93,10 @@ /datum/light_source/proc/add_effect() // only do this if the light is turned on and is on the map - if(owner.loc && strength > 0) + if(owner.loc && radius > 0) effect = list() var/turf/To = get_turf(owner) - var/range = owner.get_light_range(strength) + var/range = owner.get_light_range(radius) for(var/turf/T in view(range, To)) var/delta_lumcount = T.lumen(src) @@ -103,18 +110,18 @@ return 0 else - owner.light = null return 1 //cause the light to be removed from the lights list and garbage collected once it's no //longer referenced by the queue /turf/proc/lumen(datum/light_source/L) - . = L.strength -#ifdef USE_CIRCULAR_LIGHTING - . -= cheap_hypotenuse(x, y, L.__x, L.__y) + var/distance = 0 +#ifdef LIGHTING_CIRCULAR + distance = cheap_hypotenuse(x, y, L.__x, L.__y) #else - . -= max(abs(x - L.__x), abs(y - L.__y)) + distance = max(abs(x - L.__x), abs(y - L.__y)) #endif - return . + return LIGHTING_CAP * (L.radius - distance) / L.radius + /turf/space/lumen() return 0 @@ -146,6 +153,10 @@ //Objects with opacity will trigger nearby lights to update at next lighting process. /atom/movable/Destroy() + if(light) + light.changed() + light.owner = null + light = null if(opacity) UpdateAffectingLights() return ..() @@ -164,15 +175,15 @@ return light = new(src) else - if(light.strength == new_luminosity) + if(light.radius == new_luminosity) return - light.strength = new_luminosity + light.radius = new_luminosity luminosity = new_luminosity - light.changed = 1 + light.changed() /atom/proc/AddLuminosity(delta_luminosity) if(light) - SetLuminosity(light.strength + delta_luminosity) + SetLuminosity(light.radius + delta_luminosity) else SetLuminosity(delta_luminosity) @@ -301,8 +312,8 @@ /turf/UpdateAffectingLights() if(affecting_lights) - for(var/thing in affecting_lights) - thing:changed = 1 //force it to update at next process() + for(var/datum/light_source/thing in affecting_lights) + thing.changed() //force it to update at next process() #define LIGHTING_MAX_LUMINOSITY_STATIC 8 //Maximum luminosity to reduce lag. @@ -311,20 +322,20 @@ #define LIGHTING_MAX_LUMINOSITY_TURF 1 //turfs have a severely shortened range to protect from inevitable floor-lighttile spam. //caps luminosity effects max-range based on what type the light's owner is. -/atom/proc/get_light_range(strength) - return min(strength, LIGHTING_MAX_LUMINOSITY_STATIC) +/atom/proc/get_light_range(radius) + return min(radius, LIGHTING_MAX_LUMINOSITY_STATIC) -/atom/movable/get_light_range(strength) - return min(strength, LIGHTING_MAX_LUMINOSITY_MOBILE) +/atom/movable/get_light_range(radius) + return min(radius, LIGHTING_MAX_LUMINOSITY_MOBILE) -/mob/get_light_range(strength) - return min(strength, LIGHTING_MAX_LUMINOSITY_MOB) +/mob/get_light_range(radius) + return min(radius, LIGHTING_MAX_LUMINOSITY_MOB) -/obj/machinery/light/get_light_range(strength) - return min(strength, LIGHTING_MAX_LUMINOSITY_STATIC) +/obj/machinery/light/get_light_range(radius) + return min(radius, LIGHTING_MAX_LUMINOSITY_STATIC) -/turf/get_light_range(strength) - return min(strength, LIGHTING_MAX_LUMINOSITY_TURF) +/turf/get_light_range(radius) + return min(radius, LIGHTING_MAX_LUMINOSITY_TURF) #undef LIGHTING_MAX_LUMINOSITY_STATIC #undef LIGHTING_MAX_LUMINOSITY_MOBILE diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index 3a329379f97..6ebee701170 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -82,7 +82,8 @@ var/datum/subsystem/garbage_collector/SSgarbage // Return true if the the GC controller should allow the object to continue existing. (Useful if pooling objects.) /datum/proc/Destroy() //del(src) - return + tag = null + return 0 /datum/var/gc_destroyed //Time when this object was destroyed. diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm index 0fc70de34b4..adffdea4d12 100644 --- a/code/controllers/subsystem/lighting.dm +++ b/code/controllers/subsystem/lighting.dm @@ -7,9 +7,9 @@ var/datum/subsystem/lighting/SSlighting wait = 5 priority = 1 - var/list/lights = list() //list of all datum/light_source - var/lights_workload = 0 //stats on the largest number of lights (max lights.len) - var/list/changed_turfs = list() //list of all turfs which need moving to a new lighting subarea + var/list/changed_lights = list() //list of all datum/light_source that need updating + var/changed_lights_workload = 0 //stats on the largest number of lights (max changed_lights.len) + var/list/changed_turfs = list() //list of all turfs which may have a different light level var/changed_turfs_workload = 0 //stats on the largest number of turfs changed (max changed_turfs.len) @@ -20,32 +20,29 @@ var/datum/subsystem/lighting/SSlighting /datum/subsystem/lighting/stat_entry() - stat(name, "[round(cost,0.001)]ds (CPU:[round(cpu,1)]%) L:[round(lights_workload,1)]/T:[round(changed_turfs_workload,1)]") + stat(name, "[round(cost,0.001)]ds (CPU:[round(cpu,1)]%) L:[round(changed_lights_workload,1)]/T:[round(changed_turfs_workload,1)]") -//Workhorse of lighting. It cycles through each light to see which ones need their effects updating. It updates their -//effects and then processes every turf in the queue, moving the turfs to the corresponing lighting sub-area. -//All queue lists prune themselves, which will cause lights with no luminosity to be garbage collected (cheaper and safer -//than deleting them). +//Workhorse of lighting. It cycles through each light that needs updating. It updates their +//effects and then processes every turf in the queue, updating their lighting object's appearance +//Any light that returns 1 in check() deletes itself //By using queues we are ensuring we don't perform more updates than are necessary /datum/subsystem/lighting/fire() - lights_workload = MC_AVERAGE(lights_workload, lights.len) + changed_lights_workload = MC_AVERAGE(changed_lights_workload, changed_lights.len) // for(var/area/A in sortedAreas) // A.luminosity = 1 - var/i=1 - for(var/thing in lights) - if(thing && !thing:check()) //yes, cry that I'm using the : operator, it's much faster looping like this. And this gets called a lot. Dealwithit. - ++i + for(var/datum/light_source/thing in changed_lights) + if(!thing || !thing.check()) continue - qdel(lights[i]) - lights.Cut(i, i+1) + qdel(thing) + changed_lights.Cut() changed_turfs_workload = MC_AVERAGE(changed_turfs_workload, changed_turfs.len) - for(var/thing in changed_turfs) - if(thing && thing:lighting_changed) - thing:redraw_lighting() + for(var/turf/thing in changed_turfs) + if(thing && thing.lighting_changed) + thing.redraw_lighting() changed_turfs.Cut() // for(var/area/A in sortedAreas) @@ -60,13 +57,11 @@ var/datum/subsystem/lighting/SSlighting // for(var/area/A in sortedAreas) // A.luminosity = 1 - var/i=1 - for(var/thing in lights) - if(thing && !thing:check()) - ++i + for(var/datum/light_source/thing in changed_lights) + if(!thing || !thing.check()) continue - qdel(lights[i]) - lights.Cut(i, i+1) + qdel(thing) + changed_lights.Cut() var/z_start = 1 var/z_finish = world.maxz @@ -77,14 +72,14 @@ var/datum/subsystem/lighting/SSlighting var/list/turfs_to_init = block(locate(1, 1, z_start), locate(world.maxx, world.maxy, z_finish)) - for(var/T in turfs_to_init) - T:init_lighting() + for(var/turf/T in turfs_to_init) + T.init_lighting() if(z_level) //we need to loop through to clear only shifted turfs from the list. or we will cause errors - i=1 - for(var/thing in changed_turfs) - if(thing && thing:z < z_start && z_finish < thing:z) + var/i=1 + for(var/turf/thing in changed_turfs) + if(thing && thing.z < z_start && z_finish < thing.z) ++i continue changed_turfs.Cut(i, i+1) @@ -102,21 +97,20 @@ var/datum/subsystem/lighting/SSlighting /datum/subsystem/lighting/Recover() if(!istype(SSlighting.changed_turfs)) SSlighting.changed_turfs = list() - if(!istype(SSlighting.lights)) - SSlighting.lights = list() + if(!istype(SSlighting.changed_lights)) + SSlighting.changed_lights = list() // for(var/area/A in sortedAreas) // A.luminosity = 1 - for(var/L in SSlighting.lights) + for(var/datum/light_source/L in SSlighting.changed_lights) spawn(-1) //so we don't crash the loop (inefficient) - L:check() - lights += L //If we didn't runtime then this will get transferred over + L.check() - for(var/T in changed_turfs) - if(T:lighting_changed) + for(var/turf/T in changed_turfs) + if(T.lighting_changed) spawn(-1) - T:redraw_lighting() + T.redraw_lighting() var/msg = "## DEBUG: [time2text(world.timeofday)] [name] subsystem restarted. Reports:\n" for(var/varname in SSlighting.vars) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 47b73bc05ad..50a27b7ebdc 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -63,9 +63,7 @@ //Called after a successful Move(). By this point, we've already moved /atom/movable/proc/Moved(atom/OldLoc, Dir) if(light) - light.changed = 1 - light.__x = x - light.__y = y + light.changed() return 1 /atom/movable/Del() @@ -78,14 +76,13 @@ ..() /atom/movable/Destroy() + . = ..() if(reagents) qdel(reagents) for(var/atom/movable/AM in contents) qdel(AM) - tag = null loc = null invisibility = 101 - // Do not call ..() // Previously known as HasEntered() // This is automatically called when something enters your square diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index d6b26c14127..89642e29838 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -277,7 +277,7 @@ update_icon() if(on) - if(!light || light.strength != brightness) + if(!light || light.radius != brightness) switchcount++ if(rigged) if(status == LIGHT_OK && trigger) diff --git a/html/changelogs/MrPerson.yml b/html/changelogs/MrPerson.yml index 6378584f30e..642ce9a49f5 100644 --- a/html/changelogs/MrPerson.yml +++ b/html/changelogs/MrPerson.yml @@ -1,2 +1,3 @@ author: MrPerson -changes: [] +changes: + - experiment: "New lighting system! Lighting now smoothly transfers from one lighting level to the next." From 01a8aa662a9f00f5795247087ce4b7e53a0e4035 Mon Sep 17 00:00:00 2001 From: MrPerson Date: Sun, 12 Apr 2015 02:25:28 -0700 Subject: [PATCH 4/4] Moved lighting stuff from controllers folder to its own module Lighting SS no longer cares about the return of check(); the light datums are responsible for deleting themselves. Cap on lighting effects from turfs is now 8 because they're static and shouldn't be flashing lights too often. This means starlight actually works now instead of being capped at 1 measly turf. Lighting related ChangeTurf() code is in the lighting module. Changed it up to be faster on lighting controller init and not leave dangling lights when a turf becomes or stops being opaque or when it turns into space. This diff log is gonna be useless sadly but take my word for it, it all works. Lighting related Moved() code is also in the lighting module. Opaque objects will now update nearby lights when they move (mechs). Opaque objects other than the light datum's owner on the same tile as its owner will block the effect of the light. In other words a mech standing on the same square as a light bulb will block all the light of that bulb. Changed "cheap_hypoteneuse()" with an even cheaper version; actually calculating the hypoteneuse! I can prove it's cheaper if needed. Removed move_contents_to() because it's unused and trying to use it would cause major bugs with lights and other shit and I have no interest in supporting that, so let's not even tempt people. --- code/__HELPERS/game.dm | 16 +- code/__HELPERS/unsorted.dm | 146 ---- code/controllers/subsystem/lighting.dm | 9 +- code/game/atoms_movable.dm | 2 - code/game/turfs/turf.dm | 17 +- .../lighting/lighting_system.dm} | 722 +++++++++--------- tgstation.dme | 2 +- 7 files changed, 389 insertions(+), 525 deletions(-) rename code/{controllers/_DynamicAreaLighting_TG.dm => modules/lighting/lighting_system.dm} (81%) diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 8d30dd0343a..aa2578347df 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -53,18 +53,10 @@ return 0 return 1 - -//Magic constants obtained by using linear regression on right-angled triangles of sides 0=dy) return (k1*dx) + (k2*dy) //No sqrt or powers :) - else return (k2*dx) + (k1*dy) -#undef k1 -#undef k2 +//We used to use linear regression to approximate the answer, but Mloc realized this was actually faster. +//And lo and behold, it is, and it's more accurate to boot. +/proc/cheap_hypotenuse(Ax,Ay,Bx,By) + return sqrt(abs(Ax - Bx)**2 + abs(Ay - By)**2) //A squared + B squared = C squared /proc/circlerange(center=usr,radius=3) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index c9d2237a3bf..8b4ad9098a7 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -878,152 +878,6 @@ Turf and target are seperate in case you want to teleport some distance from a t var/y_pos = null var/z_pos = null -/area/proc/move_contents_to(var/area/A, var/turftoleave=null, var/direction = null) - //Takes: Area. Optional: turf type to leave behind. - //Returns: Nothing. - //Notes: Attempts to move the contents of one area to another area. - // Movement based on lower left corner. Tiles that do not fit - // into the new area will not be moved. - - if(!A || !src) return 0 - - var/list/turfs_src = get_area_turfs(src.type) - var/list/turfs_trg = get_area_turfs(A.type) - - var/src_min_x = 0 - var/src_min_y = 0 - for (var/turf/T in turfs_src) - if(T.x < src_min_x || !src_min_x) src_min_x = T.x - if(T.y < src_min_y || !src_min_y) src_min_y = T.y - - var/trg_min_x = 0 - var/trg_min_y = 0 - for (var/turf/T in turfs_trg) - if(T.x < trg_min_x || !trg_min_x) trg_min_x = T.x - if(T.y < trg_min_y || !trg_min_y) trg_min_y = T.y - - var/list/refined_src = new/list() - for(var/turf/T in turfs_src) - refined_src += T - refined_src[T] = new/datum/coords - var/datum/coords/C = refined_src[T] - C.x_pos = (T.x - src_min_x) - C.y_pos = (T.y - src_min_y) - - var/list/refined_trg = new/list() - for(var/turf/T in turfs_trg) - refined_trg += T - refined_trg[T] = new/datum/coords - var/datum/coords/C = refined_trg[T] - C.x_pos = (T.x - trg_min_x) - C.y_pos = (T.y - trg_min_y) - - var/list/fromupdate = new/list() - var/list/toupdate = new/list() - - moving: - for (var/turf/T in refined_src) - var/datum/coords/C_src = refined_src[T] - for (var/turf/B in refined_trg) - var/datum/coords/C_trg = refined_trg[B] - if(C_src.x_pos == C_trg.x_pos && C_src.y_pos == C_trg.y_pos) - - var/old_dir1 = T.dir - var/old_icon_state1 = T.icon_state - var/old_icon1 = T.icon - - var/turf/X = new T.type(B) - X.dir = old_dir1 - X.icon_state = old_icon_state1 - X.icon = old_icon1 //Shuttle floors are in shuttle.dmi while the defaults are floors.dmi - - - // Give the new turf our air, if simulated - if(istype(X, /turf/simulated) && istype(T, /turf/simulated)) - var/turf/simulated/sim = X - sim.copy_air_with_tile(T) - - /* Quick visual fix for some weird shuttle corner artefacts when on transit space tiles */ - if(direction && findtext(X.icon_state, "swall_s")) - - // Spawn a new shuttle corner object - var/obj/corner = new() - corner.loc = X - corner.density = 1 - corner.anchored = 1 - corner.icon = X.icon - corner.icon_state = replacetext(X.icon_state, "_s", "_f") - corner.tag = "delete me" - corner.name = "wall" - - // Find a new turf to take on the property of - var/turf/nextturf = get_step(corner, direction) - if(!nextturf || !istype(nextturf, /turf/space)) - nextturf = get_step(corner, turn(direction, 180)) - - - // Take on the icon of a neighboring scrolling space icon - X.icon = nextturf.icon - X.icon_state = nextturf.icon_state - - - for(var/obj/O in T) - - // Reset the shuttle corners - if(O.tag == "delete me") - X.icon = 'icons/turf/shuttle.dmi' - X.icon_state = replacetext(O.icon_state, "_f", "_s") // revert the turf to the old icon_state - X.name = "wall" - qdel(O) // prevents multiple shuttle corners from stacking - continue - if(!istype(O,/obj)) continue - O.loc = X - for(var/mob/M in T) - if(!M.move_on_shuttle) - continue - M.loc = X - -// var/area/AR = X.loc - -// if(AR.lighting_use_dynamic) //TODO: rewrite this code so it's not messed by lighting ~Carn -// X.opacity = !X.opacity -// X.SetOpacity(!X.opacity) - - toupdate += X - - if(turftoleave) - var/turf/ttl = new turftoleave(T) - -// var/area/AR2 = ttl.loc - -// if(AR2.lighting_use_dynamic) //TODO: rewrite this code so it's not messed by lighting ~Carn -// ttl.opacity = !ttl.opacity -// ttl.sd_SetOpacity(!ttl.opacity) - - fromupdate += ttl - - else - T.ChangeTurf(/turf/space) - - refined_src -= T - refined_trg -= B - continue moving - - - if(toupdate.len) - for(var/turf/simulated/T1 in toupdate) - SSair.remove_from_active(T1) - T1.CalculateAdjacentTurfs() - SSair.add_to_active(T1,1) - - if(fromupdate.len) - for(var/turf/simulated/T2 in fromupdate) - SSair.remove_from_active(T2) - T2.CalculateAdjacentTurfs() - SSair.add_to_active(T2,1) - - - /proc/DuplicateObject(obj/original, var/perfectcopy = 0 , var/sameloc = 0) if(!original) return null diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm index adffdea4d12..0aacf50f5b6 100644 --- a/code/controllers/subsystem/lighting.dm +++ b/code/controllers/subsystem/lighting.dm @@ -34,9 +34,7 @@ var/datum/subsystem/lighting/SSlighting // A.luminosity = 1 for(var/datum/light_source/thing in changed_lights) - if(!thing || !thing.check()) - continue - qdel(thing) + thing.check() changed_lights.Cut() changed_turfs_workload = MC_AVERAGE(changed_turfs_workload, changed_turfs.len) @@ -58,9 +56,8 @@ var/datum/subsystem/lighting/SSlighting // A.luminosity = 1 for(var/datum/light_source/thing in changed_lights) - if(!thing || !thing.check()) - continue - qdel(thing) + thing.add_effect() + thing.changed = 0 changed_lights.Cut() var/z_start = 1 diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 50a27b7ebdc..c0db11eedd5 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -62,8 +62,6 @@ //Called after a successful Move(). By this point, we've already moved /atom/movable/proc/Moved(atom/OldLoc, Dir) - if(light) - light.changed() return 1 /atom/movable/Del() diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 74be224cc36..061a8db5665 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -126,8 +126,7 @@ /turf/proc/ChangeTurf(var/path) if(!path) return if(path == type) return src - var/old_lumcount = lighting_lumcount - initial(lighting_lumcount) - var/old_opacity = opacity + SSair.remove_from_active(src) var/turf/W = new path(src) @@ -136,18 +135,6 @@ W:Assimilate_Air() W.RemoveLattice() - W.lighting_lumcount += old_lumcount - if(old_lumcount != W.lighting_lumcount) //light levels of the turf have changed. We need to shift it to another lighting-subarea - W.lighting_changed = 1 - SSlighting.changed_turfs += W - - if(old_opacity != W.opacity) //opacity has changed. Need to update surrounding lights - if(W.lighting_lumcount) //unless we're being illuminated, don't bother (may be buggy, hard to test) - W.UpdateAffectingLights() - - for(var/turf/space/S in range(W,1)) - S.update_starlight() - W.levelupdate() W.CalculateAdjacentTurfs() return W @@ -349,4 +336,4 @@ return !density /turf/proc/can_lay_cable() - return can_have_cabling() & !intact + return can_have_cabling() & !intact diff --git a/code/controllers/_DynamicAreaLighting_TG.dm b/code/modules/lighting/lighting_system.dm similarity index 81% rename from code/controllers/_DynamicAreaLighting_TG.dm rename to code/modules/lighting/lighting_system.dm index d039e51b25b..d1a24a9a9bc 100644 --- a/code/controllers/_DynamicAreaLighting_TG.dm +++ b/code/modules/lighting/lighting_system.dm @@ -1,343 +1,379 @@ -/* - This is /tg/'s 'newer' lighting system. It's basically a combination of Forum_Account's and ShadowDarke's - respective lighting libraries heavily modified by Carnwennan for /tg/station with further edits by - MrPerson. Credits, where due, to them. - - Originally, like all other lighting libraries on BYOND, we used areas to render different hard-coded light levels. - The idea was that this was cheaper than using objects. Well as it turns out, the cost of the system is primarily from the - massive loops the system has to run, not from the areas or objects actually doing any work. Thus the newer system uses objects - so we can have more lighting states and smooth transitions between them. - - This is a queueing system. Everytime we call a change to opacity or luminosity throwgh SetOpacity() or SetLuminosity(), - we are simply updating variables and scheduling certain lights/turfs for an update. Actual updates are handled - periodically by the SSlighting subsystem. Specifically, it runs check() on every changed light datum and deletes any that - return 1. Then it runs redraw_lighting() on every turf with lighting_changed = 1. - - Unlike our older system, there are hardcoded maximum luminosities (different for certain atoms). - This is to cap the cost of creating lighting effects. - (without this, an atom with luminosity of 20 would have to update 41^2 turfs!) :s - - Each light remembers the effect it casts on each turf. It reduces cost of removing lighting effects by a lot! - - Known Issues/TODO: - Shuttles still do not have support for dynamic lighting (I hope to fix this at some point) -probably trivial now - No directional lighting support. (prototype looked ugly) - Allow lights to be weaker than 'cap' radius - Colored lights -*/ - -#define LIGHTING_CIRCULAR 1 //comment this out to use old square lighting effects. -#define LIGHTING_LAYER 15 //Drawing layer for lighting -#define LIGHTING_CAP 10 //The lumcount level at which alpha is 0 and we're fully lit. -#define LIGHTING_CAP_FRAC (255/LIGHTING_CAP) //A precal'd variable we'll use in turf/redraw_lighting() -#define LIGHTING_ICON 'icons/effects/alphacolors.dmi' -#define LIGHTING_ICON_STATE "white" -#define LIGHTING_TIME 1.2 //Time to do any lighting change. Actual number pulled out of my ass -#define LIGHTING_DARKEST_VISIBLE_ALPHA 230 //Anything darker than this is so dark, we'll just consider the whole tile unlit - -/datum/light_source - var/atom/owner - var/radius = 0 - var/changed = 1 - var/list/effect = list() - var/__x = 0 //x coordinate at last update - var/__y = 0 //y coordinate at last update - -/datum/light_source/New(atom/A) - if(!istype(A)) - CRASH("The first argument to the light object's constructor must be the atom that is the light source. Expected atom, received '[A]' instead.") - ..() - owner = A - radius = A.luminosity - __x = owner.x - __y = owner.y - SSlighting.changed_lights += src - -/datum/light_source/Destroy() - if(owner && owner.light == src) - owner.light = null - return ..() - -//Check a light to see if its effect needs reprocessing. If it does, remove any old effect and create a new one -/datum/light_source/proc/check() - if(!owner) - remove_effect() - return 1 //causes it to be deleted - - if(changed) - changed = 0 - remove_effect() - return add_effect() - return 0 - - -/datum/light_source/proc/changed() - changed = 1 - if(owner) - __x = owner.x - __y = owner.y - if(SSlighting) - SSlighting.changed_lights |= src - -/datum/light_source/proc/remove_effect() - // before we apply the effect we remove the light's current effect. - for(var/turf/T in effect) // negate the effect of this light source - T.update_lumcount(-effect[T]) - - if(T.affecting_lights && T.affecting_lights.len) - T.affecting_lights -= src - else - T.affecting_lights = null - - effect.Cut() // clear the effect list - -/datum/light_source/proc/add_effect() - // only do this if the light is turned on and is on the map - if(owner.loc && radius > 0) - effect = list() - var/turf/To = get_turf(owner) - var/range = owner.get_light_range(radius) - - for(var/turf/T in view(range, To)) - var/delta_lumcount = T.lumen(src) - if(delta_lumcount > 0) - effect[T] = delta_lumcount - T.update_lumcount(delta_lumcount) - - if(!T.affecting_lights) - T.affecting_lights = list() - T.affecting_lights += src - - return 0 - else - return 1 //cause the light to be removed from the lights list and garbage collected once it's no - //longer referenced by the queue - -/turf/proc/lumen(datum/light_source/L) - var/distance = 0 -#ifdef LIGHTING_CIRCULAR - distance = cheap_hypotenuse(x, y, L.__x, L.__y) -#else - distance = max(abs(x - L.__x), abs(y - L.__y)) -#endif - return LIGHTING_CAP * (L.radius - distance) / L.radius - - -/turf/space/lumen() - return 0 - - -/atom - var/datum/light_source/light - - -//Turfs with opacity when they are constructed will trigger nearby lights to update -//Turfs and atoms with luminosity when they are constructed will create a light_source automatically -/turf/New() - ..() - if(luminosity) - if(light) WARNING("[type] - Don't set lights up manually during New(), We do it automatically.") - light = new(src) -// luminosity = 0 - -//Movable atoms with opacity when they are constructed will trigger nearby lights to update -//Movable atoms with luminosity when they are constructed will create a light_source automatically -/atom/movable/New() - ..() - if(opacity) - UpdateAffectingLights() - if(luminosity) - if(light) WARNING("[type] - Don't set lights up manually during New(), We do it automatically.") - light = new(src) -// luminosity = 0 - -//Objects with opacity will trigger nearby lights to update at next lighting process. -/atom/movable/Destroy() - if(light) - light.changed() - light.owner = null - light = null - if(opacity) - UpdateAffectingLights() - return ..() - -//Sets our luminosity. -//If we have no light it will create one. -//If we are setting luminosity to 0 the light will be cleaned up by the controller and garbage collected once all its -//queues are complete. -//if we have a light already it is merely updated, rather than making a new one. -/atom/proc/SetLuminosity(new_luminosity) - if(new_luminosity < 0) - new_luminosity = 0 - - if(!light) - if(!new_luminosity) - return - light = new(src) - else - if(light.radius == new_luminosity) - return - light.radius = new_luminosity - luminosity = new_luminosity - light.changed() - -/atom/proc/AddLuminosity(delta_luminosity) - if(light) - SetLuminosity(light.radius + delta_luminosity) - else - SetLuminosity(delta_luminosity) - -/area/SetLuminosity(new_luminosity) //we don't want dynamic lighting for areas - luminosity = !!new_luminosity - - -//change our opacity (defaults to toggle), and then update all lights that affect us. -/atom/proc/SetOpacity(new_opacity) - if(new_opacity == null) - new_opacity = !opacity //default = toggle opacity - else if(opacity == new_opacity) - return 0 //opacity hasn't changed! don't bother doing anything - opacity = new_opacity //update opacity, the below procs now call light updates. - UpdateAffectingLights() - - -/atom/movable/SetOpacity(new_opacity) - if(..()==1) //only bother if opacity changed - if(isturf(loc)) //only bother with an update if we're on a turf - var/turf/T = loc - if(T.lighting_lumcount) //only bother with an update if our turf is currently affected by a light - UpdateAffectingLights() - -/atom/movable/light - icon = LIGHTING_ICON - icon_state = LIGHTING_ICON_STATE - layer = LIGHTING_LAYER - mouse_opacity = 0 - blend_mode = BLEND_MULTIPLY - invisibility = INVISIBILITY_LIGHTING - color = "#000" - luminosity = 0 - infra_luminosity = 1 - anchored = 1 - -/atom/movable/light/Destroy() - return 1 - -/atom/movable/light/Move() - return 0 - -/turf - var/lighting_lumcount = 0 - var/lighting_changed = 0 - var/atom/movable/light/lighting_object //Will be null for space turfs and anything in a static lighting area - var/list/affecting_lights //not initialised until used (even empty lists reserve a fair bit of memory) - -/turf/New() - lighting_object = locate() in src - if(!lighting_object && SSlighting) // Don't init_lighting() for map objects, basically - init_lighting() - return ..() - -/turf/proc/update_lumcount(amount) - lighting_lumcount += amount - if(!lighting_changed) - SSlighting.changed_turfs += src - lighting_changed = 1 - -/turf/space/update_lumcount(amount) //Keep track in case the turf becomes a floor at some point, but don't process. - lighting_lumcount += amount - -/turf/proc/init_lighting() - var/area/A = loc - if(!A.lighting_use_dynamic) - lighting_changed = 0 - else - if(!lighting_object) - lighting_object = new (src) - redraw_lighting() - -/turf/space/init_lighting() - if(config.starlight) - update_starlight() - -/turf/proc/redraw_lighting() - if(lighting_object) - var/newalpha - if(lighting_lumcount <= 0) - newalpha = 255 - else - lighting_object.luminosity = 1 - if(lighting_lumcount < LIGHTING_CAP) - var/num = round(Clamp(lighting_lumcount * LIGHTING_CAP_FRAC, 0, 255), 1) - newalpha = 255-num - else //if(lighting_lumcount >= LIGHTING_CAP) - newalpha = 0 - - if(lighting_object.alpha != newalpha) - var/change_time = LIGHTING_TIME - animate(lighting_object, alpha = newalpha, time = change_time) - if(newalpha >= LIGHTING_DARKEST_VISIBLE_ALPHA) //Doesn't actually make it darker or anything, just tells byond you can't see the tile - animate(luminosity = 0, time = 0) - - lighting_changed = 0 - -/area - var/lighting_use_dynamic = 1 //Turn this flag off to prevent sd_DynamicAreaLighting from affecting this area - -/area/proc/SetDynamicLighting() - lighting_use_dynamic = 1 - luminosity = 0 - for(var/turf/T in src.contents) - T.init_lighting() - T.update_lumcount(0) - -#undef LIGHTING_LAYER -#undef LIGHTING_CIRCULAR -#undef LIGHTING_ICON -#undef LIGHTING_ICON_STATE -#undef LIGHTING_TIME -#undef LIGHTING_CAP -#undef LIGHTING_CAP_FRAC -#undef LIGHTING_DARKEST_VISIBLE_ALPHA - - -//set the changed status of all lights which could have possibly lit this atom. -//We don't need to worry about lights which lit us but moved away, since they will have change status set already -//This proc can cause lots of lights to be updated. :( -/atom/proc/UpdateAffectingLights() - -/atom/movable/UpdateAffectingLights() - if(isturf(loc)) - loc.UpdateAffectingLights() - -/turf/UpdateAffectingLights() - if(affecting_lights) - for(var/datum/light_source/thing in affecting_lights) - thing.changed() //force it to update at next process() - - -#define LIGHTING_MAX_LUMINOSITY_STATIC 8 //Maximum luminosity to reduce lag. -#define LIGHTING_MAX_LUMINOSITY_MOBILE 5 //Moving objects have a lower max luminosity since these update more often. (lag reduction) -#define LIGHTING_MAX_LUMINOSITY_MOB 5 -#define LIGHTING_MAX_LUMINOSITY_TURF 1 //turfs have a severely shortened range to protect from inevitable floor-lighttile spam. - -//caps luminosity effects max-range based on what type the light's owner is. -/atom/proc/get_light_range(radius) - return min(radius, LIGHTING_MAX_LUMINOSITY_STATIC) - -/atom/movable/get_light_range(radius) - return min(radius, LIGHTING_MAX_LUMINOSITY_MOBILE) - -/mob/get_light_range(radius) - return min(radius, LIGHTING_MAX_LUMINOSITY_MOB) - -/obj/machinery/light/get_light_range(radius) - return min(radius, LIGHTING_MAX_LUMINOSITY_STATIC) - -/turf/get_light_range(radius) - return min(radius, LIGHTING_MAX_LUMINOSITY_TURF) - -#undef LIGHTING_MAX_LUMINOSITY_STATIC -#undef LIGHTING_MAX_LUMINOSITY_MOBILE -#undef LIGHTING_MAX_LUMINOSITY_MOB -#undef LIGHTING_MAX_LUMINOSITY_TURF +/* + This is /tg/'s 'newer' lighting system. It's basically a combination of Forum_Account's and ShadowDarke's + respective lighting libraries heavily modified by Carnwennan for /tg/station with further edits by + MrPerson. Credits, where due, to them. + + Originally, like all other lighting libraries on BYOND, we used areas to render different hard-coded light levels. + The idea was that this was cheaper than using objects. Well as it turns out, the cost of the system is primarily from the + massive loops the system has to run, not from the areas or objects actually doing any work. Thus the newer system uses objects + so we can have more lighting states and smooth transitions between them. + + This is a queueing system. Everytime we call a change to opacity or luminosity throwgh SetOpacity() or SetLuminosity(), + we are simply updating variables and scheduling certain lights/turfs for an update. Actual updates are handled + periodically by the SSlighting subsystem. Specifically, it runs check() on every light datum that ran changed(). + Then it runs redraw_lighting() on every turf that ran update_lumcount(). + + Unlike our older system, there are hardcoded maximum luminosities (different for certain atoms). + This is to cap the cost of creating lighting effects. + (without this, an atom with luminosity of 20 would have to update 41^2 turfs!) :s + + Each light remembers the effect it casts on each turf. It reduces cost of removing lighting effects by a lot! + + Known Issues/TODO: + Shuttles still do not have support for dynamic lighting (I hope to fix this at some point) -probably trivial now + No directional lighting support. (prototype looked ugly) + Allow lights to be weaker than 'cap' radius + Colored lights +*/ + +#define LIGHTING_CIRCULAR 1 //comment this out to use old square lighting effects. +#define LIGHTING_LAYER 15 //Drawing layer for lighting +#define LIGHTING_CAP 10 //The lumcount level at which alpha is 0 and we're fully lit. +#define LIGHTING_CAP_FRAC (255/LIGHTING_CAP) //A precal'd variable we'll use in turf/redraw_lighting() +#define LIGHTING_ICON 'icons/effects/alphacolors.dmi' +#define LIGHTING_ICON_STATE "white" +#define LIGHTING_TIME 1.2 //Time to do any lighting change. Actual number pulled out of my ass +#define LIGHTING_DARKEST_VISIBLE_ALPHA 230 //Anything darker than this is so dark, we'll just consider the whole tile unlit + +/datum/light_source + var/atom/owner + var/radius = 0 + var/changed = 1 + var/list/effect = list() + var/__x = 0 //x coordinate at last update + var/__y = 0 //y coordinate at last update + +/datum/light_source/New(atom/A) + if(!istype(A)) + CRASH("The first argument to the light object's constructor must be the atom that is the light source. Expected atom, received '[A]' instead.") + ..() + owner = A + radius = A.luminosity + __x = owner.x + __y = owner.y + SSlighting.changed_lights |= src + +/datum/light_source/Destroy() + if(owner && owner.light == src) + remove_effect() + owner.light = null + owner = null + return ..() + +//Check a light to see if its effect needs reprocessing. If it does, remove any old effect and create a new one +/datum/light_source/proc/check() + if(!owner) + remove_effect() + return 0 + + if(changed) + changed = 0 + remove_effect() + return add_effect() + + return 1 + +//Tell the lighting subsystem to check() next fire +/datum/light_source/proc/changed() + if(owner) + __x = owner.x + __y = owner.y + if(!changed) + changed = 1 + SSlighting.changed_lights |= src + +//Remove current effect +/datum/light_source/proc/remove_effect(). + for(var/turf/T in effect) + T.update_lumcount(-effect[T]) + + if(T.affecting_lights && T.affecting_lights.len) + T.affecting_lights -= src + + effect.Cut() + +//Apply a new effect +/datum/light_source/proc/add_effect() + // only do this if the light is turned on and is on the map + if(owner && owner.loc && radius > 0) + effect = list() + var/turf/To = get_turf(owner) + var/range = owner.get_light_range(radius) + for(var/atom/movable/AM in To) + if(AM == owner) + continue + if(AM.opacity) + range = 0 + break + + for(var/turf/T in view(range, To)) + var/delta_lumcount = T.lumen(src) + if(delta_lumcount > 0) + effect[T] = delta_lumcount + T.update_lumcount(delta_lumcount) + + if(!T.affecting_lights) + T.affecting_lights = list() + T.affecting_lights |= src + + return 1 + else + return 0 + +//How much light light_source L should apply to src +/turf/proc/lumen(datum/light_source/L) + var/distance = 0 +#ifdef LIGHTING_CIRCULAR + distance = cheap_hypotenuse(x, y, L.__x, L.__y) +#else + distance = max(abs(x - L.__x), abs(y - L.__y)) +#endif + return LIGHTING_CAP * (L.radius - distance) / L.radius +//LIGHTING_CAP == strength for now + + +/atom + var/datum/light_source/light + + +//Turfs with opacity when they are constructed will trigger nearby lights to update +//Turfs and atoms with luminosity when they are constructed will create a light_source automatically +/turf/New() + ..() + if(luminosity) + light = new(src) +// luminosity = 0 + +//Movable atoms with opacity when they are constructed will trigger nearby lights to update +//Movable atoms with luminosity when they are constructed will create a light_source automatically +/atom/movable/New() + ..() + if(opacity) + UpdateAffectingLights() + if(luminosity) + light = new(src) +// luminosity = 0 + +//Objects with opacity will trigger nearby lights to update at next SSlighting fire +/atom/movable/Destroy() + qdel(light) + if(opacity) + UpdateAffectingLights() + return ..() + +//Objects with opacity will trigger nearby lights of the old location to update at next SSlighting fire +/atom/movable/Moved(atom/OldLoc, Dir) + if(isturf(loc)) + if(opacity) + OldLoc.UpdateAffectingLights() + else + if(light) + light.changed() + return ..() + +//Sets our luminosity. +//If we have no light it will create one. +//If we are setting luminosity to 0 the light will be cleaned up by the controller and garbage collected once all its +//queues are complete. +//if we have a light already it is merely updated, rather than making a new one. +/atom/proc/SetLuminosity(new_luminosity) + if(new_luminosity < 0) + new_luminosity = 0 + + if(!light) + if(!new_luminosity) + return + light = new(src) + else + if(light.radius == new_luminosity) + return + light.radius = new_luminosity + luminosity = new_luminosity + light.changed() + +/atom/proc/AddLuminosity(delta_luminosity) + if(light) + SetLuminosity(light.radius + delta_luminosity) + else + SetLuminosity(delta_luminosity) + +/area/SetLuminosity(new_luminosity) //we don't want dynamic lighting for areas + luminosity = !!new_luminosity + + +//change our opacity (defaults to toggle), and then update all lights that affect us. +/atom/proc/SetOpacity(new_opacity) + if(new_opacity == null) + new_opacity = !opacity //default = toggle opacity + else if(opacity == new_opacity) + return 0 //opacity hasn't changed! don't bother doing anything + opacity = new_opacity //update opacity, the below procs now call light updates. + UpdateAffectingLights() + return 1 + +/atom/movable/light + icon = LIGHTING_ICON + icon_state = LIGHTING_ICON_STATE + layer = LIGHTING_LAYER + mouse_opacity = 0 + blend_mode = BLEND_OVERLAY + invisibility = INVISIBILITY_LIGHTING + color = "#000" + luminosity = 0 + infra_luminosity = 1 + anchored = 1 + +/atom/movable/light/Destroy() + return 1 + +/atom/movable/light/Move() + return 0 + +/turf + var/lighting_lumcount = 0 + var/lighting_changed = 0 + var/atom/movable/light/lighting_object //Will be null for space turfs and anything in a static lighting area + var/list/affecting_lights //not initialised until used (even empty lists reserve a fair bit of memory) + +/turf/ChangeTurf(var/path) + if(!path || path == type) //Sucks this is here but it would cause problems otherwise. + return ..() + + if(light) + qdel(light) + + var/old_lumcount = lighting_lumcount - initial(lighting_lumcount) + + var/list/our_lights //reset affecting_lights if needed + if(opacity != initial(path:opacity) && old_lumcount) + UpdateAffectingLights() + + if(affecting_lights) + our_lights = affecting_lights.Copy() + + . = ..() //At this point the turf has changed + + affecting_lights = our_lights + + lighting_changed = 1 //Don't add ourself to SSlighting.changed_turfs + update_lumcount(old_lumcount) + lighting_object = locate() in src + init_lighting() + + for(var/turf/space/S in orange(src,1)) + S.update_starlight() + +/turf/proc/update_lumcount(amount) + lighting_lumcount += amount + if(!lighting_changed) + SSlighting.changed_turfs += src + lighting_changed = 1 + +/turf/space/update_lumcount(amount) //Keep track in case the turf becomes a floor at some point, but don't process. + lighting_lumcount += amount + +/turf/proc/init_lighting() + var/area/A = loc + if(!A.lighting_use_dynamic || istype(src, /turf/space)) + lighting_changed = 0 + if(lighting_object) + lighting_object.alpha = 0 + lighting_object = null + else + if(!lighting_object) + lighting_object = new (src) + redraw_lighting(1) + +/turf/space/init_lighting() + . = ..() + if(config.starlight) + update_starlight() + +/turf/proc/redraw_lighting(var/instantly = 0) + if(lighting_object) + var/newalpha + if(lighting_lumcount <= 0) + newalpha = 255 + else + lighting_object.luminosity = 1 + if(lighting_lumcount < LIGHTING_CAP) + var/num = Clamp(lighting_lumcount * LIGHTING_CAP_FRAC, 0, 255) + newalpha = 255-num + else //if(lighting_lumcount >= LIGHTING_CAP) + newalpha = 0 + + if(lighting_object.alpha != newalpha) + var/change_time = LIGHTING_TIME + if(instantly) + change_time = 0 + animate(lighting_object, alpha = newalpha, time = change_time) + if(newalpha >= LIGHTING_DARKEST_VISIBLE_ALPHA) //Doesn't actually make it darker or anything, just tells byond you can't see the tile + animate(luminosity = 0, time = 0) + + lighting_changed = 0 + +/area + var/lighting_use_dynamic = 1 //Turn this flag off to make the area fullbright + +/area/New() + . = ..() + if(!lighting_use_dynamic) + luminosity = 1 + +/area/proc/SetDynamicLighting() + lighting_use_dynamic = 1 + luminosity = 0 + for(var/turf/T in src.contents) + T.init_lighting() + T.update_lumcount(0) + +#undef LIGHTING_LAYER +#undef LIGHTING_CIRCULAR +#undef LIGHTING_ICON +#undef LIGHTING_ICON_STATE +#undef LIGHTING_TIME +#undef LIGHTING_CAP +#undef LIGHTING_CAP_FRAC +#undef LIGHTING_DARKEST_VISIBLE_ALPHA + + +//set the changed status of all lights which could have possibly lit this atom. +//We don't need to worry about lights which lit us but moved away, since they will have change status set already +//This proc can cause lots of lights to be updated. :( +/atom/proc/UpdateAffectingLights() + +/atom/movable/UpdateAffectingLights() + if(isturf(loc)) + loc.UpdateAffectingLights() + +/turf/UpdateAffectingLights() + if(affecting_lights) + for(var/datum/light_source/thing in affecting_lights) + thing.changed() //force it to update at next process() + + +#define LIGHTING_MAX_LUMINOSITY_STATIC 8 //Maximum luminosity to reduce lag. +#define LIGHTING_MAX_LUMINOSITY_MOBILE 5 //Moving objects have a lower max luminosity since these update more often. (lag reduction) +#define LIGHTING_MAX_LUMINOSITY_MOB 5 +#define LIGHTING_MAX_LUMINOSITY_TURF 8 //turfs are static too, why was this 1?! + +//caps luminosity effects max-range based on what type the light's owner is. +/atom/proc/get_light_range(radius) + return min(radius, LIGHTING_MAX_LUMINOSITY_STATIC) + +/atom/movable/get_light_range(radius) + return min(radius, LIGHTING_MAX_LUMINOSITY_MOBILE) + +/mob/get_light_range(radius) + return min(radius, LIGHTING_MAX_LUMINOSITY_MOB) + +/obj/machinery/light/get_light_range(radius) + return min(radius, LIGHTING_MAX_LUMINOSITY_STATIC) + +/turf/get_light_range(radius) + return min(radius, LIGHTING_MAX_LUMINOSITY_TURF) + +#undef LIGHTING_MAX_LUMINOSITY_STATIC +#undef LIGHTING_MAX_LUMINOSITY_MOBILE +#undef LIGHTING_MAX_LUMINOSITY_MOB +#undef LIGHTING_MAX_LUMINOSITY_TURF diff --git a/tgstation.dme b/tgstation.dme index a9aced7f420..299fe4ca291 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -118,7 +118,6 @@ #include "code\ATMOSPHERICS\pipes\simple.dm" #include "code\ATMOSPHERICS\pipes\tank.dm" #include "code\ATMOSPHERICS\pipes\vent.dm" -#include "code\controllers\_DynamicAreaLighting_TG.dm" #include "code\controllers\configuration.dm" #include "code\controllers\failsafe.dm" #include "code\controllers\master_controller.dm" @@ -1009,6 +1008,7 @@ #include "code\modules\library\lib_items.dm" #include "code\modules\library\lib_machines.dm" #include "code\modules\library\lib_readme.dm" +#include "code\modules\lighting\lighting_system.dm" #include "code\modules\mining\equipment_locker.dm" #include "code\modules\mining\machine_input_output_plates.dm" #include "code\modules\mining\machine_processing.dm"