diff --git a/.github/guides/HARDDELETES.md b/.github/guides/HARDDELETES.md index cdbdb2a126d68..6817de0d9b31a 100644 --- a/.github/guides/HARDDELETES.md +++ b/.github/guides/HARDDELETES.md @@ -1,6 +1,6 @@ # Hard Deletes -> Garbage collection is pretty gothic when you think about it. +> Garbage collection is pretty gothic when you think about it. > >An object in code is like a ghost, clinging to its former life, and especially to the people it knew. It can only pass on and truly die when it has dealt with its unfinished business. And only when its been forgotten by everyone who ever knew it. If even one other object remembers it, it has a connection to the living world that lets it keep hanging on > @@ -52,7 +52,7 @@ This of course means they can store that location in memory in another object's /proc/someshit(mem_location) var/datum/some_obj = new() - some_obj.reference = mem_location + some_obj.reference = mem_location ``` But what happens when you get rid of the object we're passing around references to? If we just cleared it out from memory, everything that holds a reference to it would suddenly be pointing to nowhere, or worse, something totally different! @@ -135,13 +135,13 @@ If that fails, search the object's typepath, and look and see if anything is hol BYOND currently doesn't have the capability to give us information about where a hard delete is. Fortunately we can search for most all of then ourselves. The procs to perform this search are hidden behind compile time defines, since they'd be way too risky to expose to admin button pressing -If you're having issues solving a harddel and want to perform this check yourself, go to `_compile_options.dm` and uncomment `TESTING`, `REFERENCE_TRACKING`, and `GC_FAILURE_HARD_LOOKUP` +If you're having issues solving a harddel and want to perform this check yourself, go to `_compile_options.dm` and uncomment `REFERENCE_TRACKING_STANDARD`. -You can read more about what each of these do in that file, but the long and short of it is if something would hard delete our code will search for the reference (This will look like your game crashing, just hold out) and print information about anything it finds to the runtime log, which you can find inside the round folder inside `/data/logs/year/month/day` +You can read more about what each of these do in that file, but the long and short of it is if something would hard delete our code will search for the reference (This will look like your game crashing, just hold out) and print information about anything it finds to [log_dir]/harddels.log, which you can find inside the round folder inside `/data/logs/year/month/day` -It'll tell you what object is holding the ref if it's in an object, or what pattern of list transversal was required to find the ref if it's hiding in a list of some sort +It'll tell you what object is holding the ref if it's in an object, or what pattern of list transversal was required to find the ref if it's hiding in a list of some sort, alongside the references remaining. -## Techniques For Fixing Hard Deletes +## Techniques For Fixing Hard Deletes Once you've found the issue, it becomes a matter of making sure the ref is cleared as a part of Destroy(). I'm gonna walk you through a few patterns and discuss how you might go about fixing them diff --git a/.github/guides/VISUALS.md b/.github/guides/VISUALS.md index 1c3cc6360c57b..4d8f7d3b585c8 100644 --- a/.github/guides/VISUALS.md +++ b/.github/guides/VISUALS.md @@ -4,10 +4,10 @@ Welcome to a breakdown of visuals and visual effects in our codebase, and in BYO 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. +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. +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). @@ -53,10 +53,10 @@ You'll find links to the relevant reference entries at the heading of each entry ## Appearances in BYOND - [Table of Contents](#table-of-contents) -- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/appearance) +- [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. +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. @@ -65,25 +65,27 @@ To start off with, appearances are static. You can't directly edit an appearance 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, +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), +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 +// 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) +/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 ``` +> **Note:** More then being static, appearances are unique. Only one copy of each set of appearance vars exists, and when you modify any of those vars, the corrosponding appearance variable changes its value to whatever matches the new hash. That's why appearance vars can induce inconsistent cost on modification. + > **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 @@ -91,7 +93,7 @@ Somewhat like this - [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. +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. @@ -104,67 +106,67 @@ 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. +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), +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()`, +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 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. +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()`, +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) +- [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). +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, +- `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. +- `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. +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. +`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. +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) +- [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. +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. @@ -172,7 +174,7 @@ See [/client/var/images](#client-images) ## Client Images - [Table of Contents](#table-of-contents) -- [Reference Entry](https://www.byond.com/docs/ref/#/client/var/images) +- [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. @@ -180,35 +182,35 @@ The image objects are displayed at their loc variable, and can be shown to more ### 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. +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. +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. +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. +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) +- [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, +`/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. +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 @@ -218,29 +220,29 @@ Clients get some choice in literally how they want the game to be rendered to th 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. +`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, +`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) +- [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. +`/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. +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) +- [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 @@ -256,21 +258,21 @@ The classic `screen_loc` format looks something like this (keeping in mind it co 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, +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. +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) +- [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. @@ -280,7 +282,7 @@ This is how we do lighting effects, since the lighting [plane](#planes) can be u ## Appearance Flags - [Table of Contents](#table-of-contents) -- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/appearance_flags) +- [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. @@ -293,8 +295,8 @@ Flags of interest: ## Gliding - [Table of Contents](#table-of-contents) -- [Reference Entry](https://www.byond.com/docs/ref/#/{notes}/gliding) - +- [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. @@ -306,37 +308,37 @@ This is done using `/atom/movable/proc/set_glide_size`, which will inform anythi 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. +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. +> 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. +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 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) +- [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". +- `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. This is in theory very powerful, but not possible with the 'side_map' [map format](https://www.byond.com/docs/ref/#/world/var/map_format) +This allows us to capture it, and say, blur it, or redraw it elsewhere. This is in theory very powerful, but not possible with the 'side_map' [map format](https://www.byond.com/docs/ref/#/world/var/map_format) ## BYOND Lighting @@ -346,14 +348,14 @@ Alongside OUR lighting implementation, which is discussed in with color matrixes 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, +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) +- [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. @@ -363,7 +365,7 @@ You can actually force it to use a particular mob's sight to avoid aspects of th ### See in Dark - [Table of Contents](#table-of-contents) -- [Reference Entry](https://www.byond.com/docs/ref/#/mob/var/see_in_dark) +- [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. @@ -372,9 +374,9 @@ 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) +- [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. +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 @@ -390,16 +392,16 @@ It's also used to hide some more then ghost invisible things, like some timers a ## Layers - [Table of Contents](#table-of-contents) -- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/layer) +- [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. +`/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. +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. @@ -408,7 +410,7 @@ This allows us to keep relative layer differences while not needing to make all ## Planes - [Table of Contents](#table-of-contents) -- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/plane) +- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/plane) Allllright `/atom/var/plane`s. Let's talk about em. @@ -417,16 +419,16 @@ 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! +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), +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), +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. @@ -434,15 +436,15 @@ We have a system that allows for arbitrary grouping of plane masters for the pur 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. +> 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`. +> 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) +- [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. @@ -477,8 +479,8 @@ 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, +- 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. @@ -493,7 +495,7 @@ 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. +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. @@ -502,18 +504,18 @@ 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) +- [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. +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), +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)) +> 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 @@ -521,10 +523,10 @@ master's effects are applied. ## Filters - [Table of Contents](#table-of-contents) -- [Reference Entry](https://www.byond.com/docs/ref/#/{notes}/filters) +- [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. +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 @@ -546,7 +548,7 @@ 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) +- [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. @@ -560,7 +562,7 @@ It'll let you add and tweak the particles attached to that atom. ## Pixel Offsets - [Table of Contents](#table-of-contents) -- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/pixel_x) +- [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. @@ -573,7 +575,7 @@ There are two "types" of each direction offset. There's the "real" offset (x/y) 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` +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. ## Map Formats @@ -587,10 +589,10 @@ There are 4 types currently. Only 2 that are interesting to us, and one that's n Most of them involve changing how layering works, from the standard [layers](#layers) and [planes](#planes) method. There's a bit more detail here, not gonna go into it, stuff like [underlays](https://www.byond.com/docs/ref/#/atom/var/underlays) drawing under things. See [Understanding The Renderer](https://www.byond.com/docs/ref/#/{notes}/renderer) -> There is very technically more nuance here. +> There is very technically more nuance here. > In default rendering modes, byond will conflict break by using the thing that is highest in the contents list of its location. Or lowest. Don't remember. -### [`TOPDOWN_MAP`](https://www.byond.com/docs/ref/#/{notes}/topdown) +### [`TOPDOWN_MAP`](https://www.byond.com/docs/ref/#/{notes}/topdown) This is the default rendering format. What we used to use. It tells byond to render going off [plane](#planes) first, then [layer](#layers). There's a few edgecases involving big icons, but it's small peanuts. @@ -606,7 +608,7 @@ The idea is the closer to the front of the screen something is, the higher its l `pixel_y` + `y` tell the engine where something "is". `/atom/var/bound_width`, `/atom/var/bound_height` and `/atom/var/bound_x/y` describe how big it is, which lets us in theory control what it tries to layer "against". -I'm not bothering with reference links because they are entirely unrelated. +I'm not bothering with reference links because they are entirely unrelated. An issue that will crop up with this map format is needing to manage the "visual" (how/where it renders) and physical (where it is in game) aspects of position and size. Physical position tells the renderer how to layer things. Visual position and a combination of physical bounds (manually set) and visual bounds (inferred from other aspects of it. Sprite width/height, physical bounds, transforms, filters, etc) tell it what the sprite might be rendering OVER. @@ -647,28 +649,28 @@ One more thing. Big icons are fucked From the byond reference >If you use an icon wider than one tile, the "footprint" of the isometric icon (the actual map tiles it takes up) will always be a square. That is, if your normal tile size is 64 and you want to show a 128x128 icon, the icon is two tiles wide and so it will take up a 2×2-tile area on the map. The height of a big icon is irrelevant--any excess height beyond width/2 is used to show vertical features. To draw this icon properly, other tiles on that same ground will be moved behind it in the drawing order. -> One important warning about using big icons in isometric mode is that you should only do this with dense atoms. If part of a big mob icon covers the same tile as a tall building for instance, the tall building is moved back and it could be partially covered by other turfs that are actually behind it. A mob walking onto a very large non-dense turf icon would experience similar irregularities. +> One important warning about using big icons in isometric mode is that you should only do this with dense atoms. If part of a big mob icon covers the same tile as a tall building for instance, the tall building is moved back and it could be partially covered by other turfs that are actually behind it. A mob walking onto a very large non-dense turf icon would experience similar irregularities. These can cause very annoying flickering. In fact, MUCH of how rendering works causes flickering. This is because we don't decide on a pixel by pixel case, the engine groups sprites up into a sort of rendering stack, unable to split them up. -This combined with us being unable to modify bounds means that if one bit of the view is conflicting. +This combined with us being unable to modify bounds means that if one bit of the view is conflicting. If A wants to be above B and below C, but B wants to be below A and above C, we'll be unable to resolve the rendering properly, leading to flickering depending on other aspects of the layering. This can just sort of spread. Very hard to debug. -### [`ISOMETRIC_MAP`](https://www.byond.com/docs/ref/#/{notes}/isometric) - +### [`ISOMETRIC_MAP`](https://www.byond.com/docs/ref/#/{notes}/isometric) + Isometric mode, renders everything well, isometrically, biased to the north east. This gives the possibility for fake 3d, assuming you get things drawn properly. It will render things in the foreground "last", after things in the background. This is the right way of thinking about it, it's not rendering things above or below, but in a layering order. This is interesting mostly in the context of understanding [side map](#side_map-check-the-main-page-too), but we did actually run an isometric station for april fools once. It was really cursed and flickered like crazy (which causes client lag). Fun as hell though. -The mode essentially overrides the layer/plane layering discussed before, and inserts new rules. -I wish I knew what those rules EXACTLY are, but I'm betting they're similar to [side map's](#side_map-check-the-main-page-too), and lummy's actually told me those. +The mode essentially overrides the layer/plane layering discussed before, and inserts new rules. +I wish I knew what those rules EXACTLY are, but I'm betting they're similar to [side map's](#side_map-check-the-main-page-too), and lummy's actually told me those. Yes this is all rather poorly documented. Similar to sidemap, we take physical position into account when deciding layering. In addition to its height positioning, we also account for width. -So both `pixel_y` and `pixel_x` can effect layering. `pixel_z` handles strictly visual y, and `pixel_w` handles x. +So both `pixel_y` and `pixel_x` can effect layering. `pixel_z` handles strictly visual y, and `pixel_w` handles x. This has similar big icon problems to [sidemap](#side_map-check-the-main-page-too). @@ -679,14 +681,14 @@ it would be automatically broken down into smaller icon states, which you would ## Color - [Table of Contents](#table-of-contents) -- [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/color) +- [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. +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. @@ -705,7 +707,7 @@ 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) +- [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 @@ -732,17 +734,17 @@ and forget to update this file. ## Animate() - [Table of Contents](#table-of-contents) -- [Reference Entry](https://www.byond.com/docs/ref/#/proc/animate) +- [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 +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. +Lets you give radiation glow a warm pulse, that sort of thing. ## GAGS - [Table of Contents](#table-of-contents) diff --git a/SQL/database_changelog.md b/SQL/database_changelog.md index a71cf673d3dbc..31b516f2af74e 100644 --- a/SQL/database_changelog.md +++ b/SQL/database_changelog.md @@ -2,19 +2,34 @@ Any time you make a change to the schema files, remember to increment the databa Make sure to also update `DB_MAJOR_VERSION` and `DB_MINOR_VERSION`, which can be found in `code/__DEFINES/subsystem.dm`. -The latest database version is 5.26; The query to update the schema revision table is: +The latest database version is 5.27; The query to update the schema revision table is: ```sql -INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 26); +INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 27); ``` or ```sql -INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 26); +INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 27); ``` In any query remember to add a prefix to the table names if you use one. - +----------------------------------------------------- +Version 5.27, 26 April 2024, by zephyrtfa +Add the ip intel table +```sql +DROP TABLE IF EXISTS `ipintel`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ipintel` ( + `ip` int(10) unsigned NOT NULL, + `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `intel` double NOT NULL DEFAULT '0', + PRIMARY KEY (`ip`), + KEY `idx_ipintel` (`ip`,`intel`,`date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +``` ----------------------------------------------------- Version 5.26, 03 December 2023, by distributivgesetz Set the default value of cloneloss to 0, as it's obsolete and it won't be set by blackbox anymore. diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql index 64bbf4259d931..19739a306b5eb 100644 --- a/SQL/tgstation_schema.sql +++ b/SQL/tgstation_schema.sql @@ -215,6 +215,20 @@ CREATE TABLE `ipintel` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `ipintel_whitelist` +-- + +DROP TABLE IF EXISTS `ipintel_whitelist`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ipintel_whitelist` ( + `ckey` varchar(32) NOT NULL, + `admin_ckey` varchar(32) NOT NULL, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `legacy_population` -- diff --git a/code/__DEFINES/admin_verb.dm b/code/__DEFINES/admin_verb.dm index cbf856bf4365f..7e811c121961c 100644 --- a/code/__DEFINES/admin_verb.dm +++ b/code/__DEFINES/admin_verb.dm @@ -87,6 +87,7 @@ _ADMIN_VERB(verb_path_name, verb_permissions, verb_name, verb_desc, verb_categor #define ADMIN_CATEGORY_OBJECT "Object" #define ADMIN_CATEGORY_MAPPING "Mapping" #define ADMIN_CATEGORY_PROFILE "Profile" +#define ADMIN_CATEGORY_IPINTEL "IPIntel" // Visibility flags #define ADMIN_VERB_VISIBLITY_FLAG_MAPPING_DEBUG "Map-Debug" diff --git a/code/__DEFINES/ipintel.dm b/code/__DEFINES/ipintel.dm new file mode 100644 index 0000000000000..9fbc14ae40dbf --- /dev/null +++ b/code/__DEFINES/ipintel.dm @@ -0,0 +1,15 @@ +#define IPINTEL_RATE_LIMIT_MINUTE "minute" +#define IPINTEL_RATE_LIMIT_DAY "day" + +/// An internal error occurred and the query cannot be processed +#define IPINTEL_UNKNOWN_INTERNAL_ERROR "unknown_internal_error" +/// An error occurred with the query and the result is unknown +#define IPINTEL_UNKNOWN_QUERY_ERROR "unknown_query_error" +/// Cannot query as we are rate limited for the rest of the day +#define IPINTEL_RATE_LIMITED_DAY "rate_limited_day" +/// Cannot query as we are rate limited for the rest of the minute +#define IPINTEL_RATE_LIMITED_MINUTE "rate_limited_minute" +/// The IP address is a VPN or bad IP +#define IPINTEL_BAD_IP "bad_ip" +/// The IP address is not a VPN or bad IP +#define IPINTEL_GOOD_IP "good_ip" diff --git a/code/__DEFINES/pronouns.dm b/code/__DEFINES/pronouns.dm new file mode 100644 index 0000000000000..c0515426e353a --- /dev/null +++ b/code/__DEFINES/pronouns.dm @@ -0,0 +1,40 @@ +/// she, he, they, it "%PRONOUN_they" = "p_they" +/// She, He, They, It "%PRONOUN_They" = "p_They" +/// her, his, their, its "%PRONOUN_their" = "p_their" +/// Her, His, Their, Its "%PRONOUN_Their" = "p_Their" +/// hers, his, theirs, its "%PRONOUN_theirs" = "p_theirs" +/// Hers, His, Theirs, Its "%PRONOUN_Theirs" = "p_Theirs" +/// her, him, them, it "%PRONOUN_them" = "p_them" +/// Her, Him, Them, It "%PRONOUN_Them" = "p_Them" +/// has, have "%PRONOUN_have" = "p_have" +/// is, are "%PRONOUN_are" = "p_are" +/// was, were "%PRONOUN_were" = "p_were" +/// does, do "%PRONOUN_do" = "p_do" +/// she has, he has, they have, it has "%PRONOUN_theyve" = "p_theyve" +/// She has, He has, They have, It has "%PRONOUN_Theyve" = "p_Theyve" +/// she is, he is, they are, it is "%PRONOUN_theyre" = "p_theyre" +/// She is, He is, They are, It is "%PRONOUN_Theyre" = "p_Theyre" +/// s, null (she looks, they look) "%PRONOUN_s" = "p_s" +/// es, null (she goes, they go) "%PRONOUN_es" = "p_es" + +/// A list for all the pronoun procs, if you need to iterate or search through it or something. +#define ALL_PRONOUNS list( \ + "%PRONOUN_they" = TYPE_PROC_REF(/datum, p_they), \ + "%PRONOUN_They" = TYPE_PROC_REF(/datum, p_They), \ + "%PRONOUN_their" = TYPE_PROC_REF(/datum, p_their), \ + "%PRONOUN_Their" = TYPE_PROC_REF(/datum, p_Their), \ + "%PRONOUN_theirs" = TYPE_PROC_REF(/datum, p_theirs), \ + "%PRONOUN_Theirs" = TYPE_PROC_REF(/datum, p_Theirs), \ + "%PRONOUN_them" = TYPE_PROC_REF(/datum, p_them), \ + "%PRONOUN_Them" = TYPE_PROC_REF(/datum, p_Them), \ + "%PRONOUN_have" = TYPE_PROC_REF(/datum, p_have), \ + "%PRONOUN_are" = TYPE_PROC_REF(/datum, p_are), \ + "%PRONOUN_were" = TYPE_PROC_REF(/datum, p_were), \ + "%PRONOUN_do" = TYPE_PROC_REF(/datum, p_do), \ + "%PRONOUN_theyve" = TYPE_PROC_REF(/datum, p_theyve), \ + "%PRONOUN_Theyve" = TYPE_PROC_REF(/datum, p_Theyve), \ + "%PRONOUN_theyre" = TYPE_PROC_REF(/datum, p_theyre), \ + "%PRONOUN_Theyre" = TYPE_PROC_REF(/datum, p_Theyre), \ + "%PRONOUN_s" = TYPE_PROC_REF(/datum, p_s), \ + "%PRONOUN_es" = TYPE_PROC_REF(/datum, p_es) \ +) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 3878ba8c45ce3..031244d3d76c1 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -20,7 +20,7 @@ * * make sure you add an update to the schema_version stable in the db changelog */ -#define DB_MINOR_VERSION 26 +#define DB_MINOR_VERSION 27 //! ## Timing subsystem diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 90bd77c0423aa..a83ef71ddc69f 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -54,8 +54,6 @@ //Helpers for vv_get_dropdown() #define VV_DROPDOWN_OPTION(href_key, name) . += "" -//Same with VV_DROPDOWN_OPTION, but global proc doesn't have src -#define VV_DROPDOWN_OPTION_APPEARANCE(thing, href_key, name) . += "" // VV HREF KEYS #define VV_HK_TARGET "target" diff --git a/code/__HELPERS/logging/_logging.dm b/code/__HELPERS/logging/_logging.dm index b7a7b689483c2..bfcaded67f021 100644 --- a/code/__HELPERS/logging/_logging.dm +++ b/code/__HELPERS/logging/_logging.dm @@ -70,7 +70,7 @@ GLOBAL_LIST_INIT(testing_global_profiler, list("_PROFILE_NAME" = "Global")) SEND_TEXT(world.log, text) #endif -#if defined(REFERENCE_DOING_IT_LIVE) +#if defined(REFERENCE_TRACKING_LOG_APART) #define log_reftracker(msg) log_harddel("## REF SEARCH [msg]") /proc/log_harddel(text) diff --git a/code/__HELPERS/pronouns.dm b/code/__HELPERS/pronouns.dm index df84c1cdcf42a..fe2357d6ce422 100644 --- a/code/__HELPERS/pronouns.dm +++ b/code/__HELPERS/pronouns.dm @@ -1,5 +1,8 @@ +#define GET_TARGET_PRONOUN(target, pronoun, gender) call(target, ALL_PRONOUNS[pronoun])(gender) + //pronoun procs, for getting pronouns without using the text macros that only work in certain positions //datums don't have gender, but most of their subtypes do! + /datum/proc/p_they(temp_gender) return "it" @@ -69,6 +72,26 @@ else return "s" +/// A proc to replace pronouns in a string with the appropriate pronouns for a target atom. +/// Uses associative list access from a __DEFINE list, since associative access is slightly +/// faster +/datum/proc/REPLACE_PRONOUNS(target_string, atom/targeted_atom, targeted_gender = null) + /// If someone specifies targeted_gender we choose that, + /// otherwise we go off the gender of our object + var/gender + if(targeted_gender) + if(!istext(targeted_gender) || !(targeted_gender in list(MALE, FEMALE, PLURAL, NEUTER))) + stack_trace("REPLACE_PRONOUNS called with improper parameters.") + return + gender = targeted_gender + else + gender = targeted_atom.gender + var/regex/pronoun_regex = regex("%PRONOUN(_(they|They|their|Their|theirs|Theirs|them|Them|have|are|were|do|theyve|Theyve|theyre|Theyre|s|es))") + while(pronoun_regex.Find(target_string)) + target_string = pronoun_regex.Replace(target_string, GET_TARGET_PRONOUN(targeted_atom, pronoun_regex.match, gender)) + return target_string + + //like clients, which do have gender. /client/p_they(temp_gender) if(!temp_gender) diff --git a/code/_compile_options.dm b/code/_compile_options.dm index cd274c26deeaa..5186860f53bfd 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -34,6 +34,8 @@ #define FIND_REF_NO_CHECK_TICK #endif //ifdef GC_FAILURE_HARD_LOOKUP +// Log references in their own file, rather then in runtimes.log +//#define REFERENCE_TRACKING_LOG_APART #endif //ifdef REFERENCE_TRACKING /* @@ -60,8 +62,24 @@ #define REFERENCE_TRACKING // actually look for refs #define GC_FAILURE_HARD_LOOKUP +// Log references in their own file +#define REFERENCE_TRACKING_LOG_APART #endif // REFERENCE_DOING_IT_LIVE +/// Sets up the reftracker to be used locally, to hunt for hard deletions +/// Errors are logged to [log_dir]/harddels.log +//#define REFERENCE_TRACKING_STANDARD +#ifdef REFERENCE_TRACKING_STANDARD +// compile the backend +#define REFERENCE_TRACKING +// actually look for refs +#define GC_FAILURE_HARD_LOOKUP +// spend ALL our time searching, not just part of it +#define FIND_REF_NO_CHECK_TICK +// Log references in their own file +#define REFERENCE_TRACKING_LOG_APART +#endif // REFERENCE_TRACKING_STANDARD + // If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between // #define UNIT_TESTS diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 8852e9bb7def9..c62b44e47517d 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -339,6 +339,19 @@ DEFINE_BITFIELD(vis_flags, list( "VIS_UNDERLAY" = VIS_UNDERLAY, )) +// I am so sorry. Required because vis_flags is both undefinable and unreadable on mutable_appearance +// But we need to display them anyway. See /mutable_appearance/appearance_mirror +DEFINE_BITFIELD(_vis_flags, list( + "VIS_HIDE" = VIS_HIDE, + "VIS_INHERIT_DIR" = VIS_INHERIT_DIR, + "VIS_INHERIT_ICON" = VIS_INHERIT_ICON, + "VIS_INHERIT_ICON_STATE" = VIS_INHERIT_ICON_STATE, + "VIS_INHERIT_ID" = VIS_INHERIT_ID, + "VIS_INHERIT_LAYER" = VIS_INHERIT_LAYER, + "VIS_INHERIT_PLANE" = VIS_INHERIT_PLANE, + "VIS_UNDERLAY" = VIS_UNDERLAY, +)) + DEFINE_BITFIELD(zap_flags, list( "ZAP_ALLOW_DUPLICATES" = ZAP_ALLOW_DUPLICATES, "ZAP_MACHINE_EXPLOSIVE" = ZAP_MACHINE_EXPLOSIVE, diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index fc6919c3a3b86..3100456691011 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -33,7 +33,7 @@ GLOBAL_PROTECT(##log_var_name);\ DECLARE_LOG(config_error_log, DONT_START_LOG) DECLARE_LOG(perf_log, DONT_START_LOG) // Declared here but name is set in time_track subsystem -#ifdef REFERENCE_DOING_IT_LIVE +#ifdef REFERENCE_TRACKING_LOG_APART DECLARE_LOG_NAMED(harddel_log, "harddels", START_LOG) #endif diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index 93a19ae0219ee..13b0e5c3c1335 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -150,6 +150,7 @@ return /atom/proc/ai_click_alt(mob/living/silicon/ai/user) + SHOULD_CALL_PARENT(FALSE) return /atom/proc/AIShiftClick(mob/living/silicon/ai/user) @@ -168,12 +169,13 @@ /obj/machinery/door/airlock/ai_click_alt(mob/living/silicon/ai/user) if(obj_flags & EMAGGED) - return + return NONE if(!secondsElectrified) shock_perm(user) else shock_restore(user) + return CLICK_ACTION_SUCCESS /obj/machinery/door/airlock/AIShiftClick(mob/living/silicon/ai/user) // Opens and closes doors! if(obj_flags & EMAGGED) @@ -237,10 +239,10 @@ /// Toggle APC equipment settings /obj/machinery/power/apc/ai_click_alt(mob/living/silicon/ai/user) if(!can_use(user, loud = TRUE)) - return + return NONE if(!is_operational || failure_timer) - return + return CLICK_ACTION_BLOCKING equipment = equipment ? APC_CHANNEL_OFF : APC_CHANNEL_ON if (user) @@ -250,6 +252,7 @@ user.log_message("turned [enabled_or_disabled] the [src] equipment settings", LOG_GAME) update_appearance() update() + return CLICK_ACTION_SUCCESS /obj/machinery/power/apc/attack_ai_secondary(mob/living/silicon/user, list/modifiers) if(!can_use(user, loud = TRUE)) @@ -261,8 +264,9 @@ /* AI Turrets */ /obj/machinery/turretid/ai_click_alt(mob/living/silicon/ai/user) //toggles lethal on turrets if(ailock) - return + return CLICK_ACTION_BLOCKING toggle_lethal(user) + return CLICK_ACTION_SUCCESS /obj/machinery/turretid/AICtrlClick(mob/living/silicon/ai/user) //turns off/on Turrets if(ailock) @@ -275,6 +279,7 @@ balloon_alert(user, "disrupted all active calls") add_hiddenprint(user) hangup_all_calls() + return CLICK_ACTION_SUCCESS // // Override TurfAdjacent for AltClicking diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 05226ae6b9418..5292e47ecd654 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -447,6 +447,9 @@ /datum/config_entry/flag/irc_first_connection_alert // do we notify the irc channel when somebody is connecting for the first time? +/datum/config_entry/string/ipintel_base + default = "check.getipintel.net" + /datum/config_entry/string/ipintel_email /datum/config_entry/string/ipintel_email/ValidateAndSet(str_val) @@ -458,18 +461,26 @@ min_val = 0 max_val = 1 -/datum/config_entry/number/ipintel_save_good - default = 12 - integer = FALSE - min_val = 0 +/datum/config_entry/flag/ipintel_reject_rate_limited + default = TRUE -/datum/config_entry/number/ipintel_save_bad - default = 1 - integer = FALSE - min_val = 0 +/datum/config_entry/flag/ipintel_reject_bad + default = TRUE -/datum/config_entry/string/ipintel_domain - default = "check.getipintel.net" +/datum/config_entry/flag/ipintel_reject_unknown + default = FALSE + +/datum/config_entry/number/ipintel_rate_minute + default = 15 + +/datum/config_entry/number/ipintel_rate_day + default = 500 + +/datum/config_entry/number/ipintel_cache_length + default = 7 + +/datum/config_entry/number/ipintel_exempt_playtime_living + default = 0 /datum/config_entry/flag/aggressive_changelog diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm index 83cbbc4c27efc..3f1c738b09c0e 100644 --- a/code/controllers/subsystem/ipintel.dm +++ b/code/controllers/subsystem/ipintel.dm @@ -1,13 +1,295 @@ SUBSYSTEM_DEF(ipintel) name = "XKeyScore" init_order = INIT_ORDER_XKEYSCORE - flags = SS_NO_FIRE - var/enabled = FALSE //disable at round start to avoid checking reconnects - var/throttle = 0 - var/errors = 0 + flags = SS_INIT_NO_NEED|SS_NO_FIRE + /// The threshold for probability to be considered a VPN and/or bad IP + var/probability_threshold + /// The email used in conjuction with https://check.getipintel.net/check.php + var/contact_email + /// Maximum number of queries per minute + var/max_queries_per_minute + /// Maximum number of queries per day + var/max_queries_per_day + /// Query base + var/query_base + /// The length of time (days) to cache IP intel + var/ipintel_cache_length + /// The living playtime (minutes) for players to be exempt from IPIntel checks + var/exempt_living_playtime - var/list/cache = list() + /// Cache for previously queried IP addresses and those stored in the database + var/list/datum/ip_intel/cached_queries = list() + /// The store for rate limiting + var/list/rate_limits + +/// The ip intel for a given address +/datum/ip_intel + /// If this intel was just queried, the status of the query + var/query_status + var/result + var/address + var/date /datum/controller/subsystem/ipintel/Initialize() - enabled = TRUE + var/list/fail_messages = list() + + probability_threshold = CONFIG_GET(number/ipintel_rating_bad) + if(probability_threshold < 0 || probability_threshold > 1) + fail_messages += list("invalid probability threshold") + + contact_email = CONFIG_GET(string/ipintel_email) + if(isnull(contact_email) || !findtext(contact_email, "@")) + fail_messages += list("invalid contact email") + + var/max_queries_per_minute = CONFIG_GET(number/ipintel_rate_minute) + var/max_queries_per_day = CONFIG_GET(number/ipintel_rate_day) + if(max_queries_per_minute < 0 || max_queries_per_day < 0) + fail_messages += list("invalid rate limits") + + var/query_base = CONFIG_GET(string/ipintel_base) + if(isnull(query_base)) + fail_messages += list("invalid query base") + + var/ipintel_cache_length = CONFIG_GET(number/ipintel_cache_length) + if(ipintel_cache_length < 0) + fail_messages += list("invalid cache length") + + var/exempt_living_playtime = CONFIG_GET(number/ipintel_exempt_playtime_living) + if(exempt_living_playtime < 0) + fail_messages += list("invalid exempt living playtime") + + if(length(fail_messages)) + message_admins("IPIntel: Initialization failed check logs!") + logger.Log(LOG_CATEGORY_GAME_ACCESS, "IPIntel failed to initialize.", list( + "fail_messages" = fail_messages, + )) + return SS_INIT_FAILURE + return SS_INIT_SUCCESS + +/datum/controller/subsystem/ipintel/stat_entry(msg) + return "[..()] | D: [max_queries_per_day - rate_limits[IPINTEL_RATE_LIMIT_DAY]] | M: [max_queries_per_minute - rate_limits[IPINTEL_RATE_LIMIT_MINUTE]]" + +/datum/controller/subsystem/ipintel/proc/get_address_intel_state(address, probability_override) + var/datum/ip_intel/intel = query_address(address) + if(isnull(intel)) + stack_trace("query_address did not return an ip intel response") + return IPINTEL_UNKNOWN_INTERNAL_ERROR + + if(istext(intel)) + return intel + + if(!(intel.query_status in list("success", "cached"))) + return IPINTEL_UNKNOWN_QUERY_ERROR + var/check_probability = probability_override || probability_threshold + if(intel.result >= check_probability) + return IPINTEL_BAD_IP + return IPINTEL_GOOD_IP + +/datum/controller/subsystem/ipintel/proc/is_rate_limited() + var/static/minute_key + var/expected_minute_key = floor(REALTIMEOFDAY / 1 MINUTES) + + if(minute_key != expected_minute_key) + minute_key = expected_minute_key + rate_limits[IPINTEL_RATE_LIMIT_MINUTE] = 0 + + if(rate_limits[IPINTEL_RATE_LIMIT_MINUTE] >= max_queries_per_minute) + return IPINTEL_RATE_LIMITED_MINUTE + if(rate_limits[IPINTEL_RATE_LIMIT_DAY] >= max_queries_per_day) + return IPINTEL_RATE_LIMITED_DAY + return FALSE + +/datum/controller/subsystem/ipintel/proc/query_address(address, allow_cached = TRUE) + if(allow_cached && fetch_cached_ip_intel(address)) + return cached_queries[address] + var/is_rate_limited = is_rate_limited() + if(is_rate_limited) + return is_rate_limited + if(!initialized) + return IPINTEL_UNKNOWN_INTERNAL_ERROR + + rate_limits[IPINTEL_RATE_LIMIT_MINUTE] += 1 + rate_limits[IPINTEL_RATE_LIMIT_DAY] += 1 + + var/query_base = "https://[src.query_base]/check.php?ip=" + var/query = "[query_base][address]&contact=[contact_email]&flags=b&format=json" + + var/datum/http_request/request = new + request.prepare(RUSTG_HTTP_METHOD_GET, query) + request.execute_blocking() + var/datum/http_response/response = request.into_response() + var/list/data = response.body + + var/datum/ip_intel/intel = new + intel.query_status = data["status"] + if(intel.query_status != "success") + return intel + intel.result = data["result"] + intel.date = SQLtime() + intel.address = address + cached_queries[address] = intel + add_intel_to_database(intel) + return intel + +/datum/controller/subsystem/ipintel/proc/add_intel_to_database(datum/ip_intel/intel) + var/datum/db_query/query = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("ipintel")] ( \ + ip, \ + intel, \ + ) VALUES ( \ + INET_ATON(:address) \ + :result, \ + )", list( + "address" = intel.address, + "result" = intel.result, + ) + ) + query.warn_execute() + query.sync() + qdel(query) + +/datum/controller/subsystem/ipintel/proc/fetch_cached_ip_intel(address) + var/date_restrictor + if(ipintel_cache_length > 0) + date_restrictor = " AND date > DATE_SUB(NOW(), INTERVAL [ipintel_cache_length] DAY)" + var/datum/db_query/query = SSdbcore.NewQuery( + "SELECT * FROM [format_table_name("ipintel")] WHERE ip = INET_ATON(:address)[date_restrictor]", list( + "address" = address + ) + ) + query.warn_execute() + query.sync() + if(query.status == DB_QUERY_BROKEN) + qdel(query) + return null + + query.NextRow() + var/list/data = query.item + qdel(query) + if(isnull(data)) + return null + + var/datum/ip_intel/intel = new + intel.query_status = "cached" + intel.result = data["intel"] + intel.date = data["date"] + intel.address = address + return TRUE + +/datum/controller/subsystem/ipintel/proc/is_exempt(client/player) + if(exempt_living_playtime > 0) + var/list/play_records = player.prefs.exp + if (!play_records.len) + player.set_exp_from_db() + play_records = player.prefs.exp + if(length(play_records) && play_records[EXP_TYPE_LIVING] > exempt_living_playtime) + return TRUE + return FALSE + +/datum/controller/subsystem/ipintel/proc/is_whitelisted(ckey) + var/datum/db_query/query = SSdbcore.NewQuery( + "SELECT * FROM [format_table_name("ipintel_whitelist")] WHERE ckey = :ckey", list( + "ckey" = ckey + ) + ) + query.warn_execute() + query.sync() + if(query.status == DB_QUERY_BROKEN) + qdel(query) + return FALSE + query.NextRow() + return !!query.item // if they have a row, they are whitelisted + +ADMIN_VERB(ipintel_allow, R_BAN, "Whitelist Player VPN", "Allow a player to connect even if they are using a VPN.", ADMIN_CATEGORY_IPINTEL, ckey as text) + if(SSipintel.is_whitelisted(ckey)) + to_chat(user, "Player is already whitelisted.") + return + + var/datum/db_query/query = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("ipintel_whitelist")] ( \ + ckey, \ + admin_ckey \ + ) VALUES ( \ + :ckey, \ + :admin_ckey \ + )", list( + "ckey" = ckey, + "admin_ckey" = user.ckey, + ) + ) + query.warn_execute() + query.sync() + qdel(query) + message_admins("IPINTEL: [key_name_admin(user)] has whitelisted '[ckey]'") + +ADMIN_VERB(ipintel_revoke, R_BAN, "Revoke Player VPN Whitelist", "Revoke a player's VPN whitelist.", ADMIN_CATEGORY_IPINTEL, ckey as text) + if(!SSipintel.is_whitelisted(ckey)) + to_chat(user, "Player is not whitelisted.") + return + var/datum/db_query/query = SSdbcore.NewQuery( + "DELETE FROM [format_table_name("ipintel_whitelist")] WHERE ckey = :ckey", list( + "ckey" = ckey + ) + ) + query.warn_execute() + query.sync() + qdel(query) + message_admins("IPINTEL: [key_name_admin(user)] has revoked the VPN whitelist for '[ckey]'") + +/client/proc/check_ip_intel() + if(SSipintel.is_exempt(src) || SSipintel.is_whitelisted(ckey)) + return + + var/intel_state = SSipintel.get_address_intel_state(address) + var/reject_bad_intel = CONFIG_GET(flag/ipintel_reject_bad) + var/reject_unknown_intel = CONFIG_GET(flag/ipintel_reject_unknown) + var/reject_rate_limited = CONFIG_GET(flag/ipintel_reject_rate_limited) + + var/connection_rejected = FALSE + var/datum/ip_intel/intel = SSipintel.cached_queries[address] + switch(intel_state) + if(IPINTEL_BAD_IP) + log_access("IPINTEL: [ckey] was flagged as a VPN with [intel.result * 100]% likelihood.") + if(reject_bad_intel) + to_chat_immediate(src, span_boldnotice("Your connection has been detected as a VPN.")) + connection_rejected = TRUE + else + message_admins("IPINTEL: [key_name_admin(src)] has been flagged as a VPN with [intel.result * 100]% likelihood.") + + if(IPINTEL_RATE_LIMITED_DAY, IPINTEL_RATE_LIMITED_MINUTE) + log_access("IPINTEL: [ckey] was unable to be checked due to the rate limit.") + if(reject_rate_limited) + to_chat_immediate(src, span_boldnotice("New connections are not being allowed at this time.")) + connection_rejected = TRUE + else + message_admins("IPINTEL: [key_name_admin(src)] was unable to be checked due to rate limiting.") + + if(IPINTEL_UNKNOWN_INTERNAL_ERROR, IPINTEL_UNKNOWN_QUERY_ERROR) + log_access("IPINTEL: [ckey] unable to be checked due to an error.") + if(reject_unknown_intel) + to_chat_immediate(src, span_boldnotice("Your connection cannot be processed at this time.")) + connection_rejected = TRUE + else + message_admins("IPINTEL: [key_name_admin(src)] was unable to be checked due to an error.") + + if(!connection_rejected) + return + + var/list/contact_where = list() + var/forum_url = CONFIG_GET(string/forumurl) + if(forum_url) + contact_where += list("Forums") + var/appeal_url = CONFIG_GET(string/banappeals) + if(appeal_url) + contact_where += list("Ban Appeals") + + var/message_string = "Your connection has been rejected at this time. If you believe this is in error or have any questions please contact an admin" + if(length(contact_where)) + message_string += " at [english_list(contact_where)]" + else + message_string += " somehow." + message_string += "." + + to_chat_immediate(src, span_userdanger(message_string)) + qdel(src) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm index f227d5dfea6aa..f0ee4bb9bba37 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm @@ -146,7 +146,7 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ var/datum/proximity_monitor/field = controller.blackboard[BB_FIND_TARGETS_FIELD(type)] qdel(field) // autoclears so it's fine controller.CancelActions() // On retarget cancel any further queued actions so that they will setup again with new target - controller.modify_cooldown(controller, get_cooldown(controller)) + controller.modify_cooldown(src, get_cooldown(controller)) /// Returns the desired final target from the filtered list of targets /datum/ai_behavior/find_potential_targets/proc/pick_final_target(datum/ai_controller/controller, list/filtered_targets) diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm index 215f0a96302f3..693427ba4bd10 100644 --- a/code/datums/ai/monkey/monkey_controller.dm +++ b/code/datums/ai/monkey/monkey_controller.dm @@ -45,7 +45,7 @@ have ways of interacting with a specific mob and control it. /datum/ai_controller/monkey/New(atom/new_pawn) var/static/list/control_examine = list( - ORGAN_SLOT_EYES = span_monkey("eyes have a primal look in them."), + ORGAN_SLOT_EYES = span_monkey("%PRONOUN_They stare%PRONOUN_s around with wild, primal eyes."), ) AddElement(/datum/element/ai_control_examine, control_examine) return ..() diff --git a/code/datums/components/surgery_initiator.dm b/code/datums/components/surgery_initiator.dm index dd3251d98f22e..b7f04b517f774 100644 --- a/code/datums/components/surgery_initiator.dm +++ b/code/datums/components/surgery_initiator.dm @@ -139,7 +139,7 @@ for(var/obj/item/borg/cyborg_omnitool/medical/omnitool in user.held_items) if(omnitool.tool_behaviour == TOOL_CAUTERY) has_cautery = TRUE - if(has_cautery) + if(!has_cautery) patient.balloon_alert(user, "need a cautery in an inactive slot to stop the surgery!") return else if(!close_tool || close_tool.tool_behaviour != required_tool_type) diff --git a/code/datums/elements/ai_control_examine.dm b/code/datums/elements/ai_control_examine.dm index b470ac44b49a7..279fc80dc8192 100644 --- a/code/datums/elements/ai_control_examine.dm +++ b/code/datums/elements/ai_control_examine.dm @@ -46,7 +46,7 @@ if(noticable_organ_examines[possibly_noticable.slot]) make_organ_noticable(possibly_noticable.slot, possibly_noticable) -/datum/element/ai_control_examine/proc/make_organ_noticable(organ_slot, obj/item/organ/noticable_organ) +/datum/element/ai_control_examine/proc/make_organ_noticable(organ_slot, obj/item/organ/noticable_organ, mob/living/carbon/human/human_pawn) var/examine_text = noticable_organ_examines[organ_slot] var/body_zone = organ_slot != ORGAN_SLOT_BRAIN ? noticable_organ.zone : null noticable_organ.AddElement(/datum/element/noticable_organ/ai_control, examine_text, body_zone) diff --git a/code/datums/elements/noticable_organ.dm b/code/datums/elements/noticable_organ.dm index 1a6a895e53543..a6247d18bb53b 100644 --- a/code/datums/elements/noticable_organ.dm +++ b/code/datums/elements/noticable_organ.dm @@ -6,15 +6,13 @@ /datum/element/noticable_organ element_flags = ELEMENT_BESPOKE argument_hash_start_idx = 2 - /// whether we wrap the examine text in a notice span. - var/add_span = TRUE - /// "[they]|[their] [desc here]", shows on examining someone with an infused organ. - /// Uses a possessive pronoun (His/Her/Their) if a body zone is given, or a singular pronoun (He/She/They) otherwise. + + ///Shows on examining someone with an infused organ. var/infused_desc - /// Which body zone has to be exposed. If none is set, this is always noticable, and the description pronoun becomes singular instead of possesive. + /// Which body zone has to be exposed. If none is set, this is always noticable. var/body_zone -/datum/element/noticable_organ/Attach(datum/target, infused_desc, body_zone) +/datum/element/noticable_organ/Attach(obj/item/organ/target, infused_desc, body_zone) . = ..() if(!isorgan(target)) @@ -23,8 +21,10 @@ src.infused_desc = infused_desc src.body_zone = body_zone - RegisterSignal(target, COMSIG_ORGAN_IMPLANTED, PROC_REF(on_implanted)) + RegisterSignal(target, COMSIG_ORGAN_IMPLANTED, PROC_REF(enable_description)) RegisterSignal(target, COMSIG_ORGAN_REMOVED, PROC_REF(on_removed)) + if(target.owner) + enable_description(target, target.owner) /datum/element/noticable_organ/Detach(obj/item/organ/target) UnregisterSignal(target, list(COMSIG_ORGAN_IMPLANTED, COMSIG_ORGAN_REMOVED)) @@ -38,7 +38,7 @@ return FALSE return TRUE -/datum/element/noticable_organ/proc/on_implanted(obj/item/organ/target, mob/living/carbon/receiver) +/datum/element/noticable_organ/proc/enable_description(obj/item/organ/target, mob/living/carbon/receiver) SIGNAL_HANDLER RegisterSignal(receiver, COMSIG_ATOM_EXAMINE, PROC_REF(on_receiver_examine)) @@ -53,16 +53,17 @@ if(!should_show_text(examined)) return - var/examine_text = replacetext(replacetext("[body_zone ? examined.p_Their() : examined.p_They()] [infused_desc]", "%PRONOUN_ES", examined.p_es()), "%PRONOUN_S", examined.p_s()) - if(add_span) - examine_text = span_notice(examine_text) + + var/examine_text = REPLACE_PRONOUNS(infused_desc, examined) + + examine_list += examine_text /** * Subtype of noticable organs for AI control, that will make a few more ai status checks before forking over the examine. */ /datum/element/noticable_organ/ai_control - add_span = FALSE + /datum/element/noticable_organ/ai_control/should_show_text(mob/living/carbon/examined) . = ..() diff --git a/code/game/machinery/dna_infuser/dna_infuser.dm b/code/game/machinery/dna_infuser/dna_infuser.dm index 6c239089f5d60..7b5fcb501a046 100644 --- a/code/game/machinery/dna_infuser/dna_infuser.dm +++ b/code/game/machinery/dna_infuser/dna_infuser.dm @@ -64,8 +64,7 @@ balloon_alert(user, "not while it's on!") return if(occupant && infusing_from) - // Abort infusion if the occupant is invalid. - if(!is_valid_occupant(occupant, user)) + if(!occupant.can_infuse(user)) playsound(src, 'sound/machines/scanbuzz.ogg', 35, vary = TRUE) return balloon_alert(user, "starting DNA infusion...") @@ -77,92 +76,45 @@ var/mob/living/carbon/human/human_occupant = occupant infusing = TRUE visible_message(span_notice("[src] hums to life, beginning the infusion process!")) + + infusing_into = infusing_from.get_infusion_entry() var/fail_title = "" - var/fail_reason = "" - // Replace infusing_into with a [/datum/infuser_entry] - for(var/datum/infuser_entry/entry as anything in GLOB.infuser_entries) - if(entry.tier == DNA_MUTANT_UNOBTAINABLE) - continue - if(is_type_in_list(infusing_from, entry.input_obj_or_mob)) - if(entry.tier > max_tier_allowed) - fail_title = "Overcomplexity" - fail_reason = "DNA too complicated to infuse. The machine needs to infuse simpler DNA first." - infusing_into = entry - break - if(!infusing_into) - //no valid recipe, so you get a fly mutation - if(!fail_reason) - fail_title = "Unknown DNA" - fail_reason = "Unknown DNA. Consult the \"DNA infusion book\"." - infusing_into = GLOB.infuser_entries[1] + var/fail_explanation = "" + if(istype(infusing_into, /datum/infuser_entry/fly)) + fail_title = "Unknown DNA" + fail_explanation = "Unknown DNA. Consult the \"DNA infusion book\"." + if(infusing_into.tier > max_tier_allowed) + infusing_into = GLOB.infuser_entries[/datum/infuser_entry/fly] + fail_title = "Overcomplexity" + fail_explanation = "DNA too complicated to infuse. The machine needs to infuse simpler DNA first." playsound(src, 'sound/machines/blender.ogg', 50, vary = TRUE) to_chat(human_occupant, span_danger("Little needles repeatedly prick you!")) human_occupant.take_overall_damage(10) human_occupant.add_mob_memory(/datum/memory/dna_infusion, protagonist = human_occupant, deuteragonist = infusing_from, mutantlike = infusing_into.infusion_desc) Shake(duration = INFUSING_TIME) addtimer(CALLBACK(human_occupant, TYPE_PROC_REF(/mob, emote), "scream"), INFUSING_TIME - 1 SECONDS) - addtimer(CALLBACK(src, PROC_REF(end_infuse), fail_reason, fail_title), INFUSING_TIME) + addtimer(CALLBACK(src, PROC_REF(end_infuse), fail_explanation, fail_title), INFUSING_TIME) update_appearance() -/obj/machinery/dna_infuser/proc/end_infuse(fail_reason, fail_title) - if(infuse_organ(occupant)) +/obj/machinery/dna_infuser/proc/end_infuse(fail_explanation, fail_title) + var/mob/living/carbon/human/human_occupant = occupant + if(human_occupant.infuse_organ(infusing_into)) + check_tier_progression(src) to_chat(occupant, span_danger("You feel yourself becoming more... [infusing_into.infusion_desc]?")) infusing = FALSE infusing_into = null QDEL_NULL(infusing_from) playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, vary = FALSE) - if(fail_reason) + if(fail_explanation) playsound(src, 'sound/machines/printer.ogg', 100, TRUE) visible_message(span_notice("[src] prints an error report.")) var/obj/item/paper/printed_paper = new /obj/item/paper(loc) printed_paper.name = "error report - '[fail_title]'" - printed_paper.add_raw_text(fail_reason) + printed_paper.add_raw_text(fail_explanation) printed_paper.update_appearance() toggle_open() update_appearance() -/// Attempt to replace/add-to the occupant's organs with "mutated" equivalents. -/// Returns TRUE on success, FALSE on failure. -/// Requires the target mob to have an existing organic organ to "mutate". -// TODO: In the future, this should have more logic: -// - Replace non-mutant organs before mutant ones. -/obj/machinery/dna_infuser/proc/infuse_organ(mob/living/carbon/human/target) - if(!ishuman(target)) - return FALSE - var/obj/item/organ/new_organ = pick_organ(target) - if(!new_organ) - return FALSE - // Valid organ successfully picked. - new_organ = new new_organ() - new_organ.replace_into(target) - check_tier_progression(target) - return TRUE - -/// Picks a random mutated organ from the infuser entry which is also compatible with the target mob. -/// Tries to return a typepath of a valid mutant organ if all of the following criteria are true: -/// 1. Target must have a pre-existing organ in the same organ slot as the new organ; -/// - or the new organ must be external. -/// 2. Target's pre-existing organ must be organic / not robotic. -/// 3. Target must not have the same/identical organ. -/obj/machinery/dna_infuser/proc/pick_organ(mob/living/carbon/human/target) - if(!infusing_into) - return FALSE - var/list/obj/item/organ/potential_new_organs = infusing_into.output_organs.Copy() - // Remove organ typepaths from the list if they're incompatible with target. - for(var/obj/item/organ/new_organ as anything in infusing_into.output_organs) - var/obj/item/organ/old_organ = target.get_organ_slot(initial(new_organ.slot)) - if(old_organ) - if((old_organ.type != new_organ) && !IS_ROBOTIC_ORGAN(old_organ)) - continue // Old organ can be mutated! - else if(ispath(new_organ, /obj/item/organ/external)) - continue // External organ can be grown! - // Internal organ is either missing, or is non-organic. - potential_new_organs -= new_organ - // Pick a random organ from the filtered list. - if(length(potential_new_organs)) - return pick(potential_new_organs) - return FALSE - /// checks to see if the machine should progress a new tier. /obj/machinery/dna_infuser/proc/check_tier_progression(mob/living/carbon/human/target) if( @@ -254,19 +206,6 @@ infusing_from = target infusing_from.forceMove(src) -/// Verify that the occupant/target is organic, and has mutable DNA. -/obj/machinery/dna_infuser/proc/is_valid_occupant(mob/living/carbon/human/human_target, mob/user) - // Invalid: DNA is too damaged to mutate anymore / has TRAIT_BADDNA. - if(HAS_TRAIT(human_target, TRAIT_BADDNA)) - balloon_alert(user, "dna is corrupted!") - return FALSE - // Invalid: Occupant isn't Human, isn't organic, lacks DNA / has TRAIT_GENELESS. - if(!ishuman(human_target) || !human_target.can_mutate()) - balloon_alert(user, "dna is missing!") - return FALSE - // Valid: Occupant is an organic Human who has undamaged and mutable DNA. - return TRUE - /// Verify that the given infusion source/mob is a dead creature. /obj/machinery/dna_infuser/proc/is_valid_infusion(atom/movable/target, mob/user) if(user.stat != CONSCIOUS || HAS_TRAIT(user, TRAIT_UI_BLOCKED) || !Adjacent(user) || !user.Adjacent(target) || !ISADVANCEDTOOLUSER(user)) @@ -291,10 +230,10 @@ /obj/machinery/dna_infuser/click_alt(mob/user) if(infusing) balloon_alert(user, "not while it's on!") - return CLICK_ACTION_BLOCKING + return if(!infusing_from) balloon_alert(user, "no sample to eject!") - return CLICK_ACTION_BLOCKING + return balloon_alert(user, "ejected sample") infusing_from.forceMove(get_turf(src)) infusing_from = null diff --git a/code/game/machinery/dna_infuser/dna_infusion.dm b/code/game/machinery/dna_infuser/dna_infusion.dm new file mode 100644 index 0000000000000..c902240404ca7 --- /dev/null +++ b/code/game/machinery/dna_infuser/dna_infusion.dm @@ -0,0 +1,75 @@ + +///returns a boolean whether a machine occupant can be infused +/atom/movable/proc/can_infuse(mob/feedback_target) + if(feedback_target) + balloon_alert(feedback_target, "no dna!") + return FALSE + +/mob/living/can_infuse(mob/feedback_target) + if(feedback_target) + balloon_alert(feedback_target, "dna too simple!") + return FALSE + +/mob/living/carbon/human/can_infuse(mob/feedback_target) + // Checked by can_mutate but explicit feedback for this issue is good + if(HAS_TRAIT(src, TRAIT_BADDNA)) + if(feedback_target) + balloon_alert(feedback_target, "dna is corrupted!") + return FALSE + if(!can_mutate()) + if(feedback_target) + balloon_alert(feedback_target, "dna is missing!") + return FALSE + return TRUE + +///returns /datum/infuser_entry that matches an item being used for infusion, returns a fly mutation on failure +/atom/movable/proc/get_infusion_entry() as /datum/infuser_entry + var/datum/infuser_entry/found + for(var/datum/infuser_entry/entry as anything in flatten_list(GLOB.infuser_entries)) + if(entry.tier == DNA_MUTANT_UNOBTAINABLE) + continue + if(is_type_in_list(src, entry.input_obj_or_mob)) + found = entry + break + if(!found) + found = GLOB.infuser_entries[/datum/infuser_entry/fly] + return found + +/// Attempt to replace/add-to the occupant's organs with "mutated" equivalents. +/// Returns TRUE on success, FALSE on failure. +/// Requires the target mob to have an existing organic organ to "mutate". +// TODO: In the future, this should have more logic: +// - Replace non-mutant organs before mutant ones. +/mob/living/carbon/human/proc/infuse_organ(datum/infuser_entry/entry) + var/obj/item/organ/new_organ = pick_infusion_organ(entry) + if(!new_organ) + return FALSE + // Valid organ successfully picked. + new_organ = new new_organ() + new_organ.replace_into(src) + return TRUE + +/// Picks a random mutated organ from the given infuser entry which is also compatible with this human. +/// Tries to return a typepath of a valid mutant organ if all of the following criteria are true: +/// 1. Target must have a pre-existing organ in the same organ slot as the new organ; +/// - or the new organ must be external. +/// 2. Target's pre-existing organ must be organic / not robotic. +/// 3. Target must not have the same/identical organ. +/mob/living/carbon/human/proc/pick_infusion_organ(datum/infuser_entry/entry) + if(!entry) + return FALSE + var/list/obj/item/organ/potential_new_organs = entry.output_organs.Copy() + // Remove organ typepaths from the list if they're incompatible with target. + for(var/obj/item/organ/new_organ as anything in entry.output_organs) + var/obj/item/organ/old_organ = get_organ_slot(initial(new_organ.slot)) + if(old_organ) + if((old_organ.type != new_organ) && !IS_ROBOTIC_ORGAN(old_organ)) + continue // Old organ can be mutated! + else if(ispath(new_organ, /obj/item/organ/external)) + continue // External organ can be grown! + // Internal organ is either missing, or is non-organic. + potential_new_organs -= new_organ + // Pick a random organ from the filtered list. + if(length(potential_new_organs)) + return pick(potential_new_organs) + return FALSE diff --git a/code/game/machinery/dna_infuser/infuser_book.dm b/code/game/machinery/dna_infuser/infuser_book.dm index 75632178ccae3..416ed038d640a 100644 --- a/code/game/machinery/dna_infuser/infuser_book.dm +++ b/code/game/machinery/dna_infuser/infuser_book.dm @@ -29,7 +29,7 @@ var/list/data = list() // Collect all info from each intry. var/list/entry_data = list() - for(var/datum/infuser_entry/entry as anything in GLOB.infuser_entries) + for(var/datum/infuser_entry/entry as anything in flatten_list(GLOB.infuser_entries)) if(entry.tier == DNA_MUTANT_UNOBTAINABLE) continue var/list/individual_entry_data = list() diff --git a/code/game/machinery/dna_infuser/infuser_entry.dm b/code/game/machinery/dna_infuser/infuser_entry.dm index dfcdfbbe08a5d..8b0bcfb3f790d 100644 --- a/code/game/machinery/dna_infuser/infuser_entry.dm +++ b/code/game/machinery/dna_infuser/infuser_entry.dm @@ -4,17 +4,10 @@ GLOBAL_LIST_INIT(infuser_entries, prepare_infuser_entries()) /// Global proc that sets up each [/datum/infuser_entry] sub-type as singleton instances in a list, and returns it. /proc/prepare_infuser_entries() var/list/entries = list() - // Regardless of names, we want the fly/failed mutant case to show first. - var/prepended for(var/datum/infuser_entry/entry_type as anything in subtypesof(/datum/infuser_entry)) var/datum/infuser_entry/entry = new entry_type() - if(entry.type == /datum/infuser_entry/fly) - prepended = entry - continue - entries += entry - var/list/sorted = sort_names(entries) - sorted.Insert(1, prepended) - return sorted + entries[entry_type] = entry + return entries /datum/infuser_entry //-- Vars for DNA Infusion Book --// diff --git a/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm b/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm index afbb8404060f2..f44de87e92e8e 100644 --- a/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm @@ -27,7 +27,7 @@ /obj/item/organ/internal/lungs/carp/Initialize(mapload) . = ..() - AddElement(/datum/element/noticable_organ, "neck has odd gills.", BODY_ZONE_HEAD) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their neck has odd gills.", BODY_ZONE_HEAD) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/carp) ADD_TRAIT(src, TRAIT_SPACEBREATHING, REF(src)) @@ -45,7 +45,7 @@ /obj/item/organ/internal/tongue/carp/Initialize(mapload) . = ..() - AddElement(/datum/element/noticable_organ, "teeth are big and sharp.", BODY_ZONE_PRECISE_MOUTH) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their teeth are big and sharp.", BODY_ZONE_PRECISE_MOUTH) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/carp) /obj/item/organ/internal/tongue/carp/on_mob_insert(mob/living/carbon/tongue_owner, special, movement_flags) @@ -113,7 +113,7 @@ /obj/item/organ/internal/brain/carp/Initialize(mapload) . = ..() AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/carp) - AddElement(/datum/element/noticable_organ, "seem%PRONOUN_S unable to stay still.") + AddElement(/datum/element/noticable_organ, "%PRONOUN_They seem%PRONOUN_S unable to stay still.") /obj/item/organ/internal/brain/carp/on_mob_insert(mob/living/carbon/brain_owner) . = ..() @@ -151,7 +151,7 @@ /obj/item/organ/internal/heart/carp/Initialize(mapload) . = ..() - AddElement(/datum/element/noticable_organ, "skin has small patches of scales growing on it.", BODY_ZONE_CHEST) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their skin has small patches of scales growing on it.", BODY_ZONE_CHEST) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/carp) #undef CARP_ORGAN_COLOR diff --git a/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm b/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm index f9ccf16812bc7..ac3dae39b7019 100644 --- a/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm @@ -31,7 +31,7 @@ /obj/item/organ/internal/eyes/night_vision/goliath/Initialize(mapload) . = ..() - AddElement(/datum/element/noticable_organ, "eyes are blood red and stone-like.", BODY_ZONE_PRECISE_EYES) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their eyes are blood red and stone-like.", BODY_ZONE_PRECISE_EYES) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/goliath) ///goliath lungs! You can breathe lavaland air mix but can't breath pure O2 from a tank anymore. @@ -46,7 +46,7 @@ /obj/item/organ/internal/lungs/lavaland/goliath/Initialize(mapload) . = ..() - AddElement(/datum/element/noticable_organ, "back is covered in small tendrils.", BODY_ZONE_CHEST) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their back is covered in small tendrils.", BODY_ZONE_CHEST) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/goliath) ///goliath brain. you can't use gloves but one of your arms becomes a tendril hammer that can be used to mine! @@ -63,7 +63,7 @@ /obj/item/organ/internal/brain/goliath/Initialize(mapload) . = ..() - AddElement(/datum/element/noticable_organ, "arm is just a mass of plate and tendrils.", BODY_ZONE_CHEST) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their arm is just a mass of plate and tendrils.", BODY_ZONE_CHEST) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/goliath) /obj/item/organ/internal/brain/goliath/on_mob_insert(mob/living/carbon/brain_owner) @@ -170,7 +170,7 @@ /obj/item/organ/internal/heart/goliath/Initialize(mapload) . = ..() - AddElement(/datum/element/noticable_organ, "skin has visible hard plates growing from within.", BODY_ZONE_CHEST) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their skin has visible hard plates growing from within.", BODY_ZONE_CHEST) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/goliath) AddElement(/datum/element/update_icon_blocker) diff --git a/code/game/machinery/dna_infuser/organ_sets/gondola_organs.dm b/code/game/machinery/dna_infuser/organ_sets/gondola_organs.dm index 96e41a57789bd..a36ebc1d3c3a9 100644 --- a/code/game/machinery/dna_infuser/organ_sets/gondola_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/gondola_organs.dm @@ -31,7 +31,7 @@ Fluoride Stare: After someone says 5 words, blah blah blah... /obj/item/organ/internal/heart/gondola/Initialize(mapload) . = ..() AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/gondola) - AddElement(/datum/element/noticable_organ, "radiate%PRONOUN_S an aura of serenity.") + AddElement(/datum/element/noticable_organ, "%PRONOUN_They radiate%PRONOUN_S an aura of serenity.") /obj/item/organ/internal/heart/gondola/Insert(mob/living/carbon/receiver, special, movement_flags) . = ..() @@ -60,7 +60,7 @@ Fluoride Stare: After someone says 5 words, blah blah blah... /obj/item/organ/internal/tongue/gondola/Initialize(mapload) . = ..() - AddElement(/datum/element/noticable_organ, "mouth is permanently affixed into a relaxed smile.", BODY_ZONE_PRECISE_MOUTH) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their mouth is permanently affixed into a relaxed smile.", BODY_ZONE_PRECISE_MOUTH) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/gondola) /obj/item/organ/internal/tongue/gondola/Insert(mob/living/carbon/tongue_owner, special, movement_flags) @@ -83,8 +83,8 @@ Fluoride Stare: After someone says 5 words, blah blah blah... /obj/item/organ/internal/liver/gondola/Initialize(mapload) . = ..() AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/gondola) - AddElement(/datum/element/noticable_organ, "left arm has small needles breaching the skin all over it.", BODY_ZONE_L_ARM) - AddElement(/datum/element/noticable_organ, "right arm has small needles breaching the skin all over it.", BODY_ZONE_R_ARM) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their left arm has small needles breaching the skin all over it.", BODY_ZONE_L_ARM) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their right arm has small needles breaching the skin all over it.", BODY_ZONE_R_ARM) /obj/item/organ/internal/liver/gondola/Insert(mob/living/carbon/liver_owner, special, movement_flags) . = ..() diff --git a/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm b/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm index f19f3d725c711..56b147ffbee11 100644 --- a/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm @@ -29,7 +29,7 @@ /obj/item/organ/internal/eyes/night_vision/rat/Initialize(mapload) . = ..() - AddElement(/datum/element/noticable_organ, "eyes have deep, shifty black pupils, surrounded by a sickening yellow sclera.", BODY_ZONE_PRECISE_EYES) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their eyes have deep, shifty black pupils, surrounded by a sickening yellow sclera.", BODY_ZONE_PRECISE_EYES) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/rat) ///increases hunger, disgust recovers quicker, expands what is defined as "food" @@ -47,7 +47,7 @@ /obj/item/organ/internal/stomach/rat/Initialize(mapload) . = ..() AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/rat) - AddElement(/datum/element/noticable_organ, "mouth is drooling excessively.", BODY_ZONE_PRECISE_MOUTH) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their mouth is drooling excessively.", BODY_ZONE_PRECISE_MOUTH) /// makes you smaller, walk over tables, and take 1.5x damage /obj/item/organ/internal/heart/rat @@ -61,7 +61,7 @@ /obj/item/organ/internal/heart/rat/Initialize(mapload) . = ..() AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/rat) - AddElement(/datum/element/noticable_organ, "hunch%PRONOUN_ES over unnaturally!") + AddElement(/datum/element/noticable_organ, "%PRONOUN_They hunch%PRONOUN_ES over unnaturally!") /obj/item/organ/internal/heart/rat/on_mob_insert(mob/living/carbon/receiver) . = ..() @@ -98,7 +98,7 @@ /obj/item/organ/internal/tongue/rat/Initialize(mapload) . = ..() - AddElement(/datum/element/noticable_organ, "teeth are oddly shaped and yellowing.", BODY_ZONE_PRECISE_MOUTH) + AddElement(/datum/element/noticable_organ, "%PRONOUN_Their teeth are oddly shaped and yellowing.", BODY_ZONE_PRECISE_MOUTH) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/rat) /obj/item/organ/internal/tongue/rat/modify_speech(datum/source, list/speech_args) diff --git a/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm b/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm index 0644bca0354a6..11880a50cb2bc 100644 --- a/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm @@ -63,7 +63,7 @@ /obj/item/organ/internal/heart/roach/Initialize(mapload) . = ..() - AddElement(/datum/element/noticable_organ, "has hardened, somewhat translucent skin.") + AddElement(/datum/element/noticable_organ, "%PRONOUN_They %PRONOUN_have hardened, somewhat translucent skin.") AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/roach) roach_shell = new() diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 96f7d568f0e16..e32b78d9f776c 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -86,6 +86,7 @@ smoothing_groups = SMOOTH_GROUP_AIRLOCK interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_OPEN + interaction_flags_click = ALLOW_SILICON_REACH blocks_emissive = EMISSIVE_BLOCK_NONE // Custom emissive blocker. We don't want the normal behavior. ///The type of door frame to drop during deconstruction diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm index 16f89fdf3c103..1e06be41952fd 100644 --- a/code/game/machinery/hologram.dm +++ b/code/game/machinery/hologram.dm @@ -47,6 +47,7 @@ Possible to do for anyone motivated enough: armor_type = /datum/armor/machinery_holopad circuit = /obj/item/circuitboard/machine/holopad interaction_flags_atom = parent_type::interaction_flags_atom | INTERACT_ATOM_IGNORE_MOBILITY + interaction_flags_click = ALLOW_SILICON_REACH // Blue, dim light light_power = 0.8 light_color = LIGHT_COLOR_BLUE diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index 06b1b9778477c..ffbecf911808e 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -910,6 +910,7 @@ DEFINE_BITFIELD(turret_flags, list( density = FALSE req_access = list(ACCESS_AI_UPLOAD) resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + interaction_flags_click = ALLOW_SILICON_REACH /// Variable dictating if linked turrets are active and will shoot targets var/enabled = TRUE /// Variable dictating if linked turrets will shoot lethal projectiles diff --git a/code/game/objects/items/robot/items/tools.dm b/code/game/objects/items/robot/items/tools.dm index 0d06e08cf5e3d..49c3197ae1eb6 100644 --- a/code/game/objects/items/robot/items/tools.dm +++ b/code/game/objects/items/robot/items/tools.dm @@ -319,9 +319,9 @@ /obj/item/borg/cyborg_omnitool/medical/reference_item_for_parameters() var/datum/component/butchering/butchering = src.GetComponent(/datum/component/butchering) - butchering.butchering_enabled = tool_behaviour == (TOOL_SCALPEL && TOOL_SAW) + butchering.butchering_enabled = (tool_behaviour == TOOL_SCALPEL || tool_behaviour == TOOL_SAW) RemoveElement(/datum/element/eyestab) - RemoveComponentSource(/datum/component/surgery_initiator) + qdel(GetComponent(/datum/component/surgery_initiator)) item_flags = SURGICAL_TOOL switch(tool_behaviour) if(TOOL_SCALPEL) diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm deleted file mode 100644 index 7ca4ccbc320a7..0000000000000 --- a/code/modules/admin/ipintel.dm +++ /dev/null @@ -1,136 +0,0 @@ -/datum/ipintel - var/ip - var/intel = 0 - var/cache = FALSE - var/cacheminutesago = 0 - var/cachedate = "" - var/cacherealtime = 0 - -/datum/ipintel/New() - cachedate = SQLtime() - cacherealtime = world.realtime - -/datum/ipintel/proc/is_valid() - . = FALSE - if (intel < 0) - return - if (intel <= CONFIG_GET(number/ipintel_rating_bad)) - if (world.realtime < cacherealtime + (CONFIG_GET(number/ipintel_save_good) * 60 * 60 * 10)) - return TRUE - else - if (world.realtime < cacherealtime + (CONFIG_GET(number/ipintel_save_bad) * 60 * 60 * 10)) - return TRUE - -/proc/get_ip_intel(ip, bypasscache = FALSE, updatecache = TRUE) - var/datum/ipintel/res = new() - res.ip = ip - . = res - if (!ip || !CONFIG_GET(string/ipintel_email) || !SSipintel.enabled) - return - if (!bypasscache) - var/datum/ipintel/cachedintel = SSipintel.cache[ip] - if (cachedintel?.is_valid()) - cachedintel.cache = TRUE - return cachedintel - - if(SSdbcore.Connect()) - var/rating_bad = CONFIG_GET(number/ipintel_rating_bad) - var/datum/db_query/query_get_ip_intel = SSdbcore.NewQuery({" - SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW()) - FROM [format_table_name("ipintel")] - WHERE - ip = INET_ATON(':ip') - AND (( - intel < :rating_bad - AND - date + INTERVAL :save_good HOUR > NOW() - ) OR ( - intel >= :rating_bad - AND - date + INTERVAL :save_bad HOUR > NOW() - )) - "}, list("ip" = ip, "rating_bad" = rating_bad, "save_good" = CONFIG_GET(number/ipintel_save_good), "save_bad" = CONFIG_GET(number/ipintel_save_bad))) - if(!query_get_ip_intel.Execute()) - qdel(query_get_ip_intel) - return - if (query_get_ip_intel.NextRow()) - res.cache = TRUE - res.cachedate = query_get_ip_intel.item[1] - res.intel = text2num(query_get_ip_intel.item[2]) - res.cacheminutesago = text2num(query_get_ip_intel.item[3]) - res.cacherealtime = world.realtime - (text2num(query_get_ip_intel.item[3])*10*60) - SSipintel.cache[ip] = res - qdel(query_get_ip_intel) - return - qdel(query_get_ip_intel) - res.intel = ip_intel_query(ip) - if (updatecache && res.intel >= 0) - SSipintel.cache[ip] = res - if(SSdbcore.Connect()) - var/datum/db_query/query_add_ip_intel = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON(:ip), :intel) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()", - list("ip" = ip, "intel" = res.intel) - ) - query_add_ip_intel.Execute() - qdel(query_add_ip_intel) - - -/proc/ip_intel_query(ip, retryed=0) - . = -1 //default - if (!ip) - return - if (SSipintel.throttle > world.timeofday) - return - if (!SSipintel.enabled) - return - - var/list/http[] = world.Export("http://[CONFIG_GET(string/ipintel_domain)]/check.php?ip=[ip]&contact=[CONFIG_GET(string/ipintel_email)]&format=json&flags=f") - - if (http) - var/status = text2num(http["STATUS"]) - - if (status == 200) - var/response = json_decode(file2text(http["CONTENT"])) - if (response) - if (response["status"] == "success") - var/intelnum = text2num(response["result"]) - if (isnum(intelnum)) - return text2num(response["result"]) - else - ipintel_handle_error("Bad intel from server: [response["result"]].", ip, retryed) - if (!retryed) - sleep(2.5 SECONDS) - return .(ip, 1) - else - ipintel_handle_error("Bad response from server: [response["status"]].", ip, retryed) - if (!retryed) - sleep(2.5 SECONDS) - return .(ip, 1) - - else if (status == 429) - ipintel_handle_error("Error #429: We have exceeded the rate limit.", ip, 1) - return - else - ipintel_handle_error("Unknown status code: [status].", ip, retryed) - if (!retryed) - sleep(2.5 SECONDS) - return .(ip, 1) - else - ipintel_handle_error("Unable to connect to API.", ip, retryed) - if (!retryed) - sleep(2.5 SECONDS) - return .(ip, 1) - - -/proc/ipintel_handle_error(error, ip, retryed) - if (retryed) - SSipintel.errors++ - error += " Could not check [ip]. Disabling IPINTEL for [SSipintel.errors] minute[( SSipintel.errors == 1 ? "" : "s" )]" - SSipintel.throttle = world.timeofday + (10 * 120 * SSipintel.errors) - else - error += " Attempting retry on [ip]." - log_ipintel(error) - -/proc/log_ipintel(text) - log_game("IPINTEL: [text]") - debug_admins("IPINTEL: [text]") diff --git a/code/modules/admin/view_variables/debug_variable_appearance.dm b/code/modules/admin/view_variables/debug_variable_appearance.dm index 0d7bbb4b61cc0..9e92eba4605c3 100644 --- a/code/modules/admin/view_variables/debug_variable_appearance.dm +++ b/code/modules/admin/view_variables/debug_variable_appearance.dm @@ -1,236 +1,101 @@ -/* < OH MY GOD. Can't you just make "/image/proc/foo()" instead of making these? > - * /appearance is a hardcoded byond type, and it is very internal type. - * Its type is actually /image, but it isn't truly /image. We defined it as "/appearance" - * new procs to /image will only work to actual /image references, but... - * /appearance references are not capable of executing procs, because these are not real /image - * This is why these global procs exist. Welcome to the curse. - */ -#define ADD_UNUSED_VAR(varlist, thing, varname) if(NAMEOF(##thing, ##varname)) ##varlist += #varname -#define RESULT_VARIABLE_NOT_FOUND "_switch_result_variable_not_found" - -/// An alias datum that allows us to access and view the variables of an appearance by keeping certain known, yet undocumented, variables that we can access and read in a datum for debugging purposes. -/// Kindly do not use this outside of a debugging context. -/image/appearance - parent_type = /atom/movable // This is necessary to access the variables on compile-time. - - // var/override // Sad point. We can't steal byond internal variable name -#ifdef OPENDREAM - // opendream doens't support mouse_drop_zone yet. Remove this once OD supports it. - var/mouse_drop_zone -#endif - -/image/appearance/New(loc, ...) - . = ..() - CRASH("something tried to use '/image/appearance', but this isn't actual type we use. Do not fucking do this.") - -/// Makes a var list of /appearance type actually uses. This will be only called once. -/proc/build_virtual_appearance_vars() - var/list/used_variables = list("vis_flags") // manual listing. - . = used_variables - var/list/unused_var_names = list() - - var/image/appearance/nameof_reference // We don't copy vars from this. - pass(nameof_reference) // compiler complains unused variable - ADD_UNUSED_VAR(unused_var_names, nameof_reference, appearance) // it only does self-reference - ADD_UNUSED_VAR(unused_var_names, nameof_reference, x) // xyz are always 0 - ADD_UNUSED_VAR(unused_var_names, nameof_reference, y) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, z) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, weak_reference) // it's not a good idea to make a weak_ref on this, and this won't have it - ADD_UNUSED_VAR(unused_var_names, nameof_reference, vars) // inherited from /image, but /appearance hasn't this - - // Even if these vars are essential for image, these only exists in an actual type - ADD_UNUSED_VAR(unused_var_names, nameof_reference, filter_data) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, realized_overlays) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, realized_underlays) - - // we have no reason to show these, right? - ADD_UNUSED_VAR(unused_var_names, nameof_reference, _active_timers) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, _datum_components) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, _listen_lookup) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, _signal_procs) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, __auxtools_weakref_id) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, _status_traits) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, cooldowns) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, datum_flags) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, verbs) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, gc_destroyed) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, harddel_deets_dumped) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, open_uis) - ADD_UNUSED_VAR(unused_var_names, nameof_reference, tgui_shared_states) - - var/image/dummy_image = image(null, null) // actual type we'll copy variable names - for(var/each in dummy_image.vars) // try to inherit var list from /image - if(each in unused_var_names) - continue - used_variables += each - del(dummy_image) - dummy_image = null - - return used_variables - -/// debug_variable() proc but made for /appearance type specifically -/proc/debug_variable_appearance(var_name, appearance) - var/value - try - value = locate_appearance_variable(var_name, appearance) - catch - return "
  • (READ ONLY) [var_name] = (untrackable)
  • " - if(value == RESULT_VARIABLE_NOT_FOUND) - return "
  • (READ ONLY) [var_name] (Undefined var name in switch)
  • " - return "
  • (READ ONLY) [var_name] = [_debug_variable_value(var_name, value, 0, appearance, sanitize = TRUE, display_flags = NONE)]
  • " - -/// manually locate a variable through string value. -/// appearance type needs a manual var referencing because it doesn't have "vars" variable internally. -/// There's no way doing this in a fancier way. -/proc/locate_appearance_variable(var_name, image/appearance/appearance) // WARN: /image/appearance is a mocking type, not real one - switch(var_name) // Welcome to this curse - // appearance doesn't have "vars" variable. - // This means you need to target a variable manually through this way. - - // appearance vars in DM document - if(NAMEOF(appearance, alpha)) - return appearance.alpha - if(NAMEOF(appearance, appearance_flags)) - return appearance.appearance_flags - if(NAMEOF(appearance, blend_mode)) - return appearance.blend_mode - if(NAMEOF(appearance, color)) - return appearance.color - if(NAMEOF(appearance, desc)) - return appearance.desc - if(NAMEOF(appearance, gender)) - return appearance.gender - if(NAMEOF(appearance, icon)) - return appearance.icon - if(NAMEOF(appearance, icon_state)) - return appearance.icon_state - if(NAMEOF(appearance, invisibility)) - return appearance.invisibility - if(NAMEOF(appearance, infra_luminosity)) - return appearance.infra_luminosity - if(NAMEOF(appearance, filters)) - return appearance.filters - if(NAMEOF(appearance, layer)) - return appearance.layer - if(NAMEOF(appearance, luminosity)) - return appearance.luminosity - if(NAMEOF(appearance, maptext)) - return appearance.maptext - if(NAMEOF(appearance, maptext_width)) - return appearance.maptext_width - if(NAMEOF(appearance, maptext_height)) - return appearance.maptext_height - if(NAMEOF(appearance, maptext_x)) - return appearance.maptext_x - if(NAMEOF(appearance, maptext_y)) - return appearance.maptext_y - if(NAMEOF(appearance, mouse_over_pointer)) - return appearance.mouse_over_pointer - if(NAMEOF(appearance, mouse_drag_pointer)) - return appearance.mouse_drag_pointer - if(NAMEOF(appearance, mouse_drop_pointer)) - return appearance.mouse_drop_pointer - if(NAMEOF(appearance, mouse_drop_zone)) - return appearance:mouse_drop_zone - if(NAMEOF(appearance, mouse_opacity)) - return appearance.mouse_opacity - if(NAMEOF(appearance, name)) - return appearance.name - if(NAMEOF(appearance, opacity)) - return appearance.opacity - if(NAMEOF(appearance, overlays)) - return appearance.overlays - if("override") // only /image has this. mocking type can't steal byond internal var name - var/image/image_appearance = appearance - return image_appearance.override - if(NAMEOF(appearance, pixel_x)) - return appearance.pixel_x - if(NAMEOF(appearance, pixel_y)) - return appearance.pixel_y - if(NAMEOF(appearance, pixel_w)) - return appearance.pixel_w - if(NAMEOF(appearance, pixel_z)) - return appearance.pixel_z - if(NAMEOF(appearance, plane)) - return appearance.plane - if(NAMEOF(appearance, render_source)) - return appearance.render_source - if(NAMEOF(appearance, render_target)) - return appearance.render_target - if(NAMEOF(appearance, suffix)) - return appearance.suffix - if(NAMEOF(appearance, text)) - return appearance.text - if(NAMEOF(appearance, transform)) - return appearance.transform - if(NAMEOF(appearance, underlays)) - return appearance.underlays - - if(NAMEOF(appearance, parent_type)) - return appearance.parent_type - if(NAMEOF(appearance, type)) - return /image/appearance // don't fool people - - // These are not documented ones but trackable values. Maybe we'd want these. - if(NAMEOF(appearance, animate_movement)) - return appearance.animate_movement - if(NAMEOF(appearance, dir)) - return appearance.dir - if(NAMEOF(appearance, glide_size)) - return appearance.glide_size - if("pixel_step_size") - return "" //atom_appearance.pixel_step_size - // DM compiler complains this - - // I am not sure if these will be ever detected, but I made a connection just in case. - if(NAMEOF(appearance, contents)) // It's not a thing, but I don't believe how DM will change /appearance in future. - return appearance.contents - if(NAMEOF(appearance, loc)) // same reason above - return appearance.loc - if(NAMEOF(appearance, vis_contents)) // same reason above - return appearance.vis_contents - if(NAMEOF(appearance, vis_flags)) // DM document says /appearance has this, but it throws error - return appearance.vis_flags - - // we wouldn't need these, but let's these trackable anyway... - if(NAMEOF(appearance, density)) - return appearance.density - if(NAMEOF(appearance, screen_loc)) - return appearance.screen_loc - if(NAMEOF(appearance, verbs)) - return appearance.verbs - if(NAMEOF(appearance, tag)) - return appearance.tag - return RESULT_VARIABLE_NOT_FOUND - -/// Shows a header name on top when you investigate an appearance -/proc/vv_get_header_appearance(image/thing) +/// Shows a header name on top when you investigate an appearance/image +/image/vv_get_header() . = list() - var/icon_name = "[thing.icon || "null"]
    " + var/icon_name = "[icon || "null"]
    " . += replacetext(icon_name, "icons/obj", "") // shortens the name. We know the path already. - if(thing.icon) - . += thing.icon_state ? "\"[thing.icon_state]\"" : "(icon_state = null)" + if(icon) + . += icon_state ? "\"[icon_state]\"" : "(icon_state = null)" + +/// Makes nice short vv names for images +/image/debug_variable_value(name, level, datum/owner, sanitize, display_flags) + var/display_name = "[type]" + if("[src]" != "[type]") // If we have a name var, let's use it. + display_name = "[src] [type]" + + var/display_value + var/list/icon_file_name = splittext("[icon]", "/") + if(length(icon_file_name)) + display_value = icon_file_name[length(icon_file_name)] + else + display_value = "null" + + if(icon_state) + display_value = "[display_value]:[icon_state]" + + var/display_ref = get_vv_link_ref() + return "[display_name] ([display_value]) [display_ref]" + +/// Returns the ref string to use when displaying this image in the vv menu of something else +/image/proc/get_vv_link_ref() + return REF(src) + +// It is endlessly annoying to display /appearance directly for stupid byond reasons, so we copy everything we care about into a holder datum +// That we can override procs on and store other vars on and such. +/mutable_appearance/appearance_mirror + // So people can see where it came from + var/appearance_ref + // vis flags can't be displayed by mutable appearances cause it don't make sense as overlays, but appearances do carry them + // can't use the name either for byond reasons + var/_vis_flags + +// all alone at the end of the universe +GLOBAL_DATUM_INIT(pluto, /atom/movable, new /atom/movable(null)) + +// arg is actually an appearance, typed as mutable_appearance as closest mirror +/mutable_appearance/appearance_mirror/New(mutable_appearance/appearance_father) + . = ..() // /mutable_appearance/New() copies over all the appearance vars MAs care about by default + // We copy over our appearance onto an atom. This is done so we can read vars carried by but not accessible on appearances + GLOB.pluto.appearance = appearance_father + _vis_flags = GLOB.pluto.vis_flags + appearance_ref = REF(appearance_father) + +// This means if the appearance loses refs before a click it's gone, but that's consistent to other datums so it's fine +// Need to ref the APPEARANCE because we just free on our own, which sorta fucks this operation up you know? +/mutable_appearance/appearance_mirror/get_vv_link_ref() + return appearance_ref + +/mutable_appearance/appearance_mirror/can_vv_get(var_name) + var/static/datum/beloved = new() + if(beloved.vars.Find(var_name)) // If datums have it, get out + return FALSE + // If it is one of the two args on /image, yeet (I am sorry) + if(var_name == NAMEOF(src, realized_overlays)) + return FALSE + if(var_name == NAMEOF(src, realized_underlays)) + return FALSE + // Filtering out the stuff I know we don't care about + if(var_name == NAMEOF(src, x)) + return FALSE + if(var_name == NAMEOF(src, y)) + return FALSE + if(var_name == NAMEOF(src, z)) + return FALSE + // Could make an argument for these but I think they will just confuse people, so yeeet +#ifndef SPACEMAN_DMM // Spaceman doesn't believe in contents on appearances, sorry lads + if(var_name == NAMEOF(src, contents)) + return FALSE +#endif + if(var_name == NAMEOF(src, loc)) + return FALSE + if(var_name == NAMEOF(src, vis_contents)) + return FALSE + return ..() -/image/vv_get_header() // it should redirect to global proc version because /appearance can't call a proc, unless we want dupe code here - return vv_get_header_appearance(src) +/mutable_appearance/appearance_mirror/vv_get_var(var_name) + // No editing for you + var/value = vars[var_name] + return "
  • (READ ONLY) [var_name] = [_debug_variable_value(var_name, value, 0, src, sanitize = TRUE, display_flags = NONE)]
  • " -/// Makes a format name for shortened vv name. -/proc/get_appearance_vv_summary_name(image/thing) - var/icon_file_name = thing.icon ? splittext("[thing.icon]", "/") : "null" - if(islist(icon_file_name)) - icon_file_name = length(icon_file_name) ? icon_file_name[length(icon_file_name)] : "null" - if(thing.icon_state) - return "[icon_file_name]:[thing.icon_state]" - else - return "[icon_file_name]" +/mutable_appearance/appearance_mirror/vv_get_dropdown() + SHOULD_CALL_PARENT(FALSE) -/proc/vv_get_dropdown_appearance(image/thing) . = list() - // Don't add any vv option carelessly unless you have a good reason to add one for /appearance. - // /appearance type shouldn't allow general options. Even "Mark Datum" is a questionable behaviour here. - VV_DROPDOWN_OPTION_APPEARANCE(thing, "", "---") - VV_DROPDOWN_OPTION_APPEARANCE(thing, VV_HK_EXPOSE, "Show VV To Player") // only legit option - return . - -#undef ADD_UNUSED_VAR -#undef RESULT_VARIABLE_NOT_FOUND + VV_DROPDOWN_OPTION("", "---") + VV_DROPDOWN_OPTION(VV_HK_CALLPROC, "Call Proc") + VV_DROPDOWN_OPTION(VV_HK_MARK, "Mark Object") + VV_DROPDOWN_OPTION(VV_HK_TAG, "Tag Datum") + VV_DROPDOWN_OPTION(VV_HK_DELETE, "Delete") + VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player") + +/proc/get_vv_appearance(mutable_appearance/appearance) // actually appearance yadeeyada + return new /mutable_appearance/appearance_mirror(appearance) diff --git a/code/modules/admin/view_variables/debug_variables.dm b/code/modules/admin/view_variables/debug_variables.dm index d64a9c2ef9aac..a4035acd01421 100644 --- a/code/modules/admin/view_variables/debug_variables.dm +++ b/code/modules/admin/view_variables/debug_variables.dm @@ -30,6 +30,9 @@ // This is split into a seperate proc mostly to make errors that happen not break things too much /proc/_debug_variable_value(name, value, level, datum/owner, sanitize, display_flags) + if(isappearance(value)) + value = get_vv_appearance(value) + . = "DISPLAY_ERROR: ([value] [REF(value)])" // Make sure this line can never runtime if(isnull(value)) @@ -49,13 +52,6 @@ return "/icon ([value])" #endif - - if(isappearance(value)) // Reminder: Do not replace this into /image/debug_variable_value() proc. /appearance can't do that. - return "/appearance ([get_appearance_vv_summary_name(value)]) [REF(value)]" - - if(isimage(value)) - return "[value:type] ([get_appearance_vv_summary_name(value)]) [REF(value)]" - if(isfilter(value)) var/datum/filter_value = value return "/filter ([filter_value.type] [REF(filter_value)])" diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm index 45f8ef84c0e03..37bf0911c608f 100644 --- a/code/modules/admin/view_variables/view_variables.dm +++ b/code/modules/admin/view_variables/view_variables.dm @@ -21,9 +21,10 @@ ADMIN_VERB_AND_CONTEXT_MENU(debug_variables, R_NONE, "View Variables", "View the var/datum/asset/asset_cache_datum = get_asset_datum(/datum/asset/simple/vv) asset_cache_datum.send(usr) - var/isappearance = isappearance(thing) + if(isappearance(thing)) + thing = get_vv_appearance(thing) // this is /mutable_appearance/our_bs_subtype var/islist = islist(thing) || (!isdatum(thing) && hascall(thing, "Cut")) // Some special lists dont count as lists, but can be detected by if they have list procs - if(!islist && !isdatum(thing) && !isappearance) + if(!islist && !isdatum(thing)) return var/title = "" @@ -31,7 +32,7 @@ ADMIN_VERB_AND_CONTEXT_MENU(debug_variables, R_NONE, "View Variables", "View the var/icon/sprite var/hash - var/type = islist? /list : (isappearance ? "/appearance" : thing.type) + var/type = islist ? /list : thing.type var/no_icon = FALSE if(isatom(thing)) @@ -39,7 +40,7 @@ ADMIN_VERB_AND_CONTEXT_MENU(debug_variables, R_NONE, "View Variables", "View the if(!sprite) no_icon = TRUE - else if(isimage(thing) || isappearance) + else if(isimage(thing)) // icon_state=null shows first image even if dmi has no icon_state for null name. // This list remembers which dmi has null icon_state, to determine if icon_state=null should display a sprite // (NOTE: icon_state="" is correct, but saying null is obvious) @@ -49,7 +50,7 @@ ADMIN_VERB_AND_CONTEXT_MENU(debug_variables, R_NONE, "View Variables", "View the if(icon_filename_text) if(image_object.icon_state) sprite = icon(image_object.icon, image_object.icon_state) - + else // it means: icon_state="" if(!dmi_nullstate_checklist[icon_filename_text]) dmi_nullstate_checklist[icon_filename_text] = ICON_STATE_CHECKED @@ -69,7 +70,7 @@ ADMIN_VERB_AND_CONTEXT_MENU(debug_variables, R_NONE, "View Variables", "View the title = "[thing] ([REF(thing)]) = [type]" var/formatted_type = replacetext("[type]", "/", "/") - var/list/header = islist ? list("/list") : (isappearance ? vv_get_header_appearance(thing) : thing.vv_get_header()) + var/list/header = islist ? list("/list") : thing.vv_get_header() var/ref_line = "@[copytext(refid, 2, -1)]" // get rid of the brackets, add a @ prefix for copy pasting in asay @@ -103,16 +104,11 @@ ADMIN_VERB_AND_CONTEXT_MENU(debug_variables, R_NONE, "View Variables", "View the var/name = dropdownoptions[i] var/link = dropdownoptions[name] dropdownoptions[i] = "" - else if(isappearance) - dropdownoptions = vv_get_dropdown_appearance(thing) else dropdownoptions = thing.vv_get_dropdown() var/list/names = list() - if(isappearance) - var/static/list/virtual_appearance_vars = build_virtual_appearance_vars() - names = virtual_appearance_vars.Copy() - else if(!islist) + if(!islist) for(var/varname in thing.vars) names += varname @@ -127,10 +123,6 @@ ADMIN_VERB_AND_CONTEXT_MENU(debug_variables, R_NONE, "View Variables", "View the if(IS_NORMAL_LIST(list_value) && IS_VALID_ASSOC_KEY(key)) value = list_value[key] variable_html += debug_variable(i, value, 0, list_value) - else if(isappearance) - names = sort_list(names) - for(var/varname in names) - variable_html += debug_variable_appearance(varname, thing) else names = sort_list(names) for(var/varname in names) diff --git a/code/modules/asset_cache/assets/tgui.dm b/code/modules/asset_cache/assets/tgui.dm index 9c79925602c77..4b31d93e037f5 100644 --- a/code/modules/asset_cache/assets/tgui.dm +++ b/code/modules/asset_cache/assets/tgui.dm @@ -1,3 +1,23 @@ +// If you use a file(...) object, instead of caching the asset it will be loaded from disk every time it's requested. +// This is useful for development, but not recommended for production. +// And if TGS is defined, we're being run in a production environment. + +#ifdef TGS +/datum/asset/simple/tgui + keep_local_name = FALSE + assets = list( + "tgui.bundle.js" = "tgui/public/tgui.bundle.js", + "tgui.bundle.css" = "tgui/public/tgui.bundle.css", + ) + +/datum/asset/simple/tgui_panel + keep_local_name = FALSE + assets = list( + "tgui-panel.bundle.js" = "tgui/public/tgui-panel.bundle.js", + "tgui-panel.bundle.css" = "tgui/public/tgui-panel.bundle.css", + ) + +#else /datum/asset/simple/tgui keep_local_name = TRUE assets = list( @@ -11,3 +31,5 @@ "tgui-panel.bundle.js" = file("tgui/public/tgui-panel.bundle.js"), "tgui-panel.bundle.css" = file("tgui/public/tgui-panel.bundle.css"), ) + +#endif diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 913be33118271..2c8c63579e2f5 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -541,7 +541,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( initialize_menus() loot_panel = new(src) - + view_size = new(src, getScreenSize(prefs.read_preference(/datum/preference/toggle/widescreen))) view_size.resetFormat() view_size.setZoomMode() @@ -828,15 +828,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( qdel(query_get_notes) create_message("note", key, system_ckey, message, null, null, 0, 0, null, 0, 0) - -/client/proc/check_ip_intel() - set waitfor = 0 //we sleep when getting the intel, no need to hold up the client connection while we sleep - if (CONFIG_GET(string/ipintel_email)) - var/datum/ipintel/res = get_ip_intel(address) - if (res.intel >= CONFIG_GET(number/ipintel_rating_bad)) - message_admins(span_adminnotice("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a Proxy/VPN.")) - ip_intel = res.intel - /client/Click(atom/object, atom/location, control, params) if(click_intercept_time) if(click_intercept_time >= world.time) diff --git a/code/modules/jobs/access.dm b/code/modules/jobs/access.dm index 2055080710e4e..b31574bec33e3 100644 --- a/code/modules/jobs/access.dm +++ b/code/modules/jobs/access.dm @@ -19,6 +19,12 @@ if(isAdminGhostAI(accessor)) //Access can't stop the abuse return TRUE + //If the mob has the simple_access component with the requried access, we let them in. + var/attempted_access = SEND_SIGNAL(accessor, COMSIG_MOB_TRIED_ACCESS, src) + if(attempted_access & ACCESS_ALLOWED) + return TRUE + if(attempted_access & ACCESS_DISALLOWED) + return FALSE if(HAS_SILICON_ACCESS(accessor)) if(ispAI(accessor)) return FALSE @@ -28,9 +34,6 @@ if(onSyndieBase() && loc != accessor) return FALSE return TRUE //AI can do whatever it wants - //If the mob has the simple_access component with the requried access, we let them in. - else if(SEND_SIGNAL(accessor, COMSIG_MOB_TRIED_ACCESS, src) & ACCESS_ALLOWED) - return TRUE //If the mob is holding a valid ID, we let them in. get_active_held_item() is on the mob level, so no need to copypasta everywhere. else if(check_access(accessor.get_active_held_item()) || check_access(accessor.get_inactive_held_item())) return TRUE diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm index ccd4b0d617f3f..d98369294e018 100644 --- a/code/modules/mob/living/basic/bots/_bots.dm +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -104,7 +104,7 @@ GLOBAL_LIST_INIT(command_strings, list( AddElement(/datum/element/relay_attackers) RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(handle_loop_movement)) RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(after_attacked)) - RegisterSignal(src, COMSIG_OBJ_ALLOWED, PROC_REF(attempt_access)) + RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(attempt_access)) ADD_TRAIT(src, TRAIT_NO_GLIDE, INNATE_TRAIT) GLOB.bots_list += src @@ -757,7 +757,7 @@ GLOBAL_LIST_INIT(command_strings, list( /mob/living/basic/bot/proc/attempt_access(mob/bot, obj/door_attempt) SIGNAL_HANDLER - return (door_attempt.check_access(access_card) ? COMPONENT_OBJ_ALLOW : COMPONENT_OBJ_DISALLOW) + return (door_attempt.check_access(access_card) ? ACCESS_ALLOWED : ACCESS_DISALLOWED) /mob/living/basic/bot/proc/generate_speak_list() return null diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm index 21169ffd36889..61f31f7044dbc 100644 --- a/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm @@ -44,7 +44,7 @@ StartCooldown() return TRUE - do_after(owner, delay = beam_duration, target = owner) + do_after(owner, delay = beam_duration, target = owner, hidden = TRUE) extinguish_laser() StartCooldown() return TRUE diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 51337a618edf4..8d968fd895589 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -1563,10 +1563,26 @@ GLOBAL_LIST_EMPTY(features_by_species) /datum/species/proc/prepare_human_for_preview(mob/living/carbon/human/human) return -/// Returns the species's scream sound. +/// Returns the species' scream sound. /datum/species/proc/get_scream_sound(mob/living/carbon/human/human) return +/// Returns the species' cry sound. +/datum/species/proc/get_cry_sound(mob/living/carbon/human/human) + return + +/// Returns the species' cough sound. +/datum/species/proc/get_cough_sound(mob/living/carbon/human/human) + return + +/// Returns the species' laugh sound +/datum/species/proc/get_laugh_sound(mob/living/carbon/human/human) + return + +/// Returns the species' sneeze sound. +/datum/species/proc/get_sneeze_sound(mob/living/carbon/human/human) + return + /datum/species/proc/get_types_to_preload() var/list/to_store = list() to_store += mutant_organs diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm index b205eb2e2e217..c9f0ffe504618 100644 --- a/code/modules/mob/living/carbon/human/emote.dm +++ b/code/modules/mob/living/carbon/human/emote.dm @@ -1,12 +1,15 @@ /datum/emote/living/carbon/human mob_type_allowed_typecache = list(/mob/living/carbon/human) + /datum/emote/living/carbon/human/cry key = "cry" key_third_person = "cries" message = "cries." message_mime = "sobs silently." + audio_cooldown = 5 SECONDS emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE + vary = TRUE stat_allowed = SOFT_CRIT /datum/emote/living/carbon/human/cry/run_emote(mob/user, params, type_override, intentional) @@ -16,6 +19,11 @@ var/mob/living/carbon/human/human_user = user QDEL_IN(human_user.give_emote_overlay(/datum/bodypart_overlay/simple/emote/cry), 12.8 SECONDS) +/datum/emote/living/carbon/human/cry/get_sound(mob/living/carbon/human/user) + if(!istype(user)) + return + return user.dna.species.get_cry_sound(user) + /datum/emote/living/carbon/human/dap key = "dap" key_third_person = "daps" @@ -39,6 +47,36 @@ return ..() return FALSE +/datum/emote/living/carbon/human/cough + key = "cough" + key_third_person = "coughs" + message = "coughs!" + message_mime = "acts out an exaggerated cough!" + vary = TRUE + audio_cooldown = 5 SECONDS + emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE | EMOTE_RUNECHAT + +/datum/emote/living/cough/can_run_emote(mob/user, status_check = TRUE , intentional) + return !HAS_TRAIT(user, TRAIT_SOOTHED_THROAT) && ..() + +/datum/emote/living/carbon/human/cough/get_sound(mob/living/carbon/human/user) + if(!istype(user)) + return + return user.dna.species.get_cough_sound(user) +/datum/emote/living/carbon/human/sneeze + key = "sneeze" + key_third_person = "sneezes" + message = "sneezes." + audio_cooldown = 5 SECONDS + message_mime = "acts out an exaggerated silent sneeze." + vary = TRUE + emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE + +/datum/emote/living/carbon/human/sneeze/get_sound(mob/living/carbon/human/user) + if(!istype(user)) + return + return user.dna.species.get_sneeze_sound(user) + /datum/emote/living/carbon/human/glasses/run_emote(mob/user, params, type_override, intentional) . = ..() var/image/emote_animation = image('icons/mob/human/emote_visuals.dmi', user, "glasses") diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 5b2864da4e399..141c904419152 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -321,7 +321,7 @@ if(CONSCIOUS) if(HAS_TRAIT(src, TRAIT_DUMB)) msg += "[t_He] [t_has] a stupid expression on [t_his] face.\n" - if(get_organ_by_type(/obj/item/organ/internal/brain)) + if(get_organ_by_type(/obj/item/organ/internal/brain) && isnull(ai_controller)) if(!key) msg += "[span_deadsay("[t_He] [t_is] totally catatonic. The stresses of life in deep-space must have been too much for [t_him]. Any recovery is unlikely.")]\n" else if(!client) diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm index 731b6047a0644..528d9a3a0073b 100644 --- a/code/modules/mob/living/carbon/human/species_types/felinid.dm +++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm @@ -50,6 +50,54 @@ features["ears"] = pick("None", "Cat") return features +/datum/species/human/felinid/get_laugh_sound(mob/living/carbon/human/felinid) + if(felinid.physique == FEMALE) + return 'sound/voice/human/womanlaugh.ogg' + return pick( + 'sound/voice/human/manlaugh1.ogg', + 'sound/voice/human/manlaugh2.ogg', + ) + + +/datum/species/human/felinid/get_cough_sound(mob/living/carbon/human/felinid) + if(felinid.physique == FEMALE) + return pick( + 'sound/voice/human/female_cough1.ogg', + 'sound/voice/human/female_cough2.ogg', + 'sound/voice/human/female_cough3.ogg', + 'sound/voice/human/female_cough4.ogg', + 'sound/voice/human/female_cough5.ogg', + 'sound/voice/human/female_cough6.ogg', + ) + return pick( + 'sound/voice/human/male_cough1.ogg', + 'sound/voice/human/male_cough2.ogg', + 'sound/voice/human/male_cough3.ogg', + 'sound/voice/human/male_cough4.ogg', + 'sound/voice/human/male_cough5.ogg', + 'sound/voice/human/male_cough6.ogg', + ) + + +/datum/species/human/felinid/get_cry_sound(mob/living/carbon/human/felinid) + if(felinid.physique == FEMALE) + return pick( + 'sound/voice/human/female_cry1.ogg', + 'sound/voice/human/female_cry2.ogg', + ) + return pick( + 'sound/voice/human/male_cry1.ogg', + 'sound/voice/human/male_cry2.ogg', + 'sound/voice/human/male_cry3.ogg', + ) + + +/datum/species/human/felinid/get_sneeze_sound(mob/living/carbon/human/felinid) + if(felinid.physique == FEMALE) + return 'sound/voice/human/female_sneeze1.ogg' + return 'sound/voice/human/male_sneeze1.ogg' + + /proc/mass_purrbation() for(var/mob in GLOB.human_list) purrbation_apply(mob) diff --git a/code/modules/mob/living/carbon/human/species_types/humans.dm b/code/modules/mob/living/carbon/human/species_types/humans.dm index c7a181027e64e..4c575f8e48c05 100644 --- a/code/modules/mob/living/carbon/human/species_types/humans.dm +++ b/code/modules/mob/living/carbon/human/species_types/humans.dm @@ -34,6 +34,53 @@ 'sound/voice/human/femalescream_5.ogg', ) +/datum/species/human/get_cough_sound(mob/living/carbon/human/human) + if(human.physique == FEMALE) + return pick( + 'sound/voice/human/female_cough1.ogg', + 'sound/voice/human/female_cough2.ogg', + 'sound/voice/human/female_cough3.ogg', + 'sound/voice/human/female_cough4.ogg', + 'sound/voice/human/female_cough5.ogg', + 'sound/voice/human/female_cough6.ogg', + ) + return pick( + 'sound/voice/human/male_cough1.ogg', + 'sound/voice/human/male_cough2.ogg', + 'sound/voice/human/male_cough3.ogg', + 'sound/voice/human/male_cough4.ogg', + 'sound/voice/human/male_cough5.ogg', + 'sound/voice/human/male_cough6.ogg', + ) + +/datum/species/human/get_cry_sound(mob/living/carbon/human/human) + if(human.physique == FEMALE) + return pick( + 'sound/voice/human/female_cry1.ogg', + 'sound/voice/human/female_cry2.ogg', + ) + return pick( + 'sound/voice/human/male_cry1.ogg', + 'sound/voice/human/male_cry2.ogg', + 'sound/voice/human/male_cry3.ogg', + ) + + +/datum/species/human/get_sneeze_sound(mob/living/carbon/human/human) + if(human.physique == FEMALE) + return 'sound/voice/human/female_sneeze1.ogg' + return 'sound/voice/human/male_sneeze1.ogg' + +/datum/species/human/get_laugh_sound(mob/living/carbon/human/human) + if(!ishuman(human)) + return + if(human.physique == FEMALE) + return 'sound/voice/human/womanlaugh.ogg' + return pick( + 'sound/voice/human/manlaugh1.ogg', + 'sound/voice/human/manlaugh2.ogg', + ) + /datum/species/human/get_species_description() return "Humans are the dominant species in the known galaxy. \ Their kind extend from old Earth to the edges of known space." diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index 3e20d6b1a7750..93167cb689c3e 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -71,6 +71,49 @@ 'sound/voice/lizard/lizard_scream_3.ogg', ) +/datum/species/lizard/get_cough_sound(mob/living/carbon/human/lizard) + if(lizard.gender == FEMALE) + return pick( + 'sound/voice/human/female_cough1.ogg', + 'sound/voice/human/female_cough2.ogg', + 'sound/voice/human/female_cough3.ogg', + 'sound/voice/human/female_cough4.ogg', + 'sound/voice/human/female_cough5.ogg', + 'sound/voice/human/female_cough6.ogg', + ) + return pick( + 'sound/voice/human/male_cough1.ogg', + 'sound/voice/human/male_cough2.ogg', + 'sound/voice/human/male_cough3.ogg', + 'sound/voice/human/male_cough4.ogg', + 'sound/voice/human/male_cough5.ogg', + 'sound/voice/human/male_cough6.ogg', + ) + + +/datum/species/lizard/get_cry_sound(mob/living/carbon/human/lizard) + if(lizard.gender == FEMALE) + return pick( + 'sound/voice/human/female_cry1.ogg', + 'sound/voice/human/female_cry2.ogg', + ) + return pick( + 'sound/voice/human/male_cry1.ogg', + 'sound/voice/human/male_cry2.ogg', + 'sound/voice/human/male_cry3.ogg', + ) + + +/datum/species/lizard/get_sneeze_sound(mob/living/carbon/human/lizard) + if(lizard.gender == FEMALE) + return 'sound/voice/human/female_sneeze1.ogg' + return 'sound/voice/human/male_sneeze1.ogg' + +/datum/species/lizard/get_laugh_sound(mob/living/carbon/human) + if(!istype(human)) + return + return 'sound/voice/lizard/lizard_laugh1.ogg' + /datum/species/lizard/get_physical_attributes() return "Lizardpeople can withstand slightly higher temperatures than most species, but they are very vulnerable to the cold \ and can't regulate their body-temperature internally, making the vacuum of space extremely deadly to them." diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm index 68fb87142f6c9..86a9180ed07e3 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -64,9 +64,53 @@ features["moth_markings"] = pick(GLOB.moth_markings_list) return features -/datum/species/moth/get_scream_sound(mob/living/carbon/human/human) +/datum/species/moth/get_scream_sound(mob/living/carbon/human) return 'sound/voice/moth/scream_moth.ogg' +/datum/species/moth/get_cough_sound(mob/living/carbon/human/moth) + if(moth.gender == FEMALE) + return pick( + 'sound/voice/human/female_cough1.ogg', + 'sound/voice/human/female_cough2.ogg', + 'sound/voice/human/female_cough3.ogg', + 'sound/voice/human/female_cough4.ogg', + 'sound/voice/human/female_cough5.ogg', + 'sound/voice/human/female_cough6.ogg', + ) + return pick( + 'sound/voice/human/male_cough1.ogg', + 'sound/voice/human/male_cough2.ogg', + 'sound/voice/human/male_cough3.ogg', + 'sound/voice/human/male_cough4.ogg', + 'sound/voice/human/male_cough5.ogg', + 'sound/voice/human/male_cough6.ogg', + ) + + +/datum/species/moth/get_cry_sound(mob/living/carbon/human/moth) + if(moth.gender == FEMALE) + return pick( + 'sound/voice/human/female_cry1.ogg', + 'sound/voice/human/female_cry2.ogg', + ) + return pick( + 'sound/voice/human/male_cry1.ogg', + 'sound/voice/human/male_cry2.ogg', + 'sound/voice/human/male_cry3.ogg', + ) + + +/datum/species/moth/get_sneeze_sound(mob/living/carbon/human/moth) + if(moth.gender == FEMALE) + return 'sound/voice/human/female_sneeze1.ogg' + return 'sound/voice/human/male_sneeze1.ogg' + + +/datum/species/moth/get_laugh_sound(mob/living/carbon/human) + if(!istype(human)) + return + return 'sound/voice/moth/moth_laugh1.ogg' + /datum/species/moth/get_physical_attributes() return "Moths have large and fluffy wings, which help them navigate the station if gravity is offline by pushing the air around them. \ Due to that, it isn't of much use out in space. Their eyes are very sensitive." diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index 9e0d70a6c9d05..960e50d77205f 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -67,16 +67,6 @@ var/mob/living/L = user L.Unconscious(40) -/datum/emote/living/cough - key = "cough" - key_third_person = "coughs" - message = "coughs!" - message_mime = "acts out an exaggerated cough!" - emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE | EMOTE_RUNECHAT - -/datum/emote/living/cough/can_run_emote(mob/user, status_check = TRUE , intentional) - return !HAS_TRAIT(user, TRAIT_SOOTHED_THROAT) && ..() - /datum/emote/living/dance key = "dance" key_third_person = "dances" @@ -284,15 +274,11 @@ /datum/emote/living/laugh/can_run_emote(mob/living/user, status_check = TRUE , intentional) return ..() && user.can_speak(allow_mimes = TRUE) -/datum/emote/living/laugh/get_sound(mob/living/user) +/datum/emote/living/laugh/get_sound(mob/living/carbon/user) if(!ishuman(user)) return var/mob/living/carbon/human/human_user = user - if((ishumanbasic(human_user) || isfelinid(human_user)) && !HAS_MIND_TRAIT(human_user, TRAIT_MIMING)) - if(human_user.gender == FEMALE) - return 'sound/voice/human/womanlaugh.ogg' - else - return pick('sound/voice/human/manlaugh1.ogg', 'sound/voice/human/manlaugh2.ogg') + return human_user.dna.species.get_laugh_sound(user) /datum/emote/living/look key = "look" @@ -326,6 +312,38 @@ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5) return ..() +/datum/emote/living/carbon/cry + key = "cry" + key_third_person = "cries" + message = "cries." + emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE + stat_allowed = SOFT_CRIT + mob_type_blacklist_typecache = list(/mob/living/carbon/human) //Humans get specialized cry emote with sound and animation. + +/datum/emote/living/sneeze + key = "sneeze" + key_third_person = "sneezes" + message = "sneezes." + emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE + mob_type_blacklist_typecache = list(/mob/living/carbon/human) //Humans get specialized sneeze emote with sound. + +/datum/emote/living/carbon/human/glasses/run_emote(mob/user, params, type_override, intentional) + . = ..() + var/image/emote_animation = image('icons/mob/human/emote_visuals.dmi', user, "glasses") + flick_overlay_global(emote_animation, GLOB.clients, 1.6 SECONDS) + +/datum/emote/living/carbon/cough + key = "cough" + key_third_person = "coughs" + message = "coughs!" + message_mime = "acts out an exaggerated cough!" + emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE | EMOTE_RUNECHAT + mob_type_blacklist_typecache = list(/mob/living/carbon/human) //Humans get specialized cough emote with sound. + +/datum/emote/living/cough/can_run_emote(mob/user, status_check = TRUE , intentional) + return !HAS_TRAIT(user, TRAIT_SOOTHED_THROAT) && ..() + + /datum/emote/living/pout key = "pout" key_third_person = "pouts" @@ -401,13 +419,6 @@ key_third_person = "smiles" message = "smiles." -/datum/emote/living/sneeze - key = "sneeze" - key_third_person = "sneezes" - message = "sneezes." - message_mime = "acts out an exaggerated silent sneeze." - emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE - /datum/emote/living/smug key = "smug" key_third_person = "smugs" @@ -730,3 +741,17 @@ message = "says a swear word!" message_mime = "makes a rude gesture!" emote_type = EMOTE_AUDIBLE + +/datum/emote/living/carbon/whistle + key = "whistle" + key_third_person = "whistles" + message = "whistles." + message_mime = "whistles silently!" + audio_cooldown = 5 SECONDS + vary = TRUE + emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE + +/datum/emote/living/carbon/whistle/get_sound(mob/living/user) + if(!istype(user)) + return + return 'sound/voice/human/whistle1.ogg' diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index ec204dbc82b93..944a00af9b63e 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -204,6 +204,7 @@ pa_system = new(src, automated_announcements = automated_announcements) pa_system.Grant(src) + RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(attempt_access)) /mob/living/simple_animal/bot/Destroy() GLOB.bots_list -= src @@ -951,13 +952,13 @@ Pass a positive integer as an argument to override a bot's default speed. calc_summon_path() tries = 0 -/mob/living/simple_animal/bot/Bump(atom/A) //Leave no door unopened! - . = ..() - if((istype(A, /obj/machinery/door/airlock) || istype(A, /obj/machinery/door/window)) && (!isnull(access_card))) - var/obj/machinery/door/D = A - if(D.check_access(access_card)) - D.open() - frustration = 0 +/mob/living/simple_animal/bot/proc/attempt_access(mob/bot, obj/door_attempt) + SIGNAL_HANDLER + + if(door_attempt.check_access(access_card)) + frustration = 0 + return ACCESS_ALLOWED + return ACCESS_DISALLOWED /mob/living/simple_animal/bot/ui_data(mob/user) var/list/data = list() diff --git a/code/modules/mod/mod_core.dm b/code/modules/mod/mod_core.dm index 0d71db68aac2c..e62be77fe557e 100644 --- a/code/modules/mod/mod_core.dm +++ b/code/modules/mod/mod_core.dm @@ -382,8 +382,8 @@ light_power = 1.5 // Slightly better than the normal plasma core. // Not super sure if this should just be the same, but will see. - maxcharge = 15000 - charge = 15000 + maxcharge = 15 * STANDARD_CELL_CHARGE + charge = 15 * STANDARD_CELL_CHARGE /// The mob to be spawned by the core var/mob/living/spawned_mob_type = /mob/living/basic/butterfly/lavaland/temporary /// Max number of mobs it can spawn diff --git a/code/modules/power/apc/apc_main.dm b/code/modules/power/apc/apc_main.dm index 0baf800fa1037..f9ef22e300240 100644 --- a/code/modules/power/apc/apc_main.dm +++ b/code/modules/power/apc/apc_main.dm @@ -17,6 +17,7 @@ damage_deflection = 10 resistance_flags = FIRE_PROOF interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON + interaction_flags_click = ALLOW_SILICON_REACH processing_flags = START_PROCESSING_MANUALLY ///Range of the light emitted when on diff --git a/code/modules/projectiles/guns/ballistic/rifle.dm b/code/modules/projectiles/guns/ballistic/rifle.dm index 966dd2caf32a3..ac988c2541bc1 100644 --- a/code/modules/projectiles/guns/ballistic/rifle.dm +++ b/code/modules/projectiles/guns/ballistic/rifle.dm @@ -219,6 +219,10 @@ bolt_locked = FALSE update_appearance() +/obj/item/gun/ballistic/rifle/rebarxbow/shoot_live_shot(mob/living/user) + ..() + rack() + /obj/item/gun/ballistic/rifle/rebarxbow/can_shoot() if (bolt_locked) return FALSE diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index 72e670ef0065b..4524b3f0567ad 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -1,3 +1,5 @@ +#define MAX_CONTAINER_PRINT_AMOUNT 50 + /obj/machinery/chem_master name = "ChemMaster 3000" desc = "Used to separate chemicals and distribute them in a variety of forms." @@ -26,8 +28,8 @@ var/printing_progress /// Number of containers to be printed var/printing_total - /// The amount of containers that can be printed in 1 cycle - var/printing_amount = 1 + /// The time it takes to print a container + var/printing_speed = 0.75 SECONDS /obj/machinery/chem_master/Initialize(mapload) create_reagents(100) @@ -76,7 +78,7 @@ /obj/machinery/chem_master/examine(mob/user) . = ..() if(in_range(user, src) || isobserver(user)) - . += span_notice("The status display reads:
    Reagent buffer capacity: [reagents.maximum_volume] units.
    Number of containers printed per cycle [printing_amount].") + . += span_notice("The status display reads:
    Reagent buffer capacity: [reagents.maximum_volume] units.
    Printing speed: [0.75 SECONDS / printing_speed * 100]%.") if(!QDELETED(beaker)) . += span_notice("[beaker] of [beaker.reagents.maximum_volume]u capacity inserted") . += span_notice("Right click with empty hand to remove beaker") @@ -149,10 +151,11 @@ for(var/obj/item/reagent_containers/cup/beaker/beaker in component_parts) reagents.maximum_volume += beaker.reagents.maximum_volume - printing_amount = 0 + //Servo tier determines printing speed + printing_speed = 1 SECONDS for(var/datum/stock_part/servo/servo in component_parts) - printing_amount += servo.tier * 12.5 - printing_amount = min(50, ROUND_UP(printing_amount)) + printing_speed -= servo.tier * 0.25 SECONDS + printing_speed = max(printing_speed, 0.25 SECONDS) ///Return a map of category->list of containers this machine can print /obj/machinery/chem_master/proc/load_printable_containers() @@ -264,6 +267,7 @@ /obj/machinery/chem_master/ui_static_data(mob/user) var/list/data = list() + data["maxPrintable"] = MAX_CONTAINER_PRINT_AMOUNT data["categories"] = list() for(var/category in printable_containers) //make the category @@ -293,7 +297,6 @@ .["isPrinting"] = is_printing .["printingProgress"] = printing_progress .["printingTotal"] = printing_total - .["maxPrintable"] = printing_amount //contents of source beaker var/list/beaker_data = null @@ -469,7 +472,7 @@ item_count = text2num(item_count) if(isnull(item_count) || item_count <= 0) return FALSE - item_count = min(item_count, printing_amount) + item_count = min(item_count, MAX_CONTAINER_PRINT_AMOUNT) var/volume_in_each = round(reagents.total_volume / item_count, CHEMICAL_VOLUME_ROUNDING) // Generate item name @@ -529,7 +532,7 @@ //print more items item_count -- if(item_count > 0) - addtimer(CALLBACK(src, PROC_REF(create_containers), user, item_count, item_name, volume_in_each), 0.75 SECONDS) + addtimer(CALLBACK(src, PROC_REF(create_containers), user, item_count, item_name, volume_in_each), printing_speed) else is_printing = FALSE update_appearance(UPDATE_OVERLAYS) @@ -544,3 +547,5 @@ if(!length(containers)) containers = list(CAT_CONDIMENTS = GLOB.reagent_containers[CAT_CONDIMENTS]) return containers + +#undef MAX_CONTAINER_PRINT_AMOUNT diff --git a/code/modules/surgery/bone_mending.dm b/code/modules/surgery/bone_mending.dm index 87fc3db0af2c4..ae4ef6e433094 100644 --- a/code/modules/surgery/bone_mending.dm +++ b/code/modules/surgery/bone_mending.dm @@ -48,7 +48,7 @@ /datum/surgery_step/repair_bone_hairline name = "repair hairline fracture (bonesetter/bone gel/tape)" implements = list( - /obj/item/bonesetter = 100, + TOOL_BONESET = 100, /obj/item/stack/medical/bone_gel = 100, /obj/item/stack/sticky_tape/surgical = 100, /obj/item/stack/sticky_tape/super = 50, @@ -98,7 +98,7 @@ /datum/surgery_step/reset_compound_fracture name = "reset bone (bonesetter)" implements = list( - /obj/item/bonesetter = 100, + TOOL_BONESET = 100, /obj/item/stack/sticky_tape/surgical = 60, /obj/item/stack/sticky_tape/super = 40, /obj/item/stack/sticky_tape = 20) diff --git a/code/modules/tgui_input/list.dm b/code/modules/tgui_input/list.dm index 174f16fc7b57c..22c6d48edfc5a 100644 --- a/code/modules/tgui_input/list.dm +++ b/code/modules/tgui_input/list.dm @@ -111,7 +111,7 @@ /datum/tgui_list_input/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, "ListInputModal") + ui = new(user, src, "ListInputWindow") ui.open() /datum/tgui_list_input/ui_close(mob/user) diff --git a/code/modules/unit_tests/organ_set_bonus.dm b/code/modules/unit_tests/organ_set_bonus.dm index a9e9a8805f9c4..967803e223f17 100644 --- a/code/modules/unit_tests/organ_set_bonus.dm +++ b/code/modules/unit_tests/organ_set_bonus.dm @@ -22,7 +22,7 @@ /datum/infuser_entry/fly, )) // Fetch the globally instantiated DNA Infuser entries. - for(var/datum/infuser_entry/infuser_entry as anything in GLOB.infuser_entries) + for(var/datum/infuser_entry/infuser_entry as anything in flatten_list(GLOB.infuser_entries)) var/output_organs = infuser_entry.output_organs var/mob/living/carbon/human/lab_rat = allocate(/mob/living/carbon/human/consistent) var/list/obj/item/organ/inserted_organs = list() diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index fb5bcdf7e117f..332005070d5b6 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -222,8 +222,6 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests()) var/list/returnable_list = list() // The following are just generic, singular types. returnable_list = list( - //this is somehow a subtype of /atom/movable, because of its purpose... - /image/appearance, //Never meant to be created, errors out the ass for mobcode reasons /mob/living/carbon, //And another diff --git a/config/config.txt b/config/config.txt index 16ebcc2d322ce..4cad73b932e32 100644 --- a/config/config.txt +++ b/config/config.txt @@ -144,16 +144,26 @@ GUEST_BAN ## IPINTEL: ## This allows you to detect likely proxies by checking ips against getipintel.net -## Rating to warn at: (0.9 is good, 1 is 100% likely to be a spammer/proxy, 0.8 is 80%, etc) anything equal to or higher then this number triggers an admin warning -#IPINTEL_RATING_BAD 0.9 +## Rating to warn at: (0.8 is good, 1 is 100% likely to be a spammer/proxy, 0.8 is 80%, etc) anything equal to or higher then this number triggers an admin warning +#IPINTEL_RATING_BAD 0.8 ## Contact email, (required to use the service, leaving blank or default disables IPINTEL) #IPINTEL_EMAIL ch@nge.me -## How long to save good matches (ipintel rate limits to 15 per minute and 500 per day. so this shouldn't be too low, getipintel.net suggests 6 hours, time is in hours) (Your ip will get banned if you go over 500 a day too many times) -#IPINTEL_SAVE_GOOD 12 -## How long to save bad matches (these numbers can change as ips change hands, best not to save these for too long in case somebody gets a new ip used by a spammer/proxy before.) -#IPINTEL_SAVE_BAD 3 -## Domain name to query (leave commented out for the default, only needed if you pay getipintel.net for more querys) -#IPINTEL_DOMAIN check.getipintel.net +## Query base, if you pay for more queries this is what you want to change. +#IPINTEL_BASE check.getipintel.net +## Maximum number of queries in a minute +#IPINTEL_MAX_QUERY_MINUTE 15 +## Maximum number of queries in a day +#IPINTEL_MAX_QUERY_DAY 500 +## Whether clients which cannot be checked due to a rate limit will be denied +#IPINTEL_REJECT_RATE_LIMITED +## Whether clients which are flagged as a VPN will be denied +IPINTEL_REJECT_BAD +## Whether clients which cannot be checked due to an error of some form will be denied +#IPINTEL_REJECT_UNKNOWN +## How long to store results in the cache before they must be retrieved again. IN DAYS. +#IPINTEL_CACHE_LENGTH 7 +## How many minutes of living playtime to be automatically exempt from IPIntel. 0 for never. +#IPINTEL_EXEMPT_PLAYTIME_LIVING 0 ## Uncomment to allow web client connections #ALLOW_WEBCLIENT diff --git a/html/changelogs/AutoChangeLog-pr-82683.yml b/html/changelogs/AutoChangeLog-pr-82683.yml new file mode 100644 index 0000000000000..d63fc304d6d9d --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82683.yml @@ -0,0 +1,4 @@ +author: "ZephyrTFA" +delete-after: True +changes: + - admin: "The return of IPIntel" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82732.yml b/html/changelogs/AutoChangeLog-pr-82732.yml deleted file mode 100644 index 8e03debbfc081..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82732.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Momo8289" -delete-after: True -changes: - - rscadd: "The crew monitor now has sorting options and a search bar." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82824.yml b/html/changelogs/AutoChangeLog-pr-82824.yml deleted file mode 100644 index 38d01cac37b6a..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82824.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "necromanceranne" -delete-after: True -changes: - - balance: "Longfall modules no logner stun you when they activate." - - balance: "Falling from a height greater than one z-level while using the longfall module will still stagger you." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82852.yml b/html/changelogs/AutoChangeLog-pr-82852.yml deleted file mode 100644 index 2cd68a82732e3..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82852.yml +++ /dev/null @@ -1,7 +0,0 @@ -author: "Fikou" -delete-after: True -changes: - - bugfix: "you can no longer take off someones glasses or mask through atmos hardhat" - - bugfix: "once you adjust a welding helmet or something it no longer makes your cigarette or sunglasses invisible" - - bugfix: "welding gas mask works once again" - - bugfix: "quick equips dont drop the item if you cant equip it" \ No newline at end of file diff --git a/html/changelogs/archive/2024-04.yml b/html/changelogs/archive/2024-04.yml index 53106bf1f9188..c96b71a34d5b6 100644 --- a/html/changelogs/archive/2024-04.yml +++ b/html/changelogs/archive/2024-04.yml @@ -795,3 +795,49 @@ - rscadd: Adds military spear, shortsword, boarding axe, kite shields - rscadd: Adds warlord and crude armor - rscadd: Adds medieval shuttle (made by striders18) +2024-04-25: + Fikou: + - bugfix: brimbeams no longer make a cog appear above the mob + - bugfix: you can no longer take off someones glasses or mask through atmos hardhat + - bugfix: once you adjust a welding helmet or something it no longer makes your + cigarette or sunglasses invisible + - bugfix: welding gas mask works once again + - bugfix: quick equips dont drop the item if you cant equip it + Momo8289: + - rscadd: The crew monitor now has sorting options and a search bar. + grungussuss: + - rscadd: whistle emote + - refactor: Refactored how laugh, sneeze, cough and cry sound is called in the code, + report strange behavior with these emotes. + - sound: added sounds for whistle, cry, cough, sneeze, laugh for moths and lizards + emotes + necromanceranne: + - balance: Longfall modules no logner stun you when they activate. + - balance: Falling from a height greater than one z-level while using the longfall + module will still stagger you. +2024-04-26: + Ben10Omintrix: + - bugfix: all bots have their normal accesses restored + Bisar: + - bugfix: AI controlled monkeys are no longer catatonic, and they have primal eyes + again when turned into humans. + - spellcheck: Noticable organs now have more modular grammar, and their current + grammar is fixed. + - refactor: Refactored the code for displaying the messages for noticable organs. + - config: Added a documented define of all our pronouns + KingkumaArt: + - bugfix: The rebar crossbows now properly loosen their bowstring upon firing. + LT3: + - balance: ChemMaster can again print a maximum of 50 pills/patches per run + - balance: Higher tier servos increase ChemMaster printing speed + Singul0: + - bugfix: fixed cyborg bonesetter not working for compound fractures + - bugfix: butchering not disabling in cyborg omnitool + - bugfix: fixes a bug where if you select the omnitool it would be stuck in surgery + initiator mode + - bugfix: cautery in off hand for cyborg omnitools not working + jlsnow301: + - bugfix: Restored silicon alt-clicking capability + necromanceranne: + - bugfix: The plasma flower modsuit core now actually contains a reasonable quantity + of power. diff --git a/sound/attributions.txt b/sound/attributions.txt index be8df17c53577..053051a89ec83 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -147,3 +147,27 @@ https://freesound.org/people/gynation/sounds/82378/ nightmare_poof.ogg and nightmare_reappear.ogg are comprised of breath_01.wav by Nightflame (CC0) and slide-click.wav by Qat (CC0) https://freesound.org/people/Qat/sounds/108332/ https://freesound.org/people/Nightflame/sounds/368615/ + +modified by grungussuss: +male_sneeze1.ogg: https://freesound.org/people/InspectorJ/sounds/352177/ , license: CC BY 4.0 DEED +male_cry1.ogg: https://freesound.org/people/jacobmathiassen/sounds/254869/ , license: CC BY 4.0 DEED +male_cry2.ogg: https://freesound.org/people/scottemoil/sounds/263776/ , license: CC0 1.0 DEED +male_cry3.ogg: https://freesound.org/people/qubodup/sounds/200428/ , license: CC BY 4.0 DEED +female_cry1.ogg: https://freesound.org/people/Luzanne0/sounds/445299/ , license: CC BY-NC 3.0 DEED +female_cry2.ogg: https://freesound.org/people/Idalize/sounds/408211/ , license: CC BY-NC 4.0 DEED +female_cough1.ogg: https://freesound.org/people/OwlStorm/sounds/151213/ , license: CC0 1.0 DEED +female_cough2.ogg: https://freesound.org/people/thatkellytrna/sounds/425777/ , license: CC0 1.0 DEED +female_cough3.ogg: https://freesound.org/people/Luzanne0/sounds/445293/ , license: CC BY-NC 3.0 DEED +female_cough4.ogg: https://freesound.org/people/Luzanne0/sounds/445293/ , license: CC BY-NC 3.0 DEED +female_cough5.ogg: https://freesound.org/people/DarkNightPrincess/sounds/625322/ , license: CC0 1.0 DEED +female_cough6.ogg: https://freesound.org/people/drotzruhn/sounds/405206/ , license: CC BY 4.0 DEED +male_cough1.ogg: https://freesound.org/people/Harris85/sounds/208761/ , license: CC0 1.0 DEED +male_cough2.ogg: https://freesound.org/people/midwestdocumentary/sounds/722622/ , license: CC0 1.0 DEED +male_cough3.ogg: https://freesound.org/people/hadescolossus/sounds/701162/ , license: CC0 1.0 DEED +male_cough4.ogg: https://freesound.org/people/SoundDesignForYou/sounds/646652/ , license: CC0 1.0 DEED +male_cough5.ogg: https://freesound.org/people/SoundDesignForYou/sounds/646654/ , license: CC0 1.0 DEED +male_cough6.ogg: https://freesound.org/people/SoundDesignForYou/sounds/646656/ , license: CC0 1.0 DEED +lizard_laugh1.ogg: https://youtu.be/I7CX0AS8RNI , License: CC-BY-3.0 +moth_laugh1.ogg: https://github.com/BeeStation/BeeStation-Hornet/blob/11ba3fa04105c93dd96a63ad4afaef4b20c02d0d/sound/emotes/ , license: CC-BY-SA-3.0 +whistle1.ogg: https://freesound.org/people/taure/sounds/411638/ , license: CC0 1.0 DEED + diff --git a/sound/voice/human/female_cough1.ogg b/sound/voice/human/female_cough1.ogg new file mode 100644 index 0000000000000..53af74368c3bf Binary files /dev/null and b/sound/voice/human/female_cough1.ogg differ diff --git a/sound/voice/human/female_cough2.ogg b/sound/voice/human/female_cough2.ogg new file mode 100644 index 0000000000000..eb3551a31fecb Binary files /dev/null and b/sound/voice/human/female_cough2.ogg differ diff --git a/sound/voice/human/female_cough3.ogg b/sound/voice/human/female_cough3.ogg new file mode 100644 index 0000000000000..a075963d3b46d Binary files /dev/null and b/sound/voice/human/female_cough3.ogg differ diff --git a/sound/voice/human/female_cough4.ogg b/sound/voice/human/female_cough4.ogg new file mode 100644 index 0000000000000..0136ea42ccff2 Binary files /dev/null and b/sound/voice/human/female_cough4.ogg differ diff --git a/sound/voice/human/female_cough5.ogg b/sound/voice/human/female_cough5.ogg new file mode 100644 index 0000000000000..7562661bd4853 Binary files /dev/null and b/sound/voice/human/female_cough5.ogg differ diff --git a/sound/voice/human/female_cough6.ogg b/sound/voice/human/female_cough6.ogg new file mode 100644 index 0000000000000..62938b7b761af Binary files /dev/null and b/sound/voice/human/female_cough6.ogg differ diff --git a/sound/voice/human/female_cry1.ogg b/sound/voice/human/female_cry1.ogg new file mode 100644 index 0000000000000..f4f7386417194 Binary files /dev/null and b/sound/voice/human/female_cry1.ogg differ diff --git a/sound/voice/human/female_cry2.ogg b/sound/voice/human/female_cry2.ogg new file mode 100644 index 0000000000000..e81e93b5c3f83 Binary files /dev/null and b/sound/voice/human/female_cry2.ogg differ diff --git a/sound/voice/human/female_sneeze1.ogg b/sound/voice/human/female_sneeze1.ogg new file mode 100644 index 0000000000000..8fe020a8c7e8a Binary files /dev/null and b/sound/voice/human/female_sneeze1.ogg differ diff --git a/sound/voice/human/male_cough1.ogg b/sound/voice/human/male_cough1.ogg new file mode 100644 index 0000000000000..f553bd855ae94 Binary files /dev/null and b/sound/voice/human/male_cough1.ogg differ diff --git a/sound/voice/human/male_cough2.ogg b/sound/voice/human/male_cough2.ogg new file mode 100644 index 0000000000000..3dcc880175fb5 Binary files /dev/null and b/sound/voice/human/male_cough2.ogg differ diff --git a/sound/voice/human/male_cough3.ogg b/sound/voice/human/male_cough3.ogg new file mode 100644 index 0000000000000..a87ba9cc4c730 Binary files /dev/null and b/sound/voice/human/male_cough3.ogg differ diff --git a/sound/voice/human/male_cough4.ogg b/sound/voice/human/male_cough4.ogg new file mode 100644 index 0000000000000..38052dc78711b Binary files /dev/null and b/sound/voice/human/male_cough4.ogg differ diff --git a/sound/voice/human/male_cough5.ogg b/sound/voice/human/male_cough5.ogg new file mode 100644 index 0000000000000..5a1b836775dbf Binary files /dev/null and b/sound/voice/human/male_cough5.ogg differ diff --git a/sound/voice/human/male_cough6.ogg b/sound/voice/human/male_cough6.ogg new file mode 100644 index 0000000000000..cfe4e08655b94 Binary files /dev/null and b/sound/voice/human/male_cough6.ogg differ diff --git a/sound/voice/human/male_cry1.ogg b/sound/voice/human/male_cry1.ogg new file mode 100644 index 0000000000000..50ffd0cf72a7d Binary files /dev/null and b/sound/voice/human/male_cry1.ogg differ diff --git a/sound/voice/human/male_cry2.ogg b/sound/voice/human/male_cry2.ogg new file mode 100644 index 0000000000000..8d35a4d527669 Binary files /dev/null and b/sound/voice/human/male_cry2.ogg differ diff --git a/sound/voice/human/male_cry3.ogg b/sound/voice/human/male_cry3.ogg new file mode 100644 index 0000000000000..58f39b5fff134 Binary files /dev/null and b/sound/voice/human/male_cry3.ogg differ diff --git a/sound/voice/human/male_sneeze1.ogg b/sound/voice/human/male_sneeze1.ogg new file mode 100644 index 0000000000000..1c7e8f42534d8 Binary files /dev/null and b/sound/voice/human/male_sneeze1.ogg differ diff --git a/sound/voice/human/whistle1.ogg b/sound/voice/human/whistle1.ogg new file mode 100644 index 0000000000000..4109260659723 Binary files /dev/null and b/sound/voice/human/whistle1.ogg differ diff --git a/sound/voice/lizard/lizard_laugh1.ogg b/sound/voice/lizard/lizard_laugh1.ogg new file mode 100644 index 0000000000000..b2c02e6d2fcb0 Binary files /dev/null and b/sound/voice/lizard/lizard_laugh1.ogg differ diff --git a/sound/voice/moth/moth_laugh1.ogg b/sound/voice/moth/moth_laugh1.ogg new file mode 100644 index 0000000000000..391d6c5aefe2c Binary files /dev/null and b/sound/voice/moth/moth_laugh1.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 93cbf7bbc1c3e..5c1cad10da3d6 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -119,6 +119,7 @@ #include "code\__DEFINES\instruments.dm" #include "code\__DEFINES\interaction_flags.dm" #include "code\__DEFINES\inventory.dm" +#include "code\__DEFINES\ipintel.dm" #include "code\__DEFINES\is_helpers.dm" #include "code\__DEFINES\jobs.dm" #include "code\__DEFINES\keybinding.dm" @@ -182,6 +183,7 @@ #include "code\__DEFINES\procpath.dm" #include "code\__DEFINES\profile.dm" #include "code\__DEFINES\projectiles.dm" +#include "code\__DEFINES\pronouns.dm" #include "code\__DEFINES\qdel.dm" #include "code\__DEFINES\quirks.dm" #include "code\__DEFINES\radiation.dm" @@ -2087,6 +2089,7 @@ #include "code\game\machinery\computer\records\records.dm" #include "code\game\machinery\computer\records\security.dm" #include "code\game\machinery\dna_infuser\dna_infuser.dm" +#include "code\game\machinery\dna_infuser\dna_infusion.dm" #include "code\game\machinery\dna_infuser\infuser_book.dm" #include "code\game\machinery\dna_infuser\infuser_entry.dm" #include "code\game\machinery\dna_infuser\infuser_entries\infuser_tier_one_entries.dm" @@ -2797,7 +2800,6 @@ #include "code\modules\admin\fun_balloon.dm" #include "code\modules\admin\greyscale_modify_menu.dm" #include "code\modules\admin\holder2.dm" -#include "code\modules\admin\ipintel.dm" #include "code\modules\admin\IsBanned.dm" #include "code\modules\admin\known_alts.dm" #include "code\modules\admin\outfit_editor.dm" diff --git a/tgui/packages/tgui/interfaces/ListInputModal.tsx b/tgui/packages/tgui/interfaces/ListInputWindow/ListInputModal.tsx similarity index 57% rename from tgui/packages/tgui/interfaces/ListInputModal.tsx rename to tgui/packages/tgui/interfaces/ListInputWindow/ListInputModal.tsx index 8695ac842f72e..a56363f231097 100644 --- a/tgui/packages/tgui/interfaces/ListInputModal.tsx +++ b/tgui/packages/tgui/interfaces/ListInputWindow/ListInputModal.tsx @@ -7,35 +7,26 @@ import { KEY_ESCAPE, KEY_UP, KEY_Z, -} from '../../common/keycodes'; -import { useBackend } from '../backend'; -import { Autofocus, Button, Input, Section, Stack } from '../components'; -import { Window } from '../layouts'; -import { InputButtons } from './common/InputButtons'; -import { Loader } from './common/Loader'; +} from '../../../common/keycodes'; +import { useBackend } from '../../backend'; +import { Autofocus, Button, Input, Section, Stack } from '../../components'; +import { InputButtons } from '../common/InputButtons'; -type ListInputData = { - init_value: string; +type ListInputModalProps = { items: string[]; - large_buttons: boolean; + default_item: string; message: string; - timeout: number; - title: string; + on_selected: (entry: string) => void; + on_cancel: () => void; }; -export const ListInputModal = (props) => { - const { act, data } = useBackend(); - const { - items = [], - message = '', - init_value, - large_buttons, - timeout, - title, - } = data; - const [selected, setSelected] = useState(items.indexOf(init_value)); +export const ListInputModal = (props: ListInputModalProps) => { + const { items = [], default_item, message, on_selected, on_cancel } = props; + + const [selected, setSelected] = useState(items.indexOf(default_item)); const [searchBarVisible, setSearchBarVisible] = useState(items.length > 9); const [searchQuery, setSearchQuery] = useState(''); + // User presses up or down on keyboard // Simulates clicking an item const onArrowKey = (key: number) => { @@ -99,82 +90,77 @@ export const ListInputModal = (props) => { const filteredItems = items.filter((item) => item?.toLowerCase().includes(searchQuery.toLowerCase()), ); - // Dynamically changes the window height based on the message. - const windowHeight = - 325 + Math.ceil(message.length / 3) + (large_buttons ? 5 : 0); // Grabs the cursor when no search bar is visible. if (!searchBarVisible) { setTimeout(() => document!.getElementById(selected.toString())?.focus(), 1); } return ( - - {timeout && } - { - const keyCode = window.event ? event.which : event.keyCode; - if (keyCode === KEY_DOWN || keyCode === KEY_UP) { - event.preventDefault(); - onArrowKey(keyCode); - } - if (keyCode === KEY_ENTER) { - event.preventDefault(); - act('submit', { entry: filteredItems[selected] }); - } - if (!searchBarVisible && keyCode >= KEY_A && keyCode <= KEY_Z) { - event.preventDefault(); - onLetterSearch(keyCode); - } - if (keyCode === KEY_ESCAPE) { - event.preventDefault(); - act('cancel'); - } - }} - > -
    onSearchBarToggle()} - /> +
    { + const keyCode = window.event ? event.which : event.keyCode; + if (keyCode === KEY_DOWN || keyCode === KEY_UP) { + event.preventDefault(); + onArrowKey(keyCode); + } + if (keyCode === KEY_ENTER) { + event.preventDefault(); + on_selected(filteredItems[selected]); + } + if (!searchBarVisible && keyCode >= KEY_A && keyCode <= KEY_Z) { + event.preventDefault(); + onLetterSearch(keyCode); + } + if (keyCode === KEY_ESCAPE) { + event.preventDefault(); + on_cancel(); + } + }} + buttons={ +
    - - + tooltipPosition="left" + onClick={() => onSearchBarToggle()} + /> + } + className="ListInput__Section" + fill + title={message} + > + + + + + {searchBarVisible && ( + + )} + + on_selected(filteredItems[selected])} + on_cancel={on_cancel} + /> + + +
    ); }; @@ -183,7 +169,7 @@ export const ListInputModal = (props) => { * If a search query is provided, filters the items. */ const ListDisplay = (props) => { - const { act } = useBackend(); + const { act } = useBackend(); const { filteredItems, onClick, onFocusSearch, searchBarVisible, selected } = props; @@ -227,7 +213,7 @@ const ListDisplay = (props) => { * Closing the bar defaults input to an empty string. */ const SearchBar = (props) => { - const { act } = useBackend(); + const { act } = useBackend(); const { filteredItems, onSearch, searchQuery, selected } = props; return ( diff --git a/tgui/packages/tgui/interfaces/ListInputWindow/index.tsx b/tgui/packages/tgui/interfaces/ListInputWindow/index.tsx new file mode 100644 index 0000000000000..29355ff5d213f --- /dev/null +++ b/tgui/packages/tgui/interfaces/ListInputWindow/index.tsx @@ -0,0 +1,44 @@ +import { useBackend } from '../../backend'; +import { Window } from '../../layouts'; +import { Loader } from '../common/Loader'; +import { ListInputModal } from './ListInputModal'; + +type ListInputData = { + init_value: string; + items: string[]; + large_buttons: boolean; + message: string; + timeout: number; + title: string; +}; + +export const ListInputWindow = () => { + const { act, data } = useBackend(); + const { + items = [], + message = '', + init_value, + large_buttons, + timeout, + title, + } = data; + + // Dynamically changes the window height based on the message. + const windowHeight = + 325 + Math.ceil(message.length / 3) + (large_buttons ? 5 : 0); + + return ( + + {timeout && } + + act('submit', { entry })} + on_cancel={() => act('cancel')} + /> + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/common/InputButtons.tsx b/tgui/packages/tgui/interfaces/common/InputButtons.tsx index aad3d92f081fb..aa74ff1fdc017 100644 --- a/tgui/packages/tgui/interfaces/common/InputButtons.tsx +++ b/tgui/packages/tgui/interfaces/common/InputButtons.tsx @@ -8,19 +8,36 @@ type InputButtonsData = { type InputButtonsProps = { input: string | number | string[]; + on_submit?: () => void; + on_cancel?: () => void; message?: string; }; export const InputButtons = (props: InputButtonsProps) => { const { act, data } = useBackend(); const { large_buttons, swapped_buttons } = data; - const { input, message } = props; + const { input, message, on_submit, on_cancel } = props; + + let on_submit_actual = on_submit; + if (!on_submit_actual) { + on_submit_actual = () => { + act('submit', { entry: input }); + }; + } + + let on_cancel_actual = on_cancel; + if (!on_cancel_actual) { + on_cancel_actual = () => { + act('cancel'); + }; + } + const submitButton = (