mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2025-12-27 10:41:42 +00:00
Ports Goon Lighting from /vg/station. Summary - This adds smooth edges to all lighting in order to increase the aesthetic appeal of the lighting system. It works by using a matrix to change the appearance of the lighting overlay sprite, which has been changed to be a base for the matrix to modify. Ported from /vg/station with the help of @PJB3005. This lighting system is a hybrid between Mloc's lighting rewrite and Tobba's Goonlights. Notable changes: - Darksight now matters on mobs. The lighting overlays are not alpha=255 when they are completely dark, meaning you can still see the floor- assuming you can view the turf at all, because it retains the luminousity setting. - This means Tajaran have 100% night vision again as they are intended to. Humans can see in a 3x3 square centered around themselves. - No, I'm not changing this, if it's even possible. This is how BYOND's lighting is meant to work. If you have any complaints about game balance, please feel free to make a pull request to change see_in_dark values, which will be seperately evaluated. - The lighting controller now runs at world.tick_lag, to emulate the realtime function of StonedMC. So far with my testing, this hasn't caused any noticable performance decreases- the lighting system is obviously more expensive than our previous iteration, however, it's not the next ZAS ;) Technical Details: - /atom/movable/lighting_overlay/proc/get_clamped_lum has been removed, succeeded by /turf/proc/get_lumcount. They behave identically. - Turf lighting is actually controlled by four "corner" datums, which feed information into the overlay. - The way opacity is factored into the system has changed. Anything that doesn't use set_opacity is not going to work to block light. - /area/lighting_use_dynamic has been renamed to /area/dynamic_lighting, for consistency with /turf/dynamic_lighting. - Lighting is no longer seperately initialized for away missions. It is handled in ChangeTurf() as it should be. Known & Unfixable issues: - There is a 5-10 second delay from starting the round to the lights turning on. Attribute it to "the powernet being spun up" if you would like to- but it's actually just how long it takes the lighting system to update every single turf on the map. - When you walk with a light on you, the light will actually jump ahead of you before you visually get to the tile. This is because of the movement gliding on mobs, realtime lighting actually goes faster than the glide takes to complete, so it appears that your lights are moving faster than you. Thank you krausy~ Animated Goonlights This adds an animate() call to the update_overlays() proc. This makes it so that any light changes will smoothly transition between the changes instead of instantly changing their appearance. Also fixes a few issues pointed out on Github. Change lighting animation (turns out the old one totally breaks if you toggle a light quickly, whoopsies) Kill LIGHTING_INSTANT_UPDATES isturf
370 lines
9.6 KiB
Plaintext
370 lines
9.6 KiB
Plaintext
/area/awaymission/upperlevel
|
|
name = "Open Space"
|
|
color = "#888"
|
|
dynamic_lighting = 0
|
|
requires_power = 0
|
|
|
|
// Used by /turf/unsimulated/floor/upperlevel as a reference for where the other floor is
|
|
/obj/effect/levelref
|
|
name = "level reference"
|
|
icon = 'icons/mob/screen_gen.dmi'
|
|
icon_state = "x2"
|
|
invisibility = 101
|
|
|
|
var/id = null
|
|
var/obj/effect/levelref/other = null
|
|
var/offset_x
|
|
var/offset_y
|
|
var/offset_z
|
|
var/global/list/levels[0]
|
|
|
|
/obj/effect/levelref/New()
|
|
..()
|
|
levels += src
|
|
|
|
/obj/effect/levelref/initialize()
|
|
..()
|
|
for(var/obj/effect/levelref/O in levels)
|
|
if(id == O.id && O != src)
|
|
other = O
|
|
update_offset()
|
|
O.other = src
|
|
O.update_offset()
|
|
for(var/turf/unsimulated/floor/upperlevel/U in get_area(loc))
|
|
U.init(src)
|
|
return
|
|
|
|
/obj/effect/levelref/Destroy()
|
|
levels -= src
|
|
return ..()
|
|
|
|
/obj/effect/levelref/proc/update_offset()
|
|
offset_x = other.x - x
|
|
offset_y = other.y - y
|
|
offset_z = other.z - z
|
|
|
|
// Used by /turf/unsimulated/floor/upperlevel and /obj/effect/view_portal/visual
|
|
// to know if the world changed on the remote side
|
|
/obj/effect/portal_sensor
|
|
invisibility = 101
|
|
var/light_hash = -1
|
|
var/triggered_this_tick = 0
|
|
var/datum/owner // owner that receive signals
|
|
var/list/params[0] // what to send to the main object to indicate which sensor
|
|
var/trigger_limit = 5 // number of time we're allowed to trigger per ptick
|
|
|
|
/obj/effect/portal_sensor/New(loc, o, ...)
|
|
..()
|
|
owner = o
|
|
if(args.len >= 3)
|
|
params = args.Copy(3)
|
|
processing_objects += src
|
|
trigger()
|
|
|
|
/obj/effect/portal_sensor/Destroy()
|
|
processing_objects -= src
|
|
return ..()
|
|
|
|
/obj/effect/portal_sensor/Crossed(A)
|
|
trigger()
|
|
|
|
/obj/effect/portal_sensor/Uncrossed(A)
|
|
trigger()
|
|
|
|
/obj/effect/portal_sensor/process()
|
|
check_light()
|
|
if(triggered_this_tick >= trigger_limit)
|
|
call(owner, "trigger")(arglist(params))
|
|
triggered_this_tick = 0
|
|
|
|
/obj/effect/portal_sensor/proc/trigger()
|
|
triggered_this_tick++
|
|
if(triggered_this_tick < trigger_limit)
|
|
call(owner, "trigger")(arglist(params))
|
|
|
|
/obj/effect/portal_sensor/proc/check_light()
|
|
var/turf/T = loc
|
|
if(istype(T) && T.lighting_overlay && !T.lighting_overlay.needs_update)
|
|
var/atom/movable/lighting_overlay/O = T.lighting_overlay
|
|
var/hash = O.lum_r + O.lum_g + O.lum_b
|
|
if(hash != light_hash)
|
|
light_hash = hash
|
|
trigger()
|
|
else
|
|
if(light_hash != -1)
|
|
light_hash = -1
|
|
trigger()
|
|
|
|
// for second floor showing floor below
|
|
/turf/unsimulated/floor/upperlevel
|
|
icon = 'icons/turf/areas.dmi'
|
|
icon_state = "dark128"
|
|
layer = AREA_LAYER + 0.5
|
|
appearance_flags = TILE_BOUND | KEEP_TOGETHER
|
|
var/turf/lower_turf
|
|
var/obj/effect/portal_sensor/sensor
|
|
|
|
/turf/unsimulated/floor/upperlevel/New()
|
|
..()
|
|
var/obj/effect/levelref/R = locate() in get_area(src)
|
|
if(R && R.other)
|
|
init(R)
|
|
|
|
/turf/unsimulated/floor/upperlevel/Destroy()
|
|
qdel(sensor)
|
|
sensor = null
|
|
return ..()
|
|
|
|
/turf/unsimulated/floor/upperlevel/proc/init(var/obj/effect/levelref/R)
|
|
lower_turf = locate(x + R.offset_x, y + R.offset_y, z + R.offset_z)
|
|
if(lower_turf)
|
|
sensor = new(lower_turf, src)
|
|
|
|
/turf/unsimulated/floor/upperlevel/Entered(atom/movable/AM, atom/OL, ignoreRest = 0)
|
|
if(isliving(AM) || istype(AM, /obj))
|
|
if(isliving(AM))
|
|
var/mob/living/M = AM
|
|
M.emote("scream")
|
|
M.SpinAnimation(5, 1)
|
|
AM.forceMove(lower_turf)
|
|
|
|
/turf/unsimulated/floor/upperlevel/attack_ghost(mob/user)
|
|
user.forceMove(lower_turf)
|
|
|
|
/turf/unsimulated/floor/upperlevel/proc/trigger()
|
|
name = lower_turf.name
|
|
desc = lower_turf.desc
|
|
|
|
// render each atom
|
|
underlays.Cut()
|
|
for(var/X in list(lower_turf) + lower_turf.contents)
|
|
var/atom/A = X
|
|
if(A && A.invisibility <= SEE_INVISIBLE_LIVING)
|
|
var/image/I = image(A, layer = AREA_LAYER + A.layer * 0.01, dir = A.dir)
|
|
I.pixel_x = A.pixel_x
|
|
I.pixel_y = A.pixel_y
|
|
underlays += I
|
|
|
|
// remote end of narnia portal
|
|
/obj/effect/view_portal
|
|
name = "portal target"
|
|
icon = 'icons/mob/screen_gen.dmi'
|
|
icon_state = "x2"
|
|
invisibility = 101
|
|
anchored = 1
|
|
|
|
var/id = null // id of other portal turf we connect to
|
|
|
|
var/obj/effect/view_portal/other = null
|
|
var/global/list/portals[0]
|
|
|
|
/obj/effect/view_portal/New()
|
|
..()
|
|
portals += src
|
|
|
|
/obj/effect/view_portal/initialize()
|
|
..()
|
|
if(id)
|
|
for(var/obj/effect/view_portal/O in portals)
|
|
if(id == O.id && O != src && can_link(O))
|
|
other = O
|
|
O.other = src
|
|
linkup()
|
|
O.linkup()
|
|
if(other)
|
|
return
|
|
|
|
/obj/effect/view_portal/Destroy()
|
|
portals -= src
|
|
return ..()
|
|
|
|
/obj/effect/view_portal/proc/can_link(obj/effect/view_portal/P)
|
|
return P.type == /obj/effect/view_portal/visual && !P.other
|
|
|
|
/obj/effect/view_portal/proc/linkup()
|
|
// allow it to link to multiple visual nodes
|
|
other = null
|
|
|
|
// near end of nania portal
|
|
/obj/effect/view_portal/visual
|
|
name = "???"
|
|
desc = "You'll have to get closer to clearly see what this is."
|
|
|
|
icon = 'icons/turf/floors.dmi'
|
|
icon_state = "loadingarea"
|
|
opacity = 1
|
|
density = 1
|
|
invisibility = 0
|
|
appearance_flags = TILE_BOUND | KEEP_TOGETHER
|
|
var/dist = 6 // dist that we render out
|
|
var/radius = 3 // dist we render on other axis, in each direction
|
|
var/frustrum = 0 // if 1, get wider and wider at each step outward
|
|
var/teleport = 1 // should teleport?
|
|
|
|
var/list/render_block
|
|
var/list/sensors[0]
|
|
var/list/tiles[0]
|
|
|
|
var/list/near_render_block
|
|
var/turf/near_viewpoint
|
|
|
|
/obj/effect/view_portal/visual/Destroy()
|
|
for(var/T in sensors)
|
|
qdel(sensors[T])
|
|
sensors.Cut()
|
|
sensors = null
|
|
for(var/T in tiles)
|
|
qdel(tiles[T])
|
|
tiles.Cut()
|
|
tiles = null
|
|
render_block = null
|
|
near_render_block = null
|
|
near_viewpoint = null
|
|
return ..()
|
|
|
|
/obj/effect/view_portal/visual/can_link(obj/effect/view_portal/P)
|
|
return P.type == /obj/effect/view_portal
|
|
|
|
/obj/effect/view_portal/visual/linkup()
|
|
icon = null
|
|
icon_state = null
|
|
var/turf/Tloc = get_turf(loc)
|
|
if(Tloc)
|
|
Tloc.icon = null
|
|
Tloc.icon_state = null
|
|
Tloc.dynamic_lighting = 0
|
|
layer = AREA_LAYER + 0.5
|
|
|
|
// setup references
|
|
var/crossdir = angle2dir((dir2angle(dir) + 90) % 360)
|
|
near_viewpoint = get_step(get_turf(src), GetOppositeDir(dir))
|
|
|
|
// setup far turfs
|
|
var/turf/T1 = get_turf(other)
|
|
var/turf/T2 = T1
|
|
|
|
for(var/i in 1 to radius)
|
|
T1 = get_step(T1, crossdir)
|
|
T2 = get_step(T2, GetOppositeDir(crossdir))
|
|
if(frustrum)
|
|
// make a trapazoid, with length dist, short end radius*2 long,
|
|
// and 45 degree angles
|
|
render_block = block(T1, T2)
|
|
for(var/i in 1 to dist)
|
|
T1 = get_step(get_step(T1, dir), crossdir)
|
|
T2 = get_step(get_step(T2, dir), GetOppositeDir(crossdir))
|
|
render_block += block(T1, T2)
|
|
else
|
|
// else make a box dist x radius*2
|
|
for(var/i in 1 to dist)
|
|
T2 = get_step(T2, dir)
|
|
render_block = block(T1, T2)
|
|
for(var/turf/T in render_block)
|
|
sensors[T] = new /obj/effect/portal_sensor(T, src, 0, T)
|
|
|
|
// setup turfs on this side of the portal to cover the map streaming
|
|
// has to be done later for view() to be correct (so it happens when the walls exist)
|
|
/obj/effect/view_portal/visual/proc/setup_near()
|
|
var/nvs = dir & (EAST|WEST) ? near_viewpoint.x - x : near_viewpoint.y - y
|
|
if(nvs)
|
|
nvs = SIGN(nvs)
|
|
// need a mob for view() to work correctly
|
|
var/mob/M = new(near_viewpoint)
|
|
M.see_invisible = SEE_INVISIBLE_LIVING
|
|
near_render_block = view(M, world.view)
|
|
qdel(M)
|
|
for(var/A in near_render_block)
|
|
var/turf/T = A
|
|
if(istype(T))
|
|
var/ts = dir & (EAST|WEST) ? T.x - x : T.y - y
|
|
if(ts)
|
|
ts = SIGN(ts)
|
|
if(nvs == ts)
|
|
sensors[T] = new /obj/effect/portal_sensor(T, src, 1, T)
|
|
else
|
|
near_render_block -= T
|
|
else
|
|
near_render_block -= T
|
|
|
|
/obj/effect/view_portal/visual/Bumped(atom/movable/thing)
|
|
if((istype(thing, /obj) || isliving(thing)) && other && teleport)
|
|
if(!near_render_block)
|
|
setup_near()
|
|
|
|
var/mob/living/M = thing
|
|
// make the person glide onto the dest, giving a smooth transition
|
|
var/ox = thing.x - x
|
|
var/oy = thing.y - y
|
|
if(istype(M) && M.client)
|
|
M.notransform = 1
|
|
// cover up client-side map loading
|
|
M.screen_loc = "CENTER"
|
|
M.client.screen += M
|
|
for(var/T in tiles)
|
|
M.client.screen += tiles[T]
|
|
|
|
// wait a tick for the screen to replicate across network
|
|
// or this whole exercise of covering the transition is pointless
|
|
spawn(1)
|
|
thing.forceMove(locate(other.x + ox, other.y + oy, other.z))
|
|
sleep(1)
|
|
if(istype(M) && M.client)
|
|
for(var/T in tiles)
|
|
M.client.screen -= tiles[T]
|
|
M.client.screen -= M
|
|
M.screen_loc = initial(M.screen_loc)
|
|
thing.forceMove(get_turf(other.loc))
|
|
if(istype(M) && M.client)
|
|
M.notransform = 0
|
|
|
|
/obj/effect/view_portal/visual/attack_ghost(mob/user)
|
|
user.forceMove(get_turf(other.loc))
|
|
|
|
/obj/effect/view_portal/visual/proc/trigger(near, turf/T)
|
|
var/obj/effect/view_portal_dummy/D = tiles[T]
|
|
if(D)
|
|
D.overlays.Cut()
|
|
else
|
|
D = new(src, near, T)
|
|
tiles[T] = D
|
|
|
|
// render atoms to overlays of a dummy object
|
|
if(D.name != T.name)
|
|
D.name = T.name
|
|
D.desc = T.desc
|
|
for(var/AX in list(T) + T.contents)
|
|
var/atom/A = AX
|
|
if(A && A.invisibility <= SEE_INVISIBLE_LIVING)
|
|
var/image/I = image(A, layer = D.layer + A.layer * 0.01, dir = A.dir)
|
|
I.pixel_x = A.pixel_x
|
|
I.pixel_y = A.pixel_y
|
|
D.overlays += I
|
|
|
|
// tile of rendered other side for narnia portal
|
|
/obj/effect/view_portal_dummy
|
|
var/obj/effect/view_portal/visual/owner
|
|
|
|
/obj/effect/view_portal_dummy/New(obj/effect/view_portal/visual/V, near, turf/T)
|
|
..()
|
|
if(!near)
|
|
loc = V.loc
|
|
owner = V
|
|
|
|
var/ox
|
|
var/oy
|
|
if(near)
|
|
ox = (T.x - V.near_viewpoint.x)
|
|
oy = (T.y - V.near_viewpoint.y)
|
|
layer = AREA_LAYER + 0.4
|
|
else
|
|
ox = T.x - V.other.x + V.x - V.near_viewpoint.x
|
|
oy = T.y - V.other.y + V.y - V.near_viewpoint.y
|
|
pixel_x = 32 * (T.x - V.other.x)
|
|
pixel_y = 32 * (T.y - V.other.y)
|
|
layer = AREA_LAYER + 0.5
|
|
if(abs(ox) <= world.view && abs(oy) <= world.view)
|
|
screen_loc = "CENTER[ox >= 0 ? "+" : ""][ox],CENTER[oy >= 0 ? "+" : ""][oy]"
|
|
|
|
/obj/effect/view_portal_dummy/attack_ghost(mob/user)
|
|
owner.attack_ghost(user)
|