Files
Bubberstation/code/modules/hydroponics/hydroponics.dm
SpaceLoveSs13 ba5c112a86 Huge Mirror fixes (#27488)
* Fixes incorrect operator usage in mecha code (#82570)

## About The Pull Request

I completely screwed up and told the original PR author of #82415
(9922d2f237) to use the `XOR` operator
instead of the `OR` operator (I wasn't thinking right for some reason
when I was reading the ref), anyways this PR just fixes that because I
misled the contributor into doing something that wasn't correct and
actually would BREAK functionality instead.

* Fixes TGUI debugging tools (#82569)

This project doesn't interfere with the game logic and aims to fix
multiple debugging features that are currently broken. Unfortunately,
kitchen sink and debug layout became broken after migration to Redux.
This PR aims to fix those features.

* Removes unused code for HTML UIs (#82589)

## About The Pull Request

This is the final PR for https://hackmd.io/XLt5MoRvRxuhFbwtk4VAUA that
I've been slowly inching towards the past few months.

This removes ``updateDialog``, ``updateUsrDialog``, ``IN_USE``,
``INTERACT_MACHINE_SET_MACHINE``, and everything surrounding it. Also
fixes advanced camera consoles not booting you off when you're moved out
of reach.

We called ``check_eye`` on mob life whenever they had their machine var
set, but their machine var would never be set to anything that actually
used it, which I found to be a little funny but was also probably my
fault.

## Why It's Good For The Game

This is poor and unmaintained code used for HTML UIs that we no longer
need thanks to TGUI, we should get rid of it to encourage the use of
TGUI in the future instead.

## Changelog


🆑
fix: Advanced camera consoles now boots you off when you're moved out of
reach.
/🆑

* Fixes a variety of input stalling exploits (#82577)

## About The Pull Request

Fixes the following input stalling exploits (maybe missed some): 

- Changing GPS tag 
- Setting teleporter destination
- Request Console Reply
- Various AI law board interactions
- Note, I used `is_holding` but technically this means these fail with
telekinesis. I can swap them to `can_perform_action(...)`, which allows
TK, but I noticed some places explicitly deny TK interactions with Ai
law boards. Not sure which is preferred.
- Borg Rename Board
- Plumbing Machines and Ducts
- APCs and SMES terminal placements
- Stargazers Telepathy
- Go Go Gadget Hat

## Changelog

🆑 Melbert
fix: You can't change the GPS tag of something unless you can actually
use the GPS
fix: You can't set the teleporter to a location unless you can actually
use the teleporter
fix: You can't reply to request console requests unless you can actually
use the console
fix: You can't update AI lawboards unless you're actually holding them 
fix: You can't update a borg rename board unless you're actually holding
it
fix: You can't mess with plumbing machines unless you can actually use
them
fix: You can't recolor / relayer ducts unless you're actually holding
them
fix: You can't magically wire APCs and SMESs unless you're right by them
fix: You can't use Stargazer Telepathy on people who you can't see
fix: You can't configure the Inspector Hat unless you can actually use
it
/🆑

* [NO GBP] Power outage operation fixes for chem master (#82591)

## About The Pull Request
- If the chem master runs out of power mid printing, it will properly
stop the printing process and its animation
- When transferring reagents it correctly checks if we have enough power
without forcing it

## Changelog
🆑
fix: chem master properly shuts down if it loses power mid printing and
won't transfer reagents for the same
/🆑

* Refactor renaming UNIQUE_RENAME items from the pen to an element (#82491)

## About The Pull Request

So a bit ago someone in code_general wanted to make plushies renamable,
but learnt that just adding the `UNIQUE_RENAME` flag wouldn't work as
pens would murder the plushie and only THEN let you rename it. I noted
refactoring both pens and plushies to use the new
`item_interaction(...)` procs would Just Solve This, but, well, they
didn't really have any coding experience.

But, hey, renaming being hardcoded to the pens has annoyed me ever since
I laid my eyes upon the hot mess that is paperwork code.
So here we are!

### We're making it an element.

There's not really much to this, this is mostly the same code but moved
to an element and with some minor cleanups.

First, we move it all from `/obj/item/pen` to a new element we called
`/datum/element/tool_renaming`. With this, instead of having it proc on
`/obj/item/pen/afterattack(...)`, we register it to proc on the
`COMSIG_ITEM_INTERACTING_WITH_ATOM` signal.

6e36ed9840/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm (L59-L62)
Secondly, we realize the code is just going through each if statement
regardless of whether the previous was correct.

6e36ed9840/code/modules/paperwork/pen.dm (L225-L258)
And, as we're dealing with text, just make it a switch statement
instead.
```dm
switch(pen_choice)
		if("Rename")
			(...)

		if("Description")
			(...)

		if("Reset")
			(...)
```
Then, we replace all single letter variables with descriptive ones,
replace the if-elses with early returns, and make it actually return
item interaction flags.

Finally, we slap this onto the pen, and we're done.
Now we can slap it onto other fitting renaming tools, and it uses the
proper item interaction system.
## Why It's Good For The Game

I feel it's generally better to not hardcode this to just pens, we have
plenty other writing utensils and possible renaming tools.
It's also a bit cleaner than before.
Apart from that, moves it from using `afterattack(...)` to the proper
item interaction chain by using `COMSIG_ITEM_INTERACTING_WITH_ATOM`,
which should reduce janky interactions.
## Changelog
🆑
refactor: Instead of being hardcoded to the pen, renaming items is now
an element. Currently only pens have this, and functionality should be
the same, but please report it if you find any items that were renamable
but now aren't.
/🆑

* Adds various quality of life changes for cooking to make it less click intensive. (#82566)

## About The Pull Request

- Increases tray item size by 1 item.

- Ranges and griddles can now be fed from trays.

Click when closed => fill soup pot.
Click when open => fill associated oven tray.
Right click when open => fill tray from oven tray
Click griddle => fill griddle surface.
Right click => fill tray from griddle surface

- Martian batter is now 5u of each ingredient into 10u of batter.

Hopefully will make it bug out less where it makes far fewer reagents
than it is supposed to, fixing reagents, or well soups specifically...
is out of scope for this PR.

- Adds the ability to print soup pots and large trays from the service
lathe

Soup pot: 5 Iron sheets, 0.4 bluespace crystal (given their size of
200U)
Large serving tray: 2 iron sheets

## Why It's Good For The Game

Makes cooking a lot less tedious. Especially for people with low
precision when it comes to filling oven trays. This also bring the
behavior up to parity with how you can click microwaves with trays to
fill them, ditto for the food processor. It also allows chef to use the
whole capacity of an oven, as previously you couldn't easily click 6
cake batters or other giant sprites onto the tiny tray.

The tray is now sized to be able to easily feed a griddle 8 items.

## Changelog

🆑
qol: chef equipment can now deposit and withdraw to/from trays!
qol: chef now has access to griddle and oven sized trays!
qol: service can now print soup pots
/🆑

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com>

* Removes grid usage + heavy refactors (#82571)

## About The Pull Request
Grid has been deprecated for quite some time and we still use it. I
won't completely remove the component, this way downstreams won't
immediately suffer, but I can remove it from usage.

Some of these UIs had issues with them and as a hobby project I've
refactored them into typescript / rebuilt them. Airlock electronics, for
instance, looks substantially better.

<details>
<summary>before/after as requested</summary>

current airlock electronics scrolls into oblivion

![6RJ29HCPob](https://github.com/tgstation/tgstation/assets/42397676/ba82bc20-40fa-4af0-b709-7c8846c25652)

updated
![Screenshot 2024-04-11
164321](https://github.com/tgstation/tgstation/assets/42397676/05507e06-6305-4175-8476-778c345f02c8)

</details>

## Why It's Good For The Game
Code improvement + probably UI bug fixes
## Changelog
🆑
fix: Airlock electronics and other access-config type UIs should look
much better.
/🆑

* modular fixes

* [No GBP] Removes cogbar from some stealthy actions (#82593)

Issue brought some missed hidden actions to my attention.

I left cogbars in for _breaking_ handcuffs because resisting is sort of
a gray area. On one hand, you don't want someone to see you doing it; on
the other, there is a visible warning that you started doing it. So,
meet in the the middle, breaking handcuffs is still visible while
resisting isn't.
Closes #82583
Cogbars are not intended to ruin stealth
🆑
fix: Deviants buffed: Rogue shoelacing, pickpocketing and restraint
resisting no longer give cogbar icons.
/🆑

* [NO GBP] ...Remember to add SIGNAL_HANDLER (#82630)

## About The Pull Request

Just realized I forgot to add `SIGNAL_HANDLER` to the all-nighter
`on_removed_limb(...)` proc, even though it handles signals.
## Why It's Good For The Game


fe26373572/code/__DEFINES/dcs/helpers.dm (L9-L11)

* React cleanup (#82607)

## About The Pull Request
- No defaultHooks in react. Might fix issues where pages were not
scrollable on hover.
- createRef in a functional component. should be useref

## Why It's Good For The Game
Code improvement

* Security photobooths have their own ID (#82628)

## About The Pull Request

Prevents the HoP's photobooth button from connecting to the security
photobooth via having the same ID.

## Why It's Good For The Game

I forgot to add this when I made the security photobooth but it's
important that by default without any varedits, the HoP and security
photobooths stay separate.

## Changelog

🆑
fix: The HoP's photobooth button is now consistently connected to the
HoP's photobooth.
/🆑

* Fix buckled alert unbuckling not working properly (#82627)

## About The Pull Request

So funny thing, while trying to reproduce a different issue on the
current master, I coincidentally let my local instance start without
reading, latejoined on the shuttle, and I noticed it wasn't letting me
unbuckle as easily.

Looking into this a bit later, it seems as if it's a line #82593
accidentally changed while moving around the
`/mob/living/carbon/resist_buckle()` proc's flow.

fe26373572/code/modules/mob/living/carbon/carbon.dm (L238-L241)
While before it was
```dm
/mob/living/carbon/resist_buckle()
	if(HAS_TRAIT(src, TRAIT_RESTRAINED))
		(...)
	else
		buckled.user_unbuckle_mob(src,src)
```
Just changing this to `buckled.user_unbuckle_mob(src, src)` fixes this.
## Why It's Good For The Game

Fixes buckled alert unbuckling not working properly.
Fixes #82627.

## Changelog
🆑
fix: Clicking the buckled alert unbuckles you again.
/🆑

* Advanced camera consoles correctly deactivates when something happens to it or the user (#82619)

## About The Pull Request
- Fixes #82520

1. The eye deactivates when the machine is destroyed/deleted
2. The eye deactivates when the machine loses power
3. The computer constantly moniters the users status inside `process()`
and will deactivate when anything happens to them. Its not enough to
just hook onto to the mobs `COMSIG_MOVABLE_MOVED` signal. Literarly
anything can happen to them so we have to check constantly for any
changes

## Changelog
🆑
fix: advanced camera consoles correctly deactivate when something
happens(no proximity, no power etc) to its user
/🆑

* Oven tray checks for ovens (#82615)

## About The Pull Request
- Fixes #82610

Only oven trays have this proc not serving trays or other stuff
![Screenshot
(408)](https://github.com/tgstation/tgstation/assets/110812394/4867cc14-9df3-4398-9d2d-f8e38b5f0da9)

Also oven trays have a null atom storage which prevents it from being
put back in the oven after taking it out. So we remove that check

## Changelog
🆑
fix: you can put back the oven tray after you take it out
fix: only oven trays are allowed in ovens preventing baked food runtimes
/🆑

* Living Limb fixes (feat: Basic mobs attack random body zones again) (#82556)

## About The Pull Request

Reworks Living Limb code to fix a bunch of runtimes and issues I saw
while testing Bioscrambler.
Specifically, the contained mobs are now initialised via element
following attachment so that signal registration can occur at the
correct time. This allows limbs to function correctly when added from
nullspace via admin panel or bioscrambler.

Secondarily (and more wide-ranging) at some point (probably #79563) we
inadvertently made basic mobs only attack the target's chest instead of
spreading damage.
This is problematic for Living Flesh which can only attach itself to
damaged limbs but was left unable to attack damaged limbs.

I've fixed this in a way which is maybe stupid: adding an element which
randomises attack zone pre-attack.
Living limbs also limit this to _only_ limbs (although it will fall back
to chest if you have no limbs at all).
This is _technically_ still different, the previous behaviour used
`adjustBruteLoss` and `adjustFireLoss` and would spread the damage
across your entire body, but there isn't a route to that via the new
interface and this seems close enough.

## Changelog

🆑
fix: Living Limbs created by Bioscrambler will be alive.
fix: Living Limbs can once more attach themselves to your body.
balance: Living Limbs will prioritise attacking your limbs.
fix: Basic Mobs will once again spread their damage across body zones
instead of only attacking your chest.
/🆑

* RPG Loot: Revisited & READY (#82533)

Revival of #72881

A new alt click window with a tarkov-y loading spinner. Replaces the
object item window in stat panel.

<details>
<summary>vids</summary>

toggleable grouping:

![syAA5zf6RK](https://github.com/tgstation/tgstation/assets/42397676/c89b372d-29f6-4ebe-895d-f73bbdc41c19)

now lists the floor as first obj:

![abc](https://github.com/tgstation/tgstation/assets/42397676/cd8dc962-2ac7-41bf-a5d3-b9e926116b06)

in action:

![dreamseeker_IkrPKt2QZt](https://github.com/tgstation/tgstation/assets/42397676/1f990aa0-60f0-47e7-9d93-b63e35d05273)

</details>

- search by name
- 515 image generator is much faster than alt click menu
- opening a gargantuan amount of items shouldnt freeze your screen
- groups similar items together in stacks by default, toggleable
- shows tile as first item
- <kbd>Shift</kbd> and <kbd>Ctrl</kbd> compatible with LMB
🖱️
- RMB points points at items (sry i could not get MMB working)
- key <kbd>Esc</kbd> to exit the window.

For devs:
- A new image generation tech.
- An error refetch mechanic to the Image component
- It does not "smart track" the items being added to the pile, just
reopen or refresh. This was a design decision.

Honestly I just dislike the stat panel

Fixes #53824

Fixes

![image](https://github.com/tgstation/tgstation/assets/42397676/0e50faab-7d4d-4bf7-8c5b-4ac28547bfbd)

🆑
add: Added a loot window for alt-clicking tiles.
del: Removed the item browser from the stat panel.
/🆑

---------

Co-authored-by: Zephyr <12817816+ZephyrTFA@users.noreply.github.com>
Co-authored-by: AnturK <AnturK@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>

* Reverts parts of #82602 (nodeath checks) (#82637)

## About The Pull Request

Reverts the nodeath checks of #82602

I opened a review thinking these checks were sus and the PR author said
they would remove them, but it was merged before that happened.

TL;DR 

1. I just noticed this now but it only affects carbons / humans it
doesn't even cover living or any other subtypes
2. Kinda sus. Some code intentionally skips checking nodeath (I guess?
Like removing the brain for example) so we would need a larger audit of
this rather than haphazardly throwing it in.

* Fixes to battle arcade (#82620)

## About The Pull Request

Added gear for world nine, removed the "Gear" gear that did nothing.
Made counterattacks to kill an enemy properly kill the enemy.
I renamed some gear items to fit the theme of the area they are unlocked
in just as a small thing.

## Why It's Good For The Game

Closes https://github.com/tgstation/tgstation/issues/82613

## Changelog

🆑
fix: Battle arcade's higher levels no longer gives you a "Gear" gear,
and counterattacks can now properly kill enemies.
/🆑

* Fixes SMES terminal placing under the SMES and not under the player (#82665)

## About The Pull Request

Changes `src` to`user` to get intended behavior.

* Birdshot: Toy crate (#82633)

## About The Pull Request
Gives the clown+mime their toy crate.
## Why It's Good For The Game
*honk*

* tram ai sat starts with a full smes (#82646)

## About The Pull Request

consistency and also this is fixes a bug introduced by that one power
refactor

## Why It's Good For The Game

bug bad

## Changelog
🆑
fix: tramstation AI sat starts full
/🆑

* [no gbp] Space Ruin bioscramblers shouldn't chase people around (#82649)

## About The Pull Request

See title
They wouldn't lock on to people on the station from a space ruin, but
would to whoever entered their z level the second it was entered.
Also fixes bug where I changed `status_flags` to `status_effects` for
some reason which isn't where you look for godmode

## Why It's Good For The Game

We have a space ruin whcih several (coreless) anomalies spawn on, the
bioscrambler was put as an option because it was already immortal. It's
weird though to zone into the ruin and immediately have every anomaly in
there lock onto you, the best intended effect is probably for these ones
specifically not to be bloodthirsty.
We kind of only care about that behaviour on the station.

## Changelog

🆑
fix: Anomalous Research ruin Bioscrambler anomalies won't home in on
targets
fix: Bioscrambler won't randomly drop its target for no reason
/🆑

* Sunders the many unused sprites and organizes what's left in structures.dmi (#82658)

## About The Pull Request
Hello again, I noticed the /obj/structures.dmi file had a lot of unused
stuff like tables from two generations ago, so I changed some stuff
around:
- Many unused, old icons deleted, mostly window variants used in old
smoothing systems I imagine
- Reorganized many sprites in the file so they're more grouped together
- Tweaked some barricade sprite naming to be consistent/standardized,
and to let others know they're not _too_ old...
- Fixed a misnomer that I believe was making directional tinted windows
look like frosted windows

## Why It's Good For The Game
Saves on file space, and satisfies your brain's pattern recognition bits

### Spriting
Old: 

![image](https://github.com/tgstation/tgstation/assets/143908044/0717940e-787e-40ee-85e2-0a0c5ebc0837)

New:

![image](https://github.com/tgstation/tgstation/assets/143908044/3954ba3b-b261-4700-986a-d30f3aa0e2a6)
also good lord those linen bin sprites are a crime

## Changelog
🆑
fix: Probably fixed directional tinted windows looking like directional
frosted windows
image: Deleted a bunch of unused structure sprites
/🆑

* Birdshot Wall Sanity Pass (#82598)

## About The Pull Request

Cleans up minor artifacting in the Birdshot Sec-Tram Closed Turfs

## Why It's Good For The Game

Someone definitely didn't mean to place some machines under Closed
Turfs. This barely qualifies as player facing.

## Changelog

🆑
fix: Cleans up some rocks on Birdshot
/🆑

* [NO GBP] Fixes deconstruction of closets & crates under a special case (#82612)

## About The Pull Request
So if a closet/crate has the `NO_DEBRIS_AFTER_DECONSTRUCTION` set on it
and if someone/something is still inside, then after deconstruction they
get deleted rather than getting dumped out first.

Could cause potential hard delete of mobs & stuff. We don't want to deal
with that

## Changelog
🆑
fix: closets & crates will dump all contents out first before deleting
itself regardless of `NO_DEBRIS_AFTER_DECONSTRUCTION` thus not for e.g.
hard deleting mobs inside it
/🆑

* Fixes ordinance lab igniter in IceBox (#82595)

## About The Pull Request
- Fixes #82294

Basically the same idea of merging ordanance lab with the burn chamber
so they share the same apc as already implemented in #82322

## Changelog
🆑
fix: Ordinance lab igniter in Icebox works again 
/🆑

* Birdshot: engi wardrope. (#82639)

## About The Pull Request

Add engi wardrope on Birdshot.

## Why It's Good For The Game

Birdshot doesn't have engi wardrope.

🆑
fix: Birdshot now have engi wardrope
/🆑

* Gives shadow walk a new, spookier, and shorter sound effect that no longer ignores walls (#82689)

## About The Pull Request

This gives shadow walk a snazzy new sound effect for entering/exiting
jaunt.


https://github.com/tgstation/tgstation/assets/28870487/c25f720f-5bad-4063-8d6e-140fd41bd740

This also has the sounds it plays no longer passes through walls.
## Why It's Good For The Game

The ethereal_entrance/exit sound effects are drawn out, and pretty
grating. They work for the other jaunts they're used for because a jaunt
typically lasts longer than the sound itself. Nightmares are frequently
dancing in and out of jaunt, and the sound effects for entering/exiting
tend to overlap. It gets loud and annoying really fast.

This sound effect is quicker, spookier, and more distinct.

As for making the sound not ignore walls, I think it's pretty dumb how
easy it is to detect the spooky scary shadow antag just by sitting in
your department. It takes a lot of the initial fear and paranoia they
have the potential for is wasted when Joe Geneticist can hear them
messing around in their territory without having to leave their chair.
## Changelog
🆑 Rhials
sound: Nightmare has a new sound effect for entering/exiting shadow
jaunt. It also no longer can be heard through walls.
/🆑

* [MIRROR] Alt click refactor (#2029)

* Alt click refactor

* Some early conflict removal

* Big modular refactor

* Update console.dm

* Update paper.dm

---------

Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com>
Co-authored-by: Mal <13398309+vinylspiders@users.noreply.github.com>

* Yeets `ATTACK_QDELETED`, fixes welding torches not using fuel on attacking non-mobs (2 year old bug)  (#82694)

## About The Pull Request

- Deletes `ATTACK_QDELETED`
- May have been necessary in the past but it's pointless now. All it
does is clutter the attack chain. Perish.

- Fixes welders not using fuel on attacking non-mobs
- #65762 "fixed" welders consuming fuel on clicking turfs by adding an
`isliving` check and not an `ismovable` check?


## Changelog

🆑 Melbert
fix: Blobs may rejoice, welding torches now consume fuel when attacking
objects again after two years.
/🆑

* electric_welder fire

* Quirks, which give items, now have quirk_item arg specified as obj/item, instead of being just a var (#82650)

## About The Pull Request
quirk_item is now /obj/item, since it will allow for calling procs or
getting variables from this item

It's required for non-modular translation to call for item's name to
remove articles

## Why It's Good For The Game
It's always an item, and if it's a path, it's already checked for it.
Better usage in the future.

* turns martial arts gloves into a component (#82599)

sleeping carp gloves also work on mind init

this means for the sake of deathmatch you dont have to put them off and
on

fixes #82321

🆑
fix: you no longer need to put your sleeping carp gloves off and on in
Deathmatch to get the martial art
/🆑

---------

Co-authored-by: san7890 <the@san7890.com>

* Regal Rats can now tear down posters (#82673)

## About The Pull Request

i was fixing something on bagil and someone who was playing a regal rat
(after the round ended) said they wanted to be able to tear down posters
as a regal rat so i decided to code it because it made sense.

it's an element so literally any mob can tear down posters but i can't
think of any other mobs that would make sense to let it tear down
posters so we'll leave it just for _The Champion of All Mislaid
Creatures_ for now
## Why It's Good For The Game

Regal Rats should be all about sludgemaxxing and fucking up maintenance
to make it look even more grody than it should be. Being able to tear up
those disgusting and well-drawn posters to leave behind nothing but
scraps fits that motif. The element has a `do_after()` just to make sure
His Holiness doesn't accidentally tear down his posters while clicking
(i think all mobs should have this but that's a different issue man)

also includes some code improvement and user feedback in some failure
cases that already existed in the code.
## Changelog
🆑
add: Regal Rats are now able to tear down those colorful posters those
weird grey creatures keep spackling up on the walls of their rightful
domain.
/🆑

* Adds "Strong Stomach" quirk, a core CDDA/PZ quirk we've sorely been missing. Also Deviant Tastes dirty food re-nerf. (#82562)

## About The Pull Request

- Adds Strong Stomach quirk. 
   - 4 points
   - You can eat dirty food without risk of getting disease. 
- You suffer less negative effects from vomiting. Vomit stuns you for
half the duration, and you lose half as much nutrition.

- Reverts https://github.com/tgstation/tgstation/pull/76864 , integrates
its effects into Strong Stomach instead.

## Why It's Good For The Game

- Lotta people (namely Lizards and sometimes Felines with Deviant
Tastes) run gimmicks involving them being a gremlin person and eating
trash off the ground, and it's rather hard to accomplish this now since
it makes you a public medbay enemy # 1. This quirk should give them an
option to avoid that.
- Also (as mentioned in the title) both CDDA and PZ have this trait and
I can't believe we're missing it! This is something in
modifiable-character-traits/quirks-101.

- I moved the effects from #76864 to this quirk because 1. I thought it
was more fitting and 2. I thought the original PR was kinda wack for
what is (generally) a neutral quirk.

## Changelog

🆑 Melbert
add: Adds the Strong Stomach quirk, which allows you to eat grimy food
without worry about disease, and makes you a bit more resilient to the
effects of vomiting.
del: Deviant Tastes no longer prevents you from getting a negative
moodlet from eating dirty food. Strong Stomach does that now.
/🆑

---------

Co-authored-by: Jacquerel <hnevard@gmail.com>

* Remove several functions from collections.js which have ES5 equivalents (#82417)

* Makes it EVEN EASIER to work with atom item interactions ft. "Leaf and Branch" & "Death to Chains" (#82625)

* apc fix

* Gulag Adjustments Two (#82561)

## About The Pull Request

I have received feedback that after the prior changes in #81971, the
gulag is still a little bit too subject to RNG.
The main culprit (as in my previous PR) is Iron being kind of cheap and
the fact that unlike the old Gulag you no longer have any way of
headhunting more valuable materials (everything appears as boulders on
your ore scanner).

My solution to this is wider than the last one of tweaking point values,
but also much simpler:
Just make every boulder you mine be worth the same amount of points
regardless of what is inside of it.

On the average test I made I could comfortably mine about 40-45 boulders
in ten minutes.
We'll make some adjustments to that rather than leaving 40 as the target
number;
Most players upon being teleported to the gulag are going to spend a few
minutes whining and bemoaning their fate instead of getting straight to
work. I had the benefit of being able to make sure my run started as
soon as a storm ended so I wouldn't need any kind of midpoint break. I
was also always the only person playing on my local instance, there
hadn't been any other pesky prisoners before me who had already mined
out all the nearest available deposits. And of course, let us not
forget, I am an MLG master league ss13 player who was surely performing
well above average.

So we'll round that down to: Each boulder is worth 33 points, meaning
you need to collect 31 boulders to complete a 1000 point (roughly ten
minute) sentence.

How do I ensure that every boulder is worth the same amount of points?
Well it's pretty easy.
One boulder = one material sheet. One material sheet = 33 points.
Simple.

"Now Jacquerel", I hear you not saying because you don't want me to know
about this thing you would prefer to do instead of hitting rocks
outside; "if I simply smash all of the tables and microwaves and botany
trays and bed in the gulag I can easily get like 65 sheets of Iron,
which is almost enough to buy the freedom for two entire people!"
Unfortunately I knew you were going to try and do that and the prisoner
point machine will only give you points for material sheets which have
been printed from the material smelter (well, any material smelter
actually but you should probably use the one in the gulag). You'll be
able to tell because if you examine a valid material sheet it will
mention a little maker's mark on it, which is absent in the beat-up iron
that you get from smashing furniture to bits.

Also glass is worth 0 points. Don't waste time digging up that shit. 

As glass has had all of its point value removed, I have added a "work
pit" to the gulag to compensate. You can pull boulders out of this
indefinitely via effort, however it also stamcrits you every time.
It's not very fun to do this, but that's because I would prefer you to
go find the rocks out in the field instead. This is a last resort.
You can do this if there's no boulders left to mine or if you really
really really hate mining and would rather very slowly click on one tile
repeatedly to get your boulders instead.
As a tiny bonus doing this gives workout experience.

This isn't a totally ideal solution but I think it'll do for now.

## Why It's Good For The Game

What we want out of the gulag is:
- Something where officers can vaguely approximate an expected sentence
duration.
- A task that requires players to actually be spending that time doing
something to get out of here.
- Produces at least some amount of useful materials.

In I think roughly that order.
I hope this change accomplishes all three of these in a way that is
somewhat predictable rather than throwing darts at a board.

## Changelog

🆑
balance: Gulag mining has been rebalanced so that every boulder is worth
the same amount of points to mine for a prisoner regardless of what it
contains, and should be more consistent.
add: A vent which boulders can be hauled out of by hand has been added
to the gulag which you can use if there's nothing left to mine. It is
very slow, but at least it gives you a workout...
/🆑

* stone

* Makes test merge bot continue with other PRs if updating one fails. (#82717)

Right now updating
https://github.com/tgstation/tgstation/pull/81089#issuecomment-1907296233
fails because it exceeds github character limit for comments.

This will make it work until backed is updated.

* Fixes the RnD console by adding a removed import (#82750)

## About The Pull Request
The 'map' import was removed from this file by #82417 but it's still
used in place in code. This re-adds the import

## Why It's Good For The Game
Fixes RnD consoles

## Changelog
🆑
fix: Fixed RnD consoles not being able to be opened.
/🆑

Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com>

* Fixes cargo import (#82755)

## About The Pull Request
One of the imports got removed and there were no warnings... Man if only
there were a technology that could warn us in advance
## Why It's Good For The Game
UI fixes
## Changelog
🆑
fix: Fixed a bluescreen in cargo console
/🆑

* fixes

* Fixes, fixes.

* Pre-emptive mirror of https://github.com/tgstation/tgstation/pull/82892

* Turf weakref persists in changeturf / Fix plasma cutters  (#82906)

## About The Pull Request

Turf references don't change so logically, turf weakrefs wouldn't change
if the turf changes.

By not doing this this can cause bugs: See #82886 . (This Fixes #82886) 

(Projectiles hold a list of weakrefs to atoms hit to determine what they
have already hit.

Because turf weakrefs reset, we could "hit" the same turf twice if it
destroyed the turf.

Old behavior - this was fine but now that they're weakrefs, we get two
weakref datums in the list that point to the same ref.)

Less hacky alternative to #82901 . (Closes #82901) 

## Changelog

🆑 Melbert
fix: Plasma cutters work again
/🆑

---------

Co-authored-by: san7890 <the@san7890.com>
Co-authored-by: Interception&? <137328283+intercepti0n@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: Ketrai <zottielolly@gmail.com>
Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com>
Co-authored-by: Jacquerel <hnevard@gmail.com>
Co-authored-by: Zephyr <12817816+ZephyrTFA@users.noreply.github.com>
Co-authored-by: AnturK <AnturK@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: Iajret <8430839+Iajret@users.noreply.github.com>
Co-authored-by: vect0r <71346830+Vect0r2@users.noreply.github.com>
Co-authored-by: jimmyl <70376633+mc-oofert@users.noreply.github.com>
Co-authored-by: AMyriad <143908044+AMyriad@users.noreply.github.com>
Co-authored-by: Zytolg <33048583+Zytolg@users.noreply.github.com>
Co-authored-by: Xackii <120736708+Xackii@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: NovaBot <154629622+NovaBot13@users.noreply.github.com>
Co-authored-by: Mal <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: larentoun <31931237+larentoun@users.noreply.github.com>
Co-authored-by: Arthri <41360489+Arthri@users.noreply.github.com>
Co-authored-by: Watermelon914 <37270891+Watermelon914@users.noreply.github.com>
Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com>
Co-authored-by: Useroth <37159550+Useroth@users.noreply.github.com>
2024-04-28 22:24:01 +02:00

1329 lines
52 KiB
Plaintext

/obj/machinery/hydroponics
name = "hydroponics tray"
desc = "A basin used to grow plants in."
icon = 'icons/obj/service/hydroponics/equipment.dmi'
icon_state = "hydrotray"
density = TRUE
pass_flags_self = PASSMACHINE | LETPASSTHROW
pixel_z = 8
obj_flags = CAN_BE_HIT | UNIQUE_RENAME
circuit = /obj/item/circuitboard/machine/hydroponics
use_power = NO_POWER_USE
///The amount of water in the tray (max 100)
var/waterlevel = 0
///The maximum amount of water in the tray
var/maxwater = 100
///How many units of nutrients will be drained in the tray.
var/nutridrain = 1
///The maximum nutrient reagent container size of the tray.
var/maxnutri = 20
///The amount of pests in the tray (max 10)
var/pestlevel = 0
///The amount of weeds in the tray (max 10)
var/weedlevel = 0
///Nutriment's effect on yield
var/yieldmod = 1
///Nutriment's effect on mutations
var/mutmod = 1
///Toxicity in the tray?
var/toxic = 0
///Current age
var/age = 0
///The status of the plant in the tray. Whether it's harvestable, alive, missing or dead.
var/plant_status = HYDROTRAY_NO_PLANT
///Its health
var/plant_health
///Last time it was harvested
var/lastproduce = 0
///Used for timing of cycles.
var/lastcycle = 0
///About 10 seconds / cycle
var/cycledelay = HYDROTRAY_CYCLE_DELAY
///The currently planted seed
var/obj/item/seeds/myseed
///Obtained from the quality of the parts used in the tray, determines nutrient drain rate.
var/rating = 1
///Can it be unwrenched to move?
var/unwrenchable = TRUE
///Have we been visited by a bee recently, so bees dont overpollinate one plant
var/recent_bee_visit = FALSE
///The last user to add a reagent to the tray, mostly for logging purposes.
var/datum/weakref/lastuser
///If the tray generates nutrients and water on its own
var/self_sustaining = FALSE
///The icon state for the overlay used to represent that this tray is self-sustaining.
var/self_sustaining_overlay_icon_state = "gaia_blessing"
/obj/machinery/hydroponics/Initialize(mapload)
//ALRIGHT YOU DEGENERATES. YOU HAD REAGENT HOLDERS FOR AT LEAST 4 YEARS AND NONE OF YOU MADE HYDROPONICS TRAYS HOLD NUTRIENT CHEMS INSTEAD OF USING "Points".
//SO HERE LIES THE "nutrilevel" VAR. IT'S DEAD AND I PUT IT OUT OF IT'S MISERY. USE "reagents" INSTEAD. ~ArcaneMusic, accept no substitutes.
create_reagents(maxnutri, INJECTABLE)
if(mapload)
reagents.add_reagent(/datum/reagent/plantnutriment/eznutriment, max(maxnutri / 2, 10)) //Half filled nutrient trays for dirt trays to have more to grow with in prison/lavaland.
waterlevel = maxwater
. = ..()
var/static/list/hovering_item_typechecks = list(
/obj/item/plant_analyzer = list(
SCREENTIP_CONTEXT_LMB = "Scan tray stats",
SCREENTIP_CONTEXT_RMB = "Scan tray chemicals"
),
/obj/item/cultivator = list(
SCREENTIP_CONTEXT_LMB = "Remove weeds",
),
/obj/item/shovel = list(
SCREENTIP_CONTEXT_LMB = "Clear tray",
),
)
AddElement(/datum/element/contextual_screentip_item_typechecks, hovering_item_typechecks)
register_context()
/obj/machinery/hydroponics/add_context(
atom/source,
list/context,
obj/item/held_item,
mob/living/user,
)
// If we don't have a seed, we can't do much.
// The only option is to plant a new seed.
if(!myseed)
if(istype(held_item, /obj/item/seeds))
context[SCREENTIP_CONTEXT_LMB] = "Plant seed"
return CONTEXTUAL_SCREENTIP_SET
return NONE
// If we DO have a seed, we can do a few things!
// With a hand we can harvest or remove dead plants
// If the plant's not in either state, we can't do much else, so early return.
if(isnull(held_item))
// Silicons can't interact with trays :frown:
if(HAS_SILICON_ACCESS(user))
return NONE
switch(plant_status)
if(HYDROTRAY_PLANT_DEAD)
context[SCREENTIP_CONTEXT_LMB] = "Remove dead plant"
return CONTEXTUAL_SCREENTIP_SET
if(HYDROTRAY_PLANT_HARVESTABLE)
context[SCREENTIP_CONTEXT_LMB] = "Harvest plant"
return CONTEXTUAL_SCREENTIP_SET
return NONE
// If the plant is harvestable, we can graft it with secateurs or harvest it with a plant bag.
if(plant_status == HYDROTRAY_PLANT_HARVESTABLE)
if(istype(held_item, /obj/item/secateurs))
context[SCREENTIP_CONTEXT_LMB] = "Graft plant"
return CONTEXTUAL_SCREENTIP_SET
if(istype(held_item, /obj/item/storage/bag/plants))
context[SCREENTIP_CONTEXT_LMB] = "Harvest plant"
return CONTEXTUAL_SCREENTIP_SET
// If the plant's in good health, we can shear it.
if(istype(held_item, /obj/item/geneshears) && plant_health > GENE_SHEAR_MIN_HEALTH)
context[SCREENTIP_CONTEXT_LMB] = "Remove plant gene"
return CONTEXTUAL_SCREENTIP_SET
// If we've got a charged somatoray, we can mutation lock it.
if(istype(held_item, /obj/item/gun/energy/floragun) && myseed.endurance > FLORA_GUN_MIN_ENDURANCE && LAZYLEN(myseed.mutatelist))
var/obj/item/gun/energy/floragun/flower_gun = held_item
if(flower_gun.cell.charge >= flower_gun.cell.maxcharge)
context[SCREENTIP_CONTEXT_LMB] = "Lock mutation"
return CONTEXTUAL_SCREENTIP_SET
// Edibles and pills can be composted.
if(IS_EDIBLE(held_item) || istype(held_item, /obj/item/reagent_containers/pill))
context[SCREENTIP_CONTEXT_LMB] = "Compost"
return CONTEXTUAL_SCREENTIP_SET
// And if a reagent container has water or plant fertilizer in it, we can use it on the plant.
if(is_reagent_container(held_item) && length(held_item.reagents.reagent_list))
var/datum/reagent/most_common_reagent = held_item.reagents.get_master_reagent()
context[SCREENTIP_CONTEXT_LMB] = "[istype(most_common_reagent, /datum/reagent/water) ? "Water" : "Feed"] plant"
return CONTEXTUAL_SCREENTIP_SET
return NONE
/obj/machinery/hydroponics/constructable
name = "hydroponics tray"
icon = 'icons/obj/service/hydroponics/equipment.dmi'
icon_state = "hydrotray3"
/obj/machinery/hydroponics/constructable/Initialize(mapload)
. = ..()
AddComponent(/datum/component/simple_rotation)
AddComponent(/datum/component/plumbing/hydroponics)
AddComponent(/datum/component/usb_port, list(/obj/item/circuit_component/hydroponics))
AddComponent(/datum/component/fishing_spot, /datum/fish_source/hydro_tray)
/obj/machinery/hydroponics/constructable/RefreshParts()
. = ..()
var/tmp_capacity = 0
for (var/datum/stock_part/matter_bin/matter_bin in component_parts)
tmp_capacity += matter_bin.tier
for (var/datum/stock_part/servo/servo in component_parts)
rating = servo.tier
maxwater = tmp_capacity * 50 // Up to 300
maxnutri = (tmp_capacity * 5) + STATIC_NUTRIENT_CAPACITY // Up to 50 Maximum
reagents.maximum_volume = maxnutri
nutridrain = 1/rating
/obj/machinery/hydroponics/constructable/examine(mob/user)
. = ..()
. += span_notice("Use <b>Ctrl-Click</b> to activate autogrow. <b>RMB</b> to empty the tray's nutrients.")
if(in_range(user, src) || isobserver(user))
. += span_notice("The status display reads: Tray efficiency at <b>[rating*100]%</b>.")
/obj/machinery/hydroponics/constructable/add_context(
atom/source,
list/context,
obj/item/held_item,
mob/living/user,
)
// Constructible trays will always show that you can activate auto-grow with ctrl+click
. = ..()
context[SCREENTIP_CONTEXT_CTRL_LMB] = "Activate auto-grow"
return CONTEXTUAL_SCREENTIP_SET
/obj/machinery/hydroponics/Destroy()
if(myseed)
QDEL_NULL(myseed)
return ..()
/obj/machinery/hydroponics/Exited(atom/movable/gone)
. = ..()
if(!QDELETED(src) && gone == myseed)
set_seed(null, FALSE)
/obj/machinery/hydroponics/constructable/attackby(obj/item/I, mob/living/user, params)
if (!user.combat_mode)
// handle opening the panel
if(default_deconstruction_screwdriver(user, icon_state, icon_state, I))
return
if(default_deconstruction_crowbar(I))
return
return ..()
/// Special demand connector that consumes as normal, but redirects water into the magical water space.
/datum/component/plumbing/hydroponics
demand_connects = SOUTH
/// Alternate reagents container to buffer incoming water
var/datum/reagents/water_reagents
/// Actual parent reagents that has nutrients
var/datum/reagents/nutri_reagents
/datum/component/plumbing/hydroponics/Initialize(start=TRUE, _ducting_layer, _turn_connects=TRUE, datum/reagents/custom_receiver)
. = ..()
if(!istype(parent, /obj/machinery/hydroponics/constructable))
return COMPONENT_INCOMPATIBLE
var/obj/machinery/hydroponics/constructable/hydro_parent = parent
water_reagents = new(hydro_parent.maxwater)
water_reagents.my_atom = hydro_parent
nutri_reagents = reagents
/datum/component/plumbing/hydroponics/Destroy()
qdel(water_reagents)
nutri_reagents = null
return ..()
/datum/component/plumbing/hydroponics/send_request(dir)
var/obj/machinery/hydroponics/constructable/hydro_parent = parent
var/initial_nutri_amount = nutri_reagents.total_volume
if(initial_nutri_amount < nutri_reagents.maximum_volume)
// Well boy howdy, we have no way to tell a supply to not mix the water with everything else,
// So we'll let it leak in, and move the water over.
set_recipient_reagents_holder(nutri_reagents)
reagents = nutri_reagents
process_request(dir = dir)
// Move the leaked water from nutrients to... water
var/leaking_water_amount = nutri_reagents.get_reagent_amount(/datum/reagent/water)
if(leaking_water_amount)
nutri_reagents.trans_to(water_reagents, leaking_water_amount, target_id = /datum/reagent/water)
// We should only take MACHINE_REAGENT_TRANSFER every tick; this is the remaining amount we can take
var/remaining_transfer_amount = max(MACHINE_REAGENT_TRANSFER - (nutri_reagents.total_volume - initial_nutri_amount), 0)
// How much extra water we should gather this tick to try to fill the water tray.
var/extra_water_to_gather = clamp(hydro_parent.maxwater - hydro_parent.waterlevel - water_reagents.total_volume, 0, remaining_transfer_amount)
if(extra_water_to_gather > 0)
set_recipient_reagents_holder(water_reagents)
reagents = water_reagents
process_request(
amount = extra_water_to_gather,
reagent = /datum/reagent/water,
dir = dir,
)
// Now transfer all remaining water in that buffer and clear it out.
var/final_water_amount = water_reagents.total_volume
if(final_water_amount)
hydro_parent.adjust_waterlevel(round(final_water_amount))
// Using a pipe doesn't afford you extra water storage and the baseline behavior for trays is that excess water goes into the shadow realm.
water_reagents.del_reagent(/datum/reagent/water)
// Plumbing pauses if reagents is full.. so let's cheat and make sure it ticks unless both trays are happy
reagents = hydro_parent.waterlevel < hydro_parent.maxwater ? water_reagents : nutri_reagents
/obj/machinery/hydroponics/bullet_act(obj/projectile/Proj) //Works with the Somatoray to modify plant variables.
if(!myseed)
return ..()
if(istype(Proj , /obj/projectile/energy/flora/mut))
mutate()
else if(istype(Proj , /obj/projectile/energy/flora/yield))
return myseed.bullet_act(Proj)
else if(istype(Proj , /obj/projectile/energy/flora/evolution))
if(myseed)
if(LAZYLEN(myseed.mutatelist))
myseed.set_instability(myseed.instability/2)
mutatespecie()
else
return ..()
/obj/machinery/hydroponics/power_change()
. = ..()
if((machine_stat & NOPOWER) && self_sustaining)
set_self_sustaining(FALSE)
/obj/machinery/hydroponics/process(seconds_per_tick)
var/needs_update = FALSE // Checks if the icon needs updating so we don't redraw empty trays every time
if(self_sustaining)
if(powered())
adjust_waterlevel(rand(1,2) * seconds_per_tick * 0.5)
adjust_weedlevel(-0.5 * seconds_per_tick)
adjust_pestlevel(-0.5 * seconds_per_tick)
else
set_self_sustaining(FALSE)
visible_message(span_warning("[name]'s auto-grow functionality shuts off!"))
if(world.time > (lastcycle + cycledelay))
lastcycle = world.time
if(myseed && plant_status != HYDROTRAY_PLANT_DEAD)
// Advance age
age++
if(age < myseed.maturation)
lastproduce = age
needs_update = TRUE
//Nutrients//////////////////////////////////////////////////////////////
// Nutrients deplete at a constant rate, since new nutrients can boost stats far easier.
apply_chemicals(lastuser?.resolve())
if(self_sustaining)
reagents.remove_all(min(0.5, nutridrain))
else
reagents.remove_all(nutridrain)
// Lack of nutrients hurts non-weeds
if(reagents.total_volume <= 0 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy))
adjust_plant_health(-rand(1,3))
//Photosynthesis/////////////////////////////////////////////////////////
// Lack of light hurts non-mushrooms
if(isturf(loc))
var/turf/currentTurf = loc
var/lightAmt = currentTurf.get_lumcount()
var/is_fungus = myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)
if(lightAmt < (is_fungus ? 0.2 : 0.4))
adjust_plant_health((is_fungus ? -1 : -2) / rating)
//Water//////////////////////////////////////////////////////////////////
// Drink random amount of water
adjust_waterlevel(-rand(1,6) / rating)
// If the plant is dry, it loses health pretty fast, unless mushroom
if(waterlevel <= 10 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism))
adjust_plant_health(-rand(0,1) / rating)
if(waterlevel <= 0)
adjust_plant_health(-rand(0,2) / rating)
// Sufficient water level and nutrient level = plant healthy but also spawns weeds
else if(waterlevel > 10 && reagents.total_volume > 0)
adjust_plant_health(rand(1,2) / rating)
if(myseed && prob(myseed.weed_chance))
adjust_weedlevel(myseed.weed_rate)
else if(prob(5)) //5 percent chance the weed population will increase
adjust_weedlevel(1 / rating)
//Toxins/////////////////////////////////////////////////////////////////
// Too much toxins cause harm, but when the plant drinks the contaiminated water, the toxins disappear slowly
if(toxic >= 40 && toxic < 80)
adjust_plant_health(-1 / rating)
adjust_toxic(-rating * 2)
else if(toxic >= 80) // I don't think it ever gets here tbh unless above is commented out
adjust_plant_health(-3)
adjust_toxic(-rating * 3)
//Pests & Weeds//////////////////////////////////////////////////////////
if(pestlevel >= 8)
if(!myseed.get_gene(/datum/plant_gene/trait/carnivory))
if(myseed.potency >= 30)
myseed.adjust_potency(-rand(2,6)) //Pests eat leaves and nibble on fruit, lowering potency.
myseed.set_potency(min((myseed.potency), CARNIVORY_POTENCY_MIN, MAX_PLANT_POTENCY))
else
adjust_plant_health(2 / rating)
adjust_pestlevel(-1 / rating)
else if(pestlevel >= 4)
if(!myseed.get_gene(/datum/plant_gene/trait/carnivory))
if(myseed.potency >= 30)
myseed.adjust_potency(-rand(1,4))
myseed.set_potency(min((myseed.potency), CARNIVORY_POTENCY_MIN, MAX_PLANT_POTENCY))
else
adjust_plant_health(1 / rating)
if(prob(50))
adjust_pestlevel(-1 / rating)
else if(pestlevel < 4 && myseed.get_gene(/datum/plant_gene/trait/carnivory))
if(prob(5))
adjust_pestlevel(-1 / rating)
// If it's a weed, it doesn't stunt the growth
if(weedlevel >= 5 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy))
if(myseed.yield >= 3)
myseed.adjust_yield(-rand(1,2)) //Weeds choke out the plant's ability to bear more fruit.
myseed.set_yield(min((myseed.yield), WEED_HARDY_YIELD_MIN, MAX_PLANT_YIELD))
//This is the part with pollination
pollinate()
//This is where stability mutations exist now.
if(myseed.instability >= 80)
var/mutation_chance = myseed.instability - 75
mutate(0, 0, 0, 0, 0, 0, 0, mutation_chance, 0) //Scaling odds of a random trait or chemical
if(myseed.instability >= 60)
if(prob((myseed.instability)/2) && !self_sustaining && LAZYLEN(myseed.mutatelist) && !myseed.get_gene(/datum/plant_gene/trait/never_mutate)) //Minimum 30%, Maximum 50% chance of mutating every age tick when not on autogrow or having Prosophobic Inclination trait.
mutatespecie()
myseed.set_instability(myseed.instability/2)
if(myseed.instability >= 40)
if(prob(myseed.instability) && !myseed.get_gene(/datum/plant_gene/trait/stable_stats)) //No hardmutation if Symbiotic Resilience trait is present.
hardmutate()
if(myseed.instability >= 20 )
if(prob(myseed.instability) && !myseed.get_gene(/datum/plant_gene/trait/stable_stats)) //No mutation if Symbiotic Resilience trait is present.
mutate()
//Health & Age///////////////////////////////////////////////////////////
// Plant dies if plant_health <= 0
if(plant_health <= 0)
plantdies()
adjust_weedlevel(1 / rating) // Weeds flourish
// If the plant is too old, lose health fast
if(age > myseed.lifespan)
adjust_plant_health(-rand(1,5) / rating)
// Harvest code
if(age > myseed.production && (age - lastproduce) > myseed.production && plant_status == HYDROTRAY_PLANT_GROWING)
if(myseed && myseed.yield != -1) // Unharvestable shouldn't be harvested
set_plant_status(HYDROTRAY_PLANT_HARVESTABLE)
else
lastproduce = age
if(prob(5)) // On each tick, there's a 5 percent chance the pest population will increase
adjust_pestlevel(1 / rating)
else
if(waterlevel > 10 && reagents.total_volume > 0 && prob(10)) // If there's no plant, the percentage chance is 10%
adjust_weedlevel(1 / rating)
// Weeeeeeeeeeeeeeedddssss
if(weedlevel >= 10 && prob(50) && !self_sustaining) // At this point the plant is kind of fucked. Weeds can overtake the plant spot.
if(myseed && myseed.yield >= 3)
myseed.adjust_yield(-rand(1,2)) //Loses even more yield per tick, quickly dropping to 3 minimum.
myseed.set_yield(min((myseed.yield), WEED_HARDY_YIELD_MIN, MAX_PLANT_YIELD))
if(!myseed)
weedinvasion()
needs_update = 1
if (needs_update)
update_appearance()
if(myseed)
SEND_SIGNAL(myseed, COMSIG_SEED_ON_GROW, src)
return
/obj/machinery/hydroponics/update_appearance(updates)
. = ..()
if(self_sustaining)
set_light(3)
return
if(myseed?.get_gene(/datum/plant_gene/trait/glow)) // Hydroponics needs a refactor, badly.
var/datum/plant_gene/trait/glow/G = myseed.get_gene(/datum/plant_gene/trait/glow)
set_light(G.glow_range(myseed), G.glow_power(myseed), G.glow_color)
return
set_light(0)
/obj/machinery/hydroponics/update_name(updates)
. = ..()
if(myseed)
name = "[initial(name)] ([myseed.plantname])"
else
name = initial(name)
/obj/machinery/hydroponics/update_overlays()
. = ..()
if(myseed)
. += update_plant_overlay()
. += update_status_light_overlays()
if(self_sustaining && self_sustaining_overlay_icon_state)
. += mutable_appearance(icon, self_sustaining_overlay_icon_state)
/obj/machinery/hydroponics/proc/update_plant_overlay()
var/mutable_appearance/plant_overlay = mutable_appearance(myseed.growing_icon, layer = OBJ_LAYER + 0.01)
switch(plant_status)
if(HYDROTRAY_PLANT_DEAD)
plant_overlay.icon_state = myseed.icon_dead
if(HYDROTRAY_PLANT_HARVESTABLE)
if(!myseed.icon_harvest)
plant_overlay.icon_state = "[myseed.icon_grow][myseed.growthstages]"
else
plant_overlay.icon_state = myseed.icon_harvest
else
var/t_growthstate = clamp(round((age / myseed.maturation) * myseed.growthstages), 1, myseed.growthstages)
plant_overlay.icon_state = "[myseed.icon_grow][t_growthstate]"
plant_overlay.pixel_y = myseed.plant_icon_offset
return plant_overlay
/obj/machinery/hydroponics/proc/update_status_light_overlays()
. = list()
if(waterlevel <= 10)
. += mutable_appearance('icons/obj/service/hydroponics/equipment.dmi', "over_lowwater3")
if(reagents.total_volume <= 2)
. += mutable_appearance('icons/obj/service/hydroponics/equipment.dmi', "over_lownutri3")
if(plant_health <= (myseed.endurance / 2))
. += mutable_appearance('icons/obj/service/hydroponics/equipment.dmi', "over_lowhealth3")
if(weedlevel >= 5 || pestlevel >= 5 || toxic >= 40)
. += mutable_appearance('icons/obj/service/hydroponics/equipment.dmi', "over_alert3")
if(plant_status == HYDROTRAY_PLANT_HARVESTABLE)
. += mutable_appearance('icons/obj/service/hydroponics/equipment.dmi', "over_harvest3")
///Sets a new value for the myseed variable, which is the seed of the plant that's growing inside the tray.
/obj/machinery/hydroponics/proc/set_seed(obj/item/seeds/new_seed, delete_old_seed = TRUE)
var/old_seed = myseed
myseed = new_seed
if(old_seed && delete_old_seed)
qdel(old_seed)
set_plant_status(new_seed ? HYDROTRAY_PLANT_GROWING : HYDROTRAY_NO_PLANT) //To make sure they can't just put in another seed and insta-harvest it
if(myseed && myseed.loc != src)
myseed.forceMove(src)
SEND_SIGNAL(src, COMSIG_HYDROTRAY_SET_SEED, new_seed)
update_appearance()
if(isnull(myseed))
particles = null
/*
* Setter proc to set a tray to a new self_sustaining state and update all values associated with it.
*
* new_value - true / false value that self_sustaining is being set to
*/
/obj/machinery/hydroponics/proc/set_self_sustaining(new_value)
if(self_sustaining == new_value)
return
self_sustaining = new_value
update_use_power(self_sustaining ? ACTIVE_POWER_USE : NO_POWER_USE)
update_appearance()
SEND_SIGNAL(src, COMSIG_HYDROTRAY_SET_SELFSUSTAINING, new_value)
/obj/machinery/hydroponics/proc/set_weedlevel(new_weedlevel, update_icon = TRUE)
if(weedlevel == new_weedlevel)
return
SEND_SIGNAL(src, COMSIG_HYDROTRAY_SET_WEEDLEVEL, new_weedlevel)
weedlevel = new_weedlevel
if(update_icon)
update_appearance()
/obj/machinery/hydroponics/proc/set_pestlevel(new_pestlevel, update_icon = TRUE)
if(pestlevel == new_pestlevel)
return
SEND_SIGNAL(src, COMSIG_HYDROTRAY_SET_PESTLEVEL, new_pestlevel)
pestlevel = new_pestlevel
if(update_icon)
update_appearance()
/obj/machinery/hydroponics/proc/set_waterlevel(new_waterlevel, update_icon = TRUE)
if(waterlevel == new_waterlevel)
return
SEND_SIGNAL(src, COMSIG_HYDROTRAY_SET_WATERLEVEL, new_waterlevel)
waterlevel = new_waterlevel
if(update_icon)
update_appearance()
var/difference = new_waterlevel - waterlevel
if(difference > 0)
adjust_toxic(-round(difference/4))//Toxicity dilutation code. The more water you put in, the lesser the toxin concentration.
/obj/machinery/hydroponics/proc/set_plant_health(new_plant_health, update_icon = TRUE, forced = FALSE)
if(plant_health == new_plant_health || ((!myseed || plant_status == HYDROTRAY_PLANT_DEAD) && !forced))
return
SEND_SIGNAL(src, COMSIG_HYDROTRAY_SET_PLANT_HEALTH, new_plant_health)
plant_health = new_plant_health
if(update_icon)
update_appearance()
/obj/machinery/hydroponics/proc/set_toxic(new_toxic, update_icon = TRUE)
if(toxic == new_toxic)
return
SEND_SIGNAL(src, COMSIG_HYDROTRAY_SET_TOXIC, new_toxic)
toxic = new_toxic
if(update_icon)
update_appearance()
/obj/machinery/hydroponics/proc/set_plant_status(new_plant_status)
if(plant_status == new_plant_status)
return
SEND_SIGNAL(src, COMSIG_HYDROTRAY_SET_PLANT_STATUS, new_plant_status)
plant_status = new_plant_status
// The following procs adjust the hydroponics tray variables, and make sure that the stat doesn't go out of bounds.
/**
* Adjust water.
* Raises or lowers tray water values by a set value. Adding water will dillute toxicity from the tray.
* Returns the amount of water actually added/taken
* * adjustamt - determines how much water the tray will be adjusted upwards or downwards.
*/
/obj/machinery/hydroponics/proc/adjust_waterlevel(amt)
var/initial_waterlevel = waterlevel
set_waterlevel(clamp(waterlevel+amt, 0, maxwater), FALSE)
return waterlevel-initial_waterlevel
/**
* Adjust Health.
* Raises the tray's plant_health stat by a given amount, with total health determined by the seed's endurance.
* * adjustamt - Determines how much the plant_health will be adjusted upwards or downwards.
*/
/obj/machinery/hydroponics/proc/adjust_plant_health(amt)
set_plant_health(clamp(plant_health + amt, 0, myseed?.endurance), FALSE)
/**
* Adjust toxicity.
* Raises the plant's toxic stat by a given amount.
* * adjustamt - Determines how much the toxic will be adjusted upwards or downwards.
*/
/obj/machinery/hydroponics/proc/adjust_toxic(amt)
set_toxic(clamp(toxic + amt, 0, MAX_TRAY_TOXINS), FALSE)
/**
* Adjust Pests.
* Raises the tray's pest level stat by a given amount.
* * adjustamt - Determines how much the pest level will be adjusted upwards or downwards.
*/
/obj/machinery/hydroponics/proc/adjust_pestlevel(amt)
set_pestlevel(clamp(pestlevel + amt, 0, MAX_TRAY_PESTS), FALSE)
/**
* Adjust Weeds.
* Raises the plant's weed level stat by a given amount.
* * adjustamt - Determines how much the weed level will be adjusted upwards or downwards.
*/
/obj/machinery/hydroponics/proc/adjust_weedlevel (amt)
set_weedlevel(clamp(weedlevel + amt, 0, MAX_TRAY_WEEDS), FALSE)
/obj/machinery/hydroponics/examine(user)
. = ..()
if(myseed)
. += span_info("It has [span_name("[myseed.plantname]")] planted.")
if (plant_status == HYDROTRAY_PLANT_DEAD)
. += span_warning("It's dead!")
else if (plant_status == HYDROTRAY_PLANT_HARVESTABLE)
. += span_info("It's ready to harvest.")
else if (plant_health <= (myseed.endurance / 2))
. += span_warning("It looks unhealthy.")
else
. += span_info("It's empty.")
. += span_info("Water: [waterlevel]/[maxwater].")
. += span_info("Nutrient: [reagents.total_volume]/[maxnutri].")
if(self_sustaining)
. += span_info("The tray's autogrow is active, protecting it from species mutations, weeds, and pests.")
if(weedlevel >= 5)
. += span_warning("It's filled with weeds!")
if(pestlevel >= 5)
. += span_warning("It's filled with tiny worms!")
/**
* What happens when a tray's weeds grow too large.
* Plants a new weed in an empty tray, then resets the tray.
*/
/obj/machinery/hydroponics/proc/weedinvasion()
var/oldPlantName
if(myseed) // In case there's nothing in the tray beforehand
oldPlantName = myseed.plantname
else
oldPlantName = "empty tray"
var/obj/item/seeds/new_seed
switch(rand(1,18)) // randomly pick predominative weed
if(16 to 18)
new_seed = new /obj/item/seeds/reishi(src)
if(14 to 15)
new_seed = new /obj/item/seeds/nettle(src)
if(12 to 13)
new_seed = new /obj/item/seeds/harebell(src)
if(10 to 11)
new_seed = new /obj/item/seeds/amanita(src)
if(8 to 9)
new_seed = new /obj/item/seeds/chanter(src)
if(6 to 7)
new_seed = new /obj/item/seeds/tower(src)
if(4 to 5)
new_seed = new /obj/item/seeds/plump(src)
else
new_seed = new /obj/item/seeds/starthistle(src)
set_seed(new_seed)
age = 0
lastcycle = world.time
set_plant_health(myseed.endurance, update_icon = FALSE)
set_weedlevel(0, update_icon = FALSE) // Reset
set_pestlevel(0) // Reset
visible_message(span_warning("The [oldPlantName] is overtaken by some [myseed.plantname]!"))
/obj/machinery/hydroponics/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25, wrmut = 2, wcmut = 5, traitmut = 0, stabmut = 3) // Mutates the current seed
if(!myseed)
return
myseed.mutate(lifemut, endmut, productmut, yieldmut, potmut, wrmut, wcmut, traitmut, stabmut)
/obj/machinery/hydroponics/proc/hardmutate(lifemut = 4, endmut = 10, productmut = 2, yieldmut = 4, potmut = 50, wrmut = 4, wcmut = 10, traitmut = 0, stabmut = 4)
mutate(lifemut, endmut, productmut, yieldmut, potmut, wrmut, wcmut, traitmut, stabmut)
/obj/machinery/hydroponics/proc/mutatespecie() // Mutagent produced a new plant!
if(!myseed || plant_status == HYDROTRAY_PLANT_DEAD || !LAZYLEN(myseed.mutatelist))
return
var/oldPlantName = myseed.plantname
var/mutantseed = pick(myseed.mutatelist)
set_seed(new mutantseed(src))
hardmutate()
age = 0
set_plant_health(myseed.endurance, update_icon = FALSE)
lastcycle = world.time
set_weedlevel(0, update_icon = FALSE)
var/message = span_warning("[oldPlantName] suddenly mutates into [myseed.plantname]!")
addtimer(CALLBACK(src, PROC_REF(after_mutation), message), 0.5 SECONDS)
/obj/machinery/hydroponics/proc/polymorph() // Polymorph a plant into another plant
if(!myseed || plant_status == HYDROTRAY_PLANT_DEAD)
return
var/oldPlantName = myseed.plantname
var/polymorph_seed = pick(subtypesof(/obj/item/seeds))
set_seed(new polymorph_seed(src))
hardmutate()
age = 0
set_plant_health(myseed.endurance, update_icon = FALSE)
lastcycle = world.time
set_weedlevel(0, update_icon = FALSE)
var/message = span_warning("[oldPlantName] suddenly polymorphs into [myseed.plantname]!")
addtimer(CALLBACK(src, PROC_REF(after_mutation), message), 0.5 SECONDS)
/obj/machinery/hydroponics/proc/mutateweed() // If the weeds gets the mutagent instead. Mind you, this pretty much destroys the old plant
if( weedlevel > 5 )
set_seed(null)
var/newWeed = pick(/obj/item/seeds/liberty, /obj/item/seeds/angel, /obj/item/seeds/nettle/death, /obj/item/seeds/kudzu)
set_seed(new newWeed(src))
hardmutate()
age = 0
set_plant_health(myseed.endurance, update_icon = FALSE)
lastcycle = world.time
set_weedlevel(0, update_icon = FALSE) // Reset
var/message = span_warning("The mutated weeds in [src] spawn some [myseed.plantname]!")
addtimer(CALLBACK(src, PROC_REF(after_mutation), message), 0.5 SECONDS)
else
to_chat(usr, span_warning("The few weeds in [src] seem to react, but only for a moment..."))
/**
* Called after plant mutation, update the appearance of the tray content and send a visible_message()
*/
/obj/machinery/hydroponics/proc/after_mutation(message)
visible_message(message)
update_appearance()
/**
* Plant Death Proc.
* Cleans up various stats for the plant upon death, including pests, harvestability, and plant health.
*/
/obj/machinery/hydroponics/proc/plantdies()
set_plant_health(0, update_icon = FALSE, forced = TRUE)
set_plant_status(HYDROTRAY_PLANT_DEAD)
set_pestlevel(0, update_icon = FALSE) // Pests die
lastproduce = 0
update_appearance()
SEND_SIGNAL(src, COMSIG_HYDROTRAY_PLANT_DEATH)
/**
* Plant Cross-Pollination.
* Checks all plants in the tray's oview range, then averages out the seed's potency, instability, and yield values.
* If the seed's instability is >= 20, the seed donates one of it's reagents to that nearby plant.
* * Range - The Oview range of trays to which to look for plants to donate reagents.
*/
/obj/machinery/hydroponics/proc/pollinate(range = 1)
var/any_adjacent = FALSE
for(var/obj/machinery/hydroponics/T in oview(src, range))
//Here is where we check for window blocking.
if(!Adjacent(T) && range <= 1)
continue
if(T.myseed && T.plant_status != HYDROTRAY_PLANT_DEAD)
T.myseed.set_potency(round((T.myseed.potency+(1/10)*(myseed.potency-T.myseed.potency))))
T.myseed.set_instability(round((T.myseed.instability+(1/10)*(myseed.instability-T.myseed.instability))))
T.myseed.set_yield(round((T.myseed.yield+(1/2)*(myseed.yield-T.myseed.yield))))
any_adjacent = TRUE
if(isnull(particles))
particles = new /particles/pollen()
if(myseed.instability >= 20 && prob(70) && length(T.myseed.reagents_add))
var/list/datum/plant_gene/reagent/possible_reagents = list()
for(var/datum/plant_gene/reagent/reag in T.myseed.genes)
possible_reagents += reag
var/datum/plant_gene/reagent/reagent_gene = pick(possible_reagents) //Let this serve as a lession to delete your WIP comments before merge.
if(reagent_gene.can_add(myseed))
if(!reagent_gene.try_upgrade_gene(myseed))
myseed.genes += reagent_gene.Copy()
myseed.reagents_from_genes()
continue
if(!any_adjacent)
particles = null
/**
* Bee pollinate proc.
* Checks if the bee can pollinate the plant
*/
/obj/machinery/hydroponics/proc/can_bee_pollinate()
if(isnull(myseed))
return FALSE
if(plant_status == HYDROTRAY_PLANT_DEAD || recent_bee_visit)
return FALSE
return TRUE
/**
* Pest Mutation Proc.
* When a tray is mutated with high pest values, it will spawn spiders.
* * User - Person who last added chemicals to the tray for logging purposes.
*/
/obj/machinery/hydroponics/proc/mutatepest(mob/user)
if(pestlevel > 5)
message_admins("[ADMIN_LOOKUPFLW(user)] last altered a hydro tray's contents which spawned spiderlings.")
user.log_message("last altered a hydro tray, which spiderlings spawned from.", LOG_GAME)
visible_message(span_warning("The pests seem to behave oddly..."))
spawn_atom_to_turf(/mob/living/basic/spider/growing/spiderling/hunter, src, 3, FALSE)
else if(myseed)
visible_message(span_warning("The pests seem to behave oddly in [myseed.name] tray, but quickly settle down..."))
/obj/machinery/hydroponics/wrench_act(mob/living/user, obj/item/tool)
. = ..()
default_unfasten_wrench(user, tool)
return ITEM_INTERACT_SUCCESS
/obj/machinery/hydroponics/attackby(obj/item/O, mob/user, params)
//Called when mob user "attacks" it with object O
if(IS_EDIBLE(O) || is_reagent_container(O)) // Syringe stuff (and other reagent containers now too)
var/obj/item/reagent_containers/reagent_source = O
if(!reagent_source.reagents.total_volume)
to_chat(user, span_warning("[reagent_source] is empty!"))
return 1
if(reagents.total_volume >= reagents.maximum_volume && !reagent_source.reagents.has_reagent(/datum/reagent/water, 1))
to_chat(user, span_notice("[src] is full."))
return
var/list/trays = list(src)//makes the list just this in cases of syringes and compost etc
var/target = myseed ? myseed.plantname : src
var/visi_msg = ""
var/transfer_amount
if(IS_EDIBLE(reagent_source) || istype(reagent_source, /obj/item/reagent_containers/pill))
visi_msg="[user] composts [reagent_source], spreading it through [target]"
transfer_amount = reagent_source.reagents.total_volume
SEND_SIGNAL(reagent_source, COMSIG_ITEM_ON_COMPOSTED, user)
else
transfer_amount = min(reagent_source.amount_per_transfer_from_this, reagent_source.reagents.total_volume)
if(istype(reagent_source, /obj/item/reagent_containers/syringe/))
var/obj/item/reagent_containers/syringe/syr = reagent_source
visi_msg="[user] injects [target] with [syr]"
// Beakers, bottles, buckets, etc.
if(reagent_source.is_drainable())
playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE)
var/mutable_appearance/splash_animation = mutable_appearance('icons/effects/effects.dmi', "splash_hydroponics")
splash_animation.color = mix_color_from_reagents(reagent_source.reagents.reagent_list)
flick_overlay_view(splash_animation, 1.1 SECONDS)
if(visi_msg)
visible_message(span_notice("[visi_msg]."))
for(var/obj/machinery/hydroponics/H in trays)
//cause I don't want to feel like im juggling 15 tamagotchis and I can get to my real work of ripping flooring apart in hopes of validating my life choices of becoming a space-gardener
//This was originally in apply_chemicals, but due to apply_chemicals only holding nutrients, we handle it here now.
if(reagent_source.reagents.has_reagent(/datum/reagent/water))
var/water_amt = reagent_source.reagents.get_reagent_amount(/datum/reagent/water) * transfer_amount / reagent_source.reagents.total_volume
var/water_amt_adjusted = H.adjust_waterlevel(round(water_amt))
reagent_source.reagents.remove_reagent(/datum/reagent/water, water_amt_adjusted)
for(var/datum/reagent/not_water_reagent as anything in reagent_source.reagents.reagent_list)
if(istype(not_water_reagent,/datum/reagent/water))
continue
var/transfer_me_to_tray = reagent_source.reagents.get_reagent_amount(not_water_reagent.type) * transfer_amount / reagent_source.reagents.total_volume
reagent_source.reagents.trans_to(H.reagents, transfer_me_to_tray, target_id = not_water_reagent.type)
else
reagent_source.reagents.trans_to(H.reagents, transfer_amount, transferred_by = user)
lastuser = WEAKREF(user)
if(IS_EDIBLE(reagent_source) || istype(reagent_source, /obj/item/reagent_containers/pill))
qdel(reagent_source)
H.update_appearance()
return 1
H.update_appearance()
if(reagent_source) // If the source wasn't composted and destroyed
reagent_source.update_appearance()
return 1
else if(istype(O, /obj/item/seeds))
if(!myseed)
if(istype(O, /obj/item/seeds/kudzu))
investigate_log("had Kudzu planted in it by [key_name(user)] at [AREACOORD(src)].", INVESTIGATE_BOTANY)
if(!user.transferItemToLoc(O, src))
return
SEND_SIGNAL(O, COMSIG_SEED_ON_PLANTED, src)
to_chat(user, span_notice("You plant [O]."))
set_seed(O)
age = 1
set_plant_health(myseed.endurance)
lastcycle = world.time
return
else
to_chat(user, span_warning("[src] already has seeds in it!"))
return
else if(istype(O, /obj/item/cultivator))
if(weedlevel > 0)
user.visible_message(span_notice("[user] uproots the weeds."), span_notice("You remove the weeds from [src]."))
set_weedlevel(0)
return
else
to_chat(user, span_warning("This plot is completely devoid of weeds! It doesn't need uprooting."))
return
else if(istype(O, /obj/item/secateurs))
if(!myseed)
to_chat(user, span_notice("This plot is empty."))
return
else if(plant_status != HYDROTRAY_PLANT_HARVESTABLE)
to_chat(user, span_notice("This plant must be harvestable in order to be grafted."))
return
else if(myseed.grafted)
to_chat(user, span_notice("This plant has already been grafted."))
return
else
user.visible_message(span_notice("[user] grafts off a limb from [src]."), span_notice("You carefully graft off a portion of [src]."))
var/obj/item/graft/snip = myseed.create_graft()
if(!snip)
return // The plant did not return a graft.
snip.forceMove(drop_location())
myseed.grafted = TRUE
adjust_plant_health(-5)
return
else if(istype(O, /obj/item/geneshears))
if(!myseed)
to_chat(user, span_notice("The tray is empty."))
return
if(plant_health <= GENE_SHEAR_MIN_HEALTH)
to_chat(user, span_notice("This plant looks too unhealty to be sheared right now."))
return
var/list/current_traits = list()
for(var/datum/plant_gene/gene in myseed.genes)
if(islist(gene))
continue
if(!(gene.mutability_flags & PLANT_GENE_REMOVABLE))
continue // Don't show genes that can't be removed.
current_traits[gene.name] = gene
var/removed_trait = tgui_input_list(user, "Trait to remove from the [myseed.plantname]", "Plant Trait Removal", sort_list(current_traits))
if(isnull(removed_trait))
return
if(!user.can_perform_action(src))
return
if(!myseed)
return
if(plant_health <= GENE_SHEAR_MIN_HEALTH) //Check health again to make sure they're not keeping inputs open to get free shears.
return
for(var/datum/plant_gene/gene in myseed.genes)
if(gene.name == removed_trait)
if(myseed.genes.Remove(gene))
gene.on_removed(myseed)
qdel(gene)
break
myseed.reagents_from_genes()
adjust_plant_health(-15)
to_chat(user, span_notice("You carefully shear the genes off of the [myseed.plantname], leaving the plant looking weaker."))
update_appearance()
return
else if(istype(O, /obj/item/graft))
var/obj/item/graft/snip = O
if(!myseed)
to_chat(user, span_notice("The tray is empty."))
return
if(myseed.apply_graft(snip))
to_chat(user, "<span class='notice'>You carefully integrate the grafted plant limb onto [myseed.plantname], granting it [snip.stored_trait.get_name()].</span>")
else
to_chat(user, "<span class='warning'>You integrate the grafted plant limb onto [myseed.plantname], but it does not accept the [snip.stored_trait.get_name()] trait from the [snip].</span>")
qdel(snip)
return
else if(istype(O, /obj/item/storage/bag/plants))
if(plant_status == HYDROTRAY_PLANT_HARVESTABLE)
var/list/harvest = myseed.harvest(user)
for(var/obj/item/food/grown/G in harvest)
O.atom_storage?.attempt_insert(G, user, TRUE)
else if(plant_status == HYDROTRAY_PLANT_DEAD)
to_chat(user, span_notice("You remove the dead plant from [src]."))
set_seed(null)
return
else if(O.tool_behaviour == TOOL_SHOVEL)
if(!myseed && !weedlevel)
to_chat(user, span_warning("[src] doesn't have any plants or weeds!"))
return
user.visible_message(span_notice("[user] starts digging out [src]'s plants..."),
span_notice("You start digging out [src]'s plants..."))
if(O.use_tool(src, user, 50, volume=50) || (!myseed && !weedlevel))
user.visible_message(span_notice("[user] digs out the plants in [src]!"), span_notice("You dig out all of [src]'s plants!"))
if(myseed) //Could be that they're just using it as a de-weeder
age = 0
set_plant_health(0, update_icon = FALSE, forced = TRUE)
lastproduce = 0
set_seed(null)
set_weedlevel(0) //Has a side effect of cleaning up those nasty weeds
return
else if(istype(O, /obj/item/storage/part_replacer))
RefreshParts()
return
else if(istype(O, /obj/item/gun/energy/floragun))
var/obj/item/gun/energy/floragun/flowergun = O
if(flowergun.cell.charge < flowergun.cell.maxcharge)
to_chat(user, span_notice("[flowergun] must be fully charged to lock in a mutation!"))
return
if(!myseed)
to_chat(user, span_warning("[src] is empty!"))
return
if(myseed.endurance <= FLORA_GUN_MIN_ENDURANCE)
to_chat(user, span_warning("[myseed.plantname] isn't hardy enough to sequence it's mutation!"))
return
if(!LAZYLEN(myseed.mutatelist))
to_chat(user, span_warning("[myseed.plantname] has nothing else to mutate into!"))
return
else
var/list/fresh_mut_list = list()
for(var/muties in myseed.mutatelist)
var/obj/item/seeds/another_mut = new muties
fresh_mut_list[another_mut.plantname] = muties
var/locked_mutation = tgui_input_list(user, "Mutation to lock", "Plant Mutation Locks", sort_list(fresh_mut_list))
if(isnull(locked_mutation))
return
if(isnull(fresh_mut_list[locked_mutation]))
return
if(!user.can_perform_action(src))
return
myseed.mutatelist = list(fresh_mut_list[locked_mutation])
myseed.set_endurance(myseed.endurance/2)
flowergun.cell.use(flowergun.cell.charge)
flowergun.update_appearance()
to_chat(user, span_notice("[myseed.plantname]'s mutation was set to [locked_mutation], depleting [flowergun]'s cell!"))
return
else
return ..()
/obj/machinery/hydroponics/attackby_secondary(obj/item/weapon, mob/user, params)
if (istype(weapon, /obj/item/reagent_containers/syringe))
to_chat(user, span_warning("You can't get any extract out of this plant."))
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
return SECONDARY_ATTACK_CALL_NORMAL
/obj/machinery/hydroponics/can_be_unfasten_wrench(mob/user, silent)
if (!unwrenchable) // case also covered by NODECONSTRUCT checks in default_unfasten_wrench
return CANT_UNFASTEN
return ..()
/obj/machinery/hydroponics/attack_hand(mob/user, list/modifiers)
. = ..()
if(.)
return
if(HAS_SILICON_ACCESS(user)) //How does AI know what plant is?
return
if(plant_status == HYDROTRAY_PLANT_HARVESTABLE)
return myseed.harvest(user)
else if(plant_status == HYDROTRAY_PLANT_DEAD)
to_chat(user, span_notice("You remove the dead plant from [src]."))
set_seed(null)
else
if(user)
user.examinate(src)
/obj/machinery/hydroponics/CtrlClick(mob/user)
. = ..()
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return
if(!powered())
to_chat(user, span_warning("[name] has no power."))
update_use_power(NO_POWER_USE)
return
if(!anchored)
return
set_self_sustaining(!self_sustaining)
to_chat(user, span_notice("You [self_sustaining ? "activate" : "deactivated"] [src]'s autogrow function[self_sustaining ? ", maintaining the tray's health while using high amounts of power" : ""]."))
/obj/machinery/hydroponics/attack_hand_secondary(mob/user, list/modifiers)
. = ..()
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
return
if(!anchored)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
var/warning = tgui_alert(user, "Are you sure you wish to empty the tray's nutrient beaker?","Empty Tray Nutrients?", list("Yes", "No"))
if(warning == "Yes" && user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
reagents.clear_reagents()
to_chat(user, span_warning("You empty [src]'s nutrient tank."))
update_appearance()
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/**
* Update Tray Proc
* Handles plant harvesting on the tray side, by clearing the seed, names, description, and dead stat.
* Shuts off autogrow if enabled.
* Sends messages to the cleaer about plants harvested, or if nothing was harvested at all.
* * User - The mob who clears the tray.
*/
/obj/machinery/hydroponics/proc/update_tray(mob/user, product_count)
lastproduce = age
if(istype(myseed, /obj/item/seeds/replicapod))
to_chat(user, span_notice("You harvest from the [myseed.plantname]."))
else if(product_count <= 0)
to_chat(user, span_warning("You fail to harvest anything useful!"))
else
to_chat(user, span_notice("You harvest [product_count] items from the [myseed.plantname]."))
if(!myseed.get_gene(/datum/plant_gene/trait/repeated_harvest))
set_seed(null)
if(self_sustaining) //No reason to pay for an empty tray.
set_self_sustaining(FALSE)
else
set_plant_status(HYDROTRAY_PLANT_GROWING)
update_appearance()
SEND_SIGNAL(src, COMSIG_HYDROTRAY_ON_HARVEST, user, product_count)
/**
* Spawn Plant.
* Upon using strange reagent on a tray, it will spawn a killer tomato or killer tree at random.
*/
/obj/machinery/hydroponics/proc/spawnplant() // why would you put strange reagent in a hydro tray you monster I bet you also feed them blood
var/list/livingplants = list(/mob/living/basic/tree, /mob/living/basic/killer_tomato)
var/chosen = pick(livingplants)
var/mob/living/C = new chosen(get_turf(src))
C.faction = list(FACTION_PLANTS)
///////////////////////////////////////////////////////////////////////////////
/obj/machinery/hydroponics/soil //Not actually hydroponics at all! Honk!
name = "soil"
desc = "A patch of dirt."
icon = 'icons/obj/service/hydroponics/equipment.dmi'
icon_state = "soil"
gender = PLURAL
circuit = null
density = FALSE
use_power = NO_POWER_USE
unwrenchable = FALSE
self_sustaining_overlay_icon_state = null
maxnutri = 15
/obj/machinery/hydroponics/soil/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/screwdriver)
return NONE
/obj/machinery/hydroponics/soil/default_deconstruction_crowbar(obj/item/crowbar, ignore_panel, custom_deconstruct)
return NONE
/obj/machinery/hydroponics/soil/update_icon(updates=ALL)
. = ..()
if(self_sustaining)
add_atom_colour(rgb(255, 175, 0), FIXED_COLOUR_PRIORITY)
/obj/machinery/hydroponics/soil/update_status_light_overlays()
return // Has no lights
/obj/machinery/hydroponics/soil/attackby_secondary(obj/item/weapon, mob/user, params)
if(weapon.tool_behaviour != TOOL_SHOVEL) //Spades can still uproot plants on left click
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
balloon_alert(user, "clearing up soil...")
if(weapon.use_tool(src, user, 1 SECONDS, volume=50))
balloon_alert(user, "cleared")
deconstruct(disassembled = TRUE)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/machinery/hydroponics/soil/CtrlClick(mob/user)
return //Soil has no electricity.
/obj/machinery/hydroponics/soil/on_deconstruction(disassembled)
new /obj/item/stack/ore/glass(drop_location(), 3)
///The usb port circuit
/obj/item/circuit_component/hydroponics
display_name = "Hydropnics Tray"
desc = "Automate the means of botanical production. Trigger to toggle auto-grow."
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
var/obj/machinery/hydroponics/attached_tray
///If self-sustaining (also called auto-grow) should be turned on or off when the trigger is triggered.
var/datum/port/input/selfsustaining_setting
///Whether the plant in the tray is harvestable, alive, missing or dead.
var/datum/port/output/plant_status
///Whether the self sustaining mode is on
var/datum/port/output/is_self_sustaining
///Triggered when the plant is harvested
var/datum/port/output/plant_harvested
///The product amount of the last harvest
var/datum/port/output/last_harvest
///Triggered when the plant dies
var/datum/port/output/plant_died
///Triggered when a seed is either planted by someone or takes over the tray.
var/datum/port/output/seeds_planted
///The amount of water in the tray.
var/datum/port/output/water_level
///The amount of toxins in the tray.
var/datum/port/output/toxic_level
///The amount of pests in the tray.
var/datum/port/output/pests_level
///The amount of weeds in the tray.
var/datum/port/output/weeds_level
///The health of the plant in the tray.
var/datum/port/output/plant_health
///The amount of reagents in the tray
var/datum/port/output/reagents_level
/obj/item/circuit_component/hydroponics/populate_ports()
selfsustaining_setting = add_input_port("Auto-Grow Setting", PORT_TYPE_NUMBER)
plant_status = add_output_port("Plant Status", PORT_TYPE_NUMBER)
is_self_sustaining = add_output_port("Auto-Grow Status", PORT_TYPE_NUMBER)
plant_harvested = add_output_port("Plant Harvested", PORT_TYPE_SIGNAL)
last_harvest = add_output_port("Last Harvest Amount", PORT_TYPE_NUMBER)
plant_died = add_output_port("Plant Died", PORT_TYPE_SIGNAL)
seeds_planted = add_output_port("Seeds Planted", PORT_TYPE_SIGNAL)
water_level = add_output_port("Water Level", PORT_TYPE_NUMBER)
toxic_level = add_output_port("Toxins Level", PORT_TYPE_NUMBER)
pests_level = add_output_port("Pests Level", PORT_TYPE_NUMBER)
weeds_level = add_output_port("Weeds Level", PORT_TYPE_NUMBER)
plant_health = add_output_port("Plant Health", PORT_TYPE_NUMBER)
reagents_level = add_output_port("Reagents Level", PORT_TYPE_NUMBER)
/obj/item/circuit_component/hydroponics/register_usb_parent(atom/movable/parent)
. = ..()
if(istype(parent, /obj/machinery/hydroponics))
attached_tray = parent
RegisterSignal(attached_tray, COMSIG_HYDROTRAY_SET_SEED, PROC_REF(on_set_seed))
RegisterSignal(attached_tray, COMSIG_HYDROTRAY_SET_SELFSUSTAINING, PROC_REF(on_set_selfsustaining))
RegisterSignal(attached_tray, COMSIG_HYDROTRAY_SET_WEEDLEVEL, PROC_REF(on_set_weedlevel))
RegisterSignal(attached_tray, COMSIG_HYDROTRAY_SET_PESTLEVEL, PROC_REF(on_set_pestlevel))
RegisterSignal(attached_tray, COMSIG_HYDROTRAY_SET_WATERLEVEL, PROC_REF(on_set_waterlevel))
RegisterSignal(attached_tray, COMSIG_HYDROTRAY_SET_PLANT_HEALTH, PROC_REF(on_set_plant_health))
RegisterSignal(attached_tray, COMSIG_HYDROTRAY_SET_TOXIC, PROC_REF(on_set_toxic_level))
RegisterSignal(attached_tray, COMSIG_HYDROTRAY_SET_PLANT_STATUS, PROC_REF(on_set_plant_status))
RegisterSignal(attached_tray, COMSIG_HYDROTRAY_ON_HARVEST, PROC_REF(on_harvest))
RegisterSignal(attached_tray, COMSIG_HYDROTRAY_PLANT_DEATH, PROC_REF(on_plant_death))
var/list/reagents_holder_signals = list(
COMSIG_REAGENTS_ADD_REAGENT,
COMSIG_REAGENTS_REM_REAGENT,
COMSIG_REAGENTS_NEW_REAGENT,
COMSIG_REAGENTS_DEL_REAGENT,
)
RegisterSignal(attached_tray, reagents_holder_signals, PROC_REF(update_reagents_level))
/obj/item/circuit_component/hydroponics/unregister_usb_parent(atom/movable/parent)
attached_tray = null
UnregisterSignal(parent, list(COMSIG_HYDROTRAY_SET_SEED, COMSIG_HYDROTRAY_SET_SELFSUSTAINING,
COMSIG_HYDROTRAY_SET_WEEDLEVEL, COMSIG_HYDROTRAY_SET_PESTLEVEL, COMSIG_HYDROTRAY_SET_WATERLEVEL,
COMSIG_HYDROTRAY_SET_PLANT_HEALTH, COMSIG_HYDROTRAY_SET_TOXIC, COMSIG_HYDROTRAY_SET_PLANT_STATUS,
COMSIG_HYDROTRAY_ON_HARVEST, COMSIG_HYDROTRAY_PLANT_DEATH))
if(parent.reagents)
UnregisterSignal(parent.reagents, list(COMSIG_REAGENTS_ADD_REAGENT, COMSIG_REAGENTS_REM_REAGENT,
COMSIG_REAGENTS_NEW_REAGENT, COMSIG_REAGENTS_DEL_REAGENT))
return ..()
/obj/item/circuit_component/hydroponics/get_ui_notices()
. = ..()
. += create_ui_notice("Plant Status Index: \"[HYDROTRAY_NO_PLANT]\", \"[HYDROTRAY_PLANT_GROWING]\", \"[HYDROTRAY_PLANT_DEAD]\", \"[HYDROTRAY_PLANT_HARVESTABLE]\"", "orange", "info")
/obj/item/circuit_component/hydroponics/proc/on_set_seed(datum/source, obj/item/seeds/new_seed)
SIGNAL_HANDLER
seeds_planted.set_output(COMPONENT_SIGNAL)
/obj/item/circuit_component/hydroponics/proc/on_set_selfsustaining(datum/source, new_value)
SIGNAL_HANDLER
is_self_sustaining.set_output(new_value)
/obj/item/circuit_component/hydroponics/proc/on_set_weedlevel(datum/source, new_value)
SIGNAL_HANDLER
weeds_level.set_output(new_value)
/obj/item/circuit_component/hydroponics/proc/on_set_pestlevel(datum/source, new_value)
SIGNAL_HANDLER
pests_level.set_output(new_value)
/obj/item/circuit_component/hydroponics/proc/on_set_waterlevel(datum/source, new_value)
SIGNAL_HANDLER
water_level.set_output(new_value)
/obj/item/circuit_component/hydroponics/proc/on_set_plant_health(datum/source, new_value)
SIGNAL_HANDLER
plant_health.set_output(new_value)
/obj/item/circuit_component/hydroponics/proc/on_set_toxic_level(datum/source, new_value)
SIGNAL_HANDLER
toxic_level.set_output(new_value)
/obj/item/circuit_component/hydroponics/proc/on_set_plant_status(datum/source, new_value)
SIGNAL_HANDLER
plant_status.set_output(new_value)
/obj/item/circuit_component/hydroponics/proc/on_harvest(datum/source, product_amount)
SIGNAL_HANDLER
last_harvest.set_output(product_amount)
plant_harvested.set_output(COMPONENT_SIGNAL)
/obj/item/circuit_component/hydroponics/proc/on_plant_death(datum/source)
SIGNAL_HANDLER
plant_died.set_output(COMPONENT_SIGNAL)
/obj/item/circuit_component/hydroponics/proc/update_reagents_level(datum/source)
SIGNAL_HANDLER
reagents_level.set_output(attached_tray.reagents.total_volume)
/obj/item/circuit_component/hydroponics/input_received(datum/port/input/port)
if(attached_tray.anchored && attached_tray.powered())
attached_tray.set_self_sustaining(!!selfsustaining_setting.value)