Files
Bubberstation/code/_onclick/hud/rendering/plane_master.dm
lessthanthree 7305d12d29 [MANUAL MIRROR] Nightvision Rework (In the name of color) (#19608)
* Nightvision Rework (In the name of color) (#73094)

Relies on #72886 for some render relay expansion I use for light_mask
stuff.

Hello bestie! Night vision pissed me off, so I've come to burn this
place to the ground.
Two sections to discuss here. First we'll talk about see_in_dark and why
I hate it, second we'll discuss the lighting plane and how we brighten
it, plus introducing color to the party.

https://www.byond.com/docs/ref/#/mob/var/see_in_dark

See in dark lets us control how far away from us a turf can be before we
hide it/its contents if it's dark (not got luminosity set)
We currently set it semi inconsistently to provide nightvision to mobs.

The trouble is stuff that produces light != stuff that sets luminosity.
The worst case of this can be seen by walking out of escape on icebox,
where you'll see this

![image](https://user-images.githubusercontent.com/58055496/215683654-587fb00f-ebb8-4c83-962d-a1b2bf429c4a.png)

Snow draws above the lighting plane, so the snow will intermittently
draw, depending on see_in_dark and the luminosity from tracking lights.
This would in theory be solvable by modifying the area, but the same
problem applies across many things in the codebase.
As things currently stand, to be emissive you NEED to have a light on
your tile. People are bad at this, and honestly it's a bit much to
expect of them. An emissive overlay on a canister shouldn't need an
element or something and a list on turfs to manage it.
This gets worse when you factor in the patterns I'm using to avoid
drawing lights above nothing, which leads to lights that should show,
but are misoffset because their parent pixel offsets.

It's silly. We do it so we can have things like mesons without just
handing out night vision, but even there the effect of just hiding
objects and mobs looks baddddddd when moving. It's always bothered me.
I'll complain about mesons more later, but really just like, they're too
bright as it is.

I'm proposing here that rather then manually hiding stuff based off
distance from the player, we can instead show/hide using just the
lighting plane. This means things like mesons are gonna get dimmer, but
that's fine because they suck.

It does have some side effects, things like view() on mobs won't hide
stuff in darkness, but that's fine because none actually thinks about
view like that, I think.

Oh and I added a case to prevent examining stuff that's in darkness, and
not right next to you when you don't have enough nightvision, to match
the old behavior `see_in_dark` gave us.

Now I'd like to go on a mild tangent about color, please bare with me

You ever walk around with mesons on when there's a fire going, or an
ethereal or firelocks down.
You notice how there isn't really much color to our lights? Doesn't that
suck?

It's because the way we go about brighting lighting is by making
everything on the lighting plane transparent.
This is fine for brightening things, but it ends up looking kinda crummy
in the end and leads to really washed out colors that should be bright.
Playing engineer or miner gets fucking depressing.

The central idea of this pr, that everything else falls out of, is
instead of making the plane more transparent, we can use color matrixes
to make things AT LEAST x bright.

https://www.byond.com/docs/ref/#/{notes}/color-matrix

Brief recap for color matrixes, fully expanded they're a set of 20
different values in a list
Units generally scale 0-1 as multipliers, though since it's
multiplication in order to make an rgb(1,1,1) pixel fullbright you would
need to use 255s.

A "unit matrix" for color looks like this:
```
list(1, 0, 0, 0,
     0, 1, 0, 0,
     0, 0, 1, 0,
     0, 0, 0, 1,
     0, 0, 0, 0
)
```

The first four rows are how much each r, g, b and a impact r, g, b and
well a.
So a first row of `(1, 0, 0, 0)` means 1 unit of r results in 1 unit of
r. and 0 units of green, blue and alpha, and so on.
A first row of `(0, 1, 0, 0)` would make 1 red component into 1 green
component, and leave red, blue and alpha alone, shifting any red of
whatever it's applied to a green.

Using these we can essentially color transform our world. It's a fun
tool. But there's more.

That last row there doesn't take a variable input like the others.
Instead, it ADDS some fraction of 255 to red, green, blue and alpha.

So a fifth row of `(1, 0, 0, 0)` would make every pixel as red as it
could possibly be.

This is what we're going to exploit here. You see all these values
accept negative multipliers, so we can lower colors down instead of
raising them up!
The key idea is using color matrix filters
https://www.byond.com/docs/ref/#/{notes}/filters/color to chain these
operations together.

Pulling alllll the way back, we want to brighten darkness without
affecting brighter colors.
Lower rgb values are darker, higher ones are brighter. This relationship
isn't really linear because of suffering reasons, but it's good enough
for this.
Let's try chaining some matrixes on the lighting plane, which is bright
where fullbright, and dark where dark.

Take a list like this

```
list(1, 0, 0, 0,
     0, 1, 0, 0,
     0, 0, 1, 0,
     0, 0, 0, 1,
     -0.2, -0.2, -0.2, 0
)
```
That would darken the lighting a bit, but negative values will get
rounded to 0
A subsequent raising by the same amount
```
list(1, 0, 0, 0,
     0, 1, 0, 0,
     0, 0, 1, 0,
     0, 0, 0, 1,
     0.2, 0.2, 0.2, 0
)
```
Will essentially threshold our brightness at that value.
This ensures we aren't washing out colors when we make things brighter,
while leaving higher values unaffected since they basically just had a
constant subtracted and then readded.

You may have noticed, we gain access to individual color components
here.
This means not only can we darken and lighten by thresholds, we can
COLOR those thresholds.
```
list(1, 0, 0, 0,
     0, 1, 0, 0,
     0, 0, 1, 0,
     0, 0, 0, 1,
     0.1, 0.2, 0.1, 0
)
```
Something like the above, if applied with its inverse, would tint the
darkness green.
The delta between the different scalars will determine how vivid the
color is, and the actual value will impact the brightness.

Something that's always bothered me about nightvision is it's just
greyscale for the most part, there isn't any color to it.
There was an old idea of coloring the game plane to match their lenses,
but if you've ever played with the colorblind quirk you know that gets
headachey really fast.
So instead of that, lets color just the darkness that these glasses
produce.
It provides some reminder that you're wearing them, instead of just
being something you forget about while playing, and provides a reason to
use flashlights and such since they can give you a clearer, less tinted
view of things while retaining the ability to look around things.

I've so far applied this pattern to JUST headwear for humans (also those
mining wisps)
I'm planning on furthering it to mobs that use nightvision, but I wanted
to get this up cause I don't wanna pr it the day before the freeze.

Mesons are green, sec night vision is red, thermals orange, etc.

I think the effect this gives is really really nice.
I've tuned most things to work for the station, though mesons works for
lavaland for obvious reasons.

I've tuned things significantly darker then we have them set currently,
since I really hate flat lighting and this system suffers when
interacting with it.

My goal with these is to give you a rough idea of what's around you,
without a good eye for detail.
That's the difference between say, mesons, and night vision. One helps
you see outlines, the other gives you detail and prevents missing
someone in the darkness.

It's hard to balance this precisely because of different colored
backgrounds (looking at you icebox)
More can be done on this front in future but I'm quite happy with things
as of now

I have since expanded to all uses of nightvision, coloring most all of
them.

Along the way I turned some toggleable nightvision into just one level.
Fullbright sucks, and I'd rather just have one "good" value.

I've kept it for a few cases, mostly eyes you rip out of mobs.
Impacted mobs are nightmares, aliens, zombies, revenants, states and
sort of stands.

I've done a pass on all mobs and items that impact nightvision and added
what I thought was the right level of color to them. This includes stuff
like blobs and shuttle control consoles
As with glasses much of this was around reducing vision, though I kept
it stronger here, since many of these mobs rely on it for engaging with
the game

<details>
<summary>
Technical Changes
</summary>

filter transitions.
Found this when testing this pr, seemed silly.

This avoids dumbass overlay lighting lighting up wallmounts.
We switch modes if some turfflags are set, to accomplish the same thing
with more overhead, and support showing things through the darkness.

Also fixes a bug where you'd only get one fullscreen object per mob, so
opening and closing a submap would take it away

Also also fixes the lighting backdrop not actually spanning the screen.
It doesn't actually do anything anymore because of the fullscreen light
we have, but just in case that's unsued.
Needs cleanup in future.

color with a sprite

This is to support the above
We relay this plane to lighting mask so openspace can like, have
lighting

vision goggles and such
Side affect of removing see_in_dark. This logic is a bit weak atm, needs
some work.

It's a dupe of the nightvision action button, and newly redundant since
I've removed all uses of it

trasnparent won't render

These sucked
Also transparent stuff should never render, if it does you'll get white
blobs which suck

</details>

Videos! (Github doesn't like using a summary here I'm sorry)
<details>

Demonstration of ghost lighting, and color

https://user-images.githubusercontent.com/58055496/215693983-99e00f9e-7214-4cf4-a76a-6e669a8a1103.mp4

Engi-glass mesons and walking in maint (Potentially overtuned, yellow is
hard)

https://user-images.githubusercontent.com/58055496/215695978-26e7dc45-28aa-4285-ae95-62ea3d79860f.mp4

Diagnostic nightvision goggles and see_in_dark not hiding emissives

https://user-images.githubusercontent.com/58055496/215692233-115b4094-1099-4393-9e94-db2088d834f3.mp4

Sec nightvision (I just think it looks neat)

https://user-images.githubusercontent.com/58055496/215692269-bc08335e-0223-49c3-9faf-d2d7b22fe2d2.mp4

Medical nightvision goggles and other colors

https://user-images.githubusercontent.com/58055496/215692286-0ba3de6a-b1d5-4aed-a6eb-c32794ea45da.mp4

Miner mesons and mobs hiding in lavaland (This is basically the darkest
possible environment)

https://user-images.githubusercontent.com/58055496/215696327-26958b69-0e1c-4412-9298-4e9e68b3df68.mp4

Thermal goggles and coloring displayed mobs

https://user-images.githubusercontent.com/58055496/215692710-d2b101f3-7922-498c-918c-9b528d181430.mp4

</details>

I think it's pretty, and see_in_dark sucks butt.

<!-- If your PR modifies aspects of the game that can be concretely
observed by players or admins you should add a changelog. If your change
does NOT meet this description, remove this section. Be sure to properly
mark your PRs to prevent unnecessary GBP loss. You can read up on GBP
and it's effects on PRs in the tgstation guides for contributors. Please
note that maintainers freely reserve the right to remove and add tags
should they deem it appropriate. You can attempt to finagle the system
all you want, but it's best to shoot for clear communication right off
the bat. -->

🆑
add: The darkness that glasses and hud goggles that impact your
nightvision (think mesons, nightvision goggles, etc) lighten is now
tinted to match the glasses. S pretty IMO, and hopefully it helps with
forgetting you're wearing X.
balance: Nightvision is darker. I think bright looks bad, and things
like mesons do way too much
balance: Mesons (and mobs in general) no longer have a static distance
you can see stuff in the dark. If a tile is lit, you can now see it.
fix: Nightvision no longer dims colored lights, instead simply
thresholding off bits of darkness that are dimmer then some level.
/🆑

* modular edits

* see_in_dark

* [MIRROR] Adds a unit test to detect double stacked lights [MDB IGNORE] (#19564)

* Adds a unit test to detect double stacked lights

* we really need to get that night vision pr done

* lints fixes

---------

Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: Paxilmaniac <paxilmaniac@gmail.com>

* Update augments_eyes.dm

* Update augments_eyes.dm

* eeee

---------

Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: SkyratBot <59378654+SkyratBot@users.noreply.github.com>
Co-authored-by: Paxilmaniac <paxilmaniac@gmail.com>
Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
2023-03-10 04:17:22 +00:00

642 lines
29 KiB
Plaintext

// I hate this place
INITIALIZE_IMMEDIATE(/atom/movable/screen/plane_master)
/atom/movable/screen/plane_master
screen_loc = "CENTER"
icon_state = "blank"
appearance_flags = PLANE_MASTER
blend_mode = BLEND_OVERLAY
plane = LOWEST_EVER_PLANE
/// Will be sent to the debug ui as a description for each plane
/// Also useful as a place to explain to coders how/why your plane works, and what it's meant to do
/// Plaintext and basic html are fine to use here.
/// I'll bonk you if I find you putting "lmao stuff" in here, make this useful.
var/documentation = ""
/// Our real alpha value, so alpha can persist through being hidden/shown
var/true_alpha = 255
/// Tracks if we're using our true alpha, or being manipulated in some other way
var/alpha_enabled = TRUE
/// The plane master group we're a member of, our "home"
var/datum/plane_master_group/home
/// If our plane master allows for offsetting
/// Mostly used for planes that really don't need to be duplicated, like the hud planes
var/allows_offsetting = TRUE
/// Our offset from our "true" plane, see below
var/offset
/// When rendering multiz, lower levels get their own set of plane masters
/// Real plane here represents the "true" plane value of something, ignoring the offset required to handle lower levels
var/real_plane
//--rendering relay vars--
/// list of planes we will relay this plane's render to
var/list/render_relay_planes = list(RENDER_PLANE_GAME)
/// blend mode to apply to the render relay in case you dont want to use the plane_masters blend_mode
var/blend_mode_override
/// list of current relays this plane is utilizing to render
var/list/atom/movable/render_plane_relay/relays = list()
/// if render relays have already be generated
var/relays_generated = FALSE
/// If this plane master should be hidden from the player at roundstart
/// We do this so PMs can opt into being temporary, to reduce load on clients
var/start_hidden = FALSE
/// If this plane master is being forced to hide.
/// Hidden PMs will dump ANYTHING relayed or drawn onto them. Be careful with this
/// Remember: a hidden plane master will dump anything drawn directly to it onto the output render. It does NOT hide its contents
/// Use alpha for that
var/force_hidden = FALSE
/// If this plane should be scaled by multiz
/// Planes with this set should NEVER be relay'd into each other, as that will cause visual fuck
var/multiz_scaled = TRUE
/// Bitfield that describes how this plane master will render if its z layer is being "optimized"
/// If a plane master is NOT critical, it will be completely dropped if we start to render outside a client's multiz boundary prefs
/// Of note: most of the time we will relay renders to non critical planes in this stage. so the plane master will end up drawing roughly "in order" with its friends
/// This is NOT done for parallax and other problem children, because the rules of BLEND_MULTIPLY appear to not behave as expected :(
/// This will also just make debugging harder, because we do fragile things in order to ensure things operate as epected. I'm sorry
/// Compile time
/// See [code\__DEFINES\layers.dm] for our bitflags
var/critical = NONE
/// If this plane master is outside of our visual bounds right now
var/is_outside_bounds = FALSE
/atom/movable/screen/plane_master/Initialize(mapload, datum/plane_master_group/home, offset = 0)
. = ..()
src.offset = offset
true_alpha = alpha
real_plane = plane
if(!set_home(home))
return INITIALIZE_HINT_QDEL
update_offset()
if(!documentation && !(istype(src, /atom/movable/screen/plane_master) || istype(src, /atom/movable/screen/plane_master/rendering_plate)))
stack_trace("Plane master created without a description. Document how your thing works so people will know in future, and we can display it in the debug menu")
if(start_hidden)
hide_plane(home.our_hud?.mymob)
generate_render_relays()
/atom/movable/screen/plane_master/Destroy()
if(home)
// NOTE! We do not clear ourselves from client screens
// We relay on whoever qdel'd us to reset our hud, and properly purge us
home.plane_masters -= "[plane]"
home = null
. = ..()
QDEL_LIST(relays)
/// Sets the plane group that owns us, it also determines what screen we render to
/// Returns FALSE if the set_home fails, TRUE otherwise
/atom/movable/screen/plane_master/proc/set_home(datum/plane_master_group/home)
if(!istype(home, /datum/plane_master_group))
return FALSE
src.home = home
if(home.map)
screen_loc = "[home.map]:[screen_loc]"
assigned_map = home.map
return TRUE
/// Updates our "offset", basically what layer of multiz we're meant to render
/// Top is 0, goes up as you go down
/// It's taken into account by render targets and relays, so we gotta make sure they're on the same page
/atom/movable/screen/plane_master/proc/update_offset()
name = "[initial(name)] #[offset]"
SET_PLANE_W_SCALAR(src, real_plane, offset)
for(var/i in 1 to length(render_relay_planes))
render_relay_planes[i] = GET_NEW_PLANE(render_relay_planes[i], offset)
if(initial(render_target))
render_target = OFFSET_RENDER_TARGET(initial(render_target), offset)
/atom/movable/screen/plane_master/proc/set_alpha(new_alpha)
true_alpha = new_alpha
if(!alpha_enabled)
return
alpha = new_alpha
/atom/movable/screen/plane_master/proc/disable_alpha()
alpha_enabled = FALSE
alpha = 0
/atom/movable/screen/plane_master/proc/enable_alpha()
alpha_enabled = TRUE
alpha = true_alpha
/// Shows a plane master to the passed in mob
/// Override this to apply unique effects and such
/// Returns TRUE if the call is allowed, FALSE otherwise
/atom/movable/screen/plane_master/proc/show_to(mob/mymob)
SHOULD_CALL_PARENT(TRUE)
if(force_hidden)
return FALSE
var/client/our_client = mymob?.client
// Alright, let's get this out of the way
// Mobs can move z levels without their client. If this happens, we need to ensure critical display settings are respected
// This is done here. Mild to severe pain but it's nessesary
if(check_outside_bounds())
if(!(critical & PLANE_CRITICAL_DISPLAY))
return FALSE
if(!our_client)
return TRUE
our_client.screen += src
if(!(critical & PLANE_CRITICAL_NO_RELAY))
our_client.screen += relays
return TRUE
return TRUE
if(!our_client)
return TRUE
our_client.screen += src
our_client.screen += relays
return TRUE
/// Hook to allow planes to work around is_outside_bounds
/// Return false to allow a show, true otherwise
/atom/movable/screen/plane_master/proc/check_outside_bounds()
return is_outside_bounds
/// Hides a plane master from the passeed in mob
/// Do your effect cleanup here
/atom/movable/screen/plane_master/proc/hide_from(mob/oldmob)
SHOULD_CALL_PARENT(TRUE)
var/client/their_client = oldmob?.client
if(!their_client)
return
their_client.screen -= src
their_client.screen -= relays
/// Forces this plane master to hide, until unhide_plane is called
/// This allows us to disable unused PMs without breaking anything else
/atom/movable/screen/plane_master/proc/hide_plane(mob/cast_away)
force_hidden = TRUE
hide_from(cast_away)
/// Disables any forced hiding, allows the plane master to be used as normal
/atom/movable/screen/plane_master/proc/unhide_plane(mob/enfold)
force_hidden = FALSE
show_to(enfold)
/// Mirrors our force hidden state to the hidden state of the plane that came before, assuming it's valid
/// This allows us to mirror any hidden sets from before we were created, no matter how low that chance is
/atom/movable/screen/plane_master/proc/mirror_parent_hidden()
var/mob/our_mob = home?.our_hud?.mymob
var/atom/movable/screen/plane_master/true_plane = our_mob?.hud_used?.get_plane_master(plane)
if(true_plane == src || !true_plane)
return
if(true_plane.force_hidden == force_hidden)
return
// If one of us already exists and it's not hidden, unhide ourselves
if(true_plane.force_hidden)
hide_plane(our_mob)
else
unhide_plane(our_mob)
/atom/movable/screen/plane_master/proc/outside_bounds(mob/relevant)
if(force_hidden || is_outside_bounds)
return
is_outside_bounds = TRUE
// If we're of critical importance, AND we're below the rendering layer
if(critical & PLANE_CRITICAL_DISPLAY)
// We here assume that your render target starts with *
if(critical & PLANE_CRITICAL_CUT_RENDER && render_target)
render_target = copytext_char(render_target, 2)
if(!(critical & PLANE_CRITICAL_NO_RELAY))
return
var/client/our_client = relevant.client
if(our_client)
for(var/atom/movable/render_plane_relay/relay as anything in relays)
our_client.screen -= relay
return
hide_from(relevant)
/atom/movable/screen/plane_master/proc/inside_bounds(mob/relevant)
is_outside_bounds = FALSE
if(critical & PLANE_CRITICAL_DISPLAY)
// We here assume that your render target starts with *
if(critical & PLANE_CRITICAL_CUT_RENDER && render_target)
render_target = "*[render_target]"
if(!(critical & PLANE_CRITICAL_NO_RELAY))
return
var/client/our_client = relevant.client
if(our_client)
for(var/atom/movable/render_plane_relay/relay as anything in relays)
our_client.screen += relay
return
show_to(relevant)
/atom/movable/screen/plane_master/clickcatcher
name = "Click Catcher"
documentation = "Contains the screen object we use as a backdrop to catch clicks on portions of the screen that would otherwise contain nothing else. \
<br>Will always be below almost everything else"
plane = CLICKCATCHER_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
multiz_scaled = FALSE
critical = PLANE_CRITICAL_DISPLAY
/atom/movable/screen/plane_master/clickcatcher/Initialize(mapload, datum/plane_master_group/home, offset)
. = ..()
RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(offset_increased))
offset_increased(SSmapping, 0, SSmapping.max_plane_offset)
/atom/movable/screen/plane_master/clickcatcher/proc/offset_increased(datum/source, old_off, new_off)
SIGNAL_HANDLER
// We only want need the lowest level
// If my system better supported changing PM plane values mid op I'd do that, but I do NOT so
if(new_off > offset)
hide_plane(home?.our_hud?.mymob)
/atom/movable/screen/plane_master/parallax_white
name = "Parallax whitifier"
documentation = "Essentially a backdrop for the parallax plane. We're rendered just below it, so we'll be multiplied by its well, parallax.\
<br>If you want something to look as if it has parallax on it, draw it to this plane."
plane = PLANE_SPACE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
render_relay_planes = list(RENDER_PLANE_GAME, LIGHT_MASK_PLANE)
critical = PLANE_CRITICAL_FUCKO_PARALLAX // goes funny when touched. no idea why I don't trust byond
/atom/movable/screen/plane_master/parallax_white/Initialize(mapload, datum/plane_master_group/home, offset)
. = ..()
add_relay_to(GET_NEW_PLANE(EMISSIVE_RENDER_PLATE, offset), relay_layer = EMISSIVE_SPACE_LAYER)
///Contains space parallax
/atom/movable/screen/plane_master/parallax
name = "Parallax"
documentation = "Contains parallax, or to be more exact the screen objects that hold parallax.\
<br>Note the BLEND_MULTIPLY. The trick here is how low our plane value is. Because of that, we draw below almost everything in the game.\
<br>We abuse this to ensure we multiply against the Parallax whitifier plane, or space's plane. It's set to full white, so when you do the multiply you just get parallax out where it well, makes sense to be.\
<br>Also notice that the parent parallax plane is mirrored down to all children. We want to support viewing parallax across all z levels at once."
plane = PLANE_SPACE_PARALLAX
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
blend_mode = BLEND_MULTIPLY
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
multiz_scaled = FALSE
/atom/movable/screen/plane_master/parallax/Initialize(mapload, datum/plane_master_group/home, offset)
. = ..()
if(offset != 0)
// You aren't the source? don't change yourself
return
RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(on_offset_increase))
offset_increase(0, SSmapping.max_plane_offset)
/atom/movable/screen/plane_master/parallax/proc/on_offset_increase(datum/source, old_offset, new_offset)
SIGNAL_HANDLER
offset_increase(old_offset, new_offset)
/atom/movable/screen/plane_master/parallax/proc/offset_increase(old_offset, new_offset)
// Parallax will be mirrored down to any new planes that are added, so it will properly render across mirage borders
for(var/offset in old_offset to new_offset)
if(offset != 0)
// Overlay so we don't multiply twice, and thus fuck up our rendering
add_relay_to(GET_NEW_PLANE(plane, offset), BLEND_OVERLAY)
// Hacky shit to ensure parallax works in perf mode
/atom/movable/screen/plane_master/parallax/outside_bounds(mob/relevant)
if(offset == 0)
remove_relay_from(GET_NEW_PLANE(RENDER_PLANE_GAME, 0))
is_outside_bounds = TRUE // I'm sorry :(
return
// If we can't render, and we aren't the bottom layer, don't render us
// This way we only multiply against stuff that's not fullwhite space
var/atom/movable/screen/plane_master/parent_parallax = home.our_hud.get_plane_master(PLANE_SPACE_PARALLAX)
var/turf/viewing_turf = get_turf(relevant)
if(!viewing_turf || offset != GET_LOWEST_STACK_OFFSET(viewing_turf.z))
parent_parallax.remove_relay_from(plane)
else
parent_parallax.add_relay_to(plane, BLEND_OVERLAY)
return ..()
/atom/movable/screen/plane_master/parallax/inside_bounds(mob/relevant)
if(offset == 0)
add_relay_to(GET_NEW_PLANE(RENDER_PLANE_GAME, 0))
is_outside_bounds = FALSE
return
// Always readd, just in case we lost it
var/atom/movable/screen/plane_master/parent_parallax = home.our_hud.get_plane_master(PLANE_SPACE_PARALLAX)
parent_parallax.add_relay_to(plane, BLEND_OVERLAY)
return ..()
// Needs to handle rejoining on a lower z level, so we NEED to readd old planes
/atom/movable/screen/plane_master/parallax/check_outside_bounds()
// If we're outside bounds AND we're the 0th plane, we need to show cause parallax is hacked to hell
return offset != 0 && is_outside_bounds
/atom/movable/screen/plane_master/gravpulse
name = "Gravpulse"
documentation = "Ok so this one's fun. Basically, we want to be able to distort the game plane when a grav annom is around.\
<br>So we draw the pattern we want to use to this plane, and it's then used as a render target by a distortion filter on the game plane.\
<br>Note the blend mode and lack of relay targets. This plane exists only to distort, it's never rendered anywhere."
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
plane = GRAVITY_PULSE_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
blend_mode = BLEND_ADD
render_target = GRAVITY_PULSE_RENDER_TARGET
render_relay_planes = list()
///Contains just the floor
/atom/movable/screen/plane_master/floor
name = "Floor"
documentation = "The well, floor. This is mostly used as a sorting mechanism, but it also lets us create a \"border\" around the game world plane, so its drop shadow will actually work."
plane = FLOOR_PLANE
render_relay_planes = list(RENDER_PLANE_GAME, LIGHT_MASK_PLANE)
/atom/movable/screen/plane_master/transparent_floor
name = "Transparent Floor"
documentation = "Really just openspace, stuff that is a turf but has no color or alpha whatsoever.\
<br>We use this to draw to just the light mask plane, cause if it's not there we get holes of blackness over openspace"
plane = TRANSPARENT_FLOOR_PLANE
render_relay_planes = list(LIGHT_MASK_PLANE)
// Needs to be critical or it uh, it'll look white
critical = PLANE_CRITICAL_DISPLAY|PLANE_CRITICAL_NO_RELAY
/atom/movable/screen/plane_master/floor/Initialize(mapload, datum/plane_master_group/home, offset)
. = ..()
add_relay_to(GET_NEW_PLANE(EMISSIVE_RENDER_PLATE, offset), relay_layer = EMISSIVE_FLOOR_LAYER, relay_color = GLOB.em_block_color)
/atom/movable/screen/plane_master/wall
name = "Wall"
documentation = "Holds all walls. We render this onto the game world. Separate so we can use this + space and floor planes as a guide for where byond blackness is NOT."
plane = WALL_PLANE
render_relay_planes = list(RENDER_PLANE_GAME_WORLD, LIGHT_MASK_PLANE)
/atom/movable/screen/plane_master/wall/Initialize(mapload, datum/plane_master_group/home, offset)
. = ..()
add_relay_to(GET_NEW_PLANE(EMISSIVE_RENDER_PLATE, offset), relay_layer = EMISSIVE_WALL_LAYER, relay_color = GLOB.em_block_color)
/atom/movable/screen/plane_master/game
name = "Lower game world"
documentation = "Exists mostly because of FOV shit. Basically, if you've just got a normal not ABOVE fov thing, and you don't want it masked, stick it here yeah?"
plane = GAME_PLANE
render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
/atom/movable/screen/plane_master/game_world_fov_hidden
name = "lower game world fov hidden"
documentation = "If you want something to be hidden by fov, stick it on this plane. We're masked by the fov blocker plane, so the items on us can actually well, disappear."
plane = GAME_PLANE_FOV_HIDDEN
render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
/atom/movable/screen/plane_master/game_world_fov_hidden/Initialize(mapload)
. = ..()
add_filter("vision_cone", 1, alpha_mask_filter(render_source = OFFSET_RENDER_TARGET(FIELD_OF_VISION_BLOCKER_RENDER_TARGET, offset), flags = MASK_INVERSE))
/atom/movable/screen/plane_master/field_of_vision_blocker
name = "Field of vision blocker"
documentation = "This is one of those planes that's only used as a filter. It masks out things that want to be hidden by fov.\
<br>Literally just contains FOV images, or masks."
plane = FIELD_OF_VISION_BLOCKER_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
render_target = FIELD_OF_VISION_BLOCKER_RENDER_TARGET
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
render_relay_planes = list()
// We do NOT allow offsetting, because there's no case where you would want to block only one layer, at least currently
allows_offsetting = FALSE
start_hidden = TRUE
// We mark as multiz_scaled FALSE so transforms don't effect us, and we draw to the planes below us as if they were us.
// This is safe because we will ALWAYS be on the top z layer, so it DON'T MATTER
multiz_scaled = FALSE
/atom/movable/screen/plane_master/field_of_vision_blocker/Initialize(mapload, datum/plane_master_group/home, offset)
. = ..()
mirror_parent_hidden()
/atom/movable/screen/plane_master/game_world_upper
name = "Upper game world"
documentation = "Ok so fov is kinda fucky, because planes in byond serve both as effect groupings and as rendering orderers. Since that's true, we need a plane that we can stick stuff that draws above fov blocked stuff on."
plane = GAME_PLANE_UPPER
render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
/atom/movable/screen/plane_master/wall_upper
name = "Upper wall"
documentation = "There are some walls that want to render above most things (mostly minerals since they shift over.\
<br>We draw them to their own plane so we can hijack them for our emissive mask stuff"
plane = WALL_PLANE_UPPER
render_relay_planes = list(RENDER_PLANE_GAME_WORLD, LIGHT_MASK_PLANE)
/atom/movable/screen/plane_master/wall_upper/Initialize(mapload, datum/plane_master_group/home, offset)
. = ..()
add_relay_to(GET_NEW_PLANE(EMISSIVE_RENDER_PLATE, offset), relay_layer = EMISSIVE_WALL_LAYER, relay_color = GLOB.em_block_color)
/atom/movable/screen/plane_master/game_world_upper_fov_hidden
name = "Upper game world fov hidden"
documentation = "Just as we need a place to draw things \"above\" the hidden fov plane, we also need to be able to hide stuff that draws over the upper game plane."
plane = GAME_PLANE_UPPER_FOV_HIDDEN
render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
/atom/movable/screen/plane_master/game_world_upper_fov_hidden/Initialize(mapload)
. = ..()
// Dupe of the other hidden plane
add_filter("vision_cone", 1, alpha_mask_filter(render_source = OFFSET_RENDER_TARGET(FIELD_OF_VISION_BLOCKER_RENDER_TARGET, offset), flags = MASK_INVERSE))
/atom/movable/screen/plane_master/seethrough
name = "Seethrough"
documentation = "Holds the seethrough versions (done using image overrides) of large objects. Mouse transparent, so you can click through them."
plane = SEETHROUGH_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
start_hidden = TRUE
/atom/movable/screen/plane_master/game_world_above
name = "Above game world"
documentation = "We need a place that's unmasked by fov that also draws above the upper game world fov hidden plane. I told you fov was hacky man."
plane = ABOVE_GAME_PLANE
render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
/**
* Plane master that byond will by default draw to
* Shouldn't be used, exists to prevent people using plane 0
* NOTE: If we used SEE_BLACKNESS on a map format that wasn't SIDE_MAP, this is where its darkness would land
* This would allow us to control it and do fun things. But we can't because side map doesn't support it, so this is just a stub
*/
/atom/movable/screen/plane_master/default
name = "Default"
documentation = "This is quite fiddly, so bear with me. By default (in byond) everything in the game is rendered onto plane 0. It's the default plane. \
<br>But, because we've moved everything we control off plane 0, all that's left is stuff byond internally renders. \
<br>What I'd like to do with this is capture byond blackness by giving mobs the SEE_BLACKNESS sight flag. \
<br>But we CAN'T because SEE_BLACKNESS does not work with our rendering format. So I just eat it I guess"
plane = DEFAULT_PLANE
multiz_scaled = FALSE
start_hidden = TRUE // Doesn't DO anything, exists to hold this place
/atom/movable/screen/plane_master/area
name = "Area"
documentation = "Holds the areas themselves, which ends up meaning it holds any overlays/effects we apply to areas. NOT snow or rad storms, those go on above lighting"
plane = AREA_PLANE
/atom/movable/screen/plane_master/massive_obj
name = "Massive object"
documentation = "Huge objects need to render above everything else on the game plane, otherwise they'd well, get clipped and look not that huge. This does that."
plane = MASSIVE_OBJ_PLANE
/atom/movable/screen/plane_master/point
name = "Point"
documentation = "I mean like, what do you want me to say? Points draw over pretty much everything else, so they get their own plane. Remember we layer render relays to draw planes in their proper order on render plates."
plane = POINT_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
///Contains all turf lighting
/atom/movable/screen/plane_master/turf_lighting
name = "Turf Lighting"
documentation = "Contains all lighting drawn to turfs. Not so complex, draws directly onto the lighting plate."
plane = LIGHTING_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
render_relay_planes = list(RENDER_PLANE_LIGHTING)
blend_mode_override = BLEND_ADD
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
critical = PLANE_CRITICAL_DISPLAY
/// This will not work through multiz, because of a byond bug with BLEND_MULTIPLY
/// Bug report is up, waiting on a fix
/atom/movable/screen/plane_master/o_light_visual
name = "Overlight light visual"
documentation = "Holds overlay lighting objects, or the sort of lighting that's a well, overlay stuck to something.\
<br>Exists because lighting updating is really slow, and movement needs to feel smooth.\
<br>We draw to the game plane, and mask out space for ourselves on the lighting plane so any color we have has the chance to display."
plane = O_LIGHTING_VISUAL_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
render_target = O_LIGHTING_VISUAL_RENDER_TARGET
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
blend_mode = BLEND_MULTIPLY
critical = PLANE_CRITICAL_DISPLAY
/atom/movable/screen/plane_master/above_lighting
name = "Above lighting"
plane = ABOVE_LIGHTING_PLANE
documentation = "Anything on the game plane that needs a space to draw on that will be above the lighting plane.\
<br>Mostly little alerts and effects, also sometimes contains things that are meant to look as if they glow."
/**
* Handles emissive overlays and emissive blockers.
*/
/atom/movable/screen/plane_master/emissive
name = "Emissive"
documentation = "Holds things that will be used to mask the lighting plane later on. Masked by the Emissive Mask plane to ensure we don't emiss out under a wall.\
<br>Relayed onto the Emissive render plane to do the actual masking of lighting, since we need to be transformed and other emissive stuff needs to be transformed too.\
<br>Don't want to double scale now."
plane = EMISSIVE_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
render_relay_planes = list(EMISSIVE_RENDER_PLATE)
critical = PLANE_CRITICAL_DISPLAY
/atom/movable/screen/plane_master/pipecrawl
name = "Pipecrawl"
documentation = "Holds pipecrawl images generated during well, pipecrawling.\
<br>Has a few effects and a funky color matrix designed to make things a bit more visually readable."
plane = PIPECRAWL_IMAGES_PLANE
start_hidden = TRUE
/atom/movable/screen/plane_master/pipecrawl/Initialize(mapload)
. = ..()
// Makes everything on this plane slightly brighter
// Has a nice effect, makes thing stand out
color = list(1.2,0,0,0, 0,1.2,0,0, 0,0,1.2,0, 0,0,0,1, 0,0,0,0)
// This serves a similar purpose, I want the pipes to pop
add_filter("pipe_dropshadow", 1, drop_shadow_filter(x = -1, y= -1, size = 1, color = "#0000007A"))
mirror_parent_hidden()
/atom/movable/screen/plane_master/camera_static
name = "Camera static"
documentation = "Holds camera static images. Usually only visible to people who can well, see static.\
<br>We use images rather then vis contents because they're lighter on maptick, and maptick sucks butt."
plane = CAMERA_STATIC_PLANE
start_hidden = TRUE
/atom/movable/screen/plane_master/camera_static/show_to(mob/mymob)
// If we aren't an AI, we have no need for this plane master (most of the time, ai eyes are weird and annoying)
if(force_hidden && isAI(mymob))
unhide_plane(mymob)
. = ..()
if(!.)
return
if(isAI(mymob))
return
return FALSE
/atom/movable/screen/plane_master/high_game
name = "High Game"
documentation = "Holds anything that wants to be displayed above the rest of the game plane, and doesn't want to be clickable. \
<br>This includes atmos debug overlays, blind sound images, and mining scanners. \
<br>Really only exists for its layering potential, we don't use this for any vfx"
plane = HIGH_GAME_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/atom/movable/screen/plane_master/ghost
name = "Ghost"
documentation = "Ghosts draw here, so they don't get mixed up in the visuals of the game world. Note, this is not not how we HIDE ghosts from people, that's done with invisible and see_invisible."
plane = GHOST_PLANE
render_relay_planes = list(RENDER_PLANE_NON_GAME)
/atom/movable/screen/plane_master/fullscreen
name = "Fullscreen"
documentation = "Holds anything that applies to or above the full screen. \
<br>Note, it's still rendered underneath hud objects, but this lets us control the order that things like death/damage effects render in."
plane = FULLSCREEN_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
render_relay_planes = list(RENDER_PLANE_NON_GAME)
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
allows_offsetting = FALSE
/atom/movable/screen/plane_master/runechat
name = "Runechat"
documentation = "Holds runechat images, that text that pops up when someone say something. Uses a dropshadow to well, look nice."
plane = RUNECHAT_PLANE
render_relay_planes = list(RENDER_PLANE_NON_GAME)
/atom/movable/screen/plane_master/runechat/show_to(mob/mymob)
. = ..()
if(!.)
return
remove_filter("AO")
if(istype(mymob) && mymob.client?.prefs?.read_preference(/datum/preference/toggle/ambient_occlusion))
add_filter("AO", 1, drop_shadow_filter(x = 0, y = -2, size = 4, color = "#04080FAA"))
/atom/movable/screen/plane_master/balloon_chat
name = "Balloon chat"
documentation = "Holds ballon chat images, those little text bars that pop up for a second when you do some things. NOT runechat."
plane = BALLOON_CHAT_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
render_relay_planes = list(RENDER_PLANE_NON_GAME)
/atom/movable/screen/plane_master/hud
name = "HUD"
documentation = "Contains anything that want to be rendered on the hud. Typically is just screen elements."
plane = HUD_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
render_relay_planes = list(RENDER_PLANE_NON_GAME)
allows_offsetting = FALSE
/atom/movable/screen/plane_master/above_hud
name = "Above HUD"
documentation = "Anything that wants to be drawn ABOVE the rest of the hud. Typically close buttons and other elements that need to be always visible. Think preventing draggable action button memes."
plane = ABOVE_HUD_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
render_relay_planes = list(RENDER_PLANE_NON_GAME)
allows_offsetting = FALSE
/atom/movable/screen/plane_master/splashscreen
name = "Splashscreen"
documentation = "Cinematics and the splash screen."
plane = SPLASHSCREEN_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
render_relay_planes = list(RENDER_PLANE_NON_GAME)
allows_offsetting = FALSE
/atom/movable/screen/plane_master/escape_menu
name = "Escape Menu"
documentation = "Anything relating to the escape menu."
plane = ESCAPE_MENU_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
render_relay_planes = list(RENDER_PLANE_MASTER)
allows_offsetting = FALSE