mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-22 00:26:36 +01:00
Upwards lights now go up more than one Z-level, but for real this time. Supersedes #5302.
424 lines
12 KiB
Plaintext
424 lines
12 KiB
Plaintext
var/global/datum/lighting_corner/dummy/dummy_lighting_corner = new
|
|
// Because we can control each corner of every lighting overlay.
|
|
// And corners get shared between multiple turfs (unless you're on the corners of the map, then 1 corner doesn't).
|
|
// For the record: these should never ever ever be deleted, even if the turf doesn't have dynamic lighting.
|
|
|
|
// This list is what the code that assigns corners listens to, the order in this list is the order in which corners are added to the /turf/corners list.
|
|
var/global/list/LIGHTING_CORNER_DIAGONAL = list(NORTHEAST, SOUTHEAST, SOUTHWEST, NORTHWEST)
|
|
|
|
// This is the reverse of the above - the position in the array is a dir. Update this if the above changes.
|
|
var/global/list/REVERSE_LIGHTING_CORNER_DIAGONAL = list(0, 0, 0, 0, 3, 4, 0, 0, 2, 1)
|
|
|
|
/datum/lighting_corner
|
|
// t1 through t4 are our masters, in no particular order.
|
|
// They are split into vars like this in the interest of reducing memory usage.
|
|
// tX is the turf itself, tXi is the index of this corner in that turf's corners list.
|
|
var/turf/t1
|
|
var/t1i
|
|
var/turf/t2
|
|
var/t2i
|
|
var/turf/t3
|
|
var/t3i
|
|
var/turf/t4
|
|
var/t4i
|
|
|
|
var/list/datum/light_source/affecting // Light sources affecting us.
|
|
var/active = FALSE // TRUE if one of our masters has dynamic lighting.
|
|
|
|
var/x = 0
|
|
var/y = 0
|
|
var/z = 0
|
|
|
|
// Our own intensity, from lights directly shining on us.
|
|
var/self_r = 0
|
|
var/self_g = 0
|
|
var/self_b = 0
|
|
|
|
// The intensity we're inheriting from the turfs below us, if we're a Z-turf. This is a sum of all below Z-turfs.
|
|
var/below_r = 0
|
|
var/below_g = 0
|
|
var/below_b = 0
|
|
|
|
// Ambient turf lighting that's not inherited from a light source. These are updated as absolute values.
|
|
var/ambient_r = 0
|
|
var/ambient_g = 0
|
|
var/ambient_b = 0
|
|
|
|
// The turf above us' ambient
|
|
var/above_ambient_r = 0
|
|
var/above_ambient_g = 0
|
|
var/above_ambient_b = 0
|
|
|
|
// The final intensity, all things considered.
|
|
var/apparent_r = 0
|
|
var/apparent_g = 0
|
|
var/apparent_b = 0
|
|
|
|
var/needs_update = FALSE
|
|
|
|
var/cache_r = 0
|
|
var/cache_g = 0
|
|
var/cache_b = 0
|
|
var/cache_mx = 0
|
|
|
|
/// Used for planet lighting. Probably needs a better system to prevent over-updating when not needed at some point.
|
|
var/update_gen = 0
|
|
|
|
/datum/lighting_corner/New(turf/new_turf, diagonal, oi)
|
|
SSlighting.total_lighting_corners += 1
|
|
|
|
var/has_ambience = FALSE
|
|
|
|
t1 = new_turf
|
|
z = new_turf.z
|
|
t1i = oi
|
|
|
|
if (TURF_IS_AMBIENT_LIT_UNSAFE(new_turf))
|
|
has_ambience = TRUE
|
|
|
|
var/vertical = diagonal & ~(diagonal - 1) // The horizontal directions (4 and 8) are bigger than the vertical ones (1 and 2), so we can reliably say the lsb is the horizontal direction.
|
|
var/horizontal = diagonal & ~vertical // Now that we know the horizontal one we can get the vertical one.
|
|
|
|
x = new_turf.x + (horizontal == EAST ? 0.5 : -0.5)
|
|
y = new_turf.y + (vertical == NORTH ? 0.5 : -0.5)
|
|
|
|
// My initial plan was to make this loop through a list of all the dirs (horizontal, vertical, diagonal).
|
|
// Issue being that the only way I could think of doing it was very messy, slow and honestly overengineered.
|
|
// So we'll have this hardcode instead.
|
|
var/turf/T
|
|
|
|
|
|
// Diagonal one is easy.
|
|
T = get_step(new_turf, diagonal)
|
|
if (T) // In case we're on the map's border.
|
|
if (!T.corners)
|
|
T.corners = new(4)
|
|
|
|
t2 = T
|
|
t2i = REVERSE_LIGHTING_CORNER_DIAGONAL[diagonal]
|
|
T.corners[t2i] = src
|
|
if (TURF_IS_AMBIENT_LIT_UNSAFE(T))
|
|
has_ambience = TRUE
|
|
|
|
// Now the horizontal one.
|
|
T = get_step(new_turf, horizontal)
|
|
if (T) // Ditto.
|
|
if (!T.corners)
|
|
T.corners = new(4)
|
|
|
|
t3 = T
|
|
t3i = REVERSE_LIGHTING_CORNER_DIAGONAL[((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH)] // Get the dir based on coordinates.
|
|
T.corners[t3i] = src
|
|
if (TURF_IS_AMBIENT_LIT_UNSAFE(T))
|
|
has_ambience = TRUE
|
|
|
|
// And finally the vertical one.
|
|
T = get_step(new_turf, vertical)
|
|
if (T)
|
|
if (!T.corners)
|
|
T.corners = new(4)
|
|
|
|
t4 = T
|
|
t4i = REVERSE_LIGHTING_CORNER_DIAGONAL[((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH)] // Get the dir based on coordinates.
|
|
T.corners[t4i] = src
|
|
if (TURF_IS_AMBIENT_LIT_UNSAFE(T))
|
|
has_ambience = TRUE
|
|
|
|
update_active()
|
|
if (has_ambience)
|
|
init_ambient()
|
|
|
|
#define OVERLAY_PRESENT(T) (T && T.lighting_overlay)
|
|
|
|
/datum/lighting_corner/proc/update_active()
|
|
active = FALSE
|
|
|
|
if (OVERLAY_PRESENT(t1) || OVERLAY_PRESENT(t2) || OVERLAY_PRESENT(t3) || OVERLAY_PRESENT(t4))
|
|
active = TRUE
|
|
|
|
#undef OVERLAY_PRESENT
|
|
|
|
#define GET_ABOVE(T) (T.above())
|
|
#define GET_BELOW(T) (T.below())
|
|
|
|
#define UPDATE_APPARENT(T, CH) T.apparent_##CH = T.self_##CH + T.below_##CH + T.ambient_##CH + T.above_ambient_##CH
|
|
|
|
/datum/lighting_corner/proc/init_ambient()
|
|
var/sum_r = 0
|
|
var/sum_g = 0
|
|
var/sum_b = 0
|
|
|
|
var/turf/T
|
|
for (var/i in 1 to 4)
|
|
// this is ugly as fuck, but it's still more legible than doing this with a macro
|
|
switch (i)
|
|
if (1) T = t1
|
|
if (2) T = t2
|
|
if (3) T = t3
|
|
if (4) T = t4
|
|
|
|
if (!T || !T.ambient_light)
|
|
continue
|
|
|
|
var/list/parts = rgb2num(T.ambient_light)
|
|
|
|
sum_r += (parts[1] / 255) * T.ambient_light_multiplier
|
|
sum_g += (parts[2] / 255) * T.ambient_light_multiplier
|
|
sum_b += (parts[3] / 255) * T.ambient_light_multiplier
|
|
|
|
sum_r /= 4
|
|
sum_g /= 4
|
|
sum_b /= 4
|
|
|
|
update_ambient_lumcount(sum_r, sum_g, sum_b)
|
|
|
|
// God that was a mess, now to do the rest of the corner code! Hooray!
|
|
/datum/lighting_corner/proc/update_lumcount(delta_r, delta_g, delta_b, now = FALSE)
|
|
if (!(delta_r + delta_g + delta_b))
|
|
return
|
|
|
|
self_r += delta_r
|
|
self_g += delta_g
|
|
self_b += delta_b
|
|
|
|
UPDATE_APPARENT(src, r)
|
|
UPDATE_APPARENT(src, g)
|
|
UPDATE_APPARENT(src, b)
|
|
|
|
var/turf/T
|
|
var/Ti
|
|
// Grab the first master that's a Z-turf, if one exists.
|
|
// The above var cannot be relied on due to init ordering, but we can use it if it is set.
|
|
if (t1 && (T = t1.above || GET_ABOVE(t1)) && (T.mz_flags & MZ_ALLOW_LIGHTING))
|
|
Ti = t1i
|
|
else if (t2 && (T = t2.above || GET_ABOVE(t2)) && (T.mz_flags & MZ_ALLOW_LIGHTING))
|
|
Ti = t2i
|
|
else if (t3 && (T = t3.above || GET_ABOVE(t3)) && (T.mz_flags & MZ_ALLOW_LIGHTING))
|
|
Ti = t3i
|
|
else if (t4 && (T = t4.above || GET_ABOVE(t4)) && (T.mz_flags & MZ_ALLOW_LIGHTING))
|
|
Ti = t4i
|
|
else // Nothing above us that cares about below light.
|
|
T = null
|
|
|
|
if (T)
|
|
do
|
|
if (!T.corners || !T.corners[Ti])
|
|
T.generate_missing_corners()
|
|
|
|
// These never get instant updates; they're less important, so better to avoid risk of lag.
|
|
T.corners[Ti].update_below_lumcount(delta_r, delta_g, delta_b)
|
|
while ((T = T.above) && (T.mz_flags & MZ_ALLOW_LIGHTING))
|
|
|
|
// This needs to be down here instead of the above if so the lum values are properly updated.
|
|
if (needs_update)
|
|
return
|
|
|
|
if (now)
|
|
update_overlays(TRUE)
|
|
else
|
|
needs_update = TRUE
|
|
SSlighting.corner_queue += src
|
|
|
|
/datum/lighting_corner/proc/update_below_lumcount(delta_r, delta_g, delta_b, now = FALSE)
|
|
if (!(delta_r + delta_g + delta_b))
|
|
return
|
|
|
|
below_r += delta_r
|
|
below_g += delta_g
|
|
below_b += delta_b
|
|
|
|
UPDATE_APPARENT(src, r)
|
|
UPDATE_APPARENT(src, g)
|
|
UPDATE_APPARENT(src, b)
|
|
|
|
// This needs to be down here instead of the above if so the lum values are properly updated.
|
|
if (needs_update)
|
|
return
|
|
|
|
if (!now)
|
|
needs_update = TRUE
|
|
SSlighting.corner_queue += src
|
|
else
|
|
update_overlays(TRUE)
|
|
|
|
/datum/lighting_corner/proc/update_ambient_lumcount(delta_r, delta_g, delta_b, skip_update = FALSE)
|
|
ambient_r += delta_r
|
|
ambient_g += delta_g
|
|
ambient_b += delta_b
|
|
|
|
UPDATE_APPARENT(src, r)
|
|
UPDATE_APPARENT(src, g)
|
|
UPDATE_APPARENT(src, b)
|
|
|
|
var/turf/T
|
|
var/Ti
|
|
|
|
if (t1)
|
|
T = t1
|
|
Ti = t1i
|
|
else if (t2)
|
|
T = t2
|
|
Ti = t2i
|
|
else if (t3)
|
|
T = t3
|
|
Ti = t3i
|
|
else if (t4)
|
|
T = t4
|
|
Ti = t4i
|
|
else
|
|
// This should be impossible to reach -- how do we exist without at least one master turf?
|
|
CRASH("Corner has no masters!")
|
|
|
|
var/datum/lighting_corner/below = src
|
|
|
|
var/turf/lasT
|
|
|
|
// We init before Z-Mimic, cannot rely on above/below.
|
|
while ((lasT = T) && (T = T.below || GET_BELOW(T)) && (lasT.mz_flags & MZ_ALLOW_LIGHTING) && TURF_IS_DYNAMICALLY_LIT_UNSAFE(T))
|
|
T.ambient_has_indirect = TRUE
|
|
|
|
if (!T.corners || !T.corners[Ti])
|
|
T.generate_missing_corners()
|
|
|
|
ASSERT(T.corners?.len)
|
|
|
|
below = T.corners[Ti]
|
|
below.above_ambient_r += delta_r
|
|
below.above_ambient_g += delta_g
|
|
below.above_ambient_b += delta_b
|
|
|
|
UPDATE_APPARENT(below, r)
|
|
UPDATE_APPARENT(below, g)
|
|
UPDATE_APPARENT(below, b)
|
|
|
|
if (!skip_update && !below.needs_update)
|
|
below.needs_update = TRUE
|
|
SSlighting.corner_queue += below
|
|
|
|
if (needs_update || skip_update)
|
|
return
|
|
|
|
// Always queue for this, not important enough to hit the synchronous path.
|
|
needs_update = TRUE
|
|
SSlighting.corner_queue += src
|
|
|
|
//? This tonemap is nice, but rounding kills a lot of the details.
|
|
// #define ACES_RRT_ODT_TONEMAP(X) clamp((X * (X + 0.0245786) - 0.000090537) / (X * (0.983729 * X + 0.4329510) + 0.238081), 0, 1)
|
|
//? Don't even attempt to use with current darksight...
|
|
// #define ACES_TONEMAP(X) clamp((X * (2.51 * X + 0.03)) / (X * (2.43 * X + 0.59) + 0.14), 0, 1)
|
|
|
|
/datum/lighting_corner/proc/update_overlays(now = FALSE)
|
|
var/lr = apparent_r
|
|
var/lg = apparent_g
|
|
var/lb = apparent_b
|
|
|
|
// Cache these values a head of time so 4 individual lighting overlays don't all calculate them individually.
|
|
var/mx = max(lr, lg, lb) // Scale it so 1 is the strongest lum, if it is above 1.
|
|
. = 1 // factor
|
|
if (mx > 1)
|
|
. = 1 / mx
|
|
|
|
cache_r = round(lr * ., LIGHTING_ROUND_VALUE)
|
|
cache_g = round(lg * ., LIGHTING_ROUND_VALUE)
|
|
cache_b = round(lb * ., LIGHTING_ROUND_VALUE)
|
|
|
|
cache_mx = round(mx, LIGHTING_ROUND_VALUE)
|
|
|
|
var/turf/T
|
|
for (var/i in 1 to 4)
|
|
// this is ugly as fuck, but it's still more legible than doing this with a macro
|
|
switch (i)
|
|
if (1) T = t1
|
|
if (2) T = t2
|
|
if (3) T = t3
|
|
if (4) T = t4
|
|
|
|
var/atom/movable/lighting_overlay/Ov
|
|
if (T && (Ov = T.lighting_overlay))
|
|
if (now)
|
|
Ov.update_overlay()
|
|
else if (!Ov.needs_update)
|
|
Ov.needs_update = TRUE
|
|
SSlighting.overlay_queue += Ov
|
|
|
|
/// This is called when our turf's downward Z-opacity changes.
|
|
/datum/lighting_corner/proc/rebuild_ztraversal(new_opacity)
|
|
/*
|
|
If opacity transitions to off:
|
|
- Look down stack, removing below lights contributing to us
|
|
- Look up stack, updating turfs with our new below lighting value
|
|
If opacity transitions to on:
|
|
- Look down stack, add below contributors
|
|
- Look up stack, updating turfs with our new below lighting value
|
|
*/
|
|
below_r = below_g = below_b = 0
|
|
var/turf/T = null
|
|
var/datum/lighting_corner/Tcorn = src
|
|
var/Ti
|
|
|
|
if (!new_opacity)
|
|
for (;;)
|
|
if (Tcorn.t1 && (T = Tcorn.t1.below || GET_BELOW(Tcorn.t1)) && (T.above?.mz_flags & MZ_ALLOW_LIGHTING))
|
|
Ti = Tcorn.t1i
|
|
else if (Tcorn.t2 && (T = Tcorn.t2.below || GET_BELOW(Tcorn.t2)) && (T.above?.mz_flags & MZ_ALLOW_LIGHTING))
|
|
Ti = Tcorn.t2i
|
|
else if (Tcorn.t3 && (T = Tcorn.t3.below || GET_BELOW(Tcorn.t3)) && (T.above?.mz_flags & MZ_ALLOW_LIGHTING))
|
|
Ti = Tcorn.t3i
|
|
else if (Tcorn.t4 && (T = Tcorn.t4.below || GET_BELOW(Tcorn.t4)) && (T.above?.mz_flags & MZ_ALLOW_LIGHTING))
|
|
Ti = Tcorn.t4i
|
|
else // Nothing above us that cares about below light.
|
|
break
|
|
|
|
Tcorn = T.corners[Ti]
|
|
below_r += Tcorn.apparent_r
|
|
below_g += Tcorn.apparent_g
|
|
below_b += Tcorn.apparent_b
|
|
|
|
UPDATE_APPARENT(src, r)
|
|
UPDATE_APPARENT(src, g)
|
|
UPDATE_APPARENT(src, b)
|
|
|
|
if (!needs_update)
|
|
needs_update = TRUE
|
|
SSlighting.corner_queue += src
|
|
|
|
T = null
|
|
Tcorn = src
|
|
for (;;)
|
|
if (Tcorn.t1 && (T = Tcorn.t1.above || GET_ABOVE(Tcorn.t1)) && (T.mz_flags & MZ_ALLOW_LIGHTING))
|
|
Ti = Tcorn.t1i
|
|
else if (Tcorn.t2 && (T = Tcorn.t2.above || GET_ABOVE(Tcorn.t2)) && (T.mz_flags & MZ_ALLOW_LIGHTING))
|
|
Ti = Tcorn.t2i
|
|
else if (Tcorn.t3 && (T = Tcorn.t3.above || GET_ABOVE(Tcorn.t3)) && (T.mz_flags & MZ_ALLOW_LIGHTING))
|
|
Ti = Tcorn.t3i
|
|
else if (Tcorn.t4 && (T = Tcorn.t4.above || GET_ABOVE(Tcorn.t4)) && (T.mz_flags & MZ_ALLOW_LIGHTING))
|
|
Ti = Tcorn.t4i
|
|
else // Nothing above us that cares about below light.
|
|
break
|
|
|
|
Tcorn = T.corners[Ti]
|
|
Tcorn.below_r += apparent_r
|
|
Tcorn.below_g += apparent_g
|
|
Tcorn.below_b += apparent_b
|
|
|
|
UPDATE_APPARENT(Tcorn, r)
|
|
UPDATE_APPARENT(Tcorn, g)
|
|
UPDATE_APPARENT(Tcorn, b)
|
|
|
|
if (!Tcorn.needs_update)
|
|
Tcorn.needs_update = TRUE
|
|
SSlighting.corner_queue += Tcorn
|
|
|
|
#undef UPDATE_APPARENT
|
|
|
|
/datum/lighting_corner/Destroy(force = FALSE)
|
|
stack_trace("Someone [force ? "force-" : ""]deleted a lighting corner.")
|
|
if (!force)
|
|
return QDEL_HINT_LETMELIVE
|
|
|
|
SSlighting.total_lighting_corners -= 1
|
|
return ..()
|
|
|
|
/datum/lighting_corner/dummy/New()
|
|
return
|