diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 4220e4a8eb1..7ecb808d0a8 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -105,17 +105,13 @@ Things you **CAN'T** do:
#### Misc
-[Policy configuration system](./guides/POLICYCONFIG.md)
-
-[Hard deletes](./guides/HARDDELETES.md)
-
-[UI Development](../tgui/README.md)
-
-[AI Datums](../code/datums/ai/making_your_ai.md)
-
-[MC Tab Guide](./guides/MC_tab.md)
-
-[Embedding tgui components in chat](../tgui/docs/chat-embedded-components.md)
+- [Visual Effects and Systems](./guides/VISUALS.md)
+- [Policy Configuration System](./guides/POLICYCONFIG.md)
+- [Hard Deletes](./guides/HARDDELETES.md)
+- [UI Development](../tgui/README.md)
+- [AI Datums](../code/datums/ai/making_your_ai.md)
+- [MC Tab Guide](./guides/MC_tab.md)
+- [Embedding TGUI Components in Chat](../tgui/docs/chat-embedded-components.md)
## Pull Request Process
There is no strict process when it comes to merging pull requests. Pull requests will sometimes take a while before they are looked at by a maintainer; the bigger the change, the more time it will take before they are accepted into the code. Every team member is a volunteer who is giving up their own time to help maintain and contribute, so please be courteous and respectful. Here are some helpful ways to make it easier for you and for the maintainers when making a pull request.
diff --git a/.github/guides/VISUALS.md b/.github/guides/VISUALS.md
new file mode 100644
index 00000000000..938f70eab2f
--- /dev/null
+++ b/.github/guides/VISUALS.md
@@ -0,0 +1,650 @@
+# Visuals in /tg/station 13
+
+Welcome to a breakdown of visuals and visual effects in our codebase, and in BYOND.
+
+I will be describing all of the existing systems we use, alongside explaining and providing references to BYOND's ref for each tool.
+
+Note, I will not be covering things that are trivial to understand, and which we don't mess with much.
+For a complete list of byond ref stuff relevant to this topic, see [here](https://www.byond.com/docs/ref/#/atom/var/appearance).
+
+This is to some extent a collation of the BYOND ref, alongside a description of how we actually use these tools.
+My hope is after reading this you'll be able to understand and implement different visual effects in our codebase.
+
+Also please see the ref entry on the [renderer](https://www.byond.com/docs/ref/#/{notes}/renderer).
+
+We do a LOT, so this document might run on for a bit. Forgive me.
+
+You'll find links to the relevant reference entries at the heading of each entry, alongside a hook back to the head of this document.
+
+### Table of Contents
+
+- [Appearances](#appearances-in-byond)
+- [Overlays](#overlays)
+- [Visual contents](#visual-contents)
+- [Images](#images)
+- [Client images](#client-images)
+- [View](#view)
+- [Eye](#eye)
+- [Client screen](#client-screen)
+- [Blend mode](#client-screen)
+- [Appearance flags](#appearance-flags)
+- [Gliding](#gliding)
+- [Sight](#sight)
+- [BYOND lighting](#byond-lighting)
+ - [Luminosity](#luminosity)
+ - [See in dark](#see-in-dark)
+ - [Infrared](#infrared)
+- [Invisibility](#invisibility)
+- [Layers](#layers)
+- [Planes](#planes)
+- [Render target/source](#render-targetsource)
+- [Multiz](#multiz)
+- [Mouse opacity](#mouse-opacity)
+- [Filters](#filters)
+- [Particles](#particles)
+- [Pixel offsets](#pixel-offsets)
+- [Color](#color)
+- [Transform](#transform)
+- [Lighting](#lighting)
+- [Animate()](#animate())
+- [GAGS](#gags)
+
+## Appearances in BYOND
+
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/appearance)
+
+Everything that is displayed on the map has an appearance variable that describes exactly how it should be rendered.
+To be clear, it doesn't contain EVERYTHING, [plane masters](#planes) exist separately and so do many other factors.
+But it sets out a sort of recipe of everything that could effect rendering.
+
+Appearances have a few quirks that can be helpful or frustrating depending on what you're trying to do.
+
+To start off with, appearances are static. You can't directly edit an appearance "datum", it will throw a runtime or just yell at you.
+
+The way to edit them most of the time is to just modify the corresponding variable on the thing the appearance represents.
+
+This doesn't mean it's impossible to modify them directly however. While appearances are static,
+their cousins mutable appearances [(Ref Entry)](https://www.byond.com/docs/ref/info.html#/mutable_appearance) **are**.
+
+What we can do is create a new mutable appearance, set its appearance to be a copy of the static one (remember all appearance variables are static),
+edit it, and then set the desired thing's appearance var to the appearance var of the mutable.
+
+Somewhat like this
+
+```byond
+// NOTE: we do not actually have access to a raw appearance type, so we will often
+// Lie to the compiler, and pretend we are using a mutable appearance
+// This lets us access vars as expected. Be careful with it tho
+/proc/mutate_icon_state(mutable_appearance/thing)
+ var/mutable_appearance/temporary_lad = new()
+ temporary_lad.appearance = thing
+ temporary_lad.icon_state += "haha_owned"
+ return temporary_lad.appearance
+```
+
+> **Warning:** BYOND has been observed to have issues with appearance corruption, it's something to be weary of when "realizing" appearances in this manner.
+
+## Overlays
+
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/overlays) (Also see [rendering](https://www.byond.com/docs/ref/#/{notes}/renderer))
+
+Overlays are a list of static [appearances](#appearances-in-byond) that we render on top of ourselves.
+Said appearances can be edited via the realizing method mentioned above.
+
+Their rendering order is determined by [layer](#layers) and [plane](#planes), but conflicts are resolved based off order of appearance inside the list.
+
+While overlays are stored as static appearances they can be created using icon states to draw from the overlay'd thing icon, or using `/icon` objects.
+
+Also of note: overlays have a cost on addition, which is why as we will discuss we cache modifications to the list.
+
+It's not significant, but it is there, and something to be aware of.
+
+### Our Implementation
+
+We use overlays as our primary method of overlaying visuals.
+However, since overlays are COPIES of a thing's appearance, ensuring that they can be cleared is semi troublesome.
+
+To solve this problem, we manage most overlays using `update_overlays()`.
+
+This proc is called whenever an atom's appearance is updated with `update_appearance()`
+(essentially just a way to tell an object to rerender anything static about it, like icon state or name),
+which will often call `update_icon()`.
+
+`update_icon()` handles querying the object for its desired icon, and also manages its overlays, by calling `update_overlays()`.
+
+Said proc returns a list of things to turn into static appearances, which are then passed into `add_overlay()`,
+which makes them static with `build_appearance_list()` before queuing an overlay compile.
+
+This list of static appearances is then queued inside a list called `managed_overlays` on `/atom`.
+This is so we can clear old overlays out before running an update.
+
+We actually compile queued overlay builds once every tick using a dedicated subsystem.
+This is done to avoid adding/removing/adding again to the overlays list in cases like humans where it's mutated a lot.
+
+You can bypass this managed overlays system if you'd like, using `add_overlay()` and `cut_overlay()`,
+but this is semi dangerous because you don't by default have a way to "clear" the overlay.
+Be careful of this.
+
+## Visual Contents
+
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/vis_contents)
+
+The `vis_contents` list allows you to essentially say "Hey, render this thing ON me".
+
+The definition of "ON" varies significantly with the `vis_flags` value of the *thing* being relayed.
+See the ref [here](https://www.byond.com/docs/ref/#/atom/var/vis_flags).
+
+Some flags of interest:
+- `VIS_INHERIT_ID`: This allows you to link the object DIRECTLY to the thing it's drawn on,
+so clicking on the `vis_contents`'d object is just like clicking on the thing
+- `VIS_INHERIT_PLANE`: We will discuss [planes](#planes) more in future, but we use them to both effect rendering order and apply effects as a group.
+This flag changes the plane of any `vis_contents`'d object (while displayed on the source object) to the source's.
+This is occasionally useful, but should be used with care as it breaks any effects that rely on plane.
+
+Anything inside a `vis_contents` list will have its loc stored in its `vis_locs` variable.
+We very rarely use this, primarily just for clearing references from `vis_contents`.
+
+`vis_contents`, unlike `overlays` is a reference, not a copy. So you can update a `vis_contents`'d thing and have it mirror properly.
+This is how we do multiz by the by, with uh, some more hell discussed under [multiz](#multiz).
+
+To pay for this additional behavior however, vis_contents has additional cost in maptick.
+Because it's not a copy, we need to constantly check if it's changed at all, which leads to cost scaling with player count.
+Careful how much you use it.
+
+## Images
+
+
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/image)
+
+Images are technically parents of [mutable appearances](#appearances-in-byond).
+We don't often use them, mostly because we can accomplish their behavior with just MAs.
+
+Images exist both to be used in overlays, and to display things to only select clients on the map.
+See [/client/var/images](#client-images)
+
+> Note: the inheritance between the two is essentially for engine convenience. Don't rely on it.
+
+## Client Images
+
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/client/var/images)
+
+`/client/var/images` is a list of image objects to display to JUST that particular client.
+
+The image objects are displayed at their loc variable, and can be shown to more then one user at once.
+
+### Our Implementation
+
+We use client images in a few ways. Often they will be used just as intended, to modify the view of just one user.
+Think tray scanner or technically ai static.
+
+However, we often want to show a set of images to the same GROUP of people, but in a limited manner.
+For this, we use the `/datum/atom_hud` (hereafter hud) system.
+
+This is different from `/datum/hud`, which I will discuss later.
+
+HUDs are datums that represent categories of images to display to users.
+They are most often global, but can be created on an atom to atom bases in rare cases.
+
+They store a list of images to display (sorted by source z level to reduce lag) and a list of clients to display to.
+
+We then mirror this group of images into/out of the client's images list, based on what HUDs they're able to see.
+This is the pattern we use for things like the medihud, or robot trails.
+
+## View
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/client/var/view)
+
+`/client/var/view` is actually a pretty simple topic,
+but I'm gonna take this chance to discuss the other things we do to manage pixel sizing and such since there isn't a better place for it,
+and they're handled in the same place by us.
+
+Alright then, view. This is pretty simple, but it basically just lets us define the tile bound we want to show to our client.
+
+This can either be a number for an X by X square, or a string in the form "XxY" for more control.
+
+We use `/datum/view_data` to manage and track view changes, so zoom effects can work without canceling or being canceled by anything else.
+
+### Client Rendering Modes
+
+- [Zoom Ref](https://www.byond.com/docs/ref/#/{skin}/param/zoom) / [Zoom Mode Ref](https://www.byond.com/docs/ref/#/{skin}/param/zoom-mode)
+
+Clients get some choice in literally how they want the game to be rendered to them.
+
+The two I'm gonna discuss here are `zoom`, and `zoom-mode` mode, both of which are skin params (basically just variables that live on the client)
+
+`zoom` decides how the client wants to display the turfs shown to it.
+It can have two types of values.
+If it's equal to 0 it will stretch the tiles sent to the client to fix the size of the map-window.
+Otherwise, any other numbers will lead to pixels being scaled by some multiple.
+This effect can only really result in nice clean edges if you pass in whole numbers which is why most of the constant scaling we give players are whole numbers.
+
+`zoom-mode` controls how a pixel will be up-scaled, if it needs to be.
+See the ref for more details, but `normal` is gonna have the sharpest output, `distort` uses nearest neighbor,
+which causes some blur, and `blur` uses bilinear sampling, which causes a LOT of blur.
+
+## Eye
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/client/var/eye)
+
+`/client/var/eye` is the atom or mob at which our view should be centered.
+Any screen objects we display will show "off" this, as will our actual well eye position.
+
+It is by default `/client/var/mob` but it can be modified.
+This is how we accomplish ai eyes and ventcrawling, alongside most other effects that involve a player getting "into" something.
+
+## Client Screen
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/{notes}/HUD)
+
+Similar to client images but not *quite* the same, we can also insert objects onto our client's literal screen
+
+This is done by giving it an appropriate `screen_loc` value, and inserting it into the client's `screen` list.
+
+Note: we use screen for other things too, I'll get to that eventually.
+
+`screen` is actually rather uninteresting, but `screen_loc` has a LOT more nuance.
+
+To start with, the format.
+The classic `screen_loc` format looks something like this (keeping in mind it counts from the top left):
+`x:px,y:py`
+
+The pixel offsets can be discarded as optional, but crucially the x and y values do not NEED to be absolute.
+
+We can use cardinal keywords like `NORTH` to anchor screen objects to the view size of the client (a topic that will be discussed soon).
+You can also use directional keywords like `TOP` to anchor to the actual visible map-window, which prevents any accidental out of bounds.
+Oh yeah you can use absolute offsets to position screen objects out of the view range, which will cause the map-window to forcefully expand,
+exposing the parts of the map byond uses to ahead of time render border things so moving is smooth.
+
+### Secondary Maps
+
+While we're here, this is a bit of a side topic but you can have more then one map-window on a client's screen at once.
+
+This gets into dmf fuckery but you can use [window ids](https://www.byond.com/docs/ref/#/{skin}/param/id) to tell a screen object to render to a secondary map.
+Useful for creating popup windows and such.
+
+## Blend Mode
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/blend_mode)
+
+`/atom/var/blend_mode` defines how an atom well, renders onto the map.
+
+There's a whole bunch of options but really the only one you need to know offhand is `BLEND_MULTIPLY`, which multiplies the thing being drawn "on" by us.
+
+This is how we do lighting effects, since the lighting [plane](#planes) can be used to multiply just normal coloring. If it's all black, the full screen goes black.
+
+## Appearance Flags
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/appearance_flags)
+
+`/atom/var/appearance_flags` is a catch all for toggles that apply to visual elements of an atom.
+I won't go over all of them, but I will discuss a few.
+
+Flags of interest:
+- `LONG_GLIDE`: without this, diagonal movements will automatically take sqrt(2) more time, to account for the greater distance. We do this calculus automatically, and so want this flipped to disable the behavior.
+- `KEEP_TOGETHER`: this allows us to force overlays to render in the same manner as the thing they're overlaid on. Most useful for humans to make alpha changes effect all overlays.
+- `PLANE_MASTER`: I will get into this later, but this allows us to use the [plane](#planes) var to relay renders onto screen objects, so we can apply visual effects and masks and such.
+- `TILE_BOUND`: By default if something is part in one tile and part in another it will display if either is visible. With this set it'll go off its loc value only.
+
+## Gliding
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/{notes}/gliding)
+
+You may have noticed that moving between tiles is smooth, or at least as close as we can get it.
+Moving at 0.2 or 10 tiles per second will be smooth. This is because we have control over the speed at which atoms animate between moves.
+
+This is done using two patterns. One is how we handle input, the other is BYOND's gliding.
+
+We can edit `/atom/movable/var/glide_size` to set the amount of pixels our mob should move per SERVER tick (Our server tick rate is 20 times a second, or 0.5 deciseconds).
+This is done using `/atom/movable/proc/set_glide_size`, which will inform anything we are "carrying" to match our rate.
+
+Glide size is often set in the context of some rate of movement. Either the movement delay of a mob, set in `/client/Move()`, or the delay of a movement subsystem.
+
+We use defines to turn delays into pixels per tick.
+Client moves will be limited by `DELAY_TO_GLIDE_SIZE` which will allow at most 32 pixels a tick.
+Subsystems and other niche uses use `MOVEMENT_ADJUSTED_GLIDE_SIZE`.
+We will also occasionally use glide size as a way to force a transition between different movement types, like space-drift into normal walking.
+There's extra cruft here.
+
+> Something you should know: Our gliding system attempts to account for time dilation when setting move rates.
+This is done in a very simplistic way however, so a spike in td will lead to jumping around as glide rate is outpaced by mob movement rate.
+
+On that note, it is VERY important that glide rate is the same or near the same as actual move rate.
+Otherwise you will get strange jumping and jitter.
+This can also lead to stupid shit where people somehow manage to intentionally shorten a movement delay to jump around. Dumb.
+
+Related to the above, we are not always able to maintain sync between glide rate and mob move rate.
+This is because mob move rate is a function of the initial move delay and a bunch of slowdown/speedup modifiers.
+In order to maintain sync we would need to issue a move command the MOMENT a delay is up, and if delays are not cleanly divisible by our tick rate (0.5 deciseconds) this is impossible.
+This is why you'll sometime see a stutter in your step when slowed
+
+Just so you know, client movement works off `/client/var/move_delay` which sets the next time an input will be accepted. It's typically glide rate, but is in some cases just 1 tick.
+
+## Sight
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/mob/var/sight)
+
+`/mob/var/sight` is a set of bitflags that *mostly* set what HAS to render on your screen. Be that mobs, turfs, etc.
+That said, there is some nuance here so I'ma get into that.
+
+- `SEE_INFRA`: I'll get into this later, but infrared is essentially a copy of BYOND darkness, it's not something we currently use.
+- `SEE_BLACKNESS`: This relates heavily to [planes](#planes), essentially typically the "blackness" (that darkness that masks things that you can't see)
+is rendered separately, out of our control as "users".
+However, if the `SEE_BLACKNESS` flag is set, it will instead render on plane 0, the default BYOND plane.
+This allows us to capture it, and say, blur it, or redraw it elsewhere. Very very powerful, we always have this flag set.
+
+## BYOND Lighting
+
+- [Table of Contents](#table-of-contents)
+
+Alongside OUR lighting implementation, which is discussed in with color matrixes, BYOND has its own lighting system.
+
+It's very basic. Essentially, a tile is either "lit" or it's not.
+
+If a tile is not lit, and it matches some other preconditions, it and all its contents will be hidden from the user,
+sort of like if there was a wall between them. This hiding uses BYOND darkness, and is thus controllable.
+
+I'll use this section to discuss all the little bits that contribute to this behavior
+
+### Luminosity
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/luminosity)
+
+`/atom/var/luminosity` is a variable that lets us inject light into BYOND's lighting system.
+It's real simple, just a range of tiles that will be lit, respecting sight-lines and such of course.
+
+> This "light" is how `/proc/view()` knows if something is in view or not. Oh by the by `view()` respects lighting.
+You can actually force it to use a particular mob's sight to avoid aspects of this, this is what `dview()` is
+
+### See in Dark
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/mob/var/see_in_dark)
+
+`/mob/var/see_in_dark` sets the radius of a square around the mob that cuts out BYOND darkness.
+
+This is why when you stand in darkness you can see yourself, and why you can see a line of objects appear when you use mesons (horrible effect btw).
+It's quite simple, but worth describing.
+
+### Infrared
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/mob/var/see_infrared)
+
+Infrared vision can be thought of as a hidden copy of standard BYOND darkness.
+It's not something we actually use, but I think you should know about it, because the whole thing is real confusing without context.
+
+## Invisibility
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/invisibility)
+
+`/atom/var/invisibility` is a rudimentary way of hiding things from select groups of users. Think of it like [planes](#planes), or [client images](#client-images) but more limited.
+We use this to hide ghosts, ghost visible things, and in the past we used it to hide/show backdrops for the lighting plane, which is semi redundant now.
+
+It's also used to hide some more then ghost invisible things, like some timers and countdowns. It scales from 0 to 101.
+
+`/mob/var/see_invisible` is the catcher of invisibility. If a mob's see_invisible is higher then a target/s invisibility, it'll be shown. Really basic stuff.
+
+## Layers
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/layer)
+
+`/atom/var/layer` is the first bit of logic that decides the order in which things on the map render.
+Rendering order depends a LOT on the [map format](https://www.byond.com/docs/ref/#/world/var/map_format),
+which I will not get into in this document because it is not yet relevant.
+All you really need to know is for our current format,
+the objects that appear first in something's contents will draw first, and render lowest.
+Think of it like stacking little paper cutouts.
+
+Layer has a bit more nuance then just being lowest to highest, tho it's not a lot.
+There are a few snowflake layers that can be used to accomplish niche goals, alongside floating layers, which are essentially just any layer that is negative.
+
+Floating layers will float "up" the chain of things they're being drawn onto, until they find a real layer. They'll then offset off of that.
+
+This allows us to keep relative layer differences while not needing to make all sources static. Often very useful.
+
+## Planes
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/plane)
+
+Allllright `/atom/var/plane`s. Let's talk about em.
+
+They serve two purposes. The first is really simple, and basically just a copy of [layers](#layers).
+Higher planes will (**normally**) render over lower ones. Very clearcut.
+
+Similarly to [layers](#layers), planes also support "floating" with `FLOAT_PLANE`. See above for an explanation of that.
+
+However, they can be used for more complex and... fun things too!
+If a client has an atom with the `PLANE_MASTER` [appearance flag](#appearance-flags) in their [screen](#client-screen),
+then rather then being all rendered normally, anything in the client's view is instead first rendered onto the plane master.
+
+This is VERY powerful, because it lets us [hide](https://www.byond.com/docs/ref/#/atom/var/alpha), [color](#color),
+and [distort](#filters) whole classes of objects, among other things.
+I cannot emphasize enough how useful this is. It does have some downsides however.
+
+Because planes are tied to both grouping and rendering order, there are some effects that require splitting a plane into bits.
+It's also possible for some effects, especially things relating to [map format](https://www.byond.com/docs/ref/#/world/var/map_format),
+to just be straight up impossible, or conflict with each other.
+It's dumb, but it's what we've got brother so we're gonna use it like it's a free ticket to the bahamas.
+
+We have a system that allows for arbitrary grouping of plane masters for the purposes of [filter effects](#filters)
+called `/atom/movable/plane_master_controller`.
+This is somewhat outmoded by our use of [render relays](#render-targetsource), but it's still valid and occasionally useful.
+
+> Something you should know: Plane masters effect ONLY the map their screen_loc is on.
+For this reason, we are forced to generate whole copies of the set of plane masters with the proper screen_loc to make subviews look right
+
+> Warning: Planes have some restrictions on valid values. They NEED to be whole integers, and they NEED to have an absolute value of `10000`.
+This is to support `FLOAT_PLANE`, which lives out at the very edge of the 32 bit int range.
+
+## Render Target/Source
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/render_target)
+
+Render targets are a way of rendering one thing onto another. Not like vis_contents but in a literal sense ONTO.
+The target object is given a `/atom/var/render_target` value, and anything that wishes to "take" it sets its `/atom/var/render_source` var to match.
+
+When I say render onto, I mean it literally. It is like adding a second step in the rendering process.
+
+You can even prepend * to the render target value to disable the initial render, and JUST render via the render source.
+
+### Our Implementation
+
+We use render targets to create "render relays" which can be used to link [plane masters](#planes) together and accomplish more advanced effects.
+See [the renderer documentation](../../code/_onclick/hud/rendering/_render_readme.md) for visualizations for this.
+
+> Of note: this linking behavior is accomplished by adding a screen object to link onto with a plane value of the desired PM we want to relay onto.
+Layer is VERY important here, and will be set based off the layer of the last plane master.
+This means plane order is not always the absolute order in which different plane masters render. Be careful of this.
+
+> To edit and display planes and plane connections in game, run the `Edit/Debug Planes` command.
+It will open a ui that allows you to view relay connections, plane master descriptions, and edit their values and effects.
+
+## Multiz
+- [Table of Contents](#table-of-contents)
+- Reference: Hell of our own creation
+
+I'm gonna explain how our multiz system works. But first I need to explain how it used to work.
+
+What we used to do was take an openspace turf above, insert the turf below into its [vis_contents](#visual-contents), and call it a day.
+This worked because everything on the map had the `VIS_INHERIT_PLANE` flag, and openspace had a plane master below most everything.
+
+This meant the turf below looked as if it was offset, and everything was good.
+
+Except not, for 2 reasons. One more annoying then the other.
+
+- 1: It looked like dog doo-doo. This pattern destroyed the old planes of everything vis_contents'd, so effects/lighting/dropshadows broke bad.
+- 2: I alluded to this earlier, but it totally breaks the `side_map` [map format](https://www.byond.com/docs/ref/#/world/var/map_format)
+which I need for a massive resprite I'm helping with. This is because `side_map` changes how rendering order works,
+going off "distance" from the front of the frame.
+The issue here is it of course needs a way to group things that are even allowed to overlap, so it uses plane.
+So when you squish everything down onto one plane, this of course breaks horribly and fucks you.
+
+Ok then, old way's not workable. What will we do instead?
+
+There's two problems here. The first is that all our plane masters come pre-ordered. We need a way to have lower and upper plane masters.
+
+This is well... not trivial but not hard either. We essentially duplicate all our plane masters out like a tree, and link the head of the master rendering plate
+to the openspace plane master one level up. More then doable.
+
+SECOND problem. How do we get everything below to "land" on the right plane?
+
+The answer to this is depressing but still true. We manually offset every single object on the map's plane based off its "z layer".
+This includes any `overlays` or `vis_contents` with a unique plane value.
+
+Mostly we require anything that sets the plane var to pass in a source of context, like a turf or something that can be used to derive a turf.
+There are a few edge cases where we need to work in explicitly offsets, but those are much rarer.
+
+This is stupid, but it's makable, and what we do.
+
+## Mouse Opacity
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/mouse_opacity)
+
+`/atom/var/mouse_opacity` tells clients how to treat mousing over the atom in question.
+
+A value of 0 means it is completely ignored, no matter what.
+A value of 1 means it is transparent/opaque based off the alpha of the icon at any particular part.
+A value of 2 means it will count as opaque across ALL of the icon-state. All 32x32 (or whatever) of it.
+
+We will on occasion use mouse opacity to expand hitboxes, but more often this is done with [vis_contents](#visual-contents),
+or just low alpha pixels on the sprite.
+
+> Note: Mouse opacity will only matter if the atom is being rendered on its own. [Overlays](#overlays)(and [images](#images))
+will NOT work as expected with this.
+However, you can still have totally transparent overlays. If you render them onto a [plane master](#planes) with the desired mouse opacity value
+it will work as expected. This is because as a step of the rendering pipeline the overlay is rendered ONTO the plane master, and then the plane
+master's effects are applied.
+
+## Filters
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/{notes}/filters)
+
+Filters are a general purpose system for applying a limited set of shaders to a render.
+These shaders run on the client's machine. This has upsides and downsides.
+Upside: Very cheap for the server. Downside: Potentially quite laggy for the client.
+Take care with these
+
+Like I said, they're quite general purpose. There's a LOT of different effects, and many things you can do with them.
+
+There's two things I want you to know about them, partly to put across their usefulness, and partially so you know their limitations.
+
+On Usefulness. There are filters for alpha masking. They accept render sources as params, which means we can use say, one plane master
+to mask out another. This + some fucking bullshit is how emissive lighting works.
+
+Similarly there are filters for distortions. This is how we accomplish the grav anomaly effect, as it too accepts a render source as a param.
+
+On limitations: Filters, like many things in BYOND, are stored in a snowflake list on `/atom`. This means if we want to manage them,
+we will need our own management system. This is why we, unlike byond, use a wrapper around filters to set priorities and manage addition/removal.
+This system has the potential to break animations and other such things. Take care.
+
+> We have a debug tool for filters, called filterrific. You can access it in-game by vving an atom, going to the dropdown, and hitting `Edit Filters`
+It'll let you add and tweak *most* of the filters in BYOND.
+
+## Particles
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/{notes}/particles)
+
+Particles are a system that allows you to attach "generators" to atoms on the world, and have them spit out little visual effects.
+This is done by creating a subtype of the `/particles` type, and giving it the values you want.
+
+At base BYOND only allows you to attach one particle emitter to any one `/atom`. We get around this using an atom inserted into the loc of some parent atom to follow.
+The type is `/obj/effect/abstract/particle_holder`. Interacting with it's real simple, you just pass in the location to mirror, and the type to use.
+It'll do the rest.
+
+## Pixel Offsets
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/pixel_x)
+
+This is a real simple idea and I normally wouldn't mention it, but I have something else I wanna discuss related to it, so I'ma take this chance.
+
+`/atom/var/pixel_x/y/w/z` are variables that allow us to offset the DISPLAY position of an atom. This doesn't effect its position on the map mind,
+just where it APPEARS to be. This is useful for many little effects, and some larger ones.
+
+Anyway, onto why I'm mentioning this.
+
+There are two "types" of each direction offset. There's the "real" offset (x/y) and the "fake" offset (w,z).
+Real offsets will change both the visual position (IE: where it renders) and also the positional position (IE: where the renderer thinks they are).
+Fake offsets only effect visual position.
+
+This doesn't really matter for our current map format, but for anything that takes position into account when layering, like `side_map` or `isometric_map`
+it matters a whole ton. It's kinda a hard idea to get across, but I hope you have at least some idea.
+
+## Color
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/color)
+
+`/atom/var/color` is another one like [pixel offsets](#pixel-offsets) where its most common use is really uninteresting, but it has an interesting
+edge case I think is fun to discuss/important to know.
+
+So let's get the base case out of the way shall we?
+
+At base, you can set an atom's color to some `rrggbbaa` string (see [here](https://www.byond.com/docs/ref/#/{{appendix}}/html-colors)). This will shade every pixel on that atom to said color, and override its [`/atom/var/alpha`](https://www.byond.com/docs/ref/#/atom/var/alpha) value.
+See [appearance flags](#appearance-flags) for how this effect can carry into overlays and such.
+
+That's the boring stuff, now the fun shit.
+
+> Before we get into this. `rr` is read as "red to red". `ag` is read as "alpha to green", etc. `c` is read as constant, and always has a value of 255
+
+You can use the color variable to not just shade, but shift the colors of the atom.
+It accepts a list (functionally a matrix if you know those) in the format `list(rr,br,gr,ar, rb,bb,gb,ab, rg,bg,gg,ag, ra,ba,ga,aa, cr,cb,cg,ca)`
+This allows us to essentially multiply the color of each pixel by some other other. The values inserted in each multiple are not really bounded.
+
+You can accomplish some really fun effects with this trick, it gives you a LOT of control over the color of a sprite or say, a [plane master](#planes)
+and leads to some fun vfx.
+
+> We have a debug tool for color matrixes. Just VV an atom, go to the VV dropdown and look for the `Edit Color as Matrix` entry.
+It'll help visualize this process quite well. Play around with it, it's fun.
+
+## Transform
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/transform)
+
+`/atom/var/transform` allows you to shift, contort, rotate and scale atoms visually.
+This is done using a matrix, similarly to color matrixes. You will likely never need to use it manually however, since there are
+helper procs for pretty much everything it can do.
+
+> Note: the transform var is COPIED whenever you read it. So if you want to modify it, you will need to reset the atom var back to your changes.
+
+It's not totally without explanation, and I figured you might wanna know about it. Not a whole lot more to say tho. Neat tool.
+
+## Lighting
+- [Table of Contents](#table-of-contents)
+- Reference: Hell of our own creation
+
+I wanted to take this chance to briefly explain the essentials of how our lighting system works.
+Essentially, each tile has a lighting [overlay](#overlays) (technically an [underlay](https://www.byond.com/docs/ref/#/atom/var/underlays)
+ which is just overlays but drawn under).
+Anyway, each underlay is a color gradient, with red green and blue and alpha in each corner.
+Every "corner" (we call them lighting corners) on the map impacts the 4 colors that touch it.
+This is done with color matrixes. This allows us to apply color and lighting in a smooth way, while only needing 1 overlay per tile.
+
+There's a lot of nuance here, like how color is calculated and stored, and our overlay lighting system which is a whole other beast.
+But it covers the core idea, the rest should be derivable, and you're more qualified to do so then me, assuming some bastard will come along to change it
+and forget to update this file.
+
+## Animate()
+- [Table of Contents](#table-of-contents)
+- [Reference Entry](https://www.byond.com/docs/ref/#/proc/animate)
+
+The animate proc allows us to VISUALLY transition between different values on an appearance on clients, while in actuality
+setting the values instantly on the servers.
+
+This is quite powerful, and lets us do many things, like slow fades, shakes, hell even parallax using matrixes.
+
+It doesn't support everything, and it can be quite temperamental especially if you use things like the flag that makes it work in
+parallel. It's got a lot of nuance to it, but it's real useful. Works on filters and their variables too, which is AGGRESSIVELY useful.
+
+Lets you give radiation glow a warm pulse, that sort of thing.
+
+## GAGS
+- [Table of Contents](#table-of-contents)
+- Reference: Hell of our own creation
+
+GAGS is a system of our own design built to support runtime creation of icons from split components.
+
+This means recoloring is trivial, and bits of sprites can be combined and split easily. Very useful.
+
+I won't go into much detail here, check out the [starter guide](https://hackmd.io/@tgstation/GAGS-Walkthrough) for more info if you're interested.
diff --git a/code/__DEFINES/atmospherics/atmos_helpers.dm b/code/__DEFINES/atmospherics/atmos_helpers.dm
index e321404ad15..4b1b4227c7d 100644
--- a/code/__DEFINES/atmospherics/atmos_helpers.dm
+++ b/code/__DEFINES/atmospherics/atmos_helpers.dm
@@ -47,16 +47,20 @@
GLOBAL_LIST_INIT(nonoverlaying_gases, typecache_of_gases_with_no_overlays())
///Returns a list of overlays of every gas in the mixture
-#define GAS_OVERLAYS(gases, out_var)\
- out_var = list();\
- for(var/_ID in gases){\
- if(GLOB.nonoverlaying_gases[_ID]) continue;\
- var/_GAS = gases[_ID];\
- var/_GAS_META = _GAS[GAS_META];\
- if(_GAS[MOLES] <= _GAS_META[META_GAS_MOLES_VISIBLE]) continue;\
- var/_GAS_OVERLAY = _GAS_META[META_GAS_OVERLAY];\
- out_var += _GAS_OVERLAY[min(TOTAL_VISIBLE_STATES, CEILING(_GAS[MOLES] / MOLES_GAS_VISIBLE_STEP, 1))];\
- }
+#define GAS_OVERLAYS(gases, out_var, z_layer_turf)\
+ do { \
+ out_var = list();\
+ var/offset = GET_TURF_PLANE_OFFSET(z_layer_turf);\
+ for(var/_ID in gases){\
+ if(GLOB.nonoverlaying_gases[_ID]) continue;\
+ var/_GAS = gases[_ID];\
+ var/_GAS_META = _GAS[GAS_META];\
+ if(_GAS[MOLES] <= _GAS_META[META_GAS_MOLES_VISIBLE]) continue;\
+ var/_GAS_OVERLAY = _GAS_META[META_GAS_OVERLAY][offset + 1];\
+ out_var += _GAS_OVERLAY[min(TOTAL_VISIBLE_STATES, CEILING(_GAS[MOLES] / MOLES_GAS_VISIBLE_STEP, 1))];\
+ } \
+ }\
+ while (FALSE)
#ifdef TESTING
GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0))
@@ -79,7 +83,7 @@ GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0))
*
* To equalize two gas mixtures, we simply pool the energy and divide it by the pooled heat capacity.
* T' = (W1+W2) / (C1+C2)
- * But if we want to moderate this conduction, maybe we can calculate the energy transferred
+ * But if we want to moderate this conduction, maybe we can calculate the energy transferred
* and multiply a coefficient to it instead.
* This is the energy transferred:
* W = T' * C1 - W1
@@ -91,20 +95,20 @@ GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0))
* W = (W2C1 - W1C2) / (C1+C2)
* W = (T2*C2*C1 - T1*C1*C2) / (C1+C2)
* W = (C1*C2) * (T2-T1) / (C1+C2)
- *
+ *
* W: Energy involved in the operation
* T': Combined temperature
* T1, C1, W1: Temp, heat cap, and thermal energy of the first gas mixture
* T2, C2, W2: Temp, heat cap, and thermal energy of the second gas mixture
*
* Not immediately obvious, but saves us operation time.
- *
- * We put a lot of parentheses here because the numbers get really really big.
+ *
+ * We put a lot of parentheses here because the numbers get really really big.
* By prioritizing the division we try to tone the number down so we dont get overflows.
- *
+ *
* Arguments:
* * temperature_delta: T2 - T1. [/datum/gas_mixture/var/temperature]
- * If you have any moderating (less than 1) coefficients and are dealing with very big numbers
+ * If you have any moderating (less than 1) coefficients and are dealing with very big numbers
* multiply the temperature_delta by it first before passing so we get even more breathing room.
* * heat_capacity_one: gasmix one's [/datum/gas_mixture/proc/heat_capacity]
* * heat_capacity_two: gasmix two's [/datum/gas_mixture/proc/heat_capacity]
diff --git a/code/__DEFINES/blend_modes.dm b/code/__DEFINES/blend_modes.dm
new file mode 100644
index 00000000000..65a4df96ba4
--- /dev/null
+++ b/code/__DEFINES/blend_modes.dm
@@ -0,0 +1,36 @@
+
+// Taken from https://www.byond.com/docs/ref/#/atom/var/blend_mode
+// I want you to be able to get these values without using global.vars manually yourself.
+// The suggestions here are from the ref, and therefore are NOT ALWAYS ACCURATE TO SS13
+
+// Controls the way the atom's icon is blended onto the icons behind it.
+// The blend mode used by an atom is inherited by any attached overlays, unless they override it.
+// BLEND_DEFAULT will use the main atom's blend mode; for the atom itself, it's the same as BLEND_OVERLAY.
+// #define BLEND_DEFAULT 0
+
+// BLEND_OVERLAY will draw an icon the normal way.
+// #define BLEND_OVERLAY 1
+
+// BLEND_ADD will do additive blending, so that the colors in the icon are added to whatever is behind it.
+// Light effects like explosions will tend to look better in this mode.
+// #define BLEND_ADD 2
+
+// BLEND_SUBTRACT is for subtractive blending. This may be useful for special effects.
+// #define BLEND_SUBTRACT 3
+
+// BLEND_MULTIPLY will multiply the icon's colors by whatever is behind it.
+// This is typically only useful for applying a colored light effect; for simply darkening, using a translucent black icon with normal overlay blending is a better option.
+// #define BLEND_MULTIPLY 4
+
+// BLEND_INSET_OVERLAY overlays the icon, but masks it by the image being drawn on.
+// This is pretty much not at all useful directly on the map, but can be very useful for an overlay for an atom that uses KEEP_TOGETHER (see appearance_flags), or for the layering filter.
+// #define BLEND_INSET_OVERLAY 5
+
+GLOBAL_LIST_INIT(blend_names, list(
+ "0" = "BLEND_DEFAULT",
+ "1" = "BLEND_OVERLAY",
+ "2" = "BLEND_ADD",
+ "3" = "BLEND_SUBTRACT",
+ "4" = "BLEND_MULTIPLY",
+ "5" = "BLEND_INSET_OVERLAY",
+))
diff --git a/code/__DEFINES/dcs/signals/mapping.dm b/code/__DEFINES/dcs/signals/mapping.dm
new file mode 100644
index 00000000000..d80d6036580
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/mapping.dm
@@ -0,0 +1,2 @@
+// Sent when the max plane offset changes : (old_max_offset, new_max_offset)
+#define COMSIG_PLANE_OFFSET_INCREASE "plane_offset_increase"
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
index 18c1c438b18..7a233a6a431 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
@@ -45,7 +45,7 @@
#define COMSIG_MOVABLE_POST_THROW "movable_post_throw"
///from base of datum/thrownthing/finalize(): (obj/thrown_object, datum/thrownthing) used for when a throw is finished
#define COMSIG_MOVABLE_THROW_LANDED "movable_throw_landed"
-///from base of atom/movable/on_changed_z_level(): (turf/old_turf, turf/new_turf)
+///from base of atom/movable/on_changed_z_level(): (turf/old_turf, turf/new_turf, same_z_layer)
#define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit"
///called when the movable is placed in an unaccessible area, used for stationloving: ()
#define COMSIG_MOVABLE_SECLUDED_LOCATION "movable_secluded"
diff --git a/code/__DEFINES/dcs/signals/signals_client.dm b/code/__DEFINES/dcs/signals/signals_client.dm
index 2d12cb9de88..e20946d925a 100644
--- a/code/__DEFINES/dcs/signals/signals_client.dm
+++ b/code/__DEFINES/dcs/signals/signals_client.dm
@@ -2,3 +2,6 @@
// from /client/proc/change_view() : (new_size)
#define COMSIG_VIEW_SET "view_set"
+
+// from /client/proc/handle_popup_close() : (window_id)
+#define COMSIG_POPUP_CLEARED "popup_cleared"
diff --git a/code/__DEFINES/dcs/signals/signals_hud.dm b/code/__DEFINES/dcs/signals/signals_hud.dm
new file mode 100644
index 00000000000..50e3069a614
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_hud.dm
@@ -0,0 +1,2 @@
+/// Sent from /datum/hud/proc/eye_z_changed() : (old_offset, new_offset)
+#define COMSIG_HUD_OFFSET_CHANGED "hud_offset_changed"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
index cde74e11a7e..52ceb04a575 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
@@ -42,8 +42,11 @@
#define COMSIG_MOB_CLIENT_MOVED "mob_client_moved"
/// From base of /client/proc/change_view() (mob/source, new_size)
#define COMSIG_MOB_CLIENT_CHANGE_VIEW "mob_client_change_view"
-/// From base of /mob/proc/reset_perspective() (mob/source)
+/// From base of /mob/proc/reset_perspective() : ()
#define COMSIG_MOB_RESET_PERSPECTIVE "mob_reset_perspective"
+/// from base of /client/proc/set_eye() : (atom/old_eye, atom/new_eye)
+#define COMSIG_CLIENT_SET_EYE "client_set_eye"
+
///from mind/transfer_to. Sent to the receiving mob.
#define COMSIG_MOB_MIND_TRANSFERRED_INTO "mob_mind_transferred_into"
@@ -62,6 +65,16 @@
///from base of mob/create_mob_hud(): ()
#define COMSIG_MOB_HUD_CREATED "mob_hud_created"
+///from base of hud/show_to(): (datum/hud/hud_source)
+#define COMSIG_MOB_HUD_REFRESHED "mob_hud_refreshed"
+
+///from base of mob/set_sight(): (new_sight, old_sight)
+#define COMSIG_MOB_SIGHT_CHANGE "mob_sight_changed"
+///from base of mob/set_invis_see(): (new_invis, old_invis)
+#define COMSIG_MOB_SEE_INVIS_CHANGE "mob_see_invis_change"
+///from base of mob/set_see_in_dark(): (new_range, old_range)
+#define COMSIG_MOB_SEE_IN_DARK_CHANGE "mob_see_in_dark_change"
+
///from base of /mob/living/proc/apply_damage(): (damage, damagetype, def_zone)
#define COMSIG_MOB_APPLY_DAMAGE "mob_apply_damage"
diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm
index 0d249fb4073..daf45ed4d7a 100644
--- a/code/__DEFINES/hud.dm
+++ b/code/__DEFINES/hud.dm
@@ -211,9 +211,14 @@
#define SCRN_OBJ_IN_LIST "list"
/// In the collapseable palette
#define SCRN_OBJ_IN_PALETTE "palette"
-
///Inserted first in the list
#define SCRN_OBJ_INSERT_FIRST "first"
+// Plane group keys, used to group swaths of plane masters that need to appear in subwindows
+/// The primary group, holds everything on the main window
+#define PLANE_GROUP_MAIN "main"
+/// A secondary group, used when a client views a generic window
+#define PLANE_GROUP_POPUP_WINDOW(screen) "popup-[REF(screen)]"
+
/// The filter name for the hover outline
#define HOVER_OUTLINE_FILTER "hover_outline"
diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm
index 6402f81d2d1..6d67e96089a 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -2,23 +2,20 @@
//KEEP THESE IN A NICE ACSCENDING ORDER, PLEASE
//NEVER HAVE ANYTHING BELOW THIS PLANE ADJUST IF YOU NEED MORE SPACE
-#define LOWEST_EVER_PLANE -200
+#define LOWEST_EVER_PLANE -100
-#define FIELD_OF_VISION_BLOCKER_PLANE -199
+#define FIELD_OF_VISION_BLOCKER_PLANE -90
#define FIELD_OF_VISION_BLOCKER_RENDER_TARGET "*FIELD_OF_VISION_BLOCKER_RENDER_TARGET"
-#define CLICKCATCHER_PLANE -99
+#define CLICKCATCHER_PLANE -80
-#define PLANE_SPACE -95
-#define PLANE_SPACE_PARALLAX -90
+#define PLANE_SPACE -25
+#define PLANE_SPACE_PARALLAX -20
#define GRAVITY_PULSE_PLANE -12
#define GRAVITY_PULSE_RENDER_TARGET "*GRAVPULSE_RENDER_TARGET"
-#define TRANSPARENT_FLOOR_PLANE -11 //Transparent plane that shows openspace underneath the floor
-#define OPENSPACE_PLANE -10 //Openspace plane below all turfs
-#define OPENSPACE_BACKDROP_PLANE -9 //Black square just over openspace plane to guaranteed cover all in openspace turf
-
+#define RENDER_PLANE_TRANSPARENT -9 //Transparent plane that shows openspace underneath the floor
#define FLOOR_PLANE -8
@@ -28,13 +25,79 @@
#define GAME_PLANE_UPPER_FOV_HIDDEN -4
///Slightly above the game plane but does not catch mouse clicks. Useful for certain visuals that should be clicked through, like seethrough trees
-#define ABOVE_GAME_NO_MOUSE_PLANE -3
+#define SEETHROUGH_PLANE -3
#define ABOVE_GAME_PLANE -2
+#define RENDER_PLANE_GAME_WORLD -1
+
+#define BLACKNESS_PLANE 0 //To keep from conflicts with SEE_BLACKNESS internals
+
+#define AREA_PLANE 2
+#define MASSIVE_OBJ_PLANE 3
+#define GHOST_PLANE 4
+#define POINT_PLANE 5
+
+//---------- LIGHTING -------------
+///Normal 1 per turf dynamic lighting underlays
+#define LIGHTING_PLANE 10
+
+///Lighting objects that are "free floating"
+#define O_LIGHTING_VISUAL_PLANE 11
+#define O_LIGHTING_VISUAL_RENDER_TARGET "O_LIGHT_VISUAL_PLANE"
+
+///Things that should render ignoring lighting
+#define ABOVE_LIGHTING_PLANE 12
+
+/// This plane masks out lighting to create an "emissive" effect, ie for glowing lights in otherwise dark areas.
+#define EMISSIVE_PLANE 14
+
+#define RENDER_PLANE_LIGHTING 15
+
+///---------------- MISC -----------------------
+
+///Pipecrawling images
+#define PIPECRAWL_IMAGES_PLANE 20
+
+///AI Camera Static
+#define CAMERA_STATIC_PLANE 21
+
+///Anything that wants to be part of the game plane, but also wants to draw above literally everything else
+#define HIGH_GAME_PLANE 22
+
+#define FULLSCREEN_PLANE 23
+
+///--------------- FULLSCREEN RUNECHAT BUBBLES ------------
+
+///Popup Chat Messages
+#define RUNECHAT_PLANE 30
+/// Plane for balloon text (text that fades up)
+#define BALLOON_CHAT_PLANE 31
+
+//-------------------- HUD ---------------------
+//HUD layer defines
+#define HUD_PLANE 40
+#define ABOVE_HUD_PLANE 41
+
+///Plane of the "splash" icon used that shows on the lobby screen. only render plate planes should be above this
+#define SPLASHSCREEN_PLANE 50
+
+//-------------------- Rendering ---------------------
+#define RENDER_PLANE_GAME 100
+#define RENDER_PLANE_NON_GAME 101
+#define RENDER_PLANE_MASTER 102
+
+// Lummox I swear to god I will find you
+// NOTE! You can only ever have planes greater then -10000, if you add too many with large offsets you will brick multiz
+// Same can be said for large multiz maps. Tread carefully mappers
+#define HIGHEST_EVER_PLANE RENDER_PLANE_MASTER
+/// The range unique planes can be in
+#define PLANE_RANGE (HIGHEST_EVER_PLANE - LOWEST_EVER_PLANE)
+
// PLANE_SPACE layer(s)
#define SPACE_LAYER 1.8
//#define TURF_LAYER 2 //For easy recordkeeping; this is a byond define. Most floors (FLOOR_PLANE) and walls (GAME_PLANE) use this.
+#define OPENSPACE_LAYER 600 //Openspace layer over all turfs
// GAME_PLANE layers
#define CULT_OVERLAY_LAYER 2.01
@@ -120,60 +183,26 @@
#define GASFIRE_LAYER 5.05
#define RIPPLE_LAYER 5.1
-#define OPENSPACE_LAYER 600 //Openspace layer over all
-
-#define BLACKNESS_PLANE 0 //To keep from conflicts with SEE_BLACKNESS internals
-
-#define AREA_PLANE 60
-#define MASSIVE_OBJ_PLANE 70
-#define GHOST_PLANE 80
-#define POINT_PLANE 90
-
//---------- LIGHTING -------------
-///Normal 1 per turf dynamic lighting underlays
-#define LIGHTING_PLANE 100
-
-///Lighting objects that are "free floating"
-#define O_LIGHTING_VISUAL_PLANE 110
-#define O_LIGHTING_VISUAL_RENDER_TARGET "O_LIGHT_VISUAL_PLANE"
-
-///Things that should render ignoring lighting
-#define ABOVE_LIGHTING_PLANE 120
#define LIGHTING_PRIMARY_LAYER 15 //The layer for the main lights of the station
#define LIGHTING_PRIMARY_DIMMER_LAYER 15.1 //The layer that dims the main lights of the station
#define LIGHTING_SECONDARY_LAYER 16 //The colourful, usually small lights that go on top
-///visibility + hiding of things outside of light source range
-#define BYOND_LIGHTING_PLANE 130
//---------- EMISSIVES -------------
//Layering order of these is not particularly meaningful.
//Important part is the seperation of the planes for control via plane_master
-/// This plane masks out lighting to create an "emissive" effect, ie for glowing lights in otherwise dark areas.
-#define EMISSIVE_PLANE 150
/// The render target used by the emissive layer.
#define EMISSIVE_RENDER_TARGET "*EMISSIVE_PLANE"
/// The layer you should use if you _really_ don't want an emissive overlay to be blocked.
#define EMISSIVE_LAYER_UNBLOCKABLE 9999
-///---------------- MISC -----------------------
-
-///Pipecrawling images
-#define PIPECRAWL_IMAGES_PLANE 180
-
-///AI Camera Static
-#define CAMERA_STATIC_PLANE 200
-
-///Debug Atmos Overlays
-#define ATMOS_GROUP_PLANE 450
-
///--------------- FULLSCREEN IMAGES ------------
-#define FULLSCREEN_PLANE 500
#define FLASH_LAYER 1
#define FULLSCREEN_LAYER 2
#define UI_DAMAGE_LAYER 3
@@ -183,19 +212,9 @@
#define FOV_EFFECTS_LAYER 10000 //Blindness effects are not layer 4, they lie to you
///--------------- FULLSCREEN RUNECHAT BUBBLES ------------
-
-///Popup Chat Messages
-#define RUNECHAT_PLANE 501
-/// Plane for balloon text (text that fades up)
-#define BALLOON_CHAT_PLANE 502
/// Bubble for typing indicators
#define TYPING_LAYER 500
-//-------------------- HUD ---------------------
-//HUD layer defines
-#define HUD_PLANE 1000
-#define ABOVE_HUD_PLANE 1100
-
#define RADIAL_BACKGROUND_LAYER 0
///1000 is an unimportant number, it's just to normalize copied layers
#define RADIAL_CONTENT_LAYER 1000
@@ -205,14 +224,6 @@
///Layer for screentips
#define SCREENTIP_LAYER 4
-///Plane of the "splash" icon used that shows on the lobby screen. only render plate planes should be above this
-#define SPLASHSCREEN_PLANE 9900
-
-//-------------------- Rendering ---------------------
-#define RENDER_PLANE_GAME 9990
-#define RENDER_PLANE_NON_GAME 9995
-#define RENDER_PLANE_MASTER 9999
-//----------------------------------------------------
#define LOBBY_BACKGROUND_LAYER 3
#define LOBBY_BUTTON_LAYER 4
diff --git a/code/__DEFINES/movement.dm b/code/__DEFINES/movement.dm
index 10f9e67025f..9cebcad7bbf 100644
--- a/code/__DEFINES/movement.dm
+++ b/code/__DEFINES/movement.dm
@@ -4,7 +4,7 @@
/// This shouldn't be higher than the icon size, and generally you shouldn't be changing this, but it's here just in case.
#define MAX_GLIDE_SIZE 32
-/// Compensating for time dialation
+/// Compensating for time dilation
GLOBAL_VAR_INIT(glide_size_multiplier, 1.0)
///Broken down, here's what this does:
diff --git a/code/__DEFINES/sight.dm b/code/__DEFINES/sight.dm
index 698ffb3a06c..924049db8ff 100644
--- a/code/__DEFINES/sight.dm
+++ b/code/__DEFINES/sight.dm
@@ -30,3 +30,30 @@
#define VISOR_VISIONFLAGS (1<<2) //all following flags only matter for glasses
#define VISOR_DARKNESSVIEW (1<<3)
#define VISOR_INVISVIEW (1<<4)
+
+// BYOND internal values for the sight flags
+// See [https://www.byond.com/docs/ref/#/mob/var/sight]
+/// can't see anything
+//#define BLIND (1<<0)
+/// can see all mobs, no matter what
+//#define SEE_MOBS (1<<2)
+/// can see all objs, no matter what
+//#define SEE_OBJS (1<<3)
+// can see all turfs (and areas), no matter what
+//#define SEE_TURFS (1<<4)
+/// can see self, no matter what
+//#define SEE_SELF (1<<5)
+/// can see infra-red objects (different sort of luminosity, essentially a copy of it, one we do not use)
+//#define SEE_INFRA (1<<6)
+/// if an object is located on an unlit area, but some of its pixels are
+/// in a lit area (via pixel_x,y or smooth movement), can see those pixels
+//#define SEE_PIXELS (1<<8)
+/// can see through opaque objects
+//#define SEE_THRU (1<<9)
+/// render dark tiles as blackness (Note, this basically means we draw dark tiles to plane 0)
+/// we can then hijack that plane with a plane master, and start drawing it anywhere we want
+//#define SEE_BLACKNESS (1<<10)
+
+/// Bitfield of sight flags that show things "inside" the blackness plane
+/// We've gotta alpha it down if we get this, cause otherwise the sight flag won't work
+#define BLACKNESS_CUTTING (SEE_MOBS|SEE_OBJS|SEE_TURFS|SEE_TURFS|SEE_TURFS)
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index cc655f59ff5..0ae13df5e7e 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -121,6 +121,7 @@
#define VV_HK_DIRECT_CONTROL "direct_control"
#define VV_HK_GIVE_DIRECT_CONTROL "give_direct_control"
#define VV_HK_OFFER_GHOSTS "offer_ghosts"
+#define VV_HK_VIEW_PLANES "view_planes"
// /mob/living
#define VV_HK_GIVE_SPEECH_IMPEDIMENT "impede_speech"
diff --git a/code/__HELPERS/_planes.dm b/code/__HELPERS/_planes.dm
new file mode 100644
index 00000000000..1af0b636514
--- /dev/null
+++ b/code/__HELPERS/_planes.dm
@@ -0,0 +1,78 @@
+// This file contains helper macros for plane operations
+// See the planes section of Visuals.md for more detail, but essentially
+// When we render multiz, we do it by placing all atoms on lower levels on well, lower planes
+// This is done with stacks of plane masters (things we use to apply effects to planes)
+// These macros exist to facilitate working with this system, and other associated small bits
+
+/// Takes an atom to change the plane of, a new plane value, and something that can be used as a reference to a z level as input
+/// Modifies the new value to match the plane we actually want. Note, if you pass in an already offset plane the offsets will add up
+/// Use PLANE_TO_TRUE() to avoid this
+#define SET_PLANE(thing, new_value, z_reference) (thing.plane = MUTATE_PLANE(new_value, z_reference))
+
+/// Takes a plane and a z reference, and offsets the plane by the mutation
+/// The SSmapping.max_plane_offset bit here is technically redundant, but saves a bit of work in the base case
+/// And the base case is important to me. Non multiz shouldn't get hit too bad by this code
+#define MUTATE_PLANE(new_value, z_reference) ((SSmapping.max_plane_offset) ? GET_NEW_PLANE(new_value, GET_TURF_PLANE_OFFSET(z_reference)) : (new_value))
+
+/// Takes a z reference that we are unsure of, sanity checks it
+/// Returns either its offset, or 0 if it's not a valid ref
+#define GET_TURF_PLANE_OFFSET(z_reference) ((SSmapping.max_plane_offset && isatom(z_reference)) ? GET_Z_PLANE_OFFSET(z_reference.z) : 0)
+/// Essentially just an unsafe version of GET_TURF_PLANE_OFFSET()
+/// Takes a z value we returns its offset with a list lookup
+/// Will runtime during parts of init. Be careful :)
+#define GET_Z_PLANE_OFFSET(z) (SSmapping.z_level_to_plane_offset[z])
+
+/// Takes a plane to offset, and the multiplier to use, and well, does the offsetting
+/// Respects a blacklist we use to remove redundant plane masters, such as hud objects
+#define GET_NEW_PLANE(new_value, multiplier) (SSmapping.plane_offset_blacklist?["[new_value]"] ? new_value : (new_value) - (PLANE_RANGE * (multiplier)))
+
+// Now for the more niche things
+
+/// Takes an object, new plane, and multipler, and offsets the plane
+/// This is for cases where you have a multipler precalculated, and just want to use it
+/// Often an optimization, sometimes a necessity
+#define SET_PLANE_W_SCALAR(thing, new_value, multiplier) (thing.plane = GET_NEW_PLANE(new_value, multiplier))
+
+
+/// Implicit plane set. We take the turf from the object we're changing the plane of, and use ITS z as a spokesperson for our plane value
+#define SET_PLANE_IMPLICIT(thing, new_value) SET_PLANE_EXPLICIT(thing, new_value, thing)
+
+// This is an unrolled and optimized version of SET_PLANE, for use anywhere where you are unsure of a source's "turfness"
+// The plane is cached to allow for fancy stuff to be eval'd once, rather then often
+#define SET_PLANE_EXPLICIT(thing, new_value, source) \
+ do {\
+ if(SSmapping.max_plane_offset) {\
+ var/_cached_plane = new_value;\
+ var/turf/_our_turf = get_turf(source);\
+ if(_our_turf){\
+ thing.plane = GET_NEW_PLANE(_cached_plane, GET_Z_PLANE_OFFSET(_our_turf.z));\
+ }\
+ }\
+ else {\
+ thing.plane = new_value;\
+ }\
+ }\
+ while (FALSE)
+
+// Now for macros that exist to get info from SSmapping
+// Mostly about details of planes, or z levels
+
+/// Takes a z level, gets the lowest plane offset in its "stack"
+#define GET_LOWEST_STACK_OFFSET(z) ((SSmapping.max_plane_offset) ? SSmapping.z_level_to_lowest_plane_offset[z] : 0)
+/// Takes a plane, returns the canonical, unoffset plane it represents
+#define PLANE_TO_TRUE(plane) ((SSmapping.plane_offset_to_true) ? SSmapping.plane_offset_to_true["[plane]"] : plane)
+/// Takes a plane, returns the offset it uses
+#define PLANE_TO_OFFSET(plane) ((SSmapping.plane_to_offset) ? SSmapping.plane_to_offset["[plane]"] : plane)
+/// Takes a true plane, returns the offset planes that would canonically represent it
+#define TRUE_PLANE_TO_OFFSETS(plane) ((SSmapping.true_to_offset_planes) ? SSmapping.true_to_offset_planes["[plane]"] : list(plane))
+/// Takes a render target and an offset, returns a canonical render target string for it
+#define OFFSET_RENDER_TARGET(render_target, offset) (_OFFSET_RENDER_TARGET(render_target, SSmapping.render_offset_blacklist?["[render_target]"] ? 0 : offset))
+/// Helper macro for the above
+/// Honestly just exists to make the pattern of render target strings more readable
+#define _OFFSET_RENDER_TARGET(render_target, offset) ("[(render_target)] #[(offset)]")
+
+// Known issues:
+// Potentially too much client load? Hard to tell due to not having a potato pc to hand.
+// This is solvable with lowspec preferences, which would not be hard to implement
+// Player popups will now render their effects, like overlay lights. this is fixable, but I've not gotten to it
+// I think overlay lights can render on the wrong z layer. s fucked
diff --git a/code/__HELPERS/icon_smoothing.dm b/code/__HELPERS/icon_smoothing.dm
index b8f5d00a7ca..e001e4c30b7 100644
--- a/code/__HELPERS/icon_smoothing.dm
+++ b/code/__HELPERS/icon_smoothing.dm
@@ -379,7 +379,7 @@ DEFINE_BITFIELD(smoothing_junction, list(
var/junction_dir = reverse_ndir(smoothing_junction)
var/turned_adjacency = REVERSE_DIR(junction_dir)
var/turf/neighbor_turf = get_step(src, turned_adjacency & (NORTH|SOUTH))
- var/mutable_appearance/underlay_appearance = mutable_appearance(layer = TURF_LAYER, plane = FLOOR_PLANE)
+ var/mutable_appearance/underlay_appearance = mutable_appearance(layer = TURF_LAYER, offset_spokesman = src, plane = FLOOR_PLANE)
if(!neighbor_turf.get_smooth_underlay_icon(underlay_appearance, src, turned_adjacency))
neighbor_turf = get_step(src, turned_adjacency & (EAST|WEST))
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 71cb65266eb..37c7b753c87 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -1357,9 +1357,8 @@ GLOBAL_LIST_EMPTY(transformation_animation_objects)
* result_appearance - End result appearance/atom/image
* time - Animation duration
* transform_overlay - Appearance/atom/image of effect that moves along the animation - should be horizonatally centered
- * reset_after - If FALSE, filters won't be reset and helper vis_objects will not be removed after animation duration expires. Cleanup must be handled by the caller!
*/
-/atom/movable/proc/transformation_animation(result_appearance,time = 3 SECONDS,transform_overlay,reset_after=TRUE)
+/atom/movable/proc/transformation_animation(result_appearance,time = 3 SECONDS,transform_overlay)
var/list/transformation_objects = GLOB.transformation_animation_objects[src] || list()
//Disappearing part
var/top_part_filter = filter(type="alpha",icon=icon('icons/effects/alphacolors.dmi',"white"),y=0)
@@ -1388,8 +1387,7 @@ GLOBAL_LIST_EMPTY(transformation_animation_objects)
GLOB.transformation_animation_objects[src] = transformation_objects
for(var/A in transformation_objects)
vis_contents += A
- if(reset_after)
- addtimer(CALLBACK(src,.proc/_reset_transformation_animation,filter_index),time)
+ addtimer(CALLBACK(src,.proc/_reset_transformation_animation,filter_index),time)
/*
* Resets filters and removes transformation animations helper objects from vis contents.
diff --git a/code/__HELPERS/lighting.dm b/code/__HELPERS/lighting.dm
index 0a000b99a83..efde257218c 100644
--- a/code/__HELPERS/lighting.dm
+++ b/code/__HELPERS/lighting.dm
@@ -1,13 +1,13 @@
/// Produces a mutable appearance glued to the [EMISSIVE_PLANE] dyed to be the [EMISSIVE_COLOR].
-/proc/emissive_appearance(icon, icon_state = "", layer = FLOAT_LAYER, alpha = 255, appearance_flags = NONE)
- var/mutable_appearance/appearance = mutable_appearance(icon, icon_state, layer, EMISSIVE_PLANE, alpha, appearance_flags | EMISSIVE_APPEARANCE_FLAGS)
+/proc/emissive_appearance(icon, icon_state = "", atom/offset_spokesman, layer = FLOAT_LAYER, alpha = 255, appearance_flags = NONE, offset_const)
+ var/mutable_appearance/appearance = mutable_appearance(icon, icon_state, layer, offset_spokesman, EMISSIVE_PLANE, alpha, appearance_flags | EMISSIVE_APPEARANCE_FLAGS, offset_const)
appearance.color = GLOB.emissive_color
return appearance
/// Produces a mutable appearance glued to the [EMISSIVE_PLANE] dyed to be the [EM_BLOCK_COLOR].
-/proc/emissive_blocker(icon, icon_state = "", layer = FLOAT_LAYER, alpha = 255, appearance_flags = NONE)
+/proc/emissive_blocker(icon, icon_state = "", atom/offset_spokesman, layer = FLOAT_LAYER, alpha = 255, appearance_flags = NONE, offset_const)
// Note: alpha doesn't "do" anything, since it's overriden by the color set shortly after
// Consider removing it someday?
- var/mutable_appearance/appearance = mutable_appearance(icon, icon_state, layer, EMISSIVE_PLANE, alpha, appearance_flags | EMISSIVE_APPEARANCE_FLAGS)
+ var/mutable_appearance/appearance = mutable_appearance(icon, icon_state, layer, offset_spokesman, EMISSIVE_PLANE, alpha, appearance_flags | EMISSIVE_APPEARANCE_FLAGS, offset_const)
appearance.color = GLOB.em_block_color
return appearance
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index de4e6190f0c..67bc19f3a7d 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -894,7 +894,7 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
GLOB.dview_mob.loc = center
- GLOB.dview_mob.see_invisible = invis_flags
+ GLOB.dview_mob.set_invis_see(invis_flags)
. = view(range, GLOB.dview_mob)
GLOB.dview_mob.loc = null
@@ -928,7 +928,7 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
#define FOR_DVIEW(type, range, center, invis_flags) \
GLOB.dview_mob.loc = center; \
- GLOB.dview_mob.see_invisible = invis_flags; \
+ GLOB.dview_mob.set_invis_see(invis_flags); \
for(type in view(range, GLOB.dview_mob))
#define FOR_DVIEW_END GLOB.dview_mob.loc = null
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 0972a958a8d..9cae713658f 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -530,6 +530,16 @@
M.Scale(px/sx, py/sy)
transform = M
+/atom/movable/screen/click_catcher/Initialize(mapload)
+ . = ..()
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, .proc/offset_increased)
+ offset_increased(SSmapping, 0, SSmapping.max_plane_offset)
+
+// Draw to the lowest plane level offered
+/atom/movable/screen/click_catcher/proc/offset_increased(datum/source, old_offset, new_offset)
+ SIGNAL_HANDLER
+ SET_PLANE_W_SCALAR(src, initial(plane), new_offset)
+
/atom/movable/screen/click_catcher/Click(location, control, params)
var/list/modifiers = params2list(params)
if(LAZYACCESS(modifiers, MIDDLE_CLICK) && iscarbon(usr))
diff --git a/code/_onclick/hud/blob_overmind.dm b/code/_onclick/hud/blob_overmind.dm
index a157e2157b8..456347d007d 100644
--- a/code/_onclick/hud/blob_overmind.dm
+++ b/code/_onclick/hud/blob_overmind.dm
@@ -163,7 +163,7 @@
blobpwrdisplay.icon_state = "block"
blobpwrdisplay.screen_loc = ui_health
blobpwrdisplay.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- blobpwrdisplay.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(blobpwrdisplay, ABOVE_HUD_PLANE, owner)
blobpwrdisplay.hud = src
infodisplay += blobpwrdisplay
diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm
index f3882e7054d..e036dd1351b 100644
--- a/code/_onclick/hud/fullscreen.dm
+++ b/code/_onclick/hud/fullscreen.dm
@@ -56,6 +56,19 @@
else
client.screen -= screen
+/mob/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(!same_z_layer)
+ relayer_fullscreens()
+
+/mob/proc/relayer_fullscreens()
+ var/turf/our_lad = get_turf(src)
+ var/offset = GET_TURF_PLANE_OFFSET(our_lad)
+ var/atom/movable/screen/fullscreen/screen
+ for(var/category in screens)
+ screen = screens[category]
+ screen.plane = GET_NEW_PLANE(initial(screen.plane), offset)
+
/atom/movable/screen/fullscreen
icon = 'icons/hud/screen_full.dmi'
icon_state = "default"
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index fe55d7fd804..333a030850d 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -47,10 +47,18 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
var/list/screenoverlays = list() //the screen objects used as whole screen overlays (flash, damageoverlay, etc...)
var/list/inv_slots[SLOTS_AMT] // /atom/movable/screen/inventory objects, ordered by their slot ID.
var/list/hand_slots // /atom/movable/screen/inventory/hand objects, assoc list of "[held_index]" = object
- var/list/atom/movable/screen/plane_master/plane_masters = list() // see "appearance_flags" in the ref, assoc list of "[plane]" = object
+
+ /// Assoc list of key => "plane master groups"
+ /// This is normally just the main window, but it'll occasionally contain things like spyglasses windows
+ var/list/datum/plane_master_group/master_groups = list()
///Assoc list of controller groups, associated with key string group name with value of the plane master controller ref
var/list/atom/movable/plane_master_controller/plane_master_controllers = list()
+ /// Think of multiz as a stack of z levels. Each index in that stack has its own group of plane masters
+ /// This variable is the plane offset our mob/client is currently "on"
+ /// We use it to track what we should show/not show
+ /// Goes from 0 to the max (z level stack size - 1)
+ var/current_plane_offset = 0
///UI for screentips that appear when you mouse over things
var/atom/movable/screen/screentip/screentip_text
@@ -60,6 +68,8 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
/// had with a proc call, especially on one of the hottest procs in the
/// game (MouseEntered).
var/screentips_enabled = SCREENTIP_PREFERENCE_ENABLED
+ /// If this client is being shown atmos debug overlays or not
+ var/atmos_debug_overlays = FALSE
/// The color to use for the screentips.
/// This is updated by the preference for cheaper reads than would be
@@ -98,10 +108,8 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
hand_slots = list()
- for(var/mytype in subtypesof(/atom/movable/screen/plane_master)- /atom/movable/screen/plane_master/rendering_plate)
- var/atom/movable/screen/plane_master/instance = new mytype()
- plane_masters["[instance.plane]"] = instance
- instance.backdrop(mymob)
+ var/datum/plane_master_group/main/main_group = new(PLANE_GROUP_MAIN)
+ main_group.attach_to(src)
var/datum/preferences/preferences = owner?.client?.prefs
screentip_color = preferences?.read_preference(/datum/preference/color/screentip_color)
@@ -116,6 +124,59 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
owner.overlay_fullscreen("see_through_darkness", /atom/movable/screen/fullscreen/see_through_darkness)
AddComponent(/datum/component/zparallax, owner.client)
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, .proc/on_plane_increase)
+ RegisterSignal(mymob, COMSIG_MOB_LOGIN, .proc/client_refresh)
+ RegisterSignal(mymob, COMSIG_MOB_LOGOUT, .proc/clear_client)
+ RegisterSignal(mymob, COMSIG_MOB_SIGHT_CHANGE, .proc/update_sightflags)
+ update_sightflags(mymob, mymob.sight, NONE)
+
+/datum/hud/proc/client_refresh(datum/source)
+ RegisterSignal(mymob.client, COMSIG_CLIENT_SET_EYE, .proc/on_eye_change)
+ on_eye_change(null, null, mymob.client.eye)
+
+/datum/hud/proc/clear_client(datum/source)
+ if(mymob.canon_client)
+ UnregisterSignal(mymob.canon_client, COMSIG_CLIENT_SET_EYE)
+
+/datum/hud/proc/on_eye_change(datum/source, atom/old_eye, atom/new_eye)
+ SIGNAL_HANDLER
+ if(old_eye)
+ UnregisterSignal(old_eye, COMSIG_MOVABLE_Z_CHANGED)
+ if(new_eye)
+ // By the time logout runs, the client's eye has already changed
+ // There's just no log of the old eye, so we need to override
+ // :sadkirby:
+ RegisterSignal(new_eye, COMSIG_MOVABLE_Z_CHANGED, .proc/eye_z_changed, override = TRUE)
+ eye_z_changed(new_eye)
+
+/datum/hud/proc/update_sightflags(datum/source, new_sight, old_sight)
+ // If neither the old and new flags can see turfs but not objects, don't transform the turfs
+ // This is to ensure parallax works when you can't see holder objects
+ if(should_sight_scale(new_sight) == should_sight_scale(old_sight))
+ return
+
+ var/datum/plane_master_group/group = get_plane_group(PLANE_GROUP_MAIN)
+ group.transform_lower_turfs(src, current_plane_offset)
+
+/datum/hud/proc/should_use_scale()
+ return should_sight_scale(mymob.sight)
+
+/datum/hud/proc/should_sight_scale(sight_flags)
+ return (sight_flags & (SEE_TURFS | SEE_OBJS)) != SEE_TURFS
+
+/datum/hud/proc/eye_z_changed(atom/eye)
+ SIGNAL_HANDLER
+ var/turf/eye_turf = get_turf(eye)
+ var/new_offset = GET_TURF_PLANE_OFFSET(eye_turf)
+ if(current_plane_offset == new_offset)
+ return
+ var/old_offset = current_plane_offset
+ current_plane_offset = new_offset
+
+ SEND_SIGNAL(src, COMSIG_HUD_OFFSET_CHANGED, old_offset, new_offset)
+ var/datum/plane_master_group/group = get_plane_group(PLANE_GROUP_MAIN)
+ if(group && should_use_scale())
+ group.transform_lower_turfs(src, new_offset)
/datum/hud/Destroy()
if(mymob.hud_used == src)
@@ -150,7 +211,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
alien_queen_finder = null
combo_display = null
- QDEL_LIST_ASSOC_VAL(plane_masters)
+ QDEL_LIST_ASSOC_VAL(master_groups)
QDEL_LIST_ASSOC_VAL(plane_master_controllers)
QDEL_LIST(screenoverlays)
mymob = null
@@ -159,6 +220,38 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
return ..()
+/datum/hud/proc/on_plane_increase(datum/source, old_max_offset, new_max_offset)
+ SIGNAL_HANDLER
+ build_plane_groups(old_max_offset + 1, new_max_offset)
+
+/// Creates the required plane masters to fill out new z layers (because each "level" of multiz gets its own plane master set)
+/datum/hud/proc/build_plane_groups(starting_offset, ending_offset)
+ for(var/group_key in master_groups)
+ var/datum/plane_master_group/group = master_groups[group_key]
+ group.build_plane_masters(starting_offset, ending_offset)
+
+/// Returns the plane master that matches the input plane from the passed in group
+/datum/hud/proc/get_plane_master(plane, group_key = PLANE_GROUP_MAIN)
+ var/plane_key = "[plane]"
+ var/datum/plane_master_group/group = master_groups[group_key]
+ return group.plane_masters[plane_key]
+
+/// Returns a list of all plane masters that match the input true plane, drawn from the passed in group (ignores z layer offsets)
+/datum/hud/proc/get_true_plane_masters(true_plane, group_key = PLANE_GROUP_MAIN)
+ var/list/atom/movable/screen/plane_master/masters = list()
+ for(var/plane in TRUE_PLANE_TO_OFFSETS(true_plane))
+ masters += get_plane_master(plane, group_key)
+ return masters
+
+/// Returns all the planes belonging to the passed in group key
+/datum/hud/proc/get_planes_from(group_key)
+ var/datum/plane_master_group/group = master_groups[group_key]
+ return group.plane_masters
+
+/// Returns the corresponding plane group datum if one exists
+/datum/hud/proc/get_plane_group(key)
+ return master_groups[key]
+
/mob/proc/create_mob_hud()
if(!client || hud_used)
return
@@ -252,14 +345,14 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
else if (viewmob.hud_used)
viewmob.hud_used.plane_masters_update()
+ SEND_SIGNAL(screenmob, COMSIG_MOB_HUD_REFRESHED, src)
return TRUE
/datum/hud/proc/plane_masters_update()
- // Plane masters are always shown to OUR mob, never to observers
- for(var/thing in plane_masters)
- var/atom/movable/screen/plane_master/PM = plane_masters[thing]
- PM.backdrop(mymob)
- mymob.client.screen += PM
+ for(var/group_key in master_groups)
+ var/datum/plane_master_group/group = master_groups[group_key]
+ // Plane masters are always shown to OUR mob, never to observers
+ group.refresh_hud()
/datum/hud/human/show_hud(version = 0,mob/viewmob)
. = ..()
diff --git a/code/_onclick/hud/map_popups.dm b/code/_onclick/hud/map_popups.dm
index 991e252b146..f0277d187ff 100644
--- a/code/_onclick/hud/map_popups.dm
+++ b/code/_onclick/hud/map_popups.dm
@@ -1,12 +1,3 @@
-/**
- * A screen object, which acts as a container for turfs and other things
- * you want to show on the map, which you usually attach to "vis_contents".
- */
-/atom/movable/screen/map_view
- // Map view has to be on the lowest plane to enable proper lighting
- layer = GAME_PLANE
- plane = GAME_PLANE
-
/**
* A generic background object.
* It is also implicitly used to allocate a rectangle on the map, which will
@@ -84,9 +75,10 @@
*
* Returns a map name.
*/
-/client/proc/create_popup(name, ratiox = 100, ratioy = 100)
+/client/proc/create_popup(name, title, ratiox = 100, ratioy = 100)
winclone(src, "popupwindow", name)
var/list/winparams = list()
+ winparams["title"] = title
winparams["size"] = "[ratiox]x[ratioy]"
winparams["on-close"] = "handle-popup-close [name]"
winset(src, "[name]", list2params(winparams))
@@ -109,13 +101,13 @@
* Width and height are multiplied by 64 by default.
*/
/client/proc/setup_popup(popup_name, width = 9, height = 9, \
- tilesize = 2, bg_icon)
+ tilesize = 2, title, bg_icon)
if(!popup_name)
return
clear_map("[popup_name]_map")
var/x_value = world.icon_size * tilesize * width
var/y_value = world.icon_size * tilesize * height
- var/map_name = create_popup(popup_name, x_value, y_value)
+ var/map_name = create_popup(popup_name, title, x_value, y_value)
var/atom/movable/screen/background/background = new
background.assigned_map = map_name
@@ -139,3 +131,4 @@
/client/verb/handle_popup_close(window_id as text)
set hidden = TRUE
clear_map("[window_id]_map")
+ SEND_SIGNAL(src, COMSIG_POPUP_CLEARED, window_id)
diff --git a/code/_onclick/hud/map_view.dm b/code/_onclick/hud/map_view.dm
new file mode 100644
index 00000000000..bc304f20f8a
--- /dev/null
+++ b/code/_onclick/hud/map_view.dm
@@ -0,0 +1,69 @@
+/**
+ * A screen object, which acts as a container for turfs and other things
+ * you want to show on the map, which you usually attach to "vis_contents".
+ * Additionally manages the plane masters required to display said container contents
+ */
+INITIALIZE_IMMEDIATE(/atom/movable/screen/map_view)
+/atom/movable/screen/map_view
+ name = "screen"
+ // Map view has to be on the lowest plane to enable proper lighting
+ layer = GAME_PLANE
+ plane = GAME_PLANE
+ del_on_map_removal = FALSE
+
+ // Weakrefs of all our hud viewers -> a weakref to the hud datum they last used
+ var/list/datum/weakref/viewers_to_huds = list()
+
+/atom/movable/screen/map_view/Destroy()
+ for(var/datum/weakref/client_ref in viewers_to_huds)
+ var/client/our_client = client_ref.resolve()
+ if(!our_client)
+ continue
+ hide_from(our_client.mob)
+
+ return ..()
+
+/atom/movable/screen/map_view/proc/generate_view(map_key)
+ // Map keys have to start and end with an A-Z character,
+ // and definitely NOT with a square bracket or even a number.
+ // I wasted 6 hours on this. :agony:
+ // -- Stylemistake
+ assigned_map = map_key
+ set_position(1, 1)
+
+/atom/movable/screen/map_view/proc/display_to(mob/show_to)
+ show_to.client.register_map_obj(src)
+ // We need to add planesmasters to the popup, otherwise
+ // blending fucks up massively. Any planesmaster on the main screen does
+ // NOT apply to map popups. If there's ever a way to make planesmasters
+ // omnipresent, then this wouldn't be needed.
+ // We lazy load this because there's no point creating all these if none's gonna see em
+
+ // Store this info in a client -> hud pattern, so ghosts closing the window nukes the right group
+ var/datum/weakref/client_ref = WEAKREF(show_to.client)
+
+ var/datum/weakref/hud_ref = viewers_to_huds[client_ref]
+ var/datum/hud/our_hud = hud_ref?.resolve()
+ if(our_hud)
+ return our_hud.get_plane_group(PLANE_GROUP_POPUP_WINDOW(src))
+
+ // Generate a new plane group for this case
+ var/datum/plane_master_group/popup/pop_planes = new(PLANE_GROUP_POPUP_WINDOW(src), assigned_map)
+ viewers_to_huds[client_ref] = WEAKREF(show_to.hud_used)
+ pop_planes.attach_to(show_to.hud_used)
+
+ return pop_planes
+
+/atom/movable/screen/map_view/proc/hide_from(mob/hide_from)
+ hide_from?.canon_client.clear_map(assigned_map)
+ var/client_ref = WEAKREF(hide_from?.canon_client)
+
+ // Make sure we clear the *right* hud
+ var/datum/weakref/hud_ref = viewers_to_huds[client_ref]
+ viewers_to_huds -= client_ref
+ var/datum/hud/clear_from = hud_ref?.resolve()
+ if(!clear_from)
+ return
+
+ var/datum/plane_master_group/popup/pop_planes = clear_from.get_plane_group(PLANE_GROUP_POPUP_WINDOW(src))
+ qdel(pop_planes)
diff --git a/code/_onclick/hud/parallax.dm b/code/_onclick/hud/parallax.dm
index 0d602717479..ef48cc51559 100755
--- a/code/_onclick/hud/parallax.dm
+++ b/code/_onclick/hud/parallax.dm
@@ -20,28 +20,30 @@
C.parallax_layers.len = C.parallax_layers_max
C.screen |= (C.parallax_layers)
- var/atom/movable/screen/plane_master/PM = screenmob.hud_used.plane_masters["[PLANE_SPACE]"]
- if(screenmob != mymob)
- C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
- C.screen += PM
- PM.color = list(
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 1, 1, 1, 1,
- 0, 0, 0, 0
- )
-
+ // We could do not do parallax for anything except the main plane group
+ // This could be changed, but it would require refactoring this whole thing
+ // And adding non client particular hooks for all the inputs, and I do not have the time I'm sorry :(
+ for(var/atom/movable/screen/plane_master/plane_master in screenmob.hud_used.get_true_plane_masters(PLANE_SPACE))
+ if(screenmob != mymob)
+ C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
+ C.screen += plane_master
+ plane_master.color = list(
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 1, 1, 1, 1,
+ 0, 0, 0, 0
+ )
/datum/hud/proc/remove_parallax(mob/viewmob)
var/mob/screenmob = viewmob || mymob
var/client/C = screenmob.client
C.screen -= (C.parallax_layers_cached)
- var/atom/movable/screen/plane_master/PM = screenmob.hud_used.plane_masters["[PLANE_SPACE]"]
- if(screenmob != mymob)
- C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
- C.screen += PM
- PM.color = initial(PM.color)
+ for(var/atom/movable/screen/plane_master/plane_master in screenmob.hud_used.get_true_plane_masters(PLANE_SPACE))
+ if(screenmob != mymob)
+ C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
+ C.screen += plane_master
+ plane_master.color = initial(plane_master.color)
C.parallax_layers = null
/datum/hud/proc/apply_parallax_pref(mob/viewmob)
@@ -247,7 +249,6 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/parallax_layer)
var/speed = 1
var/offset_x = 0
var/offset_y = 0
- var/view_sized
var/absolute = FALSE
blend_mode = BLEND_ADD
plane = PLANE_SPACE_PARALLAX
@@ -289,7 +290,6 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/parallax_layer)
new_overlays += texture_overlay
cut_overlays()
add_overlay(new_overlays)
- view_sized = view
/atom/movable/screen/parallax_layer/layer_1
icon_state = "layer1"
diff --git a/code/_onclick/hud/picture_in_picture.dm b/code/_onclick/hud/picture_in_picture.dm
index 8814e4e43c1..9c0fd3e43a4 100644
--- a/code/_onclick/hud/picture_in_picture.dm
+++ b/code/_onclick/hud/picture_in_picture.dm
@@ -16,6 +16,12 @@
/atom/movable/screen/movable/pic_in_pic/Initialize(mapload)
. = ..()
make_backgrounds()
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, .proc/multiz_offset_increase)
+ multiz_offset_increase(SSmapping)
+
+/atom/movable/screen/movable/pic_in_pic/proc/multiz_offset_increase(datum/source)
+ SIGNAL_HANDLER
+ SET_PLANE_W_SCALAR(src, initial(plane), SSmapping.max_plane_offset)
/atom/movable/screen/movable/pic_in_pic/Destroy()
for(var/C in shown_to)
diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index 2f9f83930b7..85af5ef80d1 100644
--- a/code/_onclick/hud/radial.dm
+++ b/code/_onclick/hud/radial.dm
@@ -6,6 +6,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
/atom/movable/screen/radial
icon = 'icons/hud/radial.dmi'
plane = ABOVE_HUD_PLANE
+ vis_flags = VIS_INHERIT_PLANE
var/datum/radial_menu/parent
/atom/movable/screen/radial/proc/set_parent(new_value)
@@ -249,7 +250,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
E.add_overlay(choices_icons[choice_id])
if (choice_datum?.info)
var/obj/effect/abstract/info/info_button = new(E, choice_datum.info)
- info_button.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(info_button, ABOVE_HUD_PLANE, anchor)
info_button.layer = RADIAL_CONTENT_LAYER
E.vis_contents += info_button
@@ -293,7 +294,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/mutable_appearance/MA = new /mutable_appearance(to_extract_from)
if(MA)
- MA.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(MA, ABOVE_HUD_PLANE, anchor)
MA.layer = RADIAL_CONTENT_LAYER
MA.appearance_flags |= RESET_TRANSFORM
return MA
@@ -312,7 +313,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
current_user = M.client
//Blank
menu_holder = image(icon='icons/effects/effects.dmi',loc=anchor,icon_state="nothing", layer = RADIAL_BACKGROUND_LAYER)
- menu_holder.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(menu_holder, ABOVE_HUD_PLANE, M)
menu_holder.appearance_flags |= KEEP_APART|RESET_ALPHA|RESET_COLOR|RESET_TRANSFORM
menu_holder.vis_contents += elements + close_button
current_user.images += menu_holder
diff --git a/code/_onclick/hud/rendering/plane_master.dm b/code/_onclick/hud/rendering/plane_master.dm
index cdcb4dc5da4..39f80375ceb 100644
--- a/code/_onclick/hud/rendering/plane_master.dm
+++ b/code/_onclick/hud/rendering/plane_master.dm
@@ -1,218 +1,460 @@
+// 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|NO_CLIENT_COLOR
+ appearance_flags = PLANE_MASTER
blend_mode = BLEND_OVERLAY
plane = LOWEST_EVER_PLANE
- var/show_alpha = 255
- var/hide_alpha = 0
+ /// 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--
- ///integer: what plane we will relay this planes render to
- var/render_relay_plane = RENDER_PLANE_GAME
- ///bool: Whether this plane should get a render target automatically generated
- var/generate_render_target = TRUE
- ///integer: blend mode to apply to the render relay in case you dont want to use the plane_masters blend_mode
+ /// 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
- ///reference: current relay this plane is utilizing to render
- var/atom/movable/render_plane_relay/relay
+ /// 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
-/atom/movable/screen/plane_master/proc/Show(override)
- alpha = override || show_alpha
+ /// 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
-/atom/movable/screen/plane_master/proc/Hide(override)
- alpha = override || hide_alpha
+ /// 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
-//Why do plane masters need a backdrop sometimes? Read https://secure.byond.com/forum/?post=2141928
-//Trust me, you need one. Period. If you don't think you do, you're doing something extremely wrong.
-/atom/movable/screen/plane_master/proc/backdrop(mob/mymob)
- SHOULD_CALL_PARENT(TRUE)
- if(!isnull(render_relay_plane))
- relay_render_to_plane(mymob, render_relay_plane)
-
-///Things rendered on "openspace"; holes in multi-z
-/atom/movable/screen/plane_master/openspace_backdrop
- name = "open space backdrop plane master"
- plane = OPENSPACE_BACKDROP_PLANE
- appearance_flags = PLANE_MASTER
- blend_mode = BLEND_MULTIPLY
- alpha = 255
-
-/atom/movable/screen/plane_master/openspace
- name = "open space plane master"
- plane = OPENSPACE_PLANE
- appearance_flags = PLANE_MASTER
-
-/atom/movable/screen/plane_master/openspace/Initialize(mapload)
+/atom/movable/screen/plane_master/Initialize(mapload, datum/plane_master_group/home, offset = 0)
. = ..()
- add_filter("first_stage_openspace", 1, drop_shadow_filter(color = "#04080FAA", size = -10))
- add_filter("second_stage_openspace", 2, drop_shadow_filter(color = "#04080FAA", size = -15))
- add_filter("third_stage_openspace", 3, drop_shadow_filter(color = "#04080FAA", size = -20))
+ src.offset = offset
+ true_alpha = alpha
+ real_plane = plane
-///For any transparent multi-z tiles we want to render
-/atom/movable/screen/plane_master/transparent
- name = "transparent plane master"
- plane = TRANSPARENT_FLOOR_PLANE
- appearance_flags = PLANE_MASTER
+ 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
+ if(!our_client)
+ return TRUE
+
+ our_client.screen += src
+ our_client.screen += relays
+ return TRUE
+
+/// 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/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. \
+ Will always be below almost everything else"
+ plane = CLICKCATCHER_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ multiz_scaled = FALSE
+
+/atom/movable/screen/plane_master/clickcatcher/Initialize(mapload, datum/plane_master_group/home, offset)
+ . = ..()
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, .proc/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.\
+ 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
+
+///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.\
+ 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.\
+ 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.\
+ 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/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)
+
+/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.\
+ 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.\
+ 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 plane master"
+ 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
- appearance_flags = PLANE_MASTER
- blend_mode = BLEND_OVERLAY
-///Contains most things in the game world
-/atom/movable/screen/plane_master/game_world
- name = "game world plane master"
+/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
- appearance_flags = PLANE_MASTER //should use client color
- blend_mode = BLEND_OVERLAY
-
-/atom/movable/screen/plane_master/game_world/backdrop(mob/mymob)
- . = ..()
- 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"))
+ render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
/atom/movable/screen/plane_master/game_world_fov_hidden
- name = "game world fov hidden plane master"
+ 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_plane = GAME_PLANE
- appearance_flags = PLANE_MASTER //should use client color
- blend_mode = BLEND_OVERLAY
+ 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 = FIELD_OF_VISION_BLOCKER_RENDER_TARGET, flags = MASK_INVERSE))
+ 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.\
+ 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 plane master"
+ 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_plane = GAME_PLANE
- appearance_flags = PLANE_MASTER //should use client color
- blend_mode = BLEND_OVERLAY
+ render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
/atom/movable/screen/plane_master/game_world_upper_fov_hidden
- name = "upper game world fov hidden plane master"
+ 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_plane = GAME_PLANE_FOV_HIDDEN
- appearance_flags = PLANE_MASTER //should use client color
- blend_mode = BLEND_OVERLAY
+ render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
+
+/atom/movable/screen/plane_master/game_world_upper_fov_hidden/Initialize()
+ . = ..()
+ // 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(GAME_PLANE)
+ start_hidden = TRUE
/atom/movable/screen/plane_master/game_world_above
- name = "above game world plane master"
+ 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_plane = GAME_PLANE
- appearance_flags = PLANE_MASTER //should use client color
- blend_mode = BLEND_OVERLAY
-
-/atom/movable/screen/plane_master/game_world_above_no_mouse
- name = "above game world no mouse plane master"
- plane = ABOVE_GAME_NO_MOUSE_PLANE
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
- render_relay_plane = GAME_PLANE
- appearance_flags = PLANE_MASTER
- blend_mode = BLEND_OVERLAY
-
-/atom/movable/screen/plane_master/massive_obj
- name = "massive object plane master"
- plane = MASSIVE_OBJ_PLANE
- appearance_flags = PLANE_MASTER //should use client color
- blend_mode = BLEND_OVERLAY
-
-/atom/movable/screen/plane_master/ghost
- name = "ghost plane master"
- plane = GHOST_PLANE
- appearance_flags = PLANE_MASTER //should use client color
- blend_mode = BLEND_OVERLAY
- render_relay_plane = RENDER_PLANE_NON_GAME
-
-/atom/movable/screen/plane_master/point
- name = "point plane master"
- plane = POINT_PLANE
- appearance_flags = PLANE_MASTER //should use client color
- blend_mode = BLEND_OVERLAY
+ render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
/**
* Plane master handling byond internal blackness
* vars are set as to replicate behavior when rendering to other planes
* do not touch this unless you know what you are doing
*/
+// Blackness renders weird when you view down openspace, because of transforms and borders and such
+// This is a consequence of not using lummy's grouped transparency, but I couldn't get that to work without totally fucking up
+// Sight flags, and shooting vis_contents usage to the moon. So we're doin it different.
+// Look into lessening this, maybe mirror down all the time? idk
+// Part of the issue is it isn't actually the blackness plane, it's just normal blackness
+// (If image vis contents worked (it should in 515), and we were ok with a maptick cost (wait for threaded maptick)) this could be fixed
/atom/movable/screen/plane_master/blackness
- name = "darkness plane master"
+ name = "Darkness"
+ 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. \
+ But, because we've moved everything we control off plane 0, all that's left is stuff byond internally renders. \
+ What we're doing here is using plane 0 to capture \"Blackness\", or the mask that hides tiles. Note, this only works if our mob has the SEE_PIXELS or SEE_BLACKNESS sight flags.\
+ We relay this plane master (on plane 0) down to other copies of itself, depending on the layer your mob is on at the moment.\
+ Of note: plane master blackness, and the blackness that comes from having nothing to display look similar, but are not the same thing,\
+ mind yourself when you're working with this plane, you might have accidentially been trying to work with the wrong thing."
plane = BLACKNESS_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ // Note: we don't set this to blend multiply because it just dies when its alpha is modified, because of fun byond bugs
+ // Marked as multiz_scaled = FALSE because it should not scale, scaling lets you see "through" the floor
+ multiz_scaled = FALSE
+
+/atom/movable/screen/plane_master/blackness/show_to(mob/mymob)
+ . = ..()
+ if(!.)
+ return
+ if(offset != 0)
+ // You aren't the source? don't change yourself
+ return
+ RegisterSignal(mymob, COMSIG_MOB_SIGHT_CHANGE, .proc/handle_sight_value)
+ handle_sight_value(mymob, mymob.sight, 0)
+ var/datum/hud/hud = home.our_hud
+ if(hud)
+ RegisterSignal(hud, COMSIG_HUD_OFFSET_CHANGED, .proc/on_offset_change)
+ offset_change(0, hud.current_plane_offset)
+
+/atom/movable/screen/plane_master/blackness/hide_from(mob/oldmob)
+ . = ..()
+ if(offset != 0)
+ return
+ UnregisterSignal(oldmob, COMSIG_MOB_SIGHT_CHANGE)
+ var/datum/hud/hud = home.our_hud
+ if(hud)
+ UnregisterSignal(hud, COMSIG_HUD_OFFSET_CHANGED, .proc/on_offset_change)
+
+/// Reacts to some new plane master value
+/atom/movable/screen/plane_master/blackness/proc/handle_sight_value(datum/source, new_sight, old_sight)
+ SIGNAL_HANDLER
+ // Tryin to set a sight flag that cuts blackness eh?
+ if(new_sight & BLACKNESS_CUTTING)
+ // Better set alpha then, so it'll actually work
+ // We just get the one because there is only one blackness PM, it's just mirrored around
+ disable_alpha()
+ else
+ enable_alpha()
+
+/atom/movable/screen/plane_master/blackness/proc/on_offset_change(datum/source, old_offset, new_offset)
+ SIGNAL_HANDLER
+ offset_change(old_offset, new_offset)
+
+/atom/movable/screen/plane_master/blackness/proc/offset_change(old_offset, new_offset)
+ // Basically, the rule here is the blackness we harvest from the mob using the SEE_BLACKNESS flag will be relayed to the darkness
+ // Plane that we're actually on
+ if(old_offset != 0) // If our old target wasn't just ourselves
+ remove_relay_from(GET_NEW_PLANE(plane, old_offset))
+
+ if(new_offset != 0)
+ add_relay_to(GET_NEW_PLANE(plane, new_offset))
+
+/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
+
+/// 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.\
+ Exists because lighting updating is really slow, and movement needs to feel smooth.\
+ 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
- appearance_flags = PLANE_MASTER | NO_CLIENT_COLOR | PIXEL_SCALE
- //byond internal end
-
-///Contains all lighting objects
-/atom/movable/screen/plane_master/lighting
- name = "lighting plane master"
- plane = LIGHTING_PLANE
- blend_mode_override = BLEND_MULTIPLY
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
-
-/atom/movable/screen/plane_master/lighting/backdrop(mob/mymob)
- . = ..()
- mymob.overlay_fullscreen("lighting_backdrop_lit", /atom/movable/screen/fullscreen/lighting_backdrop/lit)
- mymob.overlay_fullscreen("lighting_backdrop_unlit", /atom/movable/screen/fullscreen/lighting_backdrop/unlit)
-
-/*!
- * This system works by exploiting BYONDs color matrix filter to use layers to handle emissive blockers.
- *
- * Emissive overlays are pasted with an atom color that converts them to be entirely some specific color.
- * Emissive blockers are pasted with an atom color that converts them to be entirely some different color.
- * Emissive overlays and emissive blockers are put onto the same plane.
- * The layers for the emissive overlays and emissive blockers cause them to mask eachother similar to normal BYOND objects.
- * A color matrix filter is applied to the emissive plane to mask out anything that isn't whatever the emissive color is.
- * This is then used to alpha mask the lighting plane.
- */
-/atom/movable/screen/plane_master/lighting/Initialize(mapload)
- . = ..()
- add_filter("emissives", 1, alpha_mask_filter(render_source = EMISSIVE_RENDER_TARGET, flags = MASK_INVERSE))
- add_filter("object_lighting", 2, alpha_mask_filter(render_source = O_LIGHTING_VISUAL_RENDER_TARGET, flags = MASK_INVERSE))
+/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.\
+ 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 plane master"
+ name = "Emissive"
+ documentation = "This system works by exploiting BYONDs color matrix filter to use layers to handle emissive blockers.\
+ Emissive overlays are pasted with an atom color that converts them to be entirely some specific color.\
+ Emissive blockers are pasted with an atom color that converts them to be entirely some different color.\
+ Emissive overlays and emissive blockers are put onto the same plane (This one).\
+ The layers for the emissive overlays and emissive blockers cause them to mask eachother similar to normal BYOND objects.\
+ A color matrix filter is applied to the emissive plane to mask out anything that isn't whatever the emissive color is.\
+ This is then used to alpha mask the lighting plane."
plane = EMISSIVE_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
render_target = EMISSIVE_RENDER_TARGET
- render_relay_plane = null
+ render_relay_planes = list()
/atom/movable/screen/plane_master/emissive/Initialize(mapload)
. = ..()
add_filter("em_block_masking", 1, color_matrix_filter(GLOB.em_mask_matrix))
-/atom/movable/screen/plane_master/above_lighting
- name = "above lighting plane master"
- plane = ABOVE_LIGHTING_PLANE
- appearance_flags = PLANE_MASTER //should use client color
- blend_mode = BLEND_OVERLAY
-
-///Contains space parallax
-/atom/movable/screen/plane_master/parallax
- name = "parallax plane master"
- plane = PLANE_SPACE_PARALLAX
- blend_mode = BLEND_MULTIPLY
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
-/atom/movable/screen/plane_master/parallax_white
- name = "parallax whitifier plane master"
- plane = PLANE_SPACE
-
/atom/movable/screen/plane_master/pipecrawl
- name = "pipecrawl plane master"
+ name = "Pipecrawl"
+ documentation = "Holds pipecrawl images generated during well, pipecrawling.\
+ Has a few effects and a funky color matrix designed to make things a bit more visually readable."
plane = PIPECRAWL_IMAGES_PLANE
- appearance_flags = PLANE_MASTER
- blend_mode = BLEND_OVERLAY
+ start_hidden = TRUE
/atom/movable/screen/plane_master/pipecrawl/Initialize(mapload)
. = ..()
@@ -221,85 +463,91 @@
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 plane master"
+ name = "Camera static"
+ documentation = "Holds camera static images. Usually only visible to people who can well, see static.\
+ We use images rather then vis contents because they're lighter on maptick, and maptick sucks butt."
plane = CAMERA_STATIC_PLANE
- appearance_flags = PLANE_MASTER
- blend_mode = BLEND_OVERLAY
+ start_hidden = TRUE
-/atom/movable/screen/plane_master/excited_turfs
- name = "atmos excited turfs"
- plane = ATMOS_GROUP_PLANE
- appearance_flags = PLANE_MASTER
- blend_mode = BLEND_OVERLAY
- alpha = 0
+/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/o_light_visual
- name = "overlight light visual plane master"
- plane = O_LIGHTING_VISUAL_PLANE
- render_target = O_LIGHTING_VISUAL_RENDER_TARGET
+/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. \
+ This includes atmos debug overlays, blind sound images, and mining scanners. \
+ Really only exists for its layering potential, we don't use this for any vfx"
+ plane = HIGH_GAME_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- blend_mode = BLEND_MULTIPLY
- blend_mode_override = BLEND_MULTIPLY
+
+/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. \
+ 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 plane master"
+ 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
- appearance_flags = PLANE_MASTER
- blend_mode = BLEND_OVERLAY
- render_relay_plane = RENDER_PLANE_NON_GAME
+ render_relay_planes = list(RENDER_PLANE_NON_GAME)
-/atom/movable/screen/plane_master/runechat/backdrop(mob/mymob)
+/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/gravpulse
- name = "gravpulse plane"
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- plane = GRAVITY_PULSE_PLANE
- blend_mode = BLEND_ADD
- blend_mode_override = BLEND_ADD
- render_target = GRAVITY_PULSE_RENDER_TARGET
- render_relay_plane = null
-
-/atom/movable/screen/plane_master/area
- name = "area plane"
- plane = AREA_PLANE
-
/atom/movable/screen/plane_master/balloon_chat
- name = "balloon alert plane"
+ 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
- render_relay_plane = RENDER_PLANE_NON_GAME
-
-/atom/movable/screen/plane_master/fullscreen
- name = "fullscreen alert plane"
- plane = FULLSCREEN_PLANE
- render_relay_plane = RENDER_PLANE_NON_GAME
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
-/atom/movable/screen/plane_master/field_of_vision_blocker
- name = "field of vision blocker plane master"
- plane = FIELD_OF_VISION_BLOCKER_PLANE
- render_target = FIELD_OF_VISION_BLOCKER_RENDER_TARGET
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- render_relay_plane = null
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ render_relay_planes = list(RENDER_PLANE_NON_GAME)
/atom/movable/screen/plane_master/hud
- name = "HUD plane"
+ name = "HUD"
+ documentation = "Contains anything that want to be rendered on the hud. Typically is just screen elements."
plane = HUD_PLANE
- render_relay_plane = RENDER_PLANE_NON_GAME
+ 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 plane"
+ 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
- render_relay_plane = RENDER_PLANE_NON_GAME
+ 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 plane"
+ name = "Splashscreen"
+ documentation = "Anything that's drawn above LITERALLY everything else. Think cinimatics and the well, spashscreen."
plane = SPLASHSCREEN_PLANE
- render_relay_plane = RENDER_PLANE_NON_GAME
-
-
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ render_relay_planes = list(RENDER_PLANE_NON_GAME)
+ allows_offsetting = FALSE
diff --git a/code/_onclick/hud/rendering/plane_master_controller.dm b/code/_onclick/hud/rendering/plane_master_controller.dm
index 1c2a4f75936..58fc1bb9963 100644
--- a/code/_onclick/hud/rendering/plane_master_controller.dm
+++ b/code/_onclick/hud/rendering/plane_master_controller.dm
@@ -1,6 +1,6 @@
///Atom that manages and controls multiple planes. It's an atom so we can hook into add_filter etc. Multiple controllers can control one plane.
/atom/movable/plane_master_controller
- ///List of planes in this controllers control. Initially this is a normal list, but becomes an assoc list of plane numbers as strings | plane instance
+ ///List of planes as defines in this controllers control
var/list/controlled_planes = list()
///hud that owns this controller
var/datum/hud/owner_hud
@@ -12,71 +12,68 @@ INITIALIZE_IMMEDIATE(/atom/movable/plane_master_controller)
. = ..()
if(!istype(hud))
return
-
owner_hud = hud
- var/assoc_controlled_planes = list()
- for(var/i in controlled_planes)
- var/atom/movable/screen/plane_master/instance = owner_hud.plane_masters["[i]"]
- if(!instance) //If we looked for a hud that isn't instanced, just keep going
- stack_trace("[i] isn't a valid plane master layer for [owner_hud.type], are you sure it exists in the first place?")
- continue
- assoc_controlled_planes["[i]"] = instance
- controlled_planes = assoc_controlled_planes
+
+/atom/movable/plane_master_controller/proc/get_planes()
+ var/returned_planes = list()
+ for(var/true_plane in controlled_planes)
+ returned_planes += get_true_plane(true_plane)
+ return returned_planes
+
+/atom/movable/plane_master_controller/proc/get_true_plane(true_plane)
+ var/list/returned_planes = owner_hud.get_true_plane_masters(true_plane)
+ if(!length(returned_planes)) //If we looked for a hud that isn't instanced, just keep going
+ stack_trace("[plane] isn't a valid plane master layer for [owner_hud.type], are you sure it exists in the first place?")
+ return
+
+ return returned_planes
///Full override so we can just use filterrific
/atom/movable/plane_master_controller/add_filter(name, priority, list/params)
. = ..()
- for(var/i in controlled_planes)
- var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
pm_iterator.add_filter(name, priority, params)
///Full override so we can just use filterrific
/atom/movable/plane_master_controller/remove_filter(name_or_names)
. = ..()
- for(var/i in controlled_planes)
- var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
pm_iterator.remove_filter(name_or_names)
/atom/movable/plane_master_controller/update_filters()
. = ..()
- for(var/i in controlled_planes)
- var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
pm_iterator.update_filters()
///Gets all filters for this controllers plane masters
/atom/movable/plane_master_controller/proc/get_filters(name)
. = list()
- for(var/i in controlled_planes)
- var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
. += pm_iterator.get_filter(name)
///Transitions all filters owned by this plane master controller
/atom/movable/plane_master_controller/transition_filter(name, time, list/new_params, easing, loop)
. = ..()
- for(var/i in controlled_planes)
- var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
pm_iterator.transition_filter(name, time, new_params, easing, loop)
///Full override so we can just use filterrific
/atom/movable/plane_master_controller/add_atom_colour(coloration, colour_priority)
. = ..()
- for(var/i in controlled_planes)
- var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
pm_iterator.add_atom_colour(coloration, colour_priority)
///Removes an instance of colour_type from the atom's atom_colours list
/atom/movable/plane_master_controller/remove_atom_colour(colour_priority, coloration)
. = ..()
- for(var/i in controlled_planes)
- var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
pm_iterator.remove_atom_colour(colour_priority, coloration)
///Resets the atom's color to null, and then sets it to the highest priority colour available
/atom/movable/plane_master_controller/update_atom_colour()
- for(var/i in controlled_planes)
- var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
pm_iterator.update_atom_colour()
@@ -84,7 +81,7 @@ INITIALIZE_IMMEDIATE(/atom/movable/plane_master_controller)
name = PLANE_MASTERS_GAME
controlled_planes = list(
FLOOR_PLANE,
- TRANSPARENT_FLOOR_PLANE,
+ RENDER_PLANE_TRANSPARENT,
GAME_PLANE,
GAME_PLANE_FOV_HIDDEN,
GAME_PLANE_UPPER,
@@ -108,7 +105,7 @@ INITIALIZE_IMMEDIATE(/atom/movable/plane_master_controller)
GAME_PLANE_FOV_HIDDEN,
GAME_PLANE_UPPER,
GAME_PLANE_UPPER_FOV_HIDDEN,
- ABOVE_GAME_NO_MOUSE_PLANE,
+ SEETHROUGH_PLANE,
ABOVE_GAME_PLANE,
MASSIVE_OBJ_PLANE,
GHOST_PLANE,
@@ -118,7 +115,7 @@ INITIALIZE_IMMEDIATE(/atom/movable/plane_master_controller)
ABOVE_LIGHTING_PLANE,
CAMERA_STATIC_PLANE,
PIPECRAWL_IMAGES_PLANE,
- ATMOS_GROUP_PLANE,
+ HIGH_GAME_PLANE,
FULLSCREEN_PLANE,
RUNECHAT_PLANE,
HUD_PLANE,
diff --git a/code/_onclick/hud/rendering/plane_master_group.dm b/code/_onclick/hud/rendering/plane_master_group.dm
new file mode 100644
index 00000000000..71eb11c3c8c
--- /dev/null
+++ b/code/_onclick/hud/rendering/plane_master_group.dm
@@ -0,0 +1,153 @@
+/// Datum that represents one "group" of plane masters
+/// So all the main window planes would be in one, all the spyglass planes in another
+/// Etc
+/datum/plane_master_group
+ /// Our key in the group list on /datum/hud
+ /// Should be unique for any group of plane masters in the world
+ var/key
+ /// Our parent hud
+ var/datum/hud/our_hud
+ /// List in the form "[plane]" = object, the plane masters we own
+ var/list/atom/movable/screen/plane_master/plane_masters = list()
+ /// The visual offset we are currently using
+ var/active_offset = 0
+
+ /// What, if any, submap we render onto
+ var/map = ""
+
+/datum/plane_master_group/New(key, map = "")
+ . = ..()
+ src.key = key
+ src.map = map
+ build_plane_masters(0, SSmapping.max_plane_offset)
+
+/datum/plane_master_group/Destroy()
+ orphan_hud()
+ QDEL_LIST_ASSOC_VAL(plane_masters)
+ return ..()
+
+/// Display a plane master group to some viewer, so show all our planes to it
+/datum/plane_master_group/proc/attach_to(datum/hud/viewing_hud)
+ if(viewing_hud.master_groups[key])
+ stack_trace("Hey brother, our key [key] is already in use by a plane master group on the passed in hud, belonging to [viewing_hud.mymob]. Ya fucked up, why are there dupes")
+ return
+
+ our_hud = viewing_hud
+ our_hud.master_groups[key] = src
+ show_hud()
+ transform_lower_turfs(our_hud, active_offset)
+
+/// Hide the plane master from its current hud, fully clear it out
+/datum/plane_master_group/proc/orphan_hud()
+ if(our_hud)
+ our_hud.master_groups -= key
+ hide_hud()
+ our_hud = null
+
+/// Well, refresh our group, mostly useful for plane specific updates
+/datum/plane_master_group/proc/refresh_hud()
+ hide_hud()
+ show_hud()
+
+/// Fully regenerate our group, resetting our planes to their compile time values
+/datum/plane_master_group/proc/rebuild_hud()
+ hide_hud()
+ QDEL_LIST_ASSOC_VAL(plane_masters)
+ build_plane_masters(0, SSmapping.max_plane_offset)
+ show_hud()
+ transform_lower_turfs(our_hud, active_offset)
+
+/datum/plane_master_group/proc/hide_hud()
+ for(var/thing in plane_masters)
+ var/atom/movable/screen/plane_master/plane = plane_masters[thing]
+ plane.hide_from(our_hud.mymob)
+
+/datum/plane_master_group/proc/show_hud()
+ for(var/thing in plane_masters)
+ var/atom/movable/screen/plane_master/plane = plane_masters[thing]
+ show_plane(plane)
+
+/// This is mostly a proc so it can be overriden by popups, since they have unique behavior they want to do
+/datum/plane_master_group/proc/show_plane(atom/movable/screen/plane_master/plane)
+ plane.show_to(our_hud.mymob)
+
+/// Returns a list of all the plane master types we want to create
+/datum/plane_master_group/proc/get_plane_types()
+ return subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/rendering_plate
+
+/// Actually generate our plane masters, in some offset range (where offset is the z layers to render to, because each "layer" in a multiz stack gets its own plane master cube)
+/datum/plane_master_group/proc/build_plane_masters(starting_offset, ending_offset)
+ for(var/atom/movable/screen/plane_master/mytype as anything in get_plane_types())
+ for(var/plane_offset in starting_offset to ending_offset)
+ if(plane_offset != 0 && !initial(mytype.allows_offsetting))
+ continue
+ var/atom/movable/screen/plane_master/instance = new mytype(null, src, plane_offset)
+ plane_masters["[instance.plane]"] = instance
+ prep_plane_instance(instance)
+
+/// Similarly, exists so subtypes can do unique behavior to planes on creation
+/datum/plane_master_group/proc/prep_plane_instance(atom/movable/screen/plane_master/instance)
+ return
+
+// It would be nice to setup parallaxing for stairs and things when doing this
+// So they look nicer. if you can't it's all good, if you think you can sanely look at monster's work
+// It's hard, and potentially expensive. be careful
+/datum/plane_master_group/proc/transform_lower_turfs(datum/hud/source, new_offset, use_scale = TRUE)
+ // No offset? piss off
+ if(!SSmapping.max_plane_offset)
+ return
+ active_offset = new_offset
+ // Each time we go "down" a visual z level, we'll reduce the scale by this amount
+ // Chosen because mothblocks liked it, didn't cause motion sickness while also giving a sense of height
+ var/scale_by = 0.965
+ // If our mob can see through walls
+ if(!use_scale)
+ // This is a workaround for two things
+ // First of all, if a mob can see objects but not turfs, they will not be shown the holder objects we use for
+ // What I'd like to do is revert to images if this case throws, but image vis_contents is broken
+ // https://www.byond.com/forum/post/2821969
+ // If that's ever fixed, please just use that. thanks :)
+ scale_by = 1
+
+ var/list/offsets = list()
+ // We accept negatives so going down "zooms" away the drop above as it goes
+ for(var/offset in -SSmapping.max_plane_offset to SSmapping.max_plane_offset)
+ // No transformations if we're landing ON you
+ if(offset == 0)
+ offsets += null
+ continue
+ var/scale = scale_by ** (offset)
+ var/matrix/multiz_shrink = matrix()
+ multiz_shrink.Scale(scale)
+ offsets += multiz_shrink
+
+ // So we can talk in 1 -> max_offset * 2 + 1, rather then -max_offset -> max_offset
+ var/offset_offset = SSmapping.max_plane_offset + 1
+
+ for(var/plane_key in plane_masters)
+ var/atom/movable/screen/plane_master/plane = plane_masters[plane_key]
+ if(!plane.multiz_scaled || !plane.allows_offsetting)
+ continue
+ var/visual_offset = plane.offset - new_offset
+ if(plane.force_hidden || visual_offset < 0)
+ // We don't animate here because it should be invisble, but we do mark because it'll look nice
+ plane.transform = offsets[visual_offset + offset_offset]
+ continue
+ animate(plane, transform = offsets[visual_offset + offset_offset], 0.05 SECONDS, easing = LINEAR_EASING)
+
+/// Holds plane masters for popups, like camera windows
+/// Note: We do not scale this plane, even though we could
+/// This is because it's annoying to get turfs to position inside it correctly
+/// If you wanna try someday feel free, but I can't manage it
+/datum/plane_master_group/popup
+
+/datum/plane_master_group/popup/transform_lower_turfs(datum/hud/source, new_offset, use_scale = TRUE)
+ return ..(source, new_offset, FALSE)
+
+/// Holds the main plane master
+/datum/plane_master_group/main
+
+/datum/plane_master_group/main/transform_lower_turfs(datum/hud/source, new_offset, use_scale = TRUE)
+ if(use_scale)
+ return ..(source, new_offset, source.should_use_scale())
+ return ..()
diff --git a/code/_onclick/hud/rendering/render_plate.dm b/code/_onclick/hud/rendering/render_plate.dm
index 8bda402bf0b..ebd90506e2e 100644
--- a/code/_onclick/hud/rendering/render_plate.dm
+++ b/code/_onclick/hud/rendering/render_plate.dm
@@ -24,60 +24,263 @@
* remember that once planes are unified on a render plate you cant change the layering of them!
*/
/atom/movable/screen/plane_master/rendering_plate
- name = "default rendering plate"
-
+ name = "Default rendering plate"
+ multiz_scaled = FALSE
///this plate renders the final screen to show to the player
/atom/movable/screen/plane_master/rendering_plate/master
- name = "master rendering plate"
+ name = "Master rendering plate"
+ documentation = "The endpoint of all plane masters, you can think of this as the final \"view\" we draw.\
+ If offset is not 0 this will be drawn to the transparent plane of the floor above, but otherwise this is drawn to nothing, or shown to the player."
plane = RENDER_PLANE_MASTER
- render_relay_plane = null
- generate_render_target = FALSE
+ render_relay_planes = list()
+
+/atom/movable/screen/plane_master/rendering_plate/master/show_to(mob/mymob)
+ . = ..()
+ if(!.)
+ return
+ if(offset == 0)
+ return
+ // Non 0 offset render plates will relay up to the transparent plane above them, assuming they're not on the same z level as their target of course
+ var/datum/hud/hud = home.our_hud
+ if(hud)
+ RegisterSignal(hud, COMSIG_HUD_OFFSET_CHANGED, .proc/on_offset_change)
+ offset_change(hud.current_plane_offset)
+
+/atom/movable/screen/plane_master/rendering_plate/master/hide_from(mob/oldmob)
+ . = ..()
+ if(offset == 0)
+ return
+ var/datum/hud/hud = home.our_hud
+ if(hud)
+ UnregisterSignal(hud, COMSIG_HUD_OFFSET_CHANGED, .proc/on_offset_change)
+
+/atom/movable/screen/plane_master/rendering_plate/master/proc/on_offset_change(datum/source, old_offset, new_offset)
+ SIGNAL_HANDLER
+ offset_change(new_offset)
+
+/atom/movable/screen/plane_master/rendering_plate/master/proc/offset_change(new_offset)
+ if(new_offset == offset) // If we're on our own z layer, relay to nothing, just draw
+ remove_relay_from(GET_NEW_PLANE(RENDER_PLANE_TRANSPARENT, offset - 1))
+ else // Otherwise, regenerate the relay
+ add_relay_to(GET_NEW_PLANE(RENDER_PLANE_TRANSPARENT, offset - 1))
///renders general in charachter game objects
-/atom/movable/screen/plane_master/rendering_plate/game_world
- name = "game rendering plate"
+/atom/movable/screen/plane_master/rendering_plate/game_plate
+ name = "Game rendering plate"
+ documentation = "Holds all objects that are ahhh, in character? is maybe the best way to describe it.\
+ We apply a displacement effect from the gravity pulse plane too, so we can warp the game world."
plane = RENDER_PLANE_GAME
- render_relay_plane = RENDER_PLANE_MASTER
+ render_relay_planes = list(RENDER_PLANE_MASTER)
-/atom/movable/screen/plane_master/rendering_plate/game_world/Initialize(mapload)
+/atom/movable/screen/plane_master/rendering_plate/game_plate/Initialize(mapload)
. = ..()
- add_filter("displacer", 1, displacement_map_filter(render_source = GRAVITY_PULSE_RENDER_TARGET, size = 10))
+ add_filter("displacer", 1, displacement_map_filter(render_source = OFFSET_RENDER_TARGET(GRAVITY_PULSE_RENDER_TARGET, offset), size = 10))
+
+/atom/movable/screen/plane_master/rendering_plate/transparent
+ name = "Transparent plate"
+ documentation = "The master rendering plate from the offset below ours will be mirrored onto this plane. That way we achive a \"stack\" effect.\
+ This plane exists to uplayer the master rendering plate to the correct spot in our z layer's rendering order"
+ plane = RENDER_PLANE_TRANSPARENT
+ appearance_flags = PLANE_MASTER
+
+/atom/movable/screen/plane_master/rendering_plate/transparent/Initialize(mapload, datum/plane_master_group/home, offset)
+ . = ..()
+ // Don't display us if we're below everything else yeah?
+ AddComponent(/datum/component/plane_hide_highest_offset)
+ color = list(0.9,0,0,0, 0,0.9,0,0, 0,0,0.9,0, 0,0,0,1, 0,0,0,0)
+
+///Contains most things in the game world
+/atom/movable/screen/plane_master/rendering_plate/game_world
+ name = "Game world plate"
+ documentation = "Contains most of the objects in the world. Mobs, machines, etc. Note the drop shadow, it gives a very nice depth effect."
+ plane = RENDER_PLANE_GAME_WORLD
+ appearance_flags = PLANE_MASTER //should use client color
+ blend_mode = BLEND_OVERLAY
+
+/atom/movable/screen/plane_master/rendering_plate/game_world/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"))
+
+///Contains all lighting objects
+/atom/movable/screen/plane_master/rendering_plate/lighting
+ name = "Lighting plate"
+ documentation = "Anything on this plane will be multiplied with the plane it's rendered onto (typically the game plane).\
+ That's how lighting functions at base. Because it uses BLEND_MULTIPLY and occasionally color matrixes, it needs a backdrop of blackness.\
+ See This byond post\
+ Lemme see uh, we're masked by the emissive plane so it can actually function (IE: make things glow in the dark).\
+ We're also masked by the overlay lighting plane, which contains all the movable lights in the game. It draws to us and also the game plane.\
+ Masks us out so it has the breathing room to apply its effect.\
+ Oh and we quite often have our alpha changed to achive night vision effects, or things of that sort."
+ plane = RENDER_PLANE_LIGHTING
+ blend_mode_override = BLEND_MULTIPLY
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+/atom/movable/screen/plane_master/rendering_plate/lighting/show_to(mob/mymob)
+ . = ..()
+ if(!.)
+ return
+ // This applies a backdrop to our lighting plane
+ // Why do plane masters need a backdrop sometimes? Read https://secure.byond.com/forum/?post=2141928
+ // Basically, we need something to brighten
+ // unlit is perhaps less needed rn, it exists to provide a fullbright for things that can't see the lighting plane
+ // but we don't actually use invisibility to hide the lighting plane anymore, so it's pointless
+ mymob.overlay_fullscreen("lighting_backdrop_lit", /atom/movable/screen/fullscreen/lighting_backdrop/lit)
+ mymob.overlay_fullscreen("lighting_backdrop_unlit", /atom/movable/screen/fullscreen/lighting_backdrop/unlit)
+
+ // Sorry, this is a bit annoying
+ // Basically, we only want the lighting plane we can actually see to attempt to render
+ // If we don't our lower plane gets totally overriden by the black void of the upper plane
+ var/datum/hud/hud = home.our_hud
+ if(hud)
+ RegisterSignal(hud, COMSIG_HUD_OFFSET_CHANGED, .proc/on_offset_change)
+ offset_change(hud.current_plane_offset)
+ set_alpha(mymob.lighting_alpha)
+
+
+/atom/movable/screen/plane_master/rendering_plate/lighting/hide_from(mob/oldmob)
+ . = ..()
+ oldmob.clear_fullscreen("lighting_backdrop_lit")
+ oldmob.clear_fullscreen("lighting_backdrop_unlit")
+ var/datum/hud/hud = home.our_hud
+ if(hud)
+ UnregisterSignal(hud, COMSIG_HUD_OFFSET_CHANGED, .proc/on_offset_change)
+
+/atom/movable/screen/plane_master/rendering_plate/lighting/proc/on_offset_change(datum/source, old_offset, new_offset)
+ SIGNAL_HANDLER
+ offset_change(new_offset)
+
+/atom/movable/screen/plane_master/rendering_plate/lighting/proc/offset_change(mob_offset)
+ // Offsets stack down remember. This implies that we're above the mob's view plane, and shouldn't render
+ if(offset < mob_offset)
+ disable_alpha()
+ else
+ enable_alpha()
+
+/*!
+ * This system works by exploiting BYONDs color matrix filter to use layers to handle emissive blockers.
+ *
+ * Emissive overlays are pasted with an atom color that converts them to be entirely some specific color.
+ * Emissive blockers are pasted with an atom color that converts them to be entirely some different color.
+ * Emissive overlays and emissive blockers are put onto the same plane.
+ * The layers for the emissive overlays and emissive blockers cause them to mask eachother similar to normal BYOND objects.
+ * A color matrix filter is applied to the emissive plane to mask out anything that isn't whatever the emissive color is.
+ * This is then used to alpha mask the lighting plane.
+ */
+/atom/movable/screen/plane_master/rendering_plate/lighting/Initialize(mapload)
+ . = ..()
+ add_filter("emissives", 1, alpha_mask_filter(render_source = OFFSET_RENDER_TARGET(EMISSIVE_RENDER_TARGET, offset), flags = MASK_INVERSE))
+ add_filter("object_lighting", 2, alpha_mask_filter(render_source = OFFSET_RENDER_TARGET(O_LIGHTING_VISUAL_RENDER_TARGET, offset), flags = MASK_INVERSE))
///render plate for OOC stuff like ghosts, hud-screen effects, etc
/atom/movable/screen/plane_master/rendering_plate/non_game
- name = "non-game rendering plate"
+ name = "Non-Game rendering plate"
+ documentation = "Renders anything that's out of character. Mostly useful as a converse to the game rendering plate."
plane = RENDER_PLANE_NON_GAME
- render_relay_plane = RENDER_PLANE_MASTER
+ render_relay_planes = list(RENDER_PLANE_MASTER)
/**
- * Plane master proc called in backdrop() that creates a relay object, sets it as needed and then adds it to the clients screen
+ * Plane master proc called in Initialize() that creates relay objects, and sets them uo as needed
* Sets:
* * layer from plane to avoid z-fighting
- * * plane to relay the render to
- * * render_source so that the plane will render on this object
+ * * planes to relay the render to
+ * * render_source so that the plane will render on these objects
* * mouse opacity to ensure proper mouse hit tracking
* * name for debugging purposes
* Other vars such as alpha will automatically be applied with the render source
- * Arguments:
- * * mymob: mob whose plane is being backdropped
- * * relay_plane: plane we are relaying this plane master to
*/
-/atom/movable/screen/plane_master/proc/relay_render_to_plane(mob/mymob, relay_plane)
- if(relay in mymob.client.screen) //backdrop can be called multiple times
- return
- if(!render_target && generate_render_target)
- render_target = "*[name]: AUTOGENERATED RENDER TGT"
- relay = new()
- relay.render_source = render_target
- relay.plane = relay_plane
- relay.layer = (plane + abs(LOWEST_EVER_PLANE))*0.5 //layer must be positive but can be a decimal
- if(blend_mode_override)
- relay.blend_mode = blend_mode_override
- else
- relay.blend_mode = blend_mode
- relay.mouse_opacity = mouse_opacity
- relay.name = render_target
- mymob.client.screen += relay
+/atom/movable/screen/plane_master/proc/generate_render_relays()
+ var/relay_loc = "CENTER"
+ // If we're using a submap (say for a popup window) make sure we draw onto it
+ if(home?.map)
+ relay_loc = "[home.map]:[relay_loc]"
+
+ var/list/generated_planes = list()
+ for(var/atom/movable/render_plane_relay/relay as anything in relays)
+ generated_planes += relay.plane
+
+ for(var/relay_plane in (render_relay_planes - generated_planes))
+ generate_relay_to(relay_plane, relay_loc)
+
if(blend_mode != BLEND_MULTIPLY)
blend_mode = BLEND_DEFAULT
+ relays_generated = TRUE
+
+/// Creates a connection between this plane master and the passed in plane
+/// Helper for out of system code, shouldn't be used in this file
+/// Build system to differenchiate between generated and non generated render relays
+/atom/movable/screen/plane_master/proc/add_relay_to(target_plane, blend_override)
+ if(get_relay_to(target_plane))
+ return
+ render_relay_planes += target_plane
+ if(!relays_generated && isnull(blend_override))
+ return
+ var/client/display_lad = home?.our_hud?.mymob?.client
+ generate_relay_to(target_plane, show_to = display_lad, blend_override = blend_override)
+
+/proc/get_plane_master_render_base(name)
+ return "*[name]: AUTOGENERATED RENDER TGT"
+
+/atom/movable/screen/plane_master/proc/generate_relay_to(target_plane, relay_loc, client/show_to, blend_override)
+ if(!length(relays) && !initial(render_target))
+ render_target = OFFSET_RENDER_TARGET(get_plane_master_render_base(name), offset)
+ if(!relay_loc)
+ relay_loc = "CENTER"
+ // If we're using a submap (say for a popup window) make sure we draw onto it
+ if(home?.map)
+ relay_loc = "[home.map]:[relay_loc]"
+ var/blend_to_use = blend_override
+ if(isnull(blend_to_use))
+ blend_to_use = blend_mode_override || initial(blend_mode)
+
+ var/atom/movable/render_plane_relay/relay = new()
+ relay.render_source = render_target
+ relay.plane = target_plane
+ relay.screen_loc = relay_loc
+ // There are two rules here
+ // 1: layer needs to be positive (negative layers are treated as float layers)
+ // 2: lower planes (including offset ones) need to be layered below higher ones (because otherwise they'll render fucky)
+ // By multiplying LOWEST_EVER_PLANE by 30, we give 30 offsets worth of room to planes before they start going negative
+ // Bet
+ relay.layer = (plane + abs(LOWEST_EVER_PLANE * 30)) //layer must be positive but can be a decimal
+ relay.blend_mode = blend_to_use
+ relay.mouse_opacity = mouse_opacity
+ relay.name = render_target
+ relays += relay
+ // Relays are sometimes generated early, before huds have a mob to display stuff to
+ // That's what this is for
+ if(show_to)
+ show_to.screen += relay
+ return relay
+
+/// Breaks a connection between this plane master, and the passed in place
+/atom/movable/screen/plane_master/proc/remove_relay_from(target_plane)
+ render_relay_planes -= target_plane
+ var/atom/movable/render_plane_relay/existing_relay = get_relay_to(target_plane)
+ if(!existing_relay)
+ return
+ relays -= existing_relay
+ if(!length(relays) && !initial(render_target))
+ render_target = null
+ var/client/lad = home?.our_hud?.mymob?.client
+ if(lad)
+ lad.screen -= existing_relay
+
+/// Gets the relay atom we're using to connect to the target plane, if one exists
+/atom/movable/screen/plane_master/proc/get_relay_to(target_plane)
+ for(var/atom/movable/render_plane_relay/relay in relays)
+ if(relay.plane == target_plane)
+ return relay
+
+ return null
+
+/// Basically, trigger a full hud rebuild so our relays will be added to the screen
+/// I hate hud code
+/atom/movable/screen/plane_master/proc/rebuild_relays()
+ relays = list()
+ var/datum/hud/hud = home.our_hud
+ hud.show_hud(hud.hud_version)
diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm
index ac189048e09..951d0f626f9 100644
--- a/code/_onclick/hud/robot.dm
+++ b/code/_onclick/hud/robot.dm
@@ -136,6 +136,8 @@
static_inventory += using
robit.interfaceButton = using
if(robit.modularInterface)
+ // Just trust me
+ robit.modularInterface.vis_flags |= VIS_INHERIT_PLANE
using.vis_contents += robit.modularInterface
var/atom/movable/screen/robot/modpc/tabletbutton = using
tabletbutton.robot = robit
@@ -231,7 +233,7 @@
A.screen_loc = "CENTER[x]:16,SOUTH+[y]:7"
else
A.screen_loc = "CENTER+[x]:16,SOUTH+[y]:7"
- A.plane = ABOVE_HUD_PLANE
+ SET_PLANE_IMPLICIT(A, ABOVE_HUD_PLANE)
x++
if(x == 4)
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index d5633eba2b5..4e0bc221b12 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -9,10 +9,11 @@
/atom/movable/screen
name = ""
icon = 'icons/hud/screen_gen.dmi'
+ // NOTE: screen objects do NOT change their plane to match the z layer of their owner
+ // You shouldn't need this, but if you ever do and it's widespread, reconsider what you're doing.
plane = HUD_PLANE
animate_movement = SLIDE_STEPS
speech_span = SPAN_ROBOT
- vis_flags = VIS_INHERIT_PLANE
appearance_flags = APPEARANCE_UI
/// A reference to the object in the slot. Grabs or items, generally.
var/obj/master = null
@@ -455,6 +456,7 @@
vis_contents -= hover_overlays_cache[hovering]
hovering = choice
+ // Don't need to account for turf cause we're on the hud babyyy
var/obj/effect/overlay/zone_sel/overlay_object = hover_overlays_cache[choice]
if(!overlay_object)
overlay_object = new
diff --git a/code/_onclick/telekinesis.dm b/code/_onclick/telekinesis.dm
index 004d5331d6c..8a02e185cfa 100644
--- a/code/_onclick/telekinesis.dm
+++ b/code/_onclick/telekinesis.dm
@@ -282,7 +282,7 @@
var/mutable_appearance/focus_overlay = new(focus)
focus_overlay.layer = layer + 0.01
- focus_overlay.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(focus_overlay, ABOVE_HUD_PLANE, focus)
. += focus_overlay
/obj/item/tk_grab/suicide_act(mob/user)
diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm
index a362eab6941..01e3e8057f0 100644
--- a/code/controllers/subsystem/air.dm
+++ b/code/controllers/subsystem/air.dm
@@ -627,12 +627,16 @@ GLOBAL_LIST_EMPTY(colored_turfs)
GLOBAL_LIST_EMPTY(colored_images)
/datum/controller/subsystem/air/proc/setup_turf_visuals()
for(var/sharp_color in GLOB.contrast_colors)
- var/obj/effect/overlay/atmos_excited/suger_high = new()
- GLOB.colored_turfs += suger_high
- var/image/shiny = new('icons/effects/effects.dmi', suger_high, "atmos_top")
- shiny.plane = ATMOS_GROUP_PLANE
- shiny.color = sharp_color
- GLOB.colored_images += shiny
+ var/list/add_to = list()
+ GLOB.colored_turfs += list(add_to)
+ for(var/offset in 0 to SSmapping.max_plane_offset)
+ var/obj/effect/overlay/atmos_excited/suger_high = new()
+ SET_PLANE_W_SCALAR(suger_high, HIGH_GAME_PLANE, offset)
+ add_to += suger_high
+ var/image/shiny = new('icons/effects/effects.dmi', suger_high, "atmos_top")
+ SET_PLANE_W_SCALAR(shiny, HIGH_GAME_PLANE, offset)
+ shiny.color = sharp_color
+ GLOB.colored_images += shiny
/datum/controller/subsystem/air/proc/setup_template_machinery(list/atmos_machines)
var/obj/machinery/atmospherics/AM
@@ -788,8 +792,7 @@ GLOBAL_LIST_EMPTY(colored_images)
#else
data["display_max"] = FALSE
#endif
- var/atom/movable/screen/plane_master/plane = user.hud_used.plane_masters["[ATMOS_GROUP_PLANE]"]
- data["showing_user"] = (plane.alpha == 255)
+ data["showing_user"] = user.hud_used.atmos_debug_overlays
return data
/datum/controller/subsystem/air/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
@@ -827,13 +830,10 @@ GLOBAL_LIST_EMPTY(colored_images)
group.hide_turfs()
return TRUE
if("toggle_user_display")
- var/atom/movable/screen/plane_master/plane = ui.user.hud_used.plane_masters["[ATMOS_GROUP_PLANE]"]
- if(!plane.alpha)
- if(ui.user.client)
- ui.user.client.images += GLOB.colored_images
- plane.alpha = 255
+ var/mob/user = ui.user
+ user.hud_used.atmos_debug_overlays = !user.hud_used.atmos_debug_overlays
+ if(user.hud_used.atmos_debug_overlays)
+ user.client.images += GLOB.colored_images
else
- if(ui.user.client)
- ui.user.client.images -= GLOB.colored_images
- plane.alpha = 0
+ user.client.images -= GLOB.colored_images
return TRUE
diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm
index 64a3c44b99c..9a681899763 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -30,6 +30,25 @@ SUBSYSTEM_DEF(mapping)
var/list/holodeck_templates = list()
var/list/areas_in_z = list()
+ /// List of z level (as number) -> plane offset of that z level
+ /// Used to maintain the plane cube
+ var/list/z_level_to_plane_offset = list()
+ /// List of z level (as number) -> The lowest plane offset in that z stack
+ var/list/z_level_to_lowest_plane_offset = list()
+ // This pair allows for easy conversion between an offset plane, and its true representation
+ // Both are in the form "input plane" -> output plane(s)
+ /// Assoc list of string plane values to their true, non offset representation
+ var/list/plane_offset_to_true
+ /// Assoc list of true string plane values to a list of all potential offset planess
+ var/list/true_to_offset_planes
+ /// Assoc list of string plane to the plane's offset value
+ var/list/plane_to_offset
+ /// List of planes that do not allow for offsetting
+ var/list/plane_offset_blacklist
+ /// List of render targets that do not allow for offsetting
+ var/list/render_offset_blacklist
+ /// The largest plane offset we've generated so far
+ var/max_plane_offset = 0
var/loading_ruins = FALSE
var/list/turf/unused_turfs = list() //Not actually unused turfs they're unused but reserved for use for whatever requests them. "[zlevel_of_turf]" = list(turfs)
@@ -80,6 +99,17 @@ SUBSYSTEM_DEF(mapping)
if(!config || config.defaulted)
to_chat(world, span_boldannounce("Unable to load next or default map config, defaulting to Meta Station."))
config = old_config
+ plane_offset_to_true = list()
+ true_to_offset_planes = list()
+ plane_to_offset = list()
+ // VERY special cases for FLOAT_PLANE, so it will be treated as expected by plane management logic
+ // Sorry :(
+ plane_offset_to_true["[FLOAT_PLANE]"] = FLOAT_PLANE
+ true_to_offset_planes["[FLOAT_PLANE]"] = list(FLOAT_PLANE)
+ plane_to_offset["[FLOAT_PLANE]"] = 0
+ plane_offset_blacklist = list()
+ render_offset_blacklist = list()
+ create_plane_offsets(0, 0)
initialize_biomes()
loadWorld()
determine_fake_sale()
@@ -664,3 +694,108 @@ GLOBAL_LIST_EMPTY(the_station_areas)
isolated_ruins_z = add_new_zlevel("Isolated Ruins/Reserved", list(ZTRAIT_RESERVED = TRUE, ZTRAIT_ISOLATED_RUINS = TRUE))
initialize_reserved_level(isolated_ruins_z.z_value)
return isolated_ruins_z.z_value
+
+/// Takes a z level datum, and tells the mapping subsystem to manage it
+/// Also handles things like plane offset generation, and other things that happen on a z level to z level basis
+/datum/controller/subsystem/mapping/proc/manage_z_level(datum/space_level/new_z)
+ // First, add the z
+ z_list += new_z
+
+ // Then we build our lookup lists
+ var/z_value = new_z.z_value
+ // We are guarenteed that we'll always grow bottom up
+ // Suck it jannies
+ z_level_to_plane_offset.len += 1
+ z_level_to_lowest_plane_offset += 1
+ // 0's the default value, we'll update it later if required
+ z_level_to_plane_offset[z_value] = 0
+ z_level_to_lowest_plane_offset[z_value] = 0
+
+ // Now we check if this plane is offset or not
+ var/below_offset = new_z.traits[ZTRAIT_DOWN]
+ if(below_offset)
+ update_plane_tracking(new_z)
+
+ // And finally, misc global generation
+
+ // We'll have to update this if offsets change, because we load lowest z to highest z
+ generate_lighting_appearance_by_z(z_value)
+
+/datum/controller/subsystem/mapping/proc/update_plane_tracking(datum/space_level/update_with)
+ // We're essentially going to walk down the stack of connected z levels, and set their plane offset as we go
+ // Yes this will cause infinite loops if our templating is fucked. Fuck off
+ var/below_offset = 0
+ // I'm sorry, it needs to start at 0
+ var/current_level = -1
+ var/current_z = update_with.z_value
+ var/list/datum/space_level/levels_checked = list()
+ do
+ current_level += 1
+ current_z += below_offset
+ z_level_to_plane_offset[current_z] = current_level
+ var/datum/space_level/next_level = z_list[current_z]
+ below_offset = next_level.traits[ZTRAIT_DOWN]
+ levels_checked += next_level
+ while(below_offset)
+
+ /// Updates the lowest offset value
+ for(var/datum/space_level/level_to_update in levels_checked)
+ z_level_to_lowest_plane_offset[level_to_update.z_value] = current_level
+
+ // This can be affected by offsets, so we need to update it
+ // PAIN
+ for(var/i in 1 to length(z_list))
+ generate_lighting_appearance_by_z(i)
+
+ var/old_max = max_plane_offset
+ max_plane_offset = max(max_plane_offset, current_level)
+ if(max_plane_offset == old_max)
+ return
+
+ generate_offset_lists(old_max + 1, max_plane_offset)
+ SEND_SIGNAL(src, COMSIG_PLANE_OFFSET_INCREASE, old_max, max_plane_offset)
+
+/// Takes an offset to generate misc lists to, and a base to start from
+/// Use this to react globally to maintain parity with plane offsets
+/datum/controller/subsystem/mapping/proc/generate_offset_lists(gen_from, new_offset)
+ create_plane_offsets(gen_from, new_offset)
+ for(var/offset in gen_from to new_offset)
+ GLOB.fullbright_overlays += create_fullbright_overlay(offset)
+ GLOB.cryo_overlays_cover_on += create_cryo_overlay(offset, "cover-on")
+ GLOB.cryo_overlays_cover_off += create_cryo_overlay(offset, "cover-off")
+
+ for(var/datum/gas/gas_type as anything in GLOB.meta_gas_info)
+ var/list/gas_info = GLOB.meta_gas_info[gas_type]
+ if(initial(gas_type.moles_visible) != null)
+ gas_info[META_GAS_OVERLAY] += generate_gas_overlays(gen_from, new_offset, gas_type)
+
+/datum/controller/subsystem/mapping/proc/create_plane_offsets(gen_from, new_offset)
+ for(var/plane_offset in gen_from to new_offset)
+ for(var/atom/movable/screen/plane_master/master_type as anything in subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/rendering_plate)
+ var/plane_to_use = initial(master_type.plane)
+ var/string_real = "[plane_to_use]"
+
+ var/offset_plane = GET_NEW_PLANE(plane_to_use, plane_offset)
+ var/string_plane = "[offset_plane]"
+
+ if(!initial(master_type.allows_offsetting))
+ plane_offset_blacklist[string_plane] = TRUE
+ var/render_target = initial(master_type.render_target)
+ if(!render_target)
+ render_target = get_plane_master_render_base(initial(master_type.name))
+ render_offset_blacklist[render_target] = TRUE
+ if(plane_offset != 0)
+ continue
+
+ plane_offset_to_true[string_plane] = plane_to_use
+ plane_to_offset[string_plane] = plane_offset
+
+ if(!true_to_offset_planes[string_real])
+ true_to_offset_planes[string_real] = list()
+
+ true_to_offset_planes[string_real] |= offset_plane
+
+/proc/generate_lighting_appearance_by_z(z_level)
+ if(length(GLOB.default_lighting_underlays_by_z) < z_level)
+ GLOB.default_lighting_underlays_by_z.len = z_level
+ GLOB.default_lighting_underlays_by_z[z_level] = mutable_appearance(LIGHTING_ICON, "transparent", z_level, null, LIGHTING_PLANE, 255, RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM, offset_const = GET_Z_PLANE_OFFSET(z_level))
diff --git a/code/controllers/subsystem/overlays.dm b/code/controllers/subsystem/overlays.dm
index a5c268f2ad9..9ab99a1ac8e 100644
--- a/code/controllers/subsystem/overlays.dm
+++ b/code/controllers/subsystem/overlays.dm
@@ -131,3 +131,104 @@ SUBSYSTEM_DEF(overlays)
overlays |= cached_other
else if(cut_old)
cut_overlays()
+
+// Debug procs
+
+/atom
+ /// List of overlay "keys" (info about the appearance) -> mutable versions of static appearances
+ /// Drawn from the overlays list
+ var/list/realized_overlays
+
+/image
+ /// List of overlay "keys" (info about the appearance) -> mutable versions of static appearances
+ /// Drawn from the overlays list
+ var/list/realized_overlays
+
+/// Takes the atoms's existing overlays, and makes them mutable so they can be properly vv'd in the realized_overlays list
+/atom/proc/realize_overlays()
+ realized_overlays = list()
+ var/list/queue = overlays.Copy()
+ var/queue_index = 0
+ while(queue_index < length(queue))
+ queue_index++
+ // If it's not a command, we assert that it's an appearance
+ var/mutable_appearance/appearance = queue[queue_index]
+ if(!appearance) // Who fucking adds nulls to their sublists god you people are the worst
+ continue
+
+ var/mutable_appearance/new_appearance = new /mutable_appearance()
+ new_appearance.appearance = appearance
+ var/key = "[appearance.icon]-[appearance.icon_state]-[appearance.plane]-[appearance.layer]-[appearance.dir]-[appearance.color]"
+ var/tmp_key = key
+ var/overlay_indx = 1
+ while(realized_overlays[tmp_key])
+ tmp_key = "[key]-[overlay_indx]"
+ overlay_indx++
+
+ realized_overlays[tmp_key] = new_appearance
+ // Now check its children
+ for(var/mutable_appearance/child_appearance as anything in appearance.overlays)
+ queue += child_appearance
+
+/// Takes the image's existing overlays, and makes them mutable so they can be properly vv'd in the realized_overlays list
+/image/proc/realize_overlays()
+ realized_overlays = list()
+ var/list/queue = overlays.Copy()
+ var/queue_index = 0
+ while(queue_index < length(queue))
+ queue_index++
+ // If it's not a command, we assert that it's an appearance
+ var/mutable_appearance/appearance = queue[queue_index]
+ if(!appearance) // Who fucking adds nulls to their sublists god you people are the worst
+ continue
+
+ var/mutable_appearance/new_appearance = new /mutable_appearance()
+ new_appearance.appearance = appearance
+ var/key = "[appearance.icon]-[appearance.icon_state]-[appearance.plane]-[appearance.layer]-[appearance.dir]-[appearance.color]"
+ var/tmp_key = key
+ var/overlay_indx = 1
+ while(realized_overlays[tmp_key])
+ tmp_key = "[key]-[overlay_indx]"
+ overlay_indx++
+
+ realized_overlays[tmp_key] = new_appearance
+ // Now check its children
+ for(var/mutable_appearance/child_appearance as anything in appearance.overlays)
+ queue += child_appearance
+
+/// Takes two appearances as args, prints out, logs, and returns a text representation of their differences
+/// Including suboverlays
+/proc/diff_appearances(mutable_appearance/first, mutable_appearance/second, iter = 0)
+ var/list/diffs = list()
+ var/list/firstdeet = first.vars
+ var/list/seconddeet = second.vars
+ var/diff_found = FALSE
+ for(var/name in first.vars)
+ var/firstv = firstdeet[name]
+ var/secondv = seconddeet[name]
+ if(firstv ~= secondv)
+ continue
+ if((islist(firstv) || islist(secondv)) && length(firstv) == 0 && length(secondv) == 0)
+ continue
+ if(name == "vars") // Go away
+ continue
+ if(name == "comp_lookup") // This is just gonna happen with marked datums, don't care
+ continue
+ if(name == "overlays")
+ first.realize_overlays()
+ second.realize_overlays()
+ var/overlays_differ = FALSE
+ for(var/i in 1 to length(first.realized_overlays))
+ if(diff_appearances(first.realized_overlays[i], second.realized_overlays[i], iter + 1))
+ overlays_differ = TRUE
+
+ if(!overlays_differ)
+ continue
+
+ diff_found = TRUE
+ diffs += "Diffs detected at [name]: First ([firstv]), Second ([secondv])"
+
+ var/text = "Depth of: [iter]\n\t[diffs.Join("\n\t")]"
+ message_admins(text)
+ log_world(text)
+ return diff_found
diff --git a/code/datums/beam.dm b/code/datums/beam.dm
index 26dfda54898..f85d2922967 100644
--- a/code/datums/beam.dm
+++ b/code/datums/beam.dm
@@ -63,6 +63,7 @@
visuals.icon_state = icon_state
visuals.color = beam_color
visuals.layer = ABOVE_ALL_MOB_LAYER
+ visuals.vis_flags = VIS_INHERIT_PLANE
visuals.update_appearance()
Draw()
RegisterSignal(origin, COMSIG_MOVABLE_MOVED, .proc/redrawing)
@@ -170,7 +171,7 @@
/obj/effect/ebeam/update_overlays()
. = ..()
- var/mutable_appearance/emmisive = emissive_appearance(icon, icon_state)
+ var/mutable_appearance/emmisive = emissive_appearance(icon, icon_state, src)
emmisive.transform = transform
. += emmisive
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index 349981f5664..050331ab756 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -62,7 +62,7 @@
desc = "A wonderful yet fake friend."
see_in_dark = 0
lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
- sight = NONE
+ sight = SEE_BLACKNESS
mouse_opacity = MOUSE_OPACITY_ICON
see_invisible = SEE_INVISIBLE_LIVING
invisibility = INVISIBILITY_MAXIMUM
@@ -215,14 +215,19 @@
//speech bubble
if(owner.client)
var/mutable_appearance/MA = mutable_appearance('icons/mob/effects/talk.dmi', src, "default[say_test(message)]", FLY_LAYER)
- MA.plane = ABOVE_GAME_PLANE
+ SET_PLANE_EXPLICIT(MA, ABOVE_GAME_PLANE, src)
MA.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, MA, list(owner.client), 30)
+ LAZYADD(update_on_z, MA)
+ addtimer(CALLBACK(src, .proc/clear_saypopup, MA), 3.5 SECONDS)
for(var/mob/M in GLOB.dead_mob_list)
var/link = FOLLOW_LINK(M, owner)
to_chat(M, "[link] [dead_rendered]")
+/mob/camera/imaginary_friend/proc/clear_saypopup(image/say_popup)
+ LAZYREMOVE(update_on_z, say_popup)
+
/mob/camera/imaginary_friend/Move(NewLoc, Dir = 0)
if(world.time < move_delay)
return FALSE
diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
index fb86da85c2c..c91fc55b7c3 100644
--- a/code/datums/chatmessage.dm
+++ b/code/datums/chatmessage.dm
@@ -198,7 +198,7 @@
// Build message image
message = image(loc = message_loc, layer = CHAT_LAYER + CHAT_LAYER_Z_STEP * current_z_idx++)
- message.plane = RUNECHAT_PLANE
+ SET_PLANE(message, RUNECHAT_PLANE, message_loc)
message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART
message.alpha = 0
message.pixel_y = target.maptext_height
@@ -212,6 +212,7 @@
LAZYADDASSOCLIST(owned_by.seen_messages, message_loc, src)
owned_by.images |= message
animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME)
+ RegisterSignal(message_loc, COMSIG_MOVABLE_Z_CHANGED, .proc/loc_z_changed)
// Register with the runechat SS to handle EOL and destruction
var/duration = lifespan - CHAT_MESSAGE_EOL_FADE
@@ -229,6 +230,10 @@
animate(message, alpha = 0, time = fadetime, flags = ANIMATION_PARALLEL)
addtimer(CALLBACK(GLOBAL_PROC, /proc/qdel, src), fadetime, TIMER_DELETE_ME, SSrunechat)
+/datum/chatmessage/proc/loc_z_changed(datum/source, turf/old_turf, turf/new_turf, same_z_layer)
+ SIGNAL_HANDLER
+ SET_PLANE(message, RUNECHAT_PLANE, new_turf)
+
/**
* Creates a message overlay at a defined location for a given speaker
*
diff --git a/code/datums/components/fov_handler.dm b/code/datums/components/fov_handler.dm
index 0caf4de0652..296d93b1bb7 100644
--- a/code/datums/components/fov_handler.dm
+++ b/code/datums/components/fov_handler.dm
@@ -22,6 +22,9 @@
qdel(src) //no QDEL hint for components, and we dont want this to print a warning regarding bad component application
return
+ for(var/atom/movable/screen/plane_master/plane_master in mob_parent.hud_used.get_true_plane_masters(FIELD_OF_VISION_BLOCKER_PLANE))
+ plane_master.unhide_plane(mob_parent)
+
blocker_mask = new
visual_shadow = new
visual_shadow.alpha = parent_client?.prefs.read_preference(/datum/preference/numeric/fov_darkness)
@@ -31,6 +34,10 @@
update_mask()
/datum/component/fov_handler/Destroy()
+ var/mob/living/mob_parent = parent
+ for(var/atom/movable/screen/plane_master/plane_master in mob_parent.hud_used.get_true_plane_masters(FIELD_OF_VISION_BLOCKER_PLANE))
+ plane_master.hide_plane(mob_parent)
+
if(applied_mask)
remove_mask()
if(blocker_mask) // In a case of early deletion due to volatile client
diff --git a/code/datums/components/hide_highest_offset.dm b/code/datums/components/hide_highest_offset.dm
new file mode 100644
index 00000000000..6e6dfa4d641
--- /dev/null
+++ b/code/datums/components/hide_highest_offset.dm
@@ -0,0 +1,24 @@
+/// Component that takes a plane master, and will hide it if it's the highest offset of its kind
+/// This allows us to not show PMs to clients if they're not actively doing anything
+/datum/component/plane_hide_highest_offset
+
+/datum/component/plane_hide_highest_offset/Initialize()
+ if(!istype(parent, /atom/movable/screen/plane_master))
+ return
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, .proc/on_offset_increase)
+ offset_increase(SSmapping.max_plane_offset)
+
+/datum/component/plane_hide_highest_offset/proc/on_offset_increase(datum/source, old_offset, new_offset)
+ SIGNAL_HANDLER
+ offset_increase(new_offset)
+
+/datum/component/plane_hide_highest_offset/proc/offset_increase(new_offset)
+ var/atom/movable/screen/plane_master/plane_parent = parent
+ var/mob/our_mob = plane_parent.home?.our_hud?.mymob
+ var/our_offset = plane_parent.offset
+ if(!our_mob)
+ return
+ if(our_offset == new_offset)
+ plane_parent.hide_plane(our_mob)
+ else if(plane_parent.force_hidden)
+ plane_parent.unhide_plane(our_mob)
diff --git a/code/datums/components/mirage_border.dm b/code/datums/components/mirage_border.dm
index a366f4b822f..eb167a98f9f 100644
--- a/code/datums/components/mirage_border.dm
+++ b/code/datums/components/mirage_border.dm
@@ -36,7 +36,9 @@
return COMPONENT_INCOMPATIBLE
holder.forceMove(parent)
+INITIALIZE_IMMEDIATE(/obj/effect/abstract/mirage_holder)
/obj/effect/abstract/mirage_holder
name = "Mirage holder"
anchored = TRUE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
diff --git a/code/datums/components/overlay_lighting.dm b/code/datums/components/overlay_lighting.dm
index 67290d4c58c..b799e7891d7 100644
--- a/code/datums/components/overlay_lighting.dm
+++ b/code/datums/components/overlay_lighting.dm
@@ -83,13 +83,13 @@
. = ..()
visible_mask = image('icons/effects/light_overlays/light_32.dmi', icon_state = "light")
- visible_mask.plane = O_LIGHTING_VISUAL_PLANE
+ SET_PLANE_EXPLICIT(visible_mask, O_LIGHTING_VISUAL_PLANE, movable_parent)
visible_mask.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
visible_mask.alpha = 0
if(is_directional)
directional = TRUE
cone = image('icons/effects/light_overlays/light_cone.dmi', icon_state = "light")
- cone.plane = O_LIGHTING_VISUAL_PLANE
+ SET_PLANE_EXPLICIT(cone, O_LIGHTING_VISUAL_PLANE, movable_parent)
cone.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
cone.alpha = 110
cone.transform = cone.transform.Translate(-32, -32)
@@ -119,6 +119,7 @@
RegisterSignal(parent, COMSIG_ATOM_USED_IN_CRAFT, .proc/on_parent_crafted)
RegisterSignal(parent, COMSIG_LIGHT_EATER_QUEUE, .proc/on_light_eater)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_parent_moved)
+ RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, .proc/on_z_move)
var/atom/movable/movable_parent = parent
if(movable_parent.light_flags & LIGHT_ATTACHED)
overlay_lighting_flags |= LIGHTING_ATTACHED
@@ -300,6 +301,17 @@
return
make_luminosity_update()
+/datum/component/overlay_lighting/proc/on_z_move(atom/source)
+ SIGNAL_HANDLER
+ if(current_holder && overlay_lighting_flags & LIGHTING_ON)
+ current_holder.underlays -= visible_mask
+ current_holder.underlays -= cone
+ SET_PLANE_EXPLICIT(visible_mask, O_LIGHTING_VISUAL_PLANE, source)
+ if(cone)
+ SET_PLANE_EXPLICIT(cone, O_LIGHTING_VISUAL_PLANE, source)
+ if(current_holder && overlay_lighting_flags & LIGHTING_ON)
+ current_holder.underlays += visible_mask
+ current_holder.underlays += cone
///Called when the current_holder is qdeleted, to remove the light effect.
/datum/component/overlay_lighting/proc/on_parent_attached_to_qdel(atom/movable/source, force)
diff --git a/code/datums/components/seethrough.dm b/code/datums/components/seethrough.dm
index 5a34ece333f..5c43ede1182 100644
--- a/code/datums/components/seethrough.dm
+++ b/code/datums/components/seethrough.dm
@@ -95,11 +95,15 @@
///Apply the trickery image and animation
/datum/component/seethrough/proc/trick_mob(mob/fool)
+ var/datum/hud/our_hud = fool.hud_used
+ var/atom/movable/screen/plane_master/seethrough = our_hud.get_plane_master(SEETHROUGH_PLANE)
+ seethrough.unhide_plane(fool)
+
var/image/user_overlay = new(parent)
user_overlay.loc = parent
user_overlay.override = TRUE
//Special plane so we can click through the overlay
- user_overlay.plane = ABOVE_GAME_NO_MOUSE_PLANE
+ SET_PLANE_EXPLICIT(user_overlay, SEETHROUGH_PLANE, parent)
//These are inherited, but we already use the atom's loc so we end up at double the pixel offset
user_overlay.pixel_x = 0
@@ -135,6 +139,9 @@
var/image/trickery_image = tricked_mobs[fool]
fool.client?.images -= trickery_image
UnregisterSignal(fool, COMSIG_MOB_LOGOUT)
+ var/datum/hud/our_hud = fool.hud_used
+ var/atom/movable/screen/plane_master/seethrough = our_hud.get_plane_master(SEETHROUGH_PLANE)
+ seethrough.hide_plane(fool)
tricked_mobs.Cut()
@@ -145,3 +152,6 @@
tricked_mobs.Remove(fool)
UnregisterSignal(fool, COMSIG_MOB_LOGOUT)
RegisterSignal(fool, COMSIG_MOB_LOGIN, .proc/trick_mob)
+ var/datum/hud/our_hud = fool.hud_used
+ var/atom/movable/screen/plane_master/seethrough = our_hud.get_plane_master(SEETHROUGH_PLANE)
+ seethrough.hide_plane(fool)
diff --git a/code/datums/components/tactical.dm b/code/datums/components/tactical.dm
index 47677755acb..42391e114ec 100644
--- a/code/datums/components/tactical.dm
+++ b/code/datums/components/tactical.dm
@@ -1,5 +1,6 @@
/datum/component/tactical
var/allowed_slot
+ var/current_slot
/datum/component/tactical/Initialize(allowed_slot)
if(!isitem(parent))
@@ -19,6 +20,15 @@
unmodify()
return ..()
+/datum/component/tactical/proc/on_z_move(datum/source)
+ SIGNAL_HANDLER
+ var/obj/item/master = parent
+ if(!ismob(master.loc))
+ return
+ var/old_slot = current_slot
+ unmodify(master, master.loc)
+ modify(master, master.loc, old_slot)
+
/datum/component/tactical/proc/modify(obj/item/source, mob/user, slot)
SIGNAL_HANDLER
@@ -26,13 +36,16 @@
unmodify()
return
+ current_slot = slot
+
var/obj/item/master = parent
var/image/I = image(icon = master.icon, icon_state = master.icon_state, loc = user)
- I.plane = GAME_PLANE_FOV_HIDDEN
+ SET_PLANE_EXPLICIT(I, GAME_PLANE_FOV_HIDDEN, master)
I.copy_overlays(master)
I.override = TRUE
source.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "sneaking_mission", I)
I.layer = ABOVE_MOB_LAYER
+ RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, .proc/on_z_move)
/datum/component/tactical/proc/unmodify(obj/item/source, mob/user)
SIGNAL_HANDLER
@@ -44,3 +57,5 @@
user = master.loc
user.remove_alt_appearance("sneaking_mission")
+ current_slot = null
+ UnregisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED)
diff --git a/code/datums/elements/turf_transparency.dm b/code/datums/elements/turf_transparency.dm
index 8493b9cdf43..c654e8c489e 100644
--- a/code/datums/elements/turf_transparency.dm
+++ b/code/datums/elements/turf_transparency.dm
@@ -1,9 +1,180 @@
+/// List of z pillars (datums placed in the bottom left of XbyX squares that control transparency in that space)
+/// The pillars are stored in triple depth lists indexed by (world_size % pillar_size) + 1
+/// They are created at transparent turf request, and deleted when no turfs remain
+GLOBAL_LIST_EMPTY(pillars_by_z)
+#define Z_PILLAR_RADIUS 20
+// Takes a position, transforms it into a z pillar key
+#define Z_PILLAR_TRANSFORM(pos) (ROUND_UP(pos / Z_PILLAR_RADIUS))
+// Takes a z pillar key, hands back the actual posiiton it represents
+// A key of 1 becomes 1, a key of 2 becomes Z_PILLAR_RADIUS + 1, etc.
+#define Z_KEY_TO_POSITION(key) (((key - 1) * Z_PILLAR_RADIUS) + 1)
+
+/// Returns a z pillar to insert turfs into
+/proc/request_z_pillar(x, y, z)
+ var/list/pillars_by_z = GLOB.pillars_by_z
+ if(length(pillars_by_z) < z)
+ pillars_by_z.len = z
+ var/list/our_z = pillars_by_z[z]
+ if(!our_z)
+ our_z = list()
+ pillars_by_z[z] = our_z
+
+ //Now that we've got the z layer sorted, we're gonna check the X line
+ var/x_key = Z_PILLAR_TRANSFORM(x)
+ if(length(our_z) < x_key)
+ our_z.len = x_key
+ var/list/our_x = our_z[x_key]
+ if(!our_x)
+ our_x = list()
+ our_z[x_key] = our_x
+
+ //And now the y layer
+ var/y_key = Z_PILLAR_TRANSFORM(y)
+ if(length(our_x) < y_key)
+ our_x.len = y_key
+ var/datum/z_pillar/our_lad = our_x[y_key]
+ if(!our_lad)
+ our_lad = new(x_key, y_key, z)
+ our_x[y_key] = our_lad
+ return our_lad
+
+/// Exists to be placed on the turf of walls and such to hold the vis_contents of the tile below
+/// Otherwise the lower turf might get shifted around, which is dumb. do this instead.
+/obj/effect/abstract/z_holder
+ var/datum/z_pillar/pillar
+ var/turf/show_for
+ appearance_flags = PIXEL_SCALE
+ plane = HUD_PLANE
+ anchored = TRUE
+ move_resist = INFINITY
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+/obj/effect/abstract/z_holder/Destroy()
+ if(pillar)
+ pillar.drawing_object -= show_for
+ pillar = null
+ show_for = null
+ return ..()
+
+/obj/effect/abstract/z_holder/proc/display(turf/display, datum/z_pillar/behalf_of)
+ if(pillar)
+ CRASH("We attempted to use a z holder to display when it was already in use, what'd you do")
+
+ pillar = behalf_of
+ show_for = display
+ vis_contents += display
+ behalf_of.drawing_object[display] = src
+
+/// Grouping datum that manages transparency for a block of space
+/// Setup to ease debugging, and to make add/remove operations cheaper
+/datum/z_pillar
+ var/x_pos
+ var/y_pos
+ var/z_pos
+ /// Assoc list in the form displayed turf -> list of sources
+ var/list/turf_sources = list()
+ /// Assoc list of turfs using z holders in the form displayed turf -> z holder
+ var/list/drawing_object = list()
+
+/datum/z_pillar/New(x_pos, y_pos, z_pos)
+ . = ..()
+ src.x_pos = x_pos
+ src.y_pos = y_pos
+ src.z_pos = z_pos
+
+/datum/z_pillar/Destroy()
+ GLOB.pillars_by_z[z_pos][x_pos][y_pos] = null
+ // Just to be totally clear, this is code that exists to
+ // A: make sure cleanup is actually possible for this datum, just in case someone goes insane
+ // B: allow for easier debugging and making sure everything behaves as expected when fully removed
+ // It is not meant to be relied on, please don't actually it's not very fast
+ for(var/turf/displaying in turf_sources)
+ for(var/turf/displaying_for in turf_sources[displaying])
+ hide_turf(displaying, displaying_for)
+ return ..()
+
+/// Displays a turf from the z level below us on our level
+/datum/z_pillar/proc/display_turf(turf/to_display, turf/source)
+ var/list/sources = turf_sources[to_display]
+ if(!sources)
+ sources = list()
+ turf_sources[to_display] = sources
+ sources |= source
+
+ if(length(sources) != 1) // If we aren't the first to request this turf, return
+ var/obj/effect/abstract/z_holder/holding = drawing_object[to_display]
+ if(!holding)
+ return
+
+ var/turf/visual_target = to_display.above()
+ /// Basically, if we used to be under a non transparent turf, but are no longer in that position
+ /// Then we add to the transparent turf we're now under, and nuke the old object
+ if(!istransparentturf(visual_target))
+ return
+
+ holding.vis_contents -= to_display
+ qdel(holding)
+ drawing_object -= to_display
+ visual_target.vis_contents += to_display
+ return
+
+ var/turf/visual_target = to_display.above()
+ if(istransparentturf(visual_target) || isopenspaceturf(visual_target))
+ visual_target.vis_contents += to_display
+ else
+ var/obj/effect/abstract/z_holder/hold_this = new(visual_target)
+ hold_this.display(to_display, src)
+
+/// Hides an existing turf from our vis_contents, or the vis_contents of the source if applicable
+/datum/z_pillar/proc/hide_turf(turf/to_hide, turf/source)
+ var/list/sources = turf_sources[to_hide]
+ if(!sources)
+ return
+ sources -= source
+ // More sources remain
+ if(length(sources))
+ return
+
+ turf_sources -= to_hide
+ var/obj/effect/abstract/z_holder/holding = drawing_object[to_hide]
+ if(holding)
+ qdel(holding)
+ else
+ var/turf/visual_target = to_hide.above()
+ visual_target.vis_contents -= to_hide
+
+ if(!length(turf_sources) && !QDELETED(src))
+ qdel(src)
+
+/// Called when a transparent turf is cleared. We wait a tick, then check to see what
+/// Kind of turf replaced our former holder, and resetup our visuals as desired
+/// We do not need to do this for non transparent holders, because they will have their abstract object cleared
+/// When a transparent holder comes back.
+/datum/z_pillar/proc/parent_cleared(turf/visual, turf/current_holder)
+ addtimer(CALLBACK(src, .proc/refresh_orphan, visual, current_holder))
+
+/// Runs the actual refresh of some formerly orphaned via vis_loc deletiong turf
+/// We'll only reup if we either have no souece, or if the source is a transparent turf
+/datum/z_pillar/proc/refresh_orphan(turf/orphan, turf/parent)
+ var/list/sources = turf_sources[orphan]
+ if(!length(sources))
+ return
+
+ var/obj/effect/abstract/z_holder/holding = drawing_object[orphan]
+ if(holding)
+ return
+
+ if(istransparentturf(parent) || isopenspaceturf(parent))
+ parent.vis_contents += orphan
+ else
+ var/obj/effect/abstract/z_holder/hold_this = new(parent)
+ hold_this.display(orphan, src)
/datum/element/turf_z_transparency
element_flags = ELEMENT_DETACH
///This proc sets up the signals to handle updating viscontents when turfs above/below update. Handle plane and layer here too so that they don't cover other obs/turfs in Dream Maker
-/datum/element/turf_z_transparency/Attach(datum/target, is_openspace = FALSE)
+/datum/element/turf_z_transparency/Attach(datum/target, mapload)
. = ..()
if(!isturf(target))
return ELEMENT_INCOMPATIBLE
@@ -11,43 +182,71 @@
var/turf/our_turf = target
our_turf.layer = OPENSPACE_LAYER
- if(is_openspace) // openspace and windows have different visual effects but both share this component.
- our_turf.plane = OPENSPACE_PLANE
- else
- our_turf.plane = TRANSPARENT_FLOOR_PLANE
RegisterSignal(target, COMSIG_TURF_MULTIZ_DEL, .proc/on_multiz_turf_del)
RegisterSignal(target, COMSIG_TURF_MULTIZ_NEW, .proc/on_multiz_turf_new)
ADD_TRAIT(our_turf, TURF_Z_TRANSPARENT_TRAIT, ELEMENT_TRAIT(type))
- update_multi_z(our_turf)
+ if(!mapload)
+ update_multi_z(our_turf)
/datum/element/turf_z_transparency/Detach(datum/source)
. = ..()
var/turf/our_turf = source
- our_turf.vis_contents.len = 0
+ clear_multiz(our_turf)
+
UnregisterSignal(our_turf, list(COMSIG_TURF_MULTIZ_NEW, COMSIG_TURF_MULTIZ_DEL))
REMOVE_TRAIT(our_turf, TURF_Z_TRANSPARENT_TRAIT, ELEMENT_TRAIT(type))
///Updates the viscontents or underlays below this tile.
/datum/element/turf_z_transparency/proc/update_multi_z(turf/our_turf)
var/turf/below_turf = our_turf.below()
- if(below_turf) // If we actually have somethign below us, display it.
- our_turf.vis_contents += below_turf
+ if(below_turf) // If we actually have something below us, display it.
+ for(var/turf/partner in range(1, below_turf))
+ // We use our z here to ensure the pillar is actually on our level
+ var/datum/z_pillar/z_boss = request_z_pillar(partner.x, partner.y, our_turf.z)
+ z_boss.display_turf(partner, our_turf)
else
- our_turf.vis_contents.len = 0 // Nuke the list
- add_baseturf_underlay(our_turf)
+ our_turf.underlays += get_baseturf_underlay(our_turf)
+ // This shit is stupid
+ // z transparency is for making something SHOW WHAT'S BENEATH it, or if nothing is, show
+ // the appropriate underlay
+ // IT IS NOT FOR MAKING YOUR CLOSED TURF SEETHROUGH
+ // these are different concerns, and should not be HANDLED TOGETHER
+ // similarly, if you rip this out, rework diagonal closed turfs to work with this system
+ // it will make them look significantly nicer, and should let you tie into their logic more easily
+ // Just please don't break behavior yeah? thanks, I love you <3
if(isclosedturf(our_turf)) //Show girders below closed turfs
var/mutable_appearance/girder_underlay = mutable_appearance('icons/obj/structures.dmi', "girder", layer = TURF_LAYER-0.01)
girder_underlay.appearance_flags = RESET_ALPHA | RESET_COLOR
our_turf.underlays += girder_underlay
var/mutable_appearance/plating_underlay = mutable_appearance('icons/turf/floors.dmi', "plating", layer = TURF_LAYER-0.02)
- plating_underlay = RESET_ALPHA | RESET_COLOR
+ plating_underlay.appearance_flags = RESET_ALPHA | RESET_COLOR
our_turf.underlays += plating_underlay
return TRUE
+/datum/element/turf_z_transparency/proc/clear_multiz(turf/our_turf)
+ var/turf/below_turf = our_turf.below()
+ if(below_turf) // If we actually have something below us, we need to clear ourselves from it
+ for(var/turf/partner in range(1, below_turf))
+ // We use our z here to ensure the pillar is actually on our level
+ var/datum/z_pillar/z_boss = request_z_pillar(partner.x, partner.y, our_turf.z)
+ z_boss.hide_turf(partner, our_turf)
+ if(partner == below_turf)
+ z_boss.parent_cleared(below_turf, our_turf)
+ else
+ our_turf.underlays -= get_baseturf_underlay(our_turf)
+
+ if(isclosedturf(our_turf)) //Show girders below closed turfs
+ var/mutable_appearance/girder_underlay = mutable_appearance('icons/obj/structures.dmi', "girder", layer = TURF_LAYER-0.01)
+ girder_underlay.appearance_flags = RESET_ALPHA | RESET_COLOR
+ our_turf.underlays -= girder_underlay
+ var/mutable_appearance/plating_underlay = mutable_appearance('icons/turf/floors.dmi', "plating", layer = TURF_LAYER-0.02)
+ plating_underlay.appearance_flags = RESET_ALPHA | RESET_COLOR
+ our_turf.underlays -= plating_underlay
+
/datum/element/turf_z_transparency/proc/on_multiz_turf_del(turf/our_turf, turf/below_turf, dir)
SIGNAL_HANDLER
@@ -65,13 +264,13 @@
update_multi_z(our_turf)
///Called when there is no real turf below this turf
-/datum/element/turf_z_transparency/proc/add_baseturf_underlay(turf/our_turf)
+/datum/element/turf_z_transparency/proc/get_baseturf_underlay(turf/our_turf)
var/turf/path = SSmapping.level_trait(our_turf.z, ZTRAIT_BASETURF) || /turf/open/space
if(!ispath(path))
path = text2path(path)
if(!ispath(path))
warning("Z-level [our_turf.z] has invalid baseturf '[SSmapping.level_trait(our_turf.z, ZTRAIT_BASETURF)]'")
path = /turf/open/space
- var/mutable_appearance/underlay_appearance = mutable_appearance(initial(path.icon), initial(path.icon_state), layer = TURF_LAYER-0.02, plane = PLANE_SPACE)
+ var/mutable_appearance/underlay_appearance = mutable_appearance(initial(path.icon), initial(path.icon_state), layer = TURF_LAYER-0.02, offset_spokesman = our_turf, plane = PLANE_SPACE)
underlay_appearance.appearance_flags = RESET_ALPHA | RESET_COLOR
- our_turf.underlays += underlay_appearance
+ return underlay_appearance
diff --git a/code/datums/mutable_appearance.dm b/code/datums/mutable_appearance.dm
index 5fb2cdf2a3c..d85833be982 100644
--- a/code/datums/mutable_appearance.dm
+++ b/code/datums/mutable_appearance.dm
@@ -12,7 +12,20 @@
plane = FLOAT_PLANE
/// Helper similar to image()
-/proc/mutable_appearance(icon, icon_state = "", layer = FLOAT_LAYER, plane = FLOAT_PLANE, alpha = 255, appearance_flags = NONE)
+/proc/mutable_appearance(icon, icon_state = "", layer = FLOAT_LAYER, atom/offset_spokesman, plane = FLOAT_PLANE, alpha = 255, appearance_flags = NONE, offset_const)
+ if(plane != FLOAT_PLANE)
+ // Essentially, we allow users that only want one static offset to pass one in
+ if(!isnull(offset_const))
+ plane = GET_NEW_PLANE(plane, offset_const)
+ // That, or you need to pass in some non null object to reference
+ else if(!isnull(offset_spokesman))
+ // Note, we are ok with null turfs, that's not an error condition we'll just default to 0, the error would be
+ // Not passing ANYTHING in, key difference
+ var/turf/our_turf = get_turf(offset_spokesman)
+ plane = MUTATE_PLANE(plane, our_turf)
+ // otherwise if you're setting plane you better have the guts to back it up
+ else
+ stack_trace("No plane offset passed in as context for a non floating mutable appearance, ya done fucked up")
var/mutable_appearance/MA = new()
MA.icon = icon
MA.icon_state = icon_state
diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm
index 1ae4f03f8ac..16bc2b4d4a8 100644
--- a/code/datums/progressbar.dm
+++ b/code/datums/progressbar.dm
@@ -33,7 +33,7 @@
goal = goal_number
bar_loc = target
bar = image('icons/effects/progessbar.dmi', bar_loc, "prog_bar_0")
- bar.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(bar, ABOVE_HUD_PLANE, User)
bar.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
user = User
diff --git a/code/datums/proximity_monitor/fields/projectile_dampener.dm b/code/datums/proximity_monitor/fields/projectile_dampener.dm
index 37e9a2430d0..9645d135c77 100644
--- a/code/datums/proximity_monitor/fields/projectile_dampener.dm
+++ b/code/datums/proximity_monitor/fields/projectile_dampener.dm
@@ -45,9 +45,12 @@
effect.icon_state = overlay.icon_state
effect.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
effect.layer = ABOVE_ALL_MOB_LAYER
- effect.plane = ABOVE_GAME_PLANE
+ SET_PLANE(effect, ABOVE_GAME_PLANE, target)
LAZYSET(edgeturf_effects, target, effect)
+/datum/proximity_monitor/advanced/projectile_dampener/on_z_change(datum/source)
+ recalculate_field()
+
/datum/proximity_monitor/advanced/projectile_dampener/cleanup_edge_turf(turf/target)
. = ..()
var/obj/effect/abstract/effect = LAZYACCESS(edgeturf_effects, target)
diff --git a/code/datums/proximity_monitor/proximity_monitor.dm b/code/datums/proximity_monitor/proximity_monitor.dm
index db3482f9a5a..9911efe0c95 100644
--- a/code/datums/proximity_monitor/proximity_monitor.dm
+++ b/code/datums/proximity_monitor/proximity_monitor.dm
@@ -34,9 +34,10 @@
hasprox_receiver = new_host
host = new_host
RegisterSignal(new_host, COMSIG_PARENT_QDELETING, .proc/on_host_or_receiver_del)
- var/static/list/containers_connections = list(COMSIG_MOVABLE_MOVED = .proc/on_moved)
+ var/static/list/containers_connections = list(COMSIG_MOVABLE_MOVED = .proc/on_moved, COMSIG_MOVABLE_Z_CHANGED = .proc/on_z_change)
AddComponent(/datum/component/connect_containers, host, containers_connections)
RegisterSignal(host, COMSIG_MOVABLE_MOVED, .proc/on_moved)
+ RegisterSignal(host, COMSIG_MOVABLE_Z_CHANGED, .proc/on_z_change)
set_range(current_range, TRUE)
/datum/proximity_monitor/proc/on_host_or_receiver_del(datum/source)
@@ -62,6 +63,10 @@
if(source == host)
hasprox_receiver?.HasProximity(host)
+/datum/proximity_monitor/proc/on_z_change()
+ SIGNAL_HANDLER
+ return
+
/datum/proximity_monitor/proc/set_ignore_if_not_on_turf(does_ignore = TRUE)
if(ignore_if_not_on_turf == does_ignore)
return
diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm
index 6e794846b46..79a5d43ebcb 100644
--- a/code/datums/status_effects/debuffs/debuffs.dm
+++ b/code/datums/status_effects/debuffs/debuffs.dm
@@ -332,8 +332,8 @@
alert_type = null
/datum/status_effect/cultghost/on_apply()
- owner.see_invisible = SEE_INVISIBLE_OBSERVER
- owner.see_in_dark = 2
+ owner.set_invis_see(SEE_INVISIBLE_OBSERVER)
+ owner.set_see_in_dark(2)
/datum/status_effect/cultghost/tick()
if(owner.reagents)
diff --git a/code/datums/storage/storage.dm b/code/datums/storage/storage.dm
index 00974bdc23a..dc84ddb56fb 100644
--- a/code/datums/storage/storage.dm
+++ b/code/datums/storage/storage.dm
@@ -290,7 +290,7 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
/// Refreshes and item to be put back into the real world, out of storage.
/datum/storage/proc/reset_item(obj/item/thing)
thing.layer = initial(thing.layer)
- thing.plane = initial(thing.plane)
+ SET_PLANE_IMPLICIT(thing, initial(thing.plane))
thing.mouse_opacity = initial(thing.mouse_opacity)
thing.screen_loc = null
if(thing.maptext)
@@ -860,15 +860,17 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]"
var/current_x = screen_start_x
var/current_y = screen_start_y
+ var/turf/our_turf = get_turf(resolve_location)
if(islist(numerical_display_contents))
for(var/type in numerical_display_contents)
var/datum/numbered_display/numberdisplay = numerical_display_contents[type]
- numberdisplay.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE
- numberdisplay.sample_object.screen_loc = "[current_x]:[screen_pixel_x],[current_y]:[screen_pixel_y]"
- numberdisplay.sample_object.maptext = MAPTEXT("[(numberdisplay.number > 1)? "[numberdisplay.number]" : ""]")
- numberdisplay.sample_object.plane = ABOVE_HUD_PLANE
+ var/obj/item/display_sample = numberdisplay.sample_object
+ display_sample.mouse_opacity = MOUSE_OPACITY_OPAQUE
+ display_sample.screen_loc = "[current_x]:[screen_pixel_x],[current_y]:[screen_pixel_y]"
+ display_sample.maptext = MAPTEXT("[(numberdisplay.number > 1)? "[numberdisplay.number]" : ""]")
+ SET_PLANE(display_sample, ABOVE_HUD_PLANE, our_turf)
current_x++
@@ -885,6 +887,7 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
item.screen_loc = "[current_x]:[screen_pixel_x],[current_y]:[screen_pixel_y]"
item.maptext = ""
item.plane = ABOVE_HUD_PLANE
+ SET_PLANE(item, ABOVE_HUD_PLANE, our_turf)
current_x++
diff --git a/code/datums/visual_data.dm b/code/datums/visual_data.dm
new file mode 100644
index 00000000000..a09eedb50ae
--- /dev/null
+++ b/code/datums/visual_data.dm
@@ -0,0 +1,158 @@
+// Allows for linking one mob's view to another
+// Exists to make debugging stuff on live easier, please do not build gameplay around this it's not stable
+// Mostly because we don't have setters for everything (like ui elements IE: client.screen)
+
+// DEBUG ONLY, THIS IS N O T STABLE ENOUGH FOR PLAYERS
+// Should potentially support images, might be too hard tho since there's no default "refresh" tool
+
+// Convenience datum, not for use outside of this ui
+/datum/visual_data
+ /// Sight flags
+ var/sight = NONE
+ /// see_invisible values
+ var/see_invis
+ /// see_in_dark values
+ var/see_dark
+ /// What the client is seeing "out of", client.eye
+ var/datum/weakref/client_eye
+ /// Weakref to the mob we're mirroring off
+ var/datum/weakref/mirroring_off_ref
+
+ var/do_updates = FALSE
+
+ // Note: we do not attempt to mirror all of screen, instead confining ourselves to mirroring
+ // Plane master and parralax stuff and such
+ // Again, this isn't stable
+
+/datum/visual_data/proc/shadow(mob/mirror_off)
+ do_updates = FALSE
+ mirroring_off_ref = WEAKREF(mirror_off)
+ RegisterSignal(mirror_off, COMSIG_MOB_SIGHT_CHANGE, .proc/sight_changed)
+ sight_changed(mirror_off)
+ RegisterSignal(mirror_off, COMSIG_MOB_SEE_INVIS_CHANGE, .proc/invis_changed)
+ invis_changed(mirror_off)
+ RegisterSignal(mirror_off, COMSIG_MOB_SEE_IN_DARK_CHANGE, .proc/in_dark_changed)
+ in_dark_changed(mirror_off)
+ RegisterSignal(mirror_off, COMSIG_MOB_LOGIN, .proc/on_login)
+ RegisterSignal(mirror_off, COMSIG_MOB_LOGOUT, .proc/on_logout)
+ if(mirror_off.client)
+ on_login(mirror_off)
+ do_updates = TRUE
+
+/datum/visual_data/proc/paint_onto(mob/paint_to)
+ // Note: we explicitly do NOT use setters here, since it would break the behavior
+ paint_to.sight = sight
+ paint_to.see_invisible = see_invis
+ paint_to.see_in_dark = see_dark
+ if(paint_to.client)
+ var/atom/eye = client_eye?.resolve()
+ if(eye)
+ paint_to.client.eye = eye
+ // This is hacky I know, I don't have a way to update just
+ /// Plane masters. I'm sorry
+ var/mob/mirroring_off = mirroring_off_ref?.resolve()
+ if(mirroring_off?.client && paint_to != mirroring_off)
+ paint_to.client.screen = mirroring_off.client.screen
+
+/datum/visual_data/proc/on_update()
+ return
+
+/datum/visual_data/proc/sight_changed(mob/source)
+ SIGNAL_HANDLER
+ sight = source.sight
+ on_update()
+
+/datum/visual_data/proc/invis_changed(mob/source)
+ SIGNAL_HANDLER
+ see_invis = source.see_invisible
+ on_update()
+
+/datum/visual_data/proc/in_dark_changed(mob/source)
+ SIGNAL_HANDLER
+ see_dark = source.see_in_dark
+ on_update()
+
+/datum/visual_data/proc/on_login(mob/source)
+ SIGNAL_HANDLER
+ // visual data can be created off login, so conflicts here are inevitable
+ // Best to just override
+ RegisterSignal(source.client, COMSIG_CLIENT_SET_EYE, .proc/eye_change, override = TRUE)
+ set_eye(source.client.eye)
+
+/datum/visual_data/proc/on_logout(mob/source)
+ SIGNAL_HANDLER
+ // Canon here because it'll be gone come the logout signal
+ UnregisterSignal(source.canon_client, COMSIG_CLIENT_SET_EYE)
+ // We do NOT unset the eye, because it's still valid even if the mob ain't logged in
+
+/datum/visual_data/proc/eye_change(client/source)
+ SIGNAL_HANDLER
+ set_eye(source.eye)
+
+/datum/visual_data/proc/set_eye(atom/new_eye)
+ var/atom/old_eye = client_eye?.resolve()
+ if(old_eye)
+ UnregisterSignal(old_eye, COMSIG_PARENT_QDELETING)
+ if(new_eye)
+ // Need to update any party's client.eyes
+ RegisterSignal(new_eye, COMSIG_PARENT_QDELETING, .proc/eye_deleted)
+ client_eye = WEAKREF(new_eye)
+ on_update()
+
+/datum/visual_data/proc/eye_deleted(datum/source)
+ SIGNAL_HANDLER
+ set_eye(null)
+
+/// Tracks but does not relay updates to someone's visual data
+/// Accepts a second visual data datum to use as a source of truth for the mob's values
+/datum/visual_data/tracking
+ /// Weakref to the visual data datum to reset our mob to
+ var/datum/weakref/default_to_ref
+
+/datum/visual_data/tracking/Destroy()
+ var/mob/our_lad = mirroring_off_ref?.resolve()
+ if(our_lad)
+ // Reset our mob to his proper visuals
+ paint_onto(our_lad)
+ return ..()
+
+/datum/visual_data/tracking/proc/set_truth(datum/visual_data/truth)
+ default_to_ref = WEAKREF(truth)
+ on_update()
+
+/datum/visual_data/tracking/paint_onto(mob/paint_onto)
+ . = ..()
+ // Rebuild the passed in mob's screen, since we can't track it currently
+ paint_onto.hud_used?.show_hud(paint_onto.hud_used.hud_version)
+
+/datum/visual_data/tracking/on_update()
+ var/mob/updated = mirroring_off_ref?.resolve()
+ var/datum/visual_data/mirror = default_to_ref?.resolve()
+ if(!updated || !mirror)
+ return
+ mirror.paint_onto(updated)
+
+/// Tracks and updates another mob with our mob's visual data
+/datum/visual_data/mirroring
+ /// Weakref to what mob, if any, we should mirror our changes onto
+ var/datum/weakref/mirror_onto_ref
+
+/datum/visual_data/mirroring/proc/set_mirror_target(mob/target)
+ var/mob/old_target = mirror_onto_ref?.resolve()
+ if(old_target)
+ UnregisterSignal(old_target, COMSIG_MOB_HUD_REFRESHED)
+ mirror_onto_ref = WEAKREF(target)
+ if(target)
+ RegisterSignal(target, COMSIG_MOB_HUD_REFRESHED, .proc/push_ontod_hud_refreshed)
+
+/datum/visual_data/mirroring/proc/push_ontod_hud_refreshed(mob/source)
+ SIGNAL_HANDLER
+ // Our mob refreshed its hud, so we're gonna reset it to our screen
+ // I hate that I don't have a signal for this, hhhh
+ paint_onto(source)
+
+/datum/visual_data/mirroring/on_update()
+ var/mob/draw_onto = mirror_onto_ref?.resolve()
+ if(!draw_onto)
+ return
+ paint_onto(draw_onto)
diff --git a/code/datums/weather/weather.dm b/code/datums/weather/weather.dm
index 0cc4c9c91ba..6ef0905772b 100644
--- a/code/datums/weather/weather.dm
+++ b/code/datums/weather/weather.dm
@@ -67,7 +67,7 @@
/// If this bit of weather should also draw an overlay that's uneffected by lighting onto the area
/// Taken from weather_glow.dmi
var/use_glow = TRUE
- var/mutable_appearance/current_glow
+ var/list/offsets_to_overlays
/// The stage of the weather, from 1-4
var/stage = END_STAGE
@@ -239,26 +239,51 @@
if(END_STAGE)
using_icon_state = ""
- var/mutable_appearance/glow_overlay = mutable_appearance('icons/effects/glow_weather.dmi', using_icon_state, overlay_layer, ABOVE_LIGHTING_PLANE, 100)
+ // Note: what we do here is effectively apply two overlays to each area, for every unique multiz layer they inhabit
+ // One is the base, which will be masked by lighting. the other is "glowing", and provides a nice contrast
+ // This method of applying one overlay per z layer has some minor downsides, in that it could lead to improperly doubled effects if some have alpha
+ // I prefer it to creating 2 extra plane masters however, so it's a cost I'm willing to pay
+ // LU
+ var/list/new_offsets_to_overlays = list()
for(var/V in impacted_areas)
var/area/N = V
- if(current_glow)
- N.overlays -= current_glow
- if(stage == END_STAGE)
- N.color = null
- N.icon_state = using_icon_state
- N.icon = 'icons/area/areas_misc.dmi'
- N.layer = initial(N.layer)
- N.plane = initial(N.plane)
- N.set_opacity(FALSE)
- else
- N.layer = overlay_layer
- N.plane = overlay_plane
- N.icon = 'icons/effects/weather_effects.dmi'
- N.icon_state = using_icon_state
- N.color = weather_color
- if(use_glow)
- N.overlays += glow_overlay
- current_glow = glow_overlay
+ // List of overlays this area uses
+ var/list/mutable_appearance/overlays = list()
+ // Use all possible offsets
+ // Yes this is a bit annoying, but it's too slow to calculate and store these, and it shouldn't (I hope) look weird
+ for(var/offset in 0 to SSmapping.max_plane_offset)
+ var/keyd_offset = offset + 1
+ if(length(new_offsets_to_overlays) < keyd_offset)
+ new_offsets_to_overlays.len = keyd_offset
+ var/list/mutable_appearance/existing_appearances = new_offsets_to_overlays[keyd_offset]
+ if(existing_appearances)
+ overlays += existing_appearances
+ continue
+
+ var/list/offset_overlays = list()
+ var/mutable_appearance/glow_overlay = mutable_appearance('icons/effects/glow_weather.dmi', using_icon_state, overlay_layer, N, ABOVE_LIGHTING_PLANE, 100, offset_const = offset)
+ glow_overlay.color = weather_color
+ offset_overlays += glow_overlay
+
+ if(stage != END_STAGE)
+ var/mutable_appearance/weather_overlay = mutable_appearance('icons/effects/weather_effects.dmi', using_icon_state, overlay_layer, plane = overlay_plane, offset_const = offset)
+ weather_overlay.color = weather_color
+ offset_overlays += weather_overlay
+
+ new_offsets_to_overlays[keyd_offset] = offset_overlays
+ overlays += offset_overlays
+
+ var/list/mutable_appearance/old_glows = list()
+ // Offset (ha) by 1 to match the key
+ for(var/offset in 1 to SSmapping.max_plane_offset + 1)
+ if(length(offsets_to_overlays) >= offset)
+ old_glows += offsets_to_overlays[offset]
+
+ if(length(old_glows))
+ N.overlays -= old_glows
+ if(length(overlays))
+ N.overlays += overlays
+
+ offsets_to_overlays = new_offsets_to_overlays
diff --git a/code/game/alternate_appearance.dm b/code/game/alternate_appearance.dm
index f17315cdf26..28b7a4f5247 100644
--- a/code/game/alternate_appearance.dm
+++ b/code/game/alternate_appearance.dm
@@ -65,7 +65,7 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
var/atom/target
var/image/image
var/add_ghost_version = FALSE
- var/ghost_appearance
+ var/datum/atom_hud/alternate_appearance/basic/observers/ghost_appearance
uses_global_hud_category = FALSE
/datum/atom_hud/alternate_appearance/basic/New(key, image/I, options = AA_TARGET_SEE_APPEARANCE)
@@ -73,6 +73,7 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
transfer_overlays = options & AA_MATCH_TARGET_OVERLAYS
image = I
target = I.loc
+ LAZYADD(target.update_on_z, image)
if(transfer_overlays)
I.copy_overlays(target)
@@ -89,6 +90,7 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
/datum/atom_hud/alternate_appearance/basic/Destroy()
. = ..()
+ LAZYREMOVE(target.update_on_z, image)
QDEL_NULL(image)
target = null
if(ghost_appearance)
@@ -101,7 +103,7 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
/datum/atom_hud/alternate_appearance/basic/remove_atom_from_hud(atom/A)
. = ..()
- A.hud_list -= appearance_key
+ LAZYREMOVE(A.hud_list, appearance_key)
A.set_hud_image_inactive(appearance_key)
if(. && !QDELETED(src))
qdel(src)
diff --git a/code/game/area/areas/misc.dm b/code/game/area/areas/misc.dm
index 610662ac3f6..fd6d0452d84 100644
--- a/code/game/area/areas/misc.dm
+++ b/code/game/area/areas/misc.dm
@@ -5,8 +5,8 @@
requires_power = TRUE
always_unpowered = TRUE
static_lighting = FALSE
- area_has_base_lighting = FALSE
+ base_lighting_alpha = 255
power_light = FALSE
power_equip = FALSE
power_environ = FALSE
@@ -17,11 +17,6 @@
sound_environment = SOUND_AREA_SPACE
ambient_buzz = null //Space is deafeningly quiet
-/area/space/Initialize(mapload)
- . = ..()
-
- add_overlay(GLOB.fullbright_overlay)
-
/area/space/nearstation
icon_state = "space_near"
area_flags = UNIQUE_AREA | NO_ALERTS | AREA_USES_STARLIGHT
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index f4dec54d2ee..a1cee72a469 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -60,6 +60,10 @@
///overlays managed by [update_overlays][/atom/proc/update_overlays] to prevent removing overlays that weren't added by the same proc. Single items are stored on their own, not in a list.
var/list/managed_overlays
+ /// Lazylist of all images (hopefully attached to us) to update when we change z levels
+ /// You will need to manage adding/removing from this yourself, but I'll do the updating for you
+ var/list/image/update_on_z
+
///Cooldown tick timer for buckle messages
var/buckle_message_cooldown = 0
///Last fingerprints to touch this atom
@@ -235,6 +239,8 @@
if(loc)
SEND_SIGNAL(loc, COMSIG_ATOM_INITIALIZED_ON, src) /// Sends a signal that the new atom `src`, has been created at `loc`
+ SET_PLANE_IMPLICIT(src, plane)
+
if(greyscale_config && greyscale_colors)
update_greyscale()
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 5d995248ce2..1a9515399ec 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -99,7 +99,6 @@
/mutable_appearance/emissive_blocker/New()
. = ..()
// Need to do this here because it's overriden by the parent call
- plane = EMISSIVE_PLANE
color = EM_BLOCK_COLOR
appearance_flags = EMISSIVE_APPEARANCE_FLAGS
@@ -112,6 +111,7 @@
blocker.icon_state = icon_state
blocker.dir = dir
blocker.appearance_flags |= appearance_flags
+ blocker.plane = GET_NEW_PLANE(EMISSIVE_PLANE, PLANE_TO_OFFSET(plane))
// Ok so this is really cursed, but I want to set with this blocker cheaply while
// Still allowing it to be removed from the overlays list later
// So I'm gonna flatten it, then insert the flattened overlay into overlays AND the managed overlays list, directly
@@ -136,7 +136,6 @@
managed_overlays = list(managed_overlays, em_block)
else
managed_overlays = em_block
-
if(opacity)
AddElement(/datum/element/light_blocking)
switch(light_system)
@@ -202,7 +201,7 @@
if(!blocks_emissive)
return
else if (blocks_emissive == EMISSIVE_BLOCK_GENERIC)
- var/mutable_appearance/gen_emissive_blocker = emissive_blocker(icon, icon_state, alpha = src.alpha, appearance_flags = src.appearance_flags)
+ var/mutable_appearance/gen_emissive_blocker = emissive_blocker(icon, icon_state, src, alpha = src.alpha, appearance_flags = src.appearance_flags)
gen_emissive_blocker.dir = dir
return gen_emissive_blocker
else if(blocks_emissive == EMISSIVE_BLOCK_UNIQUE)
@@ -714,7 +713,8 @@
var/turf/new_turf = get_turf(src)
if (old_turf?.z != new_turf?.z)
- on_changed_z_level(old_turf, new_turf)
+ var/same_z_layer = (GET_TURF_PLANE_OFFSET(old_turf) == GET_TURF_PLANE_OFFSET(new_turf))
+ on_changed_z_level(old_turf, new_turf, same_z_layer)
if(HAS_SPATIAL_GRID_CONTENTS(src))
if(old_turf && new_turf && (old_turf.z != new_turf.z \
@@ -1031,17 +1031,31 @@
* Called when a movable changes z-levels.
*
* Arguments:
- * * old_z - The previous z-level they were on before.
- * * notify_contents - Whether or not to notify the movable's contents that their z-level has changed.
+ * * old_turf - The previous turf they were on before.
+ * * new_turf - The turf they have now entered.
+ * * same_z_layer - If their old and new z levels are on the same level of plane offsets or not
+ * * notify_contents - Whether or not to notify the movable's contents that their z-level has changed. NOTE, IF YOU SET THIS, YOU NEED TO MANUALLY SET PLANE OF THE CONTENTS LATER
*/
-/atom/movable/proc/on_changed_z_level(turf/old_turf, turf/new_turf, notify_contents = TRUE)
- SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_turf, new_turf)
+/atom/movable/proc/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents = TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_turf, new_turf, same_z_layer)
+
+ // If our turfs are on different z "layers", recalc our planes
+ if(!same_z_layer && !QDELETED(src))
+ SET_PLANE(src, PLANE_TO_TRUE(src.plane), new_turf)
+ // a TON of overlays use planes, and thus require offsets
+ // so we do this. sucks to suck
+ update_appearance()
+
+ // I so much wish this could be somewhere else. alas, no.
+ for(var/image/update in update_on_z)
+ SET_PLANE(update, PLANE_TO_TRUE(update.plane), new_turf)
if(!notify_contents)
return
for (var/atom/movable/content as anything in src) // Notify contents of Z-transition.
- content.on_changed_z_level(old_turf, new_turf)
+ content.on_changed_z_level(old_turf, new_turf, same_z_layer)
/**
* Called whenever an object moves and by mobs when they attempt to move themselves through space
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 4ed9fbea42c..20392d3cfc9 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -488,7 +488,9 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0)
GLOB.cameranet.removeCamera(src)
if (isarea(myarea))
LAZYREMOVE(myarea.cameras, src)
- GLOB.cameranet.updateChunk(x, y, z)
+ // We are not guarenteed that the camera will be on a turf. account for that
+ var/turf/our_turf = get_turf(src)
+ GLOB.cameranet.updateChunk(our_turf.x, our_turf.y, our_turf.z)
var/change_msg = "deactivates"
if(status)
change_msg = "reactivates"
@@ -532,10 +534,32 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0)
/obj/machinery/camera/proc/can_see()
var/list/see = null
var/turf/pos = get_turf(src)
+ var/check_lower = pos != get_lowest_turf(pos)
+ var/check_higher = pos != get_highest_turf(pos)
+
if(isXRay())
see = range(view_range, pos)
else
see = get_hear(view_range, pos)
+ if(check_lower || check_higher)
+ // Haha datum var access KILL ME
+ var/datum/controller/subsystem/mapping/local_mapping = SSmapping
+ for(var/turf/seen in see)
+ if(check_lower)
+ var/turf/visible = seen
+ while(visible && istransparentturf(visible))
+ var/turf/below = local_mapping.get_turf_below(visible)
+ for(var/turf/adjacent in range(1, below))
+ see += adjacent
+ see += adjacent.contents
+ visible = below
+ if(check_higher)
+ var/turf/above = local_mapping.get_turf_above(seen)
+ while(above && istransparentturf(above))
+ for(var/turf/adjacent in range(1, above))
+ see += adjacent
+ see += adjacent.contents
+ above = local_mapping.get_turf_above(above)
return see
/obj/machinery/camera/proc/Togglelight(on=0)
@@ -553,11 +577,12 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0)
user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 2)
/obj/machinery/camera/update_remote_sight(mob/living/user)
- user.see_invisible = SEE_INVISIBLE_LIVING //can't see ghosts through cameras
+ user.set_invis_see(SEE_INVISIBLE_LIVING) //can't see ghosts through cameras
if(isXRay())
- user.sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS)
- user.see_in_dark = max(user.see_in_dark, 8)
+ user.add_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ user.set_see_in_dark(max(user.see_in_dark, 8))
else
+ user.clear_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
user.sight = 0
- user.see_in_dark = 2
+ user.set_see_in_dark(2)
return 1
diff --git a/code/game/machinery/computer/_computer.dm b/code/game/machinery/computer/_computer.dm
index 4f7c1aaf081..0b328172f38 100644
--- a/code/game/machinery/computer/_computer.dm
+++ b/code/game/machinery/computer/_computer.dm
@@ -43,7 +43,7 @@
return
. += mutable_appearance(icon, icon_screen)
- . += emissive_appearance(icon, icon_screen)
+ . += emissive_appearance(icon, icon_screen, src)
/obj/machinery/computer/power_change()
. = ..()
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 90982d06e06..d42b17a9c5d 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -15,10 +15,8 @@
var/list/concurrent_users = list()
// Stuff needed to render the map
- var/map_name
var/atom/movable/screen/map_view/cam_screen
/// All the plane masters that need to be applied.
- var/list/cam_plane_masters
var/atom/movable/screen/background/cam_background
interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_SET_MACHINE|INTERACT_MACHINE_REQUIRES_SIGHT
@@ -28,33 +26,20 @@
// Map name has to start and end with an A-Z character,
// and definitely NOT with a square bracket or even a number.
// I wasted 6 hours on this. :agony:
- map_name = "camera_console_[REF(src)]_map"
+ var/map_name = "camera_console_[REF(src)]_map"
// Convert networks to lowercase
for(var/i in network)
network -= i
network += lowertext(i)
// Initialize map objects
cam_screen = new
- cam_screen.name = "screen"
- cam_screen.assigned_map = map_name
- cam_screen.del_on_map_removal = FALSE
- cam_screen.screen_loc = "[map_name]:1,1"
- cam_plane_masters = list()
- for(var/plane in subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/blackness)
- var/atom/movable/screen/plane_master/instance = new plane()
- if(instance.blend_mode_override)
- instance.blend_mode = instance.blend_mode_override
- instance.assigned_map = map_name
- instance.del_on_map_removal = FALSE
- instance.screen_loc = "[map_name]:CENTER"
- cam_plane_masters += instance
+ cam_screen.generate_view(map_name)
cam_background = new
cam_background.assigned_map = map_name
cam_background.del_on_map_removal = FALSE
/obj/machinery/computer/security/Destroy()
QDEL_NULL(cam_screen)
- QDEL_LIST(cam_plane_masters)
QDEL_NULL(cam_background)
return ..()
@@ -83,9 +68,7 @@
playsound(src, 'sound/machines/terminal_on.ogg', 25, FALSE)
use_power(active_power_usage)
// Register map objects
- user.client.register_map_obj(cam_screen)
- for(var/plane in cam_plane_masters)
- user.client.register_map_obj(plane)
+ cam_screen.display_to(user)
user.client.register_map_obj(cam_background)
// Open UI
ui = new(user, src, "CameraConsole", name)
@@ -110,7 +93,7 @@
/obj/machinery/computer/security/ui_static_data()
var/list/data = list()
- data["mapRef"] = map_name
+ data["mapRef"] = cam_screen.assigned_map
var/list/cameras = get_available_cameras()
data["cameras"] = list()
for(var/i in cameras)
@@ -181,7 +164,7 @@
// Living creature or not, we remove you anyway.
concurrent_users -= user_ref
// Unregister map objects
- user.client.clear_map(map_name)
+ cam_screen.hide_from(user)
// Turn off the console
if(length(concurrent_users) == 0 && is_living)
active_camera = null
diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm
index 69ace12caf4..c5149c06e11 100644
--- a/code/game/machinery/computer/camera_advanced.dm
+++ b/code/game/machinery/computer/camera_advanced.dm
@@ -92,6 +92,9 @@
user.remote_control = null
current_user = null
user.unset_machine()
+ var/atom/movable/screen/plane_master/plane_static = user.hud_used?.get_plane_master(CAMERA_STATIC_PLANE)
+ if(plane_static)
+ plane_static.hide_plane(user)
playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE)
/obj/machinery/computer/camera_advanced/check_eye(mob/user)
@@ -174,8 +177,12 @@
user.remote_control = eyeobj
user.reset_perspective(eyeobj)
eyeobj.setLoc(eyeobj.loc)
- if(should_supress_view_changes )
+ if(should_supress_view_changes)
user.client.view_size.supress()
+ // Who passes control like this god I hate static code
+ var/atom/movable/screen/plane_master/plane_static = user.hud_used?.get_plane_master(CAMERA_STATIC_PLANE)
+ if(plane_static)
+ plane_static.unhide_plane(user)
/mob/camera/ai_eye/remote
name = "Inactive Camera Eye"
@@ -190,9 +197,9 @@
var/image/user_image = null
/mob/camera/ai_eye/remote/update_remote_sight(mob/living/user)
- user.see_invisible = SEE_INVISIBLE_LIVING //can't see ghosts through cameras
- user.sight = SEE_TURFS | SEE_BLACKNESS
- user.see_in_dark = 2
+ user.set_invis_see(SEE_INVISIBLE_LIVING) //can't see ghosts through cameras
+ user.set_sight(SEE_TURFS)
+ user.set_see_in_dark(2)
return TRUE
/mob/camera/ai_eye/remote/Destroy()
@@ -224,7 +231,7 @@
if(eye_user.client)
eye_user.client.images -= user_image
user_image = image(icon,loc,icon_state, FLY_LAYER)
- user_image.plane = ABOVE_GAME_PLANE
+ SET_PLANE(user_image, ABOVE_GAME_PLANE, destination)
eye_user.client.images += user_image
/mob/camera/ai_eye/remote/relaymove(mob/living/user, direction)
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 80ab0fc64b3..ce45c12b17a 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -22,7 +22,7 @@
/// Someone, for the love of god, profile this. Is there a reason to cache mutable_appearance
/// if so, why are we JUST doing the airlocks when we can put this in mutable_appearance.dm for
/// everything
-/proc/get_airlock_overlay(icon_state, icon_file, em_block)
+/proc/get_airlock_overlay(icon_state, icon_file, atom/offset_spokesman, em_block)
var/static/list/airlock_overlays = list()
var/base_icon_key = "[icon_state][REF(icon_file)]"
@@ -31,10 +31,12 @@
if(isnull(em_block))
return
- var/em_block_key = "[base_icon_key][em_block]"
+ var/turf/our_turf = get_turf(offset_spokesman)
+
+ var/em_block_key = "[base_icon_key][em_block][GET_TURF_PLANE_OFFSET(our_turf)]"
var/mutable_appearance/em_blocker = airlock_overlays[em_block_key]
if(!em_blocker)
- em_blocker = airlock_overlays[em_block_key] = mutable_appearance(icon_file, icon_state, plane = EMISSIVE_PLANE, appearance_flags = EMISSIVE_APPEARANCE_FLAGS)
+ em_blocker = airlock_overlays[em_block_key] = mutable_appearance(icon_file, icon_state, offset_spokesman = offset_spokesman, plane = EMISSIVE_PLANE, appearance_flags = EMISSIVE_APPEARANCE_FLAGS)
em_blocker.color = em_block ? GLOB.em_block_color : GLOB.emissive_color
return list(., em_blocker)
@@ -500,38 +502,38 @@
frame_state = AIRLOCK_FRAME_OPENING
light_state = AIRLOCK_LIGHT_OPENING
- . += get_airlock_overlay(frame_state, icon, em_block = TRUE)
+ . += get_airlock_overlay(frame_state, icon, src, em_block = TRUE)
if(airlock_material)
- . += get_airlock_overlay("[airlock_material]_[frame_state]", overlays_file, em_block = TRUE)
+ . += get_airlock_overlay("[airlock_material]_[frame_state]", overlays_file, src, em_block = TRUE)
else
- . += get_airlock_overlay("fill_[frame_state]", icon, em_block = TRUE)
+ . += get_airlock_overlay("fill_[frame_state]", icon, src, em_block = TRUE)
if(lights && hasPower())
- . += get_airlock_overlay("lights_[light_state]", overlays_file, em_block = FALSE)
+ . += get_airlock_overlay("lights_[light_state]", overlays_file, src, em_block = FALSE)
if(panel_open)
- . += get_airlock_overlay("panel_[frame_state][security_level ? "_protected" : null]", overlays_file, em_block = TRUE)
+ . += get_airlock_overlay("panel_[frame_state][security_level ? "_protected" : null]", overlays_file, src, em_block = TRUE)
if(frame_state == AIRLOCK_FRAME_CLOSED && welded)
- . += get_airlock_overlay("welded", overlays_file, em_block = TRUE)
+ . += get_airlock_overlay("welded", overlays_file, src, em_block = TRUE)
if(airlock_state == AIRLOCK_EMAG)
- . += get_airlock_overlay("sparks", overlays_file, em_block = FALSE)
+ . += get_airlock_overlay("sparks", overlays_file, src, em_block = FALSE)
if(hasPower())
if(frame_state == AIRLOCK_FRAME_CLOSED)
if(atom_integrity < integrity_failure * max_integrity)
- . += get_airlock_overlay("sparks_broken", overlays_file, em_block = FALSE)
+ . += get_airlock_overlay("sparks_broken", overlays_file, src, em_block = FALSE)
else if(atom_integrity < (0.75 * max_integrity))
- . += get_airlock_overlay("sparks_damaged", overlays_file, em_block = FALSE)
+ . += get_airlock_overlay("sparks_damaged", overlays_file, src, em_block = FALSE)
else if(frame_state == AIRLOCK_FRAME_OPEN)
if(atom_integrity < (0.75 * max_integrity))
- . += get_airlock_overlay("sparks_open", overlays_file, em_block = FALSE)
+ . += get_airlock_overlay("sparks_open", overlays_file, src, em_block = FALSE)
if(note)
- . += get_airlock_overlay(get_note_state(frame_state), note_overlay_file, em_block = TRUE)
+ . += get_airlock_overlay(get_note_state(frame_state), note_overlay_file, src, em_block = TRUE)
if(frame_state == AIRLOCK_FRAME_CLOSED && seal)
- . += get_airlock_overlay("sealed", overlays_file, em_block = TRUE)
+ . += get_airlock_overlay("sealed", overlays_file, src, em_block = TRUE)
if(hasPower() && unres_sides)
for(var/heading in list(NORTH,SOUTH,EAST,WEST))
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index 80751b41783..923a5861cee 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -600,7 +600,7 @@
hazards.pixel_x = light_xoffset
hazards.pixel_y = light_yoffset
. += hazards
- hazards = emissive_appearance(icon, "[(obj_flags & EMAGGED) ? "firelock_alarm_type_emag" : alarm_type]", alpha = src.alpha)
+ hazards = emissive_appearance(icon, "[(obj_flags & EMAGGED) ? "firelock_alarm_type_emag" : alarm_type]", src, alpha = src.alpha)
hazards.pixel_x = light_xoffset
hazards.pixel_y = light_yoffset
. += hazards
diff --git a/code/game/machinery/ecto_sniffer.dm b/code/game/machinery/ecto_sniffer.dm
index 250e95446f0..a87e0307cf3 100644
--- a/code/game/machinery/ecto_sniffer.dm
+++ b/code/game/machinery/ecto_sniffer.dm
@@ -54,7 +54,7 @@
/obj/machinery/ecto_sniffer/update_overlays()
. = ..()
if(is_operational && on)
- . += emissive_appearance(icon, "[initial(icon_state)]-light-mask", alpha = src.alpha)
+ . += emissive_appearance(icon, "[initial(icon_state)]-light-mask", src, alpha = src.alpha)
/obj/machinery/ecto_sniffer/wrench_act(mob/living/user, obj/item/tool)
tool.play_tool_sound(src, 15)
diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm
index b008837ef64..d6f007600d0 100644
--- a/code/game/machinery/firealarm.dm
+++ b/code/game/machinery/firealarm.dm
@@ -129,25 +129,25 @@
. += mutable_appearance(icon, "fire_overlay")
if(is_station_level(z))
. += mutable_appearance(icon, "fire_[SSsecurity_level.get_current_level_as_number()]")
- . += emissive_appearance(icon, "fire_[SSsecurity_level.get_current_level_as_number()]", alpha = src.alpha)
+ . += emissive_appearance(icon, "fire_[SSsecurity_level.get_current_level_as_number()]", src, alpha = src.alpha)
else
. += mutable_appearance(icon, "fire_[SEC_LEVEL_GREEN]")
- . += emissive_appearance(icon, "fire_[SEC_LEVEL_GREEN]", alpha = src.alpha)
+ . += emissive_appearance(icon, "fire_[SEC_LEVEL_GREEN]", src, alpha = src.alpha)
if(!(my_area?.fire || LAZYLEN(my_area?.active_firelocks)))
if(my_area?.fire_detect) //If this is false, leave the green light missing. A good hint to anyone paying attention.
. += mutable_appearance(icon, "fire_off")
- . += emissive_appearance(icon, "fire_off", alpha = src.alpha)
+ . += emissive_appearance(icon, "fire_off", src, alpha = src.alpha)
else if(obj_flags & EMAGGED)
. += mutable_appearance(icon, "fire_emagged")
- . += emissive_appearance(icon, "fire_emagged", alpha = src.alpha)
+ . += emissive_appearance(icon, "fire_emagged", src, alpha = src.alpha)
else
. += mutable_appearance(icon, "fire_on")
- . += emissive_appearance(icon, "fire_on", alpha = src.alpha)
+ . += emissive_appearance(icon, "fire_on", src, alpha = src.alpha)
if(!panel_open && my_area?.fire_detect && my_area?.fire) //It just looks horrible with the panel open
. += mutable_appearance(icon, "fire_detected")
- . += emissive_appearance(icon, "fire_detected", alpha = src.alpha) //Pain
+ . += emissive_appearance(icon, "fire_detected", src, alpha = src.alpha) //Pain
/obj/machinery/firealarm/emp_act(severity)
. = ..()
diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm
index 96b67134849..3837ab687ce 100644
--- a/code/game/machinery/hologram.dm
+++ b/code/game/machinery/hologram.dm
@@ -90,7 +90,7 @@ Possible to do for anyone motivated enough:
/obj/machinery/holopad/Initialize(mapload)
. = ..()
/// We set the plane on mapload such that we can see the holopad render over atmospherics pipe and cabling in a map editor (without initialization), but so it gets that "inset" look in the floor in-game.
- plane = FLOOR_PLANE
+ SET_PLANE_IMPLICIT(src, FLOOR_PLANE)
update_appearance()
/obj/machinery/holopad/secure
@@ -531,7 +531,7 @@ Possible to do for anyone motivated enough:
Hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it.
Hologram.layer = FLY_LAYER //Above all the other objects/mobs. Or the vast majority of them.
- Hologram.plane = ABOVE_GAME_PLANE
+ SET_PLANE_EXPLICIT(Hologram, ABOVE_GAME_PLANE, src)
Hologram.set_anchored(TRUE)//So space wind cannot drag it.
Hologram.name = "[user.name] (Hologram)"//If someone decides to right click.
Hologram.set_light(2) //hologram lighting
@@ -672,6 +672,7 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
transfered = TRUE
//All is good.
holo.abstract_move(new_turf)
+ SET_PLANE(holo, ABOVE_GAME_PLANE, new_turf)
if(!transfered)
update_holoray(user,new_turf)
return TRUE
@@ -712,7 +713,7 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
holder.selected_language = record.language
Hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it.
Hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them.
- Hologram.plane = ABOVE_GAME_PLANE
+ SET_PLANE_EXPLICIT(Hologram, ABOVE_GAME_PLANE, src)
Hologram.set_anchored(TRUE)//So space wind cannot drag it.
Hologram.name = "[record.caller_name] (Hologram)"//If someone decides to right click.
Hologram.set_light(2) //hologram lighting
diff --git a/code/game/machinery/launch_pad.dm b/code/game/machinery/launch_pad.dm
index 2fe86a4dee2..a0a7b3a7c3c 100644
--- a/code/game/machinery/launch_pad.dm
+++ b/code/game/machinery/launch_pad.dm
@@ -37,13 +37,17 @@
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
diag_hud.add_atom_to_hud(src)
+ update_hud()
+
+/obj/machinery/launchpad/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer && !QDELETED(src))
+ update_hud()
+ return ..()
+
+/obj/machinery/launchpad/proc/update_hud()
var/image/holder = hud_list[DIAG_LAUNCHPAD_HUD]
- var/mutable_appearance/MA = new /mutable_appearance()
- MA.icon = 'icons/effects/effects.dmi'
- MA.icon_state = "launchpad_target"
- MA.layer = ABOVE_OPEN_TURF_LAYER
- MA.plane = GAME_PLANE
- holder.appearance = MA
+ var/mutable_appearance/target = mutable_appearance('icons/effects/effects.dmi', "launchpad_target", ABOVE_OPEN_TURF_LAYER, src, GAME_PLANE)
+ holder.appearance = target
update_indicator()
diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm
index 2c2975e50c6..522c15f8a8e 100644
--- a/code/game/machinery/lightswitch.dm
+++ b/code/game/machinery/lightswitch.dm
@@ -52,7 +52,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/light_switch, 26)
. = ..()
if(machine_stat & NOPOWER)
return ..()
- . += emissive_appearance(icon, "[base_icon_state]-emissive[area.lightswitch ? "-on" : "-off"]", alpha = src.alpha)
+ . += emissive_appearance(icon, "[base_icon_state]-emissive[area.lightswitch ? "-on" : "-off"]", src, alpha = src.alpha)
/obj/machinery/light_switch/examine(mob/user)
. = ..()
diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm
index 0d1b3a64c39..f3cf22d9f08 100644
--- a/code/game/machinery/navbeacon.dm
+++ b/code/game/machinery/navbeacon.dm
@@ -34,12 +34,12 @@
glob_lists_deregister()
return ..()
-/obj/machinery/navbeacon/on_changed_z_level(turf/old_turf, turf/new_turf)
+/obj/machinery/navbeacon/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
if (GLOB.navbeacons["[old_turf?.z]"])
GLOB.navbeacons["[old_turf?.z]"] -= src
if (GLOB.navbeacons["[new_turf?.z]"])
GLOB.navbeacons["[new_turf?.z]"] += src
- ..()
+ return ..()
// set the transponder codes assoc list from codes_txt
/obj/machinery/navbeacon/proc/set_codes()
diff --git a/code/game/machinery/newscaster/newscaster_machine.dm b/code/game/machinery/newscaster/newscaster_machine.dm
index 7d2f7f605ce..7b1490531ef 100644
--- a/code/game/machinery/newscaster/newscaster_machine.dm
+++ b/code/game/machinery/newscaster/newscaster_machine.dm
@@ -85,14 +85,15 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/newscaster, 30)
/obj/machinery/newscaster/update_overlays()
. = ..()
+
if(!(machine_stat & (NOPOWER|BROKEN)))
var/state = "[base_icon_state]_[GLOB.news_network.wanted_issue.active ? "wanted" : "normal"]"
. += mutable_appearance(icon, state)
- . += emissive_appearance(icon, state, alpha = src.alpha)
+ . += emissive_appearance(icon, state, src, alpha = src.alpha)
if(GLOB.news_network.wanted_issue.active && alert)
. += mutable_appearance(icon, "[base_icon_state]_alert")
- . += emissive_appearance(icon, "[base_icon_state]_alert", alpha = src.alpha)
+ . += emissive_appearance(icon, "[base_icon_state]_alert", src, alpha = src.alpha,)
var/hp_percent = atom_integrity * 100 / max_integrity
switch(hp_percent)
@@ -100,13 +101,13 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/newscaster, 30)
return
if(50 to 75)
. += "crack1"
- . += emissive_blocker(icon, "crack1", alpha = src.alpha)
+ . += emissive_blocker(icon, "crack1", src, alpha = src.alpha)
if(25 to 50)
. += "crack2"
- . += emissive_blocker(icon, "crack2", alpha = src.alpha)
+ . += emissive_blocker(icon, "crack2", src, alpha = src.alpha)
else
. += "crack3"
- . += emissive_blocker(icon, "crack3", alpha = src.alpha)
+ . += emissive_blocker(icon, "crack3", src, alpha = src.alpha)
/obj/machinery/newscaster/ui_interact(mob/user, datum/tgui/ui)
. = ..()
diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm
index 0cfd500f7c1..960088305af 100755
--- a/code/game/machinery/recharger.dm
+++ b/code/game/machinery/recharger.dm
@@ -189,13 +189,13 @@
if(!charging)
. += mutable_appearance(icon, "[base_icon_state]-empty", alpha = src.alpha)
- . += emissive_appearance(icon, "[base_icon_state]-empty", alpha = src.alpha)
+ . += emissive_appearance(icon, "[base_icon_state]-empty", src, alpha = src.alpha)
return
if(using_power)
. += mutable_appearance(icon, "[base_icon_state]-charging", alpha = src.alpha)
- . += emissive_appearance(icon, "[base_icon_state]-charging", alpha = src.alpha)
+ . += emissive_appearance(icon, "[base_icon_state]-charging", src, alpha = src.alpha)
return
. += mutable_appearance(icon, "[base_icon_state]-full", alpha = src.alpha)
- . += emissive_appearance(icon, "[base_icon_state]-full", alpha = src.alpha)
+ . += emissive_appearance(icon, "[base_icon_state]-full", src, alpha = src.alpha)
diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm
index 16b0af986be..8532b377462 100644
--- a/code/game/machinery/requests_console.dm
+++ b/code/game/machinery/requests_console.dm
@@ -109,7 +109,7 @@ GLOBAL_LIST_EMPTY(req_console_ckey_departments)
screen_state = "[base_icon_state]0"
. += mutable_appearance(icon, screen_state)
- . += emissive_appearance(icon, screen_state, alpha = src.alpha)
+ . += emissive_appearance(icon, screen_state, src, alpha = src.alpha)
/obj/machinery/requests_console/Initialize(mapload)
. = ..()
diff --git a/code/game/machinery/stasis.dm b/code/game/machinery/stasis.dm
index d3f7a5966eb..c519d0d24a3 100644
--- a/code/game/machinery/stasis.dm
+++ b/code/game/machinery/stasis.dm
@@ -88,6 +88,11 @@
var/easing_direction = _running ? EASE_OUT : EASE_IN
animate(mattress_on, alpha = new_alpha, time = 50, easing = CUBIC_EASING|easing_direction)
+/obj/machinery/stasis/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer)
+ return ..()
+ SET_PLANE(mattress_on, PLANE_TO_TRUE(mattress_on.plane), new_turf)
+ return ..()
/obj/machinery/stasis/atom_break(damage_flag)
. = ..()
diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm
index c97beae759d..5e7f335a53a 100644
--- a/code/game/machinery/status_display.dm
+++ b/code/game/machinery/status_display.dm
@@ -174,7 +174,7 @@
if(message1 == "" && message2 == "")
return
- . += emissive_appearance(icon, "outline", alpha = src.alpha)
+ . += emissive_appearance(icon, "outline", src, alpha = src.alpha)
// Timed process - performs nothing in the base class
/obj/machinery/status_display/process()
diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm
index 8d2ee355b1b..31df8c42e63 100644
--- a/code/game/objects/effects/anomalies.dm
+++ b/code/game/objects/effects/anomalies.dm
@@ -141,6 +141,12 @@
warp = null
return ..()
+/obj/effect/anomaly/grav/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ SET_PLANE(warp, PLANE_TO_TRUE(warp.plane), new_turf)
+
/obj/effect/anomaly/grav/anomalyEffect(delta_time)
..()
boing = 1
@@ -229,7 +235,7 @@
/obj/effect/anomaly/flux/update_overlays()
. = ..()
- . += emissive_appearance(icon, icon_state, alpha=src.alpha)
+ . += emissive_appearance(icon, icon_state, src, alpha=src.alpha)
/obj/effect/anomaly/flux/proc/on_entered(datum/source, atom/movable/AM)
SIGNAL_HANDLER
diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm
index bd17bb1297c..1bff969d368 100644
--- a/code/game/objects/effects/decals/cleanable/humans.dm
+++ b/code/game/objects/effects/decals/cleanable/humans.dm
@@ -72,6 +72,7 @@
/obj/effect/decal/cleanable/blood/splatter/over_window // special layer/plane set to appear on windows
layer = ABOVE_WINDOW_LAYER
plane = GAME_PLANE
+ vis_flags = VIS_INHERIT_PLANE
turf_loc_check = FALSE
alpha = 180
diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm
index 6317de810f1..5ade23af6cb 100644
--- a/code/game/objects/effects/decals/cleanable/misc.dm
+++ b/code/game/objects/effects/decals/cleanable/misc.dm
@@ -321,6 +321,8 @@
/obj/effect/decal/cleanable/ants/update_icon_state()
if(istype(src, /obj/effect/decal/cleanable/ants/fire)) //i fucking hate this but you're forced to call parent in update_icon_state()
return ..()
+ if(!(flags_1 & INITIALIZED_1))
+ return ..()
var/datum/component/caltrop/caltrop_comp = GetComponent(/datum/component/caltrop)
if(!caltrop_comp)
@@ -339,7 +341,7 @@
/obj/effect/decal/cleanable/ants/update_overlays()
. = ..()
- . += emissive_appearance(icon, "[icon_state]_light", alpha = src.alpha)
+ . += emissive_appearance(icon, "[icon_state]_light", src, alpha = src.alpha)
/obj/effect/decal/cleanable/ants/fire_act(exposed_temperature, exposed_volume)
var/obj/effect/decal/cleanable/ants/fire/fire_ants = new(loc)
diff --git a/code/game/objects/effects/effects.dm b/code/game/objects/effects/effects.dm
index 92ab7d3f0cb..b57402071a5 100644
--- a/code/game/objects/effects/effects.dm
+++ b/code/game/objects/effects/effects.dm
@@ -6,7 +6,6 @@
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
move_resist = INFINITY
obj_flags = NONE
- vis_flags = VIS_INHERIT_PLANE
blocks_emissive = EMISSIVE_BLOCK_GENERIC
/obj/effect/attackby(obj/item/weapon, mob/user, params)
diff --git a/code/game/objects/effects/overlays.dm b/code/game/objects/effects/overlays.dm
index aaa4da9bdd0..619071da278 100644
--- a/code/game/objects/effects/overlays.dm
+++ b/code/game/objects/effects/overlays.dm
@@ -65,8 +65,8 @@
appearance_flags = RESET_TRANSFORM | TILE_BOUND
invisibility = INVISIBILITY_ABSTRACT
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
- plane = ATMOS_GROUP_PLANE
+ vis_flags = VIS_INHERIT_PLANE
+ plane = HIGH_GAME_PLANE
/// Door overlay for animating closets
/obj/effect/overlay/closet_door
diff --git a/code/game/objects/effects/particle_holder.dm b/code/game/objects/effects/particle_holder.dm
index e6e8918a5e8..ac2641c5cca 100644
--- a/code/game/objects/effects/particle_holder.dm
+++ b/code/game/objects/effects/particle_holder.dm
@@ -4,6 +4,7 @@
anchored = TRUE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
layer = ABOVE_ALL_MOB_LAYER
+ vis_flags = VIS_INHERIT_PLANE
///typepath of the last location we're in, if it's different when moved then we need to update vis contents
var/last_attached_location_type
///the main item we're attached to at the moment, particle holders hold particles for something
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index 2eb26146604..c3f095b7b3d 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -21,7 +21,7 @@
if(SOUTH)
target_pixel_y = -16
layer = ABOVE_MOB_LAYER
- plane = GAME_PLANE_UPPER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
if(EAST)
target_pixel_x = 16
if(WEST)
@@ -36,12 +36,12 @@
target_pixel_x = 16
target_pixel_y = -16
layer = ABOVE_MOB_LAYER
- plane = GAME_PLANE_UPPER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
if(SOUTHWEST)
target_pixel_x = -16
target_pixel_y = -16
layer = ABOVE_MOB_LAYER
- plane = GAME_PLANE_UPPER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
animate(src, pixel_x = target_pixel_x, pixel_y = target_pixel_y, alpha = 0, time = duration)
/obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter
@@ -562,6 +562,7 @@
return INITIALIZE_HINT_QDEL
modsuit_image = image(icon = icon, loc = looker.loc, icon_state = real_icon_state, layer = ABOVE_ALL_MOB_LAYER, pixel_x = ((creature.x - looker.x) * 32), pixel_y = ((creature.y - looker.y) * 32))
modsuit_image.plane = ABOVE_LIGHTING_PLANE
+ SET_PLANE_EXPLICIT(modsuit_image, ABOVE_LIGHTING_PLANE, creature)
mod_man = WEAKREF(looker)
add_mind(looker)
diff --git a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm
index aa5c1f897c9..55b42dac00f 100644
--- a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm
+++ b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm
@@ -39,7 +39,7 @@
/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, new_loc, increment = 0)
var/mutable_appearance/look = new(src)
- look.plane = plane
+ SET_PLANE_EXPLICIT(look, plane, new_loc)
look.pixel_x = p_x
look.pixel_y = p_y
if(color_override)
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 9b228eefd4f..dffcfe4a104 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -1099,18 +1099,22 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
return 0
/obj/item/doMove(atom/destination)
- if (ismob(loc))
- var/mob/M = loc
- var/hand_index = M.get_held_index_of_item(src)
- if(hand_index)
- M.held_items[hand_index] = null
- M.update_held_items()
- if(M.client)
- M.client.screen -= src
- layer = initial(layer)
- plane = initial(plane)
- appearance_flags &= ~NO_CLIENT_COLOR
- dropped(M, FALSE)
+ if (!ismob(loc))
+ return ..()
+
+ var/mob/M = loc
+ var/hand_index = M.get_held_index_of_item(src)
+ if(!hand_index)
+ return ..()
+
+ M.held_items[hand_index] = null
+ M.update_held_items()
+ if(M.client)
+ M.client.screen -= src
+ layer = initial(layer)
+ SET_PLANE_IMPLICIT(src, initial(plane))
+ appearance_flags &= ~NO_CLIENT_COLOR
+ dropped(M, FALSE)
return ..()
/obj/item/proc/embedded(atom/embedded_target, obj/item/bodypart/part)
@@ -1348,7 +1352,7 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
if(!istype(loc, /turf))
return
var/image/pickup_animation = image(icon = src, loc = loc, layer = layer + 0.1)
- pickup_animation.plane = GAME_PLANE
+ SET_PLANE(pickup_animation, GAME_PLANE, loc)
pickup_animation.transform.Scale(0.75)
pickup_animation.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
diff --git a/code/game/objects/items/devices/camera_bug.dm b/code/game/objects/items/devices/camera_bug.dm
index 0fbe1116d03..f9432ae178b 100644
--- a/code/game/objects/items/devices/camera_bug.dm
+++ b/code/game/objects/items/devices/camera_bug.dm
@@ -148,9 +148,7 @@
return html
/obj/item/camera_bug/proc/get_seens()
- if(current?.can_use())
- var/list/seen = current.can_see()
- return seen
+ return current?.can_see()
/obj/item/camera_bug/proc/camera_report()
// this should only be called if current exists
diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm
index e7dd30a0d88..0c84d69f3ac 100644
--- a/code/game/objects/items/devices/chameleonproj.dm
+++ b/code/game/objects/items/devices/chameleonproj.dm
@@ -59,10 +59,10 @@
return
playsound(get_turf(src), 'sound/weapons/flash.ogg', 100, TRUE, -6)
to_chat(user, span_notice("Scanned [target]."))
- var/obj/temp = new/obj()
+ var/obj/temp = new /obj()
temp.appearance = target.appearance
temp.layer = initial(target.layer) // scanning things in your inventory
- temp.plane = initial(target.plane)
+ SET_PLANE_EXPLICIT(temp, initial(plane), src)
saved_appearance = temp.appearance
/obj/item/chameleon/proc/check_sprite(atom/target)
diff --git a/code/game/objects/items/devices/spyglasses.dm b/code/game/objects/items/devices/spyglasses.dm
index 58c9a286783..33110dac0dc 100644
--- a/code/game/objects/items/devices/spyglasses.dm
+++ b/code/game/objects/items/devices/spyglasses.dm
@@ -5,20 +5,23 @@
var/obj/item/clothing/accessory/spy_bug/linked_bug
/obj/item/clothing/glasses/sunglasses/spy/proc/show_to_user(mob/user)//this is the meat of it. most of the map_popup usage is in this.
- if(!user)
- return
- if(!user.client)
+ var/client/cool_guy = user?.client
+ if(!cool_guy)
return
if(!linked_bug)
user.audible_message(span_warning("[src] lets off a shrill beep!"))
- if(user.client.screen_maps["spypopup_map"]) //alright, the popup this object uses is already IN use, so the window is open. no point in doing any other work here, so we're good.
+ if(cool_guy.screen_maps["spypopup_map"]) //alright, the popup this object uses is already IN use, so the window is open. no point in doing any other work here, so we're good.
return
- user.client.setup_popup("spypopup", 3, 3, 2)
- user.client.register_map_obj(linked_bug.cam_screen)
- for(var/plane in linked_bug.cam_plane_masters)
- user.client.register_map_obj(plane)
+ cool_guy.setup_popup("spypopup", 3, 3, 2, "S.P.Y")
+ linked_bug.cam_screen.display_to(user)
+ RegisterSignal(cool_guy, COMSIG_POPUP_CLEARED, .proc/on_screen_clear)
+
linked_bug.update_view()
+/obj/item/clothing/glasses/sunglasses/spy/proc/on_screen_clear(client/source, window)
+ SIGNAL_HANDLER
+ linked_bug.cam_screen.hide_from(source.mob)
+
/obj/item/clothing/glasses/sunglasses/spy/equipped(mob/user, slot)
. = ..()
if(slot != ITEM_SLOT_EYES)
@@ -51,7 +54,6 @@
desc = "An advanced piece of espionage equipment in the shape of a pocket protector. It has a built in 360 degree camera for all your \"admirable\" needs. Microphone not included."
var/obj/item/clothing/glasses/sunglasses/spy/linked_glasses
var/atom/movable/screen/map_view/cam_screen
- var/list/cam_plane_masters
// Ranges higher than one can be used to see through walls.
var/cam_range = 1
var/datum/movement_detector/tracker
@@ -59,38 +61,19 @@
/obj/item/clothing/accessory/spy_bug/Initialize(mapload)
. = ..()
tracker = new /datum/movement_detector(src, CALLBACK(src, .proc/update_view))
-
cam_screen = new
- cam_screen.name = "screen"
- cam_screen.assigned_map = "spypopup_map"
- cam_screen.del_on_map_removal = FALSE
- cam_screen.set_position(1, 1)
-
- // We need to add planesmasters to the popup, otherwise
- // blending fucks up massively. Any planesmaster on the main screen does
- // NOT apply to map popups. If there's ever a way to make planesmasters
- // omnipresent, then this wouldn't be needed.
- cam_plane_masters = list()
- for(var/plane in subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/blackness)
- var/atom/movable/screen/plane_master/instance = new plane()
- if(instance.blend_mode_override)
- instance.blend_mode = instance.blend_mode_override
- instance.assigned_map = "spypopup_map"
- instance.del_on_map_removal = FALSE
- instance.screen_loc = "spypopup_map:CENTER"
- cam_plane_masters += instance
+ cam_screen.generate_view("spypopup_map")
/obj/item/clothing/accessory/spy_bug/Destroy()
if(linked_glasses)
linked_glasses.linked_bug = null
QDEL_NULL(cam_screen)
- QDEL_LIST(cam_plane_masters)
QDEL_NULL(tracker)
. = ..()
/obj/item/clothing/accessory/spy_bug/proc/update_view()//this doesn't do anything too crazy, just updates the vis_contents of its screen obj
cam_screen.vis_contents.Cut()
- for(var/turf/visible_turf in view(1,get_turf(src)))//fuck you usr
+ for(var/turf/visible_turf in view(cam_range, get_turf(src)))//fuck you usr
cam_screen.vis_contents += visible_turf
//it needs to be linked, hence a kit.
diff --git a/code/game/objects/items/food/deepfried.dm b/code/game/objects/items/food/deepfried.dm
index 0195bc41242..da83bf92aaf 100644
--- a/code/game/objects/items/food/deepfried.dm
+++ b/code/game/objects/items/food/deepfried.dm
@@ -26,7 +26,7 @@
name = fried.name //We'll determine the other stuff when it's actually removed
appearance = fried.appearance
layer = initial(layer)
- plane = initial(plane)
+ SET_PLANE_IMPLICIT(src, initial(plane))
lefthand_file = fried.lefthand_file
righthand_file = fried.righthand_file
inhand_icon_state = fried.inhand_icon_state
diff --git a/code/game/objects/items/puzzle_pieces.dm b/code/game/objects/items/puzzle_pieces.dm
index 4576e309c87..6b3e3496140 100644
--- a/code/game/objects/items/puzzle_pieces.dm
+++ b/code/game/objects/items/puzzle_pieces.dm
@@ -205,7 +205,7 @@
if(!light_list[i])
continue
var/mutable_appearance/lit_image = mutable_appearance('icons/obj/puzzle_small.dmi', "light_lit")
- var/mutable_appearance/emissive_image = emissive_appearance('icons/obj/puzzle_small.dmi', "light_lit")
+ var/mutable_appearance/emissive_image = emissive_appearance('icons/obj/puzzle_small.dmi', "light_lit", src)
lit_image.pixel_x = 8 * ((i % 3 || 3 ) - 1)
lit_image.pixel_y = -8 * (ROUND_UP(i / 3) - 1)
emissive_image.pixel_x = lit_image.pixel_x
diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm
index ff3a752be68..78f6362863f 100644
--- a/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -374,7 +374,7 @@
var/mutable_appearance/neon_overlay = mutable_appearance(neon_icon || icon, neon_icon_state || icon_state, alpha = alpha)
neon_overlay.color = neon_color
. += neon_overlay
- . += emissive_appearance(neon_icon || icon, neon_icon_state || icon_state, alpha = emissive_alpha)
+ . += emissive_appearance(neon_icon || icon, neon_icon_state || icon_state, src, alpha = emissive_alpha)
/obj/item/stack/tile/carpet/neon/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
@@ -384,7 +384,7 @@
var/mutable_appearance/neon_overlay = mutable_appearance(icon_file, neon_inhand_icon_state)
neon_overlay.color = neon_color
. += neon_overlay
- . += emissive_appearance(icon_file, neon_inhand_icon_state, alpha = emissive_alpha)
+ . += emissive_appearance(icon_file, neon_inhand_icon_state, src, alpha = emissive_alpha)
/obj/item/stack/tile/carpet/neon/simple
name = "simple neon carpet"
@@ -1157,11 +1157,11 @@
/obj/item/stack/tile/emissive_test/update_overlays()
. = ..()
- . += emissive_appearance(icon, icon_state, alpha = alpha)
+ . += emissive_appearance(icon, icon_state, src, alpha = alpha)
/obj/item/stack/tile/emissive_test/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
- . += emissive_appearance(standing.icon, standing.icon_state, alpha = standing.alpha)
+ . += emissive_appearance(standing.icon, standing.icon_state, src, alpha = standing.alpha)
/obj/item/stack/tile/emissive_test/sixty
amount = 60
diff --git a/code/game/objects/items/tcg/tcg.dm b/code/game/objects/items/tcg/tcg.dm
index 764cb48de1a..638365235cb 100644
--- a/code/game/objects/items/tcg/tcg.dm
+++ b/code/game/objects/items/tcg/tcg.dm
@@ -103,7 +103,7 @@ GLOBAL_LIST_EMPTY(tcgcard_radial_choices)
/obj/item/tcgcard/update_desc(updates)
. = ..()
if(!flipped)
- var/datum/card/template = SStrading_card_game.cached_cards[series]["ALL"][id]
+ var/datum/card/template = extract_datum()
desc = "[template.desc]"
else
desc = "It's the back of a trading card... no peeking!"
@@ -113,7 +113,9 @@ GLOBAL_LIST_EMPTY(tcgcard_radial_choices)
icon_state = "cardback"
return ..()
- var/datum/card/template = SStrading_card_game.cached_cards[series]["ALL"][id]
+ var/datum/card/template = extract_datum()
+ if(!template)
+ return
icon_state = template.icon_state
return ..()
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 76da75f42f0..2d32292c872 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -129,9 +129,9 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander/process()
if(ishuman(loc))
- var/mob/living/carbon/human/H = loc
- loc.plane = GAME_PLANE_UPPER_FOV_HIDDEN //NO HIDING BEHIND PLANTS FOR YOU, DICKWEED (HA GET IT, BECAUSE WEEDS ARE PLANTS)
- ADD_TRAIT(H, TRAIT_NOBLEED, HIGHLANDER_TRAIT) //AND WE WON'T BLEED OUT LIKE COWARDS
+ var/mob/living/carbon/human/holder = loc
+ SET_PLANE_EXPLICIT(holder, GAME_PLANE_UPPER_FOV_HIDDEN, src) //NO HIDING BEHIND PLANTS FOR YOU, DICKWEED (HA GET IT, BECAUSE WEEDS ARE PLANTS)
+ ADD_TRAIT(holder, TRAIT_NOBLEED, HIGHLANDER_TRAIT) //AND WE WON'T BLEED OUT LIKE COWARDS
else
if(!(flags_1 & ADMIN_SPAWNED_1))
qdel(src)
@@ -245,7 +245,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
return INITIALIZE_HINT_QDEL
/obj/item/claymore/highlander/robot/process()
- loc.plane = GAME_PLANE_UPPER_FOV_HIDDEN
+ SET_PLANE_IMPLICIT(loc, GAME_PLANE_UPPER_FOV_HIDDEN)
/obj/item/katana
name = "katana"
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 57f7d825089..cd96ec67ea6 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -30,8 +30,6 @@
var/drag_slowdown // Amont of multiplicative slowdown applied if pulled. >1 makes you slower, <1 makes you faster.
- vis_flags = VIS_INHERIT_PLANE //when this be added to vis_contents of something it inherit something.plane, important for visualisation of obj in openspace.
-
/// Map tag for something. Tired of it being used on snowflake items. Moved here for some semblance of a standard.
/// Next pr after the network fix will have me refactor door interactions, so help me god.
var/id_tag = null
diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm
index e617330bbff..1f9caf6fa0f 100644
--- a/code/game/objects/structures/beds_chairs/chair.dm
+++ b/code/game/objects/structures/beds_chairs/chair.dm
@@ -114,10 +114,10 @@
/obj/structure/chair/proc/handle_layer()
if(has_buckled_mobs() && dir == NORTH)
layer = ABOVE_MOB_LAYER
- plane = GAME_PLANE_UPPER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER_FOV_HIDDEN)
else
layer = OBJ_LAYER
- plane = GAME_PLANE
+ SET_PLANE_IMPLICIT(src, GAME_PLANE)
/obj/structure/chair/post_buckle_mob(mob/living/M)
. = ..()
@@ -170,10 +170,22 @@
var/mutable_appearance/armrest
/obj/structure/chair/comfy/Initialize(mapload)
+ gen_armrest()
+ return ..()
+
+/obj/structure/chair/comfy/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer)
+ return ..()
+ cut_overlay(armrest)
+ QDEL_NULL(armrest)
+ gen_armrest()
+ return ..()
+
+/obj/structure/chair/comfy/proc/gen_armrest()
armrest = GetArmrest()
armrest.layer = ABOVE_MOB_LAYER
- armrest.plane = GAME_PLANE_UPPER
- return ..()
+ SET_PLANE_EXPLICIT(armrest, GAME_PLANE_UPPER, src)
+ update_armrest()
/obj/structure/chair/comfy/proc/GetArmrest()
return mutable_appearance(icon, "[icon_state]_armrest")
diff --git a/code/game/objects/structures/beds_chairs/pew.dm b/code/game/objects/structures/beds_chairs/pew.dm
index db9dcacf173..535aadb698e 100644
--- a/code/game/objects/structures/beds_chairs/pew.dm
+++ b/code/game/objects/structures/beds_chairs/pew.dm
@@ -19,10 +19,23 @@
var/mutable_appearance/leftpewarmrest
/obj/structure/chair/pew/left/Initialize(mapload)
+ gen_armrest()
+ return ..()
+
+/obj/structure/chair/pew/left/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer)
+ return ..()
+ cut_overlay(leftpewarmrest)
+ QDEL_NULL(leftpewarmrest)
+ gen_armrest()
+ return ..()
+
+/obj/structure/chair/pew/left/proc/gen_armrest()
leftpewarmrest = GetLeftPewArmrest()
leftpewarmrest.layer = ABOVE_MOB_LAYER
- leftpewarmrest.plane = GAME_PLANE_UPPER
- return ..()
+ SET_PLANE_EXPLICIT(leftpewarmrest, GAME_PLANE_UPPER, src)
+ update_leftpewarmrest()
+
/obj/structure/chair/pew/left/proc/GetLeftPewArmrest()
return mutable_appearance('icons/obj/sofa.dmi', "pewend_left_armrest")
@@ -51,10 +64,20 @@
var/mutable_appearance/rightpewarmrest
/obj/structure/chair/pew/right/Initialize(mapload)
+ gen_armrest()
+ return ..()
+
+/obj/structure/chair/pew/right/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ cut_overlay(rightpewarmrest)
+ QDEL_NULL(rightpewarmrest)
+ gen_armrest()
+ return ..()
+
+/obj/structure/chair/pew/right/proc/gen_armrest()
rightpewarmrest = GetRightPewArmrest()
rightpewarmrest.layer = ABOVE_MOB_LAYER
- rightpewarmrest.plane = GAME_PLANE_UPPER
- return ..()
+ SET_PLANE_EXPLICIT(rightpewarmrest, GAME_PLANE_UPPER, src)
+ update_rightpewarmrest()
/obj/structure/chair/pew/right/proc/GetRightPewArmrest()
return mutable_appearance('icons/obj/sofa.dmi', "pewend_right_armrest")
diff --git a/code/game/objects/structures/beds_chairs/sofa.dm b/code/game/objects/structures/beds_chairs/sofa.dm
index 2e006e5c26b..3d934f19546 100644
--- a/code/game/objects/structures/beds_chairs/sofa.dm
+++ b/code/game/objects/structures/beds_chairs/sofa.dm
@@ -8,10 +8,22 @@
/obj/structure/chair/sofa/Initialize(mapload)
. = ..()
- armrest = mutable_appearance(initial(icon), "[icon_state]_armrest", ABOVE_MOB_LAYER)
- armrest.plane = GAME_PLANE_UPPER
+ gen_armrest()
AddElement(/datum/element/soft_landing)
+/obj/structure/chair/sofa/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer)
+ return ..()
+ cut_overlay(armrest)
+ QDEL_NULL(armrest)
+ gen_armrest()
+ return ..()
+
+/obj/structure/chair/sofa/proc/gen_armrest()
+ armrest = mutable_appearance(initial(icon), "[icon_state]_armrest", ABOVE_MOB_LAYER)
+ SET_PLANE_EXPLICIT(armrest, GAME_PLANE_UPPER, src)
+ update_armrest()
+
/obj/structure/chair/sofa/electrify_self(obj/item/assembly/shock_kit/input_shock_kit, mob/user, list/overlays_from_child_procs)
if(!overlays_from_child_procs)
overlays_from_child_procs = list(image('icons/obj/chairs.dmi', loc, "echair_over", pixel_x = -1))
diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm
index 95beabfd488..ca1a313fbc5 100644
--- a/code/game/objects/structures/bedsheet_bin.dm
+++ b/code/game/objects/structures/bedsheet_bin.dm
@@ -45,13 +45,13 @@ LINEN BINS
return
if(layer == initial(layer))
layer = ABOVE_MOB_LAYER
- plane = GAME_PLANE_UPPER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
to_chat(user, span_notice("You cover yourself with [src]."))
pixel_x = 0
pixel_y = 0
else
layer = initial(layer)
- plane = initial(plane)
+ SET_PLANE_IMPLICIT(src, initial(plane))
to_chat(user, span_notice("You smooth [src] out beneath you."))
if(user.body_position == LYING_DOWN) //The player isn't laying down currently
dir = user.dir
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index d935f292f98..cb6771c87ed 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -120,7 +120,7 @@
if(opened && has_opened_overlay)
var/mutable_appearance/door_overlay = mutable_appearance(icon, "[icon_state]_open", alpha = src.alpha)
. += door_overlay
- door_overlay.overlays += emissive_blocker(door_overlay.icon, door_overlay.icon_state, alpha = door_overlay.alpha) // If we don't do this the door doesn't block emissives and it looks weird.
+ door_overlay.overlays += emissive_blocker(door_overlay.icon, door_overlay.icon_state, src, alpha = door_overlay.alpha) // If we don't do this the door doesn't block emissives and it looks weird.
else if(has_closed_overlay)
. += "[icon_door || icon_state]_door"
@@ -133,7 +133,7 @@
if(broken || !secure)
return
//Overlay is similar enough for both that we can use the same mask for both
- . += emissive_appearance(icon, "locked", alpha = src.alpha)
+ . += emissive_appearance(icon, "locked", src, alpha = src.alpha)
. += locked ? "locked" : "unlocked"
/obj/structure/closet/vv_edit_var(vname, vval)
diff --git a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
index f7d3c88d96d..1c3f843b956 100644
--- a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
+++ b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
@@ -73,10 +73,17 @@
/// Does the MGS ! animation
/atom/proc/do_alert_animation()
var/image/alert_image = image('icons/obj/storage/closet.dmi', src, "cardboard_special", layer+1)
- alert_image.plane = ABOVE_LIGHTING_PLANE
- flick_overlay_view(alert_image, src, 8)
+ SET_PLANE_EXPLICIT(alert_image, ABOVE_LIGHTING_PLANE, src)
+ flick_overlay_view(alert_image, src, 0.8 SECONDS)
alert_image.alpha = 0
- animate(alert_image, pixel_z = 32, alpha = 255, time = 5, easing = ELASTIC_EASING)
+ animate(alert_image, pixel_z = 32, alpha = 255, time = 0.5 SECONDS, easing = ELASTIC_EASING)
+ // We use this list to update plane values on parent z change, which is why we need the timer too
+ // I'm sorry :(
+ LAZYADD(update_on_z, alert_image)
+ addtimer(CALLBACK(src, .proc/forget_alert_image, alert_image), 0.8 SECONDS)
+
+/atom/proc/forget_alert_image(image/alert_image)
+ LAZYREMOVE(update_on_z, alert_image)
/obj/structure/closet/cardboard/metal
name = "large metal box"
diff --git a/code/game/objects/structures/deployable_turret.dm b/code/game/objects/structures/deployable_turret.dm
index d6634307791..5faed760fab 100644
--- a/code/game/objects/structures/deployable_turret.dm
+++ b/code/game/objects/structures/deployable_turret.dm
@@ -93,7 +93,7 @@
M.put_in_hands(TC)
M.pixel_y = 14
layer = ABOVE_MOB_LAYER
- plane = GAME_PLANE_UPPER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
setDir(SOUTH)
playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE)
set_anchored(TRUE)
@@ -128,42 +128,43 @@
switch(dir)
if(NORTH)
layer = BELOW_MOB_LAYER
- plane = GAME_PLANE
+ SET_PLANE_IMPLICIT(src, GAME_PLANE)
user.pixel_x = 0
user.pixel_y = -14
if(NORTHEAST)
layer = BELOW_MOB_LAYER
- plane = GAME_PLANE
+ SET_PLANE_IMPLICIT(src, GAME_PLANE)
user.pixel_x = -8
user.pixel_y = -4
if(EAST)
layer = ABOVE_MOB_LAYER
- plane = GAME_PLANE_UPPER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
user.pixel_x = -14
user.pixel_y = 0
if(SOUTHEAST)
layer = BELOW_MOB_LAYER
- plane = GAME_PLANE
+ SET_PLANE_IMPLICIT(src, GAME_PLANE)
user.pixel_x = -8
user.pixel_y = 4
if(SOUTH)
layer = ABOVE_MOB_LAYER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
plane = GAME_PLANE_UPPER
user.pixel_x = 0
user.pixel_y = 14
if(SOUTHWEST)
layer = BELOW_MOB_LAYER
- plane = GAME_PLANE
+ SET_PLANE_IMPLICIT(src, GAME_PLANE)
user.pixel_x = 8
user.pixel_y = 4
if(WEST)
layer = ABOVE_MOB_LAYER
- plane = GAME_PLANE_UPPER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
user.pixel_x = 14
user.pixel_y = 0
if(NORTHWEST)
layer = BELOW_MOB_LAYER
- plane = GAME_PLANE
+ SET_PLANE_IMPLICIT(src, GAME_PLANE)
user.pixel_x = 8
user.pixel_y = -4
diff --git a/code/game/objects/structures/door_assembly.dm b/code/game/objects/structures/door_assembly.dm
index 3d0949151f9..80fd02425b3 100644
--- a/code/game/objects/structures/door_assembly.dm
+++ b/code/game/objects/structures/door_assembly.dm
@@ -295,10 +295,10 @@
/obj/structure/door_assembly/update_overlays()
. = ..()
if(!glass)
- . += get_airlock_overlay("fill_construction", icon, TRUE)
+ . += get_airlock_overlay("fill_construction", icon, src, TRUE)
else
- . += get_airlock_overlay("glass_construction", overlays_file, TRUE)
- . += get_airlock_overlay("panel_c[state+1]", overlays_file, TRUE)
+ . += get_airlock_overlay("glass_construction", overlays_file, src, TRUE)
+ . += get_airlock_overlay("panel_c[state+1]", overlays_file, src, TRUE)
/obj/structure/door_assembly/update_name()
name = ""
diff --git a/code/game/objects/structures/gym.dm b/code/game/objects/structures/gym.dm
index 58ae81b14f0..a6d594ccbda 100644
--- a/code/game/objects/structures/gym.dm
+++ b/code/game/objects/structures/gym.dm
@@ -38,7 +38,7 @@
. = ..()
if(obj_flags & IN_USE)
- . += mutable_appearance(icon, "[base_icon_state]-o", plane = GAME_PLANE_UPPER, layer = ABOVE_MOB_LAYER, alpha = src.alpha)
+ . += mutable_appearance(icon, "[base_icon_state]-o", offset_spokesman = src, plane = GAME_PLANE_UPPER, layer = ABOVE_MOB_LAYER, alpha = src.alpha)
/obj/structure/weightmachine/attack_hand(mob/living/user, list/modifiers)
. = ..()
diff --git a/code/game/objects/structures/holosign.dm b/code/game/objects/structures/holosign.dm
index d14e5263cac..317bc86b77c 100644
--- a/code/game/objects/structures/holosign.dm
+++ b/code/game/objects/structures/holosign.dm
@@ -12,9 +12,10 @@
/obj/structure/holosign/Initialize(mapload, source_projector)
. = ..()
+ var/turf/our_turf = get_turf(src)
if(use_vis_overlay)
alpha = 0
- SSvis_overlays.add_vis_overlay(src, icon, icon_state, ABOVE_MOB_LAYER, GAME_PLANE_UPPER, dir, add_appearance_flags = RESET_ALPHA) //you see mobs under it, but you hit them like they are above it
+ SSvis_overlays.add_vis_overlay(src, icon, icon_state, ABOVE_MOB_LAYER, MUTATE_PLANE(GAME_PLANE_UPPER, our_turf), dir, add_appearance_flags = RESET_ALPHA) //you see mobs under it, but you hit them like they are above it
if(source_projector)
projector = source_projector
LAZYADD(projector.signs, src)
diff --git a/code/game/objects/structures/plasticflaps.dm b/code/game/objects/structures/plasticflaps.dm
index 12e435b2932..80974a95890 100644
--- a/code/game/objects/structures/plasticflaps.dm
+++ b/code/game/objects/structures/plasticflaps.dm
@@ -15,7 +15,18 @@
/obj/structure/plasticflaps/Initialize(mapload)
. = ..()
alpha = 0
- SSvis_overlays.add_vis_overlay(src, icon, icon_state, ABOVE_MOB_LAYER, GAME_PLANE_UPPER, dir, add_appearance_flags = RESET_ALPHA) //you see mobs under it, but you hit them like they are above it
+ gen_overlay()
+
+/obj/structure/plasticflaps/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer)
+ return ..()
+ SSvis_overlays.remove_vis_overlay(managed_vis_overlays)
+ gen_overlay()
+ return ..()
+
+/obj/structure/plasticflaps/proc/gen_overlay()
+ var/turf/our_turf = get_turf(src)
+ SSvis_overlays.add_vis_overlay(src, icon, icon_state, ABOVE_MOB_LAYER, MUTATE_PLANE(GAME_PLANE_UPPER, our_turf), dir, add_appearance_flags = RESET_ALPHA) //you see mobs under it, but you hit them like they are above it
/obj/structure/plasticflaps/examine(mob/user)
. = ..()
diff --git a/code/game/objects/structures/shower.dm b/code/game/objects/structures/shower.dm
index c87d0745499..bb662570da5 100644
--- a/code/game/objects/structures/shower.dm
+++ b/code/game/objects/structures/shower.dm
@@ -197,7 +197,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/shower, (-16))
return
var/mutable_appearance/water_falling = mutable_appearance('icons/obj/watercloset.dmi', "water", ABOVE_MOB_LAYER)
water_falling.color = mix_color_from_reagents(reagents.reagent_list)
- water_falling.plane = GAME_PLANE_UPPER
+ SET_PLANE_EXPLICIT(water_falling, GAME_PLANE_UPPER, src)
switch(dir)
if(NORTH)
water_falling.pixel_y += pixel_shift
@@ -209,6 +209,12 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/shower, (-16))
water_falling.pixel_x -= pixel_shift
. += water_falling
+/obj/machinery/shower/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer)
+ return ..()
+ update_appearance()
+ return ..()
+
/obj/machinery/shower/proc/handle_mist()
// If there is no mist, and the shower was turned on (on a non-freezing temp): make mist in 5 seconds
// If there was already mist, and the shower was turned off (or made cold): remove the existing mist in 25 sec
diff --git a/code/game/objects/structures/training_machine.dm b/code/game/objects/structures/training_machine.dm
index 88367e9d8b0..76da32dc7e3 100644
--- a/code/game/objects/structures/training_machine.dm
+++ b/code/game/objects/structures/training_machine.dm
@@ -131,7 +131,7 @@
remove_attached_item()
attached_item = target
attached_item.forceMove(src)
- attached_item.vis_flags |= VIS_INHERIT_ID
+ attached_item.vis_flags |= VIS_INHERIT_ID | VIS_INHERIT_PLANE
vis_contents += attached_item
RegisterSignal(attached_item, COMSIG_PARENT_QDELETING, .proc/on_attached_delete)
handle_density()
@@ -145,6 +145,7 @@
SIGNAL_HANDLER
UnregisterSignal(attached_item, COMSIG_PARENT_QDELETING)
vis_contents -= attached_item
+ attached_item &= ~(VIS_INHERIT_ID | VIS_INHERIT_PLANE)
attached_item = null
handle_density()
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 8b19d9ac2b2..a701fe39896 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -726,12 +726,12 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink/kitchen, (-16))
open = !open
if(open)
layer = SIGN_LAYER
- plane = GAME_PLANE
+ SET_PLANE_IMPLICIT(src, GAME_PLANE)
set_density(FALSE)
set_opacity(FALSE)
else
layer = WALL_OBJ_LAYER
- plane = GAME_PLANE_UPPER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
set_density(TRUE)
if(opaque_closed)
set_opacity(TRUE)
@@ -828,7 +828,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink/kitchen, (-16))
/obj/structure/curtain/cloth/fancy/mechanical/proc/open()
icon_state = "[icon_type]-open"
layer = SIGN_LAYER
- plane = GAME_PLANE
+ SET_PLANE_IMPLICIT(src, GAME_PLANE)
set_density(FALSE)
open = TRUE
set_opacity(FALSE)
@@ -836,7 +836,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink/kitchen, (-16))
/obj/structure/curtain/cloth/fancy/mechanical/proc/close()
icon_state = "[icon_type]-closed"
layer = WALL_OBJ_LAYER
- plane = GAME_PLANE_UPPER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
set_density(TRUE)
open = FALSE
if(opaque_closed)
diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm
index a2dc2736381..3b89339ab32 100644
--- a/code/game/turfs/change_turf.dm
+++ b/code/game/turfs/change_turf.dm
@@ -78,6 +78,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
var/old_directional_opacity = directional_opacity
var/old_dynamic_lumcount = dynamic_lumcount
var/old_rcd_memory = rcd_memory
+ var/old_always_lit = always_lit
var/old_bp = blueprint_data
blueprint_data = null
@@ -125,9 +126,12 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
dynamic_lumcount = old_dynamic_lumcount
if(W.always_lit)
- W.add_overlay(GLOB.fullbright_overlay)
- else
- W.cut_overlay(GLOB.fullbright_overlay)
+ // We are guarenteed to have these overlays because of how generation works
+ var/mutable_appearance/overlay = GLOB.fullbright_overlays[GET_TURF_PLANE_OFFSET(src) + 1]
+ W.add_overlay(overlay)
+ else if (old_always_lit)
+ var/mutable_appearance/overlay = GLOB.fullbright_overlays[GET_TURF_PLANE_OFFSET(src) + 1]
+ W.cut_overlay(overlay)
if(SSlighting.initialized)
W.lighting_object = old_lighting_object
@@ -141,6 +145,12 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
for(var/turf/open/space/space_tile in RANGE_TURFS(1, src))
space_tile.update_starlight()
+ // We will only run this logic if the tile is not on the prime z layer, since we use area overlays to cover that
+ if(SSmapping.z_level_to_plane_offset[z])
+ var/area/thisarea = get_area(W)
+ if(thisarea.lighting_effects)
+ W.add_overlay(thisarea.lighting_effects[SSmapping.z_level_to_plane_offset[z]])
+
QUEUE_SMOOTH_NEIGHBORS(src)
QUEUE_SMOOTH(src)
diff --git a/code/game/turfs/closed/minerals.dm b/code/game/turfs/closed/minerals.dm
index 631f6e7ae93..dadaa690499 100644
--- a/code/game/turfs/closed/minerals.dm
+++ b/code/game/turfs/closed/minerals.dm
@@ -542,7 +542,7 @@
/turf/closed/mineral/gibtonite/proc/explosive_reaction(mob/user = null, triggered_by_explosion = 0)
if(stage == GIBTONITE_UNSTRUCK)
activated_overlay = mutable_appearance('icons/turf/smoothrocks.dmi', "rock_Gibtonite_inactive", ON_EDGED_TURF_LAYER) //shows in gaps between pulses if there are any
- activated_overlay.plane = GAME_PLANE_UPPER
+ SET_PLANE(activated_overlay, GAME_PLANE_UPPER, src)
add_overlay(activated_overlay)
name = "gibtonite deposit"
desc = "An active gibtonite reserve. Run!"
diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm
index 95035b269e1..887995232cc 100644
--- a/code/game/turfs/closed/walls.dm
+++ b/code/game/turfs/closed/walls.dm
@@ -40,11 +40,11 @@
if(is_station_level(z))
GLOB.station_turfs += src
if(smoothing_flags & SMOOTH_DIAGONAL_CORNERS && fixed_underlay) //Set underlays for the diagonal walls.
- var/mutable_appearance/underlay_appearance = mutable_appearance(layer = TURF_LAYER, plane = FLOOR_PLANE)
+ var/mutable_appearance/underlay_appearance = mutable_appearance(layer = TURF_LAYER, offset_spokesman = src, plane = FLOOR_PLANE)
if(fixed_underlay["space"])
underlay_appearance.icon = 'icons/turf/space.dmi'
underlay_appearance.icon_state = SPACE_ICON_STATE(x, y, z)
- underlay_appearance.plane = PLANE_SPACE
+ SET_PLANE(underlay_appearance, PLANE_SPACE, src)
else
underlay_appearance.icon = fixed_underlay["icon"]
underlay_appearance.icon_state = fixed_underlay["icon_state"]
diff --git a/code/game/turfs/open/floor/catwalk_plating.dm b/code/game/turfs/open/floor/catwalk_plating.dm
index 47b56ee9fc2..29dc92f0c08 100644
--- a/code/game/turfs/open/floor/catwalk_plating.dm
+++ b/code/game/turfs/open/floor/catwalk_plating.dm
@@ -44,12 +44,12 @@
if(!covered)
underfloor_accessibility = UNDERFLOOR_INTERACTABLE
layer = TURF_LAYER
- plane = FLOOR_PLANE
+ SET_PLANE_IMPLICIT(src, FLOOR_PLANE)
icon_state = "[catwalk_type]_below"
else
underfloor_accessibility = UNDERFLOOR_VISIBLE
layer = CATWALK_LAYER
- plane = GAME_PLANE
+ SET_PLANE_IMPLICIT(src, GAME_PLANE)
icon_state = "[catwalk_type]_above"
user.balloon_alert(user, "[!covered ? "cover removed" : "cover added"]")
tool.play_tool_sound(src)
diff --git a/code/game/turfs/open/floor/fancy_floor.dm b/code/game/turfs/open/floor/fancy_floor.dm
index e4b109d12c2..d3d30806319 100644
--- a/code/game/turfs/open/floor/fancy_floor.dm
+++ b/code/game/turfs/open/floor/fancy_floor.dm
@@ -408,7 +408,7 @@
/turf/open/floor/emissive_test/update_overlays()
. = ..()
- . += emissive_appearance(icon, icon_state, alpha = src.alpha)
+ . += emissive_appearance(icon, icon_state, src, alpha = src.alpha)
/turf/open/floor/emissive_test/white
icon_state = "pure_white"
@@ -800,5 +800,5 @@
/turf/open/floor/fakespace/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
underlay_appearance.icon = 'icons/turf/space.dmi'
underlay_appearance.icon_state = SPACE_ICON_STATE(x, y, z)
- underlay_appearance.plane = PLANE_SPACE
+ SET_PLANE(underlay_appearance, PLANE_SPACE, src)
return TRUE
diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm
index 834ed9895c5..265508e1bc9 100644
--- a/code/game/turfs/open/openspace.dm
+++ b/code/game/turfs/open/openspace.dm
@@ -1,16 +1,3 @@
-GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdrop, new)
-
-/atom/movable/openspace_backdrop
- name = "openspace_backdrop"
-
- anchored = TRUE
-
- icon = 'icons/turf/floors.dmi'
- icon_state = "grey"
- plane = OPENSPACE_BACKDROP_PLANE
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- vis_flags = VIS_INHERIT_ID
-
/turf/open/openspace
name = "open space"
desc = "Watch your step!"
@@ -31,7 +18,6 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr
/turf/open/openspace/Initialize(mapload) // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker
. = ..()
- overlays += GLOB.openspace_backdrop_one_for_all //Special grey square for projecting backdrop darkness filter on it.
RegisterSignal(src, COMSIG_ATOM_INITIALIZED_ON, .proc/on_atom_created)
var/area/our_area = loc
if(istype(our_area, /area/space))
@@ -40,7 +26,7 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr
/turf/open/openspace/LateInitialize()
. = ..()
- AddElement(/datum/element/turf_z_transparency, is_openspace = TRUE)
+ AddElement(/datum/element/turf_z_transparency)
/turf/open/openspace/ChangeTurf(path, list/new_baseturfs, flags)
UnregisterSignal(src, COMSIG_ATOM_INITIALIZED_ON)
diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm
index 567894543d3..79b1051dd14 100644
--- a/code/game/turfs/open/space/space.dm
+++ b/code/game/turfs/open/space/space.dm
@@ -27,6 +27,7 @@
force_no_gravity = TRUE
/turf/open/space/basic/New() //Do not convert to Initialize
+ SHOULD_CALL_PARENT(FALSE)
//This is used to optimize the map loader
return
@@ -51,23 +52,29 @@
stack_trace("Warning: [src]([type]) initialized multiple times!")
flags_1 |= INITIALIZED_1
+
+ // We make the assumption that the space plane will never be blacklisted, as an optimization
+ if(SSmapping.max_plane_offset)
+ plane = PLANE_SPACE - (PLANE_RANGE * SSmapping.z_level_to_plane_offset[z])
+
var/area/our_area = loc
- if(our_area.area_has_base_lighting && always_lit) //Only provide your own lighting if the area doesn't for you
+ if(!our_area.area_has_base_lighting && always_lit) //Only provide your own lighting if the area doesn't for you
// Intentionally not add_overlay for performance reasons.
// add_overlay does a bunch of generic stuff, like creating a new list for overlays,
// queueing compile, cloning appearance, etc etc etc that is not necessary here.
- overlays += GLOB.fullbright_overlay
+ overlays += GLOB.fullbright_overlays[GET_TURF_PLANE_OFFSET(src) + 1]
if (!mapload)
if(requires_activation)
SSair.add_to_active(src, TRUE)
- var/turf/T = SSmapping.get_turf_above(src)
- if(T)
- T.multiz_turf_new(src, DOWN)
- T = SSmapping.get_turf_below(src)
- if(T)
- T.multiz_turf_new(src, UP)
+ if(SSmapping.max_plane_offset)
+ var/turf/T = SSmapping.get_turf_above(src)
+ if(T)
+ T.multiz_turf_new(src, DOWN)
+ T = SSmapping.get_turf_below(src)
+ if(T)
+ T.multiz_turf_new(src, UP)
return INITIALIZE_HINT_NORMAL
@@ -177,7 +184,7 @@
/turf/open/space/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
underlay_appearance.icon = 'icons/turf/space.dmi'
underlay_appearance.icon_state = SPACE_ICON_STATE(x, y, z)
- underlay_appearance.plane = PLANE_SPACE
+ SET_PLANE(underlay_appearance, PLANE_SPACE, src)
return TRUE
@@ -217,16 +224,16 @@
/turf/open/space/openspace
icon = 'icons/turf/floors.dmi'
icon_state = "invisible"
+ plane = FLOOR_PLANE
/turf/open/space/openspace/Initialize(mapload) // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker
. = ..()
- overlays += GLOB.openspace_backdrop_one_for_all //Special grey square for projecting backdrop darkness filter on it.
icon_state = "invisible"
return INITIALIZE_HINT_LATELOAD
/turf/open/space/openspace/LateInitialize()
. = ..()
- AddElement(/datum/element/turf_z_transparency, is_openspace = TRUE)
+ AddElement(/datum/element/turf_z_transparency)
/turf/open/space/openspace/zAirIn()
return TRUE
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 07ceaa284db..f96f30a4518 100755
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -3,7 +3,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
/// Any floor or wall. What makes up the station and the rest of the map.
/turf
icon = 'icons/turf/floors.dmi'
- vis_flags = VIS_INHERIT_ID | VIS_INHERIT_PLANE// Important for interaction with and visualization of openspace.
+ vis_flags = VIS_INHERIT_ID // Important for interaction with and visualization of openspace.
luminosity = 1
/// Turf bitflags, see code/__DEFINES/flags.dm
@@ -105,6 +105,18 @@ GLOBAL_LIST_EMPTY(station_turfs)
stack_trace("Warning: [src]([type]) initialized multiple times!")
flags_1 |= INITIALIZED_1
+ /// We do NOT use the shortcut here, because this is faster
+ if(SSmapping.max_plane_offset)
+ if(!SSmapping.plane_offset_blacklist["[plane]"])
+ plane = plane - (PLANE_RANGE * SSmapping.z_level_to_plane_offset[z])
+
+ var/turf/T = SSmapping.get_turf_above(src)
+ if(T)
+ T.multiz_turf_new(src, DOWN)
+ T = SSmapping.get_turf_below(src)
+ if(T)
+ T.multiz_turf_new(src, UP)
+
// by default, vis_contents is inherited from the turf that was here before
vis_contents.Cut()
@@ -137,8 +149,9 @@ GLOBAL_LIST_EMPTY(station_turfs)
Entered(content, null)
var/area/our_area = loc
- if(our_area.area_has_base_lighting && always_lit) //Only provide your own lighting if the area doesn't for you
- add_overlay(GLOB.fullbright_overlay)
+ if(!our_area.area_has_base_lighting && always_lit) //Only provide your own lighting if the area doesn't for you
+ var/mutable_appearance/overlay = GLOB.fullbright_overlays[GET_TURF_PLANE_OFFSET(src) + 1]
+ add_overlay(overlay)
if(requires_activation)
CALCULATE_ADJACENT_TURFS(src, KILL_EXCITED)
@@ -146,13 +159,6 @@ GLOBAL_LIST_EMPTY(station_turfs)
if (light_power && light_range)
update_light()
- var/turf/T = SSmapping.get_turf_above(src)
- if(T)
- T.multiz_turf_new(src, DOWN)
- T = SSmapping.get_turf_below(src)
- if(T)
- T.multiz_turf_new(src, UP)
-
if (opacity)
directional_opacity = ALL_CARDINALS
@@ -540,7 +546,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
/turf/proc/add_blueprints(atom/movable/AM)
var/image/I = new
- I.plane = GAME_PLANE
+ SET_PLANE(I, GAME_PLANE, src)
I.layer = OBJ_LAYER
I.appearance = AM.appearance
I.appearance_flags = RESET_COLOR|RESET_ALPHA|RESET_TRANSFORM
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 912f056518c..cc05426c4ed 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -169,6 +169,7 @@ GLOBAL_PROTECT(admin_verbs_debug)
/client/proc/cmd_display_del_log,
/client/proc/outfit_manager,
/client/proc/open_colorblind_test,
+ /client/proc/debug_plane_masters,
/client/proc/generate_wikichem_list,
/client/proc/modify_goals,
/client/proc/debug_huds,
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index a96c1e86e0f..a6ef671a472 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -36,6 +36,7 @@ GLOBAL_PROTECT(href_token)
var/datum/filter_editor/filteriffic
var/datum/colorblind_tester/color_test = new
+ var/datum/plane_master_debug/plane_debug
/// Whether or not the user tried to connect, but was blocked by 2FA
var/blocked_by_2fa = FALSE
@@ -76,6 +77,7 @@ GLOBAL_PROTECT(href_token)
activate()
else
deactivate()
+ plane_debug = new(src)
/datum/admins/Destroy()
if(IsAdminAdvancedProcCall())
@@ -94,6 +96,7 @@ GLOBAL_PROTECT(href_token)
GLOB.deadmins -= target
GLOB.admin_datums[target] = src
deadmined = FALSE
+ QDEL_NULL(plane_debug)
if (GLOB.directory[target])
associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us
diff --git a/code/modules/admin/smites/bread.dm b/code/modules/admin/smites/bread.dm
index 2bdd34155e1..ae33bdb566a 100644
--- a/code/modules/admin/smites/bread.dm
+++ b/code/modules/admin/smites/bread.dm
@@ -8,7 +8,7 @@
. = ..()
var/mutable_appearance/bread_appearance = mutable_appearance('icons/obj/food/burgerbread.dmi', "bread")
var/mutable_appearance/transform_scanline = mutable_appearance('icons/effects/effects.dmi', "transform_effect")
- target.transformation_animation(bread_appearance,time = BREADIFY_TIME, transform_overlay=transform_scanline, reset_after=TRUE)
+ target.transformation_animation(bread_appearance,time = BREADIFY_TIME, transform_overlay=transform_scanline)
addtimer(CALLBACK(GLOBAL_PROC, .proc/breadify, target), BREADIFY_TIME)
#undef BREADIFY_TIME
diff --git a/code/modules/admin/verbs/color_blind_test.dm b/code/modules/admin/verbs/color_blind_test.dm
index 5640149cd0a..45adb5e8ce9 100644
--- a/code/modules/admin/verbs/color_blind_test.dm
+++ b/code/modules/admin/verbs/color_blind_test.dm
@@ -55,19 +55,20 @@
return TRUE
/datum/colorblind_tester/proc/set_selected_type(selected, datum/hud/remove_from)
- var/atom/movable/plane_master_controller/colorblind_plane = remove_from.plane_master_controllers[PLANE_MASTERS_COLORBLIND]
+ var/atom/movable/plane_master_controller/colorblind_planes = remove_from.plane_master_controllers[PLANE_MASTERS_COLORBLIND]
// This is dumb, but well
// The parralax plane has a blend mode of 4, or BLEND_MULTIPLY
// It's like that so it is properly masked by darkness and such
// The problem is blend modes apply to filters like this too
// So I need to manually set and reset its blendmode to allow for proper shading of the background
// Sorry...
- var/atom/movable/screen/plane_master/parralax = colorblind_plane.controlled_planes["[PLANE_SPACE_PARALLAX]"]
if(selected_type)
- colorblind_plane.remove_filter(selected_type)
- parralax.blend_mode = initial(parralax.blend_mode)
+ colorblind_planes.remove_filter(selected_type)
+ for(var/atom/movable/screen/plane_master/parralax as anything in remove_from.get_true_plane_masters(PLANE_SPACE_PARALLAX))
+ parralax.blend_mode = initial(parralax.blend_mode)
selected_type = selected
if(selected_type)
var/list/matrix = color_matrixes[selected_type]
- colorblind_plane.add_filter(selected_type, 0, color_matrix_filter(matrix))
- parralax.blend_mode = BLEND_DEFAULT
+ colorblind_planes.add_filter(selected_type, 0, color_matrix_filter(matrix))
+ for(var/atom/movable/screen/plane_master/parralax as anything in remove_from.get_true_plane_masters(PLANE_SPACE_PARALLAX))
+ parralax.blend_mode = BLEND_DEFAULT
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index b63e55d0e7e..d00092d4e8a 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -631,6 +631,23 @@
return
holder.color_test.ui_interact(mob)
+/client/proc/debug_plane_masters()
+ set category = "Debug"
+ set name = "Edit/Debug Planes"
+ set desc = "Edit and visualize plane masters and their connections (relays)"
+
+ edit_plane_masters()
+
+/client/proc/edit_plane_masters(mob/debug_on)
+ if(!holder)
+ return
+ if(debug_on)
+ holder.plane_debug.set_mirroring(TRUE)
+ holder.plane_debug.set_target(debug_on)
+ else
+ holder.plane_debug.set_mirroring(FALSE)
+ holder.plane_debug.ui_interact(mob)
+
/client/proc/debug_huds(i as num)
set category = "Debug"
set name = "Debug HUDs"
diff --git a/code/modules/admin/verbs/map_template_loadverb.dm b/code/modules/admin/verbs/map_template_loadverb.dm
index 8e773a05c69..a772f699992 100644
--- a/code/modules/admin/verbs/map_template_loadverb.dm
+++ b/code/modules/admin/verbs/map_template_loadverb.dm
@@ -23,9 +23,9 @@
center = FALSE
else
return
- for(var/S in template.get_affected_turfs(T,centered = center))
- var/image/item = image('icons/turf/overlays.dmi',S,"greenOverlay")
- item.plane = ABOVE_LIGHTING_PLANE
+ for(var/turf/place_on as anything in template.get_affected_turfs(T,centered = center))
+ var/image/item = image('icons/turf/overlays.dmi', place_on,"greenOverlay")
+ SET_PLANE(item, ABOVE_LIGHTING_PLANE, place_on)
preview += item
images += preview
if(tgui_alert(usr,"Confirm location.","Template Confirm",list("Yes","No")) == "Yes")
diff --git a/code/modules/admin/verbs/plane_debugger.dm b/code/modules/admin/verbs/plane_debugger.dm
new file mode 100644
index 00000000000..d1a613449a6
--- /dev/null
+++ b/code/modules/admin/verbs/plane_debugger.dm
@@ -0,0 +1,400 @@
+/// Used for testing/debugger plane masters and their associated rendering plates
+/datum/plane_master_debug
+ var/datum/admins/owner
+ /// Assoc list of plane master group key -> its depth stack
+ var/list/depth_stack = list()
+ /// The current plane master group we're viewing
+ var/current_group = PLANE_GROUP_MAIN
+ /// Weakref to the mob to edit
+ var/datum/weakref/mob_ref
+
+ var/datum/visual_data/tracking/stored
+ var/datum/visual_data/mirroring/mirror
+ /// If we are actively mirroring the target of our current ui
+ var/mirror_target = FALSE
+
+/datum/plane_master_debug/New(datum/admins/owner)
+ src.owner = owner
+
+/datum/plane_master_debug/Destroy()
+ if(owner)
+ owner.plane_debug = null
+ owner = null
+ return ..()
+
+/datum/plane_master_debug/proc/set_target(mob/new_mob)
+ QDEL_NULL(mirror)
+ QDEL_NULL(stored)
+
+ depth_stack = list()
+ if(!new_mob?.hud_used)
+ new_mob = owner.owner?.mob
+
+ mob_ref = WEAKREF(new_mob)
+
+ if(!mirror_target)
+ UnregisterSignal(owner.owner.mob, COMSIG_MOB_LOGOUT)
+ return
+
+ RegisterSignal(owner.owner.mob, COMSIG_MOB_LOGOUT, .proc/on_our_logout, override = TRUE)
+ mirror = new()
+ mirror.shadow(new_mob)
+
+ if(new_mob == owner.owner.mob)
+ return
+
+ create_store()
+
+/datum/plane_master_debug/proc/on_our_logout(mob/source)
+ SIGNAL_HANDLER
+ // Recreate our stored view, since we've changed mobs now
+ create_store()
+ UnregisterSignal(source, COMSIG_MOB_LOGOUT)
+ RegisterSignal(owner.owner.mob, COMSIG_MOB_LOGOUT, .proc/on_our_logout, override = TRUE)
+
+/// Create or refresh our stored visual data, represeting the viewing mob
+/datum/plane_master_debug/proc/create_store()
+ if(stored)
+ QDEL_NULL(stored)
+ stored = new()
+ stored.shadow(owner.owner.mob)
+ stored.set_truth(mirror)
+ mirror.set_mirror_target(owner.owner.mob)
+
+/datum/plane_master_debug/proc/get_target()
+ var/mob/target = mob_ref?.resolve()
+ if(!target?.hud_used)
+ target = owner.owner.mob
+ set_target(target)
+ return target
+
+/// Setter for mirror_target, basically allows for enabling/disabiling viewing through mob's sight
+/datum/plane_master_debug/proc/set_mirroring(value)
+ if(value == mirror_target)
+ return
+ mirror_target = value
+ // Refresh our target and mirrors and such
+ set_target(get_target())
+
+/datum/plane_master_debug/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/plane_master_debug/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "PlaneMasterDebug")
+ ui.open()
+
+/datum/plane_master_debug/ui_assets(mob/user)
+ return list(get_asset_datum(/datum/asset/simple/plane_background))
+
+/datum/plane_master_debug/ui_data()
+ var/list/data = list()
+
+ var/mob/reference_frame = get_target()
+ data["mob_name"] = reference_frame.name
+ data["mob_ref"] = ref(reference_frame)
+ data["our_ref"] = ref(owner.owner.mob)
+ data["tracking_active"] = mirror_target
+
+ var/datum/hud/our_hud = reference_frame.hud_used
+ var/list/our_groups = our_hud.master_groups
+ if(!our_groups[current_group])
+ // We assume we'll always have at least one group
+ current_group = our_groups[length(our_hud.master_groups)]
+
+ var/list/groups = list()
+ for(var/key in our_groups)
+ groups += key
+
+ data["enable_group_view"] = length(groups) > 1
+ data["our_group"] = current_group
+ data["present_groups"] = groups
+
+ var/list/plane_info = list()
+ data["plane_info"] = plane_info
+ var/list/relay_deets = list()
+ data["relay_info"] = relay_deets
+ var/list/filter_connections = list()
+ data["filter_connect"] = filter_connections
+
+ var/list/filter_queue = list()
+
+ // Assoc of render targets -> planes
+ // Gotta be able to look these up so filter stuff can work
+ var/list/render_target_to_plane = list()
+ // Assoc list of pending planes -> relays
+ // Used to ensure the incoming_relays list is filled, even if the relay's generated before the plane's processed
+ var/list/pending_relays = list()
+
+ var/list/our_planes = our_hud?.get_planes_from(current_group)
+ for(var/plane_string as anything in our_planes)
+ var/list/this_plane = list()
+ var/atom/movable/screen/plane_master/plane = our_planes[plane_string]
+ var/string_plane = "[plane.plane]"
+ this_plane["name"] = plane.name
+ this_plane["documentation"] = plane.documentation
+ this_plane["plane"] = plane.plane
+ this_plane["our_ref"] = string_plane
+ this_plane["offset"] = plane.offset
+ this_plane["real_plane"] = plane.real_plane
+ this_plane["renders_onto"] = plane.render_relay_planes
+ this_plane["blend_mode"] = GLOB.blend_names["[plane.blend_mode_override || initial(plane.blend_mode)]"]
+ this_plane["color"] = plane.color
+ this_plane["alpha"] = plane.alpha
+ this_plane["render_target"] = plane.render_target
+ this_plane["intended_hidden"] = plane.force_hidden
+
+
+ var/list/incoming_relays = list()
+ this_plane["incoming_relays"] = incoming_relays
+
+ for(var/pending_relay in pending_relays[string_plane])
+ incoming_relays += pending_relay
+ var/list/this_relay = relay_deets[pending_relay]
+ this_relay["target_index"] = length(incoming_relays)
+
+
+ this_plane["outgoing_relays"] = list()
+
+ // You can think of relays as connections between plane master "nodes
+ // They do have some info of their own tho, best to pass that along
+ for(var/atom/movable/render_plane_relay/relay in plane.relays)
+ var/string_target = "[relay.plane]"
+ var/list/this_relay = list()
+ this_relay["name"] = relay.name
+ this_relay["source"] = plane.plane
+ this_relay["source_ref"] = string_plane
+ this_relay["target"] = relay.plane
+ this_relay["target_ref"] = string_target
+ this_relay["layer"] = relay.layer
+
+ // Now taht we've encoded our relay, we need to hand out references to it to our source plane, alongside the target plane
+ var/relay_ref = "[string_plane]-[string_target]"
+ this_relay["our_ref"] = relay_ref
+ relay_deets[relay_ref] = this_relay
+ this_plane["outgoing_relays"] += relay_ref
+
+ // If we've already encoded our target plane, update its incoming relays list
+ // Otherwise, we'll handle this later
+ var/list/existing_target = plane_info[string_target]
+ if(existing_target)
+ existing_target["incoming_relays"] += relay_ref
+ else
+ var/list/pending_plane = pending_relays[string_target]
+ if(!pending_plane)
+ pending_plane = list()
+ pending_relays[string_target] = pending_plane
+ pending_plane += relay_ref
+
+ this_plane["incoming_filters"] = list()
+ this_plane["outgoing_filters"] = list()
+ // We're gonna collect a list of filters, partly because they're useful info
+ // But also because they can be used as connections, and we need to support that
+ for(var/filter_id in plane.filter_data)
+ var/list/filter = plane.filter_data[filter_id]
+ if(!filter["render_source"])
+ continue
+ var/list/filter_info = filter.Copy()
+ filter_info["target_ref"] = string_plane
+ filter_info["name"] = filter_id
+ filter_queue += list(filter_info)
+
+ plane_info[plane_string] = this_plane
+ render_target_to_plane[plane.render_target] = this_plane
+
+ for(var/list/filter in filter_queue)
+ var/source = filter["render_source"]
+ var/list/source_plane = render_target_to_plane[source]
+ var/list/target_plane = plane_info[filter["target_ref"]]
+ var/source_ref = source_plane["our_ref"]
+ filter["source_ref"] = source_ref
+ var/our_ref = "[source_ref]-[filter["target_ref"]]-filter"
+ filter["our_ref"] = our_ref
+ filter_connections[our_ref] = filter
+ source_plane["outgoing_filters"] += our_ref
+ target_plane["incoming_filters"] += our_ref
+
+ // Only load this once. Prevents leaving off orphaned components
+ if(!depth_stack[current_group])
+ depth_stack[current_group] = treeify(plane_info, relay_deets, filter_connections)
+
+ // We will use this js side to arrange our plane masters and such
+ // It's essentially a stack of where they should be displayed
+ data["depth_stack"] = depth_stack[current_group]
+ return data
+
+// Reading this in the queue tells the search to increase the depth, and then push another increase command to the end of the stack
+// This way we ensure groupings always stay together, and depth is respected
+#define COMMAND_DEPTH_INCREASE "increase_depth"
+#define COMMAND_NEXT_PARENT "next_parent"
+
+/// Takes a list of js formatted planes, and turns it into a tree based off the back connections of relays
+/// So start at the top master plane, and work down
+/// Haha jerry what if I added commands to my list parser lmao lol
+/datum/plane_master_debug/proc/treeify(list/plane_info, list/relay_info, list/filter_connections)
+ // List in the form [depth in num] -> list(list(plane_ref -> parent_ref, ...), ...)
+ var/list/treelike_output = list()
+ // List in the form plane ref -> current depth
+ var/list/plane_to_depth = list()
+ // List of items/commands to process. FIFO queue, to ensure the brackets are built correctly
+ var/list/processing_queue = list()
+ // A FIFO queue of parents. Used so planes can have refs to their direct parent, to make sorting easier
+ var/list/parents = list("")
+ var/parent_head = 1
+ // The current depth of our search, used with treelike_output
+ var/depth = 0
+ // Push a depth increase onto the queue, to properly setup the sorta looping effect it has
+ processing_queue += COMMAND_DEPTH_INCREASE
+ processing_queue += "[RENDER_PLANE_MASTER]"
+ // We need to do a c style loop here because we are expanding the queue, and so need to update our conditional
+ for(var/i = 1; i <= length(processing_queue); i++)
+ var/entry = processing_queue[i]
+ // We've reached the end of a depth block
+ // Increment the depth and stick another command on the end of the queue
+ if(entry == COMMAND_DEPTH_INCREASE)
+ // The plane to continue on with, assuming we can find an unvisited head to use
+ var/continue_on_with = ""
+ // Don't wanna infinite loop now
+ if(i == length(processing_queue))
+ for(var/plane in TRUE_PLANE_TO_OFFSETS(RENDER_PLANE_MASTER))
+ if(!plane_to_depth["[plane]"])
+ continue_on_with = "[plane]"
+ // We only want to handle one plane master at a time
+ break
+ if(!continue_on_with)
+ continue
+ // Increment our depth
+ depth += 1
+ treelike_output += list(list())
+ // If this isn't the end, stick another entry on the end to ensure batches work proper
+ processing_queue += COMMAND_DEPTH_INCREASE
+ // If we found a plane to use to extend our process, tack it on the end here as god intended
+ if(continue_on_with)
+ processing_queue += continue_on_with
+ continue
+ if(entry == COMMAND_NEXT_PARENT)
+ parent_head += 1
+ continue
+
+ var/old_queue_len = length(processing_queue)
+ var/existing_depth = plane_to_depth[entry]
+ // If we've seen you before, remove your last entry
+ // We always want inputs before outputs in the stack
+ if(existing_depth)
+ treelike_output[existing_depth] -= entry
+
+ // If it's not a command, it must be a plane string
+ var/list/plane = plane_info[entry]
+ /// We want master planes to ALWAYS bubble down to their own space.
+ /// Just ignore this if this is the head we're processing, yeah?
+ if(PLANE_TO_TRUE(plane["real_plane"]) == RENDER_PLANE_MASTER && i > 2)
+ // If there's other stuff already in your depth entry, or there's more then one thing (a depth increase command)
+ // Left in the queue, "bubble" down a layer.
+ if(length(treelike_output[depth]) || i + 1 != length(processing_queue))
+ processing_queue += COMMAND_NEXT_PARENT
+ parents += parents[parent_head]
+ processing_queue += entry
+ continue
+ // Add all the planes that pipe into us to the queue, Intentionally allows dupes
+ // If we find the same entry twice, it'll get moved down the depth stack
+ for(var/relay_string in plane["incoming_relays"])
+ var/list/relay = relay_info[relay_string]
+ processing_queue += relay["source_ref"]
+ for(var/filter_ref in plane["incoming_filters"])
+ var/list/filter = filter_connections[filter_ref]
+ processing_queue += filter["source_ref"]
+
+ // If the queue has grown, we're a parent, so stick us in the parent queue
+ if(old_queue_len != length(processing_queue))
+ parents += entry
+ // Stick a parent increase right before our children show up in the queue. That way we're properly set as their parent
+ processing_queue.Insert(old_queue_len + 1, COMMAND_NEXT_PARENT)
+ // Stick us in the output at our designated depth
+ var/list/plane_packet = list()
+ plane_packet[entry] = parents[parent_head]
+ treelike_output[depth] += plane_packet
+ plane_to_depth[entry] = depth
+
+ /// Walk treelike output, remove allll the empty lists we've accidentially generated
+ for(var/depth_index = 1; depth_index <= length(treelike_output); depth_index++)
+ var/list/layer = treelike_output[depth_index]
+ if(!length(layer))
+ treelike_output.Cut(depth_index, depth_index + 1)
+ depth_index -= 1
+
+ return treelike_output
+
+#undef COMMAND_DEPTH_INCREASE
+#undef COMMAND_NEXT_PARENT
+
+/datum/plane_master_debug/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+
+ var/mob/reference_frame = get_target()
+ var/datum/hud/our_hud = reference_frame.hud_used
+ var/datum/plane_master_group/group = our_hud?.master_groups[current_group]
+ if(!group) // Nothing to act on
+ return
+ var/list/our_planes = group.plane_masters
+
+ switch(action)
+ if("refresh")
+ group.rebuild_hud()
+ if("reset_mob")
+ set_target(null)
+ if("toggle_mirroring")
+ set_mirroring(!mirror_target)
+ if("vv_mob")
+ owner.owner.debug_variables(reference_frame)
+ if("set_group")
+ current_group = params["target_group"]
+ if("connect_relay")
+ var/source_plane = params["source"]
+ var/target_plane = params["target"]
+ var/atom/movable/screen/plane_master/source = our_planes["[source_plane]"]
+ if(source.get_relay_to(target_plane)) // Fuck off
+ return
+ source.add_relay_to(target_plane)
+ return TRUE
+ if("disconnect_relay")
+ var/source_plane = params["source"]
+ var/target_plane = params["target"]
+ var/atom/movable/screen/plane_master/source = our_planes["[source_plane]"]
+ source.remove_relay_from(text2num(target_plane))
+ return TRUE
+ if("disconnect_filter")
+ var/target_plane = params["target"]
+ var/atom/movable/screen/plane_master/filtered_plane = our_planes["[target_plane]"]
+ filtered_plane.remove_filter(params["name"])
+ return TRUE
+ if("vv_plane")
+ var/plane_edit = params["edit"]
+ var/atom/movable/screen/plane_master/edit = our_planes["[plane_edit]"]
+ var/mob/user = ui.user
+ user?.client?.debug_variables(edit)
+ return TRUE
+ if("set_alpha")
+ var/plane_edit = params["edit"]
+ var/atom/movable/screen/plane_master/edit = our_planes["[plane_edit]"]
+ var/newalpha = params["alpha"]
+ animate(edit, 0.4 SECONDS, alpha = newalpha)
+ return TRUE
+ if("edit_color_matrix")
+ var/plane_edit = params["edit"]
+ var/atom/movable/screen/plane_master/edit = our_planes["[plane_edit]"]
+ var/mob/user = ui.user
+ user?.client?.open_color_matrix_editor(edit)
+ return TRUE
+ if("edit_filters")
+ var/plane_edit = params["edit"]
+ var/atom/movable/screen/plane_master/edit = our_planes["[plane_edit]"]
+ var/mob/user = ui.user
+ user?.client?.open_filter_editor(edit)
+ return TRUE
+
+/datum/plane_master_debug/ui_close(mob/user)
+ . = ..()
+ set_mirroring(FALSE)
diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm
index bf1112d7811..97c69ac14c2 100644
--- a/code/modules/admin/verbs/secrets.dm
+++ b/code/modules/admin/verbs/secrets.dm
@@ -414,9 +414,12 @@ GLOBAL_DATUM(everyone_a_traitor, /datum/everyone_is_a_traitor_controller)
if (prefs["announce_players"]["value"] == "Yes")
portalAnnounce(prefs["announcement"]["value"], (prefs["playlightning"]["value"] == "Yes" ? TRUE : FALSE))
- var/mutable_appearance/storm = mutable_appearance('icons/obj/engine/energy_ball.dmi', "energy_ball_fast", FLY_LAYER)
- storm.plane = ABOVE_GAME_PLANE
- storm.color = prefs["color"]["value"]
+ var/list/storm_appearances = list()
+ for(var/offset in 0 to SSmapping.max_plane_offset)
+ var/mutable_appearance/storm = mutable_appearance('icons/obj/engine/energy_ball.dmi', "energy_ball_fast", FLY_LAYER)
+ SET_PLANE_W_SCALAR(storm, ABOVE_GAME_PLANE, offset)
+ storm.color = prefs["color"]["value"]
+ storm_appearances += storm
message_admins("[key_name_admin(holder)] has created a customized portal storm that will spawn [prefs["portalnum"]["value"]] portals, each of them spawning [prefs["amount"]["value"]] of [pathToSpawn]")
log_admin("[key_name(holder)] has created a customized portal storm that will spawn [prefs["portalnum"]["value"]] portals, each of them spawning [prefs["amount"]["value"]] of [pathToSpawn]")
@@ -430,9 +433,9 @@ GLOBAL_DATUM(everyone_a_traitor, /datum/everyone_is_a_traitor_controller)
var/ghostcandidates = list()
for (var/j in 1 to min(prefs["amount"]["value"], length(candidates)))
ghostcandidates += pick_n_take(candidates)
- addtimer(CALLBACK(GLOBAL_PROC, .proc/doPortalSpawn, get_random_station_turf(), pathToSpawn, length(ghostcandidates), storm, ghostcandidates, outfit), i*prefs["delay"]["value"])
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/doPortalSpawn, get_random_station_turf(), pathToSpawn, length(ghostcandidates), storm_appearances, ghostcandidates, outfit), i*prefs["delay"]["value"])
else if (prefs["playersonly"]["value"] != "Yes")
- addtimer(CALLBACK(GLOBAL_PROC, .proc/doPortalSpawn, get_random_station_turf(), pathToSpawn, prefs["amount"]["value"], storm, null, outfit), i*prefs["delay"]["value"])
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/doPortalSpawn, get_random_station_turf(), pathToSpawn, prefs["amount"]["value"], storm_appearances, null, outfit), i*prefs["delay"]["value"])
if("changebombcap")
if(!is_funmin)
return
@@ -602,7 +605,9 @@ GLOBAL_DATUM(everyone_a_traitor, /datum/everyone_is_a_traitor_controller)
sleep(20)
sound_to_playing_players('sound/magic/lightningbolt.ogg')
-/proc/doPortalSpawn(turf/loc, mobtype, numtospawn, portal_appearance, players, humanoutfit)
+/// Spawns a portal storm that spawns in sentient/non sentient mobs
+/// portal_appearance is a list in the form (turf's plane offset + 1) -> appearance to use
+/proc/doPortalSpawn(turf/loc, mobtype, numtospawn, list/portal_appearance, players, humanoutfit)
for (var/i in 1 to numtospawn)
var/mob/spawnedMob = new mobtype(loc)
if (length(players))
@@ -615,7 +620,7 @@ GLOBAL_DATUM(everyone_a_traitor, /datum/everyone_is_a_traitor_controller)
var/mob/living/carbon/human/H = spawnedMob
H.equipOutfit(humanoutfit)
var/turf/T = get_step(loc, SOUTHWEST)
- flick_overlay_static(portal_appearance, T, 15)
+ flick_overlay_static(portal_appearance[GET_TURF_PLANE_OFFSET(T) + 1], T, 15)
playsound(T, 'sound/magic/lightningbolt.ogg', rand(80, 100), TRUE)
///Makes sure latejoining crewmembers also become traitors.
diff --git a/code/modules/admin/view_variables/color_matrix_editor.dm b/code/modules/admin/view_variables/color_matrix_editor.dm
index e61b54ff433..5ea6d237a6a 100644
--- a/code/modules/admin/view_variables/color_matrix_editor.dm
+++ b/code/modules/admin/view_variables/color_matrix_editor.dm
@@ -1,55 +1,7 @@
-INITIALIZE_IMMEDIATE(/atom/movable/screen/color_matrix_proxy_view)
-
-/atom/movable/screen/color_matrix_proxy_view
- name = "color_matrix_proxy_view"
- del_on_map_removal = FALSE
- layer = GAME_PLANE
- plane = GAME_PLANE
-
- var/list/plane_masters = list()
-
- /// The client that is watching this view
- var/client/client
-
-/atom/movable/screen/color_matrix_proxy_view/Initialize(mapload)
- . = ..()
-
- assigned_map = "color_matrix_proxy_[REF(src)]"
- set_position(1, 1)
-
-/atom/movable/screen/color_matrix_proxy_view/Destroy()
- for (var/plane_master in plane_masters)
- client?.screen -= plane_master
- qdel(plane_master)
-
- client?.clear_map(assigned_map)
-
- client = null
- plane_masters = null
-
- return ..()
-
-/atom/movable/screen/color_matrix_proxy_view/proc/register_to_client(client/client)
- QDEL_LIST(plane_masters)
-
- src.client = client
-
- if (!client)
- return
-
- for (var/plane_master_type in subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/blackness)
- var/atom/movable/screen/plane_master/plane_master = new plane_master_type()
- plane_master.screen_loc = "[assigned_map]:CENTER"
- client?.screen |= plane_master
-
- plane_masters += plane_master
-
- client?.register_map_obj(src)
-
/datum/color_matrix_editor
var/client/owner
var/datum/weakref/target
- var/atom/movable/screen/color_matrix_proxy_view/proxy_view
+ var/atom/movable/screen/map_view/proxy_view
var/list/current_color
var/closed
@@ -61,15 +13,19 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/color_matrix_proxy_view)
current_color = color_hex2color_matrix(_target.color)
else
current_color = color_matrix_identity()
- proxy_view = new
+
+ var/mutable_appearance/view = image('icons/misc/colortest.dmi', "colors")
if(_target)
target = WEAKREF(_target)
- proxy_view.appearance = image(_target)
- else
- proxy_view.appearance = image('icons/misc/colortest.dmi', "colors")
+ if(!(_target.appearance_flags & PLANE_MASTER))
+ view = image(_target)
+ proxy_view = new
+ proxy_view.generate_view("color_matrix_proxy_[REF(src)]")
+
+ proxy_view.appearance = view
proxy_view.color = current_color
- proxy_view.register_to_client(owner)
+ proxy_view.display_to(owner.mob)
/datum/color_matrix_editor/Destroy(force, ...)
QDEL_NULL(proxy_view)
diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm
index 3a4f18ae14e..be18c38548c 100644
--- a/code/modules/antagonists/cult/runes.dm
+++ b/code/modules/antagonists/cult/runes.dm
@@ -852,9 +852,8 @@ structure_check() searches for nearby cultist structures required for the invoca
new_human.alpha = 150 //Makes them translucent
new_human.equipOutfit(/datum/outfit/ghost_cultist) //give them armor
new_human.apply_status_effect(/datum/status_effect/cultghost) //ghosts can't summon more ghosts
- new_human.see_invisible = SEE_INVISIBLE_OBSERVER
+ new_human.set_invis_see(SEE_INVISIBLE_OBSERVER)
ADD_TRAIT(new_human, TRAIT_NOBREATH, INNATE_TRAIT)
-
ghosts++
playsound(src, 'sound/magic/exit_blood.ogg', 50, TRUE)
visible_message(span_warning("A cloud of red mist forms above [src], and from within steps... a [new_human.gender == FEMALE ? "wo":""]man."))
diff --git a/code/modules/antagonists/disease/disease_mob.dm b/code/modules/antagonists/disease/disease_mob.dm
index 4fd20975127..e99d221d3e0 100644
--- a/code/modules/antagonists/disease/disease_mob.dm
+++ b/code/modules/antagonists/disease/disease_mob.dm
@@ -20,7 +20,7 @@ the new instance inside the host to be updated to the template's stats.
see_invisible = SEE_INVISIBLE_LIVING
layer = BELOW_MOB_LAYER
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- sight = SEE_SELF|SEE_THRU
+ sight = SEE_SELF|SEE_THRU|SEE_BLACKNESS
initial_language_holder = /datum/language_holder/universal
var/freemove = TRUE
@@ -239,7 +239,7 @@ the new instance inside the host to be updated to the template's stats.
A.Buy(src, TRUE, FALSE)
if(freemove_end_timerid)
deltimer(freemove_end_timerid)
- sight = SEE_SELF
+ set_sight(SEE_SELF)
/mob/camera/disease/proc/add_infection(datum/disease/advance/sentient_disease/V)
disease_instances += V
diff --git a/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm b/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm
index e0a276c4927..da6a61caa82 100644
--- a/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm
+++ b/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm
@@ -14,7 +14,7 @@
health = 80
melee_damage_lower = 12
melee_damage_upper = 16
- sight = SEE_MOBS | SEE_OBJS | SEE_TURFS
+ sight = SEE_MOBS | SEE_OBJS | SEE_TURFS | SEE_BLACKNESS
death_message = "shatters and vanishes, releasing a gust of cold air."
loot = list(
/obj/item/shard,
diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm
index 3360b374f4b..f4786655954 100644
--- a/code/modules/antagonists/revenant/revenant.dm
+++ b/code/modules/antagonists/revenant/revenant.dm
@@ -20,7 +20,7 @@
maxHealth = INFINITY
plane = GHOST_PLANE
healable = FALSE
- sight = SEE_SELF
+ sight = SEE_SELF | SEE_BLACKNESS
throwforce = 0
see_in_dark = NIGHTVISION_FOV_RANGE
diff --git a/code/modules/antagonists/traitor/objectives/bug_room.dm b/code/modules/antagonists/traitor/objectives/bug_room.dm
index 8c03552e3a3..24913bf575b 100644
--- a/code/modules/antagonists/traitor/objectives/bug_room.dm
+++ b/code/modules/antagonists/traitor/objectives/bug_room.dm
@@ -169,6 +169,7 @@
return
forceMove(target)
target.vis_contents += src
+ vis_flags |= VIS_INHERIT_PLANE
planted_on = target
RegisterSignal(planted_on, COMSIG_PARENT_QDELETING, .proc/handle_planted_on_deletion)
SEND_SIGNAL(src, COMSIG_TRAITOR_BUG_PLANTED_OBJECT, target)
@@ -178,12 +179,14 @@
/obj/item/traitor_bug/Destroy()
if(planted_on)
+ vis_flags &= ~VIS_INHERIT_PLANE
planted_on.vis_contents -= src
return ..()
/obj/item/traitor_bug/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
if(planted_on)
+ vis_flags &= ~VIS_INHERIT_PLANE
planted_on.vis_contents -= src
anchored = FALSE
UnregisterSignal(planted_on, COMSIG_PARENT_QDELETING)
diff --git a/code/modules/art/statues.dm b/code/modules/art/statues.dm
index 87be65224e7..6b5b008c8b0 100644
--- a/code/modules/art/statues.dm
+++ b/code/modules/art/statues.dm
@@ -539,22 +539,42 @@ Moving interrupts
/// Ideally we'd have knowledge what we're removing but i'd have to be done on target appearance retrieval
var/list/overlays_to_remove = list()
for(var/mutable_appearance/special_overlay as anything in content_ma.overlays)
- if(special_overlay.plane in plane_whitelist)
+ var/mutable_appearance/real = new()
+ real.appearance = special_overlay
+ if(PLANE_TO_TRUE(real.plane) in plane_whitelist)
continue
- overlays_to_remove += special_overlay
+ overlays_to_remove += real
content_ma.overlays -= overlays_to_remove
var/list/underlays_to_remove = list()
for(var/mutable_appearance/special_underlay as anything in content_ma.underlays)
- if(special_underlay.plane in plane_whitelist)
+ var/mutable_appearance/real = new()
+ real.appearance = special_underlay
+ if(PLANE_TO_TRUE(real.plane) in plane_whitelist)
continue
- underlays_to_remove += special_underlay
+ underlays_to_remove += real
content_ma.underlays -= underlays_to_remove
content_ma.appearance_flags &= ~KEEP_APART //Don't want this
content_ma.filters = filter(type="color",color=greyscale_with_value_bump,space=FILTER_COLOR_HSV)
+ update_content_planes()
update_appearance()
+/obj/structure/statue/custom/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer)
+ return ..()
+ update_content_planes()
+ update_appearance()
+
+/obj/structure/statue/custom/proc/update_content_planes()
+ if(!content_ma)
+ return
+ var/turf/our_turf = get_turf(src)
+ // MA's stored in the overlays list are not actually mutable, they've been flattened
+ // This proc unflattens them, updates them, and then reapplies
+ var/list/created = update_appearance_planes(list(content_ma), GET_TURF_PLANE_OFFSET(our_turf))
+ content_ma = created[1]
+
/obj/structure/statue/custom/update_overlays()
. = ..()
if(content_ma)
diff --git a/code/modules/asset_cache/assets/plane_debug.dm b/code/modules/asset_cache/assets/plane_debug.dm
new file mode 100644
index 00000000000..0d8ddac162c
--- /dev/null
+++ b/code/modules/asset_cache/assets/plane_debug.dm
@@ -0,0 +1,4 @@
+/datum/asset/simple/plane_background
+ assets = list(
+ "grid_background.png" = 'icons/ui_icons/tgui/grid_background.png'
+ )
diff --git a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
index 1bc50001b33..d1d96c74c2b 100644
--- a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
+++ b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
@@ -174,7 +174,7 @@
var/list/gases = air.gases
var/list/new_overlay_types
- GAS_OVERLAYS(gases, new_overlay_types)
+ GAS_OVERLAYS(gases, new_overlay_types, src)
if (atmos_overlay_types)
for(var/overlay in atmos_overlay_types-new_overlay_types) //doesn't remove overlays that would only be added
@@ -533,12 +533,14 @@
display_id = wrapping_id
for(var/thing in turf_list)
var/turf/display = thing
- display.vis_contents += GLOB.colored_turfs[display_id]
+ var/offset = GET_Z_PLANE_OFFSET(display.z) + 1
+ display.vis_contents += GLOB.colored_turfs[display_id][offset]
/datum/excited_group/proc/hide_turfs()
for(var/thing in turf_list)
var/turf/display = thing
- display.vis_contents -= GLOB.colored_turfs[display_id]
+ var/offset = GET_Z_PLANE_OFFSET(display.z) + 1
+ display.vis_contents -= GLOB.colored_turfs[display_id][offset]
display_id = 0
/datum/excited_group/proc/display_turf(turf/thing)
@@ -546,7 +548,8 @@
wrapping_id = wrapping_id % GLOB.colored_turfs.len
wrapping_id++ //We do this after because lists index at 1
display_id = wrapping_id
- thing.vis_contents += GLOB.colored_turfs[display_id]
+ var/offset = GET_Z_PLANE_OFFSET(thing.z) + 1
+ thing.vis_contents += GLOB.colored_turfs[display_id][offset]
////////////////////////SUPERCONDUCTIVITY/////////////////////////////
diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
index 22150051ee9..4711ff40a95 100644
--- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm
+++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
@@ -123,9 +123,9 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
return max(0, volume)
/// Gets the gas visuals for everything in this mixture
-/datum/gas_mixture/proc/return_visuals()
+/datum/gas_mixture/proc/return_visuals(turf/z_context)
var/list/output
- GAS_OVERLAYS(gases, output)
+ GAS_OVERLAYS(gases, output, z_context)
return output
/// Calculate thermal energy in joules
diff --git a/code/modules/atmospherics/gasmixtures/gas_types.dm b/code/modules/atmospherics/gasmixtures/gas_types.dm
index 2206a7fc785..ea36b288635 100644
--- a/code/modules/atmospherics/gasmixtures/gas_types.dm
+++ b/code/modules/atmospherics/gasmixtures/gas_types.dm
@@ -9,9 +9,7 @@
gas_info[META_GAS_MOLES_VISIBLE] = initial(gas.moles_visible)
if(initial(gas.moles_visible) != null)
- gas_info[META_GAS_OVERLAY] = new /list(TOTAL_VISIBLE_STATES)
- for(var/i in 1 to TOTAL_VISIBLE_STATES)
- gas_info[META_GAS_OVERLAY][i] = new /obj/effect/overlay/gas(initial(gas.gas_overlay), log(4, (i+0.4*TOTAL_VISIBLE_STATES) / (0.35*TOTAL_VISIBLE_STATES)) * 255)
+ gas_info[META_GAS_OVERLAY] = generate_gas_overlays(0, SSmapping.max_plane_offset, gas)
gas_info[META_GAS_FUSION_POWER] = initial(gas.fusion_power)
gas_info[META_GAS_DANGER] = initial(gas.dangerous)
@@ -19,6 +17,17 @@
gas_info[META_GAS_DESC] = initial(gas.desc)
.[gas_path] = gas_info
+/proc/generate_gas_overlays(old_offset, new_offset, datum/gas/gas_type)
+ var/list/to_return = list()
+ for(var/i in old_offset to new_offset)
+ var/fill = list()
+ to_return += list(fill)
+ for(var/j in 1 to TOTAL_VISIBLE_STATES)
+ var/obj/effect/overlay/gas/gas = new (initial(gas_type.gas_overlay), log(4, (j+0.4*TOTAL_VISIBLE_STATES) / (0.35*TOTAL_VISIBLE_STATES)) * 255)
+ SET_PLANE_W_SCALAR(gas, gas.plane, i)
+ fill += gas
+ return to_return
+
/proc/gas_id2path(id)
var/list/meta_gas = GLOB.meta_gas_info
if(id in meta_gas)
diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm
index 42466fad0b3..f27ee6821fe 100644
--- a/code/modules/atmospherics/machinery/airalarm.dm
+++ b/code/modules/atmospherics/machinery/airalarm.dm
@@ -605,7 +605,7 @@
state = "alarm1"
. += mutable_appearance(icon, state)
- . += emissive_appearance(icon, state, alpha = src.alpha)
+ . += emissive_appearance(icon, state, src, alpha = src.alpha)
/**
* main proc for throwing a shitfit if the air isnt right.
diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm
index 32dbb954164..813ce873c7e 100644
--- a/code/modules/atmospherics/machinery/atmosmachinery.dm
+++ b/code/modules/atmospherics/machinery/atmosmachinery.dm
@@ -544,7 +544,7 @@
var/client/our_client = user.client
if(!our_client)
return
- our_client.eye = target_move
+ our_client.set_eye(target_move)
// Let's smooth out that movement with an animate yeah?
// If the new x is greater (move is left to right) we get a negative offset. vis versa
our_client.pixel_x = (x - target_move.x) * world.icon_size
@@ -567,7 +567,7 @@
return list()
/obj/machinery/atmospherics/update_remote_sight(mob/user)
- user.sight |= (SEE_TURFS|BLIND)
+ user.add_sight(SEE_TURFS|BLIND)
/**
* Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it.
diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm
index 08a2ab22ccd..59d1072027a 100644
--- a/code/modules/atmospherics/machinery/components/components_base.dm
+++ b/code/modules/atmospherics/machinery/components/components_base.dm
@@ -60,7 +60,7 @@
underlays.Cut()
color = null
- plane = showpipe ? GAME_PLANE : FLOOR_PLANE
+ SET_PLANE_IMPLICIT(src, showpipe ? GAME_PLANE : FLOOR_PLANE)
if(!showpipe)
return ..()
diff --git a/code/modules/atmospherics/machinery/components/tank.dm b/code/modules/atmospherics/machinery/components/tank.dm
index ad94e7a199e..73682fe0fbf 100644
--- a/code/modules/atmospherics/machinery/components/tank.dm
+++ b/code/modules/atmospherics/machinery/components/tank.dm
@@ -270,7 +270,7 @@
window = image(icon, icon_state = "window-bg", layer = FLOAT_LAYER)
var/list/new_underlays = list()
- for(var/obj/effect/overlay/gas/gas as anything in air_contents.return_visuals())
+ for(var/obj/effect/overlay/gas/gas as anything in air_contents.return_visuals(get_turf(src)))
var/image/new_underlay = image(gas.icon, icon_state = gas.icon_state, layer = FLOAT_LAYER)
new_underlay.filters = alpha_mask_filter(icon = icon(icon, icon_state = "window-bg"))
new_underlays += new_underlay
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
index 28f28ee1bd6..7b0facba484 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
@@ -39,6 +39,7 @@
if(occupant)
vis_contents -= occupant
+ occupant.vis_flags &= ~VIS_INHERIT_PLANE
REMOVE_TRAIT(occupant, TRAIT_IMMOBILIZED, CRYO_TRAIT)
REMOVE_TRAIT(occupant, TRAIT_FORCED_STANDING, CRYO_TRAIT)
@@ -47,6 +48,8 @@
return
occupant.setDir(SOUTH)
+ // We want to pull our occupant up to our plane so we look right
+ occupant.vis_flags |= VIS_INHERIT_PLANE
vis_contents += occupant
pixel_y = 22
ADD_TRAIT(occupant, TRAIT_IMMOBILIZED, CRYO_TRAIT)
@@ -129,6 +132,12 @@
if(airs[1])
airs[1].volume = CELL_VOLUME * 0.5
+/obj/machinery/atmospherics/components/unary/cryo_cell/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ SET_PLANE(occupant_vis, PLANE_TO_TRUE(occupant_vis.plane), new_turf)
+
/obj/machinery/atmospherics/components/unary/cryo_cell/set_occupant(atom/movable/new_occupant)
. = ..()
update_appearance()
@@ -197,10 +206,14 @@
/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon()
. = ..()
- plane = initial(plane)
+ SET_PLANE_IMPLICIT(src, initial(plane))
-GLOBAL_VAR_INIT(cryo_overlay_cover_on, mutable_appearance('icons/obj/medical/cryogenics.dmi', "cover-on", layer = ABOVE_ALL_MOB_LAYER, plane = ABOVE_GAME_PLANE))
-GLOBAL_VAR_INIT(cryo_overlay_cover_off, mutable_appearance('icons/obj/medical/cryogenics.dmi', "cover-off", layer = ABOVE_ALL_MOB_LAYER, plane = ABOVE_GAME_PLANE))
+GLOBAL_LIST_INIT_TYPED(cryo_overlays_cover_on, /mutable_appearance, list(create_cryo_overlay(0, "cover-on")))
+GLOBAL_LIST_INIT_TYPED(cryo_overlays_cover_off, /mutable_appearance, list(create_cryo_overlay(0, "cover-off")))
+
+/proc/create_cryo_overlay(offset, icon_state)
+ var/mutable_appearance/cryo_overlay = mutable_appearance('icons/obj/medical/cryogenics.dmi', icon_state, ABOVE_ALL_MOB_LAYER, plane = ABOVE_GAME_PLANE, offset_const = offset)
+ return cryo_overlay
/obj/machinery/atmospherics/components/unary/cryo_cell/update_overlays()
. = ..()
@@ -208,7 +221,9 @@ GLOBAL_VAR_INIT(cryo_overlay_cover_off, mutable_appearance('icons/obj/medical/cr
. += "pod-panel"
if(state_open)
return
- . += (on && is_operational) ? GLOB.cryo_overlay_cover_on : GLOB.cryo_overlay_cover_off
+ var/turf/our_turf = get_turf(src)
+ var/offset = GET_TURF_PLANE_OFFSET(our_turf) + 1
+ . += (on && is_operational) ? GLOB.cryo_overlays_cover_on[offset] : GLOB.cryo_overlays_cover_off[offset]
/obj/machinery/atmospherics/components/unary/cryo_cell/nap_violation(mob/violator)
open_machine()
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
index 054d3c7454e..663eb279487 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
@@ -285,7 +285,7 @@
welded = FALSE
update_appearance()
pipe_vision_img = image(src, loc, dir = dir)
- pipe_vision_img.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(pipe_vision_img, ABOVE_HUD_PLANE, src)
investigate_log("was [welded ? "welded shut" : "unwelded"] by [key_name(user)]", INVESTIGATE_ATMOS)
add_fingerprint(user)
return TRUE
@@ -312,7 +312,7 @@
welded = FALSE
update_appearance()
pipe_vision_img = image(src, loc, dir = dir)
- pipe_vision_img.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(pipe_vision_img, ABOVE_HUD_PLANE, src)
playsound(loc, 'sound/weapons/bladeslice.ogg', 100, TRUE)
/obj/machinery/atmospherics/components/unary/vent_pump/high_volume
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
index 8ea11de0ee2..4c9b4a36d5a 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
@@ -419,7 +419,7 @@
welded = FALSE
update_appearance()
pipe_vision_img = image(src, loc, dir = dir)
- pipe_vision_img.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(pipe_vision_img, ABOVE_HUD_PLANE, src)
investigate_log("was [welded ? "welded shut" : "unwelded"] by [key_name(user)]", INVESTIGATE_ATMOS)
add_fingerprint(user)
return TRUE
@@ -442,7 +442,7 @@
welded = FALSE
update_appearance()
pipe_vision_img = image(src, loc, dir = dir)
- pipe_vision_img.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(pipe_vision_img, ABOVE_HUD_PLANE, src)
playsound(loc, 'sound/weapons/bladeslice.ogg', 100, TRUE)
diff --git a/code/modules/atmospherics/machinery/pipes/pipes.dm b/code/modules/atmospherics/machinery/pipes/pipes.dm
index ab04990906c..de745df7e5f 100644
--- a/code/modules/atmospherics/machinery/pipes/pipes.dm
+++ b/code/modules/atmospherics/machinery/pipes/pipes.dm
@@ -15,8 +15,6 @@
buckle_requires_restraints = TRUE
buckle_lying = NO_BUCKLE_LYING
- vis_flags = VIS_INHERIT_PLANE
-
/obj/machinery/atmospherics/pipe/New()
add_atom_colour(pipe_color, FIXED_COLOUR_PRIORITY)
volume = 35 * device_type
diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm
index dbac4ca250e..9e55560056e 100644
--- a/code/modules/atmospherics/machinery/portable/canister.dm
+++ b/code/modules/atmospherics/machinery/portable/canister.dm
@@ -365,7 +365,7 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister())
if(shielding_powered)
. += mutable_appearance(canister_overlay_file, "shielding")
- . += emissive_appearance(canister_overlay_file, "shielding")
+ . += emissive_appearance(canister_overlay_file, "shielding", src)
if(cell_container_opened)
. += mutable_appearance(canister_overlay_file, "cell_hatch")
@@ -384,16 +384,16 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister())
switch(air_pressure)
if((40 * ONE_ATMOSPHERE) to INFINITY)
. += mutable_appearance(canister_overlay_file, "can-3")
- . += emissive_appearance(canister_overlay_file, "can-3-light", alpha = src.alpha)
+ . += emissive_appearance(canister_overlay_file, "can-3-light", src, alpha = src.alpha)
if((10 * ONE_ATMOSPHERE) to (40 * ONE_ATMOSPHERE))
. += mutable_appearance(canister_overlay_file, "can-2")
- . += emissive_appearance(canister_overlay_file, "can-2-light", alpha = src.alpha)
+ . += emissive_appearance(canister_overlay_file, "can-2-light", src, alpha = src.alpha)
if((5 * ONE_ATMOSPHERE) to (10 * ONE_ATMOSPHERE))
. += mutable_appearance(canister_overlay_file, "can-1")
- . += emissive_appearance(canister_overlay_file, "can-1-light", alpha = src.alpha)
+ . += emissive_appearance(canister_overlay_file, "can-1-light", src, alpha = src.alpha)
if((10) to (5 * ONE_ATMOSPHERE))
. += mutable_appearance(canister_overlay_file, "can-0")
- . += emissive_appearance(canister_overlay_file, "can-0-light", alpha = src.alpha)
+ . += emissive_appearance(canister_overlay_file, "can-0-light", src, alpha = src.alpha)
update_window()
@@ -411,7 +411,7 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister())
cut_overlay(window)
window = image(icon, icon_state="window-base", layer=FLOAT_LAYER)
var/list/window_overlays = list()
- for(var/visual in air_contents.return_visuals())
+ for(var/visual in air_contents.return_visuals(get_turf(src)))
var/image/new_visual = image(visual, layer=FLOAT_LAYER)
new_visual.filters = alpha_filter
window_overlays += new_visual
diff --git a/code/modules/awaymissions/away_props.dm b/code/modules/awaymissions/away_props.dm
index 71d04598086..30c63ce0dfa 100644
--- a/code/modules/awaymissions/away_props.dm
+++ b/code/modules/awaymissions/away_props.dm
@@ -91,7 +91,7 @@
else
talpha = 255
obj_flags |= BLOCK_Z_OUT_DOWN | BLOCK_Z_IN_UP
- plane = ABOVE_LIGHTING_PLANE //What matters it's one above openspace, so our animation is not dependant on what's there. Up to revision with 513
+ SET_PLANE_IMPLICIT(src, ABOVE_LIGHTING_PLANE) //What matters it's one above openspace, so our animation is not dependant on what's there. Up to revision with 513
animate(src,alpha = talpha,time = 10)
addtimer(CALLBACK(src,.proc/reset_plane),10)
if(hidden)
@@ -102,7 +102,7 @@
T.zFall(AM)
/obj/structure/pitgrate/proc/reset_plane()
- plane = FLOOR_PLANE
+ SET_PLANE_IMPLICIT(src, FLOOR_PLANE)
/obj/structure/pitgrate/Destroy()
if(hidden)
diff --git a/code/modules/awaymissions/gateway.dm b/code/modules/awaymissions/gateway.dm
index b23eba46c11..57ea24960b6 100644
--- a/code/modules/awaymissions/gateway.dm
+++ b/code/modules/awaymissions/gateway.dm
@@ -170,18 +170,20 @@ GLOBAL_LIST_EMPTY(gateway_destinations)
/// bumper object, the thing that starts actual teleport
var/obj/effect/gateway_portal_bumper/portal
/// Visual object for handling the viscontents
- var/obj/effect/gateway_portal_effect/portal_visuals
- /// Overlay of the lights. They light up fully when it charges fully.
- var/image/light_overlay
+ var/atom/movable/screen/map_view/gateway_port/portal_visuals
+ var/teleportion_possible = FALSE
+ var/transport_active = FALSE
/obj/machinery/gateway/Initialize(mapload)
generate_destination()
update_appearance()
portal_visuals = new
- vis_contents += portal_visuals
+ portal_visuals.generate_view("gateway_popup_[REF(src)]")
+ portal_visuals.update_portal_filters()
return ..()
/obj/machinery/gateway/Destroy()
+ QDEL_NULL(portal_visuals)
destination.target_gateway = null
GLOB.gateway_destinations -= destination
destination = null
@@ -199,23 +201,50 @@ GLOBAL_LIST_EMPTY(gateway_destinations)
dest.deactivate(src)
QDEL_NULL(portal)
update_use_power(IDLE_POWER_USE)
+ transport_active = FALSE
update_appearance()
portal_visuals.reset_visuals()
/obj/machinery/gateway/process()
if((machine_stat & (NOPOWER)) && use_power)
+ teleportion_possible = FALSE
if(target)
deactivate()
return
- if(light_overlay)
+ if(teleportion_possible)
return
- for(var/datum/gateway_destination/destination as anything in GLOB.gateway_destinations)
- if(!destination.is_available())
+ for(var/datum/gateway_destination/possible_destination as anything in GLOB.gateway_destinations)
+ if(!valid_destination(possible_destination) || !possible_destination.is_available())
continue
- light_overlay = image(icon, "portal_light")
- light_overlay.alpha = 0
- animate(light_overlay, 3 SECONDS, alpha = 255)
- add_overlay(light_overlay)
+ teleportion_possible = TRUE
+ update_appearance()
+ break
+
+/obj/machinery/gateway/proc/valid_destination(datum/gateway_destination/possible_destination)
+ if(possible_destination == destination)
+ return FALSE
+ if(istype(possible_destination, /datum/gateway_destination/gateway))
+ var/datum/gateway_destination/gateway/gateway_dest = possible_destination
+ if(gateway_dest.target_gateway == src)
+ return FALSE
+ return TRUE
+
+/obj/machinery/gateway/proc/show_light_overlays(light_state, toggle)
+ if(!toggle)
+ return list()
+
+ var/list/image/to_animate = list()
+ to_animate += image('icons/obj/machines/gateway.dmi', light_state)
+ var/image/glowing_light = image('icons/obj/machines/gateway.dmi', light_state)
+ glowing_light.color = GLOB.emissive_color
+ SET_PLANE_EXPLICIT(glowing_light, EMISSIVE_PLANE, src)
+ to_animate += glowing_light
+ return to_animate
+
+/obj/machinery/gateway/update_overlays()
+ . = ..()
+ . += show_light_overlays("portal_light", teleportion_possible)
+ . += show_light_overlays("portal_effect", transport_active)
/obj/machinery/gateway/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE)
return
@@ -230,6 +259,7 @@ GLOBAL_LIST_EMPTY(gateway_destinations)
target = D
target.activate(destination)
portal_visuals.setup_visuals(target)
+ transport_active = TRUE
generate_bumper()
update_use_power(ACTIVE_POWER_USE)
update_appearance()
@@ -293,6 +323,7 @@ GLOBAL_LIST_EMPTY(gateway_destinations)
. = ..()
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
+ G.portal_visuals.display_to(user)
ui = new(user, src, "Gateway", name)
ui.open()
@@ -301,12 +332,13 @@ GLOBAL_LIST_EMPTY(gateway_destinations)
.["gateway_present"] = G
.["gateway_status"] = G ? G.powered() : FALSE
.["current_target"] = G?.target?.get_ui_data()
+ .["gateway_mapkey"] = G.portal_visuals.assigned_map
var/list/destinations = list()
if(G)
- for(var/datum/gateway_destination/D in GLOB.gateway_destinations)
- if(D == G.destination)
+ for(var/datum/gateway_destination/possible_destination in GLOB.gateway_destinations)
+ if(!G.valid_destination(possible_destination))
continue
- destinations += list(D.get_ui_data())
+ destinations += list(possible_destination.get_ui_data())
.["destinations"] = destinations
/obj/machinery/computer/gateway_control/ui_act(action, list/params)
@@ -326,6 +358,10 @@ GLOBAL_LIST_EMPTY(gateway_destinations)
G.deactivate()
return TRUE
+/obj/machinery/computer/gateway_control/ui_close(mob/user)
+ . = ..()
+ G.portal_visuals.hide_from(user)
+
/obj/machinery/computer/gateway_control/proc/try_to_linkup()
G = locate(/obj/machinery/gateway) in view(7,get_turf(src))
@@ -340,36 +376,62 @@ GLOBAL_LIST_EMPTY(gateway_destinations)
default_raw_text = "Congratulations,
Your station has been selected to carry out the Gateway Project.
The equipment will be shipped to you at the start of the next quarter. You are to prepare a secure location to house the equipment as outlined in the attached documents.
--Nanotrasen Bluespace Research"
name = "Confidential Correspondence, Pg 1"
-/obj/effect/gateway_portal_effect
- appearance_flags = KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- vis_flags = VIS_INHERIT_ID
- layer = GATEWAY_UNDERLAY_LAYER //Slightly lower than gateway itself
- var/alpha_icon = 'icons/obj/machines/gateway.dmi'
- var/alpha_icon_state = "portal_mask"
+/atom/movable/screen/map_view/gateway_port
var/datum/gateway_destination/our_destination
+ /// Handles the background of the portal, ensures the effect well, works properly
+ var/atom/movable/screen/background/cam_background
+
+/atom/movable/screen/map_view/gateway_port/Initialize(mapload)
+ . = ..()
+ cam_background = new
+ cam_background.del_on_map_removal = FALSE
+ // Draw above everything
+ cam_background.layer = 200
+ cam_background.plane = HIGHEST_EVER_PLANE
+ cam_background.blend_mode = BLEND_OVERLAY
-/obj/effect/gateway_portal_effect/proc/setup_visuals(datum/gateway_destination/D)
+/atom/movable/screen/map_view/gateway_port/generate_view(map_key)
+ . = ..()
+ cam_background.assigned_map = assigned_map
+ cam_background.fill_rect(1, 1, 3, 3)
+
+/atom/movable/screen/map_view/gateway_port/Destroy()
+ QDEL_NULL(cam_background)
+ return ..()
+
+/atom/movable/screen/map_view/gateway_port/display_to(mob/show_to)
+ . = ..()
+ show_to.client.register_map_obj(cam_background)
+
+/atom/movable/screen/map_view/gateway_port/proc/setup_visuals(datum/gateway_destination/D)
our_destination = D
update_portal_filters()
-/obj/effect/gateway_portal_effect/proc/reset_visuals()
+/atom/movable/screen/map_view/gateway_port/proc/reset_visuals()
our_destination = null
update_portal_filters()
-/obj/effect/gateway_portal_effect/proc/update_portal_filters()
- clear_filters()
+/atom/movable/screen/map_view/gateway_port/proc/update_portal_filters()
+ cam_background.clear_filters()
+ // ok so what this used to do was render the tiles "on the other side" of the gateway onto the gateway mask
+ // Unfortunately since I've removed the plane inheriting from /atom vis_flags, this no longer works
+ // You could setup gateways to draw onto "lower then everything" z layers, but generating a whole stack of plane masters
+ // Just for this one effect is kinda silly. Maybe next time
+ // Rather then that, let's just render a little preview port to the console, because for reasons that's trivial
vis_contents = null
if(!our_destination)
+ // Draw static
+ cam_background.icon_state = "scanline2"
+ cam_background.color = null
+ cam_background.alpha = 255
return
- add_filter("portal_alpha", 1, list("type" = "alpha", "icon" = icon(alpha_icon, alpha_icon_state), "x" = 32, "y" = 32))
- add_filter("portal_blur", 1, list("type" = "blur", "size" = 0.5))
- add_filter("portal_ripple", 1, list("type" = "ripple", "size" = 2, "radius" = 1, "falloff" = 1, "y" = 7))
-
- animate(get_filter("portal_ripple"), time = 1.3 SECONDS, loop = -1, easing = LINEAR_EASING, radius = 32)
+ cam_background.add_filter("portal_blur", 1, list("type" = "blur", "size" = 0.5))
var/turf/center_turf = our_destination.get_target_turf()
vis_contents += block(locate(center_turf.x - 1, center_turf.y - 1, center_turf.z), locate(center_turf.x + 1, center_turf.y + 1, center_turf.z))
+ cam_background.icon_state = "scanline4"
+ cam_background.color = "#adadff"
+ cam_background.alpha = 128
diff --git a/code/modules/balloon_alert/balloon_alert.dm b/code/modules/balloon_alert/balloon_alert.dm
index c2737226078..9464959e31a 100644
--- a/code/modules/balloon_alert/balloon_alert.dm
+++ b/code/modules/balloon_alert/balloon_alert.dm
@@ -43,7 +43,7 @@
bound_width = movable_source.bound_width
var/image/balloon_alert = image(loc = isturf(src) ? src : get_atom_on_turf(src), layer = ABOVE_MOB_LAYER)
- balloon_alert.plane = BALLOON_CHAT_PLANE
+ SET_PLANE_EXPLICIT(balloon_alert, BALLOON_CHAT_PLANE, src)
balloon_alert.alpha = 0
balloon_alert.appearance_flags = RESET_ALPHA|RESET_COLOR|RESET_TRANSFORM
balloon_alert.maptext = MAPTEXT("[text]")
@@ -79,8 +79,16 @@
easing = CUBIC_EASING | EASE_IN,
)
+ LAZYADD(update_on_z, balloon_alert)
+ // These two timers are not the same
+ // One manages the relation to the atom that spawned us, the other to the client we're displaying to
+ // We could lose our loc, and still need to talk to our client, so they are done seperately
+ addtimer(CALLBACK(balloon_alert.loc, .proc/forget_balloon_alert, balloon_alert), BALLOON_TEXT_TOTAL_LIFETIME(duration_mult))
addtimer(CALLBACK(GLOBAL_PROC, .proc/remove_image_from_client, balloon_alert, viewer_client), BALLOON_TEXT_TOTAL_LIFETIME(duration_mult))
+/atom/proc/forget_balloon_alert(image/balloon_alert)
+ LAZYREMOVE(update_on_z, balloon_alert)
+
#undef BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MIN
#undef BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MULT
#undef BALLOON_TEXT_FADE_TIME
diff --git a/code/modules/buildmode/bm_mode.dm b/code/modules/buildmode/bm_mode.dm
index 20e5f097538..8fcfa9b7f4f 100644
--- a/code/modules/buildmode/bm_mode.dm
+++ b/code/modules/buildmode/bm_mode.dm
@@ -55,16 +55,16 @@
overlaystate = "blueOverlay"
var/image/I = image('icons/turf/overlays.dmi', T, overlaystate)
- I.plane = ABOVE_LIGHTING_PLANE
+ SET_PLANE(I, ABOVE_LIGHTING_PLANE, T)
preview += I
BM.holder.images += preview
return T
/datum/buildmode_mode/proc/highlight_region(region)
BM.holder.images -= preview
- for(var/t in region)
- var/image/I = image('icons/turf/overlays.dmi', t, "redOverlay")
- I.plane = ABOVE_LIGHTING_PLANE
+ for(var/turf/member as anything in region)
+ var/image/I = image('icons/turf/overlays.dmi', member, "redOverlay")
+ SET_PLANE(I, ABOVE_LIGHTING_PLANE, member)
preview += I
BM.holder.images += preview
diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm
index f813a252d62..b34c66bfadf 100644
--- a/code/modules/cargo/centcom_podlauncher.dm
+++ b/code/modules/cargo/centcom_podlauncher.dm
@@ -66,7 +66,6 @@
// Stuff needed to render the map
var/map_name
var/atom/movable/screen/map_view/cam_screen
- var/list/cam_plane_masters
var/atom/movable/screen/background/cam_background
var/tabIndex = 1
var/renderLighting = FALSE
@@ -101,28 +100,18 @@
map_name = "admin_supplypod_bay_[REF(src)]_map"
// Initialize map objects
cam_screen = new
- cam_screen.name = "screen"
- cam_screen.assigned_map = map_name
- cam_screen.del_on_map_removal = TRUE
- cam_screen.screen_loc = "[map_name]:1,1"
- cam_plane_masters = list()
- for(var/plane in subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/blackness)
- var/atom/movable/screen/plane_master/instance = new plane()
- if (!renderLighting && instance.plane == LIGHTING_PLANE)
- instance.alpha = 100
- if(instance.blend_mode_override)
- instance.blend_mode = instance.blend_mode_override
- instance.assigned_map = map_name
- instance.del_on_map_removal = TRUE
- instance.screen_loc = "[map_name]:CENTER"
- cam_plane_masters += instance
+ cam_screen.generate_view(map_name)
+
+ var/datum/plane_master_group/planes = cam_screen.display_to(holder.mob)
+
+ if(!renderLighting)
+ for(var/atom/movable/screen/plane_master/instance in holder.mob.hud_used.get_true_plane_masters(LIGHTING_PLANE, planes.key))
+ instance.set_alpha(100)
+
cam_background = new
cam_background.assigned_map = map_name
cam_background.del_on_map_removal = TRUE
refreshView()
- holder.register_map_obj(cam_screen)
- for(var/plane in cam_plane_masters)
- holder.register_map_obj(plane)
holder.register_map_obj(cam_background)
/datum/centcom_podlauncher/ui_state(mob/user)
@@ -552,9 +541,7 @@
/datum/centcom_podlauncher/ui_close(mob/user) //Uses the destroy() proc. When the user closes the UI, we clean up the temp_pod and supplypod_selector variables.
QDEL_NULL(temp_pod)
- user.client?.clear_map(map_name)
QDEL_NULL(cam_screen)
- QDEL_LIST(cam_plane_masters)
QDEL_NULL(cam_background)
qdel(src)
diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm
index 6c2eb519dd3..5c279d83cc2 100644
--- a/code/modules/cargo/supplypod.dm
+++ b/code/modules/cargo/supplypod.dm
@@ -463,9 +463,15 @@
glow_effect.icon_state = "pod_glow_" + GLOB.podstyles[style][POD_GLOW]
vis_contents += glow_effect
glow_effect.layer = GASFIRE_LAYER
- glow_effect.plane = ABOVE_GAME_PLANE
+ SET_PLANE_EXPLICIT(glow_effect, ABOVE_GAME_PLANE, src)
RegisterSignal(glow_effect, COMSIG_PARENT_QDELETING, .proc/remove_glow)
+/obj/structure/closet/supplypod/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ SET_PLANE_EXPLICIT(glow_effect, ABOVE_GAME_PLANE, src)
+
/obj/structure/closet/supplypod/proc/endGlow()
if(!glow_effect)
return
@@ -629,7 +635,7 @@
setupSmoke(rotation)
pod.transform = matrix().Turn(rotation)
pod.layer = FLY_LAYER
- pod.plane = ABOVE_GAME_PLANE
+ SET_PLANE_EXPLICIT(pod, ABOVE_GAME_PLANE, src)
if (pod.style != STYLE_INVISIBLE)
animate(pod, pixel_z = -1 * abs(sin(rotation))*4, pixel_x = SUPPLYPOD_X_OFFSET + (sin(rotation) * 20), time = pod.delays[POD_FALLING], easing = LINEAR_EASING) //Make the pod fall! At an angle!
addtimer(CALLBACK(src, .proc/endLaunch), pod.delays[POD_FALLING], TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation
@@ -637,11 +643,12 @@
/obj/effect/pod_landingzone/proc/setupSmoke(rotation)
if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH)
return
+ var/turf/our_turf = get_turf(drop_location())
for ( var/i in 1 to length(smoke_effects))
var/obj/effect/supplypod_smoke/smoke_part = new (drop_location())
if (i == 1)
smoke_part.layer = FLY_LAYER
- smoke_part.plane = ABOVE_GAME_PLANE
+ SET_PLANE(smoke_part, ABOVE_GAME_PLANE, our_turf)
smoke_part.icon_state = "smoke_start"
smoke_part.transform = matrix().Turn(rotation)
smoke_effects[i] = smoke_part
@@ -660,9 +667,10 @@
animate(smoke_part.get_filter("smoke_blur"), size = 6, time = 15, easing = CUBIC_EASING|EASE_OUT, flags = ANIMATION_PARALLEL)
/obj/effect/pod_landingzone/proc/endLaunch()
+ var/turf/our_turf = get_turf(drop_location())
pod.tryMakeRubble(drop_location())
pod.layer = initial(pod.layer)
- pod.plane = initial(pod.plane)
+ SET_PLANE(pod, initial(pod.plane), our_turf)
pod.endGlow()
QDEL_NULL(helper)
pod.preOpen() //Begin supplypod open procedures. Here effects like explosions, damage, and other dangerous (and potentially admin-caused, if the centcom_podlauncher datum was used) memes will take place
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 97f70675dfd..c498395b4c3 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -1035,6 +1035,12 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
/client/proc/rescale_view(change, min, max)
view_size.setTo(clamp(change, min, max), clamp(change, min, max))
+/client/proc/set_eye(new_eye)
+ if(new_eye == eye)
+ return
+ var/atom/old_eye = eye
+ eye = new_eye
+ SEND_SIGNAL(src, COMSIG_CLIENT_SET_EYE, old_eye, new_eye)
/**
* Updates the keybinds for special keys
*
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 0ac9d2b225f..e531f6d602a 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -61,7 +61,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/list/favorite_outfits = list()
/// A preview of the current character
- var/atom/movable/screen/character_preview_view/character_preview_view
+ var/atom/movable/screen/map_view/char_preview/character_preview_view
/// A list of instantiated middleware
var/list/datum/preference_middleware/middleware = list()
@@ -121,19 +121,24 @@ GLOBAL_LIST_EMPTY(preferences_datums)
save_character() //let's save this new random character so it doesn't keep generating new ones.
/datum/preferences/ui_interact(mob/user, datum/tgui/ui)
- // If you leave and come back, re-register the character preview
- if (!isnull(character_preview_view) && !(character_preview_view in user.client?.screen))
- user.client?.register_map_obj(character_preview_view)
+ // There used to be code here that readded the preview view if you "rejoined"
+ // I'm making the assumption that ui close will be called whenever a user logs out, or loses a window
+ // If this isn't the case, kill me and restore the code, thanks
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
+ character_preview_view = create_character_preview_view(user)
+
ui = new(user, src, "PreferencesMenu")
ui.set_autoupdate(FALSE)
ui.open()
// HACK: Without this the character starts out really tiny because of some BYOND bug.
// You can fix it by changing a preference, so let's just forcably update the body to emulate this.
- addtimer(CALLBACK(character_preview_view, /atom/movable/screen/character_preview_view/proc/update_body), 1 SECONDS)
+ // Lemon from the future: this issue appears to replicate if the byond map (what we're relaying here)
+ // Is shown while the client's mouse is on the screen. As soon as their mouse enters the main map, it's properly scaled
+ // I hate this place
+ addtimer(CALLBACK(character_preview_view, /atom/movable/screen/map_view/char_preview/proc/update_body), 1 SECONDS)
/datum/preferences/ui_state(mob/user)
return GLOB.always_state
@@ -146,13 +151,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
/datum/preferences/ui_data(mob/user)
var/list/data = list()
- if (isnull(character_preview_view))
- character_preview_view = create_character_preview_view(user)
- else if (character_preview_view.client != parent)
- // The client re-logged, and doing this when they log back in doesn't seem to properly
- // carry emissives.
- character_preview_view.register_to_client(parent)
-
if (tainted_character_profiles)
data["character_profiles"] = create_character_profiles()
tainted_character_profiles = FALSE
@@ -291,9 +289,10 @@ GLOBAL_LIST_EMPTY(preferences_datums)
return TRUE
/datum/preferences/proc/create_character_preview_view(mob/user)
- character_preview_view = new(null, src, user.client)
+ character_preview_view = new(null, src)
+ character_preview_view.generate_view("character_preview_[REF(character_preview_view)]")
character_preview_view.update_body()
- character_preview_view.register_to_client(user.client)
+ character_preview_view.display_to(user)
return character_preview_view
@@ -333,62 +332,34 @@ GLOBAL_LIST_EMPTY(preferences_datums)
value_cache -= preference.type
preference.apply_to_client(parent, read_preference(preference.type))
-// This is necessary because you can open the set preferences menu before
-// the atoms SS is done loading.
-INITIALIZE_IMMEDIATE(/atom/movable/screen/character_preview_view)
-
/// A preview of a character for use in the preferences menu
-/atom/movable/screen/character_preview_view
+/atom/movable/screen/map_view/char_preview
name = "character_preview"
- del_on_map_removal = FALSE
- layer = GAME_PLANE
- plane = GAME_PLANE
/// The body that is displayed
var/mob/living/carbon/human/dummy/body
-
/// The preferences this refers to
var/datum/preferences/preferences
- var/list/plane_masters = list()
-
- /// The client that is watching this view
- var/client/client
-
-/atom/movable/screen/character_preview_view/Initialize(mapload, datum/preferences/preferences, client/client)
+/atom/movable/screen/map_view/char_preview/Initialize(mapload, datum/preferences/preferences)
. = ..()
-
- assigned_map = "character_preview_[REF(src)]"
- set_position(1, 1)
-
src.preferences = preferences
-/atom/movable/screen/character_preview_view/Destroy()
+/atom/movable/screen/map_view/char_preview/Destroy()
QDEL_NULL(body)
-
- for (var/plane_master in plane_masters)
- client?.screen -= plane_master
- qdel(plane_master)
-
- client?.clear_map(assigned_map)
-
preferences?.character_preview_view = null
-
- client = null
- plane_masters = null
preferences = null
-
return ..()
/// Updates the currently displayed body
-/atom/movable/screen/character_preview_view/proc/update_body()
+/atom/movable/screen/map_view/char_preview/proc/update_body()
if (isnull(body))
create_body()
else
body.wipe_state()
appearance = preferences.render_new_preview_appearance(body)
-/atom/movable/screen/character_preview_view/proc/create_body()
+/atom/movable/screen/map_view/char_preview/proc/create_body()
QDEL_NULL(body)
body = new
@@ -396,24 +367,6 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/character_preview_view)
// Without this, it doesn't show up in the menu
body.appearance_flags &= ~KEEP_TOGETHER
-/// Registers the relevant map objects to a client
-/atom/movable/screen/character_preview_view/proc/register_to_client(client/client)
- QDEL_LIST(plane_masters)
-
- src.client = client
-
- if (!client)
- return
-
- for (var/plane_master_type in subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/blackness)
- var/atom/movable/screen/plane_master/plane_master = new plane_master_type()
- plane_master.screen_loc = "[assigned_map]:CENTER"
- client?.screen |= plane_master
-
- plane_masters += plane_master
-
- client?.register_map_obj(src)
-
/datum/preferences/proc/create_character_profiles()
var/list/profiles = list()
diff --git a/code/modules/client/preferences/ambient_occlusion.dm b/code/modules/client/preferences/ambient_occlusion.dm
index 33c87c188d7..1a58b065968 100644
--- a/code/modules/client/preferences/ambient_occlusion.dm
+++ b/code/modules/client/preferences/ambient_occlusion.dm
@@ -6,8 +6,5 @@
/datum/preference/toggle/ambient_occlusion/apply_to_client(client/client, value)
/// Backdrop for the game world plane.
- var/atom/movable/screen/plane_master/game_world/plane_master = locate() in client?.screen
- if (!plane_master)
- return
-
- plane_master.backdrop(client.mob)
+ for(var/atom/movable/screen/plane_master/rendering_plate/game_world/plane_master in client.mob?.hud_used?.get_true_plane_masters(GAME_PLANE))
+ plane_master.show_to(client.mob)
diff --git a/code/modules/clothing/head/cone.dm b/code/modules/clothing/head/cone.dm
index 2754fe8445e..62179f47e30 100644
--- a/code/modules/clothing/head/cone.dm
+++ b/code/modules/clothing/head/cone.dm
@@ -13,10 +13,9 @@
attack_verb_simple = list("warn", "caution", "smash")
resistance_flags = NONE
-
/obj/item/clothing/head/cone/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
if(!isinhands)
- . += emissive_appearance(icon_file, "[icon_state]-emissive", alpha = src.alpha)
+ . += emissive_appearance(icon_file, "[icon_state]-emissive", src, alpha = src.alpha)
diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm
index 6d7bcbc58e0..11d29e43cfa 100644
--- a/code/modules/clothing/head/hardhat.dm
+++ b/code/modules/clothing/head/hardhat.dm
@@ -109,7 +109,7 @@
/obj/item/clothing/head/hardhat/atmos/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
if(!isinhands)
- . += emissive_appearance(icon_file, "[icon_state]-emissive", alpha = src.alpha)
+ . += emissive_appearance(icon_file, "[icon_state]-emissive", src, alpha = src.alpha)
/obj/item/clothing/head/hardhat/weldhat
name = "welding hard hat"
@@ -208,12 +208,12 @@
/obj/item/clothing/head/hardhat/pumpkinhead/update_overlays()
. = ..()
if(light_on)
- . += emissive_appearance(icon, "carved_pumpkin-emissive", alpha = src.alpha)
+ . += emissive_appearance(icon, "carved_pumpkin-emissive", src, alpha = src.alpha)
/obj/item/clothing/head/hardhat/pumpkinhead/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
if(light_on && !isinhands)
- . += emissive_appearance(icon_file, "carved_pumpkin-emissive", alpha = src.alpha)
+ . += emissive_appearance(icon_file, "carved_pumpkin-emissive", src, alpha = src.alpha)
/obj/item/clothing/head/hardhat/pumpkinhead/turn_on(mob/user)
. = ..()
diff --git a/code/modules/clothing/head/wig.dm b/code/modules/clothing/head/wig.dm
index 00307899410..963b7c272d3 100644
--- a/code/modules/clothing/head/wig.dm
+++ b/code/modules/clothing/head/wig.dm
@@ -44,7 +44,7 @@
. += hair_overlay
// So that the wig actually blocks emissives.
- hair_overlay.overlays += emissive_blocker(hair_overlay.icon, hair_overlay.icon_state, alpha = hair_overlay.alpha)
+ hair_overlay.overlays += emissive_blocker(hair_overlay.icon, hair_overlay.icon_state, src, alpha = hair_overlay.alpha)
/obj/item/clothing/head/wig/attack_self(mob/user)
var/new_style = tgui_input_list(user, "Select a hairstyle", "Wig Styling", GLOB.hairstyles_list - "Bald")
diff --git a/code/modules/clothing/suits/ethereal.dm b/code/modules/clothing/suits/ethereal.dm
index aec24976c9b..79d0811332c 100644
--- a/code/modules/clothing/suits/ethereal.dm
+++ b/code/modules/clothing/suits/ethereal.dm
@@ -18,11 +18,11 @@
/obj/item/clothing/suit/hooded/ethereal_raincoat/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
if(!isinhands)
- . += emissive_appearance('icons/mob/clothing/suits/ethereal.dmi', "eth_raincoat_glow_worn", alpha = src.alpha)
+ . += emissive_appearance('icons/mob/clothing/suits/ethereal.dmi', "eth_raincoat_glow_worn", offset_spokesman = src, alpha = src.alpha)
/obj/item/clothing/suit/hooded/ethereal_raincoat/update_overlays()
. = ..()
- . += emissive_appearance('icons/obj/clothing/suits/ethereal.dmi', "eth_raincoat_glow", alpha = src.alpha)
+ . += emissive_appearance('icons/obj/clothing/suits/ethereal.dmi', "eth_raincoat_glow", offset_spokesman = src, alpha = src.alpha)
/obj/item/clothing/suit/hooded/ethereal_raincoat/trailwarden
name = "trailwarden oilcoat"
diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm
index 8e38b94c6f3..110ce27ba52 100644
--- a/code/modules/clothing/suits/jobs.dm
+++ b/code/modules/clothing/suits/jobs.dm
@@ -151,10 +151,11 @@
resistance_flags = NONE
species_exception = list(/datum/species/golem)
+// Lemom todo: and here
/obj/item/clothing/suit/hazardvest/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
if(!isinhands)
- . += emissive_appearance(icon_file, "[icon_state]-emissive", alpha = src.alpha)
+ . += emissive_appearance(icon_file, "[icon_state]-emissive", src, alpha = src.alpha)
//Lawyer
/obj/item/clothing/suit/toggle/lawyer
diff --git a/code/modules/clothing/suits/utility.dm b/code/modules/clothing/suits/utility.dm
index 27cd4159499..5d751623c06 100644
--- a/code/modules/clothing/suits/utility.dm
+++ b/code/modules/clothing/suits/utility.dm
@@ -42,7 +42,7 @@
/obj/item/clothing/suit/utility/fire/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
if(!isinhands)
- . += emissive_appearance(icon_file, "[icon_state]-emissive", alpha = src.alpha)
+ . += emissive_appearance(icon_file, "[icon_state]-emissive", src, alpha = src.alpha)
/obj/item/clothing/suit/utility/fire/firefighter
icon_state = "firesuit"
@@ -168,4 +168,4 @@
/obj/item/clothing/suit/utility/radiation/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
if(!isinhands)
- . += emissive_appearance(icon_file, "[icon_state]-emissive", alpha = src.alpha)
+ . += emissive_appearance(icon_file, "[icon_state]-emissive", src, alpha = src.alpha)
diff --git a/code/modules/clothing/suits/wintercoats.dm b/code/modules/clothing/suits/wintercoats.dm
index efc3a21fde7..2f1699bb853 100644
--- a/code/modules/clothing/suits/wintercoats.dm
+++ b/code/modules/clothing/suits/wintercoats.dm
@@ -364,7 +364,7 @@
/obj/item/clothing/suit/hooded/wintercoat/engineering/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
if(!isinhands)
- . += emissive_appearance(icon_file, "[icon_state]-emissive", alpha = src.alpha)
+ . += emissive_appearance(icon_file, "[icon_state]-emissive", src, alpha = src.alpha)
/obj/item/clothing/head/hooded/winterhood/engineering
desc = "A yellow winter coat hood. Definitely not a replacement for a hard hat."
@@ -374,7 +374,7 @@
/obj/item/clothing/head/hooded/winterhood/engineering/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
if(!isinhands)
- . += emissive_appearance(icon_file, "[icon_state]-emissive", alpha = src.alpha)
+ . += emissive_appearance(icon_file, "[icon_state]-emissive", src, alpha = src.alpha)
// Chief Engineer
/obj/item/clothing/suit/hooded/wintercoat/engineering/ce
diff --git a/code/modules/clothing/under/accessories.dm b/code/modules/clothing/under/accessories.dm
index 51ac6064ee5..7b0d9bd487e 100755
--- a/code/modules/clothing/under/accessories.dm
+++ b/code/modules/clothing/under/accessories.dm
@@ -63,7 +63,7 @@
pixel_x -= 8
pixel_y += 8
layer = initial(layer)
- plane = initial(plane)
+ SET_PLANE_IMPLICIT(src, initial(plane))
U.cut_overlays()
U.attached_accessory = null
U.accessory_overlay = null
diff --git a/code/modules/clothing/under/ethereal.dm b/code/modules/clothing/under/ethereal.dm
index f8ccd8e524b..6361ab549de 100644
--- a/code/modules/clothing/under/ethereal.dm
+++ b/code/modules/clothing/under/ethereal.dm
@@ -17,11 +17,11 @@
/obj/item/clothing/under/ethereal_tunic/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
if(!isinhands)
- . += emissive_appearance('icons/mob/clothing/under/ethereal.dmi', "eth_tunic_emissive_worn", alpha = src.alpha)
+ . += emissive_appearance('icons/mob/clothing/under/ethereal.dmi', "eth_tunic_emissive_worn", offset_spokesman = src, alpha = src.alpha)
/obj/item/clothing/under/ethereal_tunic/update_overlays()
. = ..()
- . += emissive_appearance('icons/obj/clothing/under/ethereal.dmi', "eth_tunic_emissive", alpha = src.alpha)
+ . += emissive_appearance('icons/obj/clothing/under/ethereal.dmi', "eth_tunic_emissive", offset_spokesman = src, alpha = src.alpha)
/obj/item/clothing/under/ethereal_tunic/trailwarden
name = "trailwarden tunic"
diff --git a/code/modules/events/portal_storm.dm b/code/modules/events/portal_storm.dm
index 2934d499133..81dff0e37b8 100644
--- a/code/modules/events/portal_storm.dm
+++ b/code/modules/events/portal_storm.dm
@@ -37,12 +37,16 @@
var/list/hostiles_spawn = list()
var/list/hostile_types = list()
var/number_of_hostiles
- var/mutable_appearance/storm
+ /// List of mutable appearances in the form (plane offset + 1 -> appearance)
+ var/list/mutable_appearance/storm_appearances
/datum/round_event/portal_storm/setup()
- storm = mutable_appearance('icons/obj/engine/energy_ball.dmi', "energy_ball_fast", FLY_LAYER)
- storm.plane = ABOVE_GAME_PLANE
- storm.color = "#00FF00"
+ storm_appearances = list()
+ for(var/offset in 0 to SSmapping.max_plane_offset)
+ var/mutable_appearance/storm = mutable_appearance('icons/obj/engine/energy_ball.dmi', "energy_ball_fast", FLY_LAYER)
+ SET_PLANE_W_SCALAR(storm, ABOVE_GAME_PLANE, offset)
+ storm.color = "#00FF00"
+ storm_appearances += storm
number_of_bosses = 0
for(var/boss in boss_types)
@@ -101,7 +105,7 @@
log_game("Portal Storm failed to spawn effect due to an invalid location.")
return
T = get_step(T, SOUTHWEST) //align center of image with turf
- flick_overlay_static(storm, T, 15)
+ flick_overlay_static(storm_appearances[GET_TURF_PLANE_OFFSET(T) + 1], T, 15)
playsound(T, 'sound/magic/lightningbolt.ogg', rand(80, 100), TRUE)
/datum/round_event/portal_storm/proc/spawn_hostile()
diff --git a/code/modules/food_and_drinks/machinery/griddle.dm b/code/modules/food_and_drinks/machinery/griddle.dm
index f8551af60b8..4b65b96e75f 100644
--- a/code/modules/food_and_drinks/machinery/griddle.dm
+++ b/code/modules/food_and_drinks/machinery/griddle.dm
@@ -95,18 +95,20 @@
vis_contents += item_to_grill
griddled_objects += item_to_grill
item_to_grill.flags_1 |= IS_ONTOP_1
+ item_to_grill.vis_flags |= VIS_INHERIT_PLANE
RegisterSignal(item_to_grill, COMSIG_MOVABLE_MOVED, .proc/ItemMoved)
RegisterSignal(item_to_grill, COMSIG_GRILL_COMPLETED, .proc/GrillCompleted)
RegisterSignal(item_to_grill, COMSIG_PARENT_QDELETING, .proc/ItemRemovedFromGrill)
update_grill_audio()
update_appearance()
-/obj/machinery/griddle/proc/ItemRemovedFromGrill(obj/item/I)
+/obj/machinery/griddle/proc/ItemRemovedFromGrill(obj/item/ungrill)
SIGNAL_HANDLER
- I.flags_1 &= ~IS_ONTOP_1
- griddled_objects -= I
- vis_contents -= I
- UnregisterSignal(I, list(COMSIG_GRILL_COMPLETED, COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
+ ungrill.flags_1 &= ~IS_ONTOP_1
+ ungrill.vis_flags &= ~VIS_INHERIT_PLANE
+ griddled_objects -= ungrill
+ vis_contents -= ungrill
+ UnregisterSignal(ungrill, list(COMSIG_GRILL_COMPLETED, COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
update_grill_audio()
/obj/machinery/griddle/proc/ItemMoved(obj/item/I, atom/OldLoc, Dir, Forced)
diff --git a/code/modules/food_and_drinks/machinery/oven.dm b/code/modules/food_and_drinks/machinery/oven.dm
index 11751bb63dd..13f8895187e 100644
--- a/code/modules/food_and_drinks/machinery/oven.dm
+++ b/code/modules/food_and_drinks/machinery/oven.dm
@@ -55,7 +55,7 @@
else
. += mutable_appearance(icon, "oven_lid_closed")
if(used_tray?.contents.len)
- . += emissive_appearance(icon, "oven_light_mask", alpha = src.alpha)
+ . += emissive_appearance(icon, "oven_light_mask", src, alpha = src.alpha)
/obj/machinery/oven/process(delta_time)
..()
@@ -101,6 +101,7 @@
oven_tray.vis_flags |= VIS_HIDE
vis_contents += oven_tray
oven_tray.flags_1 |= IS_ONTOP_1
+ oven_tray.vis_flags |= VIS_INHERIT_PLANE
oven_tray.pixel_y = OVEN_TRAY_Y_OFFSET
oven_tray.pixel_x = OVEN_TRAY_X_OFFSET
@@ -116,6 +117,7 @@
/obj/machinery/oven/proc/tray_removed_from_oven(obj/item/oven_tray)
SIGNAL_HANDLER
oven_tray.flags_1 &= ~IS_ONTOP_1
+ oven_tray.vis_flags &= ~VIS_INHERIT_PLANE
vis_contents -= oven_tray
used_tray = null
UnregisterSignal(oven_tray, COMSIG_MOVABLE_MOVED)
diff --git a/code/modules/food_and_drinks/machinery/smartfridge.dm b/code/modules/food_and_drinks/machinery/smartfridge.dm
index 78777c381c1..0529c5bf313 100644
--- a/code/modules/food_and_drinks/machinery/smartfridge.dm
+++ b/code/modules/food_and_drinks/machinery/smartfridge.dm
@@ -66,7 +66,7 @@
/obj/machinery/smartfridge/update_overlays()
. = ..()
if(!machine_stat)
- . += emissive_appearance(icon, "[initial(icon_state)]-light-mask", alpha = src.alpha)
+ . += emissive_appearance(icon, "[initial(icon_state)]-light-mask", src, alpha = src.alpha)
/obj/machinery/smartfridge/wrench_act(mob/living/user, obj/item/tool)
. = ..()
diff --git a/code/modules/food_and_drinks/plate.dm b/code/modules/food_and_drinks/plate.dm
index 02ea925f67c..6bb48f2174d 100644
--- a/code/modules/food_and_drinks/plate.dm
+++ b/code/modules/food_and_drinks/plate.dm
@@ -45,6 +45,7 @@
/obj/item/plate/proc/AddToPlate(obj/item/item_to_plate)
vis_contents += item_to_plate
item_to_plate.flags_1 |= IS_ONTOP_1
+ item_to_plate.vis_flags |= VIS_INHERIT_PLANE
RegisterSignal(item_to_plate, COMSIG_MOVABLE_MOVED, .proc/ItemMoved)
RegisterSignal(item_to_plate, COMSIG_PARENT_QDELETING, .proc/ItemMoved)
update_appearance()
@@ -52,6 +53,7 @@
///This proc cleans up any signals on the item when it is removed from a plate, and ensures it has the correct state again.
/obj/item/plate/proc/ItemRemovedFromPlate(obj/item/removed_item)
removed_item.flags_1 &= ~IS_ONTOP_1
+ removed_item.vis_flags &= ~VIS_INHERIT_PLANE
vis_contents -= removed_item
UnregisterSignal(removed_item, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
diff --git a/code/modules/food_and_drinks/restaurant/_venue.dm b/code/modules/food_and_drinks/restaurant/_venue.dm
index 1feeec7e0e6..8dda83986e5 100644
--- a/code/modules/food_and_drinks/restaurant/_venue.dm
+++ b/code/modules/food_and_drinks/restaurant/_venue.dm
@@ -85,6 +85,7 @@
food_image.loc = customer_pawn
food_image.pixel_y = 32
food_image.pixel_x = 16
+ SET_PLANE_EXPLICIT(food_image, HUD_PLANE, customer_pawn)
food_image.plane = HUD_PLANE
food_image.appearance_flags = RESET_COLOR
customer_pawn.hud_to_show_on_hover = customer_pawn.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/food_demands, "food_thoughts", food_image)
diff --git a/code/modules/hallucination/_hallucination.dm b/code/modules/hallucination/_hallucination.dm
index 90622ed430f..d193adbf27a 100644
--- a/code/modules/hallucination/_hallucination.dm
+++ b/code/modules/hallucination/_hallucination.dm
@@ -135,6 +135,12 @@
who_sees_us.Cut() // probably not needed but who knows
return ..()
+/obj/effect/client_image_holder/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ SET_PLANE(shown_image, PLANE_TO_TRUE(shown_image.plane), new_turf)
+
/// Signal proc to clean up references if people who see us are deleted.
/obj/effect/client_image_holder/proc/remove_seer(mob/source)
SIGNAL_HANDLER
@@ -150,7 +156,7 @@
/// Generates the image which we take on.
/obj/effect/client_image_holder/proc/generate_image()
var/image/created = image(image_icon, src, image_state, image_layer, dir = src.dir)
- created.plane = image_plane
+ SET_PLANE_EXPLICIT(created, image_plane, src)
created.pixel_x = image_pixel_x
created.pixel_y = image_pixel_y
if(image_color)
diff --git a/code/modules/hallucination/fake_plasmaflood.dm b/code/modules/hallucination/fake_plasmaflood.dm
index d352b4d7c54..5b3a67f5312 100644
--- a/code/modules/hallucination/fake_plasmaflood.dm
+++ b/code/modules/hallucination/fake_plasmaflood.dm
@@ -78,7 +78,7 @@
var/image/plasma_image = image(image_icon, image_holder, image_state, FLY_LAYER)
plasma_image.alpha = 50
- plasma_image.plane = ABOVE_GAME_PLANE
+ SET_PLANE_EXPLICIT(plasma_image, ABOVE_GAME_PLANE, to_flood)
flood_images += plasma_image
/datum/hallucination/fake_flood/Destroy()
diff --git a/code/modules/hydroponics/grown/towercap.dm b/code/modules/hydroponics/grown/towercap.dm
index e4a4534212e..3634f1e9945 100644
--- a/code/modules/hydroponics/grown/towercap.dm
+++ b/code/modules/hydroponics/grown/towercap.dm
@@ -143,7 +143,30 @@
/obj/structure/punji_sticks/Initialize(mapload)
. = ..()
AddComponent(/datum/component/caltrop, min_damage = 20, max_damage = 30, flags = CALTROP_BYPASS_SHOES)
- stab_overlay = mutable_appearance(icon, "[icon_state]_stab", layer = ABOVE_MOB_LAYER, plane = GAME_PLANE_FOV_HIDDEN)
+ build_stab_overlay()
+
+/obj/structure/punji_sticks/proc/build_stab_overlay()
+ stab_overlay = mutable_appearance(icon, "[icon_state]_stab", layer = ABOVE_MOB_LAYER, offset_spokesman = src, plane = GAME_PLANE_FOV_HIDDEN)
+
+/obj/structure/punji_sticks/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ build_stab_overlay()
+ update_appearance()
+
+/obj/structure/punji_sticks/post_buckle_mob(mob/living/M)
+ update_appearance()
+ return ..()
+
+/obj/structure/punji_sticks/post_unbuckle_mob(mob/living/M)
+ update_appearance()
+ return ..()
+
+/obj/structure/punji_sticks/update_overlays()
+ . = ..()
+ if(length(buckled_mobs))
+ . += stab_overlay
/obj/structure/punji_sticks/intercept_zImpact(list/falling_movables, levels)
. = ..()
@@ -157,7 +180,6 @@
var/mob/living/carbon/fallen_carbon = fallen_mob
fallen_carbon.emote("scream")
fallen_carbon.bleed(30)
- add_overlay(stab_overlay)
. |= FALL_INTERCEPTED | FALL_NO_MESSAGE
/obj/structure/punji_sticks/unbuckle_mob(mob/living/buckled_mob, force, can_fall)
@@ -168,7 +190,6 @@
if(!do_after(buckled_mob, 5 SECONDS, target = src))
to_chat(buckled_mob, span_userdanger("You fail to detach yourself from [src]."))
return
- cut_overlay(stab_overlay)
return ..()
/obj/structure/punji_sticks/spikes
diff --git a/code/modules/industrial_lift/crossing_signal.dm b/code/modules/industrial_lift/crossing_signal.dm
index 4ba9607c3fa..f3cf8b7e674 100644
--- a/code/modules/industrial_lift/crossing_signal.dm
+++ b/code/modules/industrial_lift/crossing_signal.dm
@@ -206,7 +206,7 @@
var/lights_overlay = "[base_icon_state][signal_state]"
. += mutable_appearance(icon, lights_overlay)
- . += emissive_appearance(icon, "[lights_overlay]e", alpha = src.alpha)
+ . += emissive_appearance(icon, "[lights_overlay]e", offset_spokesman = src, alpha = src.alpha)
/// Shifted to NE corner for east side of southern passage.
/obj/machinery/crossing_signal/northeast
diff --git a/code/modules/industrial_lift/industrial_lift.dm b/code/modules/industrial_lift/industrial_lift.dm
index bbbf081a976..c5e36cdbdec 100644
--- a/code/modules/industrial_lift/industrial_lift.dm
+++ b/code/modules/industrial_lift/industrial_lift.dm
@@ -18,7 +18,7 @@ GLOBAL_LIST_EMPTY(lifts)
obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN
appearance_flags = PIXEL_SCALE|KEEP_TOGETHER //no TILE_BOUND since we're potentially multitile
// If we don't do this, we'll build our overlays early, and fuck up how we're rendered
- blocks_emissive = 0
+ blocks_emissive = NONE
///ID used to determine what lift types we can merge with
var/lift_id = BASIC_LIFT_ID
diff --git a/code/modules/industrial_lift/lift_indicator.dm b/code/modules/industrial_lift/lift_indicator.dm
index 52376457ef6..e67d31a8613 100644
--- a/code/modules/industrial_lift/lift_indicator.dm
+++ b/code/modules/industrial_lift/lift_indicator.dm
@@ -160,7 +160,7 @@
if(!is_operational)
return
- . += emissive_appearance(icon, "[base_icon_state]e", alpha = src.alpha)
+ . += emissive_appearance(icon, "[base_icon_state]e", offset_spokesman = src, alpha = src.alpha)
if(!current_lift_direction)
return
@@ -168,6 +168,6 @@
var/arrow_icon_state = "[base_icon_state][current_lift_direction == UP ? "up" : "down"]"
. += mutable_appearance(icon, arrow_icon_state)
- . += emissive_appearance(icon, "[arrow_icon_state]e", alpha = src.alpha)
+ . += emissive_appearance(icon, "[arrow_icon_state]e", offset_spokesman = src, alpha = src.alpha)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/lift_indicator, 38)
diff --git a/code/modules/lighting/emissive_blocker.dm b/code/modules/lighting/emissive_blocker.dm
index 1fb2dba0e1f..a3f291526ff 100644
--- a/code/modules/lighting/emissive_blocker.dm
+++ b/code/modules/lighting/emissive_blocker.dm
@@ -37,9 +37,6 @@
/atom/movable/emissive_blocker/blob_act()
return
-/atom/movable/emissive_blocker/on_changed_z_level(turf/old_turf, turf/new_turf)
- return
-
//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)
diff --git a/code/modules/lighting/lighting_area.dm b/code/modules/lighting/lighting_area.dm
index aded2c37372..ad2a60c8f0f 100644
--- a/code/modules/lighting/lighting_area.dm
+++ b/code/modules/lighting/lighting_area.dm
@@ -1,7 +1,8 @@
/area
luminosity = 1
- ///The mutable appearance we underlay to show light
- var/mutable_appearance/lighting_effect = null
+ ///List of mutable appearances we underlay to show light
+ ///In the form plane offset + 1 -> appearance to use
+ var/list/mutable_appearance/lighting_effects = 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
@@ -47,19 +48,33 @@
add_base_lighting()
/area/proc/remove_base_lighting()
- cut_overlay(lighting_effect)
- QDEL_NULL(lighting_effect)
+ var/list/z_offsets = SSmapping.z_level_to_plane_offset
+ for(var/turf/T in src)
+ if(z_offsets[T.z])
+ T.cut_overlay(lighting_effects[z_offsets[T.z] + 1])
+ cut_overlay(lighting_effects[1])
+ QDEL_LIST(lighting_effects)
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
- lighting_effect.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR
- add_overlay(lighting_effect)
+ lighting_effects = list()
+ for(var/offset in 0 to SSmapping.max_plane_offset)
+ var/mutable_appearance/lighting_effect = mutable_appearance('icons/effects/alphacolors.dmi', "white")
+ SET_PLANE_W_SCALAR(lighting_effect, LIGHTING_PLANE, offset)
+ lighting_effect.layer = LIGHTING_PRIMARY_LAYER
+ lighting_effect.blend_mode = BLEND_ADD
+ lighting_effect.alpha = base_lighting_alpha
+ lighting_effect.color = base_lighting_color
+ lighting_effect.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR
+ lighting_effects += lighting_effect
+ add_overlay(lighting_effects[1])
+ var/list/z_offsets = SSmapping.z_level_to_plane_offset
for(var/turf/T in src)
T.luminosity = 1
+ // This outside loop is EXTREMELY hot because it's run by space tiles. Don't want no part in that
+ // We will only add overlays to turfs not on the first z layer, because that's a significantly lesser portion
+ // And we need to do them separate, or lighting will go fuckey
+ if(z_offsets[T.z])
+ T.add_overlay(lighting_effects[z_offsets[T.z] + 1])
+
area_has_base_lighting = TRUE
diff --git a/code/modules/lighting/lighting_corner.dm b/code/modules/lighting/lighting_corner.dm
index a72d1c5f5cc..40222efccfc 100644
--- a/code/modules/lighting/lighting_corner.dm
+++ b/code/modules/lighting/lighting_corner.dm
@@ -7,6 +7,7 @@
var/x = 0
var/y = 0
+ var/z = 0
var/turf/master_NE
var/turf/master_SE
@@ -35,6 +36,7 @@
src.x = x + 0.5
src.y = y + 0.5
+ src.z = z
// Alright. We're gonna take a set of coords, and from them do a loop clockwise
// To build out the turfs adjacent to us. This is pretty fast
diff --git a/code/modules/lighting/lighting_source.dm b/code/modules/lighting/lighting_source.dm
index 69cefd48d14..0ad9c4e3669 100644
--- a/code/modules/lighting/lighting_source.dm
+++ b/code/modules/lighting/lighting_source.dm
@@ -33,7 +33,6 @@
/// Whether we have applied our light yet or not.
var/applied = FALSE
-
/// whether we are to be added to SSlighting's sources_queue list for an update
var/needs_update = LIGHTING_NO_UPDATE
@@ -106,11 +105,13 @@
// Yes this doesn't align correctly on anything other than 4 width tabs.
// If you want it to go switch everybody to elastic tab stops.
// Actually that'd be great if you could!
-#define EFFECT_UPDATE(level) \
- if (needs_update == LIGHTING_NO_UPDATE) \
- SSlighting.sources_queue += src; \
- if (needs_update < level) \
- needs_update = level; \
+#define EFFECT_UPDATE(level) \
+ if (needs_update == LIGHTING_NO_UPDATE) { \
+ SSlighting.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.
@@ -135,53 +136,61 @@
/datum/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.
-
// This exists so we can cache the vars used in this macro, and save MASSIVE time :)
// Most of this is saving off datum var accesses, tho some of it does actually cache computation
// You will NEED to call this before you call APPLY_CORNER
-#define SETUP_CORNERS_CACHE(lighting_source) \
- var/_turf_x = lighting_source.pixel_turf.x; \
- var/_turf_y = lighting_source.pixel_turf.y; \
+#define SETUP_CORNERS_CACHE(lighting_source) \
+ var/_turf_x = lighting_source.pixel_turf.x; \
+ var/_turf_y = lighting_source.pixel_turf.y; \
+ var/_turf_z = lighting_source.pixel_turf.z; \
var/_range_divisor = max(1, lighting_source.light_range); \
- var/_light_power = lighting_source.light_power; \
- var/_applied_lum_r = lighting_source.applied_lum_r; \
- var/_applied_lum_g = lighting_source.applied_lum_g; \
- var/_applied_lum_b = lighting_source.applied_lum_b; \
- var/_lum_r = lighting_source.lum_r; \
- var/_lum_g = lighting_source.lum_g; \
- var/_lum_b = lighting_source.lum_b; \
+ var/_light_power = lighting_source.light_power; \
+ var/_applied_lum_r = lighting_source.applied_lum_r; \
+ var/_applied_lum_g = lighting_source.applied_lum_g; \
+ var/_applied_lum_b = lighting_source.applied_lum_b; \
+ var/_lum_r = lighting_source.lum_r; \
+ var/_lum_g = lighting_source.lum_g; \
+ var/_lum_b = lighting_source.lum_b;
-#define SETUP_CORNERS_REMOVAL_CACHE(lighting_source) \
+#define SETUP_CORNERS_REMOVAL_CACHE(lighting_source) \
var/_applied_lum_r = lighting_source.applied_lum_r; \
var/_applied_lum_g = lighting_source.applied_lum_g; \
var/_applied_lum_b = lighting_source.applied_lum_b;
#define LUM_FALLOFF(C) (1 - CLAMP01(sqrt((C.x - _turf_x) ** 2 + (C.y - _turf_y) ** 2 + LIGHTING_HEIGHT) / _range_divisor))
+// You may notice we still use squares here even though there are three components
+// Because z diffs are so functionally small, cubes and cube roots are too aggressive
+#define LUM_FALLOFF_MULTIZ(C) (1 - CLAMP01(sqrt((C.x - _turf_x) ** 2 + (C.y - _turf_y) ** 2 + abs(C.z - _turf_z) ** 2 + LIGHTING_HEIGHT) / _range_divisor))
+// Macro that applies light to a new corner.
+// It is a macro in the interest of speed, yet not having to copy paste it.
+// If you're wondering what's with the backslashes, the backslashes cause BYOND to not automatically end the line.
+// As such this all gets counted as a single line.
+// The braces and semicolons are there to be able to do this on a single line.
#define APPLY_CORNER(C) \
- . = LUM_FALLOFF(C); \
- . *= _light_power; \
+ if(C.z == _turf_z) { \
+ . = LUM_FALLOFF(C); \
+ } \
+ else { \
+ . = LUM_FALLOFF_MULTIZ(C) \
+ } \
+ . *= _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) \
- ); \
+ (. * _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 \
+ . * _applied_lum_r, \
+ . * _applied_lum_g, \
+ . * _applied_lum_b \
);
/// This is the define used to calculate falloff.
@@ -206,21 +215,30 @@
// Keep in mind. Lighting corners accept the bottom left (northwest) set of cords to them as input
-#define GENERATE_MISSING_CORNERS(gen_for) \
- if (!gen_for.lighting_corner_NE) { \
- gen_for.lighting_corner_NE = new /datum/lighting_corner(gen_for.x, gen_for.y, gen_for.z); \
- } \
- if (!gen_for.lighting_corner_SE) { \
- gen_for.lighting_corner_SE = new /datum/lighting_corner(gen_for.x, gen_for.y - 1, gen_for.z); \
- } \
- if (!gen_for.lighting_corner_SW) { \
+#define GENERATE_MISSING_CORNERS(gen_for) \
+ if (!gen_for.lighting_corner_NE) { \
+ gen_for.lighting_corner_NE = new /datum/lighting_corner(gen_for.x, gen_for.y, gen_for.z); \
+ } \
+ if (!gen_for.lighting_corner_SE) { \
+ gen_for.lighting_corner_SE = new /datum/lighting_corner(gen_for.x, gen_for.y - 1, gen_for.z); \
+ } \
+ if (!gen_for.lighting_corner_SW) { \
gen_for.lighting_corner_SW = new /datum/lighting_corner(gen_for.x - 1, gen_for.y - 1, gen_for.z); \
- } \
- if (!gen_for.lighting_corner_NW) { \
- gen_for.lighting_corner_NW = new /datum/lighting_corner(gen_for.x - 1, gen_for.y, gen_for.z); \
- } \
+ } \
+ if (!gen_for.lighting_corner_NW) { \
+ gen_for.lighting_corner_NW = new /datum/lighting_corner(gen_for.x - 1, gen_for.y, gen_for.z); \
+ } \
gen_for.lighting_corners_initialised = TRUE;
+#define INSERT_CORNERS(insert_into, draw_from) \
+ if (!draw_from.lighting_corners_initialised) { \
+ GENERATE_MISSING_CORNERS(draw_from); \
+ } \
+ insert_into[draw_from.lighting_corner_NE] = 0; \
+ insert_into[draw_from.lighting_corner_SE] = 0; \
+ insert_into[draw_from.lighting_corner_SW] = 0; \
+ insert_into[draw_from.lighting_corner_NW] = 0;
+
/datum/light_source/proc/update_corners()
var/update = FALSE
var/atom/source_atom = src.source_atom
@@ -285,18 +303,44 @@
var/list/datum/lighting_corner/corners = list()
if (source_turf)
+ var/uses_multiz = !!GET_LOWEST_STACK_OFFSET(source_turf.z)
var/oldlum = source_turf.luminosity
source_turf.luminosity = CEILING(light_range, 1)
- for(var/turf/T in view(CEILING(light_range, 1), source_turf))
- if(IS_OPAQUE_TURF(T))
- continue
- if (!T.lighting_corners_initialised)
- GENERATE_MISSING_CORNERS(T)
+ if(uses_multiz)
+ for(var/turf/T in view(CEILING(light_range, 1), source_turf))
+ if(IS_OPAQUE_TURF(T))
+ continue
+ INSERT_CORNERS(corners, T)
+
+ var/turf/below = SSmapping.get_turf_below(T)
+ var/turf/previous = T
+ while(below)
+ // If we find a non transparent previous, end
+ if(!istransparentturf(previous))
+ break
+ if(IS_OPAQUE_TURF(below))
+ // If we're opaque but the tile above us is transparent, then we should be counted as part of the potential "space"
+ // Of this corner
+ break
+ // Now we do lighting things to it
+ INSERT_CORNERS(corners, below)
+ // ANNND then we add the one below it
+ previous = below
+ below = SSmapping.get_turf_below(below)
+
+ var/turf/above = SSmapping.get_turf_above(T)
+ while(above)
+ // If we find a non transparent turf, end
+ if(!istransparentturf(above) || IS_OPAQUE_TURF(above))
+ break
+ INSERT_CORNERS(corners, above)
+ above = SSmapping.get_turf_above(above)
+ else // Yes I know this could be acomplished with an if in the for loop, but it's fukin lighting code man
+ for(var/turf/T in view(CEILING(light_range, 1), source_turf))
+ if(IS_OPAQUE_TURF(T))
+ continue
+ INSERT_CORNERS(corners, T)
- corners[T.lighting_corner_NE] = 0
- corners[T.lighting_corner_SE] = 0
- corners[T.lighting_corner_SW] = 0
- corners[T.lighting_corner_NW] = 0
source_turf.luminosity = oldlum
SETUP_CORNERS_CACHE(src)
diff --git a/code/modules/lighting/lighting_turf.dm b/code/modules/lighting/lighting_turf.dm
index f9aba7eadd5..a6ea50c5769 100644
--- a/code/modules/lighting/lighting_turf.dm
+++ b/code/modules/lighting/lighting_turf.dm
@@ -100,3 +100,12 @@
else
lighting_clear_overlay()
+ // We will only run this logic on turfs off the prime z layer
+ // Since on the prime z layer, we use an overlay on the area instead, to save time
+ if(SSmapping.z_level_to_plane_offset[z])
+ var/index = SSmapping.z_level_to_plane_offset[z]
+ //Inherit overlay of new area
+ if(old_area.lighting_effects)
+ cut_overlay(old_area.lighting_effects[index])
+ if(new_area.lighting_effects)
+ add_overlay(new_area.lighting_effects[index])
diff --git a/code/modules/lighting/static_lighting_area.dm b/code/modules/lighting/static_lighting_area.dm
index 059a80d02d2..e4d222dfe81 100644
--- a/code/modules/lighting/static_lighting_area.dm
+++ b/code/modules/lighting/static_lighting_area.dm
@@ -1,9 +1,10 @@
+/// List of plane offset + 1 -> mutable appearance to use
+/// Fills with offsets as they are generated
+GLOBAL_LIST_INIT_TYPED(fullbright_overlays, /mutable_appearance, list(create_fullbright_overlay(0)))
-GLOBAL_DATUM_INIT(fullbright_overlay, /mutable_appearance, create_fullbright_overlay())
-
-/proc/create_fullbright_overlay()
+/proc/create_fullbright_overlay(offset)
var/mutable_appearance/lighting_effect = mutable_appearance('icons/effects/alphacolors.dmi', "white")
- lighting_effect.plane = LIGHTING_PLANE
+ SET_PLANE_W_SCALAR(lighting_effect, LIGHTING_PLANE, offset)
lighting_effect.layer = LIGHTING_PRIMARY_LAYER
lighting_effect.blend_mode = BLEND_ADD
return lighting_effect
diff --git a/code/modules/mapfluff/ruins/objects_and_mobs/sin_ruins.dm b/code/modules/mapfluff/ruins/objects_and_mobs/sin_ruins.dm
index 666dfbaa9f8..b82a69aed90 100644
--- a/code/modules/mapfluff/ruins/objects_and_mobs/sin_ruins.dm
+++ b/code/modules/mapfluff/ruins/objects_and_mobs/sin_ruins.dm
@@ -52,7 +52,7 @@
. = ..()
var/overlay_state = icon_screen
. += mutable_appearance(icon, overlay_state)
- . += emissive_appearance(icon, overlay_state)
+ . += emissive_appearance(icon, overlay_state, src)
/obj/structure/cursed_money
name = "bag of money"
diff --git a/code/modules/mapping/space_management/multiz_helpers.dm b/code/modules/mapping/space_management/multiz_helpers.dm
index 4f9174e719f..39f662deaa4 100644
--- a/code/modules/mapping/space_management/multiz_helpers.dm
+++ b/code/modules/mapping/space_management/multiz_helpers.dm
@@ -32,3 +32,20 @@
/turf/proc/below()
return get_step_multiz(src, DOWN)
+
+/proc/get_lowest_turf(atom/ref)
+ var/turf/us = get_turf(ref)
+ var/next = SSmapping.get_turf_below(us)
+ while(next)
+ us = next
+ next = SSmapping.get_turf_below(us)
+ return us
+
+// I wish this was lisp
+/proc/get_highest_turf(atom/ref)
+ var/turf/us = get_turf(ref)
+ var/next = SSmapping.get_turf_above(us)
+ while(next)
+ us = next
+ next = SSmapping.get_turf_above(us)
+ return us
diff --git a/code/modules/mapping/space_management/space_level.dm b/code/modules/mapping/space_management/space_level.dm
index 6be4f5b8f55..1747806cb21 100644
--- a/code/modules/mapping/space_management/space_level.dm
+++ b/code/modules/mapping/space_management/space_level.dm
@@ -18,8 +18,5 @@
else // in case a single trait is passed in
SSmapping.z_trait_levels[new_traits] += list(new_z)
- if(length(GLOB.default_lighting_underlays_by_z) < z_value)
- GLOB.default_lighting_underlays_by_z.len = z_value
- GLOB.default_lighting_underlays_by_z[z_value] = mutable_appearance(LIGHTING_ICON, "transparent", new_z, LIGHTING_PLANE, 255, RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM)
-
+
set_linkage(new_traits[ZTRAIT_LINKAGE])
diff --git a/code/modules/mapping/space_management/zlevel_manager.dm b/code/modules/mapping/space_management/zlevel_manager.dm
index 1e5d2aafc88..75801d962db 100644
--- a/code/modules/mapping/space_management/zlevel_manager.dm
+++ b/code/modules/mapping/space_management/zlevel_manager.dm
@@ -4,6 +4,8 @@
return
z_list = list()
+ z_level_to_plane_offset = list()
+ z_level_to_lowest_plane_offset = list()
var/list/default_map_traits = DEFAULT_MAP_TRAITS
if (default_map_traits.len != world.maxz)
@@ -14,7 +16,7 @@
for (var/I in 1 to default_map_traits.len)
var/list/features = default_map_traits[I]
var/datum/space_level/S = new(I, features[DL_NAME], features[DL_TRAITS])
- z_list += S
+ manage_z_level(S)
/datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level)
UNTIL(!adding_new_zlevel)
@@ -25,7 +27,7 @@
CHECK_TICK
// TODO: sleep here if the Z level needs to be cleared
var/datum/space_level/S = new z_type(new_z, name, traits)
- z_list += S
+ manage_z_level(S)
generate_linkages_for_z_level(new_z)
calculate_z_level_gravity(new_z)
adding_new_zlevel = FALSE
diff --git a/code/modules/mining/equipment/kheiral_cuffs.dm b/code/modules/mining/equipment/kheiral_cuffs.dm
index 795dcbb662d..c6324af39c0 100644
--- a/code/modules/mining/equipment/kheiral_cuffs.dm
+++ b/code/modules/mining/equipment/kheiral_cuffs.dm
@@ -93,14 +93,15 @@
if(isliving(loc))
connect_kheiral_network(loc)
+// LEMON AND HERE
/obj/item/kheiral_cuffs/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
if(!isinhands)
- . += emissive_appearance(icon_file, "strandcuff_emissive", alpha = src.alpha)
+ . += emissive_appearance(icon_file, "strandcuff_emissive", src, alpha = src.alpha)
/obj/item/kheiral_cuffs/update_overlays()
. = ..()
- . += emissive_appearance(icon, "strand_light", alpha = src.alpha)
+ . += emissive_appearance(icon, "strand_light", src, alpha = src.alpha)
/obj/item/kheiral_cuffs/suicide_act(mob/living/carbon/user)
var/mob/living/carbon/human/victim
diff --git a/code/modules/mining/equipment/mineral_scanner.dm b/code/modules/mining/equipment/mineral_scanner.dm
index ada079e0185..46b6d407e38 100644
--- a/code/modules/mining/equipment/mineral_scanner.dm
+++ b/code/modules/mining/equipment/mineral_scanner.dm
@@ -75,7 +75,7 @@
C.icon_state = M.scan_state
/obj/effect/temp_visual/mining_overlay
- plane = FULLSCREEN_PLANE
+ plane = HIGH_GAME_PLANE
layer = FLASH_LAYER
icon = 'icons/effects/ore_visuals.dmi'
appearance_flags = 0 //to avoid having TILE_BOUND in the flags, so that the 480x480 icon states let you see it no matter where you are
diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm
index f9087750b6e..d7dd174b1c0 100644
--- a/code/modules/mining/lavaland/tendril_loot.dm
+++ b/code/modules/mining/lavaland/tendril_loot.dm
@@ -277,7 +277,7 @@
/obj/effect/wisp/proc/update_user_sight(mob/user)
SIGNAL_HANDLER
- user.sight |= sight_flags
+ user.add_sight(sight_flags)
if(!isnull(lighting_alpha))
user.lighting_alpha = min(user.lighting_alpha, lighting_alpha)
@@ -495,7 +495,7 @@
list_reagents = list(/datum/reagent/flightpotion = 5)
/obj/item/reagent_containers/cup/bottle/potion/update_icon_state()
- icon_state = "potionflask[reagents.total_volume ? null : "_empty"]"
+ icon_state = "potionflask[reagents?.total_volume ? null : "_empty"]"
return ..()
/datum/reagent/flightpotion
@@ -1000,7 +1000,7 @@
/obj/item/cursed_katana/proc/cloak(mob/living/target, mob/user)
user.alpha = 150
user.invisibility = INVISIBILITY_OBSERVER // so hostile mobs cant see us or target us
- user.sight |= SEE_SELF // so we can see us
+ user.add_sight(SEE_SELF) // so we can see us
user.visible_message(span_warning("[user] vanishes into thin air!"),
span_notice("You enter the dark cloak."))
playsound(src, 'sound/magic/smoke.ogg', 50, TRUE)
@@ -1012,7 +1012,7 @@
/obj/item/cursed_katana/proc/uncloak(mob/user)
user.alpha = 255
user.invisibility = 0
- user.sight &= ~SEE_SELF
+ user.clear_sight(SEE_SELF)
user.visible_message(span_warning("[user] appears from thin air!"),
span_notice("You exit the dark cloak."))
playsound(src, 'sound/magic/summonitems_generic.ogg', 50, TRUE)
diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm
index 7228bb97504..d7963681278 100644
--- a/code/modules/mining/machine_redemption.dm
+++ b/code/modules/mining/machine_redemption.dm
@@ -391,10 +391,10 @@
ore_output.pixel_x = 32
ore_input.color = COLOR_MODERATE_BLUE
ore_output.color = COLOR_SECURITY_RED
- var/mutable_appearance/light_in = emissive_appearance(ore_input.icon, ore_input.icon_state, alpha = ore_input.alpha)
+ var/mutable_appearance/light_in = emissive_appearance(ore_input.icon, ore_input.icon_state, offset_spokesman = src, alpha = ore_input.alpha)
light_in.pixel_y = ore_input.pixel_y
light_in.pixel_x = ore_input.pixel_x
- var/mutable_appearance/light_out = emissive_appearance(ore_output.icon, ore_output.icon_state, alpha = ore_output.alpha)
+ var/mutable_appearance/light_out = emissive_appearance(ore_output.icon, ore_output.icon_state, offset_spokesman = src, alpha = ore_output.alpha)
light_out.pixel_y = ore_output.pixel_y
light_out.pixel_x = ore_output.pixel_x
. += ore_input
diff --git a/code/modules/mining/minebot.dm b/code/modules/mining/minebot.dm
index 14d47fb2b67..6a1b836135f 100644
--- a/code/modules/mining/minebot.dm
+++ b/code/modules/mining/minebot.dm
@@ -206,10 +206,10 @@
/datum/action/innate/minedrone/toggle_meson_vision/Activate()
var/mob/living/simple_animal/hostile/mining_drone/user = owner
if(user.sight & SEE_TURFS)
- user.sight &= ~SEE_TURFS
+ user.clear_sight(SEE_TURFS)
user.lighting_alpha = initial(user.lighting_alpha)
else
- user.sight |= SEE_TURFS
+ user.add_sight(SEE_TURFS)
user.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
user.sync_lighting_plane_alpha()
diff --git a/code/modules/mining/satchel_ore_boxdm.dm b/code/modules/mining/satchel_ore_boxdm.dm
index 89537e483b2..564ec2e92b0 100644
--- a/code/modules/mining/satchel_ore_boxdm.dm
+++ b/code/modules/mining/satchel_ore_boxdm.dm
@@ -44,14 +44,17 @@
/obj/structure/ore_box/proc/dump_box_contents()
var/drop = drop_location()
+ var/turf/our_turf = get_turf(src)
for(var/obj/item/stack/ore/O in src)
if(QDELETED(O))
continue
if(QDELETED(src))
break
O.forceMove(drop)
+ SET_PLANE(O, PLANE_TO_TRUE(O.plane), our_turf)
if(TICK_CHECK)
stoplag()
+ our_turf = get_turf(src)
drop = drop_location()
/obj/structure/ore_box/ui_interact(mob/user, datum/tgui/ui)
@@ -95,5 +98,5 @@
qdel(src)
/// Special override for notify_contents = FALSE.
-/obj/structure/ore_box/on_changed_z_level(turf/old_turf, turf/new_turf, notify_contents = FALSE)
+/obj/structure/ore_box/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents = FALSE)
return ..()
diff --git a/code/modules/mob/camera/camera.dm b/code/modules/mob/camera/camera.dm
index 2d1ea422c7a..1cfb31e2e72 100644
--- a/code/modules/mob/camera/camera.dm
+++ b/code/modules/mob/camera/camera.dm
@@ -8,7 +8,7 @@
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
see_in_dark = 7
invisibility = INVISIBILITY_ABSTRACT // No one can see us
- sight = SEE_SELF
+ sight = SEE_SELF | SEE_BLACKNESS
move_on_shuttle = FALSE
/mob/camera/Initialize(mapload)
diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm
index 41bb077153b..e75bd5e7296 100644
--- a/code/modules/mob/dead/dead.dm
+++ b/code/modules/mob/dead/dead.dm
@@ -3,7 +3,7 @@
INITIALIZE_IMMEDIATE(/mob/dead)
/mob/dead
- sight = SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF
+ sight = SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF | SEE_BLACKNESS
move_resist = INFINITY
throwforce = 0
@@ -12,6 +12,7 @@ INITIALIZE_IMMEDIATE(/mob/dead)
if(flags_1 & INITIALIZED_1)
stack_trace("Warning: [src]([type]) initialized multiple times!")
flags_1 |= INITIALIZED_1
+ SET_PLANE_IMPLICIT(src, plane)
tag = "mob_[next_mob_id++]"
add_to_mob_list()
@@ -30,7 +31,8 @@ INITIALIZE_IMMEDIATE(/mob/dead)
var/turf/old_turf = get_turf(src)
var/turf/new_turf = get_turf(destination)
if (old_turf?.z != new_turf?.z)
- on_changed_z_level(old_turf, new_turf)
+ var/same_z_layer = (GET_TURF_PLANE_OFFSET(old_turf) == GET_TURF_PLANE_OFFSET(new_turf))
+ on_changed_z_level(old_turf, new_turf, same_z_layer)
return ..()
/mob/dead/get_status_tab_items()
@@ -120,6 +122,6 @@ INITIALIZE_IMMEDIATE(/mob/dead)
update_z(null)
return ..()
-/mob/dead/on_changed_z_level(turf/old_turf, turf/new_turf)
+/mob/dead/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
..()
update_z(new_turf?.z)
diff --git a/code/modules/mob/dead/new_player/login.dm b/code/modules/mob/dead/new_player/login.dm
index 08969aeef4b..a1b3d8f5c77 100644
--- a/code/modules/mob/dead/new_player/login.dm
+++ b/code/modules/mob/dead/new_player/login.dm
@@ -32,7 +32,7 @@
if(spc && living_player_count() >= spc)
to_chat(src, span_notice("Server Notice:\n \t [CONFIG_GET(string/soft_popcap_message)]"))
- sight |= SEE_TURFS
+ add_sight(SEE_TURFS)
client.playtitlemusic()
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 7b6a13a8f9c..282934a35d9 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -622,9 +622,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
ghost_others = client.prefs.read_preference(/datum/preference/choiced/ghost_others) //A quick update just in case this setting was changed right before calling the proc
if (!ghostvision)
- see_invisible = SEE_INVISIBLE_LIVING
+ set_invis_see(SEE_INVISIBLE_LIVING)
else
- see_invisible = SEE_INVISIBLE_OBSERVER
+ set_invis_see(SEE_INVISIBLE_OBSERVER)
updateghostimages()
@@ -882,7 +882,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/mob/target = observetarget
observetarget = null
client?.perspective = initial(client.perspective)
- sight = initial(sight)
+ set_sight(initial(sight))
if(target)
UnregisterSignal(target, COMSIG_MOVABLE_Z_CHANGED)
LAZYREMOVE(target.observers, src)
@@ -921,10 +921,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
//Istype so we filter out points of interest that are not mobs
if(client && mob_eye && istype(mob_eye))
- client.eye = mob_eye
+ client.set_eye(mob_eye)
client.perspective = EYE_PERSPECTIVE
if(is_secret_level(mob_eye.z) && !client?.holder)
- sight = null //we dont want ghosts to see through walls in secret areas
+ set_sight(null) //we dont want ghosts to see through walls in secret areas
RegisterSignal(mob_eye, COMSIG_MOVABLE_Z_CHANGED, .proc/on_observing_z_changed)
if(mob_eye.hud_used)
client.screen = list()
@@ -936,9 +936,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
SIGNAL_HANDLER
if(is_secret_level(new_turf.z) && !client?.holder)
- sight = null //we dont want ghosts to see through walls in secret areas
+ set_sight(null) //we dont want ghosts to see through walls in secret areas
else
- sight = initial(sight)
+ set_sight(initial(sight))
/mob/dead/observer/verb/register_pai_candidate()
set category = "Ghost"
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index 83ae28b41f9..8e1ccc82b39 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -162,7 +162,7 @@
dropItemToGround(get_item_for_held_index(hand_index), force = TRUE)
I.forceMove(src)
held_items[hand_index] = I
- I.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(I, ABOVE_HUD_PLANE, src)
I.equipped(src, ITEM_SLOT_HANDS)
if(QDELETED(I)) // this is here because some ABSTRACT items like slappers and circle hands could be moved from hand to hand then delete, which meant you'd have a null in your hand until you cleared it (say, by dropping it)
held_items[hand_index] = null
@@ -242,7 +242,7 @@
return FALSE
I.forceMove(drop_location())
I.layer = initial(I.layer)
- I.plane = initial(I.plane)
+ SET_PLANE_EXPLICIT(I, initial(I.plane), drop_location())
I.dropped(src)
return FALSE
@@ -322,7 +322,7 @@
if(client)
client.screen -= I
I.layer = initial(I.layer)
- I.plane = initial(I.plane)
+ SET_PLANE_EXPLICIT(I, initial(I.plane), drop_location())
I.appearance_flags &= ~NO_CLIENT_COLOR
if(!no_move && !(I.item_flags & DROPDEL)) //item may be moved/qdel'd immedietely, don't bother moving it
if (isnull(newloc))
diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm
index f35c2f6bc1b..280bf41c97b 100644
--- a/code/modules/mob/living/carbon/alien/alien.dm
+++ b/code/modules/mob/living/carbon/alien/alien.dm
@@ -4,7 +4,7 @@
gender = FEMALE //All xenos are girls!!
dna = null
faction = list(ROLE_ALIEN)
- sight = SEE_MOBS
+ sight = SEE_MOBS | SEE_BLACKNESS
see_in_dark = 4
verb_say = "hisses"
initial_language_holder = /datum/language_holder/alien
diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid_update_icons.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid_update_icons.dm
index e7d6be07caa..1a60746281a 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/humanoid_update_icons.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid_update_icons.dm
@@ -68,10 +68,11 @@
if(handcuffed)
var/mutable_appearance/handcuff_overlay = mutable_appearance(dmi_file, cuff_icon, -HANDCUFF_LAYER)
if(handcuffed.blocks_emissive)
- handcuff_overlay += emissive_blocker(handcuff_overlay.icon, handcuff_overlay.icon_state, alpha = handcuff_overlay.alpha)
+ handcuff_overlay += emissive_blocker(handcuff_overlay.icon, handcuff_overlay.icon_state, src, alpha = handcuff_overlay.alpha)
overlays_standing[HANDCUFF_LAYER] = handcuff_overlay
apply_overlay(HANDCUFF_LAYER)
+// AND HERE MOTHERFUCKER AHHHHHH
//Royals have bigger sprites, so inhand things must be handled differently.
/mob/living/carbon/alien/humanoid/royal/update_held_items()
@@ -86,7 +87,7 @@
itm_state = l_hand.icon_state
var/mutable_appearance/l_hand_item = mutable_appearance(alt_inhands_file, "[itm_state][caste]_l", -HANDS_LAYER)
if(l_hand.blocks_emissive)
- l_hand_item.overlays += emissive_blocker(l_hand_item.icon, l_hand_item.icon_state, alpha = l_hand_item.alpha)
+ l_hand_item.overlays += emissive_blocker(l_hand_item.icon, l_hand_item.icon_state, src, alpha = l_hand_item.alpha)
hands += l_hand_item
var/obj/item/r_hand = get_item_for_held_index(2)
@@ -96,7 +97,7 @@
itm_state = r_hand.icon_state
var/mutable_appearance/r_hand_item = mutable_appearance(alt_inhands_file, "[itm_state][caste]_r", -HANDS_LAYER)
if(r_hand.blocks_emissive)
- r_hand_item.overlays += emissive_blocker(r_hand_item.icon, r_hand_item.icon_state, alpha = r_hand_item.alpha)
+ r_hand_item.overlays += emissive_blocker(r_hand_item.icon, r_hand_item.icon_state, src, alpha = r_hand_item.alpha)
hands += r_hand_item
overlays_standing[HANDS_LAYER] = hands
diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm
index 7c8e300e297..9b59ea73c21 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm
@@ -22,11 +22,11 @@
/mob/living/carbon/alien/humanoid/royal/on_lying_down(new_lying_angle)
. = ..()
- plane = GAME_PLANE_FOV_HIDDEN //So it won't hide smaller mobs.
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_FOV_HIDDEN) //So it won't hide smaller mobs.
/mob/living/carbon/alien/humanoid/royal/on_standing_up(new_lying_angle)
. = ..()
- plane = initial(plane)
+ SET_PLANE_IMPLICIT(src, initial(plane))
/mob/living/carbon/alien/humanoid/royal/can_inject(mob/user, target_zone, injection_flags)
return FALSE
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index d3fea182034..8bf993f65fd 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -323,7 +323,7 @@
W.dropped(src)
if (W)
W.layer = initial(W.layer)
- W.plane = initial(W.plane)
+ SET_PLANE_EXPLICIT(W, initial(W.plane), src)
changeNext_move(0)
if (legcuffed)
var/obj/item/W = legcuffed
@@ -336,7 +336,7 @@
W.dropped(src)
if (W)
W.layer = initial(W.layer)
- W.plane = initial(W.plane)
+ SET_PLANE_EXPLICIT(W, initial(W.plane), src)
changeNext_move(0)
update_equipment_speed_mods() // In case cuffs ever change speed
@@ -548,24 +548,24 @@
return
if(stat == DEAD)
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
- sight = null
+ set_sight(null)
else if(is_secret_level(z))
- sight = initial(sight)
+ set_sight(initial(sight))
else
- sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
- see_in_dark = 8
- see_invisible = SEE_INVISIBLE_OBSERVER
+ set_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ set_see_in_dark(8)
+ set_invis_see(SEE_INVISIBLE_OBSERVER)
return
- sight = initial(sight)
+ var/new_sight = initial(sight)
lighting_alpha = initial(lighting_alpha)
var/obj/item/organ/internal/eyes/E = getorganslot(ORGAN_SLOT_EYES)
if(!E)
update_tint()
else
- see_invisible = E.see_invisible
- see_in_dark = E.see_in_dark
- sight |= E.sight_flags
+ set_invis_see(E.see_invisible)
+ set_see_in_dark(E.see_in_dark)
+ new_sight |= E.sight_flags
if(!isnull(E.lighting_alpha))
lighting_alpha = E.lighting_alpha
@@ -576,37 +576,38 @@
if(glasses)
var/obj/item/clothing/glasses/G = glasses
- sight |= G.vision_flags
- see_in_dark = max(G.darkness_view, see_in_dark)
+ new_sight |= G.vision_flags
+ set_see_in_dark(max(G.darkness_view, see_in_dark))
if(G.invis_override)
- see_invisible = G.invis_override
+ set_invis_see(G.invis_override)
else
- see_invisible = min(G.invis_view, see_invisible)
+ set_invis_see(min(G.invis_view, see_invisible))
if(!isnull(G.lighting_alpha))
lighting_alpha = min(lighting_alpha, G.lighting_alpha)
if(HAS_TRAIT(src, TRAIT_TRUE_NIGHT_VISION))
lighting_alpha = min(lighting_alpha, LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE)
- see_in_dark = max(see_in_dark, 8)
+ set_see_in_dark(max(see_in_dark, 8))
if(HAS_TRAIT(src, TRAIT_MESON_VISION))
- sight |= SEE_TURFS
+ new_sight |= SEE_TURFS
lighting_alpha = min(lighting_alpha, LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE)
if(HAS_TRAIT(src, TRAIT_THERMAL_VISION))
- sight |= SEE_MOBS
+ new_sight |= SEE_MOBS
lighting_alpha = min(lighting_alpha, LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE)
if(HAS_TRAIT(src, TRAIT_XRAY_VISION))
- sight |= SEE_TURFS|SEE_MOBS|SEE_OBJS
- see_in_dark = max(see_in_dark, 8)
+ new_sight |= SEE_TURFS|SEE_MOBS|SEE_OBJS
+ set_see_in_dark(max(see_in_dark, 8))
if(see_override)
- see_invisible = see_override
+ set_invis_see(see_override)
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
- sight = null
+ new_sight = SEE_BLACKNESS
+ set_sight(new_sight)
return ..()
diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm
index 2aa2b74f39b..74858595106 100644
--- a/code/modules/mob/living/carbon/carbon_update_icons.dm
+++ b/code/modules/mob/living/carbon/carbon_update_icons.dm
@@ -80,6 +80,184 @@
dna.species.handle_body(src) //This calls `handle_mutant_bodyparts` which calls `update_mutant_bodyparts()`. Don't double call!
update_body_parts(is_creating)
+/mob/living/carbon/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ update_z_overlays(GET_TURF_PLANE_OFFSET(new_turf), TRUE)
+
+/mob/living/carbon/proc/refresh_loop(iter_cnt, rebuild = FALSE)
+ for(var/i in 1 to iter_cnt)
+ update_z_overlays(1, rebuild)
+ sleep(3)
+ update_z_overlays(0, rebuild)
+ sleep(3)
+
+#define NEXT_PARENT_COMMAND "next_parent"
+/// Takes a list of mutable appearances
+/// Returns a list in the form:
+/// 1 - a list of all mutable appearances that would need to be updated to change planes in the event of a z layer change, alnongside the commands required
+/// to properly track parents to update
+/// 2 - a list of all parents that will require updating
+/proc/build_planeed_apperance_queue(list/mutable_appearance/appearances)
+ var/list/queue
+ if(islist(appearances))
+ queue = appearances.Copy()
+ else
+ queue = list(appearances)
+ var/queue_index = 0
+ var/list/parent_queue = list()
+
+ // We are essentially going to unroll apperance overlays into a flattened list here, so we can filter out floating planes laster
+ // It will look like "overlay overlay overlay (change overlay parent), overlay overlay etc"
+ // We can use this list to dynamically update these non floating planes, later
+ while(queue_index < length(queue))
+ queue_index++
+ // If it's not a command, we assert that it's an appearance
+ var/mutable_appearance/appearance = queue[queue_index]
+ if(!appearance || appearance == NEXT_PARENT_COMMAND) // Who fucking adds nulls to their sublists god you people are the worst
+ continue
+
+ var/mutable_appearance/new_appearance = new /mutable_appearance()
+ new_appearance.appearance = appearance
+ // Now check its children
+ if(length(appearance.overlays))
+ queue += NEXT_PARENT_COMMAND
+ parent_queue += appearance
+ for(var/mutable_appearance/child_appearance as anything in appearance.overlays)
+ queue += child_appearance
+
+ // Now we have a flattened list of parents and their children
+ // Setup such that walking the list backwards will allow us to properly update overlays
+ // (keeping in mind that overlays only update if an apperance is removed and added, and this pattern applies in a nested fashion)
+
+ // If we found no results, return null
+ if(!length(queue))
+ return null
+
+ // ALRIGHT MOTHERFUCKER
+ // SO
+ // DID YOU KNOW THAT OVERLAY RENDERING BEHAVIOR DEPENDS PARTIALLY ON THE ORDER IN WHICH OVERLAYS ARE ADDED?
+ // WHAT WE'RE DOING HERE ENDS UP REVERSING THE OVERLAYS ADDITION ORDER (when it's walked back to front)
+ // SO GUESS WHAT I'VE GOTTA DO, I'VE GOTTA SWAP ALLLL THE MEMBERS OF THE SUBLISTS
+ // I HATE IT HERE
+ var/lower_parent = 0
+ var/upper_parent = 0
+ var/queue_size = length(queue)
+ while(lower_parent <= queue_size)
+ // Let's reorder our "lists" (spaces between parent changes)
+ // We've got a delta index, and we're gonna essentially use it to get "swap" positions from the top and bottom
+ // We only need to loop over half the deltas to swap all the entries, any more and it'd be redundant
+ // We floor so as to avoid over flipping, and ending up flipping "back" a delta
+ // etc etc
+ var/target = FLOOR((upper_parent - lower_parent) / 2, 1)
+ for(var/delta_index in 1 to target)
+ var/old_lower = queue[lower_parent + delta_index]
+ queue[lower_parent + delta_index] = queue[upper_parent - delta_index]
+ queue[upper_parent - delta_index] = old_lower
+
+ // lower bound moves to the old upper, upper bound finds a new home
+ // Note that the end of the list is a valid upper bound
+ lower_parent = upper_parent // our old upper bound is now our lower bound
+ while(upper_parent <= queue_size)
+ upper_parent += 1
+ if(length(queue) < upper_parent) // Parent found
+ break
+ if(queue[upper_parent] == NEXT_PARENT_COMMAND) // We found em lads
+ break
+
+ // One more thing to do
+ // It's much more convinient for the parent queue to be a list of indexes pointing at queue locations
+ // Rather then a list of copied appearances
+ // Let's turn what we have now into that yeah?
+ // This'll require a loop over both queues
+ // We're using an assoc list here rather then several find()s because I feel like that's more sane
+ var/list/apperance_to_position = list()
+ for(var/i in 1 to length(queue))
+ apperance_to_position[queue[i]] = i
+
+ var/list/parent_indexes = list()
+ for(var/mutable_appearance/parent as anything in parent_queue)
+ parent_indexes += apperance_to_position[parent]
+
+ // Alright. We should now have two queues, a command/appearances one, and a parents queue, which contain no fluff
+ // And when walked backwards allow for proper plane updating
+ var/list/return_pack = list(queue, parent_indexes)
+ return return_pack
+
+// Rebuilding is a hack. We should really store a list of indexes into our existing overlay list or SOMETHING
+// IDK. will work for now though, which is a lot better then not working at all
+/mob/living/carbon/proc/update_z_overlays(new_offset, rebuild = FALSE)
+ // Null entries will be filtered here
+ for(var/i in 1 to length(overlays_standing))
+ var/list/cache_grouping = overlays_standing[i]
+ if(cache_grouping && !islist(cache_grouping))
+ cache_grouping = list(cache_grouping)
+ // Need this so we can have an index, could build index into the list if we need to tho, check
+ if(!length(cache_grouping))
+ continue
+ overlays_standing[i] = update_appearance_planes(cache_grouping, new_offset)
+
+/atom/proc/update_appearance_planes(list/mutable_appearance/appearances, new_offset)
+ var/list/build_list = build_planeed_apperance_queue(appearances)
+
+ if(!length(build_list))
+ return appearances
+
+ // hand_back contains a new copy of the passed in list, with updated values
+ var/list/hand_back = list()
+
+ var/list/processing_queue = build_list[1]
+ var/list/parents_queue = build_list[2]
+ // Now that we have our queues, we're going to walk them forwards to remove, and backwards to add
+ // Note, we need to do this separately because you can only remove a mutable appearance when it
+ // Exactly matches the appearance it had when it was first "made static" (by being added to the overlays list)
+ var/parents_index = 0
+ for(var/item in processing_queue)
+ if(item == NEXT_PARENT_COMMAND)
+ parents_index++
+ continue
+ var/mutable_appearance/iter_apper = item
+ if(parents_index)
+ var/parent_src_index = parents_queue[parents_index]
+ var/mutable_appearance/parent = processing_queue[parent_src_index]
+ parent.overlays -= iter_apper.appearance
+ else // Otherwise, we're at the end of the list, and our parent is the mob
+ cut_overlay(iter_apper)
+
+ // Now the back to front stuff, to readd the updated appearances
+ var/queue_index = length(processing_queue)
+ parents_index = length(parents_queue)
+ while(queue_index >= 1)
+ var/item = processing_queue[queue_index]
+ if(item == NEXT_PARENT_COMMAND)
+ parents_index--
+ queue_index--
+ continue
+ var/mutable_appearance/new_iter = new /mutable_appearance()
+ new_iter.appearance = item
+ if(new_iter.plane != FLOAT_PLANE)
+ // Here, finally, is where we actually update the plane offsets
+ SET_PLANE_W_SCALAR(new_iter, PLANE_TO_TRUE(new_iter.plane), new_offset)
+ if(parents_index)
+ var/parent_src_index = parents_queue[parents_index]
+ var/mutable_appearance/parent = processing_queue[parent_src_index]
+ parent.overlays += new_iter.appearance
+ else
+ add_overlay(new_iter)
+ // chant a protective overlays.Copy to prevent appearance theft and overlay sticking
+ // I'm not joking without this overlays can corrupt and be replaced by other appearances
+ // the compiler might call it useless but I swear it works
+ // we conjure the spirits of the computer with our spells, we conjur- (Hey lemon make a damn issue report already)
+ var/list/does_nothing = new_iter.overlays.Copy()
+ pass(does_nothing)
+ hand_back += new_iter
+
+ queue_index--
+ return hand_back
+
+#undef NEXT_PARENT_COMMAND
+
/mob/living/carbon/regenerate_icons()
if(notransform)
return 1
@@ -235,7 +413,7 @@
if(handcuffed)
var/mutable_appearance/handcuff_overlay = mutable_appearance('icons/mob/simple/mob.dmi', "handcuff1", -HANDCUFF_LAYER)
if(handcuffed.blocks_emissive)
- handcuff_overlay.overlays += emissive_blocker(handcuff_overlay.icon, handcuff_overlay.icon_state, alpha = handcuff_overlay.alpha)
+ handcuff_overlay.overlays += emissive_blocker(handcuff_overlay.icon, handcuff_overlay.icon_state, src, alpha = handcuff_overlay.alpha)
overlays_standing[HANDCUFF_LAYER] = handcuff_overlay
apply_overlay(HANDCUFF_LAYER)
@@ -280,7 +458,7 @@
if(!blocks_emissive)
return
- . += emissive_blocker(standing.icon, standing.icon_state, alpha = standing.alpha)
+ . += emissive_blocker(standing.icon, standing.icon_state, src, alpha = standing.alpha)
///Checks to see if any bodyparts need to be redrawn, then does so. update_limb_data = TRUE redraws the limbs to conform to the owner.
/mob/living/carbon/proc/update_body_parts(update_limb_data)
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 9602b5fc0ad..7fd7a5a48b1 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -718,7 +718,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
accessory_overlay.icon_state = "m_[bodypart]_[accessory.icon_state]_[layertext]"
if(accessory.em_block)
- accessory_overlay.overlays += emissive_blocker(accessory_overlay.icon, accessory_overlay.icon_state, accessory_overlay.alpha)
+ accessory_overlay.overlays += emissive_blocker(accessory_overlay.icon, accessory_overlay.icon_state, source, accessory_overlay.alpha)
if(accessory.center)
accessory_overlay = center_image(accessory_overlay, accessory.dimension_x, accessory.dimension_y)
diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm
index cc12e8020b0..cdc068ae8a4 100644
--- a/code/modules/mob/living/carbon/inventory.dm
+++ b/code/modules/mob/living/carbon/inventory.dm
@@ -80,7 +80,7 @@
if(observe.client)
observe.client.screen -= I
I.forceMove(src)
- I.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(I, ABOVE_HUD_PLANE, src)
I.appearance_flags |= NO_CLIENT_COLOR
var/not_handled = FALSE
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index d3d5d0c890b..e782d692e50 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -1608,7 +1608,7 @@ GLOBAL_LIST_EMPTY(fire_appearances)
else
registered_z = null
-/mob/living/on_changed_z_level(turf/old_turf, turf/new_turf)
+/mob/living/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
..()
update_z(new_turf?.z)
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 13ec10eec7b..aece2515912 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -1,6 +1,6 @@
/mob/living
see_invisible = SEE_INVISIBLE_LIVING
- sight = 0
+ sight = SEE_BLACKNESS
see_in_dark = 2
hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD)
pressure_resistance = 10
diff --git a/code/modules/mob/living/living_fov.dm b/code/modules/mob/living/living_fov.dm
index 2955f44251e..06a826f7ef1 100644
--- a/code/modules/mob/living/living_fov.dm
+++ b/code/modules/mob/living/living_fov.dm
@@ -45,7 +45,7 @@
. = TRUE
// Handling nearsightnedness
- if(. && is_nearsighted())
+ if(. && is_nearsighted())
if((rel_x >= NEARSIGHTNESS_FOV_BLINDNESS || rel_x <= -NEARSIGHTNESS_FOV_BLINDNESS) || (rel_y >= NEARSIGHTNESS_FOV_BLINDNESS || rel_y <= -NEARSIGHTNESS_FOV_BLINDNESS))
return FALSE
@@ -88,12 +88,12 @@
UNSETEMPTY(fov_traits)
update_fov()
-//did you know you can subtype /image and /mutable_appearance?
+//did you know you can subtype /image and /mutable_appearance? // Stop telling them that they might actually do it
/image/fov_image
icon = 'icons/effects/fov/fov_effects.dmi'
layer = FOV_EFFECTS_LAYER
appearance_flags = RESET_COLOR | RESET_TRANSFORM
- plane = FULLSCREEN_PLANE
+ plane = HIGH_GAME_PLANE
/// Plays a visual effect representing a sound cue for people with vision obstructed by FOV or blindness
/proc/play_fov_effect(atom/center, range, icon_state, dir = SOUTH, ignore_self = FALSE, angle = 0, list/override_list)
@@ -112,6 +112,7 @@
if(!fov_image) //Make the image once we found one recipient to receive it
fov_image = new()
fov_image.loc = anchor_point
+ SET_PLANE(fov_image, HIGH_GAME_PLANE, anchor_point)
fov_image.icon_state = icon_state
fov_image.dir = dir
if(angle)
diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm
index 185571bd875..a4c92e45803 100644
--- a/code/modules/mob/living/living_say.dm
+++ b/code/modules/mob/living/living_say.dm
@@ -406,10 +406,16 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
for(var/mob/M in listening)
if(M.client && (!M.client.prefs.read_preference(/datum/preference/toggle/enable_runechat) || (SSlag_switch.measures[DISABLE_RUNECHAT] && !HAS_TRAIT(src, TRAIT_BYPASS_MEASURES))))
speech_bubble_recipients.Add(M.client)
- var/image/I = image('icons/mob/effects/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER)
- I.plane = ABOVE_GAME_PLANE
- I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
- INVOKE_ASYNC(GLOBAL_PROC, /.proc/flick_overlay, I, speech_bubble_recipients, 30)
+
+ var/image/say_popup = image('icons/mob/effects/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER)
+ SET_PLANE_EXPLICIT(say_popup, ABOVE_GAME_PLANE, src)
+ say_popup.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
+ INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, say_popup, speech_bubble_recipients, 3 SECONDS)
+ LAZYADD(update_on_z, say_popup)
+ addtimer(CALLBACK(src, .proc/clear_saypopup, say_popup), 3.5 SECONDS)
+
+/mob/living/proc/clear_saypopup(image/say_popup)
+ LAZYREMOVE(update_on_z, say_popup)
/mob/proc/binarycheck()
return FALSE
diff --git a/code/modules/mob/living/navigation.dm b/code/modules/mob/living/navigation.dm
index 62c8a30c712..ae686a4a82d 100644
--- a/code/modules/mob/living/navigation.dm
+++ b/code/modules/mob/living/navigation.dm
@@ -69,22 +69,23 @@
return
path |= get_turf(navigate_target)
for(var/i in 1 to length(path))
- var/image/path_image = image(icon = 'icons/effects/navigation.dmi', layer = HIGH_PIPE_LAYER, loc = path[i])
- path_image.plane = GAME_PLANE
+ var/turf/current_turf = path[i]
+ var/image/path_image = image(icon = 'icons/effects/navigation.dmi', layer = HIGH_PIPE_LAYER, loc = current_turf)
+ SET_PLANE(path_image, GAME_PLANE, current_turf)
path_image.color = COLOR_CYAN
path_image.alpha = 0
var/dir_1 = 0
var/dir_2 = 0
if(i == 1)
- dir_2 = turn(angle2dir(get_angle(path[i+1], path[i])), 180)
+ dir_2 = turn(angle2dir(get_angle(path[i+1], current_turf)), 180)
else if(i == length(path))
- dir_2 = turn(angle2dir(get_angle(path[i-1], path[i])), 180)
+ dir_2 = turn(angle2dir(get_angle(path[i-1], current_turf)), 180)
else
- dir_1 = turn(angle2dir(get_angle(path[i+1], path[i])), 180)
- dir_2 = turn(angle2dir(get_angle(path[i-1], path[i])), 180)
+ dir_1 = turn(angle2dir(get_angle(path[i+1], current_turf)), 180)
+ dir_2 = turn(angle2dir(get_angle(path[i-1], current_turf)), 180)
if(dir_1 > dir_2)
dir_1 = dir_2
- dir_2 = turn(angle2dir(get_angle(path[i+1], path[i])), 180)
+ dir_2 = turn(angle2dir(get_angle(path[i+1], current_turf)), 180)
path_image.icon_state = "[dir_1]-[dir_2]"
client.images += path_image
client.navigation_images += path_image
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index bc773b1460c..3b89c491843 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -21,7 +21,7 @@
density = TRUE
status_flags = CANSTUN|CANPUSH
combat_mode = TRUE //so we always get pushed instead of trying to swap
- sight = SEE_TURFS | SEE_MOBS | SEE_OBJS
+ sight = SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_BLACKNESS
see_in_dark = NIGHTVISION_FOV_RANGE
hud_type = /datum/hud/ai
med_hud = DATA_HUD_MEDICAL_BASIC
@@ -782,11 +782,12 @@
var/list/obj/machinery/camera/add = list()
var/list/obj/machinery/camera/remove = list()
var/list/obj/machinery/camera/visible = list()
- for (var/datum/camerachunk/CC in eyeobj.visibleCameraChunks)
- for (var/obj/machinery/camera/C in CC.cameras)
- if (!C.can_use() || get_dist(C, eyeobj) > 7 || !C.internal_light)
- continue
- visible |= C
+ for (var/datum/camerachunk/chunk as anything in eyeobj.visibleCameraChunks)
+ for (var/z_key in chunk.cameras)
+ for(var/obj/machinery/camera/camera as anything in chunk.cameras[z_key])
+ if (!camera.can_use() || get_dist(camera, src) > 7 || !camera.internal_light)
+ continue
+ visible |= camera
add = visible - lit_cameras
remove = lit_cameras - visible
@@ -924,34 +925,40 @@
modules_action.Grant(src)
/mob/living/silicon/ai/reset_perspective(atom/new_eye)
+ SHOULD_CALL_PARENT(FALSE) // I hate you all
if(camera_light_on)
light_cameras()
if(istype(new_eye, /obj/machinery/camera))
current = new_eye
- if(client)
- if(ismovable(new_eye))
- if(new_eye != GLOB.ai_camera_room_landmark)
- end_multicam()
- client.perspective = EYE_PERSPECTIVE
- client.eye = new_eye
- else
+ if(!client)
+ return
+
+ if(ismovable(new_eye))
+ if(new_eye != GLOB.ai_camera_room_landmark)
end_multicam()
- if(isturf(loc))
- if(eyeobj)
- client.eye = eyeobj
- client.perspective = EYE_PERSPECTIVE
- else
- client.eye = client.mob
- client.perspective = MOB_PERSPECTIVE
- else
+ client.perspective = EYE_PERSPECTIVE
+ client.set_eye(new_eye)
+ else
+ end_multicam()
+ if(isturf(loc))
+ if(eyeobj)
+ client.set_eye(eyeobj)
client.perspective = EYE_PERSPECTIVE
- client.eye = loc
- update_sight()
- if(client.eye != src)
- var/atom/AT = client.eye
- AT.get_remote_view_fullscreens(src)
+ else
+ client.set_eye(client.mob)
+ client.perspective = MOB_PERSPECTIVE
else
- clear_fullscreen("remote_view", 0)
+ client.perspective = EYE_PERSPECTIVE
+ client.set_eye(loc)
+ update_sight()
+ if(client.eye != src)
+ var/atom/AT = client.eye
+ AT.get_remote_view_fullscreens(src)
+ else
+ clear_fullscreen("remote_view", 0)
+
+ // I am so sorry
+ SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE)
/mob/living/silicon/ai/revive(full_heal = FALSE, admin_revive = FALSE)
. = ..()
diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm
index 583c3225ef8..afb32e29164 100644
--- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm
@@ -16,31 +16,44 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
var/list/chunks = list()
var/ready = 0
- ///The image cloned by all chunk static images put onto turfs cameras cant see
- var/image/obscured
+ /// List of images cloned by all chunk static images put onto turfs cameras cant see
+ /// Indexed by the plane offset to use
+ var/list/image/obscured_images
/datum/cameranet/New()
+ obscured_images = list()
+ update_offsets(SSmapping.max_plane_offset)
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, .proc/on_offset_growth)
- obscured = new('icons/effects/cameravis.dmi')
- obscured.plane = CAMERA_STATIC_PLANE
- obscured.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR | KEEP_APART
- obscured.override = TRUE
+/datum/cameranet/proc/update_offsets(new_offset)
+ for(var/i in length(obscured_images) to new_offset)
+ var/image/obscured = new('icons/effects/cameravis.dmi')
+ SET_PLANE_W_SCALAR(obscured, CAMERA_STATIC_PLANE, i)
+ obscured.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR | KEEP_APART
+ obscured.override = TRUE
+ obscured_images += obscured
+
+/datum/cameranet/proc/on_offset_growth(datum/source, old_offset, new_offset)
+ SIGNAL_HANDLER
+ update_offsets(new_offset)
/// Checks if a chunk has been Generated in x, y, z.
/datum/cameranet/proc/chunkGenerated(x, y, z)
+ var/turf/lowest = get_lowest_turf(locate(max(x, 1), max(y, 1), z))
x &= ~(CHUNK_SIZE - 1)
y &= ~(CHUNK_SIZE - 1)
- return chunks["[x],[y],[z]"]
+ return chunks["[x],[y],[lowest.z]"]
// Returns the chunk in the x, y, z.
// If there is no chunk, it creates a new chunk and returns that.
/datum/cameranet/proc/getCameraChunk(x, y, z)
+ var/turf/lowest = get_lowest_turf(locate(x, y, z))
x &= ~(CHUNK_SIZE - 1)
y &= ~(CHUNK_SIZE - 1)
- var/key = "[x],[y],[z]"
+ var/key = "[x],[y],[lowest.z]"
. = chunks[key]
if(!.)
- chunks[key] = . = new /datum/camerachunk(x, y, z)
+ chunks[key] = . = new /datum/camerachunk(x, y, lowest.z)
/// Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set.
/datum/cameranet/proc/visibility(list/moved_eyes, client/C, list/other_eyes, use_static = TRUE)
@@ -61,7 +74,6 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
var/x2 = min(world.maxx, eye.x + static_range) & ~(CHUNK_SIZE - 1)
var/y2 = min(world.maxy, eye.y + static_range) & ~(CHUNK_SIZE - 1)
-
for(var/x = x1; x <= x2; x += CHUNK_SIZE)
for(var/y = y1; y <= y2; y += CHUNK_SIZE)
visibleChunks |= getCameraChunk(x, y, eye.z)
@@ -124,10 +136,10 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
if(chunk)
if(choice == 0)
// Remove the camera.
- chunk.cameras -= c
+ chunk.cameras["[T.z]"] -= c
else if(choice == 1)
// You can't have the same camera in the list twice.
- chunk.cameras |= c
+ chunk.cameras["[T.z]"] |= c
chunk.hasChanged()
/// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0.
diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/living/silicon/ai/freelook/chunk.dm
index e5b9d84d62f..f6c653f56ff 100644
--- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/chunk.dm
@@ -11,20 +11,21 @@
///turfs our cameras can see inside our grid
var/list/visibleTurfs = list()
///cameras that can see into our grid
+ ///indexed by the z level of the camera
var/list/cameras = list()
- ///list of all turfs
+ ///list of all turfs, associative with that turf's static image
+ ///turf -> /image
var/list/turfs = list()
///camera mobs that can see turfs in our grid
var/list/seenby = list()
- ///images created to represent obscured turfs
- var/list/inactive_static_images = list()
///images currently in use on obscured turfs.
var/list/active_static_images = list()
var/changed = FALSE
var/x = 0
var/y = 0
- var/z = 0
+ var/lower_z
+ var/upper_z
/// Add an AI eye to the chunk, then update if changed.
/datum/camerachunk/proc/add(mob/camera/ai_eye/eye)
@@ -63,20 +64,22 @@
changed = TRUE
/// The actual updating. It gathers the visible turfs from cameras and puts them into the appropiate lists.
-/datum/camerachunk/proc/update()
+/// Accepts an optional partial_update argument, that blocks any calls out to chunks that could affect us, like above or below
+/datum/camerachunk/proc/update(partial_update = FALSE)
var/list/updated_visible_turfs = list()
- for(var/obj/machinery/camera/current_camera as anything in cameras)
- if(!current_camera || !current_camera.can_use())
- continue
+ for(var/z_level in lower_z to upper_z)
+ for(var/obj/machinery/camera/current_camera as anything in cameras["[z_level]"])
+ if(!current_camera || !current_camera.can_use())
+ continue
- var/turf/point = locate(src.x + (CHUNK_SIZE / 2), src.y + (CHUNK_SIZE / 2), src.z)
- if(get_dist(point, current_camera) > CHUNK_SIZE + (CHUNK_SIZE / 2))
- continue
+ var/turf/point = locate(src.x + (CHUNK_SIZE / 2), src.y + (CHUNK_SIZE / 2), z_level)
+ if(get_dist(point, current_camera) > CHUNK_SIZE + (CHUNK_SIZE / 2))
+ continue
- for(var/turf/vis_turf in current_camera.can_see())
- if(turfs[vis_turf])
- updated_visible_turfs[vis_turf] = vis_turf
+ for(var/turf/vis_turf in current_camera.can_see())
+ if(turfs[vis_turf])
+ updated_visible_turfs[vis_turf] = vis_turf
///new turfs that we couldnt see last update but can now
var/list/newly_visible_turfs = updated_visible_turfs - visibleTurfs
@@ -91,31 +94,24 @@
client.images -= active_static_images
for(var/turf/visible_turf as anything in newly_visible_turfs)
- var/image/static_image_to_deallocate = obscuredTurfs[visible_turf]
- if(!static_image_to_deallocate)
+ var/image/static_image = obscuredTurfs[visible_turf]
+ if(!static_image)
continue
- static_image_to_deallocate.loc = null
- active_static_images -= static_image_to_deallocate
- inactive_static_images += static_image_to_deallocate
-
+ active_static_images -= static_image
obscuredTurfs -= visible_turf
for(var/turf/obscured_turf as anything in newly_obscured_turfs)
if(obscuredTurfs[obscured_turf] || istype(obscured_turf, /turf/open/ai_visible))
continue
- var/image/static_image_to_allocate = inactive_static_images[length(inactive_static_images)]
- if(!static_image_to_allocate)
- stack_trace("somehow a camera chunk ran out of static images!")
+ var/image/static_image = turfs[obscured_turf]
+ if(!static_image)
+ stack_trace("somehow a camera chunk used a turf it didn't contain!!")
break
- obscuredTurfs[obscured_turf] = static_image_to_allocate
- static_image_to_allocate.loc = obscured_turf
-
- inactive_static_images -= static_image_to_allocate
- active_static_images += static_image_to_allocate
-
+ obscuredTurfs[obscured_turf] = static_image
+ active_static_images += static_image
visibleTurfs = updated_visible_turfs
changed = FALSE
@@ -129,44 +125,47 @@
/// Create a new camera chunk, since the chunks are made as they are needed.
-/datum/camerachunk/New(x, y, z)
+/datum/camerachunk/New(x, y, lower_z)
x &= ~(CHUNK_SIZE - 1)
y &= ~(CHUNK_SIZE - 1)
src.x = x
src.y = y
- src.z = z
+ src.lower_z = lower_z
+ var/turf/upper_turf = get_highest_turf(locate(x, y, lower_z))
+ src.upper_z = upper_turf.z
- for(var/obj/machinery/camera/camera in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z)))
- if(camera.can_use())
- cameras += camera
+ for(var/z_level in lower_z to upper_z)
+ var/list/local_cameras = list()
+ cameras["[z_level]"] = local_cameras
+ for(var/obj/machinery/camera/camera in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z_level)))
+ if(camera.can_use())
+ local_cameras += camera
- for(var/mob/living/silicon/sillycone in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z)))
- if(sillycone.builtInCamera?.can_use())
- cameras += sillycone.builtInCamera
+ for(var/mob/living/silicon/sillycone in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z_level)))
+ if(sillycone.builtInCamera?.can_use())
+ local_cameras += sillycone
- for(var/turf/t as anything in block(locate(max(x, 1), max(y, 1), z), locate(min(x + CHUNK_SIZE - 1, world.maxx), min(y + CHUNK_SIZE - 1, world.maxy), z)))
- turfs[t] = t
+ var/image/mirror_from = GLOB.cameranet.obscured_images[GET_Z_PLANE_OFFSET(z_level) + 1]
+ for(var/turf/lad as anything in block(locate(max(x, 1), max(y, 1), z_level), locate(min(x + CHUNK_SIZE - 1, world.maxx), min(y + CHUNK_SIZE - 1, world.maxy), z_level)))
+ var/image/our_image = new /image(mirror_from)
+ our_image.loc = lad
+ turfs[lad] = our_image
- for(var/turf in turfs)//one for each 16x16 = 256 turfs this camera chunk encompasses
- inactive_static_images += new/image(GLOB.cameranet.obscured)
+ for(var/obj/machinery/camera/camera as anything in local_cameras)
+ if(!camera)
+ continue
- for(var/obj/machinery/camera/camera as anything in cameras)
- if(!camera)
- continue
+ if(!camera.can_use())
+ continue
- if(!camera.can_use())
- continue
-
- for(var/turf/vis_turf in camera.can_see())
- if(turfs[vis_turf])
- visibleTurfs[vis_turf] = vis_turf
+ for(var/turf/vis_turf in camera.can_see())
+ if(turfs[vis_turf])
+ visibleTurfs[vis_turf] = vis_turf
for(var/turf/obscured_turf as anything in turfs - visibleTurfs)
- var/image/new_static = inactive_static_images[inactive_static_images.len]
- new_static.loc = obscured_turf
+ var/image/new_static = turfs[obscured_turf]
active_static_images += new_static
- inactive_static_images -= new_static
obscuredTurfs[obscured_turf] = new_static
#undef UPDATE_BUFFER_TIME
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index 04f7944a4b0..8f20e3c79b4 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -24,6 +24,12 @@
update_ai_detect_hud()
setLoc(loc, TRUE)
+/mob/camera/ai_eye/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ update_ai_detect_hud()
+
/mob/camera/ai_eye/examine(mob/user) //Displays a silicon's laws to ghosts
. = ..()
if(istype(ai) && ai.laws && isobserver(user))
@@ -45,11 +51,16 @@
hud.remove_atom_from_hud(src)
var/static/list/vis_contents_opaque = list()
- var/obj/effect/overlay/ai_detect_hud/hud_obj = vis_contents_opaque[ai_detector_color]
+ var/turf/our_turf = get_turf(src)
+ var/our_z_offset = GET_TURF_PLANE_OFFSET(our_turf)
+ var/key = "[our_z_offset]-[ai_detector_color]"
+
+ var/obj/effect/overlay/ai_detect_hud/hud_obj = vis_contents_opaque[key]
if(!hud_obj)
hud_obj = new /obj/effect/overlay/ai_detect_hud()
+ SET_PLANE_W_SCALAR(hud_obj, PLANE_TO_TRUE(hud_obj.plane), our_z_offset)
hud_obj.color = ai_detector_color
- vis_contents_opaque[ai_detector_color] = hud_obj
+ vis_contents_opaque[key] = hud_obj
var/list/new_images = list()
var/list/turfs = get_visible_turfs()
@@ -89,7 +100,7 @@
if(use_static)
ai.camera_visibility(src)
if(ai.client && !ai.multicam_on)
- ai.client.eye = src
+ ai.client.set_eye(src)
update_ai_detect_hud()
update_parallax_contents()
//Holopad
diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm
index f32fc139915..2550b893717 100644
--- a/code/modules/mob/living/silicon/ai/life.dm
+++ b/code/modules/mob/living/silicon/ai/life.dm
@@ -75,18 +75,16 @@
diag_hud_set_status()
/mob/living/silicon/ai/update_sight()
- see_invisible = initial(see_invisible)
- see_in_dark = initial(see_in_dark)
- sight = initial(sight)
+ set_invis_see(initial(see_invisible))
+ set_see_in_dark(initial(see_in_dark))
+ set_sight(initial(sight))
if(aiRestorePowerRoutine)
- sight = sight&~SEE_TURFS
- sight = sight&~SEE_MOBS
- sight = sight&~SEE_OBJS
- see_in_dark = 0
+ clear_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ set_see_in_dark(0)
if(see_override)
- see_invisible = see_override
- sync_lighting_plane_alpha()
+ set_invis_see(see_override)
+ return ..()
/mob/living/silicon/ai/proc/start_RestorePowerRoutine()
diff --git a/code/modules/mob/living/silicon/ai/multicam.dm b/code/modules/mob/living/silicon/ai/multicam.dm
index 338f85ab300..bdaba2a452f 100644
--- a/code/modules/mob/living/silicon/ai/multicam.dm
+++ b/code/modules/mob/living/silicon/ai/multicam.dm
@@ -88,6 +88,15 @@
icon_state = "room_background"
flags_1 = NOJAUNT
+/turf/open/ai_visible/Initialize(mapload)
+ . = ..()
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, .proc/multiz_offset_increase)
+ multiz_offset_increase(SSmapping)
+
+/turf/open/ai_visible/proc/multiz_offset_increase(datum/source)
+ SIGNAL_HANDLER
+ SET_PLANE_W_SCALAR(src, initial(plane), SSmapping.max_plane_offset)
+
/area/centcom/ai_multicam_room
name = "ai_multicam_room"
icon_state = "ai_camera_room"
@@ -153,13 +162,12 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room)
var/list/obj/machinery/camera/add = list()
var/list/obj/machinery/camera/remove = list()
var/list/obj/machinery/camera/visible = list()
- for (var/VV in visibleCameraChunks)
- var/datum/camerachunk/CC = VV
- for (var/V in CC.cameras)
- var/obj/machinery/camera/C = V
- if (!C.can_use() || (get_dist(C, src) > telegraph_range))
- continue
- visible |= C
+ for (var/datum/camerachunk/chunk as anything in visibleCameraChunks)
+ for (var/z_key in chunk.cameras)
+ for(var/obj/machinery/camera/camera as anything in chunk.cameras[z_key])
+ if (!camera.can_use() || (get_dist(camera, src) > telegraph_range))
+ continue
+ visible |= camera
add = visible - cameras_telegraphed
remove = cameras_telegraphed - visible
diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm
index 803598792f1..eb98eab25c3 100644
--- a/code/modules/mob/living/silicon/robot/inventory.dm
+++ b/code/modules/mob/living/silicon/robot/inventory.dm
@@ -66,7 +66,7 @@
held_items[module_num] = item_module
item_module.equipped(src, ITEM_SLOT_HANDS)
item_module.mouse_opacity = initial(item_module.mouse_opacity)
- item_module.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(item_module, ABOVE_HUD_PLANE, src)
item_module.forceMove(src)
if(istype(item_module, /obj/item/borg/sight))
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index cda72b61a54..1dfb6f62224 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -22,7 +22,7 @@
robot_modules_background = new()
robot_modules_background.icon_state = "block"
- robot_modules_background.plane = HUD_PLANE
+ SET_PLANE_EXPLICIT(robot_modules_background, HUD_PLANE, src)
inv1 = new /atom/movable/screen/robot/module1()
inv2 = new /atom/movable/screen/robot/module2()
@@ -352,11 +352,11 @@
if(lamp_enabled || lamp_doom)
eye_lights.icon_state = "[model.special_light_key ? "[model.special_light_key]":"[model.cyborg_base_icon]"]_l"
eye_lights.color = lamp_doom? COLOR_RED : lamp_color
- eye_lights.plane = ABOVE_LIGHTING_PLANE //glowy eyes
+ SET_PLANE_EXPLICIT(eye_lights, ABOVE_LIGHTING_PLANE, src) //glowy eyes
else
eye_lights.icon_state = "[model.special_light_key ? "[model.special_light_key]":"[model.cyborg_base_icon]"]_e"
eye_lights.color = COLOR_WHITE
- eye_lights.plane = ABOVE_GAME_PLANE
+ SET_PLANE_EXPLICIT(eye_lights, ABOVE_GAME_PLANE, src)
eye_lights.icon = icon
add_overlay(eye_lights)
@@ -373,6 +373,14 @@
add_overlay(head_overlay)
update_fire()
+/mob/living/silicon/robot/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer)
+ return ..()
+ cut_overlay(eye_lights)
+ SET_PLANE_EXPLICIT(eye_lights, PLANE_TO_TRUE(eye_lights.plane), src)
+ add_overlay(eye_lights)
+ return ..()
+
/mob/living/silicon/robot/proc/self_destruct(mob/usr)
var/turf/groundzero = get_turf(src)
message_admins(span_notice("[ADMIN_LOOKUPFLW(usr)] detonated [key_name_admin(src, client)] at [ADMIN_VERBOSEJMP(groundzero)]!"))
@@ -609,18 +617,18 @@
return
if(stat == DEAD)
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
- sight = null
+ set_sight(null)
else if(is_secret_level(z))
- sight = initial(sight)
+ set_sight(initial(sight))
else
- sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
- see_in_dark = 8
- see_invisible = SEE_INVISIBLE_OBSERVER
+ set_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ set_see_in_dark(8)
+ set_invis_see(SEE_INVISIBLE_OBSERVER)
return
- see_invisible = initial(see_invisible)
- see_in_dark = initial(see_in_dark)
- sight = initial(sight)
+ set_invis_see(initial(see_invisible))
+ set_see_in_dark(initial(see_in_dark))
+ var/new_sight = initial(sight)
lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
if(client.eye != src)
@@ -629,33 +637,34 @@
return
if(sight_mode & BORGMESON)
- sight |= SEE_TURFS
+ new_sight |= SEE_TURFS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
- see_in_dark = 1
+ set_see_in_dark(1)
if(sight_mode & BORGMATERIAL)
- sight |= SEE_OBJS
+ new_sight |= SEE_OBJS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- see_in_dark = 1
+ set_see_in_dark(1)
if(sight_mode & BORGXRAY)
- sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS)
- see_invisible = SEE_INVISIBLE_LIVING
- see_in_dark = 8
+ new_sight |= SEE_TURFS|SEE_MOBS|SEE_OBJS
+ set_invis_see(SEE_INVISIBLE_LIVING)
+ set_see_in_dark(8)
if(sight_mode & BORGTHERM)
- sight |= SEE_MOBS
+ new_sight |= SEE_MOBS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
- see_invisible = min(see_invisible, SEE_INVISIBLE_LIVING)
- see_in_dark = 8
+ set_invis_see(min(see_invisible, SEE_INVISIBLE_LIVING))
+ set_see_in_dark(8)
if(see_override)
- see_invisible = see_override
+ set_invis_see(see_override)
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
- sight = null
+ new_sight = null
- sync_lighting_plane_alpha()
+ set_sight(new_sight)
+ return ..()
/mob/living/silicon/robot/update_stat()
if(status_flags & GODMODE)
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index af9a8169062..584db0177f0 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -83,7 +83,7 @@
if(!modularInterface)
modularInterface = new /obj/item/modular_computer/tablet/integrated(src)
modularInterface.layer = ABOVE_HUD_PLANE
- modularInterface.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(modularInterface, ABOVE_HUD_PLANE, src)
modularInterface.saved_identification = real_name || name
if(iscyborg(src))
modularInterface.saved_job = "Cyborg"
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index 1429c9549e7..5b2ad2a7359 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -1052,7 +1052,7 @@ Pass a positive integer as an argument to override a bot's default speed.
MA.icon = path_image_icon
MA.icon_state = path_image_icon_state
MA.layer = ABOVE_OPEN_TURF_LAYER
- MA.plane = GAME_PLANE
+ SET_PLANE(MA, GAME_PLANE, T)
MA.appearance_flags = RESET_COLOR|RESET_TRANSFORM
MA.color = path_image_color
MA.dir = direction
diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm
index 9396aded170..edff40031be 100644
--- a/code/modules/mob/living/simple_animal/bot/mulebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm
@@ -204,7 +204,7 @@
/mob/living/simple_animal/bot/mulebot/update_icon_state() //if you change the icon_state names, please make sure to update /datum/wires/mulebot/on_pulse() as well. <3
. = ..()
- icon_state = "[base_icon][bot_mode_flags & BOT_MODE_ON ? wires.is_cut(WIRE_AVOIDANCE) : 0]"
+ icon_state = "[base_icon][(bot_mode_flags & BOT_MODE_ON) ? wires.is_cut(WIRE_AVOIDANCE) : 0]"
/mob/living/simple_animal/bot/mulebot/update_overlays()
. = ..()
@@ -452,7 +452,7 @@
load.forceMove(loc)
load.pixel_y = initial(load.pixel_y)
load.layer = initial(load.layer)
- load.plane = initial(load.plane)
+ SET_PLANE_EXPLICIT(load, initial(load.plane), src)
load = null
if(dirn) //move the thing to the delivery point.
diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm
index b5936791906..4c90edae049 100644
--- a/code/modules/mob/living/simple_animal/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs.dm
@@ -404,7 +404,7 @@
icon_living = "harvester"
maxHealth = 40
health = 40
- sight = SEE_MOBS
+ sight = SEE_MOBS | SEE_BLACKNESS
melee_damage_lower = 15
melee_damage_upper = 20
attack_verb_continuous = "butchers"
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
index df4e1d9b134..41836752800 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
@@ -30,7 +30,7 @@
healable = 0
density = FALSE
pass_flags = PASSTABLE | PASSMOB
- sight = (SEE_TURFS | SEE_OBJS)
+ sight = SEE_TURFS | SEE_OBJS| SEE_BLACKNESS
status_flags = (CANPUSH | CANSTUN | CANKNOCKDOWN)
gender = NEUTER
mob_biotypes = MOB_ROBOTIC
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm
index e3288079472..27d9a9f4dc9 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm
@@ -66,7 +66,7 @@
I.screen_loc = null // will get moved if inventory is visible
I.forceMove(src)
- I.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(I, ABOVE_HUD_PLANE, src)
switch(slot)
if(ITEM_SLOT_HEAD)
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm b/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm
index e601e3c83df..ffeca6776de 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm
@@ -35,7 +35,7 @@
hands_overlays += r_hand_overlay
if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
- r_hand.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(r_hand, ABOVE_HUD_PLANE, src)
r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand))
client.screen |= r_hand
@@ -47,7 +47,7 @@
hands_overlays += l_hand_overlay
if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
- l_hand.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(l_hand, ABOVE_HUD_PLANE, src)
l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand))
client.screen |= l_hand
diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm
index 955d190bb29..89bd8f6c7f2 100644
--- a/code/modules/mob/living/simple_animal/guardian/guardian.dm
+++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm
@@ -342,7 +342,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
I.screen_loc = null // will get moved if inventory is visible
I.forceMove(src)
I.equipped(src, slot)
- I.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(I, ABOVE_HUD_PLANE, src)
/mob/living/simple_animal/hostile/guardian/proc/apply_overlay(cache_index)
if((. = guardian_overlays[cache_index]))
@@ -364,7 +364,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
hands_overlays += r_hand.build_worn_icon(default_layer = GUARDIAN_HANDS_LAYER, default_icon_file = r_hand.righthand_file, isinhands = TRUE)
if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
- r_hand.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(r_hand, ABOVE_HUD_PLANE, src)
r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand))
client.screen |= r_hand
@@ -372,7 +372,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
hands_overlays += l_hand.build_worn_icon(default_layer = GUARDIAN_HANDS_LAYER, default_icon_file = l_hand.lefthand_file, isinhands = TRUE)
if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
- l_hand.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(l_hand, ABOVE_HUD_PLANE, src)
l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand))
client.screen |= l_hand
diff --git a/code/modules/mob/living/simple_animal/heretic_monsters.dm b/code/modules/mob/living/simple_animal/heretic_monsters.dm
index 9fd542f2214..0250186a81d 100644
--- a/code/modules/mob/living/simple_animal/heretic_monsters.dm
+++ b/code/modules/mob/living/simple_animal/heretic_monsters.dm
@@ -53,7 +53,7 @@
melee_damage_upper = 10
maxHealth = 65
health = 65
- sight = SEE_MOBS|SEE_OBJS|SEE_TURFS
+ sight = SEE_MOBS|SEE_OBJS|SEE_TURFS|SEE_BLACKNESS
loot = list(/obj/effect/gibspawner/human, /obj/item/bodypart/l_arm, /obj/item/organ/internal/eyes)
actions_to_add = list(
/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/long,
@@ -356,7 +356,7 @@
health = 75
melee_damage_lower = 15
melee_damage_upper = 20
- sight = SEE_TURFS
+ sight = SEE_TURFS|SEE_BLACKNESS
actions_to_add = list(
/datum/action/cooldown/spell/aoe/rust_conversion/small,
/datum/action/cooldown/spell/basic_projectile/rust_wave/short,
@@ -396,7 +396,7 @@
health = 75
melee_damage_lower = 15
melee_damage_upper = 20
- sight = SEE_TURFS
+ sight = SEE_TURFS|SEE_BLACKNESS
actions_to_add = list(
/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash,
/datum/action/cooldown/spell/pointed/cleave,
@@ -414,7 +414,7 @@
health = 150
melee_damage_lower = 15
melee_damage_upper = 20
- sight = SEE_MOBS
+ sight = SEE_MOBS|SEE_BLACKNESS
actions_to_add = list(
/datum/action/cooldown/spell/shapeshift/eldritch,
/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash,
diff --git a/code/modules/mob/living/simple_animal/hostile/eyeballs.dm b/code/modules/mob/living/simple_animal/hostile/eyeballs.dm
index bc84e60936c..d771236797a 100644
--- a/code/modules/mob/living/simple_animal/hostile/eyeballs.dm
+++ b/code/modules/mob/living/simple_animal/hostile/eyeballs.dm
@@ -32,7 +32,7 @@
faction = list("spooky")
del_on_death = 1
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- sight = SEE_SELF|SEE_MOBS|SEE_OBJS|SEE_TURFS
+ sight = SEE_SELF|SEE_MOBS|SEE_OBJS|SEE_TURFS|SEE_BLACKNESS
/mob/living/simple_animal/hostile/eyeball/Initialize(mapload)
. = ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm
index 3d383f91af9..2c818e83108 100644
--- a/code/modules/mob/living/simple_animal/hostile/statue.dm
+++ b/code/modules/mob/living/simple_animal/hostile/statue.dm
@@ -46,7 +46,7 @@
search_objects = 1 // So that it can see through walls
- sight = SEE_SELF|SEE_MOBS|SEE_OBJS|SEE_TURFS
+ sight = SEE_SELF|SEE_MOBS|SEE_OBJS|SEE_TURFS|SEE_BLACKNESS
move_force = MOVE_FORCE_EXTREMELY_STRONG
move_resist = MOVE_FORCE_EXTREMELY_STRONG
diff --git a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
index 459d6621f10..9029b7a7e62 100644
--- a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
+++ b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
@@ -145,7 +145,7 @@
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
unsuitable_atmos_damage = 0
/// copied over from the code from eyeballs (the mob) to make it easier for venus human traps to see in kudzu that doesn't have the transparency mutation
- sight = SEE_SELF|SEE_MOBS|SEE_OBJS|SEE_TURFS
+ sight = SEE_SELF|SEE_MOBS|SEE_OBJS|SEE_TURFS|SEE_BLACKNESS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
faction = list("hostile","vines","plants")
initial_language_holder = /datum/language_holder/venus
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 2941accdee2..a6186129d43 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -569,25 +569,26 @@
return
if(stat == DEAD)
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
- sight = null
+ set_sight(null)
else if(is_secret_level(z))
- sight = initial(sight)
+ set_sight(initial(sight))
else
- sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
- see_in_dark = NIGHTVISION_FOV_RANGE
- see_invisible = SEE_INVISIBLE_OBSERVER
+ set_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ set_see_in_dark(NIGHTVISION_FOV_RANGE)
+ set_invis_see(SEE_INVISIBLE_OBSERVER)
return
- see_invisible = initial(see_invisible)
- see_in_dark = initial(see_in_dark)
- sight = initial(sight)
+ set_invis_see(initial(see_invisible))
+ set_see_in_dark(initial(see_in_dark))
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
- sight = null
+ set_sight(null)
+ else
+ set_sight(initial(sight))
if(client.eye != src)
var/atom/A = client.eye
if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates.
return
- sync_lighting_plane_alpha()
+ return ..()
//Will always check hands first, because access_card is internal to the mob and can't be removed or swapped.
/mob/living/simple_animal/get_idcard(hand_first)
@@ -636,12 +637,14 @@
update_held_items()
/mob/living/simple_animal/update_held_items()
- if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
- for(var/obj/item/I in held_items)
- var/index = get_held_index_of_item(I)
- I.plane = ABOVE_HUD_PLANE
- I.screen_loc = ui_hand_position(index)
- client.screen |= I
+ if(!client || !hud_used || hud_used.hud_version == HUD_STYLE_NOHUD)
+ return
+ var/turf/our_turf = get_turf(src)
+ for(var/obj/item/I in held_items)
+ var/index = get_held_index_of_item(I)
+ SET_PLANE(I, ABOVE_HUD_PLANE, our_turf)
+ I.screen_loc = ui_hand_position(index)
+ client.screen |= I
//ANIMAL RIDING
@@ -678,7 +681,7 @@
if (pulledby || shouldwakeup)
toggle_ai(AI_ON)
-/mob/living/simple_animal/on_changed_z_level(turf/old_turf, turf/new_turf)
+/mob/living/simple_animal/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
..()
if (old_turf && AIStatus == AI_Z_OFF)
SSidlenpcpool.idle_mobs_by_zlevel[old_turf.z] -= src
diff --git a/code/modules/mob/living/ventcrawling.dm b/code/modules/mob/living/ventcrawling.dm
index a30e248f829..d089fe023e5 100644
--- a/code/modules/mob/living/ventcrawling.dm
+++ b/code/modules/mob/living/ventcrawling.dm
@@ -103,10 +103,6 @@
* We move first and then call update. Dont flip this around
*/
/mob/living/proc/update_pipe_vision(full_refresh = FALSE)
- // We're gonna color the lighting plane to make it darker while ventcrawling, so things look nicer
- var/atom/movable/screen/plane_master/lighting
- if(hud_used)
- lighting = hud_used?.plane_masters["[LIGHTING_PLANE]"]
// Take away all the pipe images if we're not doing anything with em
if(isnull(client) || !HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) || !istype(loc, /obj/machinery/atmospherics) || !(movement_type & VENTCRAWLING))
@@ -114,11 +110,19 @@
client.images -= current_image
pipes_shown.len = 0
pipetracker = null
- lighting?.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, "#4d4d4d")
+ for(var/atom/movable/screen/plane_master/lighting in hud_used.get_true_plane_masters(LIGHTING_PLANE))
+ lighting.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, "#4d4d4d")
+ for(var/atom/movable/screen/plane_master/pipecrawl in hud_used.get_true_plane_masters(PIPECRAWL_IMAGES_PLANE))
+ pipecrawl.hide_plane(src)
return
+ // We're gonna color the lighting plane to make it darker while ventcrawling, so things look nicer
// This is a bit hacky but it makes the background darker, which has a nice effect
- lighting?.add_atom_colour("#4d4d4d", TEMPORARY_COLOUR_PRIORITY)
+ for(var/atom/movable/screen/plane_master/lighting in hud_used.get_true_plane_masters(LIGHTING_PLANE))
+ lighting.add_atom_colour("#4d4d4d", TEMPORARY_COLOUR_PRIORITY)
+
+ for(var/atom/movable/screen/plane_master/pipecrawl in hud_used.get_true_plane_masters(PIPECRAWL_IMAGES_PLANE))
+ pipecrawl.unhide_plane(src)
var/obj/machinery/atmospherics/current_location = loc
var/list/our_pipenets = current_location.return_pipenets()
@@ -158,7 +162,8 @@
continue
if(!pipenet_part.pipe_vision_img)
+ var/turf/their_turf = get_turf(pipenet_part)
pipenet_part.pipe_vision_img = image(pipenet_part, pipenet_part.loc, dir = pipenet_part.dir)
- pipenet_part.pipe_vision_img.plane = PIPECRAWL_IMAGES_PLANE
+ SET_PLANE(pipenet_part.pipe_vision_img, PIPECRAWL_IMAGES_PLANE, their_turf)
client.images += pipenet_part.pipe_vision_img
pipes_shown += pipenet_part.pipe_vision_img
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 16b24e2ce8a..2edf7391e18 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -477,6 +477,7 @@
* reset_perspective(thing) set the eye to the thing (if it's equal to current default reset to mob perspective)
*/
/mob/proc/reset_perspective(atom/new_eye)
+ SHOULD_CALL_PARENT(TRUE)
if(!client)
return
@@ -485,29 +486,29 @@
//Set the new eye unless it's us
if(new_eye != src)
client.perspective = EYE_PERSPECTIVE
- client.eye = new_eye
+ client.set_eye(new_eye)
else
- client.eye = client.mob
+ client.set_eye(client.mob)
client.perspective = MOB_PERSPECTIVE
else if(isturf(new_eye))
//Set to the turf unless it's our current turf
if(new_eye != loc)
client.perspective = EYE_PERSPECTIVE
- client.eye = new_eye
+ client.set_eye(new_eye)
else
- client.eye = client.mob
+ client.set_eye(client.mob)
client.perspective = MOB_PERSPECTIVE
else
return TRUE //no setting eye to stupid things like areas or whatever
else
//Reset to common defaults: mob if on turf, otherwise current loc
if(isturf(loc))
- client.eye = client.mob
+ client.set_eye(client.mob)
client.perspective = MOB_PERSPECTIVE
else
client.perspective = EYE_PERSPECTIVE
- client.eye = loc
+ client.set_eye(loc)
/// Signal sent after the eye has been successfully updated, with the client existing.
SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE)
return TRUE
@@ -1101,15 +1102,16 @@
///Update the lighting plane and sight of this mob (sends COMSIG_MOB_UPDATE_SIGHT)
/mob/proc/update_sight()
+ SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT)
sync_lighting_plane_alpha()
///Set the lighting plane hud alpha to the mobs lighting_alpha var
/mob/proc/sync_lighting_plane_alpha()
- if(hud_used)
- var/atom/movable/screen/plane_master/lighting/L = hud_used.plane_masters["[LIGHTING_PLANE]"]
- if (L)
- L.alpha = lighting_alpha
+ if(!hud_used)
+ return
+ for(var/atom/movable/screen/plane_master/rendering_plate/lighting/light_plane in hud_used.get_true_plane_masters(RENDER_PLANE_LIGHTING))
+ light_plane.set_alpha(lighting_alpha)
///Update the mouse pointer of the attached client in this mob
/mob/proc/update_mouse_pointer()
@@ -1220,6 +1222,7 @@
VV_DROPDOWN_OPTION(VV_HK_DIRECT_CONTROL, "Assume Direct Control")
VV_DROPDOWN_OPTION(VV_HK_GIVE_DIRECT_CONTROL, "Give Direct Control")
VV_DROPDOWN_OPTION(VV_HK_OFFER_GHOSTS, "Offer Control to Ghosts")
+ VV_DROPDOWN_OPTION(VV_HK_VIEW_PLANES, "View/Edit Planes")
/mob/vv_do_topic(list/href_list)
. = ..()
@@ -1271,7 +1274,10 @@
if(!check_rights(NONE))
return
offer_control(src)
-
+ if(href_list[VV_HK_VIEW_PLANES])
+ if(!check_rights(R_DEBUG))
+ return
+ usr.client.edit_plane_masters(src)
/**
* extra var handling for the logging var
*/
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index c901e969c94..0e348e58646 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -26,9 +26,6 @@
var/shift_to_open_context_menu = TRUE
- ///when this be added to vis_contents of something it inherit something.plane, important for visualisation of mob in openspace.
- vis_flags = VIS_INHERIT_PLANE
-
var/lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
var/datum/mind/mind
var/static/next_mob_id = 0
diff --git a/code/modules/mob/status_procs.dm b/code/modules/mob/status_procs.dm
index 174b130b802..eb4b8a52b4a 100644
--- a/code/modules/mob/status_procs.dm
+++ b/code/modules/mob/status_procs.dm
@@ -101,3 +101,48 @@
/mob/proc/adjust_bodytemperature(amount,min_temp=0,max_temp=INFINITY)
if(bodytemperature >= min_temp && bodytemperature <= max_temp)
bodytemperature = clamp(bodytemperature + amount,min_temp,max_temp)
+
+/// Sight here is the mob.sight var, which tells byond what to actually show to our client
+/// See [code\__DEFINES\sight.dm] for more details
+/mob/proc/set_sight(new_value)
+ SHOULD_CALL_PARENT(TRUE)
+ // Can't turn this off, because we need it to make a plane master we need exist
+ // Sorry brother
+ new_value |= SEE_BLACKNESS
+ if(sight == new_value)
+ return
+ var/old_sight = sight
+ sight = new_value
+
+ SEND_SIGNAL(src, COMSIG_MOB_SIGHT_CHANGE, new_value, old_sight)
+
+/mob/proc/add_sight(new_value)
+ set_sight(sight | new_value)
+
+/mob/proc/clear_sight(new_value)
+ set_sight(sight & ~new_value)
+
+/// see invisibility is the mob's capability to see things that ought to be hidden from it
+/// Can think of it as a primitive version of changing the alpha of planes
+/// We mostly use it to hide ghosts, no real reason why
+/mob/proc/set_invis_see(new_sight)
+ SHOULD_CALL_PARENT(TRUE)
+ if(new_sight == see_invisible)
+ return
+ var/old_invis = see_invisible
+ see_invisible = new_sight
+ SEND_SIGNAL(src, COMSIG_MOB_SEE_INVIS_CHANGE, see_invisible, old_invis)
+
+/// see_in_dark is essentially just a range value
+/// Basically, if a tile has 0 luminosity affecting it, it will be counted as "dark"
+/// Then, if said tile is farther then see_in_dark from your mob, it will, rather then being rendered
+/// As a normal tile with contents, instead be covered by "darkness", the same sort exposed by SEE_BLACKNESS
+/// You can see this effect by going somewhere dark, and cranking the alpha on the lighting plane to 0
+/mob/proc/set_see_in_dark(new_dark)
+ SHOULD_CALL_PARENT(TRUE)
+ if(new_dark == see_in_dark)
+ return
+ var/old_dark = see_in_dark
+ see_in_dark = new_dark
+ SEND_SIGNAL(src, COMSIG_MOB_SEE_IN_DARK_CHANGE, see_in_dark, old_dark)
+
diff --git a/code/modules/mod/mod_paint.dm b/code/modules/mod/mod_paint.dm
index 26ff72038fd..8d670b6c607 100644
--- a/code/modules/mod/mod_paint.dm
+++ b/code/modules/mod/mod_paint.dm
@@ -11,7 +11,7 @@
icon = 'icons/obj/clothing/modsuit/mod_construction.dmi'
icon_state = "paintkit"
var/obj/item/mod/control/editing_mod
- var/atom/movable/screen/color_matrix_proxy_view/proxy_view
+ var/atom/movable/screen/map_view/proxy_view
var/list/current_color
/obj/item/mod/paint/Initialize(mapload)
@@ -43,9 +43,11 @@
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
editing_mod = mod
proxy_view = new()
+ proxy_view.generate_view("color_matrix_proxy_[REF(user.client)]")
+
proxy_view.appearance = editing_mod.appearance
proxy_view.color = null
- proxy_view.register_to_client(user.client)
+ proxy_view.display_to(user)
ui_interact(user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm
index d26489035be..d562953ad0c 100644
--- a/code/modules/mod/modules/modules_supply.dm
+++ b/code/modules/mod/modules/modules_supply.dm
@@ -231,7 +231,7 @@
. = ..()
if(!.)
return
- var/atom/game_renderer = mod.wearer.hud_used.plane_masters["[RENDER_PLANE_GAME]"]
+ var/atom/game_renderer = mod.wearer.hud_used.get_plane_master(RENDER_PLANE_GAME)
var/matrix/render_matrix = matrix(game_renderer.transform)
render_matrix.Scale(1.25, 1.25)
animate(game_renderer, launch_time, flags = SINE_EASING|EASE_IN, transform = render_matrix)
@@ -239,7 +239,7 @@
mod.wearer.visible_message(span_warning("[mod.wearer] starts whirring!"), \
blind_message = span_hear("You hear a whirring sound."))
playsound(src, 'sound/items/modsuit/loader_charge.ogg', 75, TRUE)
- lightning = mutable_appearance('icons/effects/effects.dmi', "electricity3", plane = GAME_PLANE_FOV_HIDDEN)
+ lightning = mutable_appearance('icons/effects/effects.dmi', "electricity3", offset_spokesman = src, plane = GAME_PLANE_FOV_HIDDEN)
mod.wearer.add_overlay(lightning)
balloon_alert(mod.wearer, "you start charging...")
var/power = launch_time
@@ -598,13 +598,22 @@
/obj/structure/mining_bomb/Initialize(mapload, atom/movable/firer)
. = ..()
- if(!explosion_image)
- explosion_image = image('icons/effects/96x96.dmi', "judicial_explosion")
- explosion_image.pixel_x = -32
- explosion_image.pixel_y = -32
- explosion_image.plane = ABOVE_GAME_PLANE
+ generate_image()
addtimer(CALLBACK(src, .proc/prime, firer), prime_time)
+/obj/structure/mining_bomb/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer)
+ return ..()
+ explosion_image = null
+ generate_image()
+ return ..()
+
+/obj/structure/mining_bomb/proc/generate_image()
+ explosion_image = image('icons/effects/96x96.dmi', "judicial_explosion")
+ explosion_image.pixel_x = -32
+ explosion_image.pixel_y = -32
+ SET_PLANE_EXPLICIT(explosion_image, ABOVE_GAME_PLANE, src)
+
/obj/structure/mining_bomb/proc/prime(atom/movable/firer)
add_overlay(explosion_image)
addtimer(CALLBACK(src, .proc/boom, firer), explosion_time)
diff --git a/code/modules/modular_computers/file_system/programs/secureye.dm b/code/modules/modular_computers/file_system/programs/secureye.dm
index 145dc5f2680..30a4acffa51 100644
--- a/code/modules/modular_computers/file_system/programs/secureye.dm
+++ b/code/modules/modular_computers/file_system/programs/secureye.dm
@@ -22,43 +22,27 @@
var/list/concurrent_users = list()
// Stuff needed to render the map
- var/map_name
var/atom/movable/screen/map_view/cam_screen
- /// All the plane masters that need to be applied.
- var/list/cam_plane_masters
var/atom/movable/screen/background/cam_background
/datum/computer_file/program/secureye/New()
. = ..()
// Map name has to start and end with an A-Z character,
// and definitely NOT with a square bracket or even a number.
- map_name = "camera_console_[REF(src)]_map"
+ var/map_name = "camera_console_[REF(src)]_map"
// Convert networks to lowercase
for(var/i in network)
network -= i
network += lowertext(i)
// Initialize map objects
cam_screen = new
- cam_screen.name = "screen"
- cam_screen.assigned_map = map_name
- cam_screen.del_on_map_removal = FALSE
- cam_screen.screen_loc = "[map_name]:1,1"
- cam_plane_masters = list()
- for(var/plane in subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/blackness)
- var/atom/movable/screen/plane_master/instance = new plane()
- instance.assigned_map = map_name
- if(instance.blend_mode_override)
- instance.blend_mode = instance.blend_mode_override
- instance.del_on_map_removal = FALSE
- instance.screen_loc = "[map_name]:CENTER"
- cam_plane_masters += instance
+ cam_screen.generate_view(map_name)
cam_background = new
cam_background.assigned_map = map_name
cam_background.del_on_map_removal = FALSE
/datum/computer_file/program/secureye/Destroy()
QDEL_NULL(cam_screen)
- QDEL_LIST(cam_plane_masters)
QDEL_NULL(cam_background)
return ..()
@@ -77,9 +61,7 @@
if(is_living)
concurrent_users += user_ref
// Register map objects
- user.client.register_map_obj(cam_screen)
- for(var/plane in cam_plane_masters)
- user.client.register_map_obj(plane)
+ cam_screen.display_to(user)
user.client.register_map_obj(cam_background)
return ..()
@@ -103,7 +85,7 @@
/datum/computer_file/program/secureye/ui_static_data()
var/list/data = list()
- data["mapRef"] = map_name
+ data["mapRef"] = cam_screen.assigned_map
var/list/cameras = get_available_cameras()
data["cameras"] = list()
for(var/i in cameras)
@@ -140,7 +122,7 @@
// Living creature or not, we remove you anyway.
concurrent_users -= user_ref
// Unregister map objects
- user.client.clear_map(map_name)
+ cam_screen.hide_from(user)
// Turn off the console
if(length(concurrent_users) == 0 && is_living)
camera_ref = null
diff --git a/code/modules/pai/card.dm b/code/modules/pai/card.dm
index 2e4b2b3bee8..4941a522edf 100644
--- a/code/modules/pai/card.dm
+++ b/code/modules/pai/card.dm
@@ -158,7 +158,7 @@
return
add_overlay(
list(mutable_appearance(icon, "[initial(icon_state)]-alert"),
- emissive_appearance(icon, "[initial(icon_state)]-alert", alpha = src.alpha)))
+ emissive_appearance(icon, "[initial(icon_state)]-alert", src, alpha = src.alpha)))
/** Removes any overlays */
/obj/item/pai_card/proc/remove_alert()
diff --git a/code/modules/pai/login.dm b/code/modules/pai/login.dm
index 0f418c4bf32..c28c7c84d9e 100644
--- a/code/modules/pai/login.dm
+++ b/code/modules/pai/login.dm
@@ -5,6 +5,6 @@
client.perspective = EYE_PERSPECTIVE
if(holoform)
- client.eye = src
+ client.set_eye(src)
else
- client.eye = card
+ client.set_eye(card)
diff --git a/code/modules/pai/shell.dm b/code/modules/pai/shell.dm
index 3bc8e3ac03a..133af192ba1 100644
--- a/code/modules/pai/shell.dm
+++ b/code/modules/pai/shell.dm
@@ -7,7 +7,6 @@
/mob/living/silicon/pai/start_pulling(atom/movable/thing, state, force = move_force, supress_message = FALSE)
return FALSE
-
/mob/living/silicon/pai/update_resting()
. = ..()
if(resting)
@@ -89,7 +88,7 @@
mob_head.release(display_messages = FALSE)
if(client)
client.perspective = EYE_PERSPECTIVE
- client.eye = card
+ client.set_eye(card)
var/turf/target = drop_location()
card.forceMove(target)
forceMove(card)
@@ -140,7 +139,7 @@
card.forceMove(src)
if(client)
client.perspective = EYE_PERSPECTIVE
- client.eye = src
+ client.set_eye(src)
set_light_on(FALSE)
icon_state = "[chassis]"
held_state = "[chassis]"
diff --git a/code/modules/point/point.dm b/code/modules/point/point.dm
index c3ffae4c908..f816269eba8 100644
--- a/code/modules/point/point.dm
+++ b/code/modules/point/point.dm
@@ -25,18 +25,17 @@
animate(visual, pixel_x = (tile.x - our_tile.x) * world.icon_size + pointed_atom.pixel_x, pixel_y = (tile.y - our_tile.y) * world.icon_size + pointed_atom.pixel_y, time = 1.7, easing = EASE_OUT)
/atom/movable/proc/create_point_bubble(atom/pointed_atom)
- var/obj/effect/thought_bubble_effect = new
-
var/mutable_appearance/thought_bubble = mutable_appearance(
'icons/effects/effects.dmi',
"thought_bubble",
+ offset_spokesman = src,
plane = POINT_PLANE,
appearance_flags = KEEP_APART,
)
var/mutable_appearance/pointed_atom_appearance = new(pointed_atom.appearance)
pointed_atom_appearance.blend_mode = BLEND_INSET_OVERLAY
- pointed_atom_appearance.plane = thought_bubble.plane
+ pointed_atom_appearance.plane = FLOAT_PLANE
pointed_atom_appearance.layer = FLOAT_LAYER
pointed_atom_appearance.pixel_x = 0
pointed_atom_appearance.pixel_y = 0
@@ -49,21 +48,21 @@
thought_bubble.pixel_x = 16
thought_bubble.pixel_y = 32
thought_bubble.alpha = 200
- thought_bubble.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
var/mutable_appearance/point_visual = mutable_appearance(
'icons/hud/screen_gen.dmi',
- "arrow",
- plane = thought_bubble.plane,
+ "arrow"
)
thought_bubble.overlays += point_visual
- // vis_contents is used to preserve mouse opacity
- thought_bubble_effect.appearance = thought_bubble
- vis_contents += thought_bubble_effect
+ add_overlay(thought_bubble)
+ LAZYADD(update_on_z, thought_bubble)
+ addtimer(CALLBACK(src, .proc/clear_point_bubble, thought_bubble), POINT_TIME)
- QDEL_IN(thought_bubble_effect, POINT_TIME)
+/atom/movable/proc/clear_point_bubble(mutable_appearance/thought_bubble)
+ LAZYREMOVE(update_on_z, thought_bubble)
+ cut_overlay(thought_bubble)
/obj/effect/temp_visual/point
name = "pointer"
diff --git a/code/modules/power/apc/apc_appearance.dm b/code/modules/power/apc/apc_appearance.dm
index ef6672b0c17..06e0452efad 100644
--- a/code/modules/power/apc/apc_appearance.dm
+++ b/code/modules/power/apc/apc_appearance.dm
@@ -55,18 +55,18 @@
return
. += mutable_appearance(icon, "apcox-[locked]")
- . += emissive_appearance(icon, "apcox-[locked]")
+ . += emissive_appearance(icon, "apcox-[locked]", src)
. += mutable_appearance(icon, "apco3-[charging]")
- . += emissive_appearance(icon, "apco3-[charging]")
+ . += emissive_appearance(icon, "apco3-[charging]", src)
if(!operating)
return
. += mutable_appearance(icon, "apco0-[equipment]")
- . += emissive_appearance(icon, "apco0-[equipment]")
+ . += emissive_appearance(icon, "apco0-[equipment]", src)
. += mutable_appearance(icon, "apco1-[lighting]")
- . += emissive_appearance(icon, "apco1-[lighting]")
+ . += emissive_appearance(icon, "apco1-[lighting]", src)
. += mutable_appearance(icon, "apco2-[environ]")
- . += emissive_appearance(icon, "apco2-[environ]")
+ . += emissive_appearance(icon, "apco2-[environ]", src)
/// Checks for what icon updates we will need to handle
/obj/machinery/power/apc/proc/check_updates()
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index 600655e9acf..a84f2287c6d 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -637,46 +637,33 @@ GLOBAL_LIST_INIT(wire_node_generating_types, typecacheof(list(/obj/structure/gri
machinery_layer = MACHINERY_LAYER_1
layer = WIRE_LAYER - 0.02 //Below all cables Disabled layers can lay over hub
color = "white"
- var/obj/effect/node/machinery_node
- var/obj/effect/node/layer1/cable_node_1
- var/obj/effect/node/layer2/cable_node_2
- var/obj/effect/node/layer3/cable_node_3
-
-/obj/effect/node
- icon = 'icons/obj/power_cond/layer_cable.dmi'
- icon_state = "l2-noconnection"
- vis_flags = VIS_INHERIT_ID|VIS_INHERIT_PLANE|VIS_INHERIT_LAYER
- color = "black"
-
-/obj/effect/node/layer1
- color = "red"
- icon_state = "l1-1-2-4-8-node"
- vis_flags = VIS_INHERIT_ID|VIS_INHERIT_PLANE|VIS_INHERIT_LAYER|VIS_UNDERLAY
-
-/obj/effect/node/layer2
- color = "yellow"
- icon_state = "l2-1-2-4-8-node"
- vis_flags = VIS_INHERIT_ID|VIS_INHERIT_PLANE|VIS_INHERIT_LAYER|VIS_UNDERLAY
-
-/obj/effect/node/layer3
- color = "blue"
- icon_state = "l4-1-2-4-8-node"
- vis_flags = VIS_INHERIT_ID|VIS_INHERIT_PLANE|VIS_INHERIT_LAYER|VIS_UNDERLAY
/obj/structure/cable/multilayer/update_icon_state()
SHOULD_CALL_PARENT(FALSE)
return
/obj/structure/cable/multilayer/update_icon()
- machinery_node?.alpha = machinery_layer & MACHINERY_LAYER_1 ? 255 : 0
- cable_node_1?.alpha = cable_layer & CABLE_LAYER_1 ? 255 : 0
- cable_node_2?.alpha = cable_layer & CABLE_LAYER_2 ? 255 : 0
+ . = ..()
+ underlays.Cut()
+ var/mutable_appearance/cable_node_3 = mutable_appearance('icons/obj/power_cond/layer_cable.dmi', "l4-1-2-4-8-node")
+ cable_node_3.color = "blue"
cable_node_3?.alpha = cable_layer & CABLE_LAYER_3 ? 255 : 0
- return ..()
-
+ underlays += cable_node_3
+ var/mutable_appearance/cable_node_2 = mutable_appearance('icons/obj/power_cond/layer_cable.dmi', "l2-1-2-4-8-node")
+ cable_node_2.color = "yellow"
+ cable_node_2?.alpha = cable_layer & CABLE_LAYER_2 ? 255 : 0
+ underlays += cable_node_2
+ var/mutable_appearance/cable_node_1 = mutable_appearance('icons/obj/power_cond/layer_cable.dmi', "l1-1-2-4-8-node")
+ cable_node_1.color = "red"
+ cable_node_1?.alpha = cable_layer & CABLE_LAYER_1 ? 255 : 0
+ underlays += cable_node_1
+ var/mutable_appearance/machinery_node = mutable_appearance('icons/obj/power_cond/layer_cable.dmi', "l2-noconnection")
+ machinery_node.color = "black"
+ machinery_node?.alpha = machinery_layer & MACHINERY_LAYER_1 ? 255 : 0
+ underlays += machinery_node
+
/obj/structure/cable/multilayer/Initialize(mapload)
. = ..()
-
var/turf/T = get_turf(src)
for(var/obj/structure/cable/C in T.contents - src)
if(C.cable_layer & cable_layer)
@@ -684,23 +671,8 @@ GLOBAL_LIST_INIT(wire_node_generating_types, typecacheof(list(/obj/structure/gri
if(!mapload)
auto_propagate_cut_cable(src)
- machinery_node = new /obj/effect/node()
- vis_contents += machinery_node
- cable_node_1 = new /obj/effect/node/layer1()
- vis_contents += cable_node_1
- cable_node_2 = new /obj/effect/node/layer2()
- vis_contents += cable_node_2
- cable_node_3 = new /obj/effect/node/layer3()
- vis_contents += cable_node_3
update_appearance()
-/obj/structure/cable/multilayer/Destroy() // called when a cable is deleted
- QDEL_NULL(machinery_node)
- QDEL_NULL(cable_node_1)
- QDEL_NULL(cable_node_2)
- QDEL_NULL(cable_node_3)
- return ..() // then go ahead and delete the cable
-
/obj/structure/cable/multilayer/examine(mob/user)
. += ..()
. += span_notice("L1:[cable_layer & CABLE_LAYER_1 ? "Connect" : "Disconnect"].")
diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm
index 8ead0cc4bfa..c1d043c6051 100644
--- a/code/modules/power/gravitygenerator.dm
+++ b/code/modules/power/gravitygenerator.dm
@@ -182,7 +182,7 @@ GLOBAL_LIST_EMPTY(gravity_generators)
if(count <= 3) // Their sprite is the top part of the generator
part.set_density(FALSE)
part.layer = WALL_OBJ_LAYER
- part.plane = GAME_PLANE_UPPER
+ SET_PLANE(part, GAME_PLANE_UPPER, our_turf)
part.sprite_number = count
part.main_part = src
generator_parts += part
@@ -442,6 +442,13 @@ GLOBAL_LIST_EMPTY(gravity_generators)
if(charge_count != 0 && charging_state != POWER_UP)
enable()
+/obj/machinery/gravity_generator/main/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ for(var/obj/machinery/gravity_generator/part as anything in generator_parts)
+ SET_PLANE(part, PLANE_TO_TRUE(part.plane), new_turf)
+
//prevents shuttles attempting to rotate this since it messes up sprites
/obj/machinery/gravity_generator/main/shuttleRotate(rotation, params)
params = NONE
diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm
index 872efe0ba14..2cb055e3ff0 100644
--- a/code/modules/power/solar.dm
+++ b/code/modules/power/solar.dm
@@ -44,13 +44,20 @@
unset_control() //remove from control computer
return ..()
+/obj/machinery/power/solar/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ SET_PLANE(panel_edge, PLANE_TO_TRUE(panel_edge.plane), new_turf)
+ SET_PLANE(panel, PLANE_TO_TRUE(panel.plane), new_turf)
+
/obj/machinery/power/solar/proc/add_panel_overlay(icon_state, y_offset)
var/obj/effect/overlay/overlay = new()
overlay.vis_flags = VIS_INHERIT_ID | VIS_INHERIT_ICON
overlay.appearance_flags = TILE_BOUND
overlay.icon_state = icon_state
overlay.layer = FLY_LAYER
- overlay.plane = ABOVE_GAME_PLANE
+ SET_PLANE_EXPLICIT(overlay, ABOVE_GAME_PLANE, src)
overlay.pixel_y = y_offset
vis_contents += overlay
return overlay
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index 4956032895d..7b33fee08e4 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -226,6 +226,13 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
QDEL_NULL(psyOverlay)
return ..()
+/obj/machinery/power/supermatter_crystal/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ if(warp)
+ SET_PLANE_EXPLICIT(warp, PLANE_TO_TRUE(warp.plane), src)
+
/obj/machinery/power/supermatter_crystal/proc/update_constants()
pressure_bonus_derived_steepness = (1 - 1 / pressure_bonus_max_multiplier) / (pressure_bonus_max_pressure ** pressure_bonus_curve_angle)
pressure_bonus_derived_constant = 1 / pressure_bonus_max_multiplier - pressure_bonus_derived_steepness
@@ -319,8 +326,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
/obj/machinery/power/supermatter_crystal/update_overlays()
. = ..()
- . += delamination_strategy.overlays(src)
- return .
+ if(delamination_strategy)
+ . += delamination_strategy.overlays(src)
/obj/machinery/power/supermatter_crystal/proc/force_delam()
SIGNAL_HANDLER
diff --git a/code/modules/power/tracker.dm b/code/modules/power/tracker.dm
index 694e1a5b2c3..cb18b9ac0a4 100644
--- a/code/modules/power/tracker.dm
+++ b/code/modules/power/tracker.dm
@@ -36,13 +36,20 @@
unset_control() //remove from control computer
return ..()
+/obj/machinery/power/tracker/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ SET_PLANE(tracker_dish_edge, PLANE_TO_TRUE(tracker_dish_edge.plane), new_turf)
+ SET_PLANE(tracker_dish, PLANE_TO_TRUE(tracker_dish.plane), new_turf)
+
/obj/machinery/power/tracker/proc/add_panel_overlay(icon_state, y_offset)
var/obj/effect/overlay/overlay = new()
overlay.vis_flags = VIS_INHERIT_ID | VIS_INHERIT_ICON
overlay.appearance_flags = TILE_BOUND
overlay.icon_state = icon_state
overlay.layer = FLY_LAYER
- overlay.plane = ABOVE_GAME_PLANE
+ SET_PLANE_EXPLICIT(overlay, ABOVE_GAME_PLANE, src)
overlay.pixel_y = y_offset
vis_contents += overlay
return overlay
diff --git a/code/modules/power/turbine/turbine.dm b/code/modules/power/turbine/turbine.dm
index 1ebcf024e2b..d86a9088d4a 100644
--- a/code/modules/power/turbine/turbine.dm
+++ b/code/modules/power/turbine/turbine.dm
@@ -78,7 +78,7 @@
if(active)
. += active_overlay
if(emissive)
- . += emissive_appearance(icon, active_overlay)
+ . += emissive_appearance(icon, active_overlay, src)
else
. += off_overlay
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index 25c14aeaef7..a34179e5c7d 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -981,16 +981,16 @@
var/atom/movable/plane_master_controller/pm_controller = M.hud_used.plane_master_controllers[PLANE_MASTERS_GAME]
var/rotation = min(round(current_cycle/20), 89) // By this point the player is probably puking and quitting anyway
- for(var/key in pm_controller.controlled_planes)
- animate(pm_controller.controlled_planes[key], transform = matrix(rotation, MATRIX_ROTATE), time = 5, easing = QUAD_EASING, loop = -1)
+ for(var/atom/movable/screen/plane_master/plane as anything in pm_controller.get_planes())
+ animate(plane, transform = matrix(rotation, MATRIX_ROTATE), time = 5, easing = QUAD_EASING, loop = -1)
animate(transform = matrix(-rotation, MATRIX_ROTATE), time = 5, easing = QUAD_EASING)
return ..()
/datum/reagent/toxin/rotatium/on_mob_end_metabolize(mob/living/M)
if(M?.hud_used)
var/atom/movable/plane_master_controller/pm_controller = M.hud_used.plane_master_controllers[PLANE_MASTERS_GAME]
- for(var/key in pm_controller.controlled_planes)
- animate(pm_controller.controlled_planes[key], transform = matrix(), time = 5, easing = QUAD_EASING)
+ for(var/atom/movable/screen/plane_master/plane as anything in pm_controller.get_planes())
+ animate(plane, transform = matrix(), time = 5, easing = QUAD_EASING)
..()
/datum/reagent/toxin/anacea
diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm
index b5eb2dffd8d..6df766d402b 100644
--- a/code/modules/recycling/disposal/bin.dm
+++ b/code/modules/recycling/disposal/bin.dm
@@ -392,15 +392,15 @@
//check for items in disposal - occupied light
if(contents.len > 0)
. += "dispover-full"
- . += emissive_appearance(icon, "dispover-full", alpha = src.alpha)
+ . += emissive_appearance(icon, "dispover-full", src, alpha = src.alpha)
//charging and ready light
if(pressure_charging)
. += "dispover-charge"
- . += emissive_appearance(icon, "dispover-charge-glow", alpha = src.alpha)
+ . += emissive_appearance(icon, "dispover-charge-glow", src, alpha = src.alpha)
else if(full_pressure)
. += "dispover-ready"
- . += emissive_appearance(icon, "dispover-ready-glow", alpha = src.alpha)
+ . += emissive_appearance(icon, "dispover-ready-glow", src, alpha = src.alpha)
/obj/machinery/disposal/bin/proc/do_flush()
set waitfor = FALSE
diff --git a/code/modules/research/xenobiology/vatgrowing/vatgrower.dm b/code/modules/research/xenobiology/vatgrowing/vatgrower.dm
index a4b5d7acf56..25160b7e5ce 100644
--- a/code/modules/research/xenobiology/vatgrowing/vatgrower.dm
+++ b/code/modules/research/xenobiology/vatgrowing/vatgrower.dm
@@ -93,7 +93,7 @@
if(isnull(on_overlay))
on_overlay = iconstate2appearance(icon, "growing_vat_on")
off_overlay = iconstate2appearance(icon, "growing_vat_off")
- emissive_overlay = emissive_appearance(icon, "growing_vat_glow", alpha = src.alpha)
+ emissive_overlay = emissive_appearance(icon, "growing_vat_glow", src, alpha = src.alpha)
. += emissive_overlay
if(is_operational)
if(resampler_active)
diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm
index ff6d65a00a7..83efd230119 100644
--- a/code/modules/security_levels/keycard_authentication.dm
+++ b/code/modules/security_levels/keycard_authentication.dm
@@ -97,7 +97,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/keycard_auth, 26)
if(event_source && !(machine_stat & (NOPOWER|BROKEN)))
. += mutable_appearance(icon, "auth_on")
- . += emissive_appearance(icon, "auth_on", alpha = src.alpha)
+ . += emissive_appearance(icon, "auth_on", src, alpha = src.alpha)
/obj/machinery/keycard_auth/proc/sendEvent(event_type)
triggerer = usr
diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm
index b58e02d4095..066fc3cdcc6 100644
--- a/code/modules/shuttle/navigation_computer.dm
+++ b/code/modules/shuttle/navigation_computer.dm
@@ -108,7 +108,7 @@
var/y_off = T.y - origin.y
I.loc = locate(origin.x + x_off, origin.y + y_off, origin.z) //we have to set this after creating the image because it might be null, and images created in nullspace are immutable.
I.layer = ABOVE_NORMAL_TURF_LAYER
- I.plane = ABOVE_GAME_PLANE
+ SET_PLANE(I, ABOVE_GAME_PLANE, T)
I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
the_eye.placement_images[I] = list(x_off, y_off)
@@ -196,7 +196,7 @@
var/image/newI = image('icons/effects/alphacolors.dmi', the_eye.loc, "blue")
newI.loc = I.loc //It is highly unlikely that any landing spot including a null tile will get this far, but better safe than sorry.
newI.layer = ABOVE_OPEN_TURF_LAYER
- newI.plane = ABOVE_GAME_PLANE
+ SET_PLANE_EXPLICIT(newI, ABOVE_GAME_PLANE, V)
newI.mouse_opacity = 0
the_eye.placed_images += newI
@@ -320,7 +320,7 @@
console.checkLandingSpot()
/mob/camera/ai_eye/remote/shuttle_docker/update_remote_sight(mob/living/user)
- user.sight = BLIND|SEE_TURFS
+ user.set_sight(BLIND|SEE_TURFS)
user.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
user.sync_lighting_plane_alpha()
return TRUE
diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm
index 3929b3d125a..b4628c9d439 100644
--- a/code/modules/shuttle/on_move.dm
+++ b/code/modules/shuttle/on_move.dm
@@ -115,7 +115,8 @@ All ShuttleMove procs go here
/atom/movable/proc/afterShuttleMove(turf/oldT, list/movement_force, shuttle_dir, shuttle_preferred_direction, move_dir, rotation)
var/turf/newT = get_turf(src)
if (newT.z != oldT.z)
- on_changed_z_level(oldT, newT)
+ var/same_z_layer = (GET_TURF_PLANE_OFFSET(oldT) == GET_TURF_PLANE_OFFSET(newT))
+ on_changed_z_level(oldT, newT, same_z_layer)
if(light)
update_light()
@@ -359,6 +360,10 @@ All ShuttleMove procs go here
/obj/docking_port/stationary/onShuttleMove()
return FALSE
+// Holy shit go away
+/obj/effect/abstract/z_holder/onShuttleMove()
+ return FALSE
+
// Special movable stationary port, for your mothership shenanigans
/obj/docking_port/stationary/movable/onShuttleMove(turf/newT, turf/oldT, list/movement_force, move_dir, obj/docking_port/stationary/old_dock, obj/docking_port/mobile/moving_dock)
if(!moving_dock.can_move_docking_ports || old_dock == src)
diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm
index ce7f4de8265..cf83f3aa46f 100644
--- a/code/modules/shuttle/shuttle.dm
+++ b/code/modules/shuttle/shuttle.dm
@@ -150,7 +150,7 @@
//Debug proc used to highlight bounding area
/obj/docking_port/proc/highlight(_color = "#f00")
invisibility = 0
- plane = GHOST_PLANE
+ SET_PLANE_IMPLICIT(src, GHOST_PLANE)
var/list/L = return_coords()
var/turf/T0 = locate(L[1],L[2],z)
var/turf/T1 = locate(L[3],L[4],z)
diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm
index 492ec97f31d..a9219ac1809 100644
--- a/code/modules/station_goals/bsa.dm
+++ b/code/modules/station_goals/bsa.dm
@@ -175,23 +175,35 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE)
/obj/machinery/bsa/full/Initialize(mapload, cannon_direction = WEST)
. = ..()
- if(!top_layer)
- top_layer = mutable_appearance(icon, layer = ABOVE_MOB_LAYER)
- top_layer.plane = GAME_PLANE_UPPER
switch(cannon_direction)
if(WEST)
setDir(WEST)
- top_layer.icon_state = "top_west"
icon_state = "cannon_west"
if(EAST)
setDir(EAST)
pixel_x = -128
bound_x = -128
- top_layer.icon_state = "top_east"
icon_state = "cannon_east"
- add_overlay(top_layer)
+ get_layer()
reload()
+/obj/machinery/bsa/full/proc/get_layer()
+ top_layer = mutable_appearance(icon, layer = ABOVE_MOB_LAYER)
+ SET_PLANE_EXPLICIT(top_layer, GAME_PLANE_UPPER, src)
+ switch(dir)
+ if(WEST)
+ top_layer.icon_state = "top_west"
+ if(EAST)
+ top_layer.icon_state = "top_east"
+ add_overlay(top_layer)
+
+/obj/machinery/bsa/full/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer)
+ return ..()
+ cut_overlay(top_layer)
+ get_layer()
+ return ..()
+
/obj/machinery/bsa/full/proc/fire(mob/user, turf/bullseye)
reload()
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index 159cf6b4350..c93c10ee8ce 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -841,12 +841,13 @@
//EMISSIVE CODE START
if(blocks_emissive)
- var/mutable_appearance/limb_em_block = emissive_blocker(limb.icon, limb.icon_state, alpha = limb.alpha)
+ var/atom/location = loc || owner || src
+ var/mutable_appearance/limb_em_block = emissive_blocker(limb.icon, limb.icon_state, location, alpha = limb.alpha)
limb_em_block.dir = image_dir
limb.overlays += limb_em_block
if(aux_zone)
- var/mutable_appearance/aux_em_block = emissive_blocker(aux.icon, aux.icon_state, alpha = aux.alpha)
+ var/mutable_appearance/aux_em_block = emissive_blocker(aux.icon, aux.icon_state, location, alpha = aux.alpha)
aux_em_block.dir = image_dir
aux.overlays += aux_em_block
diff --git a/code/modules/surgery/bodyparts/hair.dm b/code/modules/surgery/bodyparts/hair.dm
index 681ea719d51..bfee32967fd 100644
--- a/code/modules/surgery/bodyparts/hair.dm
+++ b/code/modules/surgery/bodyparts/hair.dm
@@ -67,26 +67,27 @@
hair_style = human_head_owner.hairstyle
facial_hairstyle = human_head_owner.facial_hairstyle
+ var/atom/location = loc || owner || src
if(facial_hairstyle && !facial_hair_hidden && (FACEHAIR in species_flags_list))
sprite_accessory = GLOB.facial_hairstyles_list[facial_hairstyle]
if(sprite_accessory)
//Create the overlay
facial_overlay = mutable_appearance(sprite_accessory.icon, sprite_accessory.icon_state, -HAIR_LAYER)
- facial_overlay.overlays += emissive_blocker(facial_overlay.icon, facial_overlay.icon_state, alpha = hair_alpha)
+ facial_overlay.overlays += emissive_blocker(facial_overlay.icon, facial_overlay.icon_state, location, alpha = hair_alpha)
//Gradients
facial_hair_gradient_style = LAZYACCESS(human_head_owner.grad_style, GRADIENT_FACIAL_HAIR_KEY)
if(facial_hair_gradient_style)
facial_hair_gradient_color = LAZYACCESS(human_head_owner.grad_color, GRADIENT_FACIAL_HAIR_KEY)
facial_gradient_overlay = make_gradient_overlay(sprite_accessory.icon, sprite_accessory.icon_state, HAIR_LAYER, GLOB.facial_hair_gradients_list[facial_hair_gradient_style], facial_hair_gradient_color)
- facial_overlay.overlays += emissive_blocker(sprite_accessory.icon, sprite_accessory.icon_state, alpha = hair_alpha)
+ facial_overlay.overlays += emissive_blocker(sprite_accessory.icon, sprite_accessory.icon_state, location, alpha = hair_alpha)
if(!hair_hidden && !show_debrained && (HAIR in species_flags_list))
sprite_accessory = GLOB.hairstyles_list[hair_style]
if(sprite_accessory)
hair_overlay = mutable_appearance(sprite_accessory.icon, sprite_accessory.icon_state, -HAIR_LAYER)
- hair_overlay.overlays += emissive_blocker(hair_overlay.icon, hair_overlay.icon_state, alpha = hair_alpha)
+ hair_overlay.overlays += emissive_blocker(hair_overlay.icon, hair_overlay.icon_state, location, alpha = hair_alpha)
hair_gradient_style = LAZYACCESS(human_head_owner.grad_style, GRADIENT_HAIR_KEY)
if(hair_gradient_style)
hair_gradient_color = LAZYACCESS(human_head_owner.grad_color, GRADIENT_HAIR_KEY)
diff --git a/code/modules/surgery/organs/autosurgeon.dm b/code/modules/surgery/organs/autosurgeon.dm
index 145776cddbb..6ca3ae28f99 100644
--- a/code/modules/surgery/organs/autosurgeon.dm
+++ b/code/modules/surgery/organs/autosurgeon.dm
@@ -34,7 +34,7 @@
. = ..()
if(stored_organ)
. += loaded_overlay
- . += emissive_appearance(icon, loaded_overlay)
+ . += emissive_appearance(icon, loaded_overlay, src)
/obj/item/autosurgeon/proc/load_organ(obj/item/organ/loaded_organ, mob/living/user)
if(user)
diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm
index 3f8e6723d5f..5ad554916f0 100644
--- a/code/modules/surgery/organs/eyes.dm
+++ b/code/modules/surgery/organs/eyes.dm
@@ -114,8 +114,8 @@
var/obscured = parent.check_obscured_slots(TRUE)
if(overlay_ignore_lighting && !(obscured & ITEM_SLOT_EYES))
- eye_left.overlays += emissive_appearance(eye_left.icon, eye_left.icon_state, alpha = eye_left.alpha)
- eye_right.overlays += emissive_appearance(eye_right.icon, eye_right.icon_state, alpha = eye_right.alpha)
+ eye_left.overlays += emissive_appearance(eye_left.icon, eye_left.icon_state, parent, alpha = eye_left.alpha)
+ eye_right.overlays += emissive_appearance(eye_right.icon, eye_right.icon_state, parent, alpha = eye_right.alpha)
// Cry emote overlay
if (HAS_TRAIT(parent, TRAIT_CRYING)) // Caused by the *cry emote
@@ -183,7 +183,6 @@
lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
else
lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
- sight_flags &= ~SEE_BLACKNESS
owner.update_sight()
/obj/item/organ/internal/eyes/night_vision/alien
diff --git a/code/modules/vehicles/atv.dm b/code/modules/vehicles/atv.dm
index 1da429d4845..6f5aa8c8ab9 100644
--- a/code/modules/vehicles/atv.dm
+++ b/code/modules/vehicles/atv.dm
@@ -53,22 +53,22 @@
turret.pixel_x = base_pixel_x
turret.pixel_y = base_pixel_y + 4
turret.layer = ABOVE_MOB_LAYER
- turret.plane = GAME_PLANE_UPPER
+ SET_PLANE(turret, GAME_PLANE_UPPER, our_turf)
if(EAST)
turret.pixel_x = base_pixel_x - 12
turret.pixel_y = base_pixel_y + 4
turret.layer = OBJ_LAYER
- turret.plane = GAME_PLANE
+ SET_PLANE(turret, GAME_PLANE, our_turf)
if(SOUTH)
turret.pixel_x = base_pixel_x
turret.pixel_y = base_pixel_y + 4
turret.layer = OBJ_LAYER
- turret.plane = GAME_PLANE
+ SET_PLANE(turret, GAME_PLANE, our_turf)
if(WEST)
turret.pixel_x = base_pixel_x + 12
turret.pixel_y = base_pixel_y + 4
turret.layer = OBJ_LAYER
- turret.plane = GAME_PLANE
+ SET_PLANE(turret, GAME_PLANE, our_turf)
/obj/vehicle/ridden/atv/welder_act(mob/living/user, obj/item/W)
if(user.combat_mode)
diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm
index 3ad5c120e46..6b7f4957f3a 100644
--- a/code/modules/vehicles/mecha/_mecha.dm
+++ b/code/modules/vehicles/mecha/_mecha.dm
@@ -196,14 +196,15 @@
/// Ui size, so you can make the UI bigger if you let it load a lot of stuff
var/ui_y = 600
/// ref to screen object that displays in the middle of the UI
- var/atom/movable/screen/mech_view/ui_view
+ var/atom/movable/screen/map_view/ui_view
/obj/item/radio/mech //this has to go somewhere
subspace_transmission = TRUE
/obj/vehicle/sealed/mecha/Initialize(mapload)
. = ..()
- ui_view = new(null, src)
+ ui_view = new()
+ ui_view.generate_view("mech_view_[REF(src)]")
if(enclosed)
internal_tank = new (src)
RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE , .proc/disconnect_air)
diff --git a/code/modules/vehicles/mecha/combat/durand.dm b/code/modules/vehicles/mecha/combat/durand.dm
index 17b84ad7ed8..e25f215085c 100644
--- a/code/modules/vehicles/mecha/combat/durand.dm
+++ b/code/modules/vehicles/mecha/combat/durand.dm
@@ -168,7 +168,7 @@ own integrity back to max. Shield is automatically dropped if we run out of powe
. = ..()
src.chassis = chassis
src.layer = layer
- src.plane = plane
+ SET_PLANE_EXPLICIT(src, plane, src)
setDir(dir)
RegisterSignal(src, COMSIG_MECHA_ACTION_TRIGGER, .proc/activate)
RegisterSignal(chassis, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, .proc/shield_glide_size_update)
diff --git a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm
index be4aff48340..4f5a8b49dcb 100644
--- a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm
+++ b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm
@@ -133,7 +133,7 @@
chassis.movedelay = 1
chassis.density = FALSE
chassis.layer = ABOVE_ALL_MOB_LAYER
- chassis.plane = GAME_PLANE_UPPER_FOV_HIDDEN
+ SET_PLANE(chassis, GAME_PLANE_UPPER_FOV_HIDDEN, launch_turf)
animate(chassis, alpha = 0, time = 8, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL)
animate(chassis, pixel_z = 400, time = 10, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) //Animate our rising mech (just like pods hehe)
addtimer(CALLBACK(src, .proc/begin_landing), 2 SECONDS)
@@ -156,6 +156,7 @@
* it's just the animations of the mecha coming down + another timer for the final landing effect
*/
/datum/action/vehicle/sealed/mecha/skyfall/proc/land()
+ var/turf/landed_on = get_turf(chassis)
chassis.visible_message(span_danger("[chassis] lands from above!"))
playsound(chassis, 'sound/effects/explosion1.ogg', 50, 1)
chassis.resistance_flags &= ~INDESTRUCTIBLE
@@ -164,12 +165,11 @@
chassis.movedelay = initial(chassis.movedelay)
chassis.density = TRUE
chassis.layer = initial(chassis.layer)
- chassis.plane = initial(chassis.plane)
+ SET_PLANE(chassis, initial(chassis.plane), landed_on)
skyfall_charge_level = 0
chassis.update_appearance(UPDATE_ICON_STATE)
for(var/mob/living/shaken in range(7, chassis))
shake_camera(shaken, 5, 5)
- var/turf/landed_on = get_turf(chassis)
for(var/thing in range(1, chassis))
if(isopenturf(thing))
var/turf/open/floor/crushed_tile = thing
diff --git a/code/modules/vehicles/mecha/mecha_ui.dm b/code/modules/vehicles/mecha/mecha_ui.dm
index 046d2c13989..b3d79bca8cb 100644
--- a/code/modules/vehicles/mecha/mecha_ui.dm
+++ b/code/modules/vehicles/mecha/mecha_ui.dm
@@ -1,42 +1,13 @@
-/// A preview of the mech for the UI
-/atom/movable/screen/mech_view
- name = "mechview"
- del_on_map_removal = FALSE
- layer = OBJ_LAYER
- plane = GAME_PLANE
-
- /// The body that is displayed
- var/obj/vehicle/sealed/mecha/owner
- ///list of plane masters to apply to owners
- var/list/plane_masters = list()
-
-/atom/movable/screen/mech_view/Initialize(mapload, obj/vehicle/sealed/mecha/newowner)
- . = ..()
- owner = newowner
- assigned_map = "mech_view_[REF(owner)]"
- set_position(1, 1)
- for(var/plane_master_type in subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/blackness)
- var/atom/movable/screen/plane_master/plane_master = new plane_master_type()
- plane_master.screen_loc = "[assigned_map]:CENTER"
- plane_masters += plane_master
-
-/atom/movable/screen/mech_view/Destroy()
- QDEL_LIST(plane_masters)
- owner = null
- return ..()
-
/obj/vehicle/sealed/mecha/ui_close(mob/user)
. = ..()
- user.client?.screen -= ui_view.plane_masters
- user.client?.clear_map(ui_view.assigned_map)
+ ui_view.hide_from(user)
/obj/vehicle/sealed/mecha/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Mecha", name, ui_x, ui_y)
ui.open()
- user.client?.screen |= ui_view.plane_masters
- user.client?.register_map_obj(ui_view)
+ ui_view.display_to(user)
/obj/vehicle/sealed/mecha/ui_status(mob/user)
if(contains(user))
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index 26615e976e7..7e5a7c92295 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -285,7 +285,7 @@
if(panel_open)
. += panel_type
if(light_mask && !(machine_stat & BROKEN) && powered())
- . += emissive_appearance(icon, light_mask)
+ . += emissive_appearance(icon, light_mask, src)
/obj/machinery/vending/atom_break(damage_flag)
. = ..()
@@ -652,7 +652,7 @@ GLOBAL_LIST_EMPTY(vending_products)
visible_message(span_danger("[src] tips over!"))
tilted = TRUE
layer = ABOVE_MOB_LAYER
- plane = GAME_PLANE_UPPER
+ SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER)
var/crit_case
if(crit)
@@ -767,7 +767,7 @@ GLOBAL_LIST_EMPTY(vending_products)
tilted = FALSE
layer = initial(layer)
- plane = initial(plane)
+ SET_PLANE_IMPLICIT(src, initial(plane))
var/matrix/M = matrix()
M.Turn(0)
diff --git a/code/modules/wiremod/components/bci/hud/bar_overlay.dm b/code/modules/wiremod/components/bci/hud/bar_overlay.dm
index 07d25f35e48..5a1a46f5716 100644
--- a/code/modules/wiremod/components/bci/hud/bar_overlay.dm
+++ b/code/modules/wiremod/components/bci/hud/bar_overlay.dm
@@ -44,8 +44,9 @@
number_clear = round(number_clear / 6.25) * 6.25
else if(current_option == COMP_BAR_OVERLAY_VERTICAL)
number_clear = round(number_clear / 10) * 10
+
var/image/cool_overlay = image(icon = 'icons/hud/screen_bci.dmi', loc = target_atom, icon_state = "[options_map[current_option]][number_clear]", layer = RIPPLE_LAYER)
- cool_overlay.plane = ABOVE_LIGHTING_PLANE
+ SET_PLANE_EXPLICIT(cool_overlay, ABOVE_LIGHTING_PLANE, target_atom)
if(image_pixel_x.value != null)
cool_overlay.pixel_x = image_pixel_x.value
diff --git a/code/modules/wiremod/components/bci/hud/counter_overlay.dm b/code/modules/wiremod/components/bci/hud/counter_overlay.dm
index 8a0389f5317..43795b54c1f 100644
--- a/code/modules/wiremod/components/bci/hud/counter_overlay.dm
+++ b/code/modules/wiremod/components/bci/hud/counter_overlay.dm
@@ -65,7 +65,7 @@
QDEL_NULL(overlay)
var/image/counter = image(icon = 'icons/hud/screen_bci.dmi', icon_state = "hud_numbers", loc = owner, layer = RIPPLE_LAYER)
- counter.plane = ABOVE_LIGHTING_PLANE
+ SET_PLANE_EXPLICIT(counter, ABOVE_LIGHTING_PLANE, owner)
if(image_pixel_x.value != null)
counter.pixel_x = image_pixel_x.value
@@ -87,7 +87,7 @@
for(var/i = 1 to 3)
var/cur_num = round(cleared_number / (10 ** (3 - i))) % 10
var/image/number = image(icon = 'icons/hud/screen_bci.dmi', icon_state = "hud_number_[cur_num]", loc = owner, layer = RIPPLE_LAYER)
- number.plane = ABOVE_LIGHTING_PLANE
+ SET_PLANE_EXPLICIT(number, ABOVE_LIGHTING_PLANE, owner)
if(image_pixel_x.value != null)
number.pixel_x = image_pixel_x.value + (i - 1) * 9
diff --git a/code/modules/wiremod/components/bci/hud/object_overlay.dm b/code/modules/wiremod/components/bci/hud/object_overlay.dm
index c7f07ccf0e7..7d4bf8e6a8e 100644
--- a/code/modules/wiremod/components/bci/hud/object_overlay.dm
+++ b/code/modules/wiremod/components/bci/hud/object_overlay.dm
@@ -100,7 +100,7 @@
QDEL_NULL(overlay)
var/image/cool_overlay = image(icon = 'icons/hud/screen_bci.dmi', loc = target_atom, icon_state = options_map[object_overlay_options.value], layer = RIPPLE_LAYER)
- cool_overlay.plane = ABOVE_LIGHTING_PLANE
+ SET_PLANE_EXPLICIT(cool_overlay, ABOVE_LIGHTING_PLANE, target_atom)
if(image_pixel_x.value != null)
cool_overlay.pixel_x = image_pixel_x.value
diff --git a/icons/hud/map_backgrounds.dmi b/icons/hud/map_backgrounds.dmi
index dc6e3e46b16..df8871833ae 100644
Binary files a/icons/hud/map_backgrounds.dmi and b/icons/hud/map_backgrounds.dmi differ
diff --git a/icons/obj/machines/gateway.dmi b/icons/obj/machines/gateway.dmi
index 4a9acb79960..b08c7dda40c 100644
Binary files a/icons/obj/machines/gateway.dmi and b/icons/obj/machines/gateway.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 363a8a44070..2e293a4e2e9 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -41,6 +41,7 @@
#include "code\__DEFINES\basic_mobs.dm"
#include "code\__DEFINES\bitfields.dm"
#include "code\__DEFINES\blackmarket.dm"
+#include "code\__DEFINES\blend_modes.dm"
#include "code\__DEFINES\blob_defines.dm"
#include "code\__DEFINES\blood.dm"
#include "code\__DEFINES\bodyparts.dm"
@@ -211,6 +212,7 @@
#include "code\__DEFINES\atmospherics\atmos_piping.dm"
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
+#include "code\__DEFINES\dcs\signals\mapping.dm"
#include "code\__DEFINES\dcs\signals\signals_action.dm"
#include "code\__DEFINES\dcs\signals\signals_admin.dm"
#include "code\__DEFINES\dcs\signals\signals_adventure.dm"
@@ -234,6 +236,7 @@
#include "code\__DEFINES\dcs\signals\signals_global_object.dm"
#include "code\__DEFINES\dcs\signals\signals_greyscale.dm"
#include "code\__DEFINES\dcs\signals\signals_heretic.dm"
+#include "code\__DEFINES\dcs\signals\signals_hud.dm"
#include "code\__DEFINES\dcs\signals\signals_hydroponic.dm"
#include "code\__DEFINES\dcs\signals\signals_janitor.dm"
#include "code\__DEFINES\dcs\signals\signals_ladder.dm"
@@ -289,6 +292,7 @@
#include "code\__DEFINES\research\research_categories.dm"
#include "code\__HELPERS\_auxtools_api.dm"
#include "code\__HELPERS\_lists.dm"
+#include "code\__HELPERS\_planes.dm"
#include "code\__HELPERS\_string_lists.dm"
#include "code\__HELPERS\admin.dm"
#include "code\__HELPERS\ai.dm"
@@ -446,6 +450,7 @@
#include "code\_onclick\hud\human.dm"
#include "code\_onclick\hud\living.dm"
#include "code\_onclick\hud\map_popups.dm"
+#include "code\_onclick\hud\map_view.dm"
#include "code\_onclick\hud\movable_screen_objects.dm"
#include "code\_onclick\hud\new_player.dm"
#include "code\_onclick\hud\ooze.dm"
@@ -459,6 +464,7 @@
#include "code\_onclick\hud\screentip.dm"
#include "code\_onclick\hud\rendering\plane_master.dm"
#include "code\_onclick\hud\rendering\plane_master_controller.dm"
+#include "code\_onclick\hud\rendering\plane_master_group.dm"
#include "code\_onclick\hud\rendering\render_plate.dm"
#include "code\controllers\admin.dm"
#include "code\controllers\controller.dm"
@@ -632,6 +638,7 @@
#include "code\datums\verb_callbacks.dm"
#include "code\datums\verbs.dm"
#include "code\datums\view.dm"
+#include "code\datums\visual_data.dm"
#include "code\datums\voice_of_god_command.dm"
#include "code\datums\weakrefs.dm"
#include "code\datums\world_topic.dm"
@@ -813,6 +820,7 @@
#include "code\datums\components\gunpoint.dm"
#include "code\datums\components\hazard_area.dm"
#include "code\datums\components\heirloom.dm"
+#include "code\datums\components\hide_highest_offset.dm"
#include "code\datums\components\holderloving.dm"
#include "code\datums\components\igniter.dm"
#include "code\datums\components\infective.dm"
@@ -2114,6 +2122,7 @@
#include "code\modules\admin\verbs\mapping.dm"
#include "code\modules\admin\verbs\maprotation.dm"
#include "code\modules\admin\verbs\panicbunker.dm"
+#include "code\modules\admin\verbs\plane_debugger.dm"
#include "code\modules\admin\verbs\playsound.dm"
#include "code\modules\admin\verbs\possess.dm"
#include "code\modules\admin\verbs\pray.dm"
@@ -2462,6 +2471,7 @@
#include "code\modules\asset_cache\assets\permissions.dm"
#include "code\modules\asset_cache\assets\pills.dm"
#include "code\modules\asset_cache\assets\pipes.dm"
+#include "code\modules\asset_cache\assets\plane_debug.dm"
#include "code\modules\asset_cache\assets\portraits.dm"
#include "code\modules\asset_cache\assets\radar.dm"
#include "code\modules\asset_cache\assets\research_designs.dm"
diff --git a/tgui/packages/tgui/interfaces/Gateway.js b/tgui/packages/tgui/interfaces/Gateway.js
index 672b72a047e..95b483f3886 100644
--- a/tgui/packages/tgui/interfaces/Gateway.js
+++ b/tgui/packages/tgui/interfaces/Gateway.js
@@ -1,10 +1,10 @@
import { useBackend } from '../backend';
-import { Box, Button, Icon, NoticeBox, ProgressBar, Section } from '../components';
+import { Box, Button, ByondUi, NoticeBox, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
export const Gateway = () => {
return (
-
+
@@ -19,6 +19,7 @@ const GatewayContent = (props, context) => {
gateway_status = false,
current_target = null,
destinations = [],
+ gateway_mapkey,
} = data;
if (!gateway_present) {
return (
@@ -33,8 +34,18 @@ const GatewayContent = (props, context) => {
if (current_target) {
return (
-
-
diff --git a/tgui/packages/tgui/interfaces/PlaneMasterDebug.tsx b/tgui/packages/tgui/interfaces/PlaneMasterDebug.tsx
new file mode 100644
index 00000000000..4e5b9f5f9c6
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PlaneMasterDebug.tsx
@@ -0,0 +1,1112 @@
+import { useBackend, useLocalState } from '../backend';
+import { InfinitePlane, Stack, Box, Button, Modal, Dropdown, Section, LabeledList, Tooltip, Slider } from '../components';
+import { sortBy } from 'common/collections';
+import { flow } from 'common/fp';
+import { classes, shallowDiffers } from 'common/react';
+import { Component, createRef, RefObject } from 'inferno';
+import { Window } from '../layouts';
+import { resolveAsset } from '../assets';
+import { MOUSE_BUTTON_LEFT, noop } from './IntegratedCircuit/constants';
+import { Connections } from './IntegratedCircuit/Connections';
+
+enum ConnectionType {
+ Relay,
+ Filter,
+}
+
+enum ConnectionDirection {
+ Incoming,
+ Outgoing,
+}
+
+type ConnectionRef = {
+ ref: string;
+ sort_by: number;
+};
+
+type Plane = {
+ name: string;
+ documentation: string;
+ plane: number;
+ our_ref: string;
+ offset: number;
+ real_plane: number;
+ renders_onto: number[];
+ blend_mode: number;
+ color: string | number[];
+ alpha: number;
+ render_target: string;
+ incoming_relays: string[];
+ outgoing_relays: string[];
+ incoming_filters: string[];
+ outgoing_filters: string[];
+ intended_hidden: boolean;
+
+ incoming_connections: ConnectionRef[];
+ outgoing_connections: ConnectionRef[];
+
+ x: number;
+ y: number;
+ step_size: number;
+ size_x: number;
+ size_y: number;
+};
+
+type Relay = {
+ name: string;
+ layer: number;
+};
+
+type Filter = {
+ type: string;
+ name: string;
+ render_source: string;
+};
+
+// Type of something that spawn a connection
+type Connected = {
+ connect_color: string;
+ source: number;
+ source_ref: string;
+ target: number;
+ target_ref: string;
+ our_ref: string;
+
+ source_index: number;
+ target_index: number;
+
+ connect_type: ConnectionType;
+};
+
+interface AssocPlane {
+ [index: string]: Plane;
+}
+
+interface AssocRelays {
+ [index: string]: Relay & Connected;
+}
+
+interface AssocFilters {
+ [index: string]: Filter & Connected;
+}
+
+interface AssocConnected {
+ [index: string]: Connected;
+}
+
+interface AssocString {
+ [index: string]: string;
+}
+
+type Position = {
+ x: number;
+ y: number;
+};
+
+type Connection = {
+ color: string;
+ from: Position;
+ to: Position;
+ ref: string;
+};
+
+type PlaneDebugData = {
+ our_group: string;
+ present_groups: string[];
+ enable_group_view: boolean;
+ relay_info: AssocRelays;
+ plane_info: AssocPlane;
+ filter_connect: AssocFilters;
+ depth_stack: AssocString[];
+ mob_name: string;
+ mob_ref: string;
+ our_ref: string;
+ tracking_active: boolean;
+};
+
+// Stolen wholesale from fontcode
+const textWidth = (text, font, fontsize) => {
+ // default font height is 12 in tgui
+ font = fontsize + 'x ' + font;
+ const c = document.createElement('canvas');
+ const ctx = c.getContext('2d') as any;
+ ctx.font = font;
+ const width = ctx.measureText(text).width;
+ return width;
+};
+
+const planeToPosition = function (plane: Plane, index, is_incoming): Position {
+ return {
+ x: is_incoming ? plane.x : plane.x + plane.size_x,
+ y:
+ 29 +
+ plane.y +
+ plane.step_size * index +
+ (plane.step_size - plane.step_size / 3),
+ };
+};
+
+// Takes a plane, returns the amount of node space it will need
+const getPlaneNodeHeight = function (plane: Plane): number {
+ return Math.max(
+ plane.incoming_relays.length + plane.incoming_filters.length,
+ plane.outgoing_relays.length + plane.outgoing_filters.length
+ );
+};
+
+const sortConnectionRefs = function (
+ refs: ConnectionRef[],
+ direction: ConnectionDirection,
+ connectSources: AssocConnected
+) {
+ refs = sortBy((connection: ConnectionRef) => connection.sort_by)(refs);
+ refs.map((connection, index) => {
+ let connectSource = connectSources[connection.ref];
+ if (direction === ConnectionDirection.Outgoing) {
+ connectSource.source_index = index;
+ } else if (direction === ConnectionDirection.Incoming) {
+ connectSource.target_index = index;
+ }
+ });
+ return refs;
+};
+
+const addConnectionRefs = function (
+ read_from: string[],
+ add_type: ConnectionDirection,
+ add_to: ConnectionRef[],
+ reference: AssocConnected,
+ plane_info: AssocPlane
+) {
+ for (const ref of read_from) {
+ const connected = reference[ref];
+ let our_plane;
+ // If we're incoming, use the target ref, and vis versa
+ if (add_type === ConnectionDirection.Incoming) {
+ our_plane = plane_info[connected.source_ref];
+ } else if (add_type === ConnectionDirection.Outgoing) {
+ our_plane = plane_info[connected.target_ref];
+ }
+ add_to.push({
+ ref: ref,
+ sort_by: our_plane.plane,
+ });
+ }
+};
+
+// Takes a list of planes, uses the depth stack to position them
+const positionPlanes = function (context, connectSources: AssocConnected) {
+ const { data } = useBackend(context);
+ const { plane_info, relay_info, filter_connect, depth_stack } = data;
+
+ // First, we concatinate our connection sources
+ // We need them in one list partly for later purposes
+ // But also so we can set their source/target index nicely
+ for (const ref of Object.keys(relay_info)) {
+ let connection_source: Connected = relay_info[ref];
+ connection_source.connect_type = ConnectionType.Relay;
+ connection_source.connect_color = 'blue';
+ connectSources[ref] = connection_source;
+ }
+ for (const ref of Object.keys(filter_connect)) {
+ let connection_source: Connected = filter_connect[ref];
+ connection_source.connect_type = ConnectionType.Filter;
+ connection_source.connect_color = 'purple';
+ connectSources[ref] = connection_source;
+ }
+
+ for (const plane_ref of Object.keys(plane_info)) {
+ let our_plane = plane_info[plane_ref];
+ const incoming_conct: ConnectionRef[] = [] as any;
+ const outgoing_conct: ConnectionRef[] = [] as any;
+ addConnectionRefs(
+ our_plane.incoming_relays,
+ ConnectionDirection.Incoming,
+ incoming_conct,
+ relay_info,
+ plane_info
+ );
+ addConnectionRefs(
+ our_plane.incoming_filters,
+ ConnectionDirection.Incoming,
+ incoming_conct,
+ filter_connect,
+ plane_info
+ );
+ addConnectionRefs(
+ our_plane.outgoing_relays,
+ ConnectionDirection.Outgoing,
+ outgoing_conct,
+ relay_info,
+ plane_info
+ );
+ addConnectionRefs(
+ our_plane.outgoing_filters,
+ ConnectionDirection.Outgoing,
+ outgoing_conct,
+ filter_connect,
+ plane_info
+ );
+ our_plane.incoming_connections = sortConnectionRefs(
+ incoming_conct,
+ ConnectionDirection.Incoming,
+ connectSources
+ );
+ our_plane.outgoing_connections = sortConnectionRefs(
+ outgoing_conct,
+ ConnectionDirection.Outgoing,
+ connectSources
+ );
+ }
+
+ // First we sort by the plane of each member,
+ // then we sort by the plane of each member's head
+ // This way we get a nicely sorted list
+ // and get rid of the now unneeded parent refs
+ const stack = depth_stack.map((layer) =>
+ flow([
+ sortBy((plane: string) => plane_info[plane].plane),
+ sortBy((plane: string) => {
+ const read_from = plane_info[layer[plane]];
+ if (!read_from) {
+ return 0;
+ }
+ return read_from.plane;
+ }),
+ ])(Object.keys(layer))
+ );
+
+ let base_x = 0;
+ let longest_name = 0;
+ let tallest_stack = 0;
+ for (const layer of stack) {
+ base_x += longest_name;
+ base_x += 150;
+ let new_longest = 0;
+ let last_node_len = 0;
+ let base_y = 0;
+ for (const plane_ref of layer) {
+ const old_y = base_y;
+ const plane = plane_info[plane_ref];
+ // - because we want to work backwards rather then forwards
+ plane.x = -base_x;
+ // I am assuming the height of a plane master with two connections looks
+ // like 50% name 50% (two) nodes
+ base_y += 45;
+ // One extra for the relay add button
+ base_y += 19 * (last_node_len + 1);
+ // We need to know how large node steps are for later
+ plane.step_size = 19;
+ plane.y = base_y;
+ const width = textWidth(plane.name, '', 16) + 30;
+ plane.size_x = width;
+ plane.size_y = old_y - base_y;
+ new_longest = Math.max(new_longest, width);
+ last_node_len = getPlaneNodeHeight(plane);
+ }
+ longest_name = new_longest;
+ tallest_stack = Math.max(tallest_stack, base_y);
+ }
+
+ // Now that we've got everything stacked, we need to center it
+ for (const layer of stack) {
+ const last_ref = layer[layer.length - 1];
+ const last_plane = plane_info[last_ref];
+ const delta_tall = tallest_stack - last_plane.y;
+ // Now that we know how "off" our height is, we can correct it
+ // We halve because otherwise this looks dumb
+ const offset = delta_tall / 2;
+ for (const plane_ref of layer) {
+ const plane = plane_info[plane_ref];
+ plane.y += offset;
+ }
+ }
+};
+
+const arrayRemove = function (arr: any, value) {
+ return arr.filter((element) => element !== value);
+};
+
+export class PlaneMasterDebug extends Component {
+ constructor() {
+ super();
+ this.handlePortClick = this.handlePortClick.bind(this);
+ }
+
+ handlePortClick(connection: Connected, isOutput, event) {
+ if (event.button !== MOUSE_BUTTON_LEFT) {
+ return;
+ }
+ const { act, data } = useBackend(this.context);
+ const { plane_info } = data;
+
+ event.preventDefault();
+ if (connection.connect_type === ConnectionType.Relay) {
+ // Close the connection
+ act('disconnect_relay', {
+ source: connection.source_ref,
+ target: connection.target_ref,
+ });
+ let source_plane = plane_info[connection.source_ref];
+ let target_plane = plane_info[connection.source_ref];
+ source_plane.outgoing_relays = arrayRemove(
+ source_plane.outgoing_relays,
+ connection.our_ref
+ );
+ target_plane.incoming_relays = arrayRemove(
+ target_plane.incoming_relays,
+ connection.our_ref
+ );
+ } else if (connection.connect_type === ConnectionType.Filter) {
+ // Close the connection
+ const filter = connection as Filter & Connected;
+ act('disconnect_filter', {
+ target: filter.target_ref,
+ name: filter.name,
+ });
+ let source_plane = plane_info[connection.source_ref];
+ let target_plane = plane_info[connection.source_ref];
+ source_plane.outgoing_filters = arrayRemove(
+ source_plane.outgoing_filters,
+ connection.our_ref
+ );
+ target_plane.incoming_filters = arrayRemove(
+ target_plane.incoming_filters,
+ connection.our_ref
+ );
+ }
+ }
+
+ render() {
+ const { act, data } = useBackend(this.context);
+ const { plane_info, mob_name } = data;
+ const [showAdd, setShowAdd] = useLocalState(this.context, 'showAdd', false);
+
+ const [connectSources, setConnectSouces] = useLocalState(
+ this.context,
+ 'connectionSources',
+ {}
+ );
+
+ positionPlanes(this.context, connectSources);
+
+ const connections: Connection[] = [];
+
+ for (const ref of Object.keys(connectSources)) {
+ const connect = connectSources[ref];
+ const source_plane = plane_info[connect.source_ref];
+ const target_plane = plane_info[connect.target_ref];
+ connections.push({
+ color: connect.connect_color,
+ from: planeToPosition(source_plane, connect.source_index, false),
+ to: planeToPosition(target_plane, connect.target_index, true),
+ ref: ref,
+ });
+ }
+
+ return (
+
+
+
+ {Object.keys(plane_info).map(
+ (plane_key, index) =>
+ plane_key && (
+
+ )
+ )}
+
+
+
+
+
+ );
+ }
+}
+
+type PlaneMasterProps = {
+ name: string;
+ incoming_connections: ConnectionRef[];
+ outgoing_connections: ConnectionRef[];
+ connected_list: AssocConnected;
+ our_plane: Plane;
+ x: number;
+ y: number;
+ onPortMouseDown: Function;
+ act: Function;
+};
+
+class PlaneMaster extends Component {
+ shouldComponentUpdate(nextProps, nextState) {
+ const { incoming_connections, outgoing_connections } = this
+ .props as PlaneMasterProps;
+
+ return (
+ shallowDiffers(this.props, nextProps) ||
+ shallowDiffers(this.state as object, nextState) ||
+ shallowDiffers(incoming_connections, nextProps.incoming_connections) ||
+ shallowDiffers(outgoing_connections, nextProps.outgoing_connections)
+ );
+ }
+
+ render() {
+ const {
+ name,
+ incoming_connections,
+ outgoing_connections,
+ connected_list,
+ our_plane,
+ x,
+ y,
+ onPortMouseDown = noop,
+ act = noop,
+ ...rest
+ } = this.props as PlaneMasterProps;
+ const [showAdd, setShowAdd] = useLocalState(this.context, 'showAdd', false);
+ const [currentPlane, setCurrentPlane] = useLocalState(
+ this.context,
+ 'currentPlane',
+ {}
+ );
+ const [readPlane, setReadPlane] = useLocalState(
+ this.context,
+ 'readPlane',
+ ''
+ );
+
+ // Assigned onto the ports
+ const PortOptions = {
+ onPortMouseDown: onPortMouseDown,
+ };
+ return (
+
+
+ {name}
+ setReadPlane(our_plane.our_ref)}
+ />
+
+
+
+
+
+ {incoming_connections.map((con_ref, portIndex) => (
+
+
+
+ ))}
+
+
+
+
+ {outgoing_connections.map((con_ref, portIndex) => (
+
+
+
+ ))}
+
+ {
+ setShowAdd(true);
+ setCurrentPlane(our_plane);
+ }}
+ right="-4px"
+ tooltip="Connect to another plane"
+ />
+
+
+
+
+
+
+ );
+ }
+}
+
+type PortProps = {
+ connection: Connected;
+ isOutput?: boolean;
+ onPortMouseDown?: Function;
+ act: Function;
+};
+class Port extends Component {
+ // Ok so like, we're basically treating iconRef as a string here
+ // Mostly so svg can work later. You're really not supposed to do this.
+ // Should really be a RefObject
+ // But it's how it was being done in circuit code, so eh
+ iconRef: RefObject | RefObject | any;
+
+ constructor() {
+ super();
+ this.iconRef = createRef();
+ this.handlePortMouseDown = this.handlePortMouseDown.bind(this);
+ }
+
+ handlePortMouseDown(e) {
+ const {
+ connection,
+ isOutput,
+ onPortMouseDown = noop,
+ } = this.props as PortProps;
+ onPortMouseDown(connection, isOutput, e);
+ }
+
+ render() {
+ const { connection, isOutput, ...rest } = this.props as PortProps;
+
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+const DrawAbovePlane = (props, context) => {
+ const [showAdd, setShowAdd] = useLocalState(context, 'showAdd', false);
+ const [showInfo, setShowInfo] = useLocalState(context, 'showInfo', false);
+ const [readPlane, setReadPlane] = useLocalState(context, 'readPlane', '');
+
+ const { act, data } = useBackend(context);
+ // Plane groups don't use relays right now, because of a byond bug
+ // This exists mostly so enabling viewing them is easy and simple
+ const { enable_group_view } = data;
+
+ return (
+ <>
+ {!!readPlane && }
+ {!readPlane && (
+ <>
+
+
+
+
+
+ >
+ )}
+ {!!enable_group_view && }
+ {!!showAdd && }
+ {!!showInfo && }
+ >
+ );
+};
+
+const PlaneWindow = (props, context) => {
+ const { data, act } = useBackend(context);
+ const { plane_info } = data;
+ const [readPlane, setReadPlane] = useLocalState(context, 'readPlane', '');
+
+ const workingPlane: Plane = plane_info[readPlane];
+
+ // NOT sanitized, since this would only be editable by admins or coders
+ const doc_html = {
+ __html: workingPlane.documentation,
+ };
+ return (
+
+
+
+
+
+
+
+ >
+ }>
+
+
+
+
+
+
+ {workingPlane.plane}
+
+
+
+
+ {workingPlane.offset}
+
+
+
+
+ {workingPlane.render_target || '""'}
+
+
+
+
+ {workingPlane.blend_mode}
+
+
+
+
+ {workingPlane.intended_hidden}
+
+
+
+
+
+
+ act('vv_plane', {
+ edit: workingPlane.our_ref,
+ })
+ }>
+ View Variables
+
+
+ act('edit_filters', {
+ edit: workingPlane.our_ref,
+ })
+ }>
+ Edit Filters
+
+
+ act('edit_color_matrix', {
+ edit: workingPlane.our_ref,
+ })
+ }>
+ Edit Color Matrix
+
+
+ act('set_alpha', {
+ edit: workingPlane.our_ref,
+ alpha: value,
+ })
+ }
+ onChange={(e, value) =>
+ act('set_alpha', {
+ edit: workingPlane.our_ref,
+ alpha: value,
+ })
+ }>
+ Alpha ({workingPlane.alpha})
+
+
+
+ );
+};
+
+const InfoButton = (props, context) => {
+ const [showInfo, setShowInfo] = useLocalState(context, 'showInfo', false);
+ const { no_position } = props;
+ const foreign = has_foreign_mob(context);
+
+ return (
+ setShowInfo(true)}
+ tooltip="Info about what this window is/why it exists"
+ />
+ );
+};
+
+const MobResetButton = (props, context): any => {
+ const { act } = useBackend(context);
+ const { no_position } = props;
+ if (!has_foreign_mob(context)) {
+ return;
+ }
+
+ return (
+ act('reset_mob')}
+ tooltip="Reset our focused mob to your active mob"
+ />
+ );
+};
+
+const ToggleMirror = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { no_position } = props;
+ const { tracking_active } = data;
+
+ return (
+ act('toggle_mirroring')}
+ tooltip={
+ (tracking_active ? 'Disables' : 'Enables') +
+ " seeing 'through' the edited mob's eyes, for debugging and such"
+ }
+ />
+ );
+};
+
+const has_foreign_mob = function (context) {
+ const { data } = useBackend(context);
+ const { mob_ref, our_ref } = data;
+ return mob_ref !== our_ref;
+};
+
+const VVButton = (props, context) => {
+ const { act } = useBackend(context);
+ const { no_position } = props;
+
+ return (
+ act('vv_mob')}
+ tooltip="View the variables of our currently focused mob"
+ />
+ );
+};
+
+const GroupDropdown = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { our_group, present_groups } = data;
+
+ return (
+
+
+
+ act('set_group', {
+ target_group: value,
+ })
+ }
+ />
+
+
+ );
+};
+
+const RefreshButton = (props, context) => {
+ const { act } = useBackend(context);
+ const { no_position } = props;
+
+ return (
+ act('refresh')}
+ tooltip="Refreshes ALL plane masters. Kinda laggy, but useful"
+ />
+ );
+};
+
+const ClosePlaneWindow = (props, context) => {
+ const [readPlane, setReadPlane] = useLocalState(context, 'readPlane', '');
+ return setReadPlane('')} />;
+};
+
+const AddModal = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { plane_info } = data;
+
+ const [showAdd, setShowAdd] = useLocalState(context, 'showAdd', false);
+ const [currentPlane, setCurrentPlane] = useLocalState(
+ context,
+ 'currentPlane',
+ {} as Plane
+ );
+ const [currentTarget, setCurrentTarget] = useLocalState(
+ context,
+ 'currentTarget',
+ {} as Plane
+ );
+
+ const plane_list = Object.keys(plane_info).map((plane) => plane_info[plane]);
+ const planes = sortBy((plane: Plane) => -plane.plane)(plane_list);
+
+ const plane_options = planes.map((plane) => plane.name);
+
+ return (
+
+
+ {
+ setCurrentTarget(planes[plane_options.indexOf(value)]);
+ }}
+ />
+
+
+ {
+ act('connect_relay', {
+ source: currentPlane.plane,
+ target: currentTarget.plane,
+ });
+ setShowAdd(false);
+ }}>
+ Confirm
+
+
+
+ setShowAdd(false)}>
+ Cancel
+
+
+
+
+
+ );
+};
+
+const InfoModal = (props, context) => {
+ const [showInfo, setShowInfo] = useLocalState(context, 'showInfo', false);
+ const pain = '';
+ const display = {
+ __html: pain,
+ };
+ return (
+
+ setShowInfo(false)}
+ />
+ }>
+
+
What is all this?
+ This UI exists to help visualize plane masters, the backbone of our
+ rendering system.
+ It also provices some tools for editing and messing with them.
+
+
How to use this UI
+ This UI exists primarially as a visualizer, mostly because this info is
+ quite obscure, and I want it to be easier to understand.
+
+
+ That said, it also supports editing plane masters, adding and removing
+ relays, and provides easy access to color matrix/filter/alpha/vv
+ editing.
+
+ To start off with, each little circle represents a{' '}
+ render_target based connection.
+
+ Blue nodes are relays, so drawing one plane onto another. Purple ones
+ are filter based connections.
+ You can tell where a node starts and ends based on the side of the plane
+ it's on.
+
+ Adding a new relay is simple, you just need to hit the + button, and
+ select a plane by name to relay onto.
+
+ Each plane can be viewed more closely by clicking the little button in
+ it's top right corner. This opens a sidebar, and displays a lot of
+ more general info about the plane and its purpose, alongside exposing
+ some useful buttons and interesting values.
+
+ Planes are aligned based off their initial setup. If you end up breaking
+ things byond repair, or just want to reset things, you can hit the
+ recycle button in the top left to totally refresh your plane masters.{' '}
+
+
+
What is a plane master?
+ You can think of a plane master as a way to group a set of objects onto
+ one rendering slate.
+ It is per client too, which makes it quite powerful. This is done using
+ the plane variable of /atom.
+
+ We first create an atom with an appearance flag that contains{' '}
+ PLANE_MASTER and give it a plane value.
+ Then we mirror the same plane value on all the atoms we
+ want to render in this group.
+
+
+ Finally, we place the PLANE_MASTER'd atom in the
+ relevent client's screen contents.
+ That sets up the bare minimum.
+
+
+ It is worth noting that the plane var does not only effect
+ this rendering grouping behavior.
+ It also effects the layering of objects on the map.
+
+ For this reason, there are some effects that are pretty much impossible
+ with planes.
+ Masking one thing while also drawing that thing in the correct order
+ with other objects on the map is a good example of this.
+
+ It is possible to do, but it's quite disruptive.
+
+
+ Normally, planes will just group, apply an effect, and then draw
+ directly to the game.
+
+ What if we wanted to draw planes onto other planes then?
+
+
Render Targets and Relays
+
+ Rendering one thing onto another is actually not that complex.
+ We can set the render_target variable of an atom to relay
+ it to some render_source.
+
+ If that render_target is preceeded by *, it will
+ not be drawn to the actual client view, and instead just relayed.{' '}
+
+
+ Ok so we can relay a plane master onto some other atom, but how do we
+ get it on another plane master? We can't just draw it with{' '}
+ render_source, since we might want to relay more then one
+ plane master.
+
+
+ Why not relay it to another atom then? and then well, set that
+ atom's plane var to the plane master we want?
+
+ That ends up being about what we do.
+ It's worth noting that render sources are often used by filters,
+ normally to apply some displacement or mask.
+
+
+
Applying effects
+ Ok so we can group and relay planes, but what can we actually do with
+ that?
+
+ Lots of stuff it turns out. Filters are quite powerful, and we use them
+ quite a bit.
+ You can use filters to mask one plane with another, or use one plane as
+ a distortion source for another.
+
+ Can do more basic stuff too, setting a plane's color matrix can be
+ quite powerful.
+ Even just setting alpha to show and hide things can be quite useful.{' '}
+
+
+ I won't get into every effect we do here, you can learn more about
+ each plane by clicking on the little button in their top right.
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/styles/interfaces/IntegratedCircuit.scss b/tgui/packages/tgui/styles/interfaces/IntegratedCircuit.scss
index aeb09396983..7cfecb250ac 100644
--- a/tgui/packages/tgui/styles/interfaces/IntegratedCircuit.scss
+++ b/tgui/packages/tgui/styles/interfaces/IntegratedCircuit.scss
@@ -30,6 +30,14 @@ $fg-map: colors.$fg-map !default;
user-select: none;
}
+.ObjectComponent__Greyed_Content {
+ white-space: nowrap;
+ background-color: rgba(19, 19, 19, 0.5);
+
+ -ms-user-select: none;
+ user-select: none;
+}
+
.ObjectComponent__Port {
position: relative;
width: 13px;