mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-07-21 04:48:18 +01:00
Replaces our lighting system with CM's. (#21465)
Depends on #21458. Ports https://github.com/cmss13-devs/cmss13/pull/4229, with the original authors as: - https://github.com/tgstation/TerraGov-Marine-Corps/pull/1964 for the lighting controller (A-lexa) - https://github.com/tgstation/TerraGov-Marine-Corps/pull/4747 and https://github.com/tgstation/TerraGov-Marine-Corps/pull/7263 for the lighting (TiviPlus) - https://github.com/tgstation/tgstation/pull/54520 for the dir lighting component - https://github.com/tgstation/tgstation/pull/75018 for the out of bounds fix in lighting - https://github.com/tgstation/TerraGov-Marine-Corps/pull/6678 for the emissives (TiviPlus) The main driving reason behind this is that current lighting consumes way too much processing power, especially for things like odysseys/away sites where a billion light sources are processing/moving at once and the game slows down to a crawl. Hopefully this improves the situation by a good margin, but we will need some testmerging to confirm that. <img width="1349" height="1349" alt="image" src="https://github.com/user-attachments/assets/1059ba2b-c0c5-495a-9c76-2d75d0c42bf2" /> <img width="1349" height="1349" alt="image" src="https://github.com/user-attachments/assets/9704b0f6-4cf6-4dfd-a6cb-5702ad07d677" /> - [x] Resolve todos - [x] Look into open space fuckery (border objects) --------- Co-authored-by: Matt Atlas <liermattia@gmail.com> Co-authored-by: JohnWildkins <john.wildkins@gmail.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# The Lighting Systems
|
||||
## Introduction
|
||||
|
||||
Hello reader, and welcome to the coders guide to lighting. TGMC uses three different lighting systems: Static Lighting, Movable Lighting and Hybrid Lighting
|
||||
These all have their pros and cons, and are explained later in this file.
|
||||
For now we will look at the frameworks we have pertaining to lighting.
|
||||
|
||||
```dm
|
||||
//Our vars:
|
||||
//The "intensity" of our light to determine how much it actually lights up
|
||||
var/light_power
|
||||
// The range of our light, aka how many turfs are actually lit up
|
||||
var/light_range
|
||||
//the color of our light
|
||||
var/light_color
|
||||
///whether the light is actually on or not, use /atom/proc/turn_on() to set this
|
||||
var/light_on
|
||||
```
|
||||
|
||||
Additionally, we have SSlighting, the lighting subsystem which handles lighting updates for Static and Hybrid lighting.
|
||||
This subsystems processes enqueued lighting object, corner and source updates, as well as taking enqueued hybrid lighting updates.
|
||||
You shouldnt really be touching this as it primarily just stops too many updates from happening at once.
|
||||
|
||||
## The Lighting systems
|
||||
As mentioned previously,lighting is split into three seperate systems who's functionality, benefits and downsides will be discussed below
|
||||
|
||||
Seperate from these systems we also have a system to update the base lighting of an area, we do this using "Base lighting"
|
||||
|
||||
```dm
|
||||
/area/proc/set_base_lighting(new_base_lighting_color = -1, new_alpha = -1)
|
||||
```
|
||||
Use this to set areas as required to luminosity. This is expensive-ish to apply/update but is very cheap to maintain. It also enables area specific light intensity and color changes.
|
||||
|
||||
### Static Lighting
|
||||
Static lighting consists of a single, static_lighting_source light source which gets all turfs in view, then tells their /datum/static_lighting_objects to update themselves. These lighting objects manage two things: lighting corners, and an underlay. The lighting corners hold data for the edges of turfs next to darkness to allow a smooth transition from dark to light, and the actual lighting is done using an underlay which is layered over the darkness layer in order to create light. Color is changed using a color matrix.
|
||||
The advantage of this system, is that it is cheap, as long as it does not need to actively update opacity changes or a moving light target.
|
||||
This system can also be used for as large lights as you want.
|
||||
The disadvantage however is that updating this type of light, such as when it moves is relatively expensive, and colors are not always the prettiest. Additionally, lighting corners are known to be a large source of RAM usage and thus you should only load lighting objects in areas hat it is needed using /area/var/static_lighting.
|
||||
Thus this should be your go to choice for large, frequent, immobile lights.
|
||||
|
||||
To update lights using this system use
|
||||
```dm
|
||||
/atom/proc/set_light(l_range, l_power, l_color, mask_type)
|
||||
```
|
||||
Note that the use of mask_type only is applicable to Hybrid lights.
|
||||
|
||||
### Movable lighting
|
||||
Movable lighting is extremely simple and cheap as it requires no updates. This is done by replacing a large amount of updating objects with one single, large vis_contents overlay, which we apply and manage through a component (/datum/component/overlay_lighting). This means that it will move smoothly when the owner moves and requires no updating, but also means that rendering issues might occur, where the overlay will seemingly "pop in" to existence as it suddenly renders when someone walks around a corner or into the 1/2-tile render buffer around the edges of the viewport.
|
||||
Thus this should be your go to ideal cheap light for small and mobile lights (NOT turfs or anchored objects!). This light also typically has more saturated colors than static lighting.
|
||||
|
||||
Note that this lighting type utilises special update procs from the other two lighting types, specifically
|
||||
```dm
|
||||
//update light variables
|
||||
/atom/movable/proc/set_light_range_power_color(range, power, color)
|
||||
set_light_range(range)
|
||||
set_light_power(power)
|
||||
set_light_color(color)
|
||||
|
||||
//turn the light on and off without changing any variables
|
||||
/atom/proc/set_light_on(new_value)
|
||||
```
|
||||
|
||||
### Hybrid lighting
|
||||
Hybrid lighting is, as the name implies, a hybrid of the two above systems. It still needs to update when the owner moves, or something in view changes like static lighting does, but uses overlays to hide areas using shadows. As a result, this has similar if not better performance to Static lighting, but has a higher drain on player GPU and thus you should ideally avoid lagging players that play on terrible computers too much. This means that you should use this lighting in decently sized lights that act as centerpieces for a scene (i.e. a fire, supermatter, etc.) since it combines the best of static and movable lighting at a clientside performance cost.
|
||||
As a rule of thumb most items will be fine using this except for light fixtures, as lag mostly seems to crop up from multiple large lighting sources.
|
||||
Using lights for turf based fires and large floodlights is thus fine, but be careful with frquesnt use.
|
||||
It functions by fetching all nearby blockers, then calculating triangles behind these blocked areas which it then masks with overlays.
|
||||
These overlays then render as an alpha mask blocking the light from appearing.
|
||||
This system also supports non-round lights, such as light cones, rotating lights, and shimmering lights through the use of
|
||||
```dm
|
||||
var/mask_type
|
||||
```
|
||||
which determines which type of icon we are going to use as the base when drawing this lights (/atom/movable/lighting_mask/flicker for shimmering lights as an example).
|
||||
|
||||
Actual updates however are handled through the same procs as Static lighting, and the mask_type argument on set_light() allows you to change the mask type that is being used on the fly.
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
-- Aurora directional lighting system, based off of /vg/lights. --
|
||||
|
||||
Documentation is present in most of the code files.
|
||||
lighting_atom.dm -> procs/vars for tracking/managing lights attached to objects.
|
||||
lighting_turf.dm -> procs/vars for managing lighting overlays bound to turfs, tracking lights affecting said turf, and getting information about the turf's light level.
|
||||
lighting_corner.dm -> contains code for tracking per-corner lighting data.
|
||||
lighting_source.dm -> contains actual light emitter datum & core lighting calculations. Directional lights and Z-lights are implemented here.
|
||||
lighting_source_novis.dm -> Same as lighting_source.dm, but does not take visibility into account - used for sun objects.
|
||||
lighting_profiler.dm -> contains code used for the diagnostic lighting profiler (currently disabled).
|
||||
lighting_area.dm -> contains area vars/procs for managing an area's dynamic lighting state.
|
||||
lighting_verbs.dm -> contains verbs for debugging lighting.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
Useful procs when using lights:
|
||||
|
||||
/atom/proc/set_light(range, power, color, uv_power, angle, no_update)
|
||||
desc: Sets an atom's light emission. `set_light(FALSE)` will disable the light.
|
||||
args:
|
||||
range -> the range of the light. 1.4 is the lowest possible value here.
|
||||
power -> the power (intensity) of the light. Generally should be 1 or lower. Optional.
|
||||
color -> The hex string (#FFFFFF) color of the light. Optional.
|
||||
uv_power -> The UV power of this light. Must be between 0 and 255. Optional.
|
||||
angle -> The angle of the cone that the light should shine at (directional lighting). Behavior of lights over 180 degrees is undefined. Best to stick to using the LIGHT_ defines for this. Optional.
|
||||
no_update -> if TRUE, the light will not be updated. Useful for when making several of these calls to the same object. Optional.
|
||||
|
||||
/atom/proc/set_opacity(new_opacity)
|
||||
desc: Sets an atom's opacity, updating affecting lights' visibility. Returns TRUE if opacity changed, FALSE otherwise.
|
||||
args:
|
||||
new_opacity -> the new opacity value.
|
||||
|
||||
/turf/proc/reconsider_lights()
|
||||
desc: Cause all lights affecting this turf to recalculate visibility.
|
||||
args: none
|
||||
|
||||
/turf/proc/force_update_lights()
|
||||
desc: Force all lights affecting this turf to regenerate. Slow, use reconsider_lights instead when possible.
|
||||
args: none
|
||||
|
||||
/turf/proc/get_avg_color()
|
||||
desc: Gets the average color of this tile as a hexadecimal color string. Used by cameras.
|
||||
|
||||
/turf/proc/get_lumcount(minlum = 0, maxlum = 1)
|
||||
desc: Gets the brightness of this tile. If not dynamically lit, always returns 0.5, otherwise returns the average brightness of all 4 corners, scaled between minlum and maxlum.
|
||||
args:
|
||||
minlum -> the low-bound of the scalar.
|
||||
maxlum -> the high-bound of the scalar.
|
||||
|
||||
/turf/proc/get_uv_lumcount(minlum = 0, maxlum = 1)
|
||||
desc: Same as above, but only considers UV light.
|
||||
args:
|
||||
minlum -> the low-bound of the scalar.
|
||||
maxlum -> the high-bound of the scalar.
|
||||
*/
|
||||
@@ -1,58 +0,0 @@
|
||||
// This is the define used to calculate falloff.
|
||||
#define LUM_FALLOFF(Cx,Cy,Tx,Ty,HEIGHT) (1 - CLAMP01(sqrt(((Cx) - (Tx)) ** 2 + ((Cy) - (Ty)) ** 2 + HEIGHT) / max(1, actual_range)))
|
||||
|
||||
// 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_SIMPLE(C) \
|
||||
. = light_power; \
|
||||
var/OLD = effect_str[C]; \
|
||||
effect_str[C] = .; \
|
||||
C.update_lumcount \
|
||||
( \
|
||||
(. * lum_r) - (OLD * applied_lum_r), \
|
||||
(. * lum_g) - (OLD * applied_lum_g), \
|
||||
(. * lum_b) - (OLD * applied_lum_b), \
|
||||
(. * lum_u) - (OLD * applied_lum_u), \
|
||||
FALSE \
|
||||
);
|
||||
|
||||
#define APPLY_CORNER(C,now,Tx,Ty,hdiff) \
|
||||
. = LUM_FALLOFF(C.x, C.y, Tx, Ty, hdiff) * light_power; \
|
||||
var/OLD = effect_str[C]; \
|
||||
effect_str[C] = .; \
|
||||
C.update_lumcount \
|
||||
( \
|
||||
(. * lum_r) - (OLD * applied_lum_r), \
|
||||
(. * lum_g) - (OLD * applied_lum_g), \
|
||||
(. * lum_b) - (OLD * applied_lum_b), \
|
||||
(. * lum_u) - (OLD * applied_lum_u), \
|
||||
now \
|
||||
);
|
||||
|
||||
// I don't need to explain what this does, do I?
|
||||
#define REMOVE_CORNER(C,now) \
|
||||
. = -effect_str[C]; \
|
||||
C.update_lumcount \
|
||||
( \
|
||||
. * applied_lum_r, \
|
||||
. * applied_lum_g, \
|
||||
. * applied_lum_b, \
|
||||
. * applied_lum_u, \
|
||||
now \
|
||||
);
|
||||
|
||||
// Converts two Z levels into a height value for LUM_FALLOFF or HEIGHT_FALLOFF.
|
||||
#define CALCULATE_CORNER_HEIGHT(ZA,ZB) (((max(ZA,ZB) - min(ZA,ZB)) + 1) * LIGHTING_HEIGHT * LIGHTING_Z_FACTOR)
|
||||
|
||||
#define APPLY_CORNER_BY_HEIGHT(now) \
|
||||
if (C.z != Sz) { \
|
||||
corner_height = CALCULATE_CORNER_HEIGHT(C.z, Sz); \
|
||||
} \
|
||||
else { \
|
||||
corner_height = LIGHTING_HEIGHT; \
|
||||
} \
|
||||
APPLY_CORNER(C, now, Sx, Sy, corner_height);
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Internal atom that copies an appearance on to the blocker plane
|
||||
*
|
||||
* Copies an appearance vis render_target and render_source on to the emissive blocking plane.
|
||||
* This means that the atom in question will block any emissive sprites.
|
||||
* This should only be used internally. If you are directly creating more of these, you're
|
||||
* almost guaranteed to be doing something wrong.
|
||||
*/
|
||||
/atom/movable/emissive_blocker
|
||||
name = "emissive blocker"
|
||||
plane = EMISSIVE_PLANE
|
||||
layer = FLOAT_LAYER
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
//Why?
|
||||
//render_targets copy the transform of the target as well, but vis_contents also applies the transform
|
||||
//to what's in it. Applying RESET_TRANSFORM here makes vis_contents not apply the transform.
|
||||
//Since only render_target handles transform we don't get any applied transform "stacking"
|
||||
appearance_flags = EMISSIVE_APPEARANCE_FLAGS
|
||||
|
||||
/atom/movable/emissive_blocker/Initialize(mapload, source)
|
||||
. = ..()
|
||||
verbs.Cut() //Cargo culting from lighting object, this maybe affects memory usage?
|
||||
|
||||
render_source = source
|
||||
color = GLOB.em_block_color
|
||||
|
||||
|
||||
/atom/movable/emissive_blocker/ex_act(severity)
|
||||
return FALSE
|
||||
|
||||
//Prevents people from moving these after creation, because they shouldn't be.
|
||||
/atom/movable/emissive_blocker/forceMove(atom/destination, no_tp=FALSE, harderforce = FALSE)
|
||||
if(harderforce)
|
||||
return ..()
|
||||
@@ -1,23 +1,59 @@
|
||||
/area
|
||||
luminosity = TRUE
|
||||
var/dynamic_lighting = TRUE
|
||||
luminosity = 1
|
||||
///The mutable appearance we underlay to show light
|
||||
var/mutable_appearance/lighting_effect = null
|
||||
///Whether this area has a currently active base lighting, bool
|
||||
var/area_has_base_lighting = FALSE
|
||||
///alpha 0-255 of lighting_effect and thus baselighting intensity
|
||||
var/base_lighting_alpha = 0
|
||||
///The colour of the light acting on this area
|
||||
var/base_lighting_color = COLOR_WHITE
|
||||
|
||||
/area/proc/set_dynamic_lighting(var/new_dynamic_lighting = TRUE)
|
||||
//L_PROF(src, "area_sdl")
|
||||
|
||||
if (new_dynamic_lighting == dynamic_lighting)
|
||||
/area/proc/set_base_lighting(new_base_lighting_color = -1, new_alpha = -1)
|
||||
if(base_lighting_alpha == new_alpha && base_lighting_color == new_base_lighting_color)
|
||||
return FALSE
|
||||
|
||||
dynamic_lighting = new_dynamic_lighting
|
||||
|
||||
if (new_dynamic_lighting)
|
||||
for (var/turf/T in src)
|
||||
if (T.dynamic_lighting)
|
||||
T.lighting_build_overlay()
|
||||
|
||||
else
|
||||
for (var/turf/T in src)
|
||||
if (T.lighting_overlay)
|
||||
T.lighting_clear_overlay()
|
||||
|
||||
if(new_alpha != -1)
|
||||
base_lighting_alpha = new_alpha
|
||||
if(new_base_lighting_color != -1)
|
||||
base_lighting_color = new_base_lighting_color
|
||||
update_base_lighting()
|
||||
return TRUE
|
||||
|
||||
/area/vv_edit_var(var_name, var_value)
|
||||
switch(var_name)
|
||||
if("base_lighting_color")
|
||||
set_base_lighting(new_base_lighting_color = var_value)
|
||||
return TRUE
|
||||
if("base_lighting_alpha")
|
||||
set_base_lighting(new_alpha = var_value)
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/area/proc/update_base_lighting()
|
||||
if(!area_has_base_lighting && (!base_lighting_alpha || !base_lighting_color))
|
||||
return
|
||||
|
||||
if(!area_has_base_lighting)
|
||||
add_base_lighting()
|
||||
return
|
||||
remove_base_lighting()
|
||||
if(base_lighting_alpha && base_lighting_color)
|
||||
add_base_lighting()
|
||||
|
||||
/area/proc/remove_base_lighting()
|
||||
for(var/turf/T in src)
|
||||
T.overlays -= lighting_effect
|
||||
QDEL_NULL(lighting_effect)
|
||||
area_has_base_lighting = FALSE
|
||||
|
||||
/area/proc/add_base_lighting()
|
||||
lighting_effect = mutable_appearance('icons/effects/alphacolors.dmi', "white")
|
||||
lighting_effect.plane = LIGHTING_PLANE
|
||||
lighting_effect.layer = LIGHTING_PRIMARY_LAYER
|
||||
lighting_effect.blend_mode = BLEND_ADD
|
||||
lighting_effect.alpha = base_lighting_alpha
|
||||
lighting_effect.color = base_lighting_color
|
||||
for(var/turf/T in src)
|
||||
T.overlays += lighting_effect
|
||||
T.luminosity = 1
|
||||
area_has_base_lighting = TRUE
|
||||
|
||||
@@ -1,123 +1,185 @@
|
||||
/atom
|
||||
var/light_power = 1 // Intensity of the light.
|
||||
var/light_range = 0 // Range in tiles of the light.
|
||||
var/light_color // Hexadecimal RGB string representing the colour of the light.
|
||||
var/uv_intensity = 255 // How much UV light is being emitted by this object. Valid range: 0-255.
|
||||
var/light_wedge // The angle that the light's emission should be restricted to. null for omnidirectional.
|
||||
#ifdef ENABLE_SUNLIGHT
|
||||
var/light_novis // If TRUE, visibility checks will be skipped when calculating this light.
|
||||
#endif
|
||||
|
||||
var/tmp/datum/light_source/light // Our light source. Don't fuck with this directly unless you have a good reason!
|
||||
var/tmp/list/light_sources // Any light sources that are "inside" of us, for example, if src here was a mob that's carrying a flashlight, that flashlight's light source would be part of this list.
|
||||
|
||||
// Nonesensical value for l_color default, so we can detect if it gets set to null.
|
||||
#define NONSENSICAL_VALUE -99999
|
||||
|
||||
// The proc you should always use to set the light of this atom.
|
||||
/atom/proc/set_light(var/l_range, var/l_power, var/l_color = NONSENSICAL_VALUE, var/uv = NONSENSICAL_VALUE, var/angle = NONSENSICAL_VALUE, var/no_update = FALSE)
|
||||
//L_PROF(src, "atom_setlight")
|
||||
. = FALSE // don't update if nothing changed
|
||||
|
||||
// Nonesensical 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, mask_type = null)
|
||||
if(l_range > 0 && l_range < MINIMUM_USEFUL_LIGHT_RANGE)
|
||||
l_range = MINIMUM_USEFUL_LIGHT_RANGE //Brings the range up to 1.4, which is just barely brighter than the soft lighting that surrounds players.
|
||||
. = TRUE
|
||||
if (l_power != null && light_power != l_power)
|
||||
|
||||
if(l_power != null && l_power != light_power)
|
||||
light_power = l_power
|
||||
. = TRUE
|
||||
if (l_range != null && light_range != l_range)
|
||||
|
||||
if(l_range != null && l_range != light_range)
|
||||
light_range = l_range
|
||||
light_on = (light_range>0) ? TRUE : FALSE
|
||||
. = TRUE
|
||||
|
||||
if (l_color != NONSENSICAL_VALUE && light_color != l_color)
|
||||
if(l_color != NONSENSICAL_VALUE && l_color != light_color)
|
||||
light_color = l_color
|
||||
. = TRUE
|
||||
|
||||
if (uv != NONSENSICAL_VALUE && uv_intensity != uv)
|
||||
set_uv(uv, no_update = TRUE)
|
||||
if(mask_type != null && mask_type != light_mask_type)
|
||||
light_mask_type = mask_type
|
||||
. = TRUE
|
||||
|
||||
if (angle != NONSENSICAL_VALUE && light_wedge != angle)
|
||||
light_wedge = angle
|
||||
. = TRUE
|
||||
|
||||
if(!no_update && .)
|
||||
if(.)
|
||||
update_light()
|
||||
|
||||
#undef NONSENSICAL_VALUE
|
||||
SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT, l_range, l_power, l_color)
|
||||
|
||||
/atom/proc/set_uv(var/intensity, var/no_update)
|
||||
//L_PROF(src, "atom_setuv")
|
||||
if (intensity < 0 || intensity > 255)
|
||||
intensity = min(max(intensity, 255), 0)
|
||||
|
||||
uv_intensity = intensity
|
||||
/atom/proc/fade_light(new_colour, time)
|
||||
light_color = new_colour
|
||||
if(light?.our_mask)
|
||||
animate(light.our_mask, color = new_colour, time = time)
|
||||
|
||||
if (no_update)
|
||||
return
|
||||
|
||||
update_light()
|
||||
|
||||
// Will update the light (duh).
|
||||
// Creates or destroys it if needed, makes it update values, makes sure it's got the correct source turf...
|
||||
/// Will update the light (duh).Creates or destroys it if needed, makes it update values, makes sure it's got the correct source turf...
|
||||
/atom/proc/update_light()
|
||||
SHOULD_NOT_SLEEP(TRUE)
|
||||
set waitfor = FALSE
|
||||
|
||||
if (QDELING(src))
|
||||
if(QDELETED(src))
|
||||
return
|
||||
if(light_system == STATIC_LIGHT)
|
||||
static_update_light()
|
||||
return
|
||||
|
||||
//L_PROF(src, "atom_update")
|
||||
|
||||
if (!light_power || !light_range) // We won't emit light anyways, destroy the light source.
|
||||
if((!light_power || !light_range) && light) // We won't emit light anyways, destroy the light source.
|
||||
QDEL_NULL(light)
|
||||
else
|
||||
. = get_light_atom()
|
||||
|
||||
#ifdef ENABLE_SUNLIGHT
|
||||
if (light) // Update the light or create it if it does not exist.
|
||||
light.update(.)
|
||||
else if (light_novis)
|
||||
light = new/datum/light_source/sunlight(src, .)
|
||||
else
|
||||
light = new/datum/light_source(src, .)
|
||||
#else
|
||||
if (light)
|
||||
light.update(.)
|
||||
else
|
||||
light = new /datum/light_source(src, .)
|
||||
#endif
|
||||
|
||||
/atom/proc/get_light_atom()
|
||||
if (!istype(loc, /atom/movable)) // We choose what atom should be the top atom of the light here.
|
||||
return src
|
||||
return loc
|
||||
return
|
||||
if(light && light_mask_type && (light_mask_type != light.mask_type))
|
||||
QDEL_NULL(light)
|
||||
if(!light) // Update the light or create it if it does not exist.
|
||||
light = new /datum/dynamic_light_source(src, light_mask_type)
|
||||
return
|
||||
light.set_light(light_range, light_power, light_color)
|
||||
light.update_position()
|
||||
|
||||
|
||||
// Should always be used to change the opacity of an atom.
|
||||
// It notifies (potentially) affected light sources so they can update (if needed).
|
||||
/atom/proc/set_opacity(var/new_opacity)
|
||||
if (new_opacity == opacity)
|
||||
return FALSE
|
||||
|
||||
//L_PROF(src, "atom_setopacity")
|
||||
/**
|
||||
* Updates the atom's opacity value.
|
||||
*
|
||||
* This exists to act as a hook for associated behavior.
|
||||
* It notifies (potentially) affected light sources so they can update (if needed).
|
||||
*/
|
||||
/atom/proc/set_opacity(new_opacity)
|
||||
if(new_opacity == opacity)
|
||||
return
|
||||
SEND_SIGNAL(src, COMSIG_ATOM_SET_OPACITY, new_opacity)
|
||||
. = opacity
|
||||
|
||||
opacity = new_opacity
|
||||
var/turf/T = loc
|
||||
if (!isturf(T))
|
||||
return FALSE
|
||||
|
||||
if (new_opacity == TRUE)
|
||||
T.has_opaque_atom = TRUE
|
||||
T.reconsider_lights()
|
||||
#ifdef AO_USE_LIGHTING_OPACITY
|
||||
T.regenerate_ao()
|
||||
#endif
|
||||
/atom/movable/set_opacity(new_opacity)
|
||||
. = ..()
|
||||
if(isnull(.) || !isturf(loc))
|
||||
return
|
||||
|
||||
if(opacity)
|
||||
AddElement(/datum/element/light_blocking)
|
||||
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()
|
||||
RemoveElement(/datum/element/light_blocking)
|
||||
|
||||
updateVisibility(src, FALSE)
|
||||
|
||||
return TRUE
|
||||
/turf/set_opacity(new_opacity)
|
||||
. = ..()
|
||||
if(isnull(.))
|
||||
return
|
||||
recalculate_directional_opacity()
|
||||
|
||||
/atom/vv_edit_var(var_name, var_value)
|
||||
switch(var_name)
|
||||
if("light_range")
|
||||
if(light_system != MOVABLE_LIGHT)
|
||||
set_light(l_range = var_value)
|
||||
else
|
||||
set_light_range(var_value)
|
||||
datum_flags |= DF_VAR_EDITED
|
||||
return TRUE
|
||||
|
||||
if("light_power")
|
||||
if(light_system != MOVABLE_LIGHT)
|
||||
set_light(l_power = var_value)
|
||||
else
|
||||
set_light_power(var_value)
|
||||
datum_flags |= DF_VAR_EDITED
|
||||
return TRUE
|
||||
|
||||
if("light_color")
|
||||
if(light_system != MOVABLE_LIGHT)
|
||||
set_light(l_color = var_value)
|
||||
else
|
||||
set_light_color(var_value)
|
||||
datum_flags |= DF_VAR_EDITED
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
|
||||
/atom/proc/flash_lighting_fx(
|
||||
_range = FLASH_LIGHT_RANGE,
|
||||
_power = FLASH_LIGHT_POWER,
|
||||
_color = COLOR_WHITE,
|
||||
_duration = FLASH_LIGHT_DURATION,
|
||||
_reset_lighting = TRUE,
|
||||
_flash_times = 1)
|
||||
new /obj/effect/light_flash(get_turf(src), _range, _power, _color, _duration, _flash_times)
|
||||
|
||||
|
||||
/obj/effect/light_flash/Initialize(mapload, _range = FLASH_LIGHT_RANGE, _power = FLASH_LIGHT_POWER, _color = COLOR_WHITE, _duration = FLASH_LIGHT_DURATION, _flash_times = 1)
|
||||
light_range = _range
|
||||
light_power = _power
|
||||
light_color = _color
|
||||
. = ..()
|
||||
do_flashes(_flash_times, _duration)
|
||||
|
||||
/obj/effect/light_flash/proc/do_flashes(_flash_times, _duration)
|
||||
set waitfor = FALSE
|
||||
for(var/i in 1 to _flash_times)
|
||||
//Something bad happened
|
||||
if(!(light?.our_mask))
|
||||
break
|
||||
light.our_mask.alpha = 255
|
||||
animate(light.our_mask, time = _duration, easing = SINE_EASING, alpha = 0, flags = ANIMATION_END_NOW)
|
||||
sleep(_duration) //this is extremely short so it's ok to sleep
|
||||
qdel(src)
|
||||
|
||||
/atom/proc/set_light_range(new_range)
|
||||
if(new_range == light_range)
|
||||
return
|
||||
SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_RANGE, new_range)
|
||||
. = light_range
|
||||
light_range = new_range
|
||||
|
||||
|
||||
/atom/proc/set_light_power(new_power)
|
||||
if(new_power == light_power)
|
||||
return
|
||||
SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_POWER, new_power)
|
||||
. = light_power
|
||||
light_power = new_power
|
||||
|
||||
|
||||
/atom/proc/set_light_color(new_color)
|
||||
if(new_color == light_color)
|
||||
return
|
||||
SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_COLOR, new_color)
|
||||
. = light_color
|
||||
light_color = new_color
|
||||
|
||||
|
||||
/atom/proc/set_light_on(new_value)
|
||||
if(new_value == light_on)
|
||||
return
|
||||
SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_ON, new_value)
|
||||
. = light_on
|
||||
light_on = new_value
|
||||
|
||||
|
||||
/// Setter for the light flags of this atom.
|
||||
/atom/proc/set_light_flags(new_value)
|
||||
if(new_value == light_flags)
|
||||
return
|
||||
SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_FLAGS, new_value)
|
||||
. = light_flags
|
||||
light_flags = new_value
|
||||
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
/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)
|
||||
|
||||
// This is the reverse of the above - the position in the array is a dir. Update this if the above changes.
|
||||
var/list/REVERSE_LIGHTING_CORNER_DIAGONAL = list(0, 0, 0, 0, 3, 4, 0, 0, 2, 1)
|
||||
|
||||
/datum/lighting_corner
|
||||
var/turf/t1 // These are in no particular order.
|
||||
var/t1i // Our index in this turf's corners list.
|
||||
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
|
||||
|
||||
var/lum_r = 0
|
||||
var/lum_g = 0
|
||||
var/lum_b = 0
|
||||
var/lum_u = 0 // UV Radiation, not visible.
|
||||
|
||||
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
|
||||
|
||||
/datum/lighting_corner/New(turf/new_turf, diagonal)
|
||||
SSlighting.lighting_corners += src
|
||||
|
||||
t1 = new_turf
|
||||
z = new_turf.z
|
||||
t1i = REVERSE_LIGHTING_CORNER_DIAGONAL[diagonal]
|
||||
|
||||
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)
|
||||
|
||||
t2 = T
|
||||
i = REVERSE_LIGHTING_CORNER_DIAGONAL[diagonal]
|
||||
t2i = i
|
||||
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)
|
||||
|
||||
t3 = T
|
||||
i = REVERSE_LIGHTING_CORNER_DIAGONAL[((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH)] // Get the dir based on coordinates.
|
||||
t3i = i
|
||||
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)
|
||||
|
||||
t4 = T
|
||||
i = REVERSE_LIGHTING_CORNER_DIAGONAL[((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH)] // Get the dir based on coordinates.
|
||||
t4i = i
|
||||
T.corners[i] = src
|
||||
|
||||
update_active()
|
||||
|
||||
#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
|
||||
|
||||
// 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, delta_u, now = FALSE)
|
||||
if (!(delta_r + delta_g + delta_b)) // Don't check u since the overlay doesn't care about it.
|
||||
return
|
||||
|
||||
lum_r += delta_r
|
||||
lum_g += delta_g
|
||||
lum_b += delta_b
|
||||
lum_u += delta_u
|
||||
|
||||
// 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)
|
||||
|
||||
#define UPDATE_MASTER(T) \
|
||||
if (T && T.lighting_overlay) { \
|
||||
if (now) { \
|
||||
T.lighting_overlay.update_overlay(); \
|
||||
} \
|
||||
else if (!T.lighting_overlay.needs_update) { \
|
||||
T.lighting_overlay.needs_update = TRUE; \
|
||||
SSlighting.overlay_queue += T.lighting_overlay; \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
#define AVERAGE_BELOW_CORNER(Tt, Ti) \
|
||||
if (TURF_IS_MIMICING(Tt)) { \
|
||||
T = GET_TURF_BELOW(Tt); \
|
||||
if (T && T.corners && TURF_IS_DYNAMICALLY_LIT_UNSAFE(T)) { \
|
||||
C = T.corners[Ti]; \
|
||||
if (C) { \
|
||||
divisor += 1; \
|
||||
lr += C.lum_r; \
|
||||
lg += C.lum_g; \
|
||||
lb += C.lum_b; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define UPDATE_ABOVE_CORNER(Tt, Ti) \
|
||||
if (Tt) { \
|
||||
T = GET_TURF_ABOVE(Tt); \
|
||||
if (TURF_IS_MIMICING(T) && TURF_IS_DYNAMICALLY_LIT_UNSAFE(T)) { \
|
||||
if (!T.corners) { \
|
||||
T.generate_missing_corners(); \
|
||||
} \
|
||||
C = T.corners[Ti]; \
|
||||
if (C && !C.needs_update) { \
|
||||
C.update_overlays(FALSE); \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
/datum/lighting_corner/proc/update_overlays(now = FALSE)
|
||||
var/lr = lum_r
|
||||
var/lg = lum_g
|
||||
var/lb = lum_b
|
||||
|
||||
#ifdef USE_CORNER_ZBLEED
|
||||
|
||||
var/divisor = 1
|
||||
var/datum/lighting_corner/C
|
||||
var/turf/T
|
||||
|
||||
AVERAGE_BELOW_CORNER(t1, t1i)
|
||||
AVERAGE_BELOW_CORNER(t2, t2i)
|
||||
AVERAGE_BELOW_CORNER(t3, t3i)
|
||||
AVERAGE_BELOW_CORNER(t4, t4i)
|
||||
|
||||
if (divisor > 1)
|
||||
lr /= divisor
|
||||
lg /= divisor
|
||||
lb /= divisor
|
||||
|
||||
#endif
|
||||
|
||||
// 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
|
||||
|
||||
else if (mx < LIGHTING_SOFT_THRESHOLD)
|
||||
. = 0 // 0 means soft lighting.
|
||||
|
||||
if (.)
|
||||
cache_r = round(lr * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
|
||||
cache_g = round(lg * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
|
||||
cache_b = round(lb * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
|
||||
else
|
||||
cache_r = cache_g = cache_b = LIGHTING_SOFT_THRESHOLD
|
||||
|
||||
cache_mx = round(mx, LIGHTING_ROUND_VALUE)
|
||||
|
||||
UPDATE_MASTER(t1)
|
||||
UPDATE_MASTER(t2)
|
||||
UPDATE_MASTER(t3)
|
||||
UPDATE_MASTER(t4)
|
||||
|
||||
#ifdef USE_CORNER_ZBLEED
|
||||
|
||||
UPDATE_ABOVE_CORNER(t1, t1i)
|
||||
UPDATE_ABOVE_CORNER(t2, t2i)
|
||||
UPDATE_ABOVE_CORNER(t3, t3i)
|
||||
UPDATE_ABOVE_CORNER(t4, t4i)
|
||||
|
||||
#endif
|
||||
|
||||
#undef UPDATE_MASTER
|
||||
#undef AVERAGE_BELOW_CORNER
|
||||
#undef UPDATE_ABOVE_CORNER
|
||||
|
||||
/datum/lighting_corner/Destroy(force = FALSE)
|
||||
crash_with("Some fuck [force ? "force-" : ""]deleted a lighting corner.")
|
||||
if (!force)
|
||||
return QDEL_HINT_LETMELIVE
|
||||
|
||||
SSlighting.lighting_corners -= src
|
||||
return ..()
|
||||
|
||||
/datum/lighting_corner/dummy/New()
|
||||
return
|
||||
@@ -0,0 +1,103 @@
|
||||
// This is where the fun begins.
|
||||
// These are the main datums that emit light.
|
||||
|
||||
/datum/dynamic_light_source
|
||||
///source atom that we belong to
|
||||
var/atom/source_atom
|
||||
///The atom that the source atom is contained inside
|
||||
var/atom/movable/contained_atom
|
||||
///our last loc
|
||||
var/atom/cached_loc
|
||||
//the turf where cached loc was
|
||||
var/turf/source_turf
|
||||
///the turf the contained atom appears to be covering
|
||||
var/turf/pixel_turf
|
||||
/// Intensity of the emitter light.
|
||||
var/light_power = 0
|
||||
/// The range of the emitted light.
|
||||
var/light_range = 0
|
||||
/// The colour of the light, string, decomposed by PARSE_LIGHT_COLOR()
|
||||
var/light_color = NONSENSICAL_VALUE
|
||||
|
||||
/// Whether we have applied our light yet or not.
|
||||
var/applied = FALSE
|
||||
|
||||
///typepath for the mask type we are using
|
||||
var/mask_type
|
||||
///reference to the mask holder effect
|
||||
var/obj/effect/lighting_mask_holder/mask_holder
|
||||
///reference to the mask contained within the mask_holder objects vis_contents
|
||||
var/atom/movable/lighting_mask/our_mask
|
||||
|
||||
/datum/dynamic_light_source/New(atom/movable/owner, mask_type = /atom/movable/lighting_mask)
|
||||
source_atom = owner // Set our new owner.
|
||||
LAZYADD(source_atom.hybrid_light_sources, src)
|
||||
|
||||
//Find the atom that contains us
|
||||
find_containing_atom()
|
||||
|
||||
source_turf = get_turf(source_atom)
|
||||
|
||||
src.mask_type = mask_type
|
||||
mask_holder = new(source_turf)
|
||||
our_mask = new mask_type
|
||||
mask_holder.assign_mask(our_mask)
|
||||
our_mask.attached_atom = owner
|
||||
|
||||
//Set light vars
|
||||
set_light(owner.light_range, owner.light_power, owner.light_color)
|
||||
|
||||
//Calculate shadows
|
||||
our_mask.queue_mask_update()
|
||||
|
||||
//Set direction
|
||||
our_mask.rotate_mask_on_holder_turn(contained_atom.dir)
|
||||
RegisterSignal(our_mask, COMSIG_ATOM_DIR_CHANGE, TYPE_PROC_REF(/atom/movable/lighting_mask, rotate_mask_on_holder_turn))
|
||||
|
||||
/datum/dynamic_light_source/Destroy(force)
|
||||
//Remove references to ourself.
|
||||
LAZYREMOVE(source_atom?.hybrid_light_sources, src)
|
||||
LAZYREMOVE(contained_atom?.hybrid_light_sources, src)
|
||||
QDEL_NULL(mask_holder)
|
||||
our_mask = null//deletion handled on holder
|
||||
return ..()
|
||||
|
||||
///Updates containing atom
|
||||
/datum/dynamic_light_source/proc/find_containing_atom()
|
||||
//Remove ourselves from the old containing atoms light sources
|
||||
if(contained_atom && contained_atom != source_atom)
|
||||
LAZYREMOVE(contained_atom.hybrid_light_sources, src)
|
||||
//Find our new container
|
||||
if(isturf(source_atom) || isarea(source_atom))
|
||||
contained_atom = source_atom
|
||||
return
|
||||
contained_atom = source_atom.loc
|
||||
for(var/sanity in 1 to 20)
|
||||
if(!contained_atom)
|
||||
//Welcome to nullspace my friend.
|
||||
contained_atom = source_atom
|
||||
return
|
||||
if(isturf(contained_atom.loc))
|
||||
break
|
||||
contained_atom = contained_atom.loc
|
||||
//Add ourselves to their light sources
|
||||
if(contained_atom != source_atom)
|
||||
LAZYADD(contained_atom.hybrid_light_sources, src)
|
||||
|
||||
///Update light if changed.
|
||||
/datum/dynamic_light_source/proc/set_light(l_range, l_power, l_color = NONSENSICAL_VALUE)
|
||||
if(!our_mask)
|
||||
return
|
||||
if(l_range && l_range != light_range)
|
||||
light_range = l_range
|
||||
our_mask.set_radius(l_range)
|
||||
if(l_power && l_power != light_power)
|
||||
light_power = l_power
|
||||
our_mask.set_intensity(l_power)
|
||||
if(l_color != NONSENSICAL_VALUE && l_color != light_color)
|
||||
light_color = l_color
|
||||
our_mask.set_color(l_color)
|
||||
|
||||
/datum/dynamic_light_source/proc/update_position()
|
||||
mask_holder.forceMove(get_turf(source_atom))
|
||||
find_containing_atom()
|
||||
@@ -0,0 +1,172 @@
|
||||
///Lighting mask sprite radius in tiles
|
||||
#define LIGHTING_MASK_RADIUS 4
|
||||
///Lighting mask sprite diameter in pixels
|
||||
#define LIGHTING_MASK_SPRITE_SIZE LIGHTING_MASK_RADIUS * 64
|
||||
|
||||
/atom/movable/lighting_mask
|
||||
name = ""
|
||||
icon = LIGHTING_ICON_BIG
|
||||
icon_state = "light_normalized"
|
||||
|
||||
anchored = TRUE
|
||||
plane = LIGHTING_PLANE
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
layer = LIGHTING_SECONDARY_LAYER
|
||||
invisibility = INVISIBILITY_LIGHTING
|
||||
blend_mode = BLEND_ADD
|
||||
appearance_flags = KEEP_TOGETHER|RESET_TRANSFORM
|
||||
|
||||
///The current angle the item is pointing at
|
||||
var/current_angle = 0
|
||||
|
||||
///The radius of illumination of the mask
|
||||
var/radius = 0
|
||||
|
||||
///The atom that we are attached to, does not need hard del protection as we are deleted with it
|
||||
var/atom/attached_atom
|
||||
|
||||
///Reference to the holder /obj/effect
|
||||
var/obj/effect/lighting_mask_holder/mask_holder
|
||||
|
||||
///Prevents us from registering for update twice before SSlighting init
|
||||
var/awaiting_update = FALSE
|
||||
///Set to TRUE if you want the light to rotate with the owner
|
||||
var/is_directional = FALSE
|
||||
|
||||
///Turfs that are being affected by this mask, this is for the sake of luminosity
|
||||
var/list/turf/affecting_turfs
|
||||
///list of mutable appearance shadows
|
||||
var/list/mutable_appearance/shadows
|
||||
var/times_calculated = 0
|
||||
|
||||
//Please dont change these
|
||||
var/calculated_position_x
|
||||
var/calculated_position_y
|
||||
|
||||
/atom/movable/lighting_mask/Initialize(mapload, ...)
|
||||
. = ..()
|
||||
add_filter("pixel_smoother", 3, gauss_blur_filter(2))
|
||||
add_filter("shadow_alpha_masking", 4, alpha_mask_filter(render_source = SHADOW_RENDER_TARGET, flags = MASK_INVERSE))
|
||||
|
||||
/atom/movable/lighting_mask/Destroy()
|
||||
//Make sure we werent destroyed in init
|
||||
SSlighting.mask_queue -= src
|
||||
//Remove from affecting turfs
|
||||
if(affecting_turfs)
|
||||
for(var/turf/thing as anything in affecting_turfs)
|
||||
var/area/A = thing.loc
|
||||
LAZYREMOVE(thing.hybrid_lights_affecting, src)
|
||||
if(!A.base_lighting_alpha)
|
||||
thing.luminosity -= 1
|
||||
affecting_turfs = null
|
||||
//Cut the shadows. Since they are overlays they will be deleted when cut from overlays.
|
||||
LAZYCLEARLIST(shadows)
|
||||
mask_holder = null
|
||||
attached_atom = null
|
||||
return ..()
|
||||
|
||||
///Sets the radius of the mask, and updates everything that needs to be updated
|
||||
/atom/movable/lighting_mask/proc/set_radius(new_radius, transform_time = 0)
|
||||
//Update our matrix
|
||||
var/matrix/new_size_matrix = get_matrix(new_radius)
|
||||
apply_matrix(new_size_matrix, transform_time)
|
||||
radius = new_radius
|
||||
//then recalculate and redraw
|
||||
queue_mask_update()
|
||||
|
||||
///if you want the matrix to grow or shrink, you can do that using this proc when applyng it
|
||||
/atom/movable/lighting_mask/proc/apply_matrix(matrix/to_apply, transform_time = 0)
|
||||
if(transform_time)
|
||||
animate(src, transform = to_apply, time = transform_time)
|
||||
else
|
||||
transform = to_apply
|
||||
|
||||
///Creates a matrix for the lighting mak to use
|
||||
/atom/movable/lighting_mask/proc/get_matrix(radius = 1)
|
||||
var/matrix/new_size_matrix = new()
|
||||
//Scale
|
||||
// - Scale to the appropriate radius
|
||||
new_size_matrix.Scale(radius / LIGHTING_MASK_RADIUS)
|
||||
//Translate
|
||||
// - Center the overlay image
|
||||
// - Ok so apparently translate is affected by the scale we already did huh.
|
||||
// ^ Future me here, its because it works as translate then scale since its backwards.
|
||||
// ^ ^ Future future me here, it totally shouldnt since the translation component of a matrix is independent to the scale component.
|
||||
new_size_matrix.Translate(-128 + 16)
|
||||
//Adjust for pixel offsets
|
||||
var/invert_offsets = attached_atom.dir & (NORTH | EAST)
|
||||
var/left_or_right = attached_atom.dir & (EAST | WEST)
|
||||
var/offset_x = (left_or_right ? attached_atom.light_pixel_y : attached_atom.light_pixel_x) * (invert_offsets ? -1 : 1)
|
||||
var/offset_y = (left_or_right ? attached_atom.light_pixel_x : attached_atom.light_pixel_y) * (invert_offsets ? -1 : 1)
|
||||
new_size_matrix.Translate(offset_x, offset_y)
|
||||
if(is_directional)
|
||||
//Rotate
|
||||
// - Rotate (Directional lights)
|
||||
new_size_matrix.Turn(current_angle)
|
||||
return new_size_matrix
|
||||
|
||||
///Rotates the light source to angle degrees.
|
||||
/atom/movable/lighting_mask/proc/rotate(angle = 0)
|
||||
//Converting our transform is pretty simple.
|
||||
var/matrix/rotated_matrix = matrix()
|
||||
rotated_matrix.Turn(angle - current_angle)
|
||||
rotated_matrix *= transform
|
||||
//Overlays cannot be edited while applied, meaning their transform cannot be changed.
|
||||
//Disconnect the shadows from the overlay, apply the transform and then reapply them as an overlay.
|
||||
//Oh also since the matrix is really weird standard rotation matrices wont work here.
|
||||
overlays.Cut()
|
||||
//Disconnect from parent matrix, become a global position
|
||||
for(var/mutable_appearance/shadow as anything in shadows) //Mutable appearances are children of icon
|
||||
shadow.transform *= transform
|
||||
shadow.transform /= rotated_matrix
|
||||
//Apply our matrix
|
||||
transform = rotated_matrix
|
||||
overlays += shadows
|
||||
|
||||
//Now we are facing this direction
|
||||
current_angle = angle
|
||||
|
||||
///Setter proc for colors
|
||||
/atom/movable/lighting_mask/proc/set_color(colour = "#ffffff")
|
||||
color = colour
|
||||
|
||||
///Setter proc for the intensity of the mask
|
||||
/atom/movable/lighting_mask/proc/set_intensity(intensity = 1)
|
||||
if(intensity >= 0)
|
||||
alpha = ALPHA_TO_INTENSITY(intensity)
|
||||
blend_mode = BLEND_ADD
|
||||
else
|
||||
alpha = ALPHA_TO_INTENSITY(-intensity)
|
||||
blend_mode = BLEND_SUBTRACT
|
||||
|
||||
///The holder atom turned, spins the mask if it's needed
|
||||
/atom/movable/lighting_mask/proc/rotate_mask_on_holder_turn(new_direction)
|
||||
SIGNAL_HANDLER
|
||||
rotate(dir2angle(new_direction) - 180)
|
||||
|
||||
///Flickering lighting mask
|
||||
/atom/movable/lighting_mask/flicker
|
||||
icon_state = "light_flicker"
|
||||
|
||||
///Conical Light mask
|
||||
/atom/movable/lighting_mask/conical
|
||||
icon_state = "light_conical"
|
||||
is_directional = TRUE
|
||||
|
||||
///Rotating Light mask
|
||||
/atom/movable/lighting_mask/rotating
|
||||
icon_state = "light_rotating-1"
|
||||
|
||||
/atom/movable/lighting_mask/rotating/Initialize(mapload, ...)
|
||||
. = ..()
|
||||
icon_state = "light_rotating-[rand(1, 3)]"
|
||||
|
||||
///rotating light mask, but only pointing in one direction
|
||||
/atom/movable/lighting_mask/rotating_conical
|
||||
icon_state = "light_conical_rotating"
|
||||
|
||||
/atom/movable/lighting_mask/ex_act(severity, target)
|
||||
return
|
||||
|
||||
#undef LIGHTING_MASK_SPRITE_SIZE
|
||||
#undef LIGHTING_MASK_RADIUS
|
||||
@@ -0,0 +1,22 @@
|
||||
///Holder for lighting mask, this is done for ensuing correct render as a viscontents
|
||||
/obj/effect/lighting_mask_holder
|
||||
name = ""
|
||||
anchored = TRUE
|
||||
appearance_flags = NONE //Removes TILE_BOUND meaning that the lighting mask will be visible even if the source turf is not.
|
||||
glide_size = INFINITY //prevent shadow jitter
|
||||
///The movable mask this holder is holding in its vis contents
|
||||
var/atom/movable/lighting_mask/held_mask
|
||||
|
||||
/obj/effect/lighting_mask_holder/proc/assign_mask(atom/movable/lighting_mask/mask)
|
||||
vis_contents += mask
|
||||
held_mask = mask
|
||||
mask.mask_holder = src
|
||||
|
||||
/obj/effect/lighting_mask_holder/Destroy(force)
|
||||
vis_contents -= held_mask
|
||||
QDEL_NULL(held_mask)
|
||||
return ..()
|
||||
|
||||
/obj/effect/lighting_mask_holder/Moved(atom/OldLoc, Dir)
|
||||
. = ..()
|
||||
held_mask?.queue_mask_update()//held mask can be null when it is deleted
|
||||
@@ -0,0 +1,662 @@
|
||||
//Lighting texture scales in world units (divide by 32)
|
||||
//256 = 8,4,2
|
||||
//1024 = 32,16,8
|
||||
#define LIGHTING_SHADOW_TEX_SIZE 8
|
||||
|
||||
///Eyeball number for radius based offsets do not touch
|
||||
#define RADIUS_BASED_OFFSET 3.5
|
||||
|
||||
///Inserts a coord list into a grouped list
|
||||
#define COORD_LIST_ADD(listtoadd, x, y) \
|
||||
if(islist(listtoadd["[x]"])) { \
|
||||
var/list/_L = listtoadd["[x]"]; \
|
||||
BINARY_INSERT_NUM(y, _L); \
|
||||
} else { \
|
||||
listtoadd["[x]"] = list(y);\
|
||||
}
|
||||
|
||||
#ifdef SHADOW_DEBUG
|
||||
///Color coded atom debug, note will break when theres planetside lgihting
|
||||
#define DEBUG_HIGHLIGHT(x, y, colour) \
|
||||
do { \
|
||||
var/turf/T = locate(x, y, 3); \
|
||||
if(T) { \
|
||||
T.color = colour; \
|
||||
}\
|
||||
} while (FALSE)
|
||||
|
||||
//For debugging use when we want to know if a turf is being affected multiple times
|
||||
//#define DEBUG_HIGHLIGHT(x, y, colour) do{var/turf/T=locate(x,y,2);if(T){switch(T.color){if("#ff0000"){T.color = "#00ff00"}if("#00ff00"){T.color="#0000ff"}else{T.color="#ff0000"}}}}while(0)
|
||||
#define DO_SOMETHING_IF_DEBUGGING_SHADOWS(something) something
|
||||
#else
|
||||
#define DEBUG_HIGHLIGHT(x, y, colour)
|
||||
#define DO_SOMETHING_IF_DEBUGGING_SHADOWS(something)
|
||||
#endif
|
||||
|
||||
/atom/movable/lighting_mask/proc/link_turf_to_light(turf/T)
|
||||
LAZYOR(affecting_turfs, T)
|
||||
LAZYOR(T.hybrid_lights_affecting, src)
|
||||
|
||||
/atom/movable/lighting_mask/proc/unlink_turf_from_light(turf/T)
|
||||
LAZYREMOVE(affecting_turfs, T)
|
||||
LAZYREMOVE(T.hybrid_lights_affecting, src)
|
||||
|
||||
///Enqueues the mask in the queue properly
|
||||
/atom/movable/lighting_mask/proc/queue_mask_update()
|
||||
SSlighting.mask_queue |= src
|
||||
awaiting_update = TRUE
|
||||
|
||||
/**
|
||||
* Returns a list of matrices corresponding to the matrices that should be applied to triangles of
|
||||
* coordinates (0,0),(1,0),(0,1) to create a triangcalculate_shadows_matricesle that respresents the shadows
|
||||
* takes in the old turf to smoothly animate shadow movement
|
||||
*/
|
||||
/atom/movable/lighting_mask/proc/calculate_lighting_shadows()
|
||||
//Check to make sure lighting is actually started
|
||||
//If not count the amount of duplicate requests created.
|
||||
if(!SSlighting.started)
|
||||
if(awaiting_update)
|
||||
SSlighting.duplicate_shadow_updates_in_init++
|
||||
return
|
||||
queue_mask_update()
|
||||
return
|
||||
awaiting_update = FALSE
|
||||
//we moved to nullspace meanwhile dont bother
|
||||
if(!attached_atom.loc)
|
||||
return
|
||||
|
||||
//Incremement the global counter for shadow calculations
|
||||
SSlighting.total_shadow_calculations ++
|
||||
|
||||
//Ceiling the range since we need it in integer form
|
||||
var/range = ceil(radius)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/timer = TICK_USAGE)
|
||||
|
||||
//Work out our position
|
||||
//Calculate shadow origin offset
|
||||
var/invert_offsets = attached_atom.dir & (NORTH | EAST)
|
||||
var/left_or_right = attached_atom.dir & (EAST | WEST)
|
||||
var/offset_x = (left_or_right ? attached_atom.light_pixel_y : attached_atom.light_pixel_x) * (invert_offsets ? -1 : 1)
|
||||
var/offset_y = (left_or_right ? attached_atom.light_pixel_x : attached_atom.light_pixel_y) * (invert_offsets ? -1 : 1)
|
||||
|
||||
//Get the origin points
|
||||
var/turf/our_turf = get_turf(attached_atom) //The mask is in nullspace, so we need the source turf of the container
|
||||
|
||||
//Account for pixel shifting and light offset
|
||||
calculated_position_x = our_turf.x + ((offset_x) / world.icon_size)
|
||||
calculated_position_y = our_turf.y + ((offset_y) / world.icon_size)
|
||||
|
||||
//Remove the old shadows
|
||||
overlays.Cut()
|
||||
|
||||
|
||||
//Reset the list
|
||||
if(islist(affecting_turfs))
|
||||
for(var/turf/T as anything in affecting_turfs)
|
||||
LAZYREMOVE(T?.hybrid_lights_affecting, src)
|
||||
//The turf is no longer affected by any lights, make it non-luminous.
|
||||
var/area/A = T.loc
|
||||
if(T?.luminosity && !A.base_lighting_alpha)
|
||||
T.luminosity -= 1
|
||||
|
||||
//Clear the list
|
||||
LAZYCLEARLIST(affecting_turfs)
|
||||
LAZYCLEARLIST(shadows)
|
||||
|
||||
//Optimise grouping by storing as
|
||||
// Key : x (AS A STRING BECAUSE BYOND DOESNT ALLOW FOR INT KEY DICTIONARIES)
|
||||
// Value: List(y values)
|
||||
var/list/opaque_atoms_in_view = list()
|
||||
|
||||
//Rebuild the list
|
||||
var/is_on_closed_turf = our_turf.density
|
||||
var/list/turfs = list()
|
||||
DVIEW(turfs, range, get_turf(attached_atom), INVISIBILITY_LIGHTING)
|
||||
for(var/turf/thing in turfs) //most expensive part of shadow code is this DVIEW and group_atoms
|
||||
link_turf_to_light(thing)
|
||||
//The turf is now affected by our light, make it luminous
|
||||
thing.luminosity += 1
|
||||
//Dont consider shadows about our turf.
|
||||
if(!is_on_closed_turf)
|
||||
if(thing == our_turf)
|
||||
continue
|
||||
if(thing.directional_opacity)
|
||||
//At this point we no longer care about
|
||||
//the atom itself, only the position values
|
||||
COORD_LIST_ADD(opaque_atoms_in_view, thing.x, thing.y)
|
||||
DEBUG_HIGHLIGHT(thing.x, thing.y, "#0000FF")
|
||||
|
||||
//We are too small to consider shadows on, luminsoty has been considered at least.
|
||||
if(radius < 2)
|
||||
return
|
||||
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("[TICK_USAGE_TO_MS(timer)]ms to process view([range], src)."))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/temp_timer = TICK_USAGE)
|
||||
|
||||
//Group atoms together for optimisation
|
||||
var/list/grouped_atoms = group_atoms(opaque_atoms_in_view)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("[TICK_USAGE_TO_MS(temp_timer)]ms to process group_atoms"))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/total_coordgroup_time = 0)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/total_cornergroup_time = 0)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/triangle_time = 0)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/culling_time = 0)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/triangle_to_matrix_time = 0)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/matrix_division_time = 0)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/MA_new_time = 0)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/MA_vars_time = 0)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/overlays_add_time = 0)
|
||||
|
||||
var/list/overlays_to_add = list()
|
||||
for(var/group in grouped_atoms)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
|
||||
|
||||
var/list/coordgroup = calculate_corners_in_group(group)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(total_coordgroup_time += TICK_USAGE_TO_MS(temp_timer))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
|
||||
|
||||
//This is where the lines are made
|
||||
var/list/cornergroup = get_corners_from_coords(coordgroup)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(total_cornergroup_time += TICK_USAGE_TO_MS(temp_timer))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
|
||||
|
||||
var/list/culledlinegroup = cull_blocked_in_group(cornergroup, opaque_atoms_in_view)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(culling_time += TICK_USAGE_TO_MS(temp_timer))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
|
||||
|
||||
if(!LAZYLEN(culledlinegroup))
|
||||
continue
|
||||
|
||||
var/list/triangles = calculate_triangle_vertices(culledlinegroup)
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(triangle_time += TICK_USAGE_TO_MS(temp_timer))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
|
||||
|
||||
for(var/triangle in triangles)
|
||||
var/matrix/triangle_matrix = triangle_to_matrix(triangle)
|
||||
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(triangle_to_matrix_time += TICK_USAGE_TO_MS(temp_timer))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
|
||||
|
||||
triangle_matrix /= transform
|
||||
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(matrix_division_time += TICK_USAGE_TO_MS(temp_timer))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
|
||||
|
||||
var/mutable_appearance/shadow = new()
|
||||
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(MA_new_time += TICK_USAGE_TO_MS(temp_timer))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
|
||||
|
||||
shadow.icon = LIGHTING_ICON_BIG
|
||||
shadow.icon_state = "triangle"
|
||||
shadow.color = "#000"
|
||||
shadow.transform = triangle_matrix
|
||||
shadow.render_target = SHADOW_RENDER_TARGET
|
||||
shadow.blend_mode = BLEND_OVERLAY
|
||||
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(MA_vars_time += TICK_USAGE_TO_MS(temp_timer))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
|
||||
|
||||
LAZYADD(shadows, shadow)
|
||||
overlays_to_add += shadow
|
||||
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(overlays_add_time += TICK_USAGE_TO_MS(temp_timer))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
|
||||
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/overlay_apply_time = TICK_USAGE)
|
||||
|
||||
overlays += overlays_to_add //batch appearance generation for free lag(tm)
|
||||
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(overlay_apply_time = TICK_USAGE_TO_MS(overlay_apply_time))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("total_coordgroup_time: [total_coordgroup_time]ms"))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("total_cornergroup_time: [total_cornergroup_time]ms"))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("triangle_time calculation: [triangle_time]ms"))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("triangle_to_matrix_time: [triangle_to_matrix_time]ms"))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("Culling Time: [culling_time]ms"))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("matrix_division_time: [matrix_division_time]ms"))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("MA_new_time: [MA_new_time]ms"))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("MA_vars_time: [MA_vars_time]ms"))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("overlays_add_time: [overlays_add_time]ms"))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("overlay_apply_time: [overlay_apply_time]ms"))
|
||||
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("[TICK_USAGE_TO_MS(timer)]ms to process total."))
|
||||
|
||||
|
||||
/**
|
||||
* Converts a triangle into a matrix that can be applied to a standardized triangle
|
||||
* to make it represent the points.
|
||||
*/
|
||||
/atom/movable/lighting_mask/proc/triangle_to_matrix(list/triangle)
|
||||
//We need the world position raw, if we use the calculated position then the pixel values will cancel.
|
||||
var/turf/our_turf = get_turf(attached_atom)
|
||||
var/ourx = our_turf.x
|
||||
var/oury = our_turf.y
|
||||
|
||||
var/originx = triangle[1][1] - ourx //~Simultaneous Variable: U
|
||||
var/originy = triangle[1][2] - oury //~Simultaneous Variable: V
|
||||
//Get points translating the first point to (0, 0)
|
||||
var/translatedPoint2x = triangle[2][1] - ourx //Simultaneous Variable: W
|
||||
var/translatedPoint2y = triangle[2][2] - oury //Simultaneous Variable: X
|
||||
var/translatedPoint3x = triangle[3][1] - ourx //Simultaneous Variable: Y
|
||||
var/translatedPoint3y = triangle[3][2] - oury //Simultaneous Variable: Z
|
||||
//message_admins("Point 1: ([originx], [originy])")
|
||||
//message_admins("Point 2: ([translatedPoint2x], [translatedPoint2y])")
|
||||
//message_admins("Point 3: ([translatedPoint3x], [translatedPoint3y])")
|
||||
//Assumption that is incorrect
|
||||
//Triangle points are
|
||||
// (-4, -4)
|
||||
// (-4, 4)
|
||||
// ( 4, -4)
|
||||
//Would be much easier if it was (0, 0) instead of (-4, -4) but since we have 6 inputs and 6 unknowns
|
||||
//we can solve the values of the matrix pretty easilly simultaneously.
|
||||
//In fact since variables U,W,Y,A,B,C are separate to V,X,Z,D,E,F its easy since its 2 identical tri-variable simultaneous equations.
|
||||
//By solving the equations simultaneously we get these results:
|
||||
//a = (y-u)/8
|
||||
var/a = (translatedPoint3x - originx) / LIGHTING_SHADOW_TEX_SIZE
|
||||
//b = (w-u)/ 8
|
||||
var/b = (translatedPoint2x - originx) / LIGHTING_SHADOW_TEX_SIZE
|
||||
//c = (y+w)/2
|
||||
var/c = (translatedPoint3x + translatedPoint2x) / 2
|
||||
//d = (z-v)/8
|
||||
var/d = (translatedPoint3y - originy) / LIGHTING_SHADOW_TEX_SIZE
|
||||
//e = (x-v)/8
|
||||
var/e = (translatedPoint2y - originy) / LIGHTING_SHADOW_TEX_SIZE
|
||||
//f = (z+x)/2
|
||||
var/f = (translatedPoint3y + translatedPoint2y) / 2
|
||||
//Matrix time g
|
||||
//a,b,d and e can be used to define the shape, C and F can be used for translation god matrices are so beautiful
|
||||
//Completely random offset that I didnt derive, I just trialled and errored for about 4 hours until it randomly worked
|
||||
//var/radius_based_offset = radius * 3 + RADIUS_BASED_OFFSET <-- for 1024x1024 lights DO NOT USE 1024x1024 SHADOWS UNLESS YOU ARE PLAYING WITH RTX200000 OR SOMETHING
|
||||
var/radius_based_offset = RADIUS_BASED_OFFSET
|
||||
var/matrix/M = matrix(a, b, (c * 32) - ((radius_based_offset) * 32), d, e, (f * 32) - ((radius_based_offset) * 32))
|
||||
//log_game("[M.a], [M.d], 0")
|
||||
//log_game("[M.b], [M.e], 0")
|
||||
//log_game("[M.c], [M.f], 1")
|
||||
return M
|
||||
|
||||
/**
|
||||
* Basically takes the 2-4 corners, extends them and then generates triangle coordinates representing shadows
|
||||
* Input: list(list(list(x, y), list(x, y)))
|
||||
* Layer 1: Lines
|
||||
* Layer 2: Vertex
|
||||
* Layer 3: X/Y value
|
||||
* OUTPUT: The same thing but with 3 lists embedded rather than 2 because they are triangles not lines now.
|
||||
*/
|
||||
/atom/movable/lighting_mask/proc/calculate_triangle_vertices(list/cornergroup)
|
||||
var/shadow_radius = max(radius + 1, 3)
|
||||
//Get the origin poin's
|
||||
var/ourx = calculated_position_x
|
||||
var/oury = calculated_position_y
|
||||
//The output
|
||||
. = list()
|
||||
//Every line has 2 triangles innit
|
||||
for(var/list/line as anything in cornergroup)
|
||||
//Get the corner vertices
|
||||
var/vertex1 = line[1]
|
||||
var/vertex2 = line[2]
|
||||
//Extend them and get end vertices
|
||||
//Calculate vertex 3 position
|
||||
var/delta_x = vertex1[1] - ourx
|
||||
var/delta_y = vertex1[2] - oury
|
||||
var/vertex3 = extend_line_to_radius(delta_x, delta_y, shadow_radius, ourx, oury)
|
||||
var/vertex3side = (vertex3[1] - ourx) == -shadow_radius ? WEST : (vertex3[1] - ourx) == shadow_radius ? EAST : (vertex3[2] - oury) == shadow_radius ? NORTH : SOUTH
|
||||
|
||||
//For vertex 4
|
||||
delta_x = vertex2[1] - ourx
|
||||
delta_y = vertex2[2] - oury
|
||||
var/vertex4 = extend_line_to_radius(delta_x, delta_y, shadow_radius, ourx, oury)
|
||||
var/vertex4side = (vertex4[1] - ourx) == -shadow_radius ? WEST : (vertex4[1] - ourx) == shadow_radius ? EAST : (vertex4[2] - oury) == shadow_radius ? NORTH : SOUTH
|
||||
|
||||
//If vertex3 is not on the same border as vertex 4 then we need more triangles to fill in the space.
|
||||
if(vertex3side != vertex4side)
|
||||
var/eitherNorth = (vertex3side == NORTH || vertex4side == NORTH)
|
||||
var/eitherEast = (vertex3side == EAST || vertex4side == EAST)
|
||||
var/eitherSouth = (vertex3side == SOUTH || vertex4side == SOUTH)
|
||||
var/eitherWest = (vertex3side == WEST || vertex4side == WEST)
|
||||
if(eitherNorth && eitherEast)
|
||||
//Add a vertex top right
|
||||
var/vertex5 = list(shadow_radius + ourx, shadow_radius + oury)
|
||||
var/triangle3 = list(vertex3, vertex4, vertex5)
|
||||
. += list(triangle3)
|
||||
else if(eitherNorth && eitherWest)
|
||||
//Add a vertex top left
|
||||
var/vertex5 = list(-shadow_radius + ourx, shadow_radius + oury)
|
||||
var/triangle3 = list(vertex3, vertex4, vertex5)
|
||||
. += list(triangle3)
|
||||
else if(eitherNorth && eitherSouth) //BLOCKER IS A | SHAPE
|
||||
//If vertex3 is to the right of the center, both vertices are to the right.
|
||||
if(vertex3[1] > ourx)
|
||||
//New vertexes are on the right
|
||||
var/vertex5 = list(ourx + shadow_radius, oury + shadow_radius)
|
||||
var/vertex6 = list(ourx + shadow_radius, oury - shadow_radius)
|
||||
//If vertex 4 is greater than 3 then triangles link as 4,5,6 and 3,4,6
|
||||
if(vertex4[2] > vertex3[2])
|
||||
var/triangle3 = list(vertex3, vertex5, vertex6)
|
||||
. += list(triangle3)
|
||||
var/triangle4 = list(vertex3, vertex4, vertex5)
|
||||
. += list(triangle4)
|
||||
else
|
||||
//Vertex 3 is greater than 4, so triangles link as 3,5,6 and 3,4,6
|
||||
var/triangle3 = list(vertex3, vertex4, vertex5)
|
||||
. += list(triangle3)
|
||||
var/triangle4 = list(vertex4, vertex5, vertex6)
|
||||
. += list(triangle4)
|
||||
else
|
||||
//New vertexes are on the left
|
||||
var/vertex5 = list(ourx - shadow_radius, oury + shadow_radius)
|
||||
var/vertex6 = list(ourx - shadow_radius, oury - shadow_radius)
|
||||
//If vertex 4 is higher than 3 then triangles link as 4,5,6 and 3,4,6
|
||||
if(vertex4[2] > vertex3[2])
|
||||
var/triangle3 = list(vertex3, vertex5, vertex6)
|
||||
. += list(triangle3)
|
||||
var/triangle4 = list(vertex3, vertex4, vertex5)
|
||||
. += list(triangle4)
|
||||
else
|
||||
//Vertex 3 is greater than 4, so triangles link as 3,5,6 and 3,4,6
|
||||
var/triangle3 = list(vertex3, vertex4, vertex5)
|
||||
. += list(triangle3)
|
||||
var/triangle4 = list(vertex4, vertex5, vertex6)
|
||||
. += list(triangle4)
|
||||
else if(eitherEast && eitherSouth)
|
||||
//Add a vertex bottom right
|
||||
var/vertex5 = list(shadow_radius + ourx, -shadow_radius + oury)
|
||||
var/triangle3 = list(vertex3, vertex4, vertex5)
|
||||
. += list(triangle3)
|
||||
else if(eitherEast && eitherWest) //BLOCKER IS A --- SHAPE
|
||||
//If vertex3 is above the center, then pointers are along the top
|
||||
if(vertex3[2] > oury)
|
||||
//New vertexes are on the right
|
||||
var/vertex5 = list(ourx + shadow_radius, oury + shadow_radius)
|
||||
var/vertex6 = list(ourx - shadow_radius, oury + shadow_radius)
|
||||
//If vertex 4 is greater than 3 then triangles link as 4,5,6 and 3,4,6
|
||||
if(vertex4[1] > vertex3[1])
|
||||
var/triangle3 = list(vertex3, vertex5, vertex6)
|
||||
. += list(triangle3)
|
||||
var/triangle4 = list(vertex3, vertex4, vertex5)
|
||||
. += list(triangle4)
|
||||
else
|
||||
//Vertex 3 is greater than 4, so triangles link as 3,5,6 and 3,4,6
|
||||
var/triangle3 = list(vertex3, vertex4, vertex5)
|
||||
. += list(triangle3)
|
||||
var/triangle4 = list(vertex4, vertex5, vertex6)
|
||||
. += list(triangle4)
|
||||
else
|
||||
//New vertexes are on the bottom
|
||||
var/vertex5 = list(ourx + shadow_radius, oury - shadow_radius)
|
||||
var/vertex6 = list(ourx - shadow_radius, oury - shadow_radius)
|
||||
//If vertex 4 is higher than 3 then triangles link as 4,5,6 and 3,4,6
|
||||
if(vertex4[1] > vertex3[1])
|
||||
var/triangle3 = list(vertex3, vertex4, vertex5)
|
||||
. += list(triangle3)
|
||||
var/triangle4 = list(vertex3, vertex5, vertex6)
|
||||
. += list(triangle4)
|
||||
else
|
||||
//Vertex 3 is greater than 4, so triangles link as 3,5,6 and 3,4,6
|
||||
var/triangle3 = list(vertex3, vertex4, vertex5)
|
||||
. += list(triangle3)
|
||||
var/triangle4 = list(vertex4, vertex5, vertex6)
|
||||
. += list(triangle4)
|
||||
else if(eitherSouth && eitherWest)
|
||||
//Bottom left
|
||||
var/vertex5 = list(-shadow_radius + ourx, -shadow_radius + oury)
|
||||
var/triangle3 = list(vertex3, vertex4, vertex5)
|
||||
. += list(triangle3)
|
||||
else
|
||||
//bug
|
||||
stack_trace("Major error: vertex in a bad position (North: [eitherNorth], East: [eitherEast], South: [eitherSouth], West: [eitherWest])")
|
||||
|
||||
//Generate triangles
|
||||
var/triangle1 = list(vertex1, vertex2, vertex3)
|
||||
var/triangle2 = list(vertex2, vertex3, vertex4)
|
||||
. += list(triangle1)
|
||||
. += list(triangle2)
|
||||
|
||||
///Takes in the list of lines and sight blockers and returns only the lines that are not blocked
|
||||
/atom/movable/lighting_mask/proc/cull_blocked_in_group(list/lines, list/sight_blockers)
|
||||
. = list()
|
||||
for(var/list/line in lines)
|
||||
var/vertex1 = line[1]
|
||||
var/vertex2 = line[2]
|
||||
var/list/lines_to_add = list()
|
||||
if(vertex1[1] == vertex2[1])
|
||||
//Vertical line.
|
||||
//Requires a block to the left and right all the way from the bottom to the top
|
||||
var/left = vertex1[1] - 0.5
|
||||
var/right = vertex1[1] + 0.5
|
||||
var/bottom = min(vertex1[2], vertex2[2]) + 0.5
|
||||
var/top = max(vertex1[2], vertex2[2]) - 0.5
|
||||
var/list/current_bottom_vertex = list(vertex1[1], bottom - 0.5)
|
||||
var/list/current_top_vertex = list(vertex1[1], bottom - 0.5)
|
||||
for(var/i in bottom to top)
|
||||
var/list/left_list = sight_blockers["[left]"]
|
||||
var/isLeftBlocked = left_list?.Find(i) ? TRUE : FALSE
|
||||
var/list/right_list = sight_blockers["[right]"]
|
||||
var/isRightBlocked = right_list?.Find(i) ? TRUE : FALSE
|
||||
if(isLeftBlocked == isRightBlocked)
|
||||
if(current_bottom_vertex[2] != current_top_vertex[2])
|
||||
lines_to_add += list(list(current_bottom_vertex, current_top_vertex))
|
||||
current_bottom_vertex = list(vertex1[1], i + 0.5)
|
||||
current_top_vertex = list(vertex1[1], i + 0.5)
|
||||
if(current_bottom_vertex[2] != current_top_vertex[2])
|
||||
lines_to_add += list(list(current_bottom_vertex, current_top_vertex))
|
||||
else
|
||||
//Horizontal line
|
||||
//Requires a block above and below for every position from left to right
|
||||
var/left = min(vertex1[1], vertex2[1]) + 0.5
|
||||
var/right = max(vertex1[1], vertex2[1]) - 0.5
|
||||
var/top = vertex1[2] + 0.5
|
||||
var/bottom = vertex1[2] - 0.5
|
||||
var/list/current_left_vertex = list(left - 0.5, vertex1[2])
|
||||
var/list/current_right_vertex = list(left - 0.5, vertex1[2])
|
||||
for(var/i in left to right)
|
||||
var/list/check_list = sight_blockers["[i]"]
|
||||
var/isAboveBlocked = check_list?.Find(top) ? TRUE : FALSE
|
||||
var/isBelowBlocked = check_list?.Find(bottom) ? TRUE : FALSE
|
||||
if(isAboveBlocked == isBelowBlocked)
|
||||
if(current_left_vertex[1] != current_right_vertex[1])
|
||||
lines_to_add += list(list(current_left_vertex, current_right_vertex))
|
||||
current_left_vertex = list(i + 0.5, vertex1[2])
|
||||
current_right_vertex = list(i + 0.5, vertex1[2])
|
||||
if(current_left_vertex[1] != current_right_vertex[1])
|
||||
lines_to_add += list(list(current_left_vertex, current_right_vertex))
|
||||
. += lines_to_add
|
||||
|
||||
/**
|
||||
* Converts the corners into the 3 (or 2) valid points
|
||||
* For example if a wall is top right of the source, the bottom left wall corner
|
||||
* can be removed otherwise the wall itself will be in the shadow.
|
||||
* Input: list(list(x1, y1), list(x2, y2))
|
||||
* Output: list(list(list(x, y), list(x, y))) <-- 2 coordinates that form a line
|
||||
*/
|
||||
/atom/movable/lighting_mask/proc/get_corners_from_coords(list/coordgroup)
|
||||
//Get the raw numbers
|
||||
var/xlow = coordgroup[1][1]
|
||||
var/ylow = coordgroup[1][2]
|
||||
var/xhigh = coordgroup[2][1]
|
||||
var/yhigh = coordgroup[2][2]
|
||||
|
||||
var/ourx = calculated_position_x
|
||||
var/oury = calculated_position_y
|
||||
|
||||
//The source is above the point (Bottom Quad)
|
||||
if(oury > yhigh)
|
||||
//Bottom Right
|
||||
if(ourx < xlow)
|
||||
return list(
|
||||
list(list(xlow, ylow), list(xhigh, ylow)),
|
||||
list(list(xhigh, ylow), list(xhigh, yhigh)),
|
||||
)
|
||||
//Bottom Left
|
||||
else if(ourx > xhigh)
|
||||
return list(
|
||||
list(list(xlow, yhigh), list(xlow, ylow)),
|
||||
list(list(xlow, ylow), list(xhigh, ylow)),
|
||||
)
|
||||
//Bottom Middle
|
||||
else
|
||||
return list(
|
||||
list(list(xlow, yhigh), list(xlow, ylow)),
|
||||
list(list(xlow, ylow), list(xhigh, ylow)),
|
||||
list(list(xhigh, ylow), list(xhigh, yhigh))
|
||||
)
|
||||
//The source is below the point (Top quad)
|
||||
else if(oury < ylow)
|
||||
//Top Right
|
||||
if(ourx < xlow)
|
||||
return list(
|
||||
list(list(xlow, yhigh), list(xhigh, yhigh)),
|
||||
list(list(xhigh, yhigh), list(xhigh, ylow)),
|
||||
)
|
||||
//Top Left
|
||||
else if(ourx > xhigh)
|
||||
return list(
|
||||
list(list(xlow, ylow), list(xlow, yhigh)),
|
||||
list(list(xlow, yhigh), list(xhigh, yhigh)),
|
||||
)
|
||||
//Top Middle
|
||||
else
|
||||
return list(
|
||||
list(list(xlow, ylow), list(xlow, yhigh)),
|
||||
list(list(xlow, yhigh), list(xhigh, yhigh)),
|
||||
list(list(xhigh, yhigh), list(xhigh, ylow))
|
||||
)
|
||||
//the source is between the group Middle something
|
||||
else
|
||||
//Middle Right
|
||||
if(ourx < xlow)
|
||||
return list(
|
||||
list(list(xlow, yhigh), list(xhigh, yhigh)),
|
||||
list(list(xhigh, yhigh), list(xhigh, ylow)),
|
||||
list(list(xhigh, ylow), list(xlow, ylow))
|
||||
)
|
||||
//Middle Left
|
||||
else if(ourx > xhigh)
|
||||
return list(
|
||||
list(list(xhigh, ylow), list(xlow, ylow)),
|
||||
list(list(xlow, ylow), list(xlow, yhigh)),
|
||||
list(list(xlow, yhigh), list(xhigh, yhigh))
|
||||
)
|
||||
//Middle Middle (Why?????????)
|
||||
else
|
||||
return list(
|
||||
list(list(xhigh, ylow), list(xlow, ylow)),
|
||||
list(list(xlow, ylow), list(xlow, yhigh)),
|
||||
list(list(xlow, yhigh), list(xhigh, yhigh)),
|
||||
list(list(xlow, yhigh), list(xhigh, ylow))
|
||||
)
|
||||
|
||||
//Calculates the coordinates of the corner
|
||||
//Takes a list of blocks and calculates the bottom left corner and the top right corner.
|
||||
//Input: Group list(list(list(x,y), list(x,y)), list(list(x, y)))
|
||||
//Output: Coordinates list(list(left, bottom), list(right, top))
|
||||
/atom/movable/lighting_mask/proc/calculate_corners_in_group(list/group)
|
||||
if(length(group) == 0)
|
||||
CRASH("Calculate_corners_in_group called on a group of length 0. Critical error.")
|
||||
if(length(group) == 1)
|
||||
var/x = group[1][1]
|
||||
var/y = group[1][2]
|
||||
return list(
|
||||
list(x - 0.5, y - 0.5),
|
||||
list(x + 0.5, y + 0.5)
|
||||
)
|
||||
//Group is multiple length, find top left and bottom right
|
||||
var/first = group[1]
|
||||
var/second = group[2]
|
||||
var/group_direction = NORTH
|
||||
if(first[1] != second[1])
|
||||
group_direction = EAST
|
||||
#ifdef SHADOW_DEBUG6
|
||||
else if(first[2] != second[2])
|
||||
message_admins("Major error, group is not 1xN or Nx1")
|
||||
#endif
|
||||
var/lowest = INFINITY
|
||||
var/highest = 0
|
||||
for(var/vector in group)
|
||||
var/value_to_comp = vector[1]
|
||||
if(group_direction == NORTH)
|
||||
value_to_comp = vector[2]
|
||||
lowest = min(lowest, value_to_comp)
|
||||
highest = max(highest, value_to_comp)
|
||||
//done ez
|
||||
if(group_direction == NORTH)
|
||||
return list(
|
||||
list(first[1] - 0.5, lowest - 0.5),
|
||||
list(first[1] + 0.5, highest + 0.5)
|
||||
)
|
||||
else
|
||||
return list(
|
||||
list(lowest - 0.5, first[2] - 0.5),
|
||||
list(highest + 0.5, first[2] + 0.5)
|
||||
)
|
||||
|
||||
///Groups things into vertical and horizontal lines.
|
||||
///Input: All atoms ungrouped list(atom1, atom2, atom3)
|
||||
///Output: List(List(Group), list(group2), ... , list(groupN))
|
||||
///Output: List(List(atom1, atom2), list(atom3, atom4...), ... , list(atom))
|
||||
/atom/movable/lighting_mask/proc/group_atoms(list/ungrouped_things)
|
||||
. = list()
|
||||
//Ungrouped things comes in as
|
||||
// Key: X
|
||||
// Value = list(y values)
|
||||
//This makes sorting vertically easy, however sorting horizontally is harder
|
||||
//While grouping elements vertically, we can put them into a new list with
|
||||
// Key: Y
|
||||
// Value = list(x values)
|
||||
//to make it much easier.
|
||||
var/list/horizontal_atoms = list()
|
||||
//=================================================
|
||||
//Vertical sorting (X locked)
|
||||
for(var/x_key in ungrouped_things)
|
||||
var/list/y_components = ungrouped_things[x_key]
|
||||
var/pointer = y_components[1]
|
||||
var/list/group = list(list(text2num(x_key), y_components[1]))
|
||||
for(var/i in 2 to length(y_components))
|
||||
var/next = y_components[i]
|
||||
if(next != pointer + 1)
|
||||
if(length(group) == 1)
|
||||
//Add the element in group to horizontal
|
||||
COORD_LIST_ADD(horizontal_atoms, pointer, text2num(x_key))
|
||||
DEBUG_HIGHLIGHT(text2num(x_key), pointer, "#FFFF00")
|
||||
else
|
||||
//Add the group to the output
|
||||
. += list(group)
|
||||
group = list()
|
||||
group += list(list(text2num(x_key), next))
|
||||
DEBUG_HIGHLIGHT(text2num(x_key), next, "#FF0000")
|
||||
pointer = next
|
||||
if(length(group) == 1)
|
||||
//Add the element in group to horizontal
|
||||
COORD_LIST_ADD(horizontal_atoms, pointer, text2num(x_key))
|
||||
DEBUG_HIGHLIGHT(text2num(x_key), pointer, "#FFFF00")
|
||||
else
|
||||
//Add the group to the output
|
||||
. += list(group)
|
||||
//=================================================
|
||||
//Horizontal sorting (Y locked)
|
||||
for(var/y_key in horizontal_atoms)
|
||||
var/list/x_components = horizontal_atoms[y_key]
|
||||
var/pointer = x_components[1]
|
||||
var/list/group = list(list(x_components[1], text2num(y_key)))
|
||||
for(var/i in 2 to length(x_components))
|
||||
var/next = x_components[i]
|
||||
if(next != pointer + 1)
|
||||
. += list(group)
|
||||
group = list()
|
||||
group += list(list(next, text2num(y_key)))
|
||||
DEBUG_HIGHLIGHT(next, text2num(y_key), "#00FF00")
|
||||
pointer = next
|
||||
. += list(group)
|
||||
|
||||
///gets a line from a x and y, to the offset x and y of length radius
|
||||
/proc/extend_line_to_radius(delta_x, delta_y, radius, offset_x, offset_y)
|
||||
if(abs(delta_x) < abs(delta_y))
|
||||
//top or bottom
|
||||
var/proportion = radius / abs(delta_y)
|
||||
return list(delta_x * proportion + offset_x, delta_y * proportion + offset_y)
|
||||
else
|
||||
var/proportion = radius / abs(delta_x)
|
||||
return list(delta_x * proportion + offset_x, delta_y * proportion + offset_y)
|
||||
|
||||
#undef LIGHTING_SHADOW_TEX_SIZE
|
||||
#undef COORD_LIST_ADD
|
||||
#undef DEBUG_HIGHLIGHT
|
||||
#undef DO_SOMETHING_IF_DEBUGGING_SHADOWS
|
||||
@@ -1,161 +0,0 @@
|
||||
/atom/movable/lighting_overlay
|
||||
name = ""
|
||||
anchored = TRUE
|
||||
icon = LIGHTING_ICON
|
||||
icon_state = LIGHTING_BASE_ICON_STATE
|
||||
color = LIGHTING_BASE_MATRIX
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
layer = LIGHTING_LAYER
|
||||
plane = LIGHTING_PLANE
|
||||
invisibility = INVISIBILITY_LIGHTING
|
||||
simulated = 0
|
||||
blend_mode = BLEND_OVERLAY
|
||||
appearance_flags = NO_CLIENT_COLOR
|
||||
|
||||
var/needs_update = FALSE
|
||||
|
||||
#if WORLD_ICON_SIZE != 32
|
||||
transform = matrix(WORLD_ICON_SIZE / 32, 0, (WORLD_ICON_SIZE - 32) / 2, 0, WORLD_ICON_SIZE / 32, (WORLD_ICON_SIZE - 32) / 2)
|
||||
#endif
|
||||
|
||||
/atom/movable/lighting_overlay/Initialize(mapload, ...)
|
||||
. = ..()
|
||||
SSlighting.total_lighting_overlays++
|
||||
|
||||
var/turf/T = loc // If this runtimes atleast we'll know what's creating overlays in things that aren't turfs.
|
||||
T.lighting_overlay = src
|
||||
T.luminosity = 0
|
||||
|
||||
needs_update = TRUE
|
||||
SSlighting.overlay_queue += src
|
||||
|
||||
/atom/movable/lighting_overlay/Destroy(force = FALSE)
|
||||
if (!force)
|
||||
return QDEL_HINT_LETMELIVE // STOP DELETING ME
|
||||
|
||||
//L_PROF(loc, "overlay_destroy")
|
||||
SSlighting.total_lighting_overlays--
|
||||
|
||||
var/turf/T = loc
|
||||
if (istype(T))
|
||||
T.lighting_overlay = null
|
||||
T.luminosity = 1
|
||||
|
||||
SSlighting.overlay_queue -= src
|
||||
|
||||
return ..()
|
||||
|
||||
// This is a macro PURELY so that the if below is actually readable.
|
||||
#define ALL_EQUAL ((rr == gr && gr == br && br == ar) && (rg == gg && gg == bg && bg == ag) && (rb == gb && gb == bb && bb == ab))
|
||||
|
||||
/atom/movable/lighting_overlay/proc/update_overlay()
|
||||
var/turf/T = loc
|
||||
if (!isturf(T)) // Erm...
|
||||
if (loc)
|
||||
warning("A lighting overlay realised its loc was NOT a turf (actual loc: [loc], [loc.type]) in update_overlay() and got deleted!")
|
||||
|
||||
else
|
||||
warning("A lighting overlay realised it was in nullspace in update_overlay() and got deleted!")
|
||||
|
||||
qdel(src, TRUE)
|
||||
return
|
||||
|
||||
// See LIGHTING_CORNER_DIAGONAL in lighting_corner.dm for why these values are what they are.
|
||||
var/list/corners = T.corners
|
||||
|
||||
//Local cache, because otherwise it accesses the global variable repeatedly, which is slower
|
||||
var/dummy_lighting_corner_cache = dummy_lighting_corner
|
||||
|
||||
var/datum/lighting_corner/cr = dummy_lighting_corner_cache
|
||||
var/datum/lighting_corner/cg = dummy_lighting_corner_cache
|
||||
var/datum/lighting_corner/cb = dummy_lighting_corner_cache
|
||||
var/datum/lighting_corner/ca = dummy_lighting_corner_cache
|
||||
if (corners)
|
||||
cr = corners[3] || dummy_lighting_corner_cache
|
||||
cg = corners[2] || dummy_lighting_corner_cache
|
||||
cb = corners[4] || dummy_lighting_corner_cache
|
||||
ca = corners[1] || dummy_lighting_corner_cache
|
||||
|
||||
luminosity = max(cr.cache_mx, cg.cache_mx, cb.cache_mx, ca.cache_mx) > LIGHTING_SOFT_THRESHOLD
|
||||
|
||||
var/rr = cr.cache_r
|
||||
var/rg = cr.cache_g
|
||||
var/rb = cr.cache_b
|
||||
|
||||
var/gr = cg.cache_r
|
||||
var/gg = cg.cache_g
|
||||
var/gb = cg.cache_b
|
||||
|
||||
var/br = cb.cache_r
|
||||
var/bg = cb.cache_g
|
||||
var/bb = cb.cache_b
|
||||
|
||||
var/ar = ca.cache_r
|
||||
var/ag = ca.cache_g
|
||||
var/ab = ca.cache_b
|
||||
|
||||
if ((rr & gr & br & ar) && (rg + gg + bg + ag + rb + gb + bb + ab == 8))
|
||||
icon_state = LIGHTING_TRANSPARENT_ICON_STATE
|
||||
color = null
|
||||
else if (!luminosity)
|
||||
icon_state = LIGHTING_DARKNESS_ICON_STATE
|
||||
color = null
|
||||
else if (rr == LIGHTING_DEFAULT_TUBE_R && rg == LIGHTING_DEFAULT_TUBE_G && rb == LIGHTING_DEFAULT_TUBE_B && ALL_EQUAL)
|
||||
icon_state = LIGHTING_STATION_ICON_STATE
|
||||
color = null
|
||||
else
|
||||
icon_state = LIGHTING_BASE_ICON_STATE
|
||||
if (islist(color))
|
||||
var/list/c_list = color
|
||||
c_list[CL_MATRIX_RR] = rr
|
||||
c_list[CL_MATRIX_RG] = rg
|
||||
c_list[CL_MATRIX_RB] = rb
|
||||
c_list[CL_MATRIX_GR] = gr
|
||||
c_list[CL_MATRIX_GG] = gg
|
||||
c_list[CL_MATRIX_GB] = gb
|
||||
c_list[CL_MATRIX_BR] = br
|
||||
c_list[CL_MATRIX_BG] = bg
|
||||
c_list[CL_MATRIX_BB] = bb
|
||||
c_list[CL_MATRIX_AR] = ar
|
||||
c_list[CL_MATRIX_AG] = ag
|
||||
c_list[CL_MATRIX_AB] = ab
|
||||
color = c_list
|
||||
else
|
||||
color = list(
|
||||
rr, rg, rb, 0,
|
||||
gr, gg, gb, 0,
|
||||
br, bg, bb, 0,
|
||||
ar, ag, ab, 0,
|
||||
0, 0, 0, 1
|
||||
)
|
||||
|
||||
#undef ALL_EQUAL
|
||||
|
||||
// Variety of overrides so the overlays don't get affected by weird things.
|
||||
|
||||
/atom/movable/lighting_overlay/ex_act(severity)
|
||||
return 0
|
||||
|
||||
/atom/movable/lighting_overlay/singularity_act()
|
||||
return
|
||||
|
||||
/atom/movable/lighting_overlay/singularity_pull()
|
||||
return
|
||||
|
||||
/atom/movable/lighting_overlay/singuloCanEat()
|
||||
return FALSE
|
||||
|
||||
/atom/movable/lighting_overlay/can_fall()
|
||||
return FALSE
|
||||
|
||||
// Override here to prevent things accidentally moving around overlays.
|
||||
/atom/movable/lighting_overlay/forceMove(atom/destination, no_tp = FALSE, harderforce = FALSE)
|
||||
if(harderforce)
|
||||
//L_PROF(loc, "overlay_forcemove")
|
||||
. = ..()
|
||||
|
||||
/atom/movable/lighting_overlay/shuttle_move(turf/loc)
|
||||
return
|
||||
|
||||
/atom/movable/lighting_overlay/conveyor_act()
|
||||
return
|
||||
@@ -1,41 +0,0 @@
|
||||
// Writes lighting updates to the database.
|
||||
// FOR DEBUGGING ONLY!
|
||||
|
||||
/proc/lprof_write(var/atom/movable/obj, var/type = "UNKNOWN")
|
||||
if (!GLOB.lighting_profiling || !obj || !establish_db_connection(GLOB.dbcon))
|
||||
return
|
||||
|
||||
var/x = null
|
||||
var/y = null
|
||||
var/z = null
|
||||
|
||||
var/name = null
|
||||
var/locname = null
|
||||
if (istype(obj))
|
||||
name = obj.name
|
||||
locname = obj.loc.name
|
||||
x = obj.loc.x
|
||||
y = obj.loc.y
|
||||
z = obj.loc.z
|
||||
|
||||
var/static/DBQuery/lprof_q
|
||||
if (!lprof_q)
|
||||
lprof_q = GLOB.dbcon.NewQuery({"INSERT INTO ss13dbg_lighting (time,tick_usage,type,name,loc_name,x,y,z)
|
||||
VALUES (:time:,:tick_usage:,:type:,:name:,:loc_name:,:x:,:y:,:z:);"})
|
||||
|
||||
lprof_q.Execute(
|
||||
list(
|
||||
"time" = world.time,
|
||||
"tick_usage" = world.tick_usage,
|
||||
"type" = type,
|
||||
"name" = name,
|
||||
"loc_name" = locname,
|
||||
"x" = x,
|
||||
"y" = y,
|
||||
"z" = z))
|
||||
|
||||
var/err = lprof_q.ErrorMsg()
|
||||
if (err)
|
||||
LOG_DEBUG("lprof_write: SQL Error: [err]")
|
||||
message_admins(SPAN_DANGER("SQL Error during lighting profiling; disabling!"))
|
||||
GLOB.lighting_profiling = FALSE
|
||||
@@ -1,10 +0,0 @@
|
||||
/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)
|
||||
T.lighting_build_overlay()
|
||||
@@ -1,468 +0,0 @@
|
||||
// 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()
|
||||
var/light_uv // The intensity of UV light, between 0 and 255.
|
||||
var/light_angle // The light's emission angle, in degrees.
|
||||
|
||||
// Variables for keeping track of the colour.
|
||||
var/lum_r
|
||||
var/lum_g
|
||||
var/lum_b
|
||||
var/lum_u
|
||||
|
||||
// The lumcount values used to apply the light.
|
||||
var/tmp/applied_lum_r
|
||||
var/tmp/applied_lum_g
|
||||
var/tmp/applied_lum_b
|
||||
var/tmp/applied_lum_u
|
||||
|
||||
// Variables used to keep track of the atom's angle.
|
||||
var/tmp/limit_a_x // The first test point's X coord for the cone.
|
||||
var/tmp/limit_a_y // The first test point's Y coord for the cone.
|
||||
var/tmp/limit_a_t // The first test point's angle.
|
||||
var/tmp/limit_b_x // The second test point's X coord for the cone.
|
||||
var/tmp/limit_b_y // The second test point's Y coord for the cone.
|
||||
var/tmp/limit_b_t // The second test point's angle.
|
||||
var/tmp/cached_origin_x // The last known X coord of the origin.
|
||||
var/tmp/cached_origin_y // The last known Y coord of the origin.
|
||||
var/tmp/old_direction // The last known direction of the origin.
|
||||
var/tmp/test_x_offset // How much the X coord should be offset due to direction.
|
||||
var/tmp/test_y_offset // How much the Y coord should be offset due to direction.
|
||||
var/tmp/facing_opaque = FALSE
|
||||
|
||||
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/needs_update = LIGHTING_NO_UPDATE
|
||||
|
||||
var/skip_falloff = FALSE // ONLY for use with sunlight, behavior is undefined if TRUE on regular sources.
|
||||
|
||||
/datum/light_source/New(atom/owner, atom/top)
|
||||
SSlighting.total_lighting_sources++
|
||||
source_atom = owner // Set our new owner.
|
||||
|
||||
LAZYADD(source_atom.light_sources, src)
|
||||
|
||||
top_atom = top
|
||||
if (top_atom != source_atom)
|
||||
LAZYADD(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
|
||||
light_uv = source_atom.uv_intensity
|
||||
light_angle = source_atom.light_wedge
|
||||
|
||||
parse_light_color()
|
||||
|
||||
update()
|
||||
|
||||
//L_PROF(source_atom, "source_new([type])")
|
||||
|
||||
// Kill ourselves.
|
||||
/datum/light_source/Destroy(force)
|
||||
//L_PROF(source_atom, "source_destroy")
|
||||
SSlighting.total_lighting_sources--
|
||||
|
||||
remove_lum()
|
||||
if (source_atom)
|
||||
LAZYREMOVE(source_atom.light_sources, src)
|
||||
|
||||
if (top_atom)
|
||||
LAZYREMOVE(top_atom.light_sources, src)
|
||||
|
||||
. = ..()
|
||||
if (!force)
|
||||
return QDEL_HINT_IWILLGC
|
||||
|
||||
#ifdef USE_INTELLIGENT_LIGHTING_UPDATES
|
||||
// Picks either scheduled or instant updates based on current server load.
|
||||
#define INTELLIGENT_UPDATE(level) \
|
||||
var/_should_update = needs_update == LIGHTING_NO_UPDATE; \
|
||||
if (needs_update < level) { \
|
||||
needs_update = level; \
|
||||
} \
|
||||
if (_should_update) { \
|
||||
if (world.tick_usage > Master.current_ticklimit || SSlighting.force_queued) { \
|
||||
SSlighting.light_queue += src; \
|
||||
} \
|
||||
else { \
|
||||
SSlighting.total_instant_updates += 1; \
|
||||
update_corners(TRUE); \
|
||||
needs_update = LIGHTING_NO_UPDATE; \
|
||||
} \
|
||||
}
|
||||
#else
|
||||
#define INTELLIGENT_UPDATE(level) \
|
||||
if (needs_update == LIGHTING_NO_UPDATE) \
|
||||
SSlighting.light_queue += src; \
|
||||
if (needs_update < level) \
|
||||
needs_update = level;
|
||||
#endif
|
||||
|
||||
// This proc will cause the light source to update the top atom, and add itself to the update queue.
|
||||
/datum/light_source/proc/update(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.
|
||||
LAZYREMOVE(top_atom.light_sources, src)
|
||||
|
||||
top_atom = new_top_atom
|
||||
|
||||
if (top_atom != source_atom)
|
||||
LAZYADD(top_atom.light_sources, src) // Add ourselves to the light sources of our new top atom.
|
||||
|
||||
//L_PROF(source_atom, "source_update")
|
||||
|
||||
INTELLIGENT_UPDATE(LIGHTING_CHECK_UPDATE)
|
||||
|
||||
// Will force an update without checking if it's actually needed.
|
||||
/datum/light_source/proc/force_update()
|
||||
//L_PROF(source_atom, "source_forceupdate")
|
||||
|
||||
INTELLIGENT_UPDATE(LIGHTING_FORCE_UPDATE)
|
||||
|
||||
// Will cause the light source to recalculate turfs that were removed or added to visibility only.
|
||||
/datum/light_source/proc/vis_update()
|
||||
//L_PROF(source_atom, "source_visupdate")
|
||||
|
||||
INTELLIGENT_UPDATE(LIGHTING_VIS_UPDATE)
|
||||
|
||||
// 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
|
||||
|
||||
if (light_uv)
|
||||
lum_u = light_uv / 255
|
||||
else
|
||||
lum_u = 0
|
||||
|
||||
#define POLAR_TO_CART_X(R,T) ((R) * cos(T))
|
||||
#define POLAR_TO_CART_Y(R,T) ((R) * sin(T))
|
||||
#define PSEUDO_WEDGE(A_X,A_Y,B_X,B_Y) ((A_X)*(B_Y) - (A_Y)*(B_X))
|
||||
#define MINMAX(NUM) ((NUM) < 0 ? -round(-(NUM)) : round(NUM))
|
||||
#define ARBITRARY_NUMBER 10
|
||||
|
||||
/datum/light_source/proc/regenerate_angle(ndir)
|
||||
old_direction = ndir
|
||||
|
||||
var/turf/front = get_step(source_turf, old_direction)
|
||||
facing_opaque = (front && front.has_opaque_atom)
|
||||
|
||||
cached_origin_x = test_x_offset = source_turf.x
|
||||
cached_origin_y = test_y_offset = source_turf.y
|
||||
|
||||
if (facing_opaque)
|
||||
return
|
||||
|
||||
var/angle = light_angle * 0.5
|
||||
switch (old_direction)
|
||||
if (NORTH)
|
||||
limit_a_t = angle + 90
|
||||
limit_b_t = -(angle) + 90
|
||||
++test_y_offset
|
||||
|
||||
if (SOUTH)
|
||||
limit_a_t = (angle) - 90
|
||||
limit_b_t = -(angle) - 90
|
||||
--test_y_offset
|
||||
|
||||
if (EAST)
|
||||
limit_a_t = angle
|
||||
limit_b_t = -(angle)
|
||||
++test_x_offset
|
||||
|
||||
if (WEST)
|
||||
limit_a_t = angle + 180
|
||||
limit_b_t = -(angle) - 180
|
||||
--test_x_offset
|
||||
|
||||
// Convert our angle + range into a vector.
|
||||
limit_a_x = POLAR_TO_CART_X(light_range + ARBITRARY_NUMBER, limit_a_t)
|
||||
limit_a_x = MINMAX(limit_a_x)
|
||||
limit_a_y = POLAR_TO_CART_Y(light_range + ARBITRARY_NUMBER, limit_a_t)
|
||||
limit_a_y = MINMAX(limit_a_y)
|
||||
limit_b_x = POLAR_TO_CART_X(light_range + ARBITRARY_NUMBER, limit_b_t)
|
||||
limit_b_x = MINMAX(limit_b_x)
|
||||
limit_b_y = POLAR_TO_CART_Y(light_range + ARBITRARY_NUMBER, limit_b_t)
|
||||
limit_b_y = MINMAX(limit_b_y)
|
||||
|
||||
#undef ARBITRARY_NUMBER
|
||||
#undef POLAR_TO_CART_X
|
||||
#undef POLAR_TO_CART_Y
|
||||
#undef MINMAX
|
||||
|
||||
/datum/light_source/proc/remove_lum(now = FALSE)
|
||||
applied = FALSE
|
||||
|
||||
var/thing
|
||||
for (thing in affecting_turfs)
|
||||
var/turf/T = thing
|
||||
LAZYREMOVE(T.affecting_lights, src)
|
||||
|
||||
affecting_turfs = null
|
||||
|
||||
for (thing in effect_str)
|
||||
var/datum/lighting_corner/C = thing
|
||||
REMOVE_CORNER(C,now)
|
||||
|
||||
LAZYREMOVE(C.affecting, src)
|
||||
|
||||
effect_str = null
|
||||
|
||||
/datum/light_source/proc/recalc_corner(datum/lighting_corner/C, now = FALSE)
|
||||
LAZYINITLIST(effect_str)
|
||||
if (effect_str[C]) // Already have one.
|
||||
REMOVE_CORNER(C,now)
|
||||
effect_str[C] = 0
|
||||
|
||||
var/actual_range = light_range
|
||||
|
||||
var/Sx = source_turf.x
|
||||
var/Sy = source_turf.y
|
||||
var/Sz = source_turf.z
|
||||
|
||||
if (skip_falloff)
|
||||
APPLY_CORNER_SIMPLE(C)
|
||||
else
|
||||
var/height = C.z == Sz ? LIGHTING_HEIGHT : CALCULATE_CORNER_HEIGHT(C.z, Sz)
|
||||
APPLY_CORNER(C, now, Sx, Sy, height)
|
||||
|
||||
UNSETEMPTY(effect_str)
|
||||
|
||||
// If you update this, update the equivalent proc in lighting_source_novis.dm.
|
||||
/datum/light_source/proc/update_corners(now = FALSE)
|
||||
var/update = FALSE
|
||||
|
||||
if (QDELETED(source_atom))
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
if (source_atom.light_power != light_power)
|
||||
light_power = source_atom.light_power
|
||||
update = TRUE
|
||||
|
||||
if (source_atom.light_range != light_range)
|
||||
light_range = source_atom.light_range
|
||||
update = TRUE
|
||||
|
||||
if (!top_atom)
|
||||
top_atom = source_atom
|
||||
update = TRUE
|
||||
|
||||
if (top_atom.loc != source_turf)
|
||||
source_turf = top_atom.loc
|
||||
update = TRUE
|
||||
|
||||
if (!light_range || !light_power)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
if (isturf(top_atom))
|
||||
if (source_turf != top_atom)
|
||||
source_turf = top_atom
|
||||
update = TRUE
|
||||
else if (top_atom.loc != source_turf)
|
||||
source_turf = top_atom.loc
|
||||
update = TRUE
|
||||
|
||||
if (!source_turf)
|
||||
return // Somehow we've got a light in nullspace, no-op.
|
||||
|
||||
if (light_range && light_power && !applied)
|
||||
update = TRUE
|
||||
|
||||
if (source_atom.light_color != light_color)
|
||||
light_color = source_atom.light_color
|
||||
parse_light_color()
|
||||
update = TRUE
|
||||
|
||||
else if (applied_lum_r != lum_r || applied_lum_g != lum_g || applied_lum_b != lum_b)
|
||||
update = TRUE
|
||||
|
||||
if (source_atom.light_wedge != light_angle)
|
||||
light_angle = source_atom.light_wedge
|
||||
update = TRUE
|
||||
|
||||
if (light_angle)
|
||||
var/ndir
|
||||
if(istype(top_atom, /mob) && top_atom:facing_dir)
|
||||
var/mob/mob = top_atom
|
||||
if(mob.facing_dir)
|
||||
ndir = mob.facing_dir
|
||||
else
|
||||
ndir = top_atom.dir
|
||||
else
|
||||
ndir = top_atom.dir
|
||||
|
||||
if (old_direction != ndir) // If our direction has changed, we need to regenerate all the angle info.
|
||||
regenerate_angle(ndir)
|
||||
update = TRUE
|
||||
else // Check if it was just a x/y translation, and update our vars without an regenerate_angle() call if it is.
|
||||
var/co_updated = FALSE
|
||||
if (source_turf.x != cached_origin_x)
|
||||
test_x_offset += source_turf.x - cached_origin_x
|
||||
cached_origin_x = source_turf.x
|
||||
|
||||
co_updated = TRUE
|
||||
|
||||
if (source_turf.y != cached_origin_y)
|
||||
test_y_offset += source_turf.y - cached_origin_y
|
||||
cached_origin_y = source_turf.y
|
||||
|
||||
co_updated = TRUE
|
||||
|
||||
if (co_updated)
|
||||
// We might be facing a wall now.
|
||||
var/turf/front = get_step(source_turf, old_direction)
|
||||
var/new_fo = (front && front.has_opaque_atom)
|
||||
if (new_fo != facing_opaque)
|
||||
facing_opaque = new_fo
|
||||
regenerate_angle(ndir)
|
||||
|
||||
update = TRUE
|
||||
|
||||
if (update)
|
||||
needs_update = LIGHTING_CHECK_UPDATE
|
||||
else if (needs_update == LIGHTING_CHECK_UPDATE)
|
||||
return // No change.
|
||||
|
||||
var/list/datum/lighting_corner/corners = list()
|
||||
var/list/turf/turfs = list()
|
||||
var/thing
|
||||
var/datum/lighting_corner/C
|
||||
var/turf/T
|
||||
var/list/Tcorners
|
||||
var/Sx = source_turf.x
|
||||
var/Sy = source_turf.y
|
||||
var/Sz = source_turf.z
|
||||
var/corner_height = LIGHTING_HEIGHT
|
||||
var/actual_range = (light_angle && facing_opaque) ? light_range * LIGHTING_BLOCKED_FACTOR : light_range
|
||||
var/test_x
|
||||
var/test_y
|
||||
|
||||
FOR_DVIEW(T, Ceiling(actual_range), source_turf, 0)
|
||||
check_t:
|
||||
|
||||
if (light_angle && !facing_opaque) // Directional lighting coordinate filter.
|
||||
test_x = T.x - test_x_offset
|
||||
test_y = T.y - test_y_offset
|
||||
|
||||
// if the signs of both of these are NOT the same, the point is NOT within the cone.
|
||||
if ((PSEUDO_WEDGE(limit_a_x, limit_a_y, test_x, test_y) > 0) || (PSEUDO_WEDGE(test_x, test_y, limit_b_x, limit_b_y) > 0))
|
||||
continue
|
||||
|
||||
if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(T) || T.light_sources)
|
||||
Tcorners = T.corners
|
||||
if (!T.lighting_corners_initialised)
|
||||
T.lighting_corners_initialised = TRUE
|
||||
|
||||
if (!Tcorners)
|
||||
T.corners = list(null, null, null, null)
|
||||
Tcorners = T.corners
|
||||
|
||||
for (var/i = 1 to 4)
|
||||
if (Tcorners[i])
|
||||
continue
|
||||
|
||||
Tcorners[i] = new /datum/lighting_corner(T, LIGHTING_CORNER_DIAGONAL[i])
|
||||
|
||||
if (!T.has_opaque_atom)
|
||||
for(var/v = 1 to 4)
|
||||
var/val = Tcorners[v]
|
||||
if(val)
|
||||
corners[val] = 0
|
||||
|
||||
|
||||
|
||||
turfs += T
|
||||
|
||||
// Note: above is defined on ALL turfs, but below is only defined on OPEN TURFS.
|
||||
|
||||
// Upwards lights are handled at the corner level, so only search down.
|
||||
if (T && (T.z_flags & ZM_MIMIC_BELOW) && T.below)
|
||||
T = T.below
|
||||
goto check_t
|
||||
|
||||
END_FOR_DVIEW
|
||||
|
||||
LAZYINITLIST(affecting_turfs)
|
||||
|
||||
var/list/L = turfs - affecting_turfs // New turfs, add us to the affecting lights of them.
|
||||
affecting_turfs += L
|
||||
for (var/turf/affected_turf as anything in L)
|
||||
if(!QDELETED(affected_turf))
|
||||
T = affected_turf
|
||||
LAZYADD(T.affecting_lights, src)
|
||||
|
||||
L = affecting_turfs - turfs // Now-gone turfs, remove us from the affecting lights.
|
||||
affecting_turfs -= L
|
||||
for (thing in L)
|
||||
T = thing
|
||||
LAZYREMOVE(T.affecting_lights, src)
|
||||
|
||||
LAZYINITLIST(effect_str)
|
||||
if (needs_update == LIGHTING_VIS_UPDATE)
|
||||
for (thing in corners - effect_str)
|
||||
C = thing
|
||||
LAZYADD(C.affecting, src)
|
||||
if (!C.active)
|
||||
effect_str[C] = 0
|
||||
continue
|
||||
|
||||
APPLY_CORNER_BY_HEIGHT(now)
|
||||
else
|
||||
L = corners - effect_str
|
||||
for (thing in L)
|
||||
C = thing
|
||||
LAZYADD(C.affecting, src)
|
||||
if (!C.active)
|
||||
effect_str[C] = 0
|
||||
continue
|
||||
|
||||
APPLY_CORNER_BY_HEIGHT(now)
|
||||
|
||||
for (thing in corners - L)
|
||||
C = thing
|
||||
if (!C.active)
|
||||
effect_str[C] = 0
|
||||
continue
|
||||
|
||||
APPLY_CORNER_BY_HEIGHT(now)
|
||||
|
||||
L = effect_str - corners
|
||||
for (thing in L)
|
||||
C = thing
|
||||
REMOVE_CORNER(C, now)
|
||||
LAZYREMOVE(C.affecting, src)
|
||||
|
||||
effect_str -= L
|
||||
|
||||
applied_lum_r = lum_r
|
||||
applied_lum_g = lum_g
|
||||
applied_lum_b = lum_b
|
||||
applied_lum_u = lum_u
|
||||
|
||||
UNSETEMPTY(effect_str)
|
||||
UNSETEMPTY(affecting_turfs)
|
||||
|
||||
#undef INTELLIGENT_UPDATE
|
||||
#undef PSEUDO_WEDGE
|
||||
@@ -1,197 +0,0 @@
|
||||
#ifdef ENABLE_SUNLIGHT
|
||||
|
||||
/datum/light_source/sunlight
|
||||
skip_falloff = TRUE
|
||||
|
||||
/datum/light_source/sunlight/update_corners()
|
||||
var/update = FALSE
|
||||
|
||||
if (QDELETED(source_atom))
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
if (source_atom.light_power != light_power)
|
||||
light_power = source_atom.light_power
|
||||
update = TRUE
|
||||
|
||||
if (source_atom.light_range != light_range)
|
||||
light_range = source_atom.light_range
|
||||
update = TRUE
|
||||
|
||||
if (!top_atom)
|
||||
top_atom = source_atom
|
||||
update = TRUE
|
||||
|
||||
if (top_atom.loc != source_turf)
|
||||
source_turf = top_atom.loc
|
||||
update = TRUE
|
||||
|
||||
if (!light_range || !light_power)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
if (isturf(top_atom))
|
||||
if (source_turf != top_atom)
|
||||
source_turf = top_atom
|
||||
update = TRUE
|
||||
else if (top_atom.loc != source_turf)
|
||||
source_turf = top_atom.loc
|
||||
update = TRUE
|
||||
|
||||
if (!source_turf)
|
||||
return // Somehow we've got a light in nullspace, no-op.
|
||||
|
||||
if (light_range && light_power && !applied)
|
||||
update = TRUE
|
||||
|
||||
if (source_atom.light_color != light_color)
|
||||
light_color = source_atom.light_color
|
||||
parse_light_color()
|
||||
update = TRUE
|
||||
|
||||
else if (applied_lum_r != lum_r || applied_lum_g != lum_g || applied_lum_b != lum_b)
|
||||
update = TRUE
|
||||
|
||||
if (update)
|
||||
needs_update = LIGHTING_CHECK_UPDATE
|
||||
else if (needs_update == LIGHTING_CHECK_UPDATE)
|
||||
return // No change.
|
||||
|
||||
var/list/datum/lighting_corner/corners = list()
|
||||
var/list/turf/turfs = list()
|
||||
var/thing
|
||||
var/datum/lighting_corner/C
|
||||
var/turf/T
|
||||
var/Tthing
|
||||
var/list/Tcorners
|
||||
|
||||
// We don't need no damn vis checks!
|
||||
for (Tthing in RANGE_TURFS(Ceiling(light_range), source_turf))
|
||||
T = Tthing
|
||||
if (GLOB.the_station_areas[T.loc])
|
||||
continue
|
||||
|
||||
check_t:
|
||||
|
||||
if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(T) || T.light_sources)
|
||||
Tcorners = T.corners
|
||||
if (!T.lighting_corners_initialised)
|
||||
T.lighting_corners_initialised = TRUE
|
||||
|
||||
if (!Tcorners)
|
||||
T.corners = list(null, null, null, null)
|
||||
Tcorners = T.corners
|
||||
|
||||
for (var/i = 1 to 4)
|
||||
if (Tcorners[i])
|
||||
continue
|
||||
|
||||
Tcorners[i] = new /datum/lighting_corner(T, LIGHTING_CORNER_DIAGONAL[i])
|
||||
|
||||
if (!T.has_opaque_atom)
|
||||
corners[Tcorners[1]] = 0
|
||||
corners[Tcorners[2]] = 0
|
||||
corners[Tcorners[3]] = 0
|
||||
corners[Tcorners[4]] = 0
|
||||
|
||||
turfs += T
|
||||
|
||||
CHECK_TICK
|
||||
|
||||
// Sunlight only checks downwards as it has no need to shine upwards, really.
|
||||
if (T && (T.z_flags & ZM_MIMIC_BELOW) && T.below)
|
||||
T = T.below
|
||||
goto check_t
|
||||
|
||||
LAZYINITLIST(affecting_turfs)
|
||||
|
||||
var/list/L = turfs - affecting_turfs // New turfs, add us to the affecting lights of them.
|
||||
affecting_turfs += L
|
||||
for (thing in L)
|
||||
T = thing
|
||||
LAZYADD(T.affecting_lights, src)
|
||||
|
||||
L = affecting_turfs - turfs // Now-gone turfs, remove us from the affecting lights.
|
||||
affecting_turfs -= L
|
||||
for (thing in L)
|
||||
T = thing
|
||||
LAZYREMOVE(T.affecting_lights, src)
|
||||
|
||||
LAZYINITLIST(effect_str)
|
||||
if (needs_update == LIGHTING_VIS_UPDATE)
|
||||
for (thing in corners - effect_str)
|
||||
C = thing
|
||||
LAZYADD(C.affecting, src)
|
||||
if (!C.active)
|
||||
effect_str[C] = 0
|
||||
continue
|
||||
|
||||
APPLY_CORNER_SIMPLE(C)
|
||||
else
|
||||
L = corners - effect_str
|
||||
for (thing in L)
|
||||
C = thing
|
||||
LAZYADD(C.affecting, src)
|
||||
if (!C.active)
|
||||
effect_str[C] = 0
|
||||
continue
|
||||
|
||||
APPLY_CORNER_SIMPLE(C)
|
||||
|
||||
for (thing in corners - L)
|
||||
C = thing
|
||||
if (!C.active)
|
||||
effect_str[C] = 0
|
||||
continue
|
||||
|
||||
APPLY_CORNER_SIMPLE(C)
|
||||
|
||||
L = effect_str - corners
|
||||
for (thing in L)
|
||||
C = thing
|
||||
REMOVE_CORNER(C, FALSE)
|
||||
LAZYREMOVE(C.affecting, src)
|
||||
|
||||
effect_str -= L
|
||||
|
||||
applied_lum_r = lum_r
|
||||
applied_lum_g = lum_g
|
||||
applied_lum_b = lum_b
|
||||
applied_lum_u = lum_u
|
||||
|
||||
UNSETEMPTY(effect_str)
|
||||
UNSETEMPTY(affecting_turfs)
|
||||
|
||||
/datum/light_source/sunlight/regenerate_angle()
|
||||
return
|
||||
|
||||
#define QUEUE_UPDATE(level) \
|
||||
if (needs_update == LIGHTING_NO_UPDATE) \
|
||||
SSlighting.light_queue += src; \
|
||||
if (needs_update < level) \
|
||||
needs_update = level;
|
||||
|
||||
/datum/light_source/sunlight/update(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.
|
||||
LAZYREMOVE(top_atom.light_sources, src)
|
||||
|
||||
top_atom = new_top_atom
|
||||
|
||||
if (top_atom != source_atom)
|
||||
LAZYADD(top_atom.light_sources, src) // Add ourselves to the light sources of our new top atom.
|
||||
|
||||
//L_PROF(source_atom, "source_update")
|
||||
|
||||
QUEUE_UPDATE(LIGHTING_CHECK_UPDATE)
|
||||
|
||||
/datum/light_source/sunlight/force_update()
|
||||
QUEUE_UPDATE(LIGHTING_FORCE_UPDATE)
|
||||
|
||||
/datum/light_source/sunlight/vis_update()
|
||||
QUEUE_UPDATE(LIGHTING_VIS_UPDATE)
|
||||
|
||||
#undef QUEUE_UPDATE
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
/area
|
||||
///Whether this area allows static lighting and thus loads the lighting objects
|
||||
var/static_lighting = TRUE
|
||||
|
||||
//Non static lighting areas.
|
||||
//Any lighting area that wont support static lights.
|
||||
//These areas will NOT have corners generated.
|
||||
|
||||
/area/space
|
||||
static_lighting = FALSE
|
||||
base_lighting_alpha = 255
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
/atom
|
||||
///The light source, datum. Dont fuck with this directly
|
||||
var/tmp/datum/static_light_source/static_light
|
||||
///Static light sources currently attached to this atom, this includes ones owned by atoms inside this atom
|
||||
var/tmp/list/static_light_sources
|
||||
|
||||
///Pretty simple, just updates static lights on this atom
|
||||
/atom/proc/static_update_light()
|
||||
set waitfor = FALSE
|
||||
if (QDELETED(src))
|
||||
return
|
||||
|
||||
if (!light_power || !light_range) // We won't emit light anyways, destroy the light source.
|
||||
QDEL_NULL(static_light)
|
||||
else
|
||||
if(!ismovableatom(loc)) // We choose what atom should be the top atom of the light here.
|
||||
. = src
|
||||
else
|
||||
. = loc
|
||||
|
||||
if(static_light) // Update the light or create it if it does not exist.
|
||||
static_light.update(.)
|
||||
else
|
||||
static_light = new/datum/static_light_source(src, .)
|
||||
@@ -0,0 +1,176 @@
|
||||
// Because we can control each corner of every lighting object.
|
||||
// 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.
|
||||
|
||||
/datum/static_lighting_corner
|
||||
var/list/datum/static_light_source/affecting // Light sources affecting us.
|
||||
|
||||
var/x = 0
|
||||
var/y = 0
|
||||
|
||||
var/turf/master_NE
|
||||
var/turf/master_SE
|
||||
var/turf/master_SW
|
||||
var/turf/master_NW
|
||||
|
||||
var/lum_r = 0
|
||||
var/lum_g = 0
|
||||
var/lum_b = 0
|
||||
|
||||
//true color values, guaranteed to be between 0 and 1
|
||||
var/cache_r = LIGHTING_SOFT_THRESHOLD
|
||||
var/cache_g = LIGHTING_SOFT_THRESHOLD
|
||||
var/cache_b = LIGHTING_SOFT_THRESHOLD
|
||||
|
||||
///the maximum of lum_r, lum_g, and lum_b. if this is > 1 then the three cached color values are divided by this
|
||||
var/largest_color_luminosity = 0
|
||||
|
||||
///whether we are to be added to SSlighting's corners_queue list for an update
|
||||
var/needs_update = FALSE
|
||||
|
||||
/datum/static_lighting_corner/New(turf/new_turf, diagonal)
|
||||
. = ..()
|
||||
save_master(new_turf, turn(diagonal, 180))
|
||||
|
||||
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/new_master_turf
|
||||
|
||||
// Diagonal one is easy.
|
||||
new_master_turf = get_step(new_turf, diagonal)
|
||||
if(new_master_turf) // In case we're on the map's border.
|
||||
save_master(new_master_turf, diagonal)
|
||||
|
||||
// Now the horizontal one.
|
||||
new_master_turf = get_step(new_turf, horizontal)
|
||||
if(new_master_turf) // Ditto.
|
||||
save_master(new_master_turf, ((new_master_turf.x > x) ? EAST : WEST) | ((new_master_turf.y > y) ? NORTH : SOUTH)) // Get the dir based on coordinates.
|
||||
|
||||
// And finally the vertical one.
|
||||
new_master_turf = get_step(new_turf, vertical)
|
||||
if (new_master_turf)
|
||||
save_master(new_master_turf, ((new_master_turf.x > x) ? EAST : WEST) | ((new_master_turf.y > y) ? NORTH : SOUTH)) // Get the dir based on coordinates.
|
||||
|
||||
/datum/static_lighting_corner/proc/save_master(turf/master, dir)
|
||||
switch (dir)
|
||||
if (NORTHEAST)
|
||||
master_NE = master
|
||||
master.lighting_corner_SW = src
|
||||
if (SOUTHEAST)
|
||||
master_SE = master
|
||||
master.lighting_corner_NW = src
|
||||
if (SOUTHWEST)
|
||||
master_SW = master
|
||||
master.lighting_corner_NE = src
|
||||
if (NORTHWEST)
|
||||
master_NW = master
|
||||
master.lighting_corner_SE = src
|
||||
|
||||
/datum/static_lighting_corner/proc/self_destruct_if_idle()
|
||||
if (!LAZYLEN(affecting))
|
||||
qdel(src, force = TRUE)
|
||||
|
||||
/datum/static_lighting_corner/proc/vis_update()
|
||||
for (var/datum/static_light_source/light_source as anything in affecting)
|
||||
light_source.vis_update()
|
||||
|
||||
/datum/static_lighting_corner/proc/full_update()
|
||||
for (var/datum/static_light_source/light_source as anything in affecting)
|
||||
light_source.recalc_corner(src)
|
||||
|
||||
// God that was a mess, now to do the rest of the corner code! Hooray!
|
||||
/datum/static_lighting_corner/proc/update_lumcount(delta_r, delta_g, delta_b)
|
||||
|
||||
if(!(delta_r || delta_g || delta_b)) // 0 is falsey ok
|
||||
return
|
||||
|
||||
lum_r += delta_r
|
||||
lum_g += delta_g
|
||||
lum_b += delta_b
|
||||
|
||||
if(!needs_update)
|
||||
needs_update = TRUE
|
||||
SSlighting.corners_queue += src
|
||||
|
||||
/datum/static_lighting_corner/proc/update_objects()
|
||||
// Cache these values a head of time so 4 individual lighting objects don't all calculate them individually.
|
||||
var/lum_r = src.lum_r
|
||||
var/lum_g = src.lum_g
|
||||
var/lum_b = src.lum_b
|
||||
var/largest_color_luminosity = max(lum_r, lum_g, lum_b) // Scale it so one of them is the strongest lum, if it is above 1.
|
||||
. = 1 // factor
|
||||
if (largest_color_luminosity > 1)
|
||||
. = 1 / largest_color_luminosity
|
||||
|
||||
#if LIGHTING_SOFT_THRESHOLD != 0
|
||||
else if (largest_color_luminosity < LIGHTING_SOFT_THRESHOLD)
|
||||
. = 0 // 0 means soft lighting.
|
||||
|
||||
cache_r = round(lum_r * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
|
||||
cache_g = round(lum_g * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
|
||||
cache_b = round(lum_b * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
|
||||
#else
|
||||
cache_r = round(lum_r * ., LIGHTING_ROUND_VALUE)
|
||||
cache_g = round(lum_g * ., LIGHTING_ROUND_VALUE)
|
||||
cache_b = round(lum_b * ., LIGHTING_ROUND_VALUE)
|
||||
#endif
|
||||
|
||||
src.largest_color_luminosity = round(largest_color_luminosity, LIGHTING_ROUND_VALUE)
|
||||
|
||||
var/datum/static_lighting_object/lighting_object = master_NE?.static_lighting_object
|
||||
if (lighting_object && !lighting_object.needs_update)
|
||||
lighting_object.needs_update = TRUE
|
||||
SSlighting.objects_queue += lighting_object
|
||||
|
||||
lighting_object = master_SE?.static_lighting_object
|
||||
if (lighting_object && !lighting_object.needs_update)
|
||||
lighting_object.needs_update = TRUE
|
||||
SSlighting.objects_queue += lighting_object
|
||||
|
||||
lighting_object = master_SW?.static_lighting_object
|
||||
if (lighting_object && !lighting_object.needs_update)
|
||||
lighting_object.needs_update = TRUE
|
||||
SSlighting.objects_queue += lighting_object
|
||||
|
||||
lighting_object = master_NW?.static_lighting_object
|
||||
if (lighting_object && !lighting_object.needs_update)
|
||||
lighting_object.needs_update = TRUE
|
||||
SSlighting.objects_queue += lighting_object
|
||||
|
||||
self_destruct_if_idle()
|
||||
|
||||
|
||||
/datum/static_lighting_corner/dummy/New()
|
||||
return
|
||||
|
||||
|
||||
/datum/static_lighting_corner/Destroy(force)
|
||||
if (!force)
|
||||
return QDEL_HINT_LETMELIVE
|
||||
|
||||
for(var/datum/static_light_source/light_source as anything in affecting)
|
||||
LAZYREMOVE(light_source.effect_str, src)
|
||||
affecting = null
|
||||
|
||||
if(master_NE)
|
||||
master_NE.lighting_corner_SW = null
|
||||
master_NE.lighting_corners_initialised = FALSE
|
||||
if(master_SE)
|
||||
master_SE.lighting_corner_NW = null
|
||||
master_SE.lighting_corners_initialised = FALSE
|
||||
if(master_SW)
|
||||
master_SW.lighting_corner_NE = null
|
||||
master_SW.lighting_corners_initialised = FALSE
|
||||
if(master_NW)
|
||||
master_NW.lighting_corner_SE = null
|
||||
master_NW.lighting_corners_initialised = FALSE
|
||||
if(needs_update)
|
||||
SSlighting.corners_queue -= src
|
||||
return ..()
|
||||
@@ -0,0 +1,115 @@
|
||||
/datum/static_lighting_object
|
||||
///the underlay we are currently applying to our turf to apply light
|
||||
var/mutable_appearance/current_underlay
|
||||
|
||||
///whether we are already in the SSlighting.objects_queue list
|
||||
var/needs_update = FALSE
|
||||
|
||||
///the turf that our light is applied to
|
||||
var/turf/affected_turf
|
||||
|
||||
/datum/static_lighting_object/New(turf/source)
|
||||
if(!isturf(source))
|
||||
qdel(src, force=TRUE)
|
||||
stack_trace("a lighting object was assigned to [source], a non turf! ")
|
||||
return
|
||||
..()
|
||||
|
||||
current_underlay = mutable_appearance(LIGHTING_ICON, "transparent", FLOAT_LAYER, LIGHTING_PLANE, 255, RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM)
|
||||
|
||||
affected_turf = source
|
||||
if (affected_turf.static_lighting_object)
|
||||
qdel(affected_turf.static_lighting_object, force = TRUE)
|
||||
stack_trace("a lighting object was assigned to a turf that already had a lighting object!")
|
||||
|
||||
affected_turf.static_lighting_object = src
|
||||
affected_turf.luminosity = 0
|
||||
|
||||
needs_update = TRUE
|
||||
SSlighting.objects_queue += src
|
||||
|
||||
/datum/static_lighting_object/Destroy(force)
|
||||
if (!force)
|
||||
return QDEL_HINT_LETMELIVE
|
||||
SSlighting.objects_queue -= src
|
||||
if (isturf(affected_turf))
|
||||
affected_turf.static_lighting_object = null
|
||||
affected_turf.luminosity = 1
|
||||
affected_turf.underlays -= current_underlay
|
||||
affected_turf = null
|
||||
return ..()
|
||||
|
||||
/datum/static_lighting_object/proc/update()
|
||||
|
||||
// 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.
|
||||
|
||||
var/static/datum/static_lighting_corner/dummy/dummy_lighting_corner = new
|
||||
|
||||
var/datum/static_lighting_corner/red_corner = affected_turf.lighting_corner_SW || dummy_lighting_corner
|
||||
var/datum/static_lighting_corner/green_corner = affected_turf.lighting_corner_SE || dummy_lighting_corner
|
||||
var/datum/static_lighting_corner/blue_corner = affected_turf.lighting_corner_NW || dummy_lighting_corner
|
||||
var/datum/static_lighting_corner/alpha_corner = affected_turf.lighting_corner_NE || dummy_lighting_corner
|
||||
|
||||
var/max = max(red_corner.largest_color_luminosity, green_corner.largest_color_luminosity, blue_corner.largest_color_luminosity, alpha_corner.largest_color_luminosity)
|
||||
|
||||
var/rr = red_corner.cache_r
|
||||
var/rg = red_corner.cache_g
|
||||
var/rb = red_corner.cache_b
|
||||
|
||||
var/gr = green_corner.cache_r
|
||||
var/gg = green_corner.cache_g
|
||||
var/gb = green_corner.cache_b
|
||||
|
||||
var/br = blue_corner.cache_r
|
||||
var/bg = blue_corner.cache_g
|
||||
var/bb = blue_corner.cache_b
|
||||
|
||||
var/ar = alpha_corner.cache_r
|
||||
var/ag = alpha_corner.cache_g
|
||||
var/ab = alpha_corner.cache_b
|
||||
|
||||
#if LIGHTING_SOFT_THRESHOLD != 0
|
||||
var/set_luminosity = max > LIGHTING_SOFT_THRESHOLD
|
||||
#else
|
||||
// Because of floating points�?, it won't even be a flat 0.
|
||||
// This number is mostly arbitrary.
|
||||
var/set_luminosity = max > 1e-6
|
||||
#endif
|
||||
|
||||
if((rr & gr & br & ar) && (rg + gg + bg + ag + rb + gb + bb + ab == 8))
|
||||
//anything that passes the first case is very likely to pass the second, and addition is a little faster in this case
|
||||
affected_turf.underlays -= current_underlay
|
||||
current_underlay.icon_state = "lighting_transparent"
|
||||
current_underlay.color = null
|
||||
affected_turf.underlays += current_underlay
|
||||
else if(!set_luminosity)
|
||||
affected_turf.underlays -= current_underlay
|
||||
current_underlay.icon_state = "lighting_dark"
|
||||
current_underlay.color = null
|
||||
affected_turf.underlays += current_underlay
|
||||
else
|
||||
affected_turf.underlays -= current_underlay
|
||||
current_underlay.icon_state = null
|
||||
current_underlay.color = list(
|
||||
rr, rg, rb, 00,
|
||||
gr, gg, gb, 00,
|
||||
br, bg, bb, 00,
|
||||
ar, ag, ab, 00,
|
||||
00, 00, 00, 01
|
||||
)
|
||||
|
||||
affected_turf.underlays += current_underlay
|
||||
|
||||
var/area/A = affected_turf.loc
|
||||
//We are luminous
|
||||
if(set_luminosity)
|
||||
affected_turf.luminosity = set_luminosity
|
||||
//We are not lit by static light OR dynamic light.
|
||||
else if(!LAZYLEN(affected_turf.hybrid_lights_affecting) && !A.base_lighting_alpha)
|
||||
affected_turf.luminosity = 0
|
||||
@@ -0,0 +1,10 @@
|
||||
/proc/create_all_lighting_objects()
|
||||
for(var/area/A in world)
|
||||
|
||||
if(!A.static_lighting)
|
||||
continue
|
||||
|
||||
for(var/turf/T in A)
|
||||
new/datum/static_lighting_object(T)
|
||||
CHECK_TICK
|
||||
CHECK_TICK
|
||||
@@ -0,0 +1,306 @@
|
||||
// This is where the fun begins.
|
||||
// These are the main datums that emit light.
|
||||
|
||||
/datum/static_light_source
|
||||
///The atom we're emitting light from (for example a mob if we're from a flashlight that's being held).
|
||||
var/atom/top_atom
|
||||
///The atom that we belong to.
|
||||
var/atom/source_atom
|
||||
|
||||
///The turf under the source atom.
|
||||
var/turf/source_turf
|
||||
///The turf the top_atom appears to over.
|
||||
var/turf/pixel_turf
|
||||
///Intensity of the emitter light.
|
||||
var/light_power
|
||||
/// The range of the emitted light.
|
||||
var/light_range
|
||||
/// The colour of the light, string, decomposed by parse_light_color()
|
||||
var/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
|
||||
|
||||
/// List used to store how much we're affecting corners.
|
||||
var/list/datum/static_lighting_corner/effect_str
|
||||
|
||||
/// Whether we have applied our light yet or not.
|
||||
var/applied = FALSE
|
||||
|
||||
/// whether we are to be added to SSlighting's static_sources_queue list for an update
|
||||
var/needs_update = LIGHTING_NO_UPDATE
|
||||
|
||||
// Thanks to Lohikar for flinging this tiny bit of code at me, increasing my brain cell count from 1 to 2 in the process.
|
||||
// This macro will only offset up to 1 tile, but anything with a greater offset is an outlier and probably should handle its own lighting offsets.
|
||||
// Anything pixelshifted 16px or more will be considered on the next tile.
|
||||
#define GET_APPROXIMATE_PIXEL_DIR(PX, PY) ((!(PX) ? 0 : ((PX >= 16 ? EAST : (PX <= -16 ? WEST : 0)))) | (!PY ? 0 : (PY >= 16 ? NORTH : (PY <= -16 ? SOUTH : 0))))
|
||||
#define UPDATE_APPROXIMATE_PIXEL_TURF var/_mask = GET_APPROXIMATE_PIXEL_DIR(top_atom.pixel_x, top_atom.pixel_y); pixel_turf = _mask ? (get_step(source_turf, _mask) || source_turf) : source_turf
|
||||
|
||||
/datum/static_light_source/New(atom/owner, atom/top)
|
||||
source_atom = owner // Set our new owner.
|
||||
LAZYADD(source_atom.static_light_sources, src)
|
||||
top_atom = top
|
||||
if (top_atom != source_atom)
|
||||
LAZYADD(top_atom.static_light_sources, src)
|
||||
|
||||
source_turf = top_atom
|
||||
UPDATE_APPROXIMATE_PIXEL_TURF
|
||||
|
||||
light_power = source_atom.light_power
|
||||
light_range = source_atom.light_range
|
||||
light_color = source_atom.light_color
|
||||
|
||||
PARSE_LIGHT_COLOR(src)
|
||||
|
||||
update()
|
||||
|
||||
/datum/static_light_source/Destroy(force)
|
||||
remove_lum()
|
||||
if (source_atom)
|
||||
LAZYREMOVE(source_atom.static_light_sources, src)
|
||||
|
||||
if (top_atom)
|
||||
LAZYREMOVE(top_atom.static_light_sources, src)
|
||||
|
||||
if (needs_update)
|
||||
SSlighting.static_sources_queue -= src
|
||||
return ..()
|
||||
|
||||
#define EFFECT_UPDATE(level) \
|
||||
if (needs_update == LIGHTING_NO_UPDATE) \
|
||||
SSlighting.static_sources_queue += src; \
|
||||
if (needs_update < level) \
|
||||
needs_update = level; \
|
||||
|
||||
|
||||
/// This proc will cause the light source to update the top atom, and add itself to the update queue.
|
||||
/datum/static_light_source/proc/update(atom/new_top_atom)
|
||||
// This top atom is different.
|
||||
if (new_top_atom && new_top_atom != top_atom)
|
||||
if(top_atom != source_atom && top_atom.static_light_sources) // Remove ourselves from the light sources of that top atom.
|
||||
LAZYREMOVE(top_atom.static_light_sources, src)
|
||||
|
||||
top_atom = new_top_atom
|
||||
|
||||
if (top_atom != source_atom)
|
||||
LAZYADD(top_atom.static_light_sources, src) // Add ourselves to the light sources of our new top atom.
|
||||
|
||||
EFFECT_UPDATE(LIGHTING_CHECK_UPDATE)
|
||||
|
||||
/// Will force an update without checking if it's actually needed.
|
||||
/datum/static_light_source/proc/force_update()
|
||||
EFFECT_UPDATE(LIGHTING_FORCE_UPDATE)
|
||||
|
||||
/// Will cause the light source to recalculate turfs that were removed or added to visibility only.
|
||||
/datum/static_light_source/proc/vis_update()
|
||||
EFFECT_UPDATE(LIGHTING_VIS_UPDATE)
|
||||
|
||||
// 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.
|
||||
|
||||
//Original lighting falloff calculation. This looks the best out of the three. However, this is also the most expensive.
|
||||
//#define LUM_FALLOFF(C, T) (1 - CLAMP01(sqrt((C.x - T.x) ** 2 + (C.y - T.y) ** 2 + LIGHTING_HEIGHT) / max(1, light_range)))
|
||||
|
||||
//Cubic lighting falloff. This has the *exact* same range as the original lighting falloff calculation, down to the exact decimal, but it looks a little unnatural due to the harsher falloff and how it's generally brighter across the board.
|
||||
//#define LUM_FALLOFF(C, T) (1 - CLAMP01((((C.x - T.x) * (C.x - T.x)) + ((C.y - T.y) * (C.y - T.y)) + LIGHTING_HEIGHT) / max(1, light_range*light_range)))
|
||||
|
||||
//Linear lighting falloff. This resembles the original lighting falloff calculation the best, but results in lights having a slightly larger range, which is most noticeable with large light sources. This also results in lights being diamond-shaped, fuck. This looks the darkest out of the three due to how lights are brighter closer to the source compared to the original falloff algorithm. This falloff method also does not at all take into account lighting height, as it acts as a flat reduction to light range with this method.
|
||||
//#define LUM_FALLOFF(C, T) (1 - CLAMP01(((abs(C.x - T.x) + abs(C.y - T.y))) / max(1, light_range+1)))
|
||||
|
||||
//Linear lighting falloff but with an octagonal shape in place of a diamond shape. Lummox JR please add pointer support.
|
||||
#define GET_LUM_DIST(DISTX, DISTY) (DISTX + DISTY + abs(DISTX - DISTY)*0.4)
|
||||
#define LUM_FALLOFF(C, T) (1 - CLAMP01(max(GET_LUM_DIST(abs(C.x - T.x), abs(C.y - T.y)),LIGHTING_HEIGHT) / max(1, light_range+1)))
|
||||
|
||||
#define APPLY_CORNER(C) \
|
||||
. = LUM_FALLOFF(C, pixel_turf); \
|
||||
. *= light_power; \
|
||||
var/OLD = effect_str[C]; \
|
||||
C.update_lumcount \
|
||||
( \
|
||||
(. * lum_r) - (OLD * applied_lum_r), \
|
||||
(. * lum_g) - (OLD * applied_lum_g), \
|
||||
(. * lum_b) - (OLD * applied_lum_b) \
|
||||
);
|
||||
|
||||
#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.
|
||||
/datum/static_light_source/proc/remove_lum()
|
||||
applied = FALSE
|
||||
for(var/datum/static_lighting_corner/corner as anything in effect_str)
|
||||
REMOVE_CORNER(corner)
|
||||
LAZYREMOVE(corner.affecting, src)
|
||||
|
||||
effect_str = null
|
||||
|
||||
/datum/static_light_source/proc/recalc_corner(datum/static_lighting_corner/corner)
|
||||
LAZYINITLIST(effect_str)
|
||||
if (effect_str[corner]) // Already have one.
|
||||
REMOVE_CORNER(corner)
|
||||
effect_str[corner] = 0
|
||||
|
||||
APPLY_CORNER(corner)
|
||||
effect_str[corner] = .
|
||||
|
||||
/datum/static_light_source/proc/update_corners()
|
||||
var/update = FALSE
|
||||
var/atom/source_atom = src.source_atom
|
||||
|
||||
if (QDELETED(source_atom))
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
if (source_atom.light_power != light_power)
|
||||
light_power = source_atom.light_power
|
||||
update = TRUE
|
||||
|
||||
if (source_atom.light_range != light_range)
|
||||
light_range = source_atom.light_range
|
||||
update = TRUE
|
||||
|
||||
if (!top_atom)
|
||||
top_atom = source_atom
|
||||
update = TRUE
|
||||
|
||||
if (!light_range || !light_power)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
if (isturf(top_atom))
|
||||
if (source_turf != top_atom)
|
||||
source_turf = top_atom
|
||||
UPDATE_APPROXIMATE_PIXEL_TURF
|
||||
update = TRUE
|
||||
else if (top_atom.loc != source_turf)
|
||||
source_turf = top_atom.loc
|
||||
UPDATE_APPROXIMATE_PIXEL_TURF
|
||||
update = TRUE
|
||||
else
|
||||
var/pixel_loc = get_turf_pixel(top_atom)
|
||||
if (pixel_loc != pixel_turf)
|
||||
pixel_turf = pixel_loc
|
||||
update = TRUE
|
||||
|
||||
if (!isturf(source_turf))
|
||||
if (applied)
|
||||
remove_lum()
|
||||
return
|
||||
|
||||
if (light_range && light_power && !applied)
|
||||
update = TRUE
|
||||
|
||||
if (source_atom.light_color != light_color)
|
||||
light_color = source_atom.light_color
|
||||
PARSE_LIGHT_COLOR(src)
|
||||
update = TRUE
|
||||
|
||||
else if (applied_lum_r != lum_r || applied_lum_g != lum_g || applied_lum_b != lum_b)
|
||||
update = TRUE
|
||||
|
||||
if (update)
|
||||
needs_update = LIGHTING_CHECK_UPDATE
|
||||
applied = TRUE
|
||||
else if (needs_update == LIGHTING_CHECK_UPDATE)
|
||||
return //nothing's changed
|
||||
|
||||
var/list/datum/static_lighting_corner/corners = list()
|
||||
var/list/turf/turfs = list()
|
||||
if (source_turf)
|
||||
var/oldlum = source_turf.luminosity
|
||||
source_turf.luminosity = ceil(light_range)
|
||||
for(var/turf/T in view(ceil(light_range), source_turf))
|
||||
if(!IS_OPAQUE_TURF(T))
|
||||
if (!T.lighting_corners_initialised)
|
||||
T.static_generate_missing_corners()
|
||||
corners[T.lighting_corner_NE] = 0
|
||||
corners[T.lighting_corner_SE] = 0
|
||||
corners[T.lighting_corner_SW] = 0
|
||||
corners[T.lighting_corner_NW] = 0
|
||||
turfs += T
|
||||
|
||||
var/turf/above = GET_TURF_ABOVE(T)
|
||||
|
||||
while(above && istransparentturf(above))
|
||||
if (!above.lighting_corners_initialised)
|
||||
above.static_generate_missing_corners()
|
||||
corners[above.lighting_corner_NE] = 0
|
||||
corners[above.lighting_corner_SE] = 0
|
||||
corners[above.lighting_corner_SW] = 0
|
||||
corners[above.lighting_corner_NW] = 0
|
||||
|
||||
above = GET_TURF_ABOVE(above)
|
||||
|
||||
turfs += above
|
||||
|
||||
var/turf/below = GET_TURF_BELOW(T)
|
||||
var/turf/previous = T
|
||||
|
||||
while(below && istransparentturf(previous))
|
||||
if (!below.lighting_corners_initialised)
|
||||
below.static_generate_missing_corners()
|
||||
corners[below.lighting_corner_NE] = 0
|
||||
corners[below.lighting_corner_SE] = 0
|
||||
corners[below.lighting_corner_SW] = 0
|
||||
corners[below.lighting_corner_NW] = 0
|
||||
|
||||
previous = below
|
||||
below = GET_TURF_BELOW(below)
|
||||
|
||||
source_turf.luminosity = oldlum
|
||||
|
||||
var/list/datum/static_lighting_corner/new_corners = (corners - effect_str)
|
||||
LAZYINITLIST(effect_str)
|
||||
if (needs_update == LIGHTING_VIS_UPDATE)
|
||||
for (var/datum/static_lighting_corner/corner as anything in new_corners)
|
||||
APPLY_CORNER(corner)
|
||||
if (. != 0)
|
||||
LAZYADD(corner.affecting, src)
|
||||
effect_str[corner] = .
|
||||
else
|
||||
for (var/datum/static_lighting_corner/corner as anything in new_corners)
|
||||
APPLY_CORNER(corner)
|
||||
if (. != 0)
|
||||
LAZYADD(corner.affecting, src)
|
||||
effect_str[corner] = .
|
||||
|
||||
for (var/datum/static_lighting_corner/corner as anything in corners - new_corners) // Existing corners
|
||||
APPLY_CORNER(corner)
|
||||
if (. != 0)
|
||||
effect_str[corner] = .
|
||||
else
|
||||
LAZYREMOVE(corner.affecting, src)
|
||||
effect_str -= corner
|
||||
|
||||
var/list/datum/static_lighting_corner/gone_corners = effect_str - corners
|
||||
for (var/datum/static_lighting_corner/corner as anything in gone_corners)
|
||||
REMOVE_CORNER(corner)
|
||||
LAZYREMOVE(corner.affecting, src)
|
||||
effect_str -= gone_corners
|
||||
|
||||
applied_lum_r = lum_r
|
||||
applied_lum_g = lum_g
|
||||
applied_lum_b = lum_b
|
||||
|
||||
UNSETEMPTY(effect_str)
|
||||
|
||||
#undef EFFECT_UPDATE
|
||||
#undef LUM_FALLOFF
|
||||
#undef GET_LUM_DIST
|
||||
#undef REMOVE_CORNER
|
||||
#undef APPLY_CORNER
|
||||
@@ -0,0 +1,64 @@
|
||||
/turf
|
||||
var/tmp/lighting_corners_initialised = FALSE
|
||||
var/tmp/datum/static_lighting_object/static_lighting_object
|
||||
|
||||
///Lighting Corner datums.
|
||||
var/tmp/datum/static_lighting_corner/lighting_corner_NE
|
||||
var/tmp/datum/static_lighting_corner/lighting_corner_SE
|
||||
var/tmp/datum/static_lighting_corner/lighting_corner_SW
|
||||
var/tmp/datum/static_lighting_corner/lighting_corner_NW
|
||||
|
||||
/turf/proc/static_lighting_clear_overlay()
|
||||
if (static_lighting_object)
|
||||
qdel(static_lighting_object, TRUE)
|
||||
|
||||
/// Builds a lighting object for us, but only if our area is dynamic.
|
||||
/turf/proc/static_lighting_build_overlay(area/our_area = loc)
|
||||
if(static_lighting_object)
|
||||
qdel(static_lighting_object, force=TRUE) //Shitty fix for lighting objects persisting after death
|
||||
|
||||
new/datum/static_lighting_object(src)
|
||||
|
||||
|
||||
|
||||
// Returns a boolean whether the turf is on soft lighting.
|
||||
// Soft lighting being the threshold at which point the overlay considers
|
||||
// itself as too dark to allow sight and see_in_dark becomes useful.
|
||||
// So basically if this returns true the tile is unlit black.
|
||||
/turf/proc/static_is_softly_lit()
|
||||
if (!static_lighting_object)
|
||||
return FALSE
|
||||
|
||||
return !(luminosity || dynamic_lumcount)
|
||||
|
||||
///Transfer the lighting of one area to another
|
||||
/turf/proc/transfer_area_lighting(area/old_area, area/new_area)
|
||||
if(SSlighting.initialized)
|
||||
if (new_area.static_lighting != old_area.static_lighting)
|
||||
if (new_area.static_lighting)
|
||||
static_lighting_build_overlay(new_area)
|
||||
else
|
||||
static_lighting_clear_overlay()
|
||||
//Inherit overlay of new area
|
||||
if(old_area.lighting_effect)
|
||||
overlays -= old_area.lighting_effect
|
||||
if(new_area.lighting_effect)
|
||||
overlays += new_area.lighting_effect
|
||||
|
||||
|
||||
|
||||
/turf/proc/static_generate_missing_corners()
|
||||
if (!lighting_corner_NE)
|
||||
lighting_corner_NE = new/datum/static_lighting_corner(src, NORTH|EAST)
|
||||
|
||||
if (!lighting_corner_SE)
|
||||
lighting_corner_SE = new/datum/static_lighting_corner(src, SOUTH|EAST)
|
||||
|
||||
if (!lighting_corner_SW)
|
||||
lighting_corner_SW = new/datum/static_lighting_corner(src, SOUTH|WEST)
|
||||
|
||||
if (!lighting_corner_NW)
|
||||
lighting_corner_NW = new/datum/static_lighting_corner(src, NORTH|WEST)
|
||||
|
||||
lighting_corners_initialised = TRUE
|
||||
|
||||
@@ -1,184 +1,84 @@
|
||||
/turf
|
||||
var/dynamic_lighting = TRUE
|
||||
luminosity = 1
|
||||
///Estimates the light power based on the alpha of the light and the range.
|
||||
///Assumes a linear fallout at (0, alpha/255) to (range, 0)
|
||||
///Used for lightig mask lumcount calculations
|
||||
#define LIGHT_POWER_ESTIMATION(alpha, range, distance) max((alpha * (range - distance)) / (255 * range), 0)
|
||||
|
||||
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.
|
||||
|
||||
// Causes any affecting light sources to be queued for a visibility update, for example a door got opened.
|
||||
/// Causes any affecting light sources to be queued for a visibility update, for example a door got opened.
|
||||
/turf/proc/reconsider_lights()
|
||||
//L_PROF(src, "turf_reconsider")
|
||||
var/datum/light_source/L
|
||||
for (var/thing in affecting_lights)
|
||||
L = thing
|
||||
L.vis_update()
|
||||
//Consider static lights
|
||||
lighting_corner_NE?.vis_update()
|
||||
lighting_corner_SE?.vis_update()
|
||||
lighting_corner_SW?.vis_update()
|
||||
lighting_corner_NW?.vis_update()
|
||||
|
||||
// Forces a lighting update. Reconsider lights is preferred when possible.
|
||||
/turf/proc/force_update_lights()
|
||||
//L_PROF(src, "turf_forceupdate")
|
||||
var/datum/light_source/L
|
||||
for (var/thing in affecting_lights)
|
||||
L = thing
|
||||
L.force_update()
|
||||
|
||||
/turf/proc/lighting_clear_overlay()
|
||||
if (lighting_overlay)
|
||||
if (lighting_overlay.loc != src)
|
||||
var/turf/badT = lighting_overlay.loc
|
||||
crash_with("Lighting overlay variable on turf [DEBUG_REF(src)] is insane, lighting overlay actually located on [DEBUG_REF(lighting_overlay.loc)] at ([badT.x],[badT.y],[badT.z])!")
|
||||
|
||||
qdel(lighting_overlay, TRUE)
|
||||
lighting_overlay = null
|
||||
|
||||
//L_PROF(src, "turf_clear_overlay")
|
||||
|
||||
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
|
||||
|
||||
//L_PROF(src, "turf_build_overlay")
|
||||
|
||||
var/area/A = loc
|
||||
if (A.dynamic_lighting && dynamic_lighting)
|
||||
if (!lighting_corners_initialised || !corners)
|
||||
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, TRUE)
|
||||
|
||||
C.active = TRUE
|
||||
|
||||
// Returns the average color of this tile. Roughly corresponds to the color of a single old-style lighting overlay.
|
||||
/turf/proc/get_avg_color()
|
||||
if (!lighting_overlay)
|
||||
return null
|
||||
|
||||
var/lum_r
|
||||
var/lum_g
|
||||
var/lum_b
|
||||
|
||||
for (var/datum/lighting_corner/L in corners)
|
||||
lum_r += L.lum_r
|
||||
lum_g += L.lum_g
|
||||
lum_b += L.lum_b
|
||||
|
||||
lum_r = CLAMP01(lum_r / 4) * 255
|
||||
lum_g = CLAMP01(lum_g / 4) * 255
|
||||
lum_b = CLAMP01(lum_b / 4) * 255
|
||||
|
||||
return "#[num2hex(lum_r, 2)][num2hex(lum_g, 2)][num2hex(lum_g, 2)]"
|
||||
|
||||
#define SCALE(targ,min,max) (targ - min) / (max - min)
|
||||
//consider dynamic lights
|
||||
for(var/atom/movable/lighting_mask/mask as anything in hybrid_lights_affecting)
|
||||
mask.queue_mask_update()
|
||||
|
||||
// Used to get a scaled lumcount.
|
||||
/turf/proc/get_lumcount(minlum = 0, maxlum = 1)
|
||||
if (!lighting_overlay)
|
||||
return 0.5
|
||||
|
||||
var/totallums = 0
|
||||
for (var/datum/lighting_corner/L in corners)
|
||||
totallums += L.lum_r + L.lum_b + L.lum_g
|
||||
if (static_lighting_object)
|
||||
var/datum/static_lighting_corner/L
|
||||
L = lighting_corner_NE
|
||||
if (L)
|
||||
totallums += L.lum_r + L.lum_b + L.lum_g
|
||||
L = lighting_corner_SE
|
||||
if (L)
|
||||
totallums += L.lum_r + L.lum_b + L.lum_g
|
||||
L = lighting_corner_SW
|
||||
if (L)
|
||||
totallums += L.lum_r + L.lum_b + L.lum_g
|
||||
L = lighting_corner_NW
|
||||
if (L)
|
||||
totallums += L.lum_r + L.lum_b + L.lum_g
|
||||
|
||||
totallums /= 12 // 4 corners, each with 3 channels, get the average.
|
||||
totallums /= 12 // 4 corners, each with 3 channels, get the average.
|
||||
|
||||
totallums = SCALE(totallums, minlum, maxlum)
|
||||
totallums = (totallums - minlum) / (maxlum - minlum)
|
||||
|
||||
return CLAMP01(totallums)
|
||||
|
||||
// Gets the current UV illumination of the turf. Always 100% for space & other static-lit tiles.
|
||||
/turf/proc/get_uv_lumcount(minlum = 0, maxlum = 1)
|
||||
if (!lighting_overlay)
|
||||
return 1
|
||||
|
||||
L_PROF(src, "turf_get_uv")
|
||||
|
||||
var/totallums = 0
|
||||
for (var/datum/lighting_corner/L in corners)
|
||||
totallums += L.lum_u
|
||||
|
||||
totallums /= 4 // average of four corners.
|
||||
|
||||
totallums = SCALE(totallums, minlum, maxlum)
|
||||
|
||||
return CLAMP01(totallums)
|
||||
|
||||
#undef SCALE
|
||||
|
||||
// Can't think of a good name, this proc will recalculate the has_opaque_atom variable.
|
||||
/turf/proc/recalc_atom_opacity()
|
||||
#ifdef AO_USE_LIGHTING_OPACITY
|
||||
var/old = has_opaque_atom
|
||||
#endif
|
||||
|
||||
has_opaque_atom = FALSE
|
||||
if (opacity)
|
||||
has_opaque_atom = TRUE
|
||||
totallums = CLAMP01(totallums)
|
||||
else
|
||||
for (var/thing in src) // Loop through every movable atom on our tile
|
||||
var/atom/movable/A = thing
|
||||
if (A.opacity && !QDELETED(A))
|
||||
has_opaque_atom = TRUE
|
||||
break // No need to continue if we find something opaque.
|
||||
|
||||
#ifdef AO_USE_LIGHTING_OPACITY
|
||||
if (old != has_opaque_atom)
|
||||
regenerate_ao()
|
||||
#endif
|
||||
|
||||
/turf/Exited(atom/movable/Obj, 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/transfer_area_lighting(area/old_area, area/new_area)
|
||||
if (new_area.dynamic_lighting != old_area.dynamic_lighting)
|
||||
if (new_area.dynamic_lighting)
|
||||
lighting_build_overlay()
|
||||
totallums = 1
|
||||
|
||||
for(var/atom/movable/lighting_mask/mask as anything in hybrid_lights_affecting)
|
||||
if(mask.blend_mode == BLEND_ADD)
|
||||
totallums += LIGHT_POWER_ESTIMATION(mask.alpha, mask.radius, get_dist(src, get_turf(mask.attached_atom)))
|
||||
else
|
||||
lighting_clear_overlay()
|
||||
totallums -= LIGHT_POWER_ESTIMATION(mask.alpha, mask.radius, get_dist(src, get_turf(mask.attached_atom)))
|
||||
totallums += dynamic_lumcount
|
||||
return clamp(totallums, 0.0, 1.0)
|
||||
|
||||
// This is inlined in lighting_source.dm and lighting_source_novis.dm.
|
||||
// Update them too if you change this.
|
||||
/turf/proc/get_corners()
|
||||
if (!dynamic_lighting && !light_sources)
|
||||
return null
|
||||
|
||||
if (!lighting_corners_initialised)
|
||||
generate_missing_corners()
|
||||
|
||||
if (has_opaque_atom)
|
||||
return null // Since this proc gets used in a for loop, null won't be looped though.
|
||||
|
||||
return corners
|
||||
|
||||
// This is inlined in lighting_source.dm and lighting_source_novis.dm.
|
||||
// Update them too if you change this.
|
||||
/turf/proc/generate_missing_corners()
|
||||
if (!dynamic_lighting && !light_sources)
|
||||
///Proc to add movable sources of opacity on the turf and let it handle lighting code.
|
||||
/turf/proc/add_opacity_source(atom/movable/new_source)
|
||||
LAZYADD(opacity_sources, new_source)
|
||||
if(opacity)
|
||||
return
|
||||
recalculate_directional_opacity()
|
||||
|
||||
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
|
||||
///Proc to remove movable sources of opacity on the turf and let it handle lighting code.
|
||||
/turf/proc/remove_opacity_source(atom/movable/old_source)
|
||||
LAZYREMOVE(opacity_sources, old_source)
|
||||
if(opacity) //Still opaque, no need to worry on updating.
|
||||
return
|
||||
recalculate_directional_opacity()
|
||||
|
||||
corners[i] = new/datum/lighting_corner(src, LIGHTING_CORNER_DIAGONAL[i])
|
||||
|
||||
///Calculate on which directions this turfs block view.
|
||||
/turf/proc/recalculate_directional_opacity()
|
||||
. = directional_opacity
|
||||
if(opacity)
|
||||
directional_opacity = ALL_CARDINALS
|
||||
if(. != directional_opacity)
|
||||
reconsider_lights()
|
||||
return
|
||||
directional_opacity = NONE
|
||||
for(var/atom/movable/opacity_source as anything in opacity_sources)
|
||||
if(opacity_source.atom_flags & ATOM_FLAG_CHECKS_BORDER)
|
||||
directional_opacity |= opacity_source.dir
|
||||
else //If fulltile and opaque, then the whole tile blocks view, no need to continue checking.
|
||||
directional_opacity = ALL_CARDINALS
|
||||
break
|
||||
if(. != directional_opacity && (. == ALL_CARDINALS || directional_opacity == ALL_CARDINALS))
|
||||
reconsider_lights() //The lighting system only cares whether the tile is fully concealed from all directions or not.
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
GLOBAL_LIST_INIT(admin_verbs_lighting, list(
|
||||
/client/proc/lighting_hide_verbs,
|
||||
/client/proc/lighting_flush,
|
||||
/client/proc/lighting_reconsider_target,
|
||||
/client/proc/lighting_build_overlay,
|
||||
/client/proc/lighting_clear_overlay,
|
||||
/client/proc/lighting_toggle_profiling
|
||||
))
|
||||
|
||||
/client/proc/lighting_show_verbs()
|
||||
set category = "Debug"
|
||||
set name = "Show Lighting Verbs"
|
||||
set desc = "Shows the lighting debug verbs."
|
||||
|
||||
if (!check_rights(R_DEBUG|R_DEV)) return
|
||||
|
||||
to_chat(src, SPAN_NOTICE("Lighting debug verbs have been shown."))
|
||||
add_verb(src, GLOB.admin_verbs_lighting)
|
||||
|
||||
/client/proc/lighting_hide_verbs()
|
||||
set category = "Lighting"
|
||||
set name = "Hide Lighting Verbs"
|
||||
set desc = "Hides the lighting debug verbs."
|
||||
|
||||
if (!check_rights(R_DEBUG|R_DEV)) return
|
||||
|
||||
to_chat(src, SPAN_NOTICE("Lighting debug verbs have been hidden."))
|
||||
remove_verb(src, GLOB.admin_verbs_lighting)
|
||||
|
||||
/client/proc/lighting_flush()
|
||||
set category = "Lighting"
|
||||
set name = "Flush Work Queue"
|
||||
set desc = "Flushes the lighting processor's current work queue."
|
||||
|
||||
if (!check_rights(R_DEBUG|R_DEV)) return
|
||||
|
||||
if (alert("Flush Lighting Work Queue? This will invalidate all pending lighting updates.", "Reset Lighting", "No", "No", "Yes") != "Yes")
|
||||
return
|
||||
|
||||
log_and_message_admins("has flushed the lighting processor queues.")
|
||||
SSlighting.light_queue = list()
|
||||
SSlighting.corner_queue = list()
|
||||
SSlighting.overlay_queue = list()
|
||||
|
||||
/client/proc/lighting_reconsider_target(turf/T in world)
|
||||
set category = "Lighting"
|
||||
set name = "Reconsider Visibility"
|
||||
set desc = "Triggers a visibility update for a turf."
|
||||
|
||||
if (!check_rights(R_DEBUG|R_DEV)) return
|
||||
|
||||
if (TURF_IS_DYNAMICALLY_LIT(T))
|
||||
to_chat(src, "That turf is not dynamically lit.")
|
||||
return
|
||||
|
||||
log_and_message_admins("has triggered a lighting update for turf [REF(T)] - [T] at ([T.x],[T.y],[T.z]) in area [T.loc].")
|
||||
|
||||
T.reconsider_lights()
|
||||
|
||||
/client/proc/lighting_build_overlay(turf/T in world)
|
||||
set category = "Lighting"
|
||||
set name = "Build Overlay"
|
||||
set desc = "Builds a lighting overlay for a turf if it does not have one."
|
||||
|
||||
if (!check_rights(R_DEBUG|R_DEV)) return
|
||||
|
||||
if (T.lighting_overlay)
|
||||
to_chat(src, "That turf already has a lighting overlay.")
|
||||
return
|
||||
|
||||
log_and_message_admins("has generated a lighting overlay for turf [REF(T)] - [T] ([T.x],[T.y],[T.z]) in area [T.loc].")
|
||||
|
||||
T.lighting_build_overlay()
|
||||
|
||||
/client/proc/lighting_clear_overlay(turf/T in world)
|
||||
set category = "Lighting"
|
||||
set name = "Clear Overlay"
|
||||
set desc = "Clears a lighting overlay for a turf if it has one."
|
||||
|
||||
if (!check_rights(R_DEBUG|R_DEV)) return
|
||||
|
||||
if (!T.lighting_overlay)
|
||||
to_chat(src, "That turf doesn't have a lighting overlay.")
|
||||
return
|
||||
|
||||
log_and_message_admins("has cleared a lighting overlay for turf [REF(T)] - [T] ([T.x],[T.y],[T.z]) in area [T.loc].")
|
||||
|
||||
T.lighting_clear_overlay()
|
||||
|
||||
/client/proc/lighting_toggle_profiling()
|
||||
set category = "Lighting"
|
||||
set name = "Profile Lighting"
|
||||
set desc = "Spams the database with lighting updates. Y'know, just 'cause."
|
||||
|
||||
if (!check_rights(R_DEBUG|R_SERVER)) return
|
||||
|
||||
if (!establish_db_connection(GLOB.dbcon))
|
||||
to_chat(usr, SPAN_ALERT("Unable to start profiling: No active database connection."))
|
||||
return
|
||||
|
||||
GLOB.lighting_profiling = !GLOB.lighting_profiling
|
||||
log_and_message_admins("has [GLOB.lighting_profiling ? "enabled" : "disabled"] lighting profiling.")
|
||||
@@ -1,13 +0,0 @@
|
||||
#undef LIGHTING_HEIGHT
|
||||
|
||||
#undef LIGHTING_ICON
|
||||
|
||||
#undef LIGHTING_BASE_MATRIX
|
||||
|
||||
#undef LUM_FALLOFF
|
||||
#undef REMOVE_CORNER
|
||||
#undef APPLY_CORNER
|
||||
|
||||
#undef CALCULATE_CORNER_HEIGHT
|
||||
#undef APPLY_CORNER_BY_HEIGHT
|
||||
#undef APPLY_CORNER_SIMPLE
|
||||
Reference in New Issue
Block a user