Goonlights

Ports Goon Lighting from /vg/station.

Summary -
 This adds smooth edges to all lighting in order to increase the
 aesthetic appeal of the lighting system.
 It works by using a matrix to change the appearance of the lighting
 overlay sprite, which has been changed to be a base for the matrix to
 modify.
 Ported from /vg/station with the help of @PJB3005. This lighting
 system is a hybrid between Mloc's lighting rewrite and Tobba's
 Goonlights.

Notable changes:
 - Darksight now matters on mobs. The lighting overlays are not
 alpha=255 when they are completely dark, meaning you can still see the
 floor- assuming you can view the turf at all, because it retains the
 luminousity setting.
   - This means Tajaran have 100% night vision again as they are
   intended to. Humans can see in a 3x3 square centered around
   themselves.

   - No, I'm not changing this, if it's even possible. This is how
   BYOND's lighting is meant to work. If you have any complaints about
   game balance, please feel free to make a pull request to change
   see_in_dark values, which will be seperately evaluated.

 - The lighting controller now runs at world.tick_lag, to emulate the
 realtime function of StonedMC. So far with my testing, this hasn't
 caused any noticable performance decreases- the lighting system is
 obviously more expensive than our previous iteration, however, it's not
 the next ZAS ;)

Technical Details:
 - /atom/movable/lighting_overlay/proc/get_clamped_lum has been removed,
 succeeded by /turf/proc/get_lumcount. They behave identically.
 - Turf lighting is actually controlled by four "corner" datums, which
 feed information into the overlay.
 - The way opacity is factored into the system has changed. Anything
 that doesn't use set_opacity is not going to work to block light.
 - /area/lighting_use_dynamic has been renamed to
 /area/dynamic_lighting, for consistency with /turf/dynamic_lighting.
 - Lighting is no longer seperately initialized for away missions. It is
 handled in ChangeTurf() as it should be.

Known & Unfixable issues:
 - There is a 5-10 second delay from starting the round to the lights
 turning on. Attribute it to "the powernet being spun up" if you would
 like to- but it's actually just how long it takes the lighting system
 to update every single turf on the map.

 - When you walk with a light on you, the light will actually jump ahead
 of you before you visually get to the tile. This is because of the
 movement gliding on mobs, realtime lighting actually goes faster than
 the glide takes to complete, so it appears that your lights are moving
 faster than you.

Thank you krausy~

Animated Goonlights

This adds an animate() call to the update_overlays() proc. This makes it
so that any light changes will smoothly transition between the changes
instead of instantly changing their appearance.

Also fixes a few issues pointed out on Github.

Change lighting animation (turns out the old one totally breaks if you toggle a light quickly, whoopsies)

Kill LIGHTING_INSTANT_UPDATES

isturf
This commit is contained in:
Tigercat2000
2017-02-05 07:08:17 -08:00
parent 9127d07b4e
commit 129a57bd9b
33 changed files with 811 additions and 498 deletions
@@ -4,10 +4,10 @@
name = "\improper Clown Planet"
icon_state = "honk"
luminosity = 1
lighting_use_dynamic = 0
dynamic_lighting = 0
requires_power = 0
report_alerts = 0
/area/awaymission/clownplanet/miningtown
name = "\improper Clown Planet - Bananium-o-Rama"
icon_state = "away1"
-7
View File
@@ -13,13 +13,6 @@ var/global/list/potentialRandomZlevels = generateMapList(filename = "config/away
air_master.setup_allturfs(turfs)
log_debug("\tTook [stop_watch(subtimer)]s")
subtimer = start_watch()
log_debug("Initializing lighting")
for(var/turf/T in turfs)
if(T.dynamic_lighting)
T.lighting_build_overlays()
log_debug("\tTook [stop_watch(subtimer)]s")
subtimer = start_watch()
log_debug("Smoothing tiles")
for(var/turf/T in smoothTurfs)
+1 -1
View File
@@ -1,7 +1,7 @@
/area/awaymission/upperlevel
name = "Open Space"
color = "#888"
lighting_use_dynamic = 0
dynamic_lighting = 0
requires_power = 0
// Used by /turf/unsimulated/floor/upperlevel as a reference for where the other floor is
-257
View File
@@ -1,257 +0,0 @@
/datum/light_source
var/atom/top_atom
var/atom/source_atom
var/turf/source_turf
var/light_power
var/light_range
var/light_color // string, decomposed by parse_light_color()
var/lum_r
var/lum_g
var/lum_b
var/tmp/applied_lum_r
var/tmp/applied_lum_g
var/tmp/applied_lum_b
var/list/effect_str
var/list/effect_turf
var/applied
var/vis_update //Whetever we should smartly recalculate visibility. and then only update tiles that became (in) visible to us
var/needs_update
var/destroyed
var/force_update
/datum/light_source/New(atom/owner, atom/top)
source_atom = owner
if(!source_atom.light_sources) source_atom.light_sources = list()
source_atom.light_sources += src
top_atom = top
if(top_atom != source_atom)
if(!top.light_sources) top.light_sources = list()
top_atom.light_sources += src
source_turf = top_atom
light_power = source_atom.light_power
light_range = source_atom.light_range
light_color = source_atom.light_color
parse_light_color()
effect_str = list()
effect_turf = list()
update()
return ..()
/datum/light_source/proc/destroy()
destroyed = 1
force_update()
if(source_atom && source_atom.light_sources) source_atom.light_sources -= src
if(top_atom && top_atom.light_sources) top_atom.light_sources -= src
/datum/light_source/proc/update(atom/new_top_atom)
if(new_top_atom && new_top_atom != top_atom)
if(top_atom != source_atom) top_atom.light_sources -= src
top_atom = new_top_atom
if(top_atom != source_atom)
if(!top_atom.light_sources) top_atom.light_sources = list()
top_atom.light_sources += src
if(!needs_update) //Incase we're already updating either way.
lighting_update_lights += src
needs_update = 1
/datum/light_source/proc/force_update()
force_update = 1
if(!needs_update) //Incase we're already updating either way.
needs_update = 1
lighting_update_lights += src
/datum/light_source/proc/vis_update()
if(!needs_update)
needs_update = 1
lighting_update_lights += src
vis_update = 1
/datum/light_source/proc/check()
if(!source_atom || !light_range || !light_power)
destroy()
return 1
if(!top_atom)
top_atom = source_atom
. = 1
if(istype(top_atom, /turf))
if(source_turf != top_atom)
source_turf = top_atom
. = 1
else if(top_atom.loc != source_turf)
source_turf = top_atom.loc
. = 1
if(source_atom.light_power != light_power)
light_power = source_atom.light_power
. = 1
if(source_atom.light_range != light_range)
light_range = source_atom.light_range
. = 1
if(light_range && light_power && !applied)
. = 1
if(source_atom.light_color != light_color)
light_color = source_atom.light_color
parse_light_color()
. = 1
/datum/light_source/proc/parse_light_color()
if(light_color)
lum_r = color2R(light_color) / 255
lum_g = color2G(light_color) / 255
lum_b = color2B(light_color) / 255
else
lum_r = 1
lum_g = 1
lum_b = 1
#if LIGHTING_FALLOFF == 1 //circular
#define LUM_DISTANCE(swapvar, O, T) swapvar = (O.x - T.x)**2 + (O.y - T.y)**2 + LIGHTING_HEIGHT
#if LIGHTING_LAMBERTIAN == 1
#define LUM_ATTENUATION(swapvar) swapvar = CLAMP01((1 - CLAMP01(sqrt(swapvar) / max(1,light_range))) * (1 / sqrt(swapvar + 1)))
#else
#define LUM_ATTENUATION(swapvar) swapvar = 1 - CLAMP01(sqrt(swapvar) / max(1,light_range))
#endif
#elif LIGHTING_FALLOFF == 2 //square
#define LUM_DISTANCE(swapvar, O, T) swapvar = abs(O.x - T.x) + abs(O.y - T.y) + LIGHTING_HEIGHT
#if LIGHTING_LAMBERTIAN == 1
#define LUM_ATTENUATION(swapvar) swapvar = CLAMP01((1 - CLAMP01(swapvar / max(1,light_range))) * (1 / sqrt(swapvar**2 + 1)))
#else
#define LUM_ATTENUATION(swapvar) swapvar = CLAMP01(swapvar / max(1,light_range))
#endif
#endif
#define LUM_FALLOFF(swapvar, O, T) \
LUM_DISTANCE(swapvar, O, T); \
LUM_ATTENUATION(swapvar);
/datum/light_source/proc/apply_lum()
applied = 1
// Cache the applied lum counts.
applied_lum_r = lum_r
applied_lum_g = lum_g
applied_lum_b = lum_b
if(istype(source_turf))
FOR_DVIEW(var/turf/T, light_range, source_turf, INVISIBILITY_LIGHTING)
if(T.lighting_overlay)
var/strength
LUM_FALLOFF(strength, T, source_turf)
strength *= light_power
if(!strength) //Don't add turfs that aren't affected to the affected turfs.
continue
strength = round(strength, LIGHTING_ROUND_VALUE) //Screw sinking points.
effect_str += strength
T.lighting_overlay.update_lumcount(
lum_r * strength,
lum_g * strength,
lum_b * strength
)
else
effect_str += 0
if(!T.affecting_lights)
T.affecting_lights = list()
T.affecting_lights += src
effect_turf += T
END_FOR_DVIEW
/datum/light_source/proc/remove_lum()
applied = 0
var/i = 1
for(var/turf/T in effect_turf)
if(T.affecting_lights)
T.affecting_lights -= src
if(T.lighting_overlay)
var/str = effect_str[i]
T.lighting_overlay.update_lumcount(-str * applied_lum_r, -str * applied_lum_g, -str * applied_lum_b)
i++
effect_str.Cut()
effect_turf.Cut()
/datum/light_source/proc/forget_turf(turf/T)
var/idx = effect_turf.Find(T)
effect_turf.Cut(idx, idx + 1)
effect_str.Cut(idx, idx + 1)
//Smartly updates the lighting, only removes lum from and adds lum to turfs that actually got changed.
//This is for lights that need to reconsider due to nearby opacity changes.
//Stupid dumb copy pasta because BYOND and speed.
/datum/light_source/proc/smart_vis_update()
var/list/view[0]
FOR_DVIEW(var/turf/T, light_range, source_turf, INVISIBILITY_LIGHTING)
view += T //Filter out turfs.
END_FOR_DVIEW
//This is the part where we calculate new turfs (if any)
var/list/new_turfs = view - effect_turf //This will result with all the tiles that are added.
for(var/turf/T in new_turfs)
if(T.lighting_overlay)
LUM_FALLOFF(., T, source_turf)
. *= light_power
if(!.) //Don't add turfs that aren't affected to the affected turfs.
continue
. = round(., LIGHTING_ROUND_VALUE)
effect_str += .
T.lighting_overlay.update_lumcount(
lum_r * .,
lum_g * .,
lum_b * .
)
else
effect_str += 0
if(!T.affecting_lights)
T.affecting_lights = list()
T.affecting_lights += src
effect_turf += T
var/list/old_turfs = effect_turf - view
for(var/turf/T in old_turfs)
//Insert not-so-huge copy paste from remove_lum().
var/idx = effect_turf.Find(T) //Get the index, luckily Find() is cheap in small lists like this. (with small I mean under a couple thousand len)
if(T.affecting_lights)
T.affecting_lights -= src
if(T.lighting_overlay)
var/str = effect_str[idx]
T.lighting_overlay.update_lumcount(-str * lum_r, -str * lum_g, -str * lum_b)
effect_turf.Cut(idx, idx + 1)
effect_str.Cut(idx, idx + 1)
#undef LUM_FALLOFF
#undef LUM_DISTANCE
#undef LUM_ATTENUATION
+9
View File
@@ -0,0 +1,9 @@
/area
luminosity = TRUE
var/dynamic_lighting = TRUE
/area/New()
. = ..()
if(dynamic_lighting)
luminosity = FALSE
+34 -7
View File
@@ -1,19 +1,30 @@
/atom
var/light_power = 1 // intensity of the light
var/light_range = 0 // range in tiles of the light
var/light_color // RGB string representing the colour of the light
var/light_color // Hexadecimal RGB string representing the colour of the light
var/datum/light_source/light
var/list/light_sources
/atom/proc/set_light(l_range, l_power, l_color)
if(l_power != null) light_power = l_power
if(l_range != null) light_range = l_range
if(l_color != null) light_color = l_color
// Nonsensical value for l_color default, so we can detect if it gets set to null.
#define NONSENSICAL_VALUE -99999
/atom/proc/set_light(l_range, l_power, l_color = NONSENSICAL_VALUE)
if(l_power != null)
light_power = l_power
if(l_range != null)
light_range = l_range
if(l_color != NONSENSICAL_VALUE)
light_color = l_color
update_light()
#undef NONSENSICAL_VALUE
/atom/proc/update_light()
set waitfor = FALSE
if(!light_power || !light_range)
if(light)
light.destroy()
@@ -31,9 +42,14 @@
/atom/New()
. = ..()
if(light_power && light_range)
update_light()
if(opacity && isturf(loc))
var/turf/T = loc
T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guranteed to be on afterwards anyways.
/atom/Destroy()
if(light)
light.destroy()
@@ -62,11 +78,22 @@
new_loc.reconsider_lights()
/atom/proc/set_opacity(new_opacity)
var/old_opacity = opacity
if(new_opacity == opacity)
return
opacity = new_opacity
var/turf/T = loc
if(old_opacity != new_opacity && istype(T))
if(!isturf(T))
return
if(new_opacity == TRUE)
T.has_opaque_atom = TRUE
T.reconsider_lights()
else
var/old_has_opaque_atom = T.has_opaque_atom
T.recalc_atom_opacity()
if(old_has_opaque_atom != T.has_opaque_atom)
T.reconsider_lights()
/obj/item/equipped()
. = ..()
+125
View File
@@ -0,0 +1,125 @@
/var/list/datum/lighting_corner/all_lighting_corners = list()
/var/datum/lighting_corner/dummy/dummy_lighting_corner = new
// Because we can control each corner of every lighting overlay.
// And corners get shared between multiple turfs (unless you're on the corners of the map, then 1 corner doesn't).
// For the record: these should never ever ever be deleted, even if the turf doesn't have dynamic lighting.
// This list is what the code that assigns corners listens to, the order in this list is the order in which corners are added to the /turf/corners list.
/var/list/LIGHTING_CORNER_DIAGONAL = list(NORTHEAST, SOUTHEAST, SOUTHWEST, NORTHWEST)
/datum/lighting_corner
var/list/turf/masters = list()
var/list/datum/light_source/affecting = list() // Light sources affecting us.
var/active = FALSE // TRUE if one of our masters has dynamic lighting.
var/x = 0
var/y = 0
var/z = 0
var/lum_r = 0
var/lum_g = 0
var/lum_b = 0
var/needs_update = FALSE
var/cache_r = LIGHTING_SOFT_THRESHOLD
var/cache_g = LIGHTING_SOFT_THRESHOLD
var/cache_b = LIGHTING_SOFT_THRESHOLD
var/cache_mx = 0
var/update_gen = 0
/datum/lighting_corner/New(var/turf/new_turf, var/diagonal)
. = ..()
all_lighting_corners += src
masters[new_turf] = turn(diagonal, 180)
z = new_turf.z
var/vertical = diagonal & ~(diagonal - 1) // The horizontal directions (4 and 8) are bigger than the vertical ones (1 and 2), so we can reliably say the lsb is the horizontal direction.
var/horizontal = diagonal & ~vertical // Now that we know the horizontal one we can get the vertical one.
x = new_turf.x + (horizontal == EAST ? 0.5 : -0.5)
y = new_turf.y + (vertical == NORTH ? 0.5 : -0.5)
// My initial plan was to make this loop through a list of all the dirs (horizontal, vertical, diagonal).
// Issue being that the only way I could think of doing it was very messy, slow and honestly overengineered.
// So we'll have this hardcode instead.
var/turf/T
var/i
// Diagonal one is easy.
T = get_step(new_turf, diagonal)
if (T) // In case we're on the map's border.
if (!T.corners)
T.corners = list(null, null, null, null)
masters[T] = diagonal
i = LIGHTING_CORNER_DIAGONAL.Find(turn(diagonal, 180))
T.corners[i] = src
// Now the horizontal one.
T = get_step(new_turf, horizontal)
if (T) // Ditto.
if (!T.corners)
T.corners = list(null, null, null, null)
masters[T] = ((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH) // Get the dir based on coordinates.
i = LIGHTING_CORNER_DIAGONAL.Find(turn(masters[T], 180))
T.corners[i] = src
// And finally the vertical one.
T = get_step(new_turf, vertical)
if (T)
if (!T.corners)
T.corners = list(null, null, null, null)
masters[T] = ((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH) // Get the dir based on coordinates.
i = LIGHTING_CORNER_DIAGONAL.Find(turn(masters[T], 180))
T.corners[i] = src
update_active()
/datum/lighting_corner/proc/update_active()
active = FALSE
for (var/turf/T in masters)
if (T.lighting_overlay)
active = TRUE
// God that was a mess, now to do the rest of the corner code! Hooray!
/datum/lighting_corner/proc/update_lumcount(var/delta_r, var/delta_g, var/delta_b)
lum_r += delta_r
lum_g += delta_g
lum_b += delta_b
if (!needs_update)
needs_update = TRUE
lighting_update_corners += src
/datum/lighting_corner/proc/update_overlays()
// Cache these values a head of time so 4 individual lighting overlays don't all calculate them individually.
var/mx = max(lum_r, lum_g, lum_b) // Scale it so 1 is the strongest lum, if it is above 1.
. = 1 // factor
if (mx > 1)
. = 1 / mx
else if (mx < LIGHTING_SOFT_THRESHOLD)
. = 0 // 0 means soft lighting.
cache_r = lum_r * . || LIGHTING_SOFT_THRESHOLD
cache_g = lum_g * . || LIGHTING_SOFT_THRESHOLD
cache_b = lum_b * . || LIGHTING_SOFT_THRESHOLD
cache_mx = mx
for (var/TT in masters)
var/turf/T = TT
if (T.lighting_overlay)
if (!T.lighting_overlay.needs_update)
T.lighting_overlay.needs_update = TRUE
lighting_update_overlays += T.lighting_overlay
/datum/lighting_corner/dummy/New()
return
+57 -68
View File
@@ -1,3 +1,4 @@
var/list/all_lighting_overlays = list() // Global list of lighting overlays.
/atom/movable/lighting_overlay
name = ""
mouse_opacity = 0
@@ -6,90 +7,75 @@
icon = LIGHTING_ICON
layer = LIGHTING_LAYER
invisibility = INVISIBILITY_LIGHTING
color = "#000000"
color = LIGHTING_BASE_MATRIX
icon_state = "light1"
auto_init = 0 // doesn't need special init
blend_mode = BLEND_MULTIPLY
var/lum_r
var/lum_g
var/lum_b
var/lum_r = 0
var/lum_g = 0
var/lum_b = 0
var/needs_update
var/needs_update = FALSE
/atom/movable/lighting_overlay/New()
/atom/movable/lighting_overlay/New(var/atom/loc, var/no_update = FALSE)
. = ..()
verbs.Cut()
global.all_lighting_overlays += src
var/turf/T = loc //If this runtimes atleast we'll know what's creating overlays outside of turfs.
T.lighting_overlay = src
T.luminosity = 0
/atom/movable/lighting_overlay/proc/get_clamped_lum(var/minlum = 0, var/maxlum = 1)
var/lum = max(lum_r, lum_g, lum_b)
if(lum <= minlum)
return 0
else if(lum >= maxlum)
return 1
else
return (lum - minlum) / (maxlum - minlum)
/atom/movable/lighting_overlay/proc/update_lumcount(delta_r, delta_g, delta_b)
if(!delta_r && !delta_g && !delta_b) //Nothing is being changed all together.
if(no_update)
return
var/should_update = 0
if(!needs_update) //If this isn't true, we're already updating anyways.
if(max(lum_r, lum_g, lum_b) < 1) //Any change that could happen WILL change appearance.
should_update = 1
else if(max(lum_r + delta_r, lum_g + delta_g, lum_b + delta_b) < 1) //The change would bring us under 1 max lum, again, guaranteed to change appearance.
should_update = 1
else //We need to make sure that the colour ratios won't change in this code block.
var/mx1 = max(lum_r, lum_g, lum_b)
var/mx2 = max(lum_r + delta_r, lum_g + delta_g, lum_b + delta_b)
if(lum_r / mx1 != (lum_r + delta_r) / mx2 || lum_g / mx1 != (lum_g + delta_g) / mx2 || lum_b / mx1 != (lum_b + delta_b) / mx2) //Stuff would change.
should_update = 1
lum_r = max(lum_r + delta_r, 0) //Lum shouldn't drop below 1
lum_g = max(lum_g + delta_g, 0)
lum_b = max(lum_b + delta_b, 0)
if(!needs_update && should_update)
needs_update = 1
lighting_update_overlays += src
update_overlay()
/atom/movable/lighting_overlay/proc/update_overlay()
set waitfor = FALSE
var/turf/T = loc
if(istype(T)) //Incase we're not on a turf, pool ourselves, something happened.
if(lum_r == lum_g && lum_r == lum_b) //greyscale
blend_mode = BLEND_OVERLAY
if(lum_r <= 0)
T.luminosity = 0
color = "#000000"
alpha = 255
else
T.luminosity = 1
color = "#000000"
alpha = (1 - min(lum_r, 1)) * 255
if(!istype(T))
if(loc)
log_debug("A lighting overlay realised its loc was NOT a turf (actual loc: [loc][loc ? ", " + loc.type : "null"]) in update_overlay() and got qdel'ed!")
else
alpha = 255
var/mx = max(lum_r, lum_g, lum_b)
. = 1 // factor
if(mx > 1)
. = 1/mx
blend_mode = BLEND_MULTIPLY
color = rgb(lum_r * 255 * ., lum_g * 255 * ., lum_b * 255 * .)
if(color != "#000000")
T.luminosity = 1
else //No light, set the turf's luminosity to 0 to remove it from view()
T.luminosity = 0
else
//warning("A lighting overlay realised its loc was NOT a turf (actual loc: [loc][loc ? ", " + loc.type : "null"]) in update_overlay() and got qdel'ed!") //fucking bullshit bugs means this spams when shuttles move, feel free to fix
log_debug("A lighting overlay realised its loc was NOT a turf (actual loc: [loc][loc ? ", " + loc.type : "null"]) in update_overlay() and got qdel'ed!")
log_debug("A lighting overlay realised it was in nullspace in update_overlay() and got pooled!")
qdel(src)
return
// To the future coder who sees this and thinks
// "Why didn't he just use a loop?"
// Well my man, it's because the loop performed like shit.
// And there's no way to improve it because
// without a loop you can make the list all at once which is the fastest you're gonna get.
// Oh it's also shorter line wise.
// Including with these comments.
// See LIGHTING_CORNER_DIAGONAL in lighting_corner.dm for why these values are what they are.
// No I seriously cannot think of a more efficient method, fuck off Comic.
var/datum/lighting_corner/cr = T.corners[3] || dummy_lighting_corner
var/datum/lighting_corner/cg = T.corners[2] || dummy_lighting_corner
var/datum/lighting_corner/cb = T.corners[4] || dummy_lighting_corner
var/datum/lighting_corner/ca = T.corners[1] || dummy_lighting_corner
var/max = max(cr.cache_mx, cg.cache_mx, cb.cache_mx, ca.cache_mx)
var/list/new_matrix = list(
cr.cache_r, cr.cache_g, cr.cache_b, 0,
cg.cache_r, cg.cache_g, cg.cache_b, 0,
cb.cache_r, cb.cache_g, cb.cache_b, 0,
ca.cache_r, ca.cache_g, ca.cache_b, 0,
0, 0, 0, 1
)
var/lum = max > LIGHTING_SOFT_THRESHOLD
if(lum)
luminosity = 1
animate(src, color = new_matrix, time = 5)
else
animate(src, color = new_matrix, time = 5)
animate(luminosity = 0, time = 0)
/atom/movable/lighting_overlay/singularity_act()
return
@@ -98,10 +84,13 @@
return
/atom/movable/lighting_overlay/Destroy()
lighting_update_overlays -= src
global.all_lighting_overlays -= src
global.lighting_update_overlays -= src
global.lighting_update_overlays_old -= src
var/turf/T = loc
if(istype(T))
T.lighting_overlay = null
T.luminosity = 1
return ..()
-35
View File
@@ -1,35 +0,0 @@
/datum/controller/process/lighting
var/last_light_count = 0
var/last_overlay_count = 0
/datum/controller/process/lighting/doWork()
var/list/lighting_update_lights_old = lighting_update_lights //We use a different list so any additions to the update lists during a delay from SCHECK don't cause things to be cut from the list without being updated.
last_light_count = lighting_update_lights.len
lighting_update_lights = null //Nulling it first because of http://www.byond.com/forum/?post=1854520
lighting_update_lights = list()
for(var/datum/light_source/L in lighting_update_lights_old)
if(L.destroyed || L.check() || L.force_update)
L.remove_lum()
if(!L.destroyed)
L.apply_lum()
else if(L.vis_update) //We smartly update only tiles that became (in) visible to use.
L.smart_vis_update()
L.vis_update = 0
L.force_update = 0
L.needs_update = 0
SCHECK
var/list/lighting_update_overlays_old = lighting_update_overlays //Same as above.
last_overlay_count = lighting_update_overlays.len
lighting_update_overlays = null //Same as above
lighting_update_overlays = list()
for(var/atom/movable/lighting_overlay/O in lighting_update_overlays_old)
O.update_overlay()
O.needs_update = 0
SCHECK
+16
View File
@@ -0,0 +1,16 @@
/proc/create_all_lighting_overlays()
for(var/zlevel = 1 to world.maxz)
create_lighting_overlays_zlevel(zlevel)
/proc/create_lighting_overlays_zlevel(var/zlevel)
ASSERT(zlevel)
for(var/turf/T in block(locate(1, 1, zlevel), locate(world.maxx, world.maxy, zlevel)))
if(!T.dynamic_lighting)
continue
var/area/A = T.loc
if(!A.dynamic_lighting)
continue
new /atom/movable/lighting_overlay(T, TRUE)
+283
View File
@@ -0,0 +1,283 @@
// This is where the fun begins.
// These are the main datums that emit light.
/datum/light_source
var/atom/top_atom // The atom we're emitting light from(for example a mob if we're from a flashlight that's being held).
var/atom/source_atom // The atom that we belong to.
var/turf/source_turf // The turf under the above.
var/light_power // Intensity of the emitter light.
var/light_range // The range of the emitted light.
var/light_color // The colour of the light, string, decomposed by parse_light_color()
// Variables for keeping track of the colour.
var/lum_r
var/lum_g
var/lum_b
// The lumcount values used to apply the light.
var/tmp/applied_lum_r
var/tmp/applied_lum_g
var/tmp/applied_lum_b
var/list/datum/lighting_corner/effect_str // List used to store how much we're affecting corners.
var/list/turf/affecting_turfs
var/applied = FALSE // Whether we have applied our light yet or not.
var/vis_update // Whether we should smartly recalculate visibility. and then only update tiles that became(in)visible to us.
var/needs_update // Whether we are queued for an update.
var/destroyed // Whether we are destroyed and need to stop emitting light.
var/force_update
/datum/light_source/New(var/atom/owner, var/atom/top)
source_atom = owner // Set our new owner.
if(!source_atom.light_sources)
source_atom.light_sources = list()
source_atom.light_sources += src // Add us to the lights of our owner.
top_atom = top
if(top_atom != source_atom)
if(!top.light_sources)
top.light_sources = list()
top_atom.light_sources += src
source_turf = top_atom
light_power = source_atom.light_power
light_range = source_atom.light_range
light_color = source_atom.light_color
parse_light_color()
effect_str = list()
affecting_turfs = list()
update()
return ..()
// Kill ourselves.
/datum/light_source/proc/destroy()
destroyed = TRUE
force_update()
if(source_atom)
source_atom.light_sources -= src
if(top_atom)
top_atom.light_sources -= src
// Call it dirty, I don't care.
// This is here so there's no performance loss on non-instant updates from the fact that the engine can also do instant updates.
// If you're wondering what's with the "BYOND" argument: BYOND won't let me have a() macro that has no arguments :|.
#define effect_update(BYOND) \
if(!needs_update) \
{ \
lighting_update_lights += src; \
needs_update = TRUE; \
}
// This proc will cause the light source to update the top atom, and add itself to the update queue.
/datum/light_source/proc/update(var/atom/new_top_atom)
// This top atom is different.
if(new_top_atom && new_top_atom != top_atom)
if(top_atom != source_atom) // Remove ourselves from the light sources of that top atom.
top_atom.light_sources -= src
top_atom = new_top_atom
if(top_atom != source_atom)
if(!top_atom.light_sources)
top_atom.light_sources = list()
top_atom.light_sources += src // Add ourselves to the light sources of our new top atom.
effect_update(null)
// Will force an update without checking if it's actually needed.
/datum/light_source/proc/force_update()
force_update = 1
effect_update(null)
// Will cause the light source to recalculate turfs that were removed or added to visibility only.
/datum/light_source/proc/vis_update()
vis_update = 1
effect_update(null)
// Will check if we actually need to update, and update any variables that may need to be updated.
/datum/light_source/proc/check()
if(!source_atom || !light_range || !light_power)
destroy()
return 1
if(!top_atom)
top_atom = source_atom
. = 1
if(isturf(top_atom))
if(source_turf != top_atom)
source_turf = top_atom
. = 1
else if(top_atom.loc != source_turf)
source_turf = top_atom.loc
. = 1
if(source_atom.light_power != light_power)
light_power = source_atom.light_power
. = 1
if(source_atom.light_range != light_range)
light_range = source_atom.light_range
. = 1
if(light_range && light_power && !applied)
. = 1
if(source_atom.light_color != light_color)
light_color = source_atom.light_color
parse_light_color()
. = 1
// Decompile the hexadecimal colour into lumcounts of each perspective.
/datum/light_source/proc/parse_light_color()
if(light_color)
lum_r = GetRedPart (light_color) / 255
lum_g = GetGreenPart(light_color) / 255
lum_b = GetBluePart (light_color) / 255
else
lum_r = 1
lum_g = 1
lum_b = 1
// Macro that applies light to a new corner.
// It is a macro in the interest of speed, yet not having to copy paste it.
// If you're wondering what's with the backslashes, the backslashes cause BYOND to not automatically end the line.
// As such this all gets counted as a single line.
// The braces and semicolons are there to be able to do this on a single line.
#define APPLY_CORNER(C) \
. = LUM_FALLOFF(C, source_turf); \
\
. *= light_power; \
\
effect_str[C] = .; \
\
C.update_lumcount \
( \
. * applied_lum_r, \
. * applied_lum_g, \
. * applied_lum_b \
);
// I don't need to explain what this does, do I?
#define REMOVE_CORNER(C) \
. = -effect_str[C]; \
C.update_lumcount \
( \
. * applied_lum_r, \
. * applied_lum_g, \
. * applied_lum_b \
);
// This is the define used to calculate falloff.
#define LUM_FALLOFF(C, T)(1 - CLAMP01(sqrt((C.x - T.x) ** 2 +(C.y - T.y) ** 2 + LIGHTING_HEIGHT) / max(1, light_range)))
/datum/light_source/proc/apply_lum()
var/static/update_gen = 1
applied = 1
// Keep track of the last applied lum values so that the lighting can be reversed
applied_lum_r = lum_r
applied_lum_g = lum_g
applied_lum_b = lum_b
FOR_DVIEW(var/turf/T, light_range, source_turf, INVISIBILITY_LIGHTING)
if(!T.lighting_corners_initialised)
T.generate_missing_corners()
for(var/datum/lighting_corner/C in T.get_corners())
if(C.update_gen == update_gen)
continue
C.update_gen = update_gen
C.affecting += src
if(!C.active)
effect_str[C] = 0
continue
APPLY_CORNER(C)
if(!T.affecting_lights)
T.affecting_lights = list()
T.affecting_lights += src
affecting_turfs += T
update_gen++
/datum/light_source/proc/remove_lum()
applied = FALSE
for(var/turf/T in affecting_turfs)
if(!T.affecting_lights)
T.affecting_lights = list()
else
T.affecting_lights -= src
affecting_turfs.Cut()
for(var/datum/lighting_corner/C in effect_str)
REMOVE_CORNER(C)
C.affecting -= src
effect_str.Cut()
/datum/light_source/proc/recalc_corner(var/datum/lighting_corner/C)
if(effect_str.Find(C)) // Already have one.
REMOVE_CORNER(C)
APPLY_CORNER(C)
/datum/light_source/proc/smart_vis_update()
var/list/datum/lighting_corner/corners = list()
var/list/turf/turfs = list()
FOR_DVIEW(var/turf/T, light_range, source_turf, 0)
if(!T.lighting_corners_initialised)
T.generate_missing_corners()
corners |= T.get_corners()
turfs += T
var/list/L = turfs - affecting_turfs // New turfs, add us to the affecting lights of them.
affecting_turfs += L
for(var/turf/T in L)
if(!T.affecting_lights)
T.affecting_lights = list(src)
else
T.affecting_lights += src
L = affecting_turfs - turfs // Now-gone turfs, remove us from the affecting lights.
affecting_turfs -= L
for(var/turf/T in L)
T.affecting_lights -= src
for(var/datum/lighting_corner/C in corners - effect_str) // New corners
C.affecting += src
if(!C.active)
effect_str[C] = 0
continue
APPLY_CORNER(C)
for(var/datum/lighting_corner/C in effect_str - corners) // Old, now gone, corners.
REMOVE_CORNER(C)
C.affecting -= src
effect_str -= C
#undef effect_update
#undef LUM_FALLOFF
#undef REMOVE_CORNER
#undef APPLY_CORNER
-25
View File
@@ -1,25 +0,0 @@
/var/list/lighting_update_lights = list()
/var/list/lighting_update_overlays = list()
/area/var/lighting_use_dynamic = 1
// duplicates lots of code, but this proc needs to be as fast as possible.
/proc/create_lighting_overlays(zlevel = 0)
var/area/A
if(zlevel == 0) // populate all zlevels
for(var/turf/T in world)
if(T.dynamic_lighting)
A = T.loc
if(A.lighting_use_dynamic)
var/atom/movable/lighting_overlay/O = new(T)
T.lighting_overlay = O
else
for(var/x = 1; x <= world.maxx; x++)
for(var/y = 1; y <= world.maxy; y++)
var/turf/T = locate(x, y, zlevel)
if(T.dynamic_lighting)
A = T.loc
if(A.lighting_use_dynamic)
var/atom/movable/lighting_overlay/O = new(T)
T.lighting_overlay = O
+89 -13
View File
@@ -1,26 +1,102 @@
/turf
var/list/affecting_lights
var/atom/movable/lighting_overlay/lighting_overlay
var/dynamic_lighting = TRUE
luminosity = 1
var/tmp/lighting_corners_initialised = FALSE
var/tmp/list/datum/light_source/affecting_lights // List of light sources affecting this turf.
var/tmp/atom/movable/lighting_overlay/lighting_overlay // Our lighting overlay.
var/tmp/list/datum/lighting_corner/corners
var/tmp/has_opaque_atom = FALSE // Not to be confused with opacity, this will be TRUE if there's any opaque atom on the tile.
/turf/New()
. = ..()
if(opacity)
has_opaque_atom = TRUE
// Causes any affecting light sources to be queued for a visibility update, for example a door got opened.
/turf/proc/reconsider_lights()
for(var/datum/light_source/L in affecting_lights)
L.vis_update()
/turf/proc/lighting_clear_overlays()
/turf/proc/lighting_clear_overlay()
if(lighting_overlay)
qdel(lighting_overlay)
/turf/proc/lighting_build_overlays()
for(var/datum/lighting_corner/C in corners)
C.update_active()
// Builds a lighting overlay for us, but only if our area is dynamic.
/turf/proc/lighting_build_overlay()
if(lighting_overlay)
return
var/area/A = loc
if(A.lighting_use_dynamic)
var/atom/movable/lighting_overlay/O = new(src)
lighting_overlay = O
// forcibly refresh lighting on this tile
// must make light forget tile or it wil remain
// convinced that it is already lighting it
for(var/datum/light_source/L in affecting_lights)
L.forget_turf(src)
L.vis_update()
if(A.dynamic_lighting)
if(!lighting_corners_initialised)
generate_missing_corners()
new /atom/movable/lighting_overlay(src)
for(var/datum/lighting_corner/C in corners)
if(!C.active) // We would activate the corner, calculate the lighting for it.
for(var/L in C.affecting)
var/datum/light_source/S = L
S.recalc_corner(C)
C.active = TRUE
// Used to get a scaled lumcount.
/turf/proc/get_lumcount(var/minlum = 0, var/maxlum = 1)
if(!lighting_overlay)
return 1
var/totallums = 0
for(var/datum/lighting_corner/L in corners)
totallums += max(L.lum_r, L.lum_g, L.lum_b)
totallums /= 4 // 4 corners, max channel selected, return the average
totallums =(totallums - minlum) /(maxlum - minlum)
return CLAMP01(totallums)
// Can't think of a good name, this proc will recalculate the has_opaque_atom variable.
/turf/proc/recalc_atom_opacity()
has_opaque_atom = FALSE
for(var/atom/A in src.contents + src) // Loop through every movable atom on our tile PLUS ourselves (we matter too...)
if(A.opacity)
has_opaque_atom = TRUE
// If an opaque movable atom moves around we need to potentially update visibility.
/turf/Entered(var/atom/movable/Obj, var/atom/OldLoc)
. = ..()
if(Obj && Obj.opacity)
has_opaque_atom = TRUE // Make sure to do this before reconsider_lights(), incase we're on instant updates. Guaranteed to be on in this case.
reconsider_lights()
/turf/Exited(var/atom/movable/Obj, var/atom/newloc)
. = ..()
if(Obj && Obj.opacity)
recalc_atom_opacity() // Make sure to do this before reconsider_lights(), incase we're on instant updates.
reconsider_lights()
/turf/proc/get_corners()
if(has_opaque_atom)
return null // Since this proc gets used in a for loop, null won't be looped though.
return corners
/turf/proc/generate_missing_corners()
lighting_corners_initialised = TRUE
if(!corners)
corners = list(null, null, null, null)
for(var/i = 1 to 4)
if(corners[i]) // Already have a corner on this direction.
continue
corners[i] = new /datum/lighting_corner(src, LIGHTING_CORNER_DIAGONAL[i])
+3 -5
View File
@@ -1,9 +1,7 @@
#undef LIGHTING_INTERVAL
#undef LIGHTING_FALLOFF
#undef LIGHTING_LAMBERTIAN
#undef LIGHTING_HEIGHT
#undef LIGHTING_RESOLUTION
#undef LIGHTING_LAYER
#undef LIGHTING_ICON
#undef LIGHTING_ICON
#undef LIGHTING_BASE_MATRIX
@@ -103,7 +103,7 @@
dir = possessed_item.dir
color = possessed_item.color
overlays = possessed_item.overlays
opacity = possessed_item.opacity
set_opacity(possessed_item.opacity)
visible_message("<span class='warning'>[src] rises into the air and begins to float!</span>") // Inform those around us that shit's gettin' spooky.
animate_ghostly_presence(src, -1, 20, 1)
+1 -1
View File
@@ -49,7 +49,7 @@
icon_state = "smatter"
temperature = T20C+80
density = 1
opacity = 1
set_opacity(1)
blocks_air = 1
spawn(2)