Merge pull request #8581 from MrPerson/lighting_2015

Object based lighting system
This commit is contained in:
Swag McYolosteinen
2015-04-12 13:14:32 +02:00
21 changed files with 486 additions and 580 deletions
+4 -12
View File
@@ -53,18 +53,10 @@
return 0
return 1
//Magic constants obtained by using linear regression on right-angled triangles of sides 0<x<1, 0<y<1
//They should approximate pythagoras theorem well enough for our needs.
#define k1 0.934
#define k2 0.427
/proc/cheap_hypotenuse(Ax,Ay,Bx,By) // T is just the second atom to check distance to center with
var/dx = abs(Ax - Bx) //sides of right-angled triangle
var/dy = abs(Ay - By)
if(dx>=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)
-148
View File
@@ -828,8 +828,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)
@@ -888,152 +886,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
-308
View File
@@ -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
+2 -1
View File
@@ -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.
+44 -54
View File
@@ -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
+1 -2
View File
@@ -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"
+6 -4
View File
@@ -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()
+15 -7
View File
@@ -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,10 @@
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)
return 1
/atom/movable/Del()
if(isnull(gc_destroyed) && loc)
testing("GC: -- [type] was deleted via del() rather than qdel() --")
@@ -68,14 +74,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
@@ -96,12 +101,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
+10 -15
View File
@@ -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)
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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 = "<b>From:</b> [unlinkedsender]<BR>[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))
-1
View File
@@ -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: <br>A.name=[A.name]<br>A.tag=[A.tag]<br>A.master=[ma]"
@@ -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)
+1 -14
View File
@@ -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
+1 -1
View File
@@ -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"
+379
View File
@@ -0,0 +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 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
+3 -3
View File
@@ -277,7 +277,7 @@
update_icon()
if(on)
if(luminosity != brightness)
if(!light || light.radius != 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)
+4 -2
View File
@@ -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)
+2 -1
View File
@@ -1,2 +1,3 @@
author: MrPerson
changes: []
changes:
- experiment: "New lighting system! Lighting now smoothly transfers from one lighting level to the next."
Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 B

After

Width:  |  Height:  |  Size: 650 B

+1 -1
View File
@@ -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"
@@ -1014,6 +1013,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"