mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-25 06:00:16 +01:00
Merge pull request #8581 from MrPerson/lighting_2015
Object based lighting system
This commit is contained in:
@@ -1,308 +0,0 @@
|
||||
/*
|
||||
Modified DynamicAreaLighting for TGstation - Coded by Carnwennan
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
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!
|
||||
|
||||
Known Issues/TODO:
|
||||
Shuttles still do not have support for dynamic lighting (I hope to fix this at some point)
|
||||
No directional lighting support. (prototype looked ugly)
|
||||
*/
|
||||
|
||||
#define USE_CIRCULAR_LIGHTING //comment this out to use old square lighting effects.
|
||||
|
||||
/datum/light_source
|
||||
var/atom/owner
|
||||
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
|
||||
__x = owner.x
|
||||
__y = owner.y
|
||||
// the lighting object maintains a list of all light sources
|
||||
SSlighting.lights += src
|
||||
|
||||
|
||||
//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.
|
||||
|
||||
// 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()
|
||||
return add_effect()
|
||||
return 0
|
||||
|
||||
|
||||
/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 && owner.luminosity > 0)
|
||||
effect = list()
|
||||
var/turf/To = get_turf(owner)
|
||||
var/range = owner.get_light_range()
|
||||
|
||||
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
|
||||
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.owner.luminosity
|
||||
#ifdef USE_CIRCULAR_LIGHTING
|
||||
. -= cheap_hypotenuse(x, y, L.__x, L.__y)
|
||||
#else
|
||||
. -= max(abs(x - L.__x), abs(y - L.__y))
|
||||
#endif
|
||||
return .
|
||||
|
||||
/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)
|
||||
|
||||
//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)
|
||||
|
||||
//Objects with opacity will trigger nearby lights to update at next lighting process.
|
||||
/atom/movable/Destroy()
|
||||
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(luminosity != new_luminosity) //non-luminous lights are removed from the lights list in add_effect()
|
||||
light.changed = 1
|
||||
else
|
||||
if(new_luminosity)
|
||||
light = new(src)
|
||||
luminosity = new_luminosity
|
||||
|
||||
/atom/proc/AddLuminosity(delta_luminosity)
|
||||
SetLuminosity(luminosity + 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()
|
||||
|
||||
|
||||
/turf
|
||||
var/lighting_lumcount = 0
|
||||
var/lighting_changed = 0
|
||||
var/list/affecting_lights //not initialised until used (even empty lists reserve a fair bit of memory)
|
||||
|
||||
/turf/space
|
||||
lighting_lumcount = 4 //starlight
|
||||
|
||||
/turf/proc/update_lumcount(amount)
|
||||
lighting_lumcount += amount
|
||||
if(!lighting_changed)
|
||||
SSlighting.changed_turfs += src
|
||||
lighting_changed = 1
|
||||
|
||||
/area/proc/lighting_tag(level)
|
||||
return tagbase + "sd_L[level]"
|
||||
|
||||
/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]
|
||||
|
||||
A.tag = tag
|
||||
A.lighting_subarea = 1
|
||||
A.lighting_space = 0 // in case it was copied from a space subarea
|
||||
A.SetLightLevel(level)
|
||||
|
||||
related += A
|
||||
return A
|
||||
|
||||
/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)
|
||||
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 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
|
||||
//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/thing in affecting_lights)
|
||||
thing:changed = 1 //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()
|
||||
return min(luminosity, LIGHTING_MAX_LUMINOSITY_STATIC)
|
||||
|
||||
/atom/movable/get_light_range()
|
||||
return min(luminosity, LIGHTING_MAX_LUMINOSITY_MOBILE)
|
||||
|
||||
/mob/get_light_range()
|
||||
return min(luminosity, LIGHTING_MAX_LUMINOSITY_MOB)
|
||||
|
||||
/obj/machinery/light/get_light_range()
|
||||
return min(luminosity, LIGHTING_MAX_LUMINOSITY_STATIC)
|
||||
|
||||
/turf/get_light_range()
|
||||
return min(luminosity, LIGHTING_MAX_LUMINOSITY_TURF)
|
||||
|
||||
#undef LIGHTING_MAX_LUMINOSITY_STATIC
|
||||
#undef LIGHTING_MAX_LUMINOSITY_MOBILE
|
||||
#undef LIGHTING_MAX_LUMINOSITY_MOB
|
||||
#undef LIGHTING_MAX_LUMINOSITY_TURF
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1,56 +1,50 @@
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
/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 ..()
|
||||
|
||||
|
||||
/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)
|
||||
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
|
||||
lights.Cut(i, i+1)
|
||||
changed_lights_workload = MC_AVERAGE(changed_lights_workload, changed_lights.len)
|
||||
|
||||
// for(var/area/A in sortedAreas)
|
||||
// A.luminosity = 1
|
||||
|
||||
for(var/datum/light_source/thing in changed_lights)
|
||||
thing.check()
|
||||
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:shift_to_subarea()
|
||||
for(var/turf/thing in changed_turfs)
|
||||
if(thing && thing.lighting_changed)
|
||||
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,12 +52,13 @@ var/datum/subsystem/lighting/SSlighting
|
||||
//z-levels with the z_level argument
|
||||
/datum/subsystem/lighting/Initialize(timeofday, z_level)
|
||||
|
||||
var/i=1
|
||||
for(var/thing in lights)
|
||||
if(thing && !thing:check())
|
||||
++i
|
||||
continue
|
||||
lights.Cut(i, i+1)
|
||||
// for(var/area/A in sortedAreas)
|
||||
// A.luminosity = 1
|
||||
|
||||
for(var/datum/light_source/thing in changed_lights)
|
||||
thing.add_effect()
|
||||
thing.changed = 0
|
||||
changed_lights.Cut()
|
||||
|
||||
var/z_start = 1
|
||||
var/z_finish = world.maxz
|
||||
@@ -72,28 +67,24 @@ 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/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)
|
||||
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
|
||||
|
||||
..()
|
||||
|
||||
@@ -103,21 +94,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()
|
||||
|
||||
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/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
|
||||
|
||||
for(var/turf/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 +122,5 @@ var/datum/subsystem/lighting/SSlighting
|
||||
msg += "\t [varname] = [varval1] -> [varval2]\n"
|
||||
world.log << msg
|
||||
|
||||
#undef LIGHTING_ICON
|
||||
#undef LIGHTING_LAYER
|
||||
// for(var/area/A in sortedAreas)
|
||||
// A.luminosity = !A.lighting_use_dynamic
|
||||
Reference in New Issue
Block a user