Compare commits

..
Author SHA1 Message Date
Vanessa 5ed135c796 lol 2026-06-29 10:21:38 -05:00
BriBroooandpre-commit-ci[bot] 448b755b75 Fixes messageId console errors (#6143)
* Fixes messageId console errors

Replaced paper.yml HydroCo's notices in the paper-misc.ftl file and moved the [bold] to the last line to prevent error messages in the console

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-29 09:59:09 -05:00
Delta-V bot 977e47b434 Automatic changelog update 2026-06-29 16:06:38 +02:00
Vanessa ee18b71805 Various Crew Monitor Fixes/Tweaks (#6139)
* Various Crew Monitor Fixes/Tweaks

* comment

* comment

* Increased wireless ranges
2026-06-29 14:06:18 +00:00
29b64c1f7e Suffix and other entity naming megafix (#6091)
* Suffix and other entity naming megafix

* migration

* others

---------

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-06-29 14:00:08 +00:00
Delta-V bot a99d488b04 Automatic changelog update 2026-06-29 15:39:43 +02:00
f18d6803b9 PORT: DV's Pool Episode - Longue Chairs + Swim Rings + Pool Tiles & Water (#6113)
* Pool-Themed Decorations: Lounge Chairs, Umbrellas, Tiles, Swim Rings, and Pool Water (#2071)

<!-- Guidelines:
https://docs.spacestation14.io/en/getting-started/pr-guideline -->
<!-- NOTE: All code submitted to this repository is ALWAYS licensed
under the AGPL-3.0-or-later license.
The REUSE Specification headers or separate .license files indicate a
secondary license (e.g., MPL or MIT) solely to facilitate
integration for projects that do not use the AGPL license. This
secondary license does not replace the fact that AGPL-3.0-or-later
remains the primary and binding license.
Uncomment and modify the following line if you wish to change the
license from the default of AGPL.-->
<!--- LICENSE: AGPL -->
<!-- What did you change? -->

This PR adds a bunch of, well, mapper toys basically, but normal players
can obtain them too. They are:
- Pool tile and dark pool tile; obtainable via the cutter machine.
- Pool water; unobtainable without mapping/admin tools, but it's 1%
chlorine.
- Lounge chairs, constructed with steel and cloth. Deconstructible with
a screwdriver.
- Beach umbrellas, constructed with rods and cloth. Deconstructible with
a screwdriver.
- Swim rings, which can be ordered from Logistics. They're sort of like
the emergency comfort units (pillows), but they're inflatable rings and
you can pick them up.

<!-- Discuss how this would affect game balance or explain why it was
changed. Link any relevant discussions or issues. -->
These assets are needed for an upcoming map I'm making and I'm giving
them to the community early out of the kindness of my heart

<!-- Summary of code changes for easier review. -->
All YML and sprites babyyy

- Entity prototypes for chairs, umbrellas, pool water, swim rings, and
the pool tile stack items
- Some construction graphs
- A cargo product
- A floor definition
- Recipes for the tile cutter

Pool water IS drinkable because it's extremely funny. Keep in mind that
50u of pool water will only give you like 2 poison damage

<!-- Attach media if the PR makes ingame changes (clothing, items,
features, etc).
Small fixes/refactors are exempt. Media may be used in SS14 progress
reports with credit. -->

Preview of the stuff:

<img width="713" height="541" alt="image"
src="https://github.com/user-attachments/assets/42f1acc8-cc83-4356-a8e0-3c715245d120"
/>

<img width="697" height="666" alt="image"
src="https://github.com/user-attachments/assets/8597017a-9d03-4419-80c5-cf475f3206d1"
/>

sorry i forgot the pink ring

<img width="253" height="179" alt="image"
src="https://github.com/user-attachments/assets/7f738f8e-42b4-4ee4-aa75-9f08b67ded0c"
/>

sorry i forgot the white chair

<img width="206" height="140" alt="image"
src="https://github.com/user-attachments/assets/6fabe541-4821-4e88-8d64-a99f463c5253"
/>

Cargo:

<img width="633" height="461" alt="image"
src="https://github.com/user-attachments/assets/b8ae9b89-4d2f-4326-9814-d000d5e88809"
/>

Crafting menu:

<img width="266" height="454" alt="image"
src="https://github.com/user-attachments/assets/cc480ee6-0e0d-4055-93c3-e5a1c4e0687c"
/>

<img width="409" height="211" alt="image"
src="https://github.com/user-attachments/assets/41970a43-1ba8-4898-a96c-deb87a05a409"
/>

<img width="409" height="211" alt="image"
src="https://github.com/user-attachments/assets/ffc00874-e330-49eb-8aba-972c6d223a3a"
/>

Pool water:

<img width="680" height="463" alt="image"
src="https://github.com/user-attachments/assets/9abc48b0-8358-46de-89ef-41fc64989787"
/>

<!-- Confirm the following by placing an X in the brackets [X]: -->
- [x] I have read and am following the [Pull Request and Changelog
Guidelines](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html).
- [x] I have added media to this PR or it does not require an ingame
showcase.
- [x] I have tested any changes or additions.
- [x] I can confirm this PR contains no AI-generated content, and did
not use any AI-generated content.
<!-- You should understand that not following the above may get your PR
closed at maintainer’s discretion -->

<!-- List any breaking changes, including namespaces, public
class/method/field changes, prototype renames; and provide instructions
for fixing them.
This will be posted in #codebase-changes. -->

Nope

**Changelog**
<!-- Add a Changelog entry to make players aware of new features or
changes that could affect gameplay.
Make sure to read the guidelines and take this Changelog template out of
the comment block in order for it to show up.
Changelog must have a 🆑 symbol, so the bot recognizes the changes and
adds them to the game's changelog. -->
🆑
- add: 13 colors of lounge chairs can be constructed out of steel and
cloth.
- add: 8 colors of beach umbrellas can be constructed out of rods and
cloth.
- add: Pool tile (and dark pool tile) can be produced at the cutter
machine.
- add: Logistics can now order a crate of bulk swim rings, in 11
possible colors!
- add: For mappers (and admins): there is a new "pool water" entity that
is a nice green-cyan color and contains about 1% chlorine.

---------

Co-authored-by: sleepyyapril <123355664+sleepyyapril@users.noreply.github.com>

* Remove Umbrella + Fixes from Port

* Pool Ring Additions

* Buff Fishing Vendor

* grr

* its no longer 2am

---------

Co-authored-by: portfiend <109661617+portfiend@users.noreply.github.com>
Co-authored-by: sleepyyapril <123355664+sleepyyapril@users.noreply.github.com>
2026-06-29 08:39:22 -05:00
Delta-V bot db38e48754 Automatic changelog update 2026-06-29 15:27:30 +02:00
MD360 3acbf512be Borgs must amogus. (#6126)
* We must scream

* +1 License

* Moved to _DV audio

* + Comment

* forgot space
2026-06-29 13:27:10 +00:00
pathetic meowmeowandVanessa cb956f605f Use the actual backported engine fix (#6138)
* Revert "I hate submodules. Why did it have to be submodules? (#6117)"

This reverts commit d3b13a6c83.

* from one workaround to another

---------

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-06-29 13:43:52 +02:00
Delta-V bot e6e094a7a1 Automatic changelog update 2026-06-29 11:46:17 +02:00
Manoob b7d8476473 Make pigs drop bacon and adjust a few recipes to do with bacon/pork (#6122)
* Make pigs drop bacon and adjust a few recipes to do with bacon/pork

* Only one piece of bacon for rice and pork!

Signed-off-by: Manoob <manu.demoen@gmail.com>

* Make pigs drop bacon AND meat

* Add 3 bacon to the chef-vend inventories

---------

Signed-off-by: Manoob <manu.demoen@gmail.com>
2026-06-29 11:45:57 +02:00
Delta-V bot 3df4bfaeb7 Automatic changelog update 2026-06-29 08:05:44 +02:00
Apachito 29b46efa16 Rebalanced Tide Crafting (#5818) 2026-06-29 02:05:24 -04:00
Vanessa e7607e1227 I love submodules. Always use submodules. (#6119)
Revert "I hate submodules. Why did it have to be submodules? (#6117)"

This reverts commit d3b13a6c83.
2026-06-29 00:16:42 -05:00
Delta-V bot 460dbe1fc1 Automatic changelog update 2026-06-29 02:54:28 +02:00
pathetic meowmeow c7c6f1a67a Add nodecrawling, implement ventcrawl with it (#5830)
* node crawling movement

* more node crawling

* quality assurance

* do-after it

* i'm ratted king

* we don't need to serialize node stuff

* rename and ser/de fix

* review fixes
2026-06-29 00:54:08 +00:00
Delta-V bot 495107e5e0 Automatic changelog update 2026-06-28 21:15:54 +02:00
pathetic meowmeowandpre-commit-ci[bot] 8366dde288 Rewrite ion laws (#6121)
* Rewrite ion laws

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* edits

* get gooned

* jumpsuit clarity

* review and tests

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-28 21:15:35 +02:00
Delta-V bot 5f9315f43b Automatic changelog update 2026-06-28 18:42:54 +02:00
Stxcking c21ddfeb41 Tweaks: New Fishing Vendor Stock and Price Changes (#6114)
* man

* Update fishing_points.yml
2026-06-28 11:42:34 -05:00
Delta-V bot 9b0eef0805 Automatic changelog update 2026-06-28 18:42:21 +02:00
StxckingandRadezolid 36bc3d0b04 The Disappearance of Hatsune Miku (#5790)
* I HATE FUN

* uh

* make it actually parent

* Update Resources/Prototypes/_DV/Catalog/Fills/Boxes/general.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Stxcking <217132821+Stxcking@users.noreply.github.com>

---------

Signed-off-by: Stxcking <217132821+Stxcking@users.noreply.github.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
2026-06-28 11:42:01 -05:00
Delta-V bot e9b6634747 Automatic changelog update 2026-06-28 15:26:15 +02:00
Vape d1390f5e98 Update tips.ftl (#6133) 2026-06-28 15:25:56 +02:00
Delta-V bot 196e2a52e3 Automatic changelog update 2026-06-28 14:19:16 +02:00
Proxy also works :3 401ae01321 Reduce Holoparasite Cost (#6115)
Update uplink_catalog.yml
2026-06-28 14:18:56 +02:00
github-actions[bot]andDeltaV-Bot 793236f12b Update Credits (#6135)
Co-authored-by: DeltaV-Bot <github@deltav.gay>
2026-06-28 04:52:00 +02:00
Delta-V bot 229491aa46 Automatic changelog update 2026-06-28 02:39:01 +02:00
Vape a09e09ac0f Remove mention of "morphotypes" (#6134) 2026-06-27 19:38:41 -05:00
Delta-V bot e59b37b081 Automatic changelog update 2026-06-27 22:18:26 +02:00
Vape b359f44c15 Fix: Fixes to both Harpy and Moth (#6131)
Bug Fix
2026-06-27 15:18:06 -05:00
Delta-V bot 932dd5e429 Automatic changelog update 2026-06-26 19:23:56 +02:00
wesker 3001253e43 Synthetic Emotes Fix (#6125)
* separate synthetic emote

* fix duplicate IDs (server error)

* Update speech_emotes.yml

* maybe this

* maybe keep the removed things from upstream lol

* imagine yamlops-ing when the problem was your trash code all along
2026-06-26 19:23:37 +02:00
Vanessa d3b13a6c83 I hate submodules. Why did it have to be submodules? (#6117)
Living on a prayer.
2026-06-25 15:07:33 -05:00
Delta-V bot 2ab0163598 Automatic changelog update 2026-06-25 20:04:07 +02:00
pathetic meowmeow 6cdeb3d9cf Suit sensors, but in space? Perish the thought. (#6054) 2026-06-25 12:59:49 -05:00
Delta-V bot d7543bd24a Automatic changelog update 2026-06-25 18:17:46 +02:00
f7f800c439 Tweaked Synthetic (#6090)
* actually not overhauled but tweaked i think

* forgot to stage this one

* make shocking 60% chance instead of 80%, and make shock dmg 5-20 random.

* update comment

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* okay this should work + remember to mention new changesa asdas asd

* I ALREADY STAGED ALL

* important summary comments to give more important info when someone's
too lazy to check the rule

* fix some stuff

* signs of shock

* Update Resources/Prototypes/_DV/Traits/meta.yml

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Signed-off-by: wesker <53323309+weskified@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Traits/meta.yml

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Signed-off-by: wesker <53323309+weskified@users.noreply.github.com>

* Update Resources/Locale/en-US/_DV/traits/traits.ftl

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Signed-off-by: wesker <53323309+weskified@users.noreply.github.com>

* changessss

---------

Signed-off-by: wesker <53323309+weskified@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-06-25 11:17:26 -05:00
Delta-V bot b6ed84325f Automatic changelog update 2026-06-25 17:37:53 +02:00
ManoobandVanessa 61aee79dc2 Replace nachos recipes (#6099)
* make nachos from tacos and change how cheesy and chili variants get made

* Change WizDen Contentfreeze recipelimit

* Update Resources/Prototypes/_DV/Recipes/Cooking/deep_fryer_recipes.yml

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Signed-off-by: Manoob <manu.demoen@gmail.com>

* Moved nacho metamorph to DV folder

* newlline, keep file as it was :^)

---------

Signed-off-by: Manoob <manu.demoen@gmail.com>
Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-06-25 10:37:33 -05:00
Delta-V bot 55fe6026ea Automatic changelog update 2026-06-25 17:28:10 +02:00
pathetic meowmeowandVanessa 6dfa12a509 AI? Being able to send faxes? Perish the thought. (#6064)
* AI? Being able to send faxes? Perish the thought.

* Update Resources/Prototypes/Entities/Mobs/Player/silicon.yml

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>

---------

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-06-25 15:27:49 +00:00
Delta-V bot 485fd14f05 Automatic changelog update 2026-06-25 16:07:32 +02:00
Theo 031b37ffcb Washing machines can hold huge items (#6086)
washer size huge
2026-06-25 09:07:12 -05:00
Delta-V bot 06365bdbf0 Automatic changelog update 2026-06-25 15:10:58 +02:00
Tmanzxd 7f18c5f346 Commented out Surgeon Job Requirement for CMO (#6112) 2026-06-25 13:10:38 +00:00
Delta-V bot d407652eb0 Automatic changelog update 2026-06-25 04:28:46 +02:00
Manoob 9108e2c9b5 Fix Low metabolism rate modifiers from truncating to zero (#6104) 2026-06-24 21:28:26 -05:00
VelcroboyandVelcroboy c7ef614f3d Edit towel spawner (#6106)
* Edit towel spawner

* numbers

---------

Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
2026-06-24 21:26:51 -05:00
VelcroboyandVelcroboy fa735f7669 Fix Chapel Access (#6105)
Fix chapel access

Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
2026-06-24 20:12:36 -05:00
Delta-V bot 6de6421636 Automatic changelog update 2026-06-25 00:03:18 +02:00
Vanessa 3cb24c8b68 Custom Client-Side Radio Colors (#6101) 2026-06-24 22:02:58 +00:00
Delta-V bot 9fad412075 Automatic changelog update 2026-06-24 15:15:21 +02:00
Vanessa fbc3104836 Fix Infinite Cloaking (#6085)
Fix infinite cloaking because I guess power draw doesn't count as using the battery charge.
2026-06-24 15:15:01 +02:00
Delta-V bot e231d9d67c Automatic changelog update 2026-06-24 15:08:40 +02:00
Stxcking 468d929c4b Tweak: AAC Subject Button Colors Return + Words Capitalized (#5567)
* Nuked StyleNano + Added colors to sheetlet

SubjectButtonColors table to define colors (same hex as the ones in StyleNano)
SubjectColorRules to loop rules for applying colors

* Capitalize every single phrase

CONSISTENCY RAHH
2026-06-24 15:08:20 +02:00
Delta-V bot 2f1aaff32c Automatic changelog update 2026-06-24 00:35:07 +02:00
Vanessa 4f49ccde68 Sleepers no longer have an annoucement (#6066)
* Sleepers no longer have an annoucement.

* NT sleepers too.
2026-06-23 18:34:47 -04:00
Vanessaandpre-commit-ci[bot] cb3b74e49e Station-Specific Department Cleanup (#6078)
* Removed some jobs from station specific jobs because they are on all stations.

* Department Prototype Cleanup

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Actually fix brigmedic on the crew monitor

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-23 18:34:44 -04:00
Vanessaandpre-commit-ci[bot] 7d15a39a77 Fixed emergency access locale string (#6044)
* Fixed emergency access locale string

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-23 18:23:22 -04:00
Delta-V bot 3ac816fee1 Automatic changelog update 2026-06-24 00:19:37 +02:00
Delta-V bot b909d9a2e5 Automatic changelog update 2026-06-23 17:27:27 +02:00
Theo 7547752159 Broken bottle slicing (#6092)
add comps
2026-06-23 10:27:07 -05:00
Manoob 9b7819288e Added a fix that resets the cook timers on fryer power changed (#6075) 2026-06-23 09:17:06 -05:00
Delta-V bot 424a4569d8 Automatic changelog update 2026-06-23 14:50:11 +02:00
wesker 12827c0e98 TraitSystem require fix (#6097)
how did i even forget this
2026-06-23 12:49:51 +00:00
Delta-V bot d1e61ba0a2 Automatic changelog update 2026-06-23 12:40:37 +02:00
652e52dbca Nuke/CosCult Global Music and SFX (#5918)
* Extended ServerGlobalSoundSystem and exposed its lies. Now its actually global.

* Fixed Epsilon event rule namespacing and formatting.

* Cosmic Cult Music Updates

* Few more CosCult changes.

* Nuke and Alert Changes

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update Content.Server/_DV/CosmicCult/EntitySystems/CosmicChantrySystem.cs

Co-authored-by: Sir Warock <67167466+SirWarock@users.noreply.github.com>
Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>

* Update Content.Server/_DV/Audio/ServerGlobalSoundSystem.cs

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>

---------

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Sir Warock <67167466+SirWarock@users.noreply.github.com>
2026-06-23 12:40:17 +02:00
Delta-V bot e7fe4865de Automatic changelog update 2026-06-22 20:06:07 +02:00
A111Vojta 84b600a39f Harmony lanyards (#6035)
* Lanyards Part1

* Lanyards Part 2

* Lanyards Part 3

* Fix

* Lanyards - Adding Comments

* replace plaintext with LocIds

* expanded the lanyard slot blacklist
2026-06-22 20:05:47 +02:00
Delta-V bot 31201c44ba Automatic changelog update 2026-06-22 18:48:51 +02:00
Theo 4f9f93eb87 Slugs structural increase TWO! (#6068)
* number up

* comment formatting
2026-06-22 11:48:31 -05:00
Delta-V bot f8a4d823b9 Automatic changelog update 2026-06-22 13:37:10 +02:00
Proxy also works :3 c61d2c6a58 guidebook shields (#6070) 2026-06-22 06:36:50 -05:00
Delta-V bot e25181e1bc Automatic changelog update 2026-06-22 13:11:55 +02:00
7c052442a8 Traits UI Modifications, Visuals, and a new Condition (#6082)
* traits ui things

* fluent

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update Resources/Locale/en-US/_DV/traits/traits-ui.ftl

Co-authored-by: Tobias Berger <toby@tobot.dev>
Signed-off-by: wesker <53323309+weskified@users.noreply.github.com>

* i hate toby

* newline

* pattern matching

* made simpler

* 2sleepy4myowngood

* kl;jasglkknf.,/.sa/.mmmmmmmmm

---------

Signed-off-by: wesker <53323309+weskified@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Tobias Berger <toby@tobot.dev>
2026-06-22 11:11:35 +00:00
Delta-V bot 19b0de35dd Automatic changelog update 2026-06-22 10:46:57 +02:00
wesker 1c66c1fe2e Fix 8 0'Clock Ghosts to be Mono (#6084)
* fix jukebox to be mono

* ACTUALLY fix this, holy shit audacity
2026-06-22 10:46:37 +02:00
Delta-V bot ef46675a2f Automatic changelog update 2026-06-22 06:23:31 +02:00
Halo3moth c0b6929c9a lower pitbull vent spawn rate (#6081) 2026-06-22 04:23:12 +00:00
Vanessa c7aa50cf85 Update C# Contribution Examples and Guidelines (#6039)
Updated C# guidelines.
2026-06-21 15:59:47 +02:00
Delta-V bot c69fb99600 Automatic changelog update 2026-06-21 15:48:33 +02:00
VelcroboyandVelcroboy 3ca37d9c2b Map Edits: The Hive (#6018)
* Map Edits: The Hive

* review changes

---------

Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
2026-06-21 15:48:12 +02:00
github-actions[bot]andDeltaV-Bot 8ba993d1a9 Update Credits (#6072)
Co-authored-by: DeltaV-Bot <github@deltav.gay>
2026-06-21 05:30:08 +02:00
Delta-V bot edd1500bf3 Automatic changelog update 2026-06-20 19:14:31 +02:00
einknusprigestoastandpre-commit-ci[bot] 2ec64360b7 ERT Security Loadout overhaul (#6049)
* ERT Selectors Amber Sec

* Added SRT Loadouts

* Added SRT Loadouts

* Added ERT selectors

* ERT Loadout tweaks

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Changed Omnitool to be ToolDebug + Multitool

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-20 19:14:11 +02:00
Delta-V bot e0540c6205 Automatic changelog update 2026-06-20 17:02:34 +02:00
7953b0469a I SCREAM, YOU SCREAM, WE ALL SCREAM FOR ICE CREAM (#5943)
* Initial commit.

* Minor tweaks to names, added caramel bottle.

* Fixing open state of caramel bottle.

* JSON cleanup.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Minor tweak to cookie dough ice cream recipe. Removed unused sprites.

* Swapping out updated sprites for rainbow sherbet, tropical sundae, neapolitan.

* Removing plushies.zip.

* Moved frozen treats to their own guidebook section under drinks to reduce clutter.

* Fixing flavor references, adding icecreamflavors.yml to contain definitions, corrected loadout ID references in loadoutgroups.

* Committing loadout changes that slipped through the cracks.

* Corrected casing in reference to RSI.

* Changed recipe for caramel to be just sugar to prevent conflict with Sigynate's recipe.

* So I'm kinda dumb and somehow it didn't occur to me that making sugar alone yield caramel would actually not only not fix the issue with Syginate but also wreck it for so many other chems, oopsie daisy, we're swapping it for Saline for a more shelf-stable salted caramel sauce.

* Tweaking ratio for Caramel, also, what are the odds that the tempermental build just works after this?

* At this point I'm just curious if a different build will fail this time.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update Resources/Prototypes/Catalog/VendingMachines/Inventories/bardrobe.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: robobozu <240598948+robobozu@users.noreply.github.com>

* Update Resources/Prototypes/Catalog/VendingMachines/Inventories/bardrobe.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: robobozu <240598948+robobozu@users.noreply.github.com>

* Update Resources/Prototypes/Catalog/VendingMachines/Inventories/boozeomat.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: robobozu <240598948+robobozu@users.noreply.github.com>

* Update Resources/Prototypes/Catalog/VendingMachines/Inventories/boozeomat.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: robobozu <240598948+robobozu@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Clothing/Uniforms/jumpsuits.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: robobozu <240598948+robobozu@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Objects/Consumable/Drinks/frozen_treats.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: robobozu <240598948+robobozu@users.noreply.github.com>

* Update Resources/Prototypes/Guidebook/references.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: robobozu <240598948+robobozu@users.noreply.github.com>

* Review changes

* Pascal-ify-ing flavor IDs.

* Update Resources/Prototypes/_DV/Entities/Objects/Consumable/Drinks/frozen_treats.yml

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Signed-off-by: robobozu <240598948+robobozu@users.noreply.github.com>

---------

Signed-off-by: robobozu <240598948+robobozu@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-06-20 15:02:13 +00:00
Delta-V bot a3d3d23600 Automatic changelog update 2026-06-20 16:24:55 +02:00
pathetic meowmeow 5ccfce1e7f NanoChat? Outside the same grid? Perish the thought. (#6057) 2026-06-20 09:24:35 -05:00
Delta-V bot 879cbf43f5 Automatic changelog update 2026-06-20 16:20:24 +02:00
einknusprigestoastandpre-commit-ci[bot] 9c84627db5 Added CentComm Turrets (#6069)
* Added CentComm Turrets

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-20 09:20:04 -05:00
Delta-V bot 3134e91fa5 Automatic changelog update 2026-06-20 12:46:22 +02:00
3ac30b7b7b [new item] Hitman card (#5896)
* Created Hitman_Case_catalog.yml

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* for hitman

credit:stormyatt. thanks for the help those on discord and the host briefcase

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* for hitman

credit: stormyatt with help from the discord sever and the hos briefcase pr

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* 16/24 items filled

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* for hitman

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* more added

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* top three filled

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* for hitman, full disguise kit

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* 27/30

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* 28/30

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* found the doorjack!

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* spyCrewMonitor is cool

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* found you!

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* for hitman

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* hitman: icons

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* Created Hitman_Case.yml

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* Create Hitman_Briefcase.rsi

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* Delete Resources/Textures/_DV/Objects/Tools/Hitman_Briefcase.rsi

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* almost made hitman card

* many modifactions to make it work.

* i need to get around to fixing my repo after this

* hope this works. reverting by hand

Signed-off-by: storm! <253825839+stormyatt@users.noreply.github.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* as requested

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* less dependies, pretty sure only needed ones now

* review 2. card space addition

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>
Signed-off-by: storm! <253825839+stormyatt@users.noreply.github.com>
Co-authored-by: stormyatt <stormyatt@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-20 12:46:02 +02:00
Delta-V bot 4daea082bc Automatic changelog update 2026-06-20 00:07:10 +02:00
Vanessa 6a37e1268e SRN Fixes (#6045)
* Fixed SRNs getting borg AA on their borgs

* SRNs can print batteries
2026-06-19 18:06:50 -04:00
Delta-V bot 331a25c7d3 Automatic changelog update 2026-06-19 23:54:07 +02:00
Vanessaandpre-commit-ci[bot] b5121e10e9 Increase Syndicate Hardsuit Aura by 15% (#6060)
* Various syndicate hardsuit sprite upgrades for overlay.

* Added (missing) overlays for Feroxi and Reptiles.

* added base bluekie light

* Added stealthsuit variant.

* Fixed duplicate yaml entry

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix missing entry in meta.json for syndie medic

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-19 17:53:47 -04:00
Delta-V bot 9584a7b0ae Automatic changelog update 2026-06-19 23:43:04 +02:00
Vanessaandpre-commit-ci[bot] f9880c148b Cloned zombie players no longer get mauled by cats (#6053)
* Moved DeltaV modifications of ZombieSystem to a partial class. Fixed factions and pacifist trait on zombie cloning. Added survival objective to cloned II.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-19 17:42:45 -04:00
Delta-V bot 25ef15f162 Automatic changelog update 2026-06-19 21:26:41 +02:00
Theo 997a6e31e6 Felinid SinkClaws action (#5996)
action and sprites
2026-06-19 14:26:22 -05:00
Delta-V bot 5a342570a4 Automatic changelog update 2026-06-19 20:35:52 +02:00
Proxy also works :3 97bb68b2a6 kill cult metashield (#6026) 2026-06-19 13:35:33 -05:00
Delta-V bot ac9f8852b3 Automatic changelog update 2026-06-19 20:12:19 +02:00
DisposableCrewmember42andneomoth 8c8f4b4f2c tweak: better screenshake (#6059)
* tweak: explosion shake directional, distance fall-off

partial port of ss14Starlight/space-station-14#3707 by neomoth:
0b6282eb267ef1efc0b2bcd78a39e53a776f1c79

Co-authored-by: neomoth <69372252+neomoth@users.noreply.github.com>

* tweak: melee shake depends on damage/wielding

partial port of ss14Starlight/space-station-14#3707 by neomoth:
0b6282eb267ef1efc0b2bcd78a39e53a776f1c79

Co-authored-by: neomoth <69372252+neomoth@users.noreply.github.com>

* tweak: explosion kick falloff

* chore: remove debug log

* feat: melee shake has heavy/light variants instead of only showing for high blunt/wielded

* refactor: use MathF instead of Math

* tweak: more noticeable camera kick falloff

* tweak: generally dampen explosion kick a little more, fall off slower

* docs: explain camera kick falloff

* docs: remove commented upstream code per review

* docs: document screenshake falloff too, clarify distance

---------

Co-authored-by: neomoth <69372252+neomoth@users.noreply.github.com>
2026-06-19 13:11:59 -05:00
Delta-V bot 8eeb5f6523 Automatic changelog update 2026-06-19 20:00:05 +02:00
Theo 45432aa3bb Kobolds exist again (#5968)
copy paste ops
2026-06-19 17:59:45 +00:00
Delta-V bot b9153f0f13 Automatic changelog update 2026-06-19 19:53:03 +02:00
Theo e3d3fe72f0 remove damage examine from bomb pen (#6010)
remove hit damage
2026-06-19 17:52:43 +00:00
Delta-V bot 0a65236363 Automatic changelog update 2026-06-19 19:49:19 +02:00
Halo3moth afe6021455 remove chameleon gears metashield (#6000)
Update 2_Metagaming.xml
2026-06-19 13:48:59 -04:00
Delta-V bot e5db02c736 Automatic changelog update 2026-06-19 18:16:18 +02:00
Theo cbc5f819c4 Security radio recolor (#6016)
* change

* less saturated
2026-06-19 11:15:58 -05:00
Delta-V bot 339b3fb32c Automatic changelog update 2026-06-19 18:06:08 +02:00
AstraandVanessa e96faec07d Allow purified salts in belts/medkits (#6062)
* Allow purified salts in medical belts

* Medkits too why not

* Update Resources/Prototypes/Entities/Objects/Specific/Medical/medkits.yml

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>

---------

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-06-19 16:05:48 +00:00
Delta-V bot a1bbf142a4 Automatic changelog update 2026-06-19 17:52:56 +02:00
DisposableCrewmember42andPieter-Jan Briers e47a0bf455 feat: chat follow links for ghosts (upstream port) (#6063)
* Ghosts have follow buttons in chat (#44284)

* Fix ghost follow buttons for station entity (#44289)

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2026-06-19 10:52:36 -05:00
Delta-V bot 72f3edd378 Automatic changelog update 2026-06-19 17:49:17 +02:00
freezer dog 030deb6f27 SV-213 QoL stuff (and a tiny bit more) (#6065)
wwoof
2026-06-19 10:48:57 -05:00
Vanessaandpathetic meowmeow 4397017c5a Added ESEffectSparks to excluded list on integration tests (#6061)
* Added ESEffectSparks to excluded list on integration tests

* Update Content.IntegrationTests/Tests/EntityTest.cs

Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>

---------

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
2026-06-18 20:32:39 -04:00
Delta-V bot 0f19340d41 Automatic changelog update 2026-06-18 18:37:52 +02:00
Stxcking 3c28ee8d20 Tweak: Broken Station Maps can be Deconstructed (#6005)
mhm
2026-06-18 11:37:32 -05:00
Delta-V bot 11cc04dd87 Automatic changelog update 2026-06-18 18:35:12 +02:00
Sugarcane f87ed28744 Bluesec in Guidebook (#6013) 2026-06-18 11:34:52 -05:00
Delta-V bot 62c76332f5 Automatic changelog update 2026-06-18 18:22:35 +02:00
pathetic meowmeow 34c212e4fb Fax machines? That are ready roundstart? Perish the thought. (#6058) 2026-06-18 11:22:15 -05:00
Radezolid 353290e55b Mostly locker entityTables cleanup (#5982)
* Remove our changes to dressers.yml

* Engineering

* Heads

* Science

* Security

* Append our tables with upstream's, apply suggestion and small comment cleanup
2026-06-17 15:55:26 -04:00
Delta-V bot 0cbe9c01fb Automatic changelog update 2026-06-17 17:32:53 +02:00
Vanessa d88938887b Fixed biomasser dividing by zero (#6042)
Fixed biomasser dividing by 0.
2026-06-17 17:32:34 +02:00
Delta-V bot 1656a91c5e Automatic changelog update 2026-06-17 16:08:07 +02:00
Proxy also works :3 c70cbcb9f5 bne (#6050) 2026-06-17 09:07:47 -05:00
Delta-V bot 3e22d89d1e Automatic changelog update 2026-06-17 15:56:46 +02:00
TNE a790954f06 Prevent zombies/non-humanoids/non-borgs from using jetpacks (#5776)
Prevent zombies from using jetpacks

Introduces CanComplexInteract check from ActionBlockerSystem alongside a translation string
When Zombifying: Removes AutomaticJetpackUserComponent, calls SharedJetpackSystem.SetEnable(..., false, ...)
2026-06-17 15:56:26 +02:00
Delta-V bot 0676186fe5 Automatic changelog update 2026-06-17 15:36:21 +02:00
Theo db6d1e3970 Uplink pt 1 touchup (#5991)
* buffs

* damage change on knuckledusters
2026-06-17 08:36:01 -05:00
fd20b74b8f Update mapping spawners (#5857)
* Update mapping spawners

* more casing

* Clothing and towels

* minor fixes

* Finalize clothing

* Begin Items

* Final updates

* Fixes

* save button

* `n`

* Add pitbull to animal spawner

* Add random satchels

* Glowsticks

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/animals.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/boardgames.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/boardgames.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/books.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/machineparts.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/schoolgirl.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/seeds.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/towels.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Mobs/NPCs/misc.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Mobs/NPCs/misc.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Update Resources/Prototypes/_DV/Entities/Markers/Spawners/Random/mobs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>

* Review changes

---------

Signed-off-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>
Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
2026-06-16 23:54:22 -03:00
Delta-V bot d016838687 Automatic changelog update 2026-06-17 03:07:15 +02:00
Vanessa 160aa17052 Pitbull Fixes (#6046)
pitbull fixes
2026-06-16 22:06:55 -03:00
Delta-V bot 2b6729fa64 Automatic changelog update 2026-06-16 22:02:38 +02:00
Field Commandandpre-commit-ci[bot] 3439340530 Updated to ghost bar syndicate prison (#6006)
* Updated to ghost bar syndicate prison

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Some last minute fixes

* Update to prison and sprites

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-16 15:02:18 -05:00
Delta-V bot 28c2223a8a Automatic changelog update 2026-06-16 19:43:30 +02:00
CarolandVanessa 1b47c246c9 Added dynamic cost to Ratkings OnRaiseArmy ability (#6017)
* added dynamic cost to Ratkings OnRaiseArmy ability

* Update Content.Server/RatKing/RatKingSystem.cs

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Signed-off-by: Carol <292806487+CarolCuchara@users.noreply.github.com>

* switched summary to one line

* added using directive for Linq

---------

Signed-off-by: Carol <292806487+CarolCuchara@users.noreply.github.com>
Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-06-16 12:43:10 -05:00
Delta-V bot 8141512cc4 Automatic changelog update 2026-06-16 19:09:52 +02:00
DisposableCrewmember42 fd986fec6e feat: Effigy-driven Colossus (mid-round Colossus rework) (#5565)
* feat: effigy-driven colossus

* feat: impl final stage, tweaks, randomization, fixes

* fix: actually broadcast first effigy audio

* style: add some newlines

* feat: notify player about updated target location in chat

* tweak: effigy always grows

* feat: make alwaysGrow show on anom scanner

* tweak: raise minimumPlayers to 35

* refactor: move FTL additions to _DV

* refactor: break out buffs into comps, add event

also added missing docs and made effigies heal colossi fully, as
specified in the PR body. I accidentally left it at 50% damage taken.

* docs: add effigy comp docs
2026-06-16 13:09:32 -04:00
Delta-V bot dd2a6e1c28 Automatic changelog update 2026-06-16 16:16:28 +02:00
TNEandDisposableCrewmember42 6c7c9de253 fix: don't show SSD examine on dead people/ghost role (#6012)
fix: don't display examine text on dead people or untaken ghost roles

Co-authored-by: DisposableCrewmember42 <disposablecrewmember42@proton.me>
2026-06-16 09:16:08 -05:00
Delta-V bot 2b96bb79f8 Automatic changelog update 2026-06-15 19:27:20 +02:00
CarolandVanessa c1239ef7bf Remove headset from pockets when player get zombified (#6030)
* Remove radio form pocket when secdogs get zombified

* Update Content.Server/Zombies/ZombieSystem.Transform.cs

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Signed-off-by: Carol <292806487+CarolCuchara@users.noreply.github.com>

---------

Signed-off-by: Carol <292806487+CarolCuchara@users.noreply.github.com>
Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-06-15 12:27:00 -05:00
Delta-V bot 62b6401e48 Automatic changelog update 2026-06-15 18:46:55 +02:00
freezer dog f79a599c65 fixing machine pistol magazine issues and clearing gun chambers (#5819)
* kill... slaughter...

* ok why not im gonna clear chambers too

* ok maybe thats all the chambers clear

* time to find out i screwed up everythin xd

* whoops i killed the chambers....

---------

Signed-off-by: freezer dog <iamabanana372456@gmail.com>
2026-06-15 13:46:35 -03:00
Delta-V bot 9acd3f2ecb Automatic changelog update 2026-06-15 18:06:32 +02:00
Delta-V bot 47215d7876 Automatic changelog update 2026-06-15 18:02:26 +02:00
fe53298d27 Delta-V: Wrath of the Pitbulls (#5915)
* DIRECTION KNOWS BEST

DIRECTION KNOWS BEST GLORY TO DELTA-V

* MORE

More names

* It begins

Im not sorry

* Pitbull changes

They fear me because of what I can do.

* They will fuck you up if you Hit them

* Update pitbull.yml

* The infamous pitbull faction

* Update ai_factions.yml

* Update ai_factions.yml

Staff Requested changes

* Update pitbull.yml

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* sprites added!

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* event addition part 1

* Pitbull Crate

* Event work part 2

* obvious

* bullshit fix part 1

* Bullshit fix part 2

* bullshit fixes part 3

* i hate it here

* removes bullshit 5137268

* shortened timer to catch crew off guard

* crash fix 1 i think

* Update cargo_livestock.yml

* They no longer maul II

* they are angry.

* they cant maul you now.

* Update pitbull.ftl

* Update pitbull.yml

* Update events.yml

* Update interaction-popup-component.ftl

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update events.yml

* Update pests.yml

* Update pitbull.yml

* Update events.yml

* Update pitbull.yml

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update pitbull.yml

* 1984 pitbull faction

* Inshallah this will work

* More names

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update pitbull_names.yml

* Update pitbull.ftl

* Update pitbull_names.yml

* Increased amount of mice and hopefully enables higher chance of ventbulls to spawn

* DIRECTION KNOWS BEST

DIRECTION KNOWS BEST
DIRECTION KNOWS BEST
DIRECTION KNOWS BEST
DIRECTION KNOWS BEST
DIRECTION KNOWS BEST

Signed-off-by: Solaris <63768197+StarboundSolaris@users.noreply.github.com>

* DIRECTION KNOWS BEST

DIRECTION KNOWS BEST

Signed-off-by: Solaris <63768197+StarboundSolaris@users.noreply.github.com>

* Refactor inheritence for pitbulls

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* forgot a file

* Makes it so the crate spawns the named versions

* Ensures they have random names.

* Update pitbull.yml

* Prep for mapped pitbull

* Adds Pibble

* Returns id to previous version

* Update pitbull.yml

* Update events.yml

---------

Signed-off-by: Solaris <63768197+StarboundSolaris@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Solaris <63768197+Solaris7518@users.noreply.github.com>
Co-authored-by: Vanessa <vanessalouwagie@gmail.com>
2026-06-15 11:02:06 -05:00
Theo 1aee2519df Kill Poncho Meta (#6001)
numbers
2026-06-15 10:51:38 -05:00
Delta-V bot e8cd052c31 Automatic changelog update 2026-06-15 17:10:19 +02:00
Carol a27fa291e7 Fix: Beer googles not stopping glasses from spilling when throw (#6031)
* fixed beer googles not keeping glasses form spilling on throw

* fixed beer googles not keeping glasses form spilling on throw
2026-06-15 10:09:59 -05:00
Delta-V bot 7e6f66a651 Automatic changelog update 2026-06-15 17:05:49 +02:00
pathetic meowmeow 08c27979a1 Interaction particles pass (#6033) 2026-06-15 10:05:29 -05:00
Delta-V bot b49a94100c Automatic changelog update 2026-06-14 20:30:54 +02:00
Carol cfa24eb20c Added a range check for pager linking (#6032) 2026-06-14 14:30:34 -04:00
Delta-V bot 1787683435 Automatic changelog update 2026-06-14 10:13:44 +02:00
Stop-Signs 389fbf99e1 Reduce WT550 SMG fire-rate (#5581)
* the PR

* comments

* hunt showdown queue times

* comment

* .desc
2026-06-14 08:13:24 +00:00
Delta-V bot 74c7bf0a05 Automatic changelog update 2026-06-14 06:13:17 +02:00
Carol 6764276a47 Changed secborgs fibers to blue (#6025)
changed secborgs fibers to blue
2026-06-14 04:12:57 +00:00
github-actions[bot]andDeltaV-Bot 03934d3fbd Update Credits (#6028)
Co-authored-by: DeltaV-Bot <github@deltav.gay>
2026-06-14 04:59:28 +02:00
SumTwo 468150c817 Adding the Logistics Beret as a loadout option for Cargo Technicians (#6015)
* Step One: Add Hat

Signed-off-by: SumTwo <its.a.joke1000@gmail.com>

* Step Two: Add Hat

Signed-off-by: SumTwo <its.a.joke1000@gmail.com>

* Step Three: Im Stupid

Signed-off-by: SumTwo <its.a.joke1000@gmail.com>

* Step Four: Im Stupid

Signed-off-by: SumTwo <its.a.joke1000@gmail.com>

* Step Five: Obey CR

Signed-off-by: SumTwo <its.a.joke1000@gmail.com>

---------

Signed-off-by: SumTwo <its.a.joke1000@gmail.com>
2026-06-13 21:07:20 -03:00
Delta-V bot 310ac0809b Automatic changelog update 2026-06-13 22:03:35 +02:00
7b71611dc9 [antag variant]Hitman midround variant (#5848)
* Created Hitman_Case_catalog.yml

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* for hitman

credit:stormyatt. thanks for the help those on discord and the host briefcase

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* for hitman

credit: stormyatt with help from the discord sever and the hos briefcase pr

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* 16/24 items filled

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* for hitman

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* more added

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* top three filled

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* for hitman, full disguise kit

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* 27/30

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* 28/30

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* found the doorjack!

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* spyCrewMonitor is cool

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* found you!

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* for hitman

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* hitman: icons

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* Created Hitman_Case.yml

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* Create Hitman_Briefcase.rsi

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* Delete Resources/Textures/_DV/Objects/Tools/Hitman_Briefcase.rsi

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fixing it all

* I dont get it either but last of the changes which our just righting things

* Hitman_Case_catalog.yml be like that

* goodbye dupe

* renaming a file and removing a duplicate

* attributions changed to repect the caps

* as requested, name changes and useless line removal

* forgot to control S here

* names changed to try and be both simple and actualy describe the goal

* doing review changes and a small change to prototype order to make sense

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* how did i get where i moved this wrong?

* recharging radio jammer to radio jammer.

* added hitman midround stuff

* made the professional not cause errors and have a ghost warp point

* enter at the ends of these files

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* updated the professional to professional

* decal adds

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fixes and stuff

* gave hitmans headset traffic and freelance comms

* all review changes made

* Apply suggestion from @Radezolid

caps my foe

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: storm! <253825839+stormyatt@users.noreply.github.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* nevermind i distrust this

* Update Resources/Prototypes/_DV/Catalog/Fills/Lockers/hitman_locker.yml

Signed-off-by: Radezolid <snappednexus@gmail.com>

---------

Signed-off-by: stormyatt <dratticuscrabs@gmail.com>
Signed-off-by: storm! <253825839+stormyatt@users.noreply.github.com>
Signed-off-by: Radezolid <snappednexus@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: stormyatt <stormyatt@users.noreply.github.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
2026-06-13 20:03:15 +00:00
Delta-V bot f2153d66c3 Automatic changelog update 2026-06-13 21:29:23 +02:00
Theo b6833cbbcf Remove Fractured Form (#6011)
* remove from the table

* full remove
2026-06-13 14:29:04 -05:00
Delta-V bot 4e2bd0c244 Automatic changelog update 2026-06-13 04:29:23 +02:00
bf0abd7ffa Add SRT Hardsuit and Antelope + Remove DS mentions (#6003)
* Added Chameleon Armor

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* nerfvest

* Lowered Price

* Changed Jackdaw and Atrides fire rates

* Changed battery on Pulse Rifle, upped fire rate

* lowered pulse rifle recharge rate

* Added SRT Hardsuit

* Added Antelope

* Revert "lowered pulse rifle recharge rate"

This reverts commit 73f348c3e7.

* Revert "Changed battery on Pulse Rifle, upped fire rate"

This reverts commit d6f408620d.

* Revert "Changed Jackdaw and Atrides fire rates"

This reverts commit 0779c08d44.

* Revert "Lowered Price"

This reverts commit d798cd92e9.

* Revert "Merge branch 'toastdrip' of https://github.com/einknusprigestoast/Delta-v into toastdrip"

This reverts commit ca038e3699, reversing
changes made to f74f3e506d.

* Revert "nerfvest"

This reverts commit f74f3e506d.

* Revert "Added Chameleon Armor"

This reverts commit b57fa738f3.

* Removed Edge from Mateba

* whoops, forgot comment

* Removed DS mention + Added NV to ERT + Added Thermal Vision to DS

* removed deathsquad mentions on the jumpsuit

* removed deathsquafd mentions on the backpack

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Unfucked my parenting

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* non-localisation maintainer requests

* Added FTLs

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* One point, one singular point

* AHHHH

* Fix comments

Signed-off-by: Radezolid <snappednexus@gmail.com>

---------

Signed-off-by: Radezolid <snappednexus@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
2026-06-12 23:29:03 -03:00
Delta-V bot c4d65da8ed Automatic changelog update 2026-06-12 19:16:19 +02:00
Stxcking 6dac24f03a Shoukou / Shoko Map Update - BlueSec (#5940)
* first

sobs

* second

* review fixes
2026-06-12 12:15:59 -05:00
Delta-V bot 9550a7d53d Automatic changelog update 2026-06-11 03:28:51 +02:00
VelcroboyandVelcroboy d826a15ff2 Map Edits: Arena (#6009)
* Map Edits: Arena

* Link lightswitch

* Remove duplicate suit storage

* reveiw edits

---------

Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
2026-06-10 20:28:31 -05:00
Delta-V bot 23a9a75703 Automatic changelog update 2026-06-10 20:23:59 +02:00
Vape 79a0000547 Xeno Rework - Pull sprites and audio from RMC (#5891)
* Additions

* Changes per direction

* Update xeno.yml

* Requested Changes
2026-06-10 18:21:13 +00:00
Delta-V bot 71badde1a0 Automatic changelog update 2026-06-09 20:48:54 +02:00
Theo 5ab49cd46b Reduce meat spike build time (#5999) 2026-06-09 14:48:35 -04:00
Delta-V bot 0398c527a1 Automatic changelog update 2026-06-09 20:10:21 +02:00
Cepelinas1 cdafd4c257 More Tips + slight tweaks to weapon names (#6004)
* first

* typo

* figured it out :)

* what reviewers wanted
2026-06-09 15:10:01 -03:00
Delta-V bot 8dc06f010a Automatic changelog update 2026-06-09 15:45:46 +02:00
Theo fee62b449f shadow cat tweaks (#5977)
* add and remove

* remove comment

* oh yeah and this one
2026-06-09 13:45:26 +00:00
Delta-V bot c0d20b3d79 Automatic changelog update 2026-06-08 23:57:59 +02:00
snowywinters b2944fba52 Edge update (#5983)
* Update edge.yml

* Removed surgeon
2026-06-08 16:57:39 -05:00
2e39916957 Admeme gun changes (#5971)
* Added Chameleon Armor

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* nerfvest

* Lowered Price

* Changed Jackdaw and Atrides fire rates

* Changed battery on Pulse Rifle, upped fire rate

* lowered pulse rifle recharge rate

* Update Resources/Prototypes/_DV/Entities/Objects/Weapons/Guns/Rifles/rifles.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: einknusprigestoast <90181825+einknusprigestoast@users.noreply.github.com>

* Update Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: einknusprigestoast <90181825+einknusprigestoast@users.noreply.github.com>

* Update Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: einknusprigestoast <90181825+einknusprigestoast@users.noreply.github.com>

* Update Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml

Co-authored-by: Radezolid <snappednexus@gmail.com>
Signed-off-by: einknusprigestoast <90181825+einknusprigestoast@users.noreply.github.com>

---------

Signed-off-by: einknusprigestoast <90181825+einknusprigestoast@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
2026-06-08 17:35:29 -03:00
Delta-V bot 874bbf4fd2 Automatic changelog update 2026-06-08 21:50:51 +02:00
VelcroboyandVelcroboy e1ff1ea820 Map Edits: Tortuga (#5965)
* Map Edits: Tortuga

* Final commits

* Robo-Proto

* Service jobs

* reveiw fixes

* Jimbo's birthday present

---------

Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
2026-06-08 21:50:31 +02:00
Delta-V bot 79a4a15403 Automatic changelog update 2026-06-08 21:36:22 +02:00
Vape 0bb2b6ee7b Updates to Submarine.yml (#5760)
* Commit#1

* Update submarine.yml

* ATTEMPT#2

* CASE SENSITIVITIY

* Committing Changes

* Bluesec

* Review updakes
2026-06-08 12:36:01 -07:00
Delta-V bot 18d9325b60 Automatic changelog update 2026-06-08 19:23:43 +02:00
Field Command f07648bc24 Ghost Bar: Syndicate Prison (#5578)
* Ghost Bar: Syndicate Prison

* Copy-paste is evil...

* formatting fix

* IPC no more

* update

* Fix
2026-06-08 12:23:24 -05:00
Delta-V bot f5da329b98 Automatic changelog update 2026-06-08 07:30:14 +02:00
ConstantlyConfused ad6581f2d3 Make the nuclear fab announce when it recieves new recipes (#5985)
make the nuclear fabiractor announce researches
2026-06-08 02:29:55 -03:00
Delta-V bot 430fef2a10 Automatic changelog update 2026-06-08 06:35:40 +02:00
Theo 6970cb9d28 Add cooldown to ninja emp (#5967)
* add 10 second cooldown

* 5 seconds instead
2026-06-08 00:35:20 -04:00
Delta-V bot ea2b8df2cc Automatic changelog update 2026-06-07 22:49:09 +02:00
Theo b87b113736 cult simplemobs deal blunt damage (#5962)
add blunt damage
2026-06-07 20:48:49 +00:00
Delta-V bot f087b2895e Automatic changelog update 2026-06-07 21:46:37 +02:00
MD360andRadezolid 0a14310574 Remove duplicate towels (#5964)
* duplicate begone

* white too

* Fix comment

Signed-off-by: Radezolid <snappednexus@gmail.com>

* Fix of the comment-fix

Signed-off-by: Radezolid <snappednexus@gmail.com>

---------

Signed-off-by: Radezolid <snappednexus@gmail.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
2026-06-07 19:46:18 +00:00
Delta-V bot d9c2f26438 Automatic changelog update 2026-06-07 21:34:18 +02:00
Stxcking a4f11c63dc Fix: Holoflares not Hurting Skia (#5976)
net
2026-06-07 16:33:58 -03:00
Delta-V bot 4ff05277fa Automatic changelog update 2026-06-07 21:05:35 +02:00
Stxcking a33e12e85e Fix: Glowsticks can now be use for glow in the dark stars! (#5970)
yipe
2026-06-07 16:05:16 -03:00
Delta-V bot 62e9ebc216 Automatic changelog update 2026-06-07 20:13:32 +02:00
biddygelson 25b69a3f9e Academy 1.9.0 BLU+ (#5972)
* * Added Poppy to Eng break room.
* Replaced all faxes with department variants. Note Justice was used over Lawyer; faxes added to Dorms, Chem, Botany, Chef, Bar. MimeLaw.
* Replaced Emergency EVAs with Epistemics EVAs in Artefact.
* Replaced RED-Sec with BLU-Sec; Sec, Brig, and Evac.
* Added Anti-Anom to solars.

* * replaced RED with BLU sec in viro security
* removed condenser in chem
* added condenser in atmos front
* added APC to atmos burn chambers
2026-06-07 20:13:12 +02:00
Field Command 7ed7dbcbb1 Maps: Timer be gone! (#5980)
✔
2026-06-07 18:05:08 +00:00
Delta-V bot fcdf1cbbf4 Automatic changelog update 2026-06-07 18:23:16 +02:00
SirrRequiem 9f786a7c1c Terra 6: Everything is Blue (#5956)
* Terra 6: Everything is Blue

* Curtains and carpets now match
2026-06-07 18:22:57 +02:00
github-actions[bot]andDeltaV-Bot e2e5c40f83 Update Credits (#5974)
Co-authored-by: DeltaV-Bot <github@deltav.gay>
2026-06-07 04:55:51 +02:00
Delta-V bot d0c3cff669 Automatic changelog update 2026-06-06 21:14:23 +02:00
ed1fa64a6e feat: Recent SSD indicator, SSD time in admin overlay (#5699)
* feat: Recent SSD indicator, SSD time in admin overlay

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* tweak: remove colon from ssd time loc string

* feat: add another stage

* feat: ssd examine text

* apply suggestion: make ssdstage serializable, use proper doc comments

Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
Signed-off-by: DisposableCrewmember42 <disposablecrewmember42@proton.me>

* apply suggestion: more compact switch, throw instead of logging if invalid stage

Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
Signed-off-by: DisposableCrewmember42 <disposablecrewmember42@proton.me>

* apply suggestion: missing DeltaV comment

Co-authored-by: Sir Warock <67167466+SirWarock@users.noreply.github.com>
Signed-off-by: DisposableCrewmember42 <disposablecrewmember42@proton.me>

* fix: add missing import

* refactor: remove unnecessary state-keeping

i don't remember why i did it like that

* chore: remove unused import

* refactor: don't separate comp from ent

---------

Signed-off-by: DisposableCrewmember42 <disposablecrewmember42@proton.me>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
Co-authored-by: Sir Warock <67167466+SirWarock@users.noreply.github.com>
2026-06-06 19:14:03 +00:00
Delta-V bot cb245477b8 Automatic changelog update 2026-06-06 14:08:17 +02:00
0fdfef7175 New Lobby/Jukebox Track Made By Me (#5620)
* woah

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* attributions updates

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update Resources/Audio/_DV/Lobby/attributions.yml

Co-authored-by: Tobias Berger <toby@tobot.dev>
Signed-off-by: This_Is_Gray <167829864+IAmNotGray@users.noreply.github.com>

* Update Resources/Audio/_DV/Jukebox/attributions.yml

Co-authored-by: Tobias Berger <toby@tobot.dev>
Signed-off-by: This_Is_Gray <167829864+IAmNotGray@users.noreply.github.com>

* Update RobustToolbox

---------

Signed-off-by: This_Is_Gray <167829864+IAmNotGray@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Tobias Berger <toby@tobot.dev>
2026-06-06 12:07:55 +00:00
Delta-V bot 646d538c4a Automatic changelog update 2026-06-06 11:53:38 +02:00
Stxcking 6ebcf78d08 Fix: Radiation Barrier Projector Works as Intended (#5787)
fix
2026-06-06 11:53:19 +02:00
Delta-V bot 2b7e3c8058 Automatic changelog update 2026-06-05 23:14:09 +02:00
pathetic meowmeow 4736a8db6f Add cyborg wire panel (#5948)
* Add cyborg wire panel

* MORE WIRES RAAAGH

* file capitalization

* renamed

* more comments
2026-06-05 23:13:49 +02:00
Delta-V bot 6057388bae Automatic changelog update 2026-06-05 19:34:29 +02:00
50a651beea Added Chameleon Armor (#5921)
* Added Chameleon Armor

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* nerfvest

* Lowered Price

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Changed Carapace Parent

* Remove irrelevant Braces

Signed-off-by: Sir Warock <67167466+SirWarock@users.noreply.github.com>

---------

Signed-off-by: Sir Warock <67167466+SirWarock@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Sir Warock <67167466+SirWarock@users.noreply.github.com>
2026-06-05 17:34:09 +00:00
VelcroboyandVelcroboy 7e1308dd12 Map Hotfix: Elegance (#5966)
Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
2026-06-05 18:06:03 +02:00
Delta-V bot 338ff271c7 Automatic changelog update 2026-06-05 17:33:16 +02:00
Stxcking fddd9079d3 Feat: Small Nuclear Reactor and Small Gas Turbine for Purchase (#5945)
mhmm
2026-06-05 11:32:56 -04:00
Delta-V bot bedc17ae73 Automatic changelog update 2026-06-05 17:29:04 +02:00
Stxcking ec80e99e26 Tweak: Added More Pride Theme Items in the Pride-o-Mat (#5946)
* gay

* more gay
2026-06-05 11:28:44 -04:00
Delta-V bot 4ceb567439 Automatic changelog update 2026-06-05 17:26:45 +02:00
Theo 6a9984475c Random nukie and ninja appearance (#5922)
* ninja and nukie random appearance component added to gamerules

* ninja and nukie random appearance component added to gamerules

* add keepgender
2026-06-05 15:26:24 +00:00
Delta-V bot 030eb95c63 Automatic changelog update 2026-06-05 17:18:10 +02:00
Stxcking 00abed028d Feat: No Paradox Clone Trait (#5937)
* yip

* whoops
2026-06-05 11:17:50 -04:00
Delta-V bot 0f3d319a6d Automatic changelog update 2026-06-05 17:08:02 +02:00
biddygelson a9ddc102e7 Academy 1.8.1 (#5880)
* added secure windoors to sec disposal chutes
* added secure windoors to epi-brig disposal chute, sec and epi locked.
* tweak medical escape pods for DV 5x7 variant
* tweak justice escape pods for DV 5x7 variant
* replaced all solar grill with random grill spawner
* tweak moved disposal light so it doesn't break from trash
2026-06-05 17:07:41 +02:00
Delta-V bot a51896b08d Automatic changelog update 2026-06-05 17:06:12 +02:00
Stxcking 356106342f Tweak: Revert Upstream Medical Borg Rework + More (#5656)
* Medical Borg Good Again :D

* Indents buh

* im blind

or dumb, theres errors that seem unrelated to the bodybags after this

* IM SORRY I FORGOT ABOUT THIS

* Anchoring it
2026-06-05 11:05:52 -04:00
Delta-V bot a3b450ac07 Automatic changelog update 2026-06-05 15:34:19 +02:00
pathetic meowmeow 4ddb5901b0 Better douner name generation (#5836)
* better douner name generation

* more names

* yet more names

* ugly sins that i hate

* screaming

* e
2026-06-05 15:33:56 +02:00
Delta-V bot 972d852bf4 Automatic changelog update 2026-06-05 14:46:20 +02:00
pathetic meowmeow ef0d317d12 Interaction particle finetuning (#5933) 2026-06-05 08:46:01 -04:00
003e08b583 Flavourful emotes (#5595)
* Changed up how emoting works

This was a long 10 hours just to get the chat to behave slightly different.

At least we can now scream over radio!

Will most definitely need some more work to get bugs out since this is the first implementation.

* Not ok

This was unmarked for some reason... I don't remember unmarking this.

* DeltaV comments

Added comments to explain changes.

Moved localization files to the _DV folder.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Moved code

Moved code to more suitable files and added some comments.

* Rights for robots

Now those with intrinsic radios can also emote!

* Fixes up some small stuff

Changes to fix some small issues with the code to align it more with modern standards.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix by-ref event

Never worked with this event type before so gonna hope this works.

* Update Content.Shared/Chat/SharedChatSystem.Emote.cs

Signed-off-by: pathetic meowmeow <uhhadd@gmail.com>

* Update Content.Shared/Chat/SharedChatSystem.cs

Signed-off-by: pathetic meowmeow <uhhadd@gmail.com>

* Update Content.Server/Chat/Systems/ChatSystem.cs

Signed-off-by: pathetic meowmeow <uhhadd@gmail.com>

* fix style nits

* remove conflicting chatsan

---------

Signed-off-by: pathetic meowmeow <uhhadd@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
2026-06-04 17:25:58 +00:00
Delta-V bot 74739709c4 Automatic changelog update 2026-06-04 12:59:17 +02:00
AeraAulinandSir Warock 61840fd5ab Rename Holy to Radiant and Shadow to Soul (#5952)
* rename shadow to soul and change it all since it's ours, hopefully change holy to radiant while touching as little as possible

* obliterate a mention of shadow

* Update Resources/Prototypes/_Impstation/Entities/Mobs/Anomalites/anomalite_damage.yml

Co-authored-by: Sir Warock <67167466+SirWarock@users.noreply.github.com>
Signed-off-by: AeraAulin <133451603+AeraAuling@users.noreply.github.com>

---------

Signed-off-by: AeraAulin <133451603+AeraAuling@users.noreply.github.com>
Co-authored-by: Sir Warock <67167466+SirWarock@users.noreply.github.com>
2026-06-04 07:58:56 -03:00
Delta-V bot 90f7aa7999 Automatic changelog update 2026-06-03 20:44:15 +02:00
turtlemutt 2260af7ce1 Bluehouse (#5898)
Update lighthouse.yml
2026-06-03 20:43:55 +02:00
1136 changed files with 151595 additions and 72556 deletions
+1
View File
@@ -32,6 +32,7 @@ jobs:
run: |
cd RobustToolbox/
git submodule update --init --recursive
rm Robust.Shared.Tests/Networking/NetEncryptionDoSTest.cs # DeltaV - work around bug in the backport
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
+1
View File
@@ -29,6 +29,7 @@ jobs:
run: |
cd RobustToolbox
git fetch --depth=1
rm Robust.Shared.Tests/Networking/NetEncryptionDoSTest.cs # DeltaV - work around bug in the backport
- name: Install dependencies
run: dotnet restore
+1
View File
@@ -23,6 +23,7 @@ jobs:
run: |
cd RobustToolbox/
git submodule update --init --recursive
rm Robust.Shared.Tests/Networking/NetEncryptionDoSTest.cs # DeltaV - work around bug in the backport
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
+1 -1
View File
@@ -269,7 +269,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
| style="border: 1px solid black;" | [[File:SL_BreakingAndEntering.png]]
! style="border: 1px solid black;" | {{anchor|Breaking and Entering}}Breaking and Entering
| style="border: 1px solid black;" | 5 minutes
| style="border: 1px solid black;" | To break and enter into a high security area where one is not authorised nor invited, with intent to commit a crime within.
| style="border: 1px solid black;" | To break and enter into an area where one is not authorised nor invited.
|-
| style="border: 1px solid black;" | 203
| style="border: 1px solid black;" | [[File:SL_Rioting.png]]
+78 -18
View File
@@ -73,28 +73,88 @@ If you are adding a lot of C# code, then take advantage of partial classes. Put
Otherwise, **add comments on or around any changed lines.**
A comment on a new imported namespace:
### Single-Line Changes
Format should look like this.
```cs
using Content.Server.Psionics.Glimmer; // DeltaV
/* Importing Namespaces - Include optional comment if its not obvious what its being used for. */
using Content.Server._DV.Psionics.Glimmer; // DeltaV
using Content.Shared.Damage.Systems; // DeltaV - Addition of HandHeldArmor
/* Changing an upstream line - Same line as the change */
if (!TryComp<EyeComponent>(ent, out var eye) || _disabled) // DeltaV - check if disabled
/* Adding - Either same line or above the line. */
EnsureComp<PotentialPsionicComponent>(entity); // Deltav - Psionics
/* "Deleting" - Don't actually delete, just comment out and say why. This only applies to upstream code. */
// args.StatusIcons.Add(_prototype.Index(component.Icon)); // DeltaV - commented out. status icon now added above
```
A pair of comments enclosing a block of added code:
> * Its pretty obvious in the example above that importing `Content.Server._DV.Psionics.Glimmer` means we'll be interacting with glimmer so putting `// DeltaV - Add Glimmer` is needlessly redundant.
> * It's not as obvious what the `Content.Shared.Damage.Systems` namespace is used for, since its so broad, so adding a comment what feature is using it helps.
> * Actual code changes should almost always include the comment after ``// DeltaV`.
### Multi-Line Changes
Depending on how much you are editing, putting a comment on EACH line may be excessive, so if you have a larger block of code you are changing, denote it like so:
```cs
private EntityUid Slice(...)
// BEGIN DeltaV - Remove innate radio and radios from pockets
for (var i = 1; i <= 4; i++) // Arachnids have 4 pockets
{
...
_transform.SetLocalRotation(sliceUid, 0);
// DeltaV - start of deep frier stuff
var slicedEv = new FoodSlicedEvent(user, uid, sliceUid);
RaiseLocalEvent(uid, ref slicedEv);
// DeltaV - end of deep frier stuff
...
if (_inventory.TryGetSlotEntity(target, $"pocket{i}", out var headset) && HasComp<HeadsetComponent>(headset))
_inventory.TryUnequip(target, $"pocket{i}", true, true);
}
RemComp<ActiveRadioComponent>(target); // If the zombie has an innate radio, get rid of it.
// END DeltaV
```
> * Denoting these with a BEGIN and END clearly shows they are block of code without having to read the entire comment. This makes it easier to tell when you're dealing with single-line comments versus a block with merging in conflicts.
> * Case and order of the first two words is less of a concern. `// DeltaV Begin` or `// Begin DeltaV` will work fine too.
> * Try to make your blocks as small as possible, but use your discretion.
> * If you deleting multiple lines, use line comments (``//``) if its a few lines but if its a larger block (like commenting out an entire function), it is preferable to use block comments (`/* */`).
#### Soft Exceptions to the Multi-Line "Rules"
Some multi-line changes can use a single-line comment in certain scenarios. But if you are UNSURE, just use `// BEGIN DeltaV` and `// END DeltaV` comments like the previous section does and it'll be fine.
I'll give some examples.
```cs
/* This change comments out 3 lines but only needs a single line comment because commenting out the if statement implies that its logic will be commented out too. */
// if (obj.WasModified<TraitPrototype>()) // DeltaV - Refreshed in TraitsTab
// {
// _profileEditor.RefreshTraits();
// }
/* Same principle here. This adds two lines but the if statement implies the next line so commenting both lines isn't really needed. */
if (_flight.IsFlying(entity.Owner)) // DeltaV - Harpy Flight
return true;
```
### New Methods or Component Variables
Sometimes, you'll need to implement a whole new method or component variable and instead of wrapping it in `// BEGIN DeltaV` and `// END DeltaV`, you can just denote that it's a DeltaV function in the summary block before the function. This denotes the WHOLE function as a DeltaV addition.
```cs
/* New Method Example */
/// <summary>
/// DeltaV - Handle revealing ninja if cloaked when attacked by a hitscan attack.
/// </summary>
private void OnNinjaAttacked(Entity<SpaceNinjaComponent> ent, ref DamageChangedEvent args)
{
...
}
/* New Component Variable Example */
/// <summary>
/// DeltaV - If disabled the action will not disable when no charges remain. Use if you want to handle no charges differently.
/// </summary>
[DataField]
public bool DisableWhenEmpty = true;
```
In short:
* Use `// BEGIN DeltaV` and `// END Delta` to denote a *block* of changes.
* Keep blocks as small as possible.
* Use `// DeltaV` on or before the line if its not a block of changes.
* Use exceptions when they make sense.
### Changing Upstream Localization Fluent .ftl files
**Move all changed locale strings to a new DeltaV file** - use a `.ftl` file in the `_DV` folder. Comment out the old strings in the upstream file, and explain that they were moved.
@@ -104,10 +164,10 @@ Example:
Commented out old string in `Resources\Locale\en-US\xenoarchaeology\artifact-analyzer.ftl`
```
# DeltaV - moved to _DV file
#analysis-console-info-effect-value = [font="Monospace" size=11][color=gray]{ $state ->
# [true] {$info}
# *[false] Unlock nodes to gain info
#}[/color][/font]
# analysis-console-info-effect-value = [font="Monospace" size=11][color=gray]{ $state ->
# [true] {$info}
# *[false] Unlock nodes to gain info
# }[/color][/font]
```
The new version of the string in `Resources\Locale\en-US\_DV\xenoarchaeology\artifact-analyzer.ftl`
@@ -8,12 +8,14 @@ using Content.Shared.CCVar;
using Content.Shared.Ghost;
using Content.Shared.Mind;
using Content.Shared.Roles;
using Content.Shared.SSDIndicator; // DeltaV - SSD time indicator
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing; // DeltaV - SSD time indicator
namespace Content.Client.Administration;
@@ -26,6 +28,7 @@ internal sealed class AdminNameOverlay : Overlay
private readonly IUserInterfaceManager _userInterfaceManager;
private readonly SharedRoleSystem _roles;
private readonly IPrototypeManager _prototypeManager;
private readonly IGameTiming _timing; // DeltaV - Add timing
private readonly Font _font;
private readonly Font _fontBold;
private AdminOverlayAntagFormat _overlayFormat;
@@ -53,7 +56,8 @@ internal sealed class AdminNameOverlay : Overlay
IUserInterfaceManager userInterfaceManager,
IConfigurationManager config,
SharedRoleSystem roles,
IPrototypeManager prototypeManager)
IPrototypeManager prototypeManager,
IGameTiming timing) // DeltaV - Add timing
{
_system = system;
_entityManager = entityManager;
@@ -62,6 +66,7 @@ internal sealed class AdminNameOverlay : Overlay
_userInterfaceManager = userInterfaceManager;
_roles = roles;
_prototypeManager = prototypeManager;
_timing = timing; // DeltaV - Add timing
ZIndex = 200;
// Setting these to a specific ttf would break the antag symbols
_font = resourceCache.NotoStack();
@@ -231,6 +236,18 @@ internal sealed class AdminNameOverlay : Overlay
currentOffset += lineoffset;
}
// DeltaV - SSD Time START
if (_entityManager.TryGetComponent<SSDIndicatorComponent>(entity, out var ssdIndicator)
&& ssdIndicator.SsdSince is {} ssdSince)
{
color = Color.MediumPurple;
color.A = alpha;
var ssdText = Loc.GetString("admin-overlay-ssd-time", ("time", (_timing.CurTime - ssdSince).ToString("%hh':'mm':'ss")));
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, ssdText, uiScale, color);
currentOffset += lineoffset;
}
// DeltaV END
// Determine antag symbol
string? symbol;
switch (_overlaySymbolStyle)
@@ -5,6 +5,7 @@ using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing; // DeltaV - SSD time indicator
namespace Content.Client.Administration.Systems
{
@@ -19,6 +20,7 @@ namespace Content.Client.Administration.Systems
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IGameTiming _timing = default!; // DeltaV - added for SSD time indicator
private AdminNameOverlay _adminNameOverlay = default!;
@@ -36,7 +38,8 @@ namespace Content.Client.Administration.Systems
_userInterfaceManager,
_configurationManager,
_roles,
_proto);
_proto,
_timing); // DeltaV - Add timing
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
}
+10
View File
@@ -7,6 +7,7 @@ using Content.Shared.IdentityManagement;
using Content.Shared.Input;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Radio.Components; // DeltaV - Client-side Radio Colors
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -158,6 +159,15 @@ namespace Content.Client.Examine
// since there's probably one open already if it's coming in from the server.
var entity = GetEntity(ev.EntityUid);
// BEGIN DeltaV - Client-Side Radio Colors
// This *seems* a bit hacky, but we basically don't want to overwrite what's been pushed on the client-side,
// because the server will send us the server-side colors (default). I've yet to find a scenario where the
// server and client don't match, so until then, this is probably fine, if a little hacky.
if (HasComp<EncryptionKeyHolderComponent>(entity) || HasComp<EncryptionKeyComponent>(entity))
return;
// END DeltaV
OpenTooltip(player.Value, entity, ev.CenterAtCursor, ev.OpenAtOldTooltip, ev.KnowTarget);
UpdateTooltipInfo(player.Value, entity, ev.Message, ev.Verbs, getVerbs: false);
}
@@ -21,11 +21,8 @@ public sealed class SignalTimerBoundUserInterface : BoundUserInterface
_window = this.CreateWindow<SignalTimerWindow>();
_window.OnStartTimer += StartTimer;
_window.OnCurrentTextChanged += OnTextChanged;
// Begin Monolith
// _window.OnCurrentDelayMinutesChanged += OnDelayChanged;
// _window.OnCurrentDelaySecondsChanged += OnDelayChanged;
_window.OnCurrentDelayChanged += OnDelayChanged;
// End Monolith
_window.OnCurrentDelayMinutesChanged += OnDelayChanged;
_window.OnCurrentDelaySecondsChanged += OnDelayChanged;
}
public void StartTimer()
@@ -38,11 +35,11 @@ public sealed class SignalTimerBoundUserInterface : BoundUserInterface
SendMessage(new SignalTimerTextChangedMessage(newText));
}
private void OnDelayChanged(TimeSpan newDelay) // Monolith - Change from string to TimeSpan
private void OnDelayChanged(string newDelay)
{
if (_window == null)
return;
SendMessage(new SignalTimerDelayChangedMessage(newDelay)); // Mono
SendMessage(new SignalTimerDelayChangedMessage(_window.GetDelay()));
}
/// <summary>
@@ -57,11 +54,8 @@ public sealed class SignalTimerBoundUserInterface : BoundUserInterface
return;
_window.SetCurrentText(cast.CurrentText);
// Begin Monolith
// _window.SetCurrentDelayMinutes(cast.CurrentDelayMinutes);
// _window.SetCurrentDelaySeconds(cast.CurrentDelaySeconds);
_window.SetCurrentDelay(cast.CurrentDelay);
// End Monolith
_window.SetCurrentDelayMinutes(cast.CurrentDelayMinutes);
_window.SetCurrentDelaySeconds(cast.CurrentDelaySeconds);
_window.SetShowText(cast.ShowText);
_window.SetTriggerTime(cast.TriggerTime);
_window.SetTimerStarted(cast.TimerStarted);
@@ -7,14 +7,10 @@
</BoxContainer>
<BoxContainer Name="DelayEdit" Orientation="Horizontal">
<Label Name="CurrentDelay" Text="{Loc signal-timer-menu-delay}" />
<!-- Mono: Changed from integer MM:SS to float seconds
<LineEdit Name="CurrentDelayEditMinutes" MinWidth="32" />
<Label Name="Colon" Text=":" />
<LineEdit Name="CurrentDelayEditSeconds" MinWidth="32" />
<Label Name="DelayInfo" Text=" (mm:ss)" />
-->
<FloatSpinBox Name="CurrentDelayEdit" MinWidth="32" />
<Label Text="s" />
</BoxContainer>
<Button Name="StartTimer" Text="{Loc signal-timer-menu-start}" />
</BoxContainer>
@@ -15,11 +15,8 @@ public sealed partial class SignalTimerWindow : DefaultWindow
private const int MaxTextLength = 5;
public event Action<string>? OnCurrentTextChanged;
// Begin Monolith
// public event Action<string>? OnCurrentDelayMinutesChanged;
// public event Action<string>? OnCurrentDelaySecondsChanged;
public event Action<TimeSpan>? OnCurrentDelayChanged;
// End Monolith
public event Action<string>? OnCurrentDelayMinutesChanged;
public event Action<string>? OnCurrentDelaySecondsChanged;
private TimeSpan? _triggerTime;
@@ -33,11 +30,8 @@ public sealed partial class SignalTimerWindow : DefaultWindow
IoCManager.InjectDependencies(this);
CurrentTextEdit.OnTextChanged += e => OnCurrentTextChange(e.Text);
// Begin Monolith
// CurrentDelayEditMinutes.OnTextChanged += e => OnCurrentDelayMinutesChange(e.Text);
// CurrentDelayEditSeconds.OnTextChanged += e => OnCurrentDelaySecondsChange(e.Text);
CurrentDelayEdit.OnValueChanged += e => CurrentDelayChange(TimeSpan.FromSeconds(e.Value));
// End Monolith
CurrentDelayEditMinutes.OnTextChanged += e => OnCurrentDelayMinutesChange(e.Text);
CurrentDelayEditSeconds.OnTextChanged += e => OnCurrentDelaySecondsChange(e.Text);
StartTimer.OnPressed += _ => StartTimerWeh();
}
@@ -83,93 +77,81 @@ public sealed partial class SignalTimerWindow : DefaultWindow
OnCurrentTextChanged?.Invoke(text);
}
// Begin Monolith
// public void OnCurrentDelayMinutesChange(string text)
// {
// List<char> toRemove = new();
// foreach (var a in text)
// {
// if (!char.IsDigit(a))
// toRemove.Add(a);
// }
// foreach (var a in toRemove)
// {
// CurrentDelayEditMinutes.Text = text.Replace(a.ToString(),"");
// }
// if (CurrentDelayEditMinutes.Text == "")
// return;
// while (CurrentDelayEditMinutes.Text[0] == '0' && CurrentDelayEditMinutes.Text.Length > 2)
// {
// CurrentDelayEditMinutes.Text = CurrentDelayEditMinutes.Text.Remove(0, 1);
// }
// if (CurrentDelayEditMinutes.Text.Length > 2)
// {
// CurrentDelayEditMinutes.Text = CurrentDelayEditMinutes.Text.Remove(2);
// }
// OnCurrentDelayMinutesChanged?.Invoke(CurrentDelayEditMinutes.Text);
// }
// public void OnCurrentDelaySecondsChange(string text)
// {
// List<char> toRemove = new();
// foreach (var a in text)
// {
// if (!char.IsDigit(a))
// toRemove.Add(a);
// }
// foreach (var a in toRemove)
// {
// CurrentDelayEditSeconds.Text = text.Replace(a.ToString(), "");
// }
// if (CurrentDelayEditSeconds.Text == "")
// return;
// while (CurrentDelayEditSeconds.Text[0] == '0' && CurrentDelayEditSeconds.Text.Length > 2)
// {
// CurrentDelayEditSeconds.Text = CurrentDelayEditSeconds.Text.Remove(0, 1);
// }
// if (CurrentDelayEditSeconds.Text.Length > 2)
// {
// CurrentDelayEditSeconds.Text = CurrentDelayEditSeconds.Text.Remove(2);
// }
// OnCurrentDelaySecondsChanged?.Invoke(CurrentDelayEditSeconds.Text);
// }
public void CurrentDelayChange(TimeSpan newValue)
public void OnCurrentDelayMinutesChange(string text)
{
OnCurrentDelayChanged?.Invoke(newValue);
List<char> toRemove = new();
foreach (var a in text)
{
if (!char.IsDigit(a))
toRemove.Add(a);
}
foreach (var a in toRemove)
{
CurrentDelayEditMinutes.Text = text.Replace(a.ToString(),"");
}
if (CurrentDelayEditMinutes.Text == "")
return;
while (CurrentDelayEditMinutes.Text[0] == '0' && CurrentDelayEditMinutes.Text.Length > 2)
{
CurrentDelayEditMinutes.Text = CurrentDelayEditMinutes.Text.Remove(0, 1);
}
if (CurrentDelayEditMinutes.Text.Length > 2)
{
CurrentDelayEditMinutes.Text = CurrentDelayEditMinutes.Text.Remove(2);
}
OnCurrentDelayMinutesChanged?.Invoke(CurrentDelayEditMinutes.Text);
}
public void OnCurrentDelaySecondsChange(string text)
{
List<char> toRemove = new();
foreach (var a in text)
{
if (!char.IsDigit(a))
toRemove.Add(a);
}
foreach (var a in toRemove)
{
CurrentDelayEditSeconds.Text = text.Replace(a.ToString(), "");
}
if (CurrentDelayEditSeconds.Text == "")
return;
while (CurrentDelayEditSeconds.Text[0] == '0' && CurrentDelayEditSeconds.Text.Length > 2)
{
CurrentDelayEditSeconds.Text = CurrentDelayEditSeconds.Text.Remove(0, 1);
}
if (CurrentDelayEditSeconds.Text.Length > 2)
{
CurrentDelayEditSeconds.Text = CurrentDelayEditSeconds.Text.Remove(2);
}
OnCurrentDelaySecondsChanged?.Invoke(CurrentDelayEditSeconds.Text);
}
// End Monolith
public void SetCurrentText(string text)
{
CurrentTextEdit.Text = text;
}
// Begin Monolith
// public void SetCurrentDelayMinutes(string delay)
// {
// CurrentDelayEditMinutes.Text = delay;
// }
// public void SetCurrentDelaySeconds(string delay)
// {
// CurrentDelayEditSeconds.Text = delay;
// }
public void SetCurrentDelay(TimeSpan delay)
public void SetCurrentDelayMinutes(string delay)
{
CurrentDelayEdit.Value = (float)delay.TotalSeconds;
CurrentDelayEditMinutes.Text = delay;
}
// End Monolith
public void SetCurrentDelaySeconds(string delay)
{
CurrentDelayEditSeconds.Text = delay;
}
public void SetShowText(bool showTime)
{
TextEdit.Visible = showTime;
@@ -194,10 +176,8 @@ public sealed partial class SignalTimerWindow : DefaultWindow
public void SetHasAccess(bool hasAccess)
{
CurrentTextEdit.Editable = hasAccess;
// Begin Monolith
// CurrentDelayEditMinutes.Editable = hasAccess;
// CurrentDelayEditSeconds.Editable = hasAccess;
// End Monolith
CurrentDelayEditMinutes.Editable = hasAccess;
CurrentDelayEditSeconds.Editable = hasAccess;
StartTimer.Disabled = !hasAccess;
}
@@ -206,13 +186,10 @@ public sealed partial class SignalTimerWindow : DefaultWindow
/// </summary>
public TimeSpan GetDelay()
{
// Begin Monolith
// if (!double.TryParse(CurrentDelayEditMinutes.Text, out var minutes))
// minutes = 0;
// if (!double.TryParse(CurrentDelayEditSeconds.Text, out var seconds))
// seconds = 0;
// return TimeSpan.FromMinutes(minutes) + TimeSpan.FromSeconds(seconds);
return TimeSpan.FromSeconds(CurrentDelayEdit.Value);
// End Monolith
if (!double.TryParse(CurrentDelayEditMinutes.Text, out var minutes))
minutes = 0;
if (!double.TryParse(CurrentDelayEditSeconds.Text, out var seconds))
seconds = 0;
return TimeSpan.FromMinutes(minutes) + TimeSpan.FromSeconds(seconds);
}
}
@@ -1,5 +1,6 @@
using Content.Client.Pinpointer.UI;
using Robust.Client.Graphics;
using Robust.Client.Player; // DeltaV
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
@@ -7,6 +8,9 @@ namespace Content.Client.Medical.CrewMonitoring;
public sealed partial class CrewMonitoringNavMapControl : NavMapControl
{
[Dependency] private IPlayerManager _playerManager = default!; // DeltaV
private readonly SharedTransformSystem _xform; // DeltaV
public NetEntity? Focus;
public Dictionary<NetEntity, string> LocalizedNames = new();
@@ -15,6 +19,9 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl
public CrewMonitoringNavMapControl() : base()
{
IoCManager.InjectDependencies(this); // DeltaV - Gotta inject!
_xform = EntManager.System<SharedTransformSystem>(); // DeltaV - Gotta inject!
WallColor = new Color(192, 122, 196);
TileColor = new(71, 42, 72);
BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity));
@@ -77,4 +84,27 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl
_trackedEntityLabel.Text = string.Empty;
_trackedEntityPanel.Visible = false;
}
/// <summary>
/// DeltaV - Do some things before the base NavMap Draw and also force the redraw of the map
/// after if the MapUid was null when the CrewMonitor UI was opened.
/// </summary>
/// <param name="handle"></param>
protected override void Draw(DrawingHandleScreen handle)
{
// MapUid will not have a value if the crew monitor UI was activated off-grid.
var forceMapUpdate = false;
if (!MapUid.HasValue && _playerManager.LocalEntity is { } player)
{
MapUid = _xform.GetGrid(player);
forceMapUpdate = true;
}
base.Draw(handle);
// We need to call this after Draw() because UpdateNavMap has functions that uses variables
// set in Draw()
if (forceMapUpdate)
UpdateNavMap();
}
}
@@ -1,13 +1,16 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.Medical.CrewMonitoring"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:crewMonitoring="clr-namespace:Content.Client._DV.CrewMonitoring"
Title="{Loc crew-monitoring-ui-title}"
Resizable="False"
SetSize="1210 700"
MinSize="1210 700">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
<ui:CrewMonitoringNavMapControl Name="NavMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20"/>
<ui:CrewMonitoringNavMapControl Name="NavMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20" Visible="False" />
<crewMonitoring:CrewMonitoringShuttleControl Name="ShuttleMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20" Visible="False" />
<BoxContainer Orientation="Vertical" Margin="0 0 10 0">
<controls:StripeBack>
<PanelContainer>
@@ -5,6 +5,7 @@ using Content.Client.Pinpointer.UI;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Medical.SuitSensor;
using Content.Shared.Station; // DeltaV - crew monitoring radar
using Content.Shared.StatusIcon;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
@@ -27,6 +28,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly SharedTransformSystem _transformSystem;
private readonly SpriteSystem _spriteSystem;
private readonly SharedStationSystem _station; // DeltaV - crew monitoring radar
private NetEntity? _trackedEntity;
private bool _tryToScrollToListFocus;
@@ -39,8 +41,10 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
_transformSystem = _entManager.System<SharedTransformSystem>();
_spriteSystem = _entManager.System<SpriteSystem>();
_station = _entManager.System<SharedStationSystem>(); // DeltaV - crew monitoring radar
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
ShuttleMap.NavMap = NavMap; // DeltaV - crew monitoring radar
}
public void Set(string stationName, EntityUid? mapUid)
@@ -50,8 +54,10 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
if (_entManager.TryGetComponent<TransformComponent>(mapUid, out var xform))
NavMap.MapUid = xform.GridUid;
else
NavMap.Visible = false;
// Begin DeltaV - handle navmap visibility based on current position
// else
// NavMap.Visible = false;
// End DeltaV - handle navmap visibility based on current position
StationName.AddStyleClass("LabelBig");
StationName.Text = stationName;
@@ -62,6 +68,28 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
{
base.FrameUpdate(args);
// Begin DeltaV - handle navmap visibility based on current position
var ent = ShuttleMap.Owner;
// If an entity is being tracked, use them as the focus, since they may be off-station
// and we want to use the nav map if they are.
if (_trackedEntity is { } trackedEntity)
ent = _entManager.GetEntity(trackedEntity);
// We need to make sure the entity is valid because for some reason, it *could* be an
// invalid entity and GetOwningStation will throw an exception.
if (ent.HasValue && ent.Value.IsValid() && _station.GetOwningStation(ent) is not null)
{
NavMap.Visible = true;
ShuttleMap.Visible = false;
}
else
{
NavMap.Visible = false;
ShuttleMap.Visible = true;
}
// End DeltaV - handle navmap visibility based on current position
if (_tryToScrollToListFocus)
TryToScrollToFocus();
}
@@ -69,6 +97,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
public void ShowSensors(List<SuitSensorStatus> sensors, EntityUid monitor, EntityCoordinates? monitorCoords)
{
ClearOutDatedData();
ShuttleMap.Owner = monitor; // DeltaV - crew monitoring radar
// No server label
if (sensors.Count == 0)
@@ -304,7 +333,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
jobContainer.AddChild(jobLabel);
// Add user coordinates to the navmap
if (coordinates != null && NavMap.Visible && _blipTexture != null)
if (coordinates != null && _blipTexture != null) // DeltaV - don't filter the navmap visibility
{
NavMap.TrackedEntities.TryAdd(sensor.SuitSensorUid,
new NavMapBlip
@@ -323,6 +352,14 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
if (_trackedEntity == sensor.SuitSensorUid)
{
_trackedEntity = null;
// BEGIN DeltaV - center on crew monitor user if deselecting
// This fixes some weird bug where if you deselect a tracked entity, it will
// just not draw the map at all in some cases.
if (ShuttleMap.Owner is { } handheld &&
_transformSystem.TryGetMapOrGridCoordinates(handheld, out var userCoords) &&
userCoords is { } coords)
NavMap.CenterToCoordinates(coords);
// END DeltaV
}
else
@@ -10,6 +10,7 @@
<tabs:AudioTab Name="AudioTab" />
<tabs:AccessibilityTab Name="AccessibilityTab" />
<tabs:AdminOptionsTab Name="AdminOptionsTab" />
<dtabs:RadioTab Name="RadioTab" /> <!-- DeltaV - Client-side Radio Options -->
<dtabs:DeltaTab Name="DeltaTab" /> <!-- DeltaV -->
</TabContainer>
</DefaultWindow>
@@ -21,7 +21,8 @@ namespace Content.Client.Options.UI
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-accessibility"));
Tabs.SetTabTitle(5, Loc.GetString("ui-options-tab-admin"));
Tabs.SetTabTitle(6, Loc.GetString("ui-options-tab-deltav")); // DeltaV specific settings
Tabs.SetTabTitle(6, Loc.GetString("ui-options-tab-radio")); // DeltaV - Radio Channel Colors
Tabs.SetTabTitle(7, Loc.GetString("ui-options-tab-deltav")); // DeltaV specific settings
UpdateTabs();
}
@@ -36,6 +37,7 @@ namespace Content.Client.Options.UI
AccessibilityTab.Control.ReloadValues();
AudioTab.Control.ReloadValues();
AdminOptionsTab.Control.ReloadValues();
RadioTab.Control.ReloadValues(); // DeltaV - Radio Channel Colors
DeltaTab.Control.ReloadValues(); // DeltaV
}
}
@@ -20,6 +20,7 @@
<CheckBox Name="ShowLoocAboveHeadCheckBox" Text="{Loc 'ui-options-show-looc-on-head'}" />
<CheckBox Name="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" />
<CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" />
<CheckBox Name="ChatFollowButton" Text="{Loc 'ui-options-chat-follow-button'}" />
<Label Text="{Loc 'ui-options-general-cursor'}"
StyleClasses="LabelKeyText"/>
<CheckBox Name="ShowHeldItemCheckBox" Text="{Loc 'ui-options-show-held-item'}" />
@@ -63,6 +63,7 @@ public sealed partial class MiscTab : Control
Control.AddOptionCheckBox(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox);
Control.AddOptionCheckBox(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox);
Control.AddOptionCheckBox(CCVars.StaticStorageUI, StaticStorageUI);
Control.AddOptionCheckBox(CCVars.InterfaceChatFollowButton, ChatFollowButton);
Control.Initialize();
}
@@ -56,8 +56,8 @@ public partial class NavMapControl : MapGridControl
// Constants
protected float UpdateTime = 1.0f;
protected float MaxSelectableDistance = 10f;
protected float MinDragDistance = 5f;
public float MaxSelectableDistance = 10f; // DeltaV - accessible to CrewMonitoringShuttleControl
public float MinDragDistance = 5f; // DeltaV - accessible to CrewMonitoringShuttleControl
protected static float MinDisplayedRange = 8f;
protected static float MaxDisplayedRange = 128f;
protected static float DefaultDisplayedRange = 48f;
@@ -193,6 +193,13 @@ public partial class NavMapControl : MapGridControl
_recenter.Disabled = false;
}
// Begin DeltaV - we need access to that from outside
public void TrackEntity(NetEntity? entity)
{
TrackedEntitySelectedAction?.Invoke(entity);
}
// End DeltaV - we need access to that from outside
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
@@ -19,6 +19,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly Shared.SSDIndicator.SSDIndicatorSystem _shared = default!; // DeltaV - SSD Recency, don't want to rename the upstream class
public override void Initialize()
{
@@ -35,13 +36,26 @@ public sealed class SSDIndicatorSystem : EntitySystem
!HasComp<ActiveNPCComponent>(uid) &&
HasComp<MindExaminableComponent>(uid))
{
// Begin DeltaV Addition
// Begin DeltaV Additions
var ev = new ShowSSDIndicatorEvent();
RaiseLocalEvent(uid, ref ev);
if (ev.Hidden)
return;
// End DeltaV Addition
args.StatusIcons.Add(_prototype.Index(component.Icon));
// SSD Recency Indicator
var stage = _shared.GetStage(new Entity<SSDIndicatorComponent>(uid, component));
var icon = stage switch
{
SsdStage.VeryRecent => component.VeryRecentIcon,
SsdStage.Recent => component.RecentIcon,
SsdStage.Cryoable => component.Icon,
_ => throw new InvalidOperationException($"{ToPrettyString(uid)} has an invalid SSD stage {stage}."),
};
args.StatusIcons.Add(_prototype.Index(icon));
// End DeltaV Additions
// args.StatusIcons.Add(_prototype.Index(component.Icon)); // DeltaV - commented out. status icon now added above
}
}
}
-111
View File
@@ -159,35 +159,6 @@ namespace Content.Client.Stylesheets
public static readonly Color ChatBackgroundColor = Color.FromHex("#25252ADD");
// DeltaV - AAC button variables
public static readonly string CommandButtonClass = "CommandButton";
public static readonly string EngineeringButtonClass = "EngineeringButton";
public static readonly string EpistemicsButtonClass = "EpistemicsButton";
public static readonly string JusticeButtonClass = "JusticeButton";
public static readonly string LogisticsButtonClass = "LogisticsButton";
public static readonly string MedicalButtonClass = "MedicalButton";
public static readonly string SecurityButtonClass = "SecurityButton";
public static readonly string ServiceButtonClass = "ServiceButton";
// DeltaV - AAC button colors
public static readonly Color CommandButtonColorDefault = Color.FromHex("#404A58");
public static readonly Color CommandColorHovered = Color.FromHex("#4F587B");
public static readonly Color EngineeringButtonColorDefault = Color.FromHex("#77684B");
public static readonly Color EngineeringColorHovered = Color.FromHex("#776D71");
public static readonly Color EpistemicsButtonColorDefault = Color.FromHex("#6F5973");
public static readonly Color EpistemicsColorHovered = Color.FromHex("#71638E");
public static readonly Color LogisticsButtonColorDefault = Color.FromHex("#61503A");
public static readonly Color LogisticsColorHovered = Color.FromHex("#675C64");
public static readonly Color JusticeButtonColorDefault = Color.FromHex("#4F3D4C");
public static readonly Color JusticeColorHovered = Color.FromHex("#5C4B5A");
public static readonly Color MedicalButtonColorDefault = Color.FromHex("#49687D");
public static readonly Color MedicalColorHovered = Color.FromHex("#556E95");
public static readonly Color SecurityButtonColorDefault = Color.FromHex("#724449");
public static readonly Color SecurityColorHovered = Color.FromHex("#745370");
public static readonly Color ServiceButtonColorDefault = Color.FromHex("#607952");
public static readonly Color ServiceColorHovered = Color.FromHex("#667A76");
// End DeltaV
public const string StyleClassCrewManifestGender = "CrewManifestGender"; //Delta-V - Manifest pronouns
// i'm not sure what the missing symbols were referencing, and this is getting obseleted anyway so:
@@ -1677,88 +1648,6 @@ namespace Content.Client.Stylesheets
BackgroundColor = FancyTreeSelectedRowColor,
}),
// DeltaV - AAC button styles
Element<ContainerButton>()
.Class(CommandButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, CommandButtonColorDefault),
Element<ContainerButton>()
.Class(CommandButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, CommandColorHovered),
Element<ContainerButton>()
.Class(EngineeringButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, EngineeringButtonColorDefault),
Element<ContainerButton>()
.Class(EngineeringButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, EngineeringColorHovered),
Element<ContainerButton>()
.Class(EpistemicsButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, EpistemicsButtonColorDefault),
Element<ContainerButton>()
.Class(EpistemicsButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, EpistemicsColorHovered),
Element<ContainerButton>()
.Class(LogisticsButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, LogisticsButtonColorDefault),
Element<ContainerButton>()
.Class(LogisticsButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, LogisticsColorHovered),
Element<ContainerButton>()
.Class(MedicalButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, MedicalButtonColorDefault),
Element<ContainerButton>()
.Class(MedicalButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, MedicalColorHovered),
Element<ContainerButton>()
.Class(SecurityButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, SecurityButtonColorDefault),
Element<ContainerButton>()
.Class(SecurityButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, SecurityColorHovered),
Element<ContainerButton>()
.Class(ServiceButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, ServiceButtonColorDefault),
Element<ContainerButton>()
.Class(ServiceButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, ServiceColorHovered),
Element<ContainerButton>()
.Class(JusticeButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, JusticeButtonColorDefault),
Element<ContainerButton>()
.Class(JusticeButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, JusticeColorHovered),
// End DeltaV
// Silicon law edit ui
Element<Label>().Class(SiliconLawContainer.StyleClassSiliconLawPositionLabel)
.Prop(Label.StylePropertyFontColor, NanoGold),
@@ -32,6 +32,21 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
}
}
// Begin DeltaV - node crawling
private Type[] _types = new Type[] { };
[ViewVariables]
public Type[] Types
{
get => _types;
set
{
_types = value;
UpdateAll();
}
}
// End DeltaV - node crawling
public override void Initialize()
{
base.Initialize();
@@ -65,6 +80,17 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
var revealed = !covered || ShowAll || scannerRevealed;
// Begin DeltaV - node crawling
foreach (var type in _types)
{
if (!HasComp(uid, type))
continue;
revealed = true;
break;
}
// End DeltaV - node crawling
// set visibility & color of each layer
foreach (var layer in args.Sprite.AllLayers)
{
@@ -1,6 +1,7 @@
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Text.RegularExpressions; // DeltaV - Client-Side Radio Channel Colors
using Content.Client.Administration.Managers;
using Content.Client.Chat;
using Content.Client.Chat.Managers;
@@ -839,8 +840,38 @@ public sealed partial class ChatUIController : UIController
}
}
/// <summary>
/// DeltaV - Regex expression to match on the chat message text in order to replace it with another color.
/// </summary>
private static readonly Regex RadioChatColorRegex = new(@"color=#?([a-fA-F0-9]{8})", RegexOptions.Compiled);
/// <summary>
/// DeltaV - Replaces the channel color hex code in a message with the client-side hex color
/// overridden by <see cref="Content.Client._DV.Radio.RadioChannelColorSystem"/> .
/// </summary>
/// <param name="message">The message to find and replace the color hex code in.</param>
/// <param name="channelProtoId">The proto ID of the channel. Basically the replacement color.</param>
/// <returns>The message with the color hex code replaced, if it exists.</returns>
private string ReplaceRadioChannelColorFromServer(string message, string channelProtoId)
{
if (_prototypeManager.Resolve<RadioChannelPrototype>(channelProtoId, out var channel))
{
return RadioChatColorRegex.Replace(message, $"color={channel.Color.ToHex()}");
}
return message;
}
public void ProcessChatMessage(ChatMessage msg, bool speechBubble = true)
{
// BEGIN DeltaV
// Needs to happen before ANY other color or pattern matching so that we don't screw up the other colors in the message.
if (msg.Channel == ChatChannel.Radio && msg.RadioChannelProtoId is { } channel)
{
msg.WrappedMessage = ReplaceRadioChannelColorFromServer(msg.WrappedMessage, channel);
}
// END DeltaV
// color the name unless it's something like "the old man"
if ((msg.Channel == ChatChannel.Local || msg.Channel == ChatChannel.Whisper) && _chatNameColorsEnabled)
{
@@ -12,6 +12,19 @@ namespace Content.Client._DV.AACTablet.UI;
[CommonSheetlet]
public sealed class AACSheetlet<T> : Sheetlet<T> where T : PalettedStylesheet, IButtonConfig
{
private static readonly (string StyleClass, Color Normal, Color Hover)[] SubjectButtonColors =
[
// Department colors
("CommandButton", Color.FromHex("#404A58"), Color.FromHex("#4F587B")),
("EngineeringButton", Color.FromHex("#77684B"), Color.FromHex("#776D71")),
("EpistemicsButton", Color.FromHex("#6F5973"), Color.FromHex("#71638E")),
("JusticeButton", Color.FromHex("#4F3D4C"), Color.FromHex("#5C4B5A")),
("LogisticsButton", Color.FromHex("#61503A"), Color.FromHex("#675C64")),
("MedicalButton", Color.FromHex("#49687D"), Color.FromHex("#556E95")),
("SecurityButton", Color.FromHex("#724449"), Color.FromHex("#745370")),
("ServiceButton", Color.FromHex("#607952"), Color.FromHex("#667A76")),
];
public override StyleRule[] GetRules(T sheet, object config)
{
// Create style boxes
@@ -81,6 +94,21 @@ public sealed class AACSheetlet<T> : Sheetlet<T> where T : PalettedStylesheet, I
.Prop(Control.StylePropertyModulateSelf, Color.DarkRed),
]);
SubjectColorRules(rules);
return rules.ToArray();
}
// AAC - Makes sure colors are applied to subject buttons
private static void SubjectColorRules(List<StyleRule> rules)
{
foreach (var (styleClass, normal, hover) in SubjectButtonColors)
{
rules.AddRange([
E<ContainerButton>().Class(ContainerButton.StyleClassButton).Class(styleClass)
.PseudoNormal().Prop(Control.StylePropertyModulateSelf, normal),
E<ContainerButton>().Class(ContainerButton.StyleClassButton).Class(styleClass)
.PseudoHovered().Prop(Control.StylePropertyModulateSelf, hover),
]);
}
}
}
@@ -218,7 +218,7 @@ public sealed partial class AACWindow : FancyWindow
MaxSize = new Vector2(ButtonWidth, ButtonWidth),
ClipText = false,
HorizontalExpand = true,
StyleClasses = { styleClass }
StyleClasses = { ContainerButton.StyleClassButton, styleClass }
};
var buttonLabel = new RichTextLabel
@@ -0,0 +1,180 @@
using System.Numerics;
using Content.Client.Pinpointer.UI;
using Content.Client.Shuttles.UI;
using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Systems;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
namespace Content.Client._DV.CrewMonitoring;
public sealed class CrewMonitoringShuttleControl : BaseShuttleControl
{
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly SharedShuttleSystem _shuttles;
private readonly SharedTransformSystem _transform;
public EntityUid? Owner;
public NavMapControl? NavMap;
private List<Entity<MapGridComponent>> _grids = new();
public CrewMonitoringShuttleControl() : base(64f, 256f, 256f)
{
_shuttles = EntManager.System<SharedShuttleSystem>();
_transform = EntManager.System<SharedTransformSystem>();
}
protected override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
DrawBacking(handle);
DrawCircles(handle);
if (Owner is not { } owner || NavMap is not { } navMap)
return;
var xformQuery = EntManager.GetEntityQuery<TransformComponent>();
var fixturesQuery = EntManager.GetEntityQuery<FixturesComponent>();
var bodyQuery = EntManager.GetEntityQuery<PhysicsComponent>();
var iffQuery = EntManager.GetEntityQuery<IFFComponent>();
if (!xformQuery.TryGetComponent(owner, out var xform)
|| xform.MapID == MapId.Nullspace)
{
return;
}
var coordinates = new EntityCoordinates(owner, Vector2.Zero);
var mapPos = _transform.ToMapCoordinates(coordinates);
var ourEntRot = Angle.Zero;
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
Matrix3x2.Invert(ourEntMatrix, out var worldToShuttle);
var shuttleToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
var worldToView = worldToShuttle * shuttleToView;
var viewBounds = new Box2Rotated(new Box2(-WorldRange, -WorldRange, WorldRange, WorldRange).Translated(mapPos.Position), ourEntRot, mapPos.Position);
var viewAABB = viewBounds.CalcBoundingBox();
_grids.Clear();
_mapManager.FindGridsIntersecting(xform.MapID, new Box2(mapPos.Position - MaxRadarRangeVector, mapPos.Position + MaxRadarRangeVector), ref _grids, approx: true, includeMap: false);
foreach (var grid in _grids)
{
if (!fixturesQuery.HasComponent(grid))
continue;
var body = bodyQuery.GetComponent(grid);
iffQuery.TryComp(grid, out var iff);
if (!_shuttles.CanDraw(grid, body, iff))
continue;
var curGridToWorld = _transform.GetWorldMatrix(grid);
var curGridToView = curGridToWorld * worldToShuttle * shuttleToView;
var gridAABB = curGridToWorld.TransformBox(grid.Comp.LocalAABB);
if (!gridAABB.Intersects(viewAABB))
continue;
var labelColor = _shuttles.GetIFFColor(grid, self: false, iff);
DrawGrid(handle, curGridToView, grid, labelColor);
}
var curTime = Timing.RealTime;
var blinkFrequency = 1f / 1f;
var lit = curTime.TotalSeconds % blinkFrequency > blinkFrequency / 2f;
var box = SizeBox;
foreach (var (coord, value) in navMap.TrackedCoordinates)
{
if (!lit || !value.Visible)
continue;
var blipPos = _transform.ToMapCoordinates(coord);
if (mapPos.MapId == MapId.Nullspace)
continue;
handle.DrawCircle(Vector2.Clamp(Vector2.Transform(blipPos.Position, worldToView), box.TopLeft, box.BottomRight), float.Sqrt(MinimapScale) * 2f, value.Color);
}
foreach (var blip in navMap.TrackedEntities.Values)
{
if (blip.Blinks && !lit)
continue;
var blipPos = _transform.ToMapCoordinates(blip.Coordinates);
if (mapPos.MapId == MapId.Nullspace)
continue;
handle.DrawCircle(Vector2.Clamp(Vector2.Transform(blipPos.Position, worldToView), box.TopLeft, box.BottomRight), float.Sqrt(MinimapScale) * 2f, blip.Color);
}
}
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
if (Owner is not { } owner || NavMap is null)
return;
if (!EntManager.TryGetComponent<TransformComponent>(owner, out var xform) || xform.MapID == MapId.Nullspace)
{
return;
}
var ourEntRot = Angle.Zero;
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
Matrix3x2.Invert(ourEntMatrix, out var worldToShuttle);
var shuttleToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
var worldToView = worldToShuttle * shuttleToView;
Matrix3x2.Invert(worldToView, out var viewToWorld);
if (args.Function == EngineKeyFunctions.UIClick)
{
if (NavMap.TrackedEntities.Count == 0)
return;
if ((StartDragPosition - args.PointerLocation.Position).Length() > NavMap.MinDragDistance)
return;
var localPosition = args.PointerLocation.Position - GlobalPixelPosition;
var worldPosition = Vector2.Transform(localPosition, viewToWorld);
var closestEntity = NetEntity.Invalid;
var closestDistance = float.PositiveInfinity;
foreach (var (currentEntity, blip) in NavMap.TrackedEntities)
{
if (!blip.Selectable)
continue;
var currentDistance = (_transform.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length();
if (closestDistance < currentDistance || currentDistance * MinimapScale > NavMap.MaxSelectableDistance)
continue;
closestEntity = currentEntity;
closestDistance = currentDistance;
}
if (closestDistance > NavMap.MaxSelectableDistance || !closestEntity.IsValid())
return;
NavMap.TrackEntity(closestEntity);
}
else if (args.Function == EngineKeyFunctions.UIRightClick)
{
NavMap.TrackEntity(null);
}
}
}
@@ -0,0 +1,43 @@
using Content.Client.SubFloor;
using Content.Shared._DV.NodeCrawl;
using Robust.Client.Player;
using Robust.Shared.Player;
namespace Content.Client._DV.NodeCrawl;
public sealed class NodeCrawlSystem : SharedNodeCrawlSystem
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly SubFloorHideSystem _subfloor = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<NodeCrawlerComponent, LocalPlayerAttachedEvent>(OnAttached);
SubscribeLocalEvent<NodeCrawlerComponent, LocalPlayerDetachedEvent>(OnDetached);
SubscribeLocalEvent<NodeCrawlerComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
}
private void OnAttached(Entity<NodeCrawlerComponent> ent, ref LocalPlayerAttachedEvent args)
{
if (ent.Comp.Mover is not null)
_subfloor.Types = ent.Comp.RevealedComponents;
}
private void OnDetached(Entity<NodeCrawlerComponent> ent, ref LocalPlayerDetachedEvent args)
{
_subfloor.Types = new Type[] { };
}
private void OnAfterAutoHandleState(Entity<NodeCrawlerComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (_player.LocalEntity != ent)
return;
if (ent.Comp.Mover is not null)
_subfloor.Types = ent.Comp.RevealedComponents;
else
_subfloor.Types = new Type[] { };
}
}
@@ -0,0 +1,87 @@
<tabs:RadioTab xmlns="https://spacestation14.io"
xmlns:tabs="clr-namespace:Content.Client._DV.Options.UI.Tabs"
xmlns:ui="clr-namespace:Content.Client.Options.UI">
<BoxContainer Orientation="Vertical">
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
<BoxContainer Orientation="Vertical" Margin="8">
<!-- Common -->
<Label Text="{Loc 'ui-options-radio-header-common'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioCommonColorSlider"
Example="{Loc 'ui-options-radio-example-common'}"/>
<Button Name="ResetCommonColorButton" Text="{Loc 'ui-options-radio-reset-common'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Command -->
<Label Text="{Loc 'ui-options-radio-header-command'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioCommandColorSlider"
Example="{Loc 'ui-options-radio-example-command'}"/>
<Button Name="ResetCommandColorButton" Text="{Loc 'ui-options-radio-reset-command'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Security -->
<Label Text="{Loc 'ui-options-radio-header-security'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioSecurityColorSlider"
Example="{Loc 'ui-options-radio-example-security'}"/>
<Button Name="ResetSecurityColorButton" Text="{Loc 'ui-options-radio-reset-security'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Medical -->
<Label Text="{Loc 'ui-options-radio-header-medical'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioMedicalColorSlider"
Example="{Loc 'ui-options-radio-example-medical'}"/>
<Button Name="ResetMedicalColorButton" Text="{Loc 'ui-options-radio-reset-medical'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Engineering -->
<Label Text="{Loc 'ui-options-radio-header-engineering'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioEngineeringColorSlider"
Example="{Loc 'ui-options-radio-example-engineering'}"/>
<Button Name="ResetEngineeringColorButton" Text="{Loc 'ui-options-radio-reset-engineering'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Science -->
<Label Text="{Loc 'ui-options-radio-header-science'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioScienceColorSlider"
Example="{Loc 'ui-options-radio-example-science'}"/>
<Button Name="ResetScienceColorButton" Text="{Loc 'ui-options-radio-reset-science'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Cargo -->
<Label Text="{Loc 'ui-options-radio-header-cargo'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioCargoColorSlider"
Example="{Loc 'ui-options-radio-example-cargo'}"/>
<Button Name="ResetCargoColorButton" Text="{Loc 'ui-options-radio-reset-cargo'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Service -->
<Label Text="{Loc 'ui-options-radio-header-service'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioServiceColorSlider"
Example="{Loc 'ui-options-radio-example-service'}"/>
<Button Name="ResetServiceColorButton" Text="{Loc 'ui-options-radio-reset-service'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Binary -->
<Label Text="{Loc 'ui-options-radio-header-binary'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioBinaryColorSlider"
Example="{Loc 'ui-options-radio-example-binary'}"/>
<Button Name="ResetBinaryColorButton" Text="{Loc 'ui-options-radio-reset-binary'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- DeltaV - Justice -->
<Label Text="{Loc 'ui-options-radio-header-justice'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioJusticeColorSlider"
Example="{Loc 'ui-options-radio-example-justice'}"/>
<Button Name="ResetJusticeColorButton" Text="{Loc 'ui-options-radio-reset-justice'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- DeltaV - Prison -->
<Label Text="{Loc 'ui-options-radio-header-prison'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioPrisonColorSlider"
Example="{Loc 'ui-options-radio-example-prison'}"/>
<Button Name="ResetPrisonColorButton" Text="{Loc 'ui-options-radio-reset-prison'}" MaxWidth="300" Margin="0 4 0 0" />
</BoxContainer>
</ScrollContainer>
<ui:OptionsTabControlRow Name="Control" Access="Public" />
</BoxContainer>
</tabs:RadioTab>
@@ -0,0 +1,55 @@
using Content.Client.Options.UI;
using Content.Shared._DV.CCVars;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
namespace Content.Client._DV.Options.UI.Tabs;
[GenerateTypedNameReferences]
public sealed partial class RadioTab : Control
{
[Dependency] private readonly IConfigurationManager _config = default!; // DeltaV
public RadioTab()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
// Having so many color sliders on one page is a lot. We should only really try
// to have common/departmental channels here, or it just becomes WAAAAY too much.
Control.AddOptionColorSlider(DCCVars.RadioColorCommon, RadioCommonColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorCommand, RadioCommandColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorSecurity, RadioSecurityColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorMedical, RadioMedicalColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorEngineering, RadioEngineeringColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorScience, RadioScienceColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorCargo, RadioCargoColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorService, RadioServiceColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorBinary, RadioBinaryColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorJustice, RadioJusticeColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorPrison, RadioPrisonColorSlider);
ResetCommonColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorCommon);
ResetCommandColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorCommand);
ResetSecurityColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorSecurity);
ResetMedicalColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorMedical);
ResetEngineeringColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorEngineering);
ResetScienceColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorScience);
ResetCargoColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorCargo);
ResetServiceColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorService);
ResetBinaryColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorBinary);
ResetJusticeColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorJustice);
ResetPrisonColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorPrison);
Control.Initialize();
}
private void OnResetColorButton(CVarDef<string> cvar)
{
_config.SetCVar(cvar, cvar.DefaultValue);
Control.ReloadValues();
}
}
@@ -1,6 +0,0 @@
using Content.Shared._DV.Psionics.Systems.PsionicPowers;
namespace Content.Client._DV.Psionics.Systems.PsionicPowers;
// This does nothing here. The code is all in the shared/server version.
public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem;
@@ -0,0 +1,49 @@
using Content.Shared._DV.CCVars;
using Content.Shared.Radio;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
namespace Content.Client._DV.Radio;
public sealed partial class RadioChannelColorSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
// Feels like there should be a better way to do this but I'd rather not add a client-side cvar ID or reference
// to a prototype in Content.Shared, since I don't want people thinking they can ever use it on the server-side.
private static readonly ProtoId<RadioChannelPrototype> CommonChannelProto = "Common";
private static readonly ProtoId<RadioChannelPrototype> CommandChannelProto = "Command";
private static readonly ProtoId<RadioChannelPrototype> SecurityChannelProto = "Security";
private static readonly ProtoId<RadioChannelPrototype> MedicalChannelProto = "Medical";
private static readonly ProtoId<RadioChannelPrototype> EngineeringChannelProto = "Engineering";
private static readonly ProtoId<RadioChannelPrototype> ScienceChannelProto = "Science";
private static readonly ProtoId<RadioChannelPrototype> CargoChannelProto = "Supply";
private static readonly ProtoId<RadioChannelPrototype> ServiceChannelProto = "Service";
private static readonly ProtoId<RadioChannelPrototype> BinaryChannelProto = "Binary";
private static readonly ProtoId<RadioChannelPrototype> JusticeChannelProto = "Justice";
private static readonly ProtoId<RadioChannelPrototype> PrisonChannelProto = "Prison";
public override void Initialize()
{
base.Initialize();
_config.OnValueChanged(DCCVars.RadioColorCommon, (value) => { SetChannelColor(CommonChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorCommand, (value) => { SetChannelColor(CommandChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorSecurity, (value) => { SetChannelColor(SecurityChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorMedical, (value) => { SetChannelColor(MedicalChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorEngineering, (value) => { SetChannelColor(EngineeringChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorScience, (value) => { SetChannelColor(ScienceChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorCargo, (value) => { SetChannelColor(CargoChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorService, (value) => { SetChannelColor(ServiceChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorBinary, (value) => { SetChannelColor(BinaryChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorJustice, (value) => { SetChannelColor(JusticeChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorPrison, (value) => { SetChannelColor(PrisonChannelProto, value); }, true);
}
private void SetChannelColor(ProtoId<RadioChannelPrototype> channel, string newColor)
{
if (Color.TryParse(newColor, out var color))
_proto.Index(channel).Color = color;
}
}
@@ -0,0 +1,17 @@
using Content.Shared.Labels.Components;
namespace Content.Client._DV.Silicons;
public static class NameHelpers
{
extension(IEntityManager self)
{
public string PaperName(EntityUid paper)
{
if (self.TryGetComponent<LabelComponent>(paper, out var label) && label.CurrentLabel is { } labelText)
return labelText;
return self.GetComponent<MetaDataComponent>(paper).EntityName;
}
}
}
@@ -0,0 +1,144 @@
using System.IO;
using Content.Shared._DV.Silicons;
using Content.Shared.Fax;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
using Robust.Shared.Containers;
namespace Content.Client._DV.Silicons;
[UsedImplicitly]
public sealed class StationAiFaxBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[Dependency] private readonly IFileDialogManager _fileDialogManager = default!;
private StationAiFaxWindow? _window;
private List<StationAiFaxPageWindow> _windows = new();
private bool _dialogIsOpen;
protected override void Open()
{
base.Open();
_window = this.CreateWindow<StationAiFaxWindow>();
_window.FileButtonPressed += OnFileButtonPressed;
_window.BlankButtonPressed += OnBlankButtonPressed;
_window.OpenSendWindowPressed += OnOpenSendWindowPressed;
_window.ExaminePressed += OnExaminePressed;
_window.ShredPressed += OnShredPressed;
_window.DuplicatePressed += OnDuplicatePressed;
UpdateFaxes(EntMan.System<SharedContainerSystem>().EnsureContainer<Container>(Owner, StationAiFaxComponent.Container, out _));
}
private async void OnFileButtonPressed()
{
if (_dialogIsOpen)
return;
_dialogIsOpen = true;
var filters = new FileDialogFilters(new FileDialogFilters.Group("txt"));
await using var file = await _fileDialogManager.OpenFile(filters, FileAccess.Read);
_dialogIsOpen = false;
if (_window == null || _window.Disposed || file == null)
{
return;
}
using var reader = new StreamReader(file);
var firstLine = await reader.ReadLineAsync();
string? label = null;
var content = await reader.ReadToEndAsync();
if (firstLine is { })
{
if (firstLine.StartsWith('#'))
{
label = firstLine[1..].Trim();
}
else
{
content = firstLine + "\n" + content;
}
}
SendMessage(new FaxFileMessage(
label?[..Math.Min(label.Length, FaxFileMessageValidation.MaxLabelSize)],
content[..Math.Min(content.Length, FaxFileMessageValidation.MaxContentSize)],
false));
}
private void OnBlankButtonPressed()
{
SendMessage(new FaxFileMessage(null, string.Empty, false));
}
private void OnOpenSendWindowPressed(EntityUid entity)
{
var window = new StationAiFaxPageWindow(entity, EntMan);
_windows.Add(window);
window.RefreshButtonPressed += OnRefreshButtonPressed;
window.PeerSelected += OnPeerSelected;
window.SendButtonPressed += OnSendDocument;
if (UiSystem.TryGetUiState<FaxUiState>(Owner, UiKey, out var state))
{
window.UpdateState(state);
}
window.OpenCentered();
}
private void OnSendDocument(EntityUid entity)
{
SendMessage(new StationAiFaxSendMessage(EntMan.GetNetEntity(entity)));
}
private void OnExaminePressed(EntityUid entity)
{
SendPredictedMessage(new StationAiFaxExamineMessage(EntMan.GetNetEntity(entity)));
}
private void OnShredPressed(EntityUid entity)
{
SendPredictedMessage(new StationAiFaxShredMessage(EntMan.GetNetEntity(entity)));
}
private void OnDuplicatePressed(EntityUid entity)
{
SendPredictedMessage(new StationAiFaxDuplicateMessage(EntMan.GetNetEntity(entity)));
}
private void OnRefreshButtonPressed()
{
SendMessage(new FaxRefreshMessage());
}
private void OnPeerSelected(string address)
{
SendMessage(new FaxDestinationMessage(address));
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not FaxUiState cast)
return;
_window?.UpdateState(cast);
foreach (var window in _windows)
{
window.UpdateState(cast);
}
}
public void UpdateFaxes(Container container)
{
_window?.UpdateFaxes(container);
}
}
@@ -0,0 +1,14 @@
<PanelContainer xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client._DV.UserInterfaces"
xmlns:xe="clr-namespace:Content.Client.UserInterface.XamlExtensions"
StyleClasses="PanelLight"
HorizontalExpand="True">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="8">
<Label Name="PaperName" HorizontalExpand="True" />
<ui:IconButton Name="Shred" StyleClasses="OpenRight" Icon="{xe:Tex '/Textures/Interface/shredder.svg.192dpi.png'}" />
<ui:IconButton Name="Duplicate" StyleClasses="OpenBoth" Icon="{xe:Tex '/Textures/Interface/files.svg.192dpi.png'}" />
<ui:IconButton Name="Examine" StyleClasses="OpenBoth" Icon="{xe:Tex '/Textures/Interface/eye.svg.192dpi.png'}" />
<ui:IconButton Name="Send" StyleClasses="OpenLeft" Icon="{xe:Tex '/Textures/Interface/send.svg.192dpi.png'}" />
</BoxContainer>
</PanelContainer>
@@ -0,0 +1,27 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._DV.Silicons;
[GenerateTypedNameReferences]
public sealed partial class StationAiFaxItem : PanelContainer
{
public string? Text { get => PaperName.Text; set => PaperName.Text = value; }
public bool CanSend { get => !Duplicate.Disabled; set => Duplicate.Disabled = !value; }
public event Action? ShredPressed;
public event Action? DuplicatePressed;
public event Action? ExaminePressed;
public event Action? SendPressed;
public StationAiFaxItem()
{
RobustXamlLoader.Load(this);
Shred.OnPressed += _ => ShredPressed?.Invoke();
Duplicate.OnPressed += _ => DuplicatePressed?.Invoke();
Examine.OnPressed += _ => ExaminePressed?.Invoke();
Send.OnPressed += _ => SendPressed?.Invoke();
}
}
@@ -0,0 +1,64 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'fax-machine-ui-page-window'}"
MinSize="380 220">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<PanelContainer StyleClasses="PanelDark" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" Margin="8">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'fax-machine-ui-sending'}"
VerticalAlignment="Bottom"
HorizontalExpand="True" />
<Label Name="SendingLabel"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Align="Right"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'fax-machine-ui-from'}"
VerticalAlignment="Bottom"
HorizontalExpand="True" />
<Label Name="FromLabel"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Align="Right"/>
</BoxContainer>
<BoxContainer
Orientation="Horizontal">
<Label Text="{Loc 'fax-machine-ui-to'}"
VerticalAlignment="Bottom"
HorizontalExpand="True"/>
<BoxContainer Orientation="Horizontal">
<OptionButton Name="PeerSelector"
StyleClasses="OpenRight" />
<Button StyleClasses="OpenLeft"
Name="RefreshButton"
ToolTip="{Loc 'fax-machine-ui-refresh-button'}">
<TextureButton
MouseFilter="Ignore"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Scale="0.9 0.9"
StyleClasses="RefreshButton" />
</Button>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</PanelContainer>
<BoxContainer Orientation="Horizontal" SeparationOverride="8" Margin="8">
<Button Name="SendButton"
Text="{Loc 'fax-machine-ui-send-button'}"
HorizontalExpand="True"
Disabled="True" />
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -0,0 +1,70 @@
using System.Linq;
using Content.Client.UserInterface.Controls;
using Content.Shared.Fax;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._DV.Silicons;
[GenerateTypedNameReferences]
public sealed partial class StationAiFaxPageWindow : FancyWindow
{
public event Action? RefreshButtonPressed;
public event Action<string>? PeerSelected;
public event Action<EntityUid>? SendButtonPressed;
public StationAiFaxPageWindow(EntityUid uid, IEntityManager entityManager)
{
RobustXamlLoader.Load(this);
SendingLabel.Text = entityManager.PaperName(uid);
RefreshButton.OnPressed += _ => RefreshButtonPressed?.Invoke();
SendButton.OnPressed += _ => SendButtonPressed?.Invoke(uid);
PeerSelector.OnItemSelected += args =>
PeerSelected?.Invoke((string) args.Button.GetItemMetadata(args.Id)!);
}
public void UpdateState(FaxUiState state)
{
SendButton.Disabled = !state.CanSendCooldown;
FromLabel.Text = state.DeviceName;
if (state.AvailablePeers.Count == 0)
{
PeerSelector.AddItem(Loc.GetString("fax-machine-ui-no-peers"));
PeerSelector.Disabled = true;
}
if (PeerSelector.Disabled && state.AvailablePeers.Count != 0)
{
PeerSelector.Clear();
PeerSelector.Disabled = false;
}
// always must be selected destination
if (string.IsNullOrEmpty(state.DestinationAddress) && state.AvailablePeers.Count != 0)
{
PeerSelected?.Invoke(state.AvailablePeers.First().Key);
return;
}
if (state.AvailablePeers.Count != 0)
{
PeerSelector.Clear();
foreach (var (address, name) in state.AvailablePeers)
{
var id = AddPeerSelect(name, address);
if (address == state.DestinationAddress)
PeerSelector.Select(id);
}
}
}
private int AddPeerSelect(string name, string address)
{
PeerSelector.AddItem(name);
PeerSelector.SetItemMetadata(PeerSelector.ItemCount - 1, address);
return PeerSelector.ItemCount - 1;
}
}
@@ -0,0 +1,37 @@
using Content.Shared._DV.Silicons;
using Content.Shared.Fax;
using Robust.Client.GameObjects;
using Robust.Shared.Containers;
namespace Content.Client._DV.Silicons;
public sealed class StationAiFaxSystem : SharedStationAiFaxSystem
{
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StationAiFaxComponent, EntRemovedFromContainerMessage>(OnRemoved);
}
protected override void OnInserted(Entity<StationAiFaxComponent> ent, ref EntInsertedIntoContainerMessage args)
{
base.OnInserted(ent, ref args);
if (!_userInterface.TryGetOpenUi<StationAiFaxBoundUserInterface>(ent.Owner, FaxUiKey.Key, out var bui))
return;
bui.UpdateFaxes(_container.EnsureContainer<Container>(ent, StationAiFaxComponent.Container, out _));
}
private void OnRemoved(Entity<StationAiFaxComponent> ent, ref EntRemovedFromContainerMessage args)
{
if (!_userInterface.TryGetOpenUi<StationAiFaxBoundUserInterface>(ent.Owner, FaxUiKey.Key, out var bui))
return;
bui.UpdateFaxes(_container.EnsureContainer<Container>(ent, StationAiFaxComponent.Container, out _));
}
}
@@ -0,0 +1,29 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'fax-machine-ui-window'}"
MinSize="500 600">
<BoxContainer Orientation="Vertical" Name="NoFaxes" HorizontalAlignment="Center" VerticalAlignment="Center" SeparationOverride="8">
<Label StyleClasses="PlaceholderText" Text="{Loc 'fax-machine-ui-no-faxes'}" Align="Center" />
<BoxContainer Orientation="Vertical" HorizontalAlignment="Center">
<Button Name="NoFaxesBlank" Text="{Loc 'fax-machine-ui-add-blank-file'}" HorizontalExpand="True" StyleClasses="OpenLeft" />
<Button Name="NoFaxesUpload" Text="{Loc 'fax-machine-ui-upload-file'}" HorizontalExpand="True" StyleClasses="OpenRight" />
</BoxContainer>
</BoxContainer>
<BoxContainer Orientation="Vertical" Name="YesFaxes" HorizontalExpand="True" VerticalExpand="True">
<PanelContainer StyleClasses="PanelDark" VerticalExpand="True" HorizontalExpand="True">
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
<BoxContainer Name="FaxList" Margin="8" VerticalAlignment="Top" HorizontalExpand="True" SeparationOverride="8" Orientation="Vertical" />
</ScrollContainer>
</PanelContainer>
<BoxContainer Orientation="Horizontal" SeparationOverride="8" Margin="8">
<Button Name="YesFaxesBlank" Text="{Loc 'fax-machine-ui-add-blank-file'}" HorizontalAlignment="Center" />
<Button Name="YesFaxesUpload" Text="{Loc 'fax-machine-ui-upload-file'}" HorizontalAlignment="Center" />
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -0,0 +1,92 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Fax;
using Content.Shared.Labels.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._DV.Silicons;
[GenerateTypedNameReferences]
public sealed partial class StationAiFaxWindow : FancyWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
public event Action? BlankButtonPressed;
public event Action? FileButtonPressed;
public event Action<EntityUid>? OpenSendWindowPressed;
public event Action<EntityUid>? ExaminePressed;
public event Action<EntityUid>? ShredPressed;
public event Action<EntityUid>? DuplicatePressed;
public bool CanSend
{
get;
set
{
if (field == value)
return;
field = value;
foreach (var item in FaxList.Children)
{
var castItem = (StationAiFaxItem)item;
castItem.CanSend = value;
}
NoFaxesBlank.Disabled = !value;
NoFaxesUpload.Disabled = !value;
YesFaxesBlank.Disabled = !value;
YesFaxesUpload.Disabled = !value;
}
}
public StationAiFaxWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
NoFaxesBlank.OnPressed += _ => BlankButtonPressed?.Invoke();
NoFaxesUpload.OnPressed += _ => FileButtonPressed?.Invoke();
YesFaxesBlank.OnPressed += _ => BlankButtonPressed?.Invoke();
YesFaxesUpload.OnPressed += _ => FileButtonPressed?.Invoke();
}
public void UpdateFaxes(Robust.Shared.Containers.Container container)
{
FaxList.RemoveAllChildren();
foreach (var item in container.ContainedEntities)
{
var page = new StationAiFaxItem
{
Text = _entityManager.TryGetComponent<LabelComponent>(item, out var label) &&
label.CurrentLabel is { } labelText
? labelText
: _entityManager.GetComponent<MetaDataComponent>(item).EntityName,
CanSend = CanSend,
};
FaxList.AddChild(page);
page.ExaminePressed += () => ExaminePressed?.Invoke(item);
page.ShredPressed += () => ShredPressed?.Invoke(item);
page.DuplicatePressed += () => DuplicatePressed?.Invoke(item);
page.SendPressed += () => OpenSendWindowPressed?.Invoke(item);
}
if (container.ContainedEntities.Count == 0)
{
YesFaxes.Visible = false;
NoFaxes.Visible = true;
}
else
{
YesFaxes.Visible = true;
NoFaxes.Visible = false;
}
}
public void UpdateState(FaxUiState state)
{
CanSend = state.CanSendCooldown;
}
}
@@ -108,6 +108,70 @@ public sealed partial class TraitCategory : BoxContainer
{
CategoryPointsLabel.Visible = false;
}
// Lock unselected entries whenever the category cap is reached so the
// player gets a clear visual cue instead of silently failing to equip.
UpdateCategoryFullState();
}
/// <summary>
/// Propagates category-cap lock state to every entry.
/// <para>
/// Selected entries are never locked - they are the traits filling the cap
/// and must stay interactive so the player can deselect them.
/// </para>
/// <para>
/// Unselected entries are locked when:
/// <list type="bullet">
/// <item><see cref="TraitCategoryPrototype.MaxTraits"/> is defined and already reached, OR</item>
/// <item><see cref="TraitCategoryPrototype.MaxPoints"/> is defined and this specific
/// trait's cost would push spending over the limit.</item>
/// </list>
/// </para>
/// </summary>
private void UpdateCategoryFullState()
{
var traitCapReached = _category.MaxTraits.HasValue && SelectedCount >= _category.MaxTraits.Value;
foreach (var (_, entry) in _traitEntries)
{
if (entry.IsSelected)
{
// Selected traits are never locked by category state.
entry.SetLockedByCategory(false);
continue;
}
var locked = traitCapReached;
// Even when the trait count cap hasn't been hit, lock this specific entry
// if adding its cost would exceed the category's point budget.
if (!locked && _category.MaxPoints.HasValue)
locked = PointsSpent + entry.TraitCost > _category.MaxPoints.Value;
entry.SetLockedByCategory(locked);
}
}
/// <summary>
/// Called by <see cref="TraitsTab"/> whenever the player's global point budget changes.
/// Locks any unselected entry whose cost the player can no longer afford.
/// <para>
/// Free (0-cost) and negative-cost traits are never locked - they cost nothing or
/// actually give points back. Selected traits are also never locked here; they
/// were affordable when picked and must stay interactive to be deselected.
/// </para>
/// </summary>
public void UpdateGlobalPointsLock(int remainingPoints)
{
foreach (var (_, entry) in _traitEntries)
{
// A selected trait is always unlocked here - same reasoning as category cap:
// it was paid for at selection time and must stay interactive.
// Traits that cost nothing or give points back are never locked by budget.
var shouldLock = !entry.IsSelected && entry.TraitCost > remainingPoints;
entry.SetLockedByPoints(shouldLock);
}
}
public void SetTraitSelected(ProtoId<TraitPrototype> traitId, bool selected)
@@ -171,11 +235,11 @@ public sealed partial class TraitCategory : BoxContainer
/// Updates condition states for all trait entries based on current job/species.
/// Traits that don't meet conditions are disabled but still visible.
/// </summary>
public void UpdateConditions(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences)
public void UpdateConditions(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences, IReadOnlySet<ProtoId<TraitPrototype>>? selectedTraits)
{
foreach (var (_, entry) in _traitEntries)
{
entry.UpdateConditionsMet(jobId, speciesId, antagPreferences);
entry.UpdateConditionsMet(jobId, speciesId, antagPreferences, selectedTraits);
}
// Update stats since some traits may have been deselected
+160 -39
View File
@@ -27,6 +27,9 @@ public sealed partial class TraitEntry : PanelContainer
private bool _isUpdating;
private readonly List<string> _failedConditionTooltips = new();
private bool _isLockedByCategory;
private bool _isLockedByPoints;
public TraitEntry(TraitPrototype trait)
{
RobustXamlLoader.Load(this);
@@ -59,9 +62,17 @@ public sealed partial class TraitEntry : PanelContainer
foreach (var condition in _trait.Conditions)
{
var tooltip = condition.GetTooltip(_prototype, _loc);
if (!string.IsNullOrEmpty(tooltip))
tooltips.Add(tooltip);
if (condition is TraitDependencyCondition depCond)
{
// Use GetTooltips() to get individual lines without duplication
tooltips.AddRange(depCond.GetTooltips(_prototype, _loc));
}
else
{
var tooltip = condition.GetTooltip(_prototype, _loc);
if (!string.IsNullOrEmpty(tooltip))
tooltips.Add(tooltip);
}
}
if (tooltips.Count > 0)
@@ -91,39 +102,92 @@ public sealed partial class TraitEntry : PanelContainer
/// <summary>
/// Updates whether conditions are met based on current job/species.
/// </summary>
public void UpdateConditionsMet(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences)
public void UpdateConditionsMet(
ProtoId<JobPrototype>? jobId,
ProtoId<SpeciesPrototype>? speciesId,
IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences,
IReadOnlySet<ProtoId<TraitPrototype>>? selectedTraits)
{
_failedConditionTooltips.Clear();
MeetsConditions = true;
foreach (var condition in _trait.Conditions)
{
var result = condition switch
{
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
HasCompCondition compCond => !compCond.Invert, // can't check in lobby but screws with the inversion logic
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
AnyOfCondition anyOfCond => CheckAnyOfCondition(anyOfCond, jobId, speciesId, antagPreferences),
_ => true,
};
bool result;
// Apply inversion
result ^= condition.Invert;
if (condition is TraitDependencyCondition depCond)
{
result = CheckDependencyCondition(depCond, selectedTraits);
// CheckDependencyCondition adds its own tooltips directly
}
else
{
result = condition switch
{
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
HasCompCondition compCond => !compCond.Invert, // can't check in lobby
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
AnyOfCondition anyOfCond => CheckAnyOfCondition(anyOfCond, jobId, speciesId, antagPreferences, selectedTraits),
_ => true,
};
// Apply inversion for non-dependency conditions
result ^= condition.Invert;
if (!result)
{
var tooltip = condition.GetTooltip(_prototype, _loc);
if (!string.IsNullOrEmpty(tooltip))
_failedConditionTooltips.Add(tooltip);
}
}
if (!result)
{
MeetsConditions = false;
var tooltip = condition.GetTooltip(_prototype, _loc);
if (!string.IsNullOrEmpty(tooltip))
_failedConditionTooltips.Add(tooltip);
}
}
UpdateDisabledState();
}
/// <summary>
/// Checks a TraitDependencyCondition and adds failure tooltips directly.
/// Returns true if the condition passes.
/// </summary>
private bool CheckDependencyCondition(TraitDependencyCondition condition, IReadOnlySet<ProtoId<TraitPrototype>>? selectedTraits)
{
var passed = true;
foreach (var conflict in condition.Conflicts)
{
if (selectedTraits == null || !selectedTraits.Contains(conflict))
continue;
if (_prototype.TryIndex(conflict, out var conflictProto))
{
_failedConditionTooltips.Add(_loc.GetString("trait-condition-trait-conflict",
("trait", _loc.GetString(conflictProto.Name))));
}
passed = false;
}
foreach (var required in condition.Requires)
{
if (selectedTraits != null && selectedTraits.Contains(required))
continue;
if (_prototype.TryIndex(required, out var requiredProto))
{
_failedConditionTooltips.Add(_loc.GetString("trait-condition-trait-required",
("trait", _loc.GetString(requiredProto.Name))));
}
passed = false;
}
return passed;
}
private bool CheckSpeciesCondition(IsSpeciesCondition condition, ProtoId<SpeciesPrototype>? speciesId)
{
if (!_prototype.TryIndex(speciesId, out var species))
@@ -159,7 +223,12 @@ public sealed partial class TraitEntry : PanelContainer
return antagPreferences.Contains(condition.Antag);
}
private bool CheckAnyOfCondition(AnyOfCondition condition, ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences)
private bool CheckAnyOfCondition(
AnyOfCondition condition,
ProtoId<JobPrototype>? jobId,
ProtoId<SpeciesPrototype>? speciesId,
IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences,
IReadOnlySet<ProtoId<TraitPrototype>>? selectedTraits)
{
if (condition.Conditions.Count == 0)
return false;
@@ -167,19 +236,27 @@ public sealed partial class TraitEntry : PanelContainer
// Return true if ANY child condition evaluates to true
foreach (var childCondition in condition.Conditions)
{
var result = childCondition switch
{
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
HasCompCondition compCond => !compCond.Invert, // can't check in lobby
AnyOfCondition nestedAnyOf => CheckAnyOfCondition(nestedAnyOf, jobId, speciesId, antagPreferences), // Recursive!
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
_ => true,
};
bool result;
// Apply child's inversion
result ^= childCondition.Invert;
if (childCondition is TraitDependencyCondition depCond)
{
result = CheckDependencyCondition(depCond, selectedTraits);
}
else
{
result = childCondition switch
{
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
HasCompCondition compCond => !compCond.Invert, // can't check in lobby
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
AnyOfCondition nestedAnyOf => CheckAnyOfCondition(nestedAnyOf, jobId, speciesId, antagPreferences, selectedTraits),
_ => true,
};
result ^= childCondition.Invert;
}
// If any child passes, the AnyOf passes
if (result)
@@ -190,16 +267,50 @@ public sealed partial class TraitEntry : PanelContainer
return false;
}
/// <summary>
/// Called by <see cref="TraitCategory"/> whenever the category's trait or
/// points-cap state changes. Locking only applies to <em>unselected</em>
/// entries - the selected traits are the ones filling the cap and must
/// remain interactive so the player can deselect them.
/// </summary>
public void SetLockedByCategory(bool locked)
{
if (_isLockedByCategory == locked)
return;
_isLockedByCategory = locked;
UpdateDisabledState();
}
/// <summary>
/// Called by <see cref="TraitCategory"/> whenever the player's global point budget
/// changes. Locks unselected entries whose cost the player can no longer afford.
/// Free (0-cost) and negative-cost traits are never locked by this.
/// </summary>
public void SetLockedByPoints(bool locked)
{
if (_isLockedByPoints == locked)
return;
_isLockedByPoints = locked;
UpdateDisabledState();
}
private void UpdateDisabledState()
{
if (!MeetsConditions)
var isSelected = TraitCheckbox.Pressed;
var conditionLocked = !MeetsConditions;
var categoryLocked = _isLockedByCategory && !isSelected;
var pointsLocked = _isLockedByPoints && !isSelected;
var isDisabled = conditionLocked || categoryLocked || pointsLocked;
if (isDisabled)
{
// Hide checkbox, show lock icon
TraitCheckbox.Visible = false;
LockIcon.Visible = true;
// Deselect if conditions no longer met
if (TraitCheckbox.Pressed)
if (isSelected && conditionLocked)
{
_isUpdating = true;
TraitCheckbox.Pressed = false;
@@ -211,14 +322,24 @@ public sealed partial class TraitEntry : PanelContainer
// Add disabled styling
AddStyleClass("TraitsEntryDisabled");
// Update tooltip to show failed conditions
if (_failedConditionTooltips.Count > 0)
// Tooltip priority for conditions: condition failures > category full > insufficient points.
if (conditionLocked && _failedConditionTooltips.Count > 0)
{
var tooltipText = Loc.GetString("trait-conditions-not-met-tooltip",
("requirements", string.Join("\n", _failedConditionTooltips)));
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
}
else if (categoryLocked)
{
var tooltipText = Loc.GetString("trait-category-full-tooltip");
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
}
else if (pointsLocked)
{
var tooltipText = Loc.GetString("trait-insufficient-points-tooltip");
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
}
}
else
{
+18 -8
View File
@@ -154,14 +154,18 @@ public sealed partial class TraitsTab : BoxContainer
}
}
// Check conflicts
foreach (var conflict in trait.Conflicts)
// Check conflicts via TraitDependencyCondition
var depCondition = trait.Conditions.OfType<Content.Shared._DV.Traits.Conditions.TraitDependencyCondition>().FirstOrDefault();
if (depCondition != null)
{
if (!_selectedTraits.Contains(conflict))
continue;
foreach (var conflict in depCondition.Conflicts)
{
if (!_selectedTraits.Contains(conflict))
continue;
RevertTraitToggle(traitId);
return;
RevertTraitToggle(traitId);
return;
}
}
_selectedTraits.Add(traitId);
@@ -176,6 +180,7 @@ public sealed partial class TraitsTab : BoxContainer
}
UpdateGlobalStats();
UpdateAllConditions();
UpdateCategoryStats(trait.Category);
OnTraitsChanged?.Invoke(_selectedTraits);
}
@@ -211,7 +216,7 @@ public sealed partial class TraitsTab : BoxContainer
// If parent width is 0 (not laid out yet), defer until layout happens
if (parentWidth > 0)
{
GlobalPointsBar.SetWidth = (int)((parentWidth - 2 ) * percentage);
GlobalPointsBar.SetWidth = (int)((parentWidth - 2) * percentage);
_awaitingLayoutUpdate = false;
}
else if (!_awaitingLayoutUpdate)
@@ -235,6 +240,11 @@ public sealed partial class TraitsTab : BoxContainer
> 0f => "TraitsProgressBarLow",
_ => "TraitsProgressBarEmpty"
});
foreach (var (_, categoryUi) in _categoryUis)
{
categoryUi.UpdateGlobalPointsLock(remainingPoints);
}
}
private void OnProgressBarParentResized()
@@ -281,7 +291,7 @@ public sealed partial class TraitsTab : BoxContainer
foreach (var (_, categoryUi) in _categoryUis)
{
// If some fork wants to use the top selected job as well, just add that to the UpdateConditions method in the editor
categoryUi.UpdateConditions(null, _profile?.Species, _profile?.AntagPreferences);
categoryUi.UpdateConditions(null, _profile?.Species, _profile?.AntagPreferences, _selectedTraits);
}
RecalculateStats();
@@ -0,0 +1,7 @@
<Button xmlns="https://spacestation14.io">
<TextureRect Name="TextureRect"
TextureScale="0.5 0.5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="KeepCentered"/>
</Button>
@@ -0,0 +1,17 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._DV.UserInterfaces;
[GenerateTypedNameReferences]
public sealed partial class IconButton : Button
{
public Texture? Icon { get => TextureRect.Texture; set => TextureRect.Texture = value; }
public IconButton()
{
RobustXamlLoader.Load(this);
}
}
@@ -20,7 +20,11 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
private const string AnimateKey = "particle-animation";
private static readonly EntProtoId InteractionParticleId = "StellarInteractionParticle";
private static readonly Dictionary<StellarInteractionParticleType, EntProtoId> InteractionParticleIds = new ()
{
{ StellarInteractionParticleType.Use, "StellarInteractionParticleUse" },
{ StellarInteractionParticleType.Pull, "StellarInteractionParticlePull" },
};
public override void Initialize()
{
@@ -38,6 +42,11 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
if (!Exists(performer) || !Exists(target))
return;
if (ev.Type == StellarInteractionParticleType.Pull)
{
(performer, target) = (target, performer);
}
var performerXform = Transform(performer);
var targetXform = Transform(target);
if (performerXform.MapID == MapId.Nullspace || targetXform.MapID == MapId.Nullspace)
@@ -47,7 +56,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
return;
var performerTargetDelta = targetXform.LocalPosition - performerXform.LocalPosition;
var particle = Spawn(InteractionParticleId, performerXform.Coordinates);
var particle = Spawn(InteractionParticleIds[ev.Type], performerXform.Coordinates);
if (used is { } usedEntity && Exists(usedEntity) && TryComp<SpriteComponent>(usedEntity, out var usedSprite))
{
@@ -58,21 +67,27 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
}
var spriteColor = Comp<SpriteComponent>(particle).Color;
_animation.Play(particle, GetAnimation(performerTargetDelta, spriteColor), AnimateKey);
var animation = ev.Type switch
{
StellarInteractionParticleType.Use => GetUseAnimation(performerTargetDelta, spriteColor),
StellarInteractionParticleType.Pull => GetPullAnimation(performerTargetDelta, spriteColor),
_ => throw new ArgumentOutOfRangeException(nameof(ev), $"Interaction particle event has unknown particle type {ev.Type}"),
};
_animation.Play(particle, animation, AnimateKey);
}
private Animation GetAnimation(Vector2 endOffset, Color color)
private Animation GetUseAnimation(Vector2 endOffset, Color color)
{
var startRotation = _random.NextAngle(Angle.FromDegrees(-80), Angle.FromDegrees(80));
var startRotation = _random.NextAngle(Angle.FromDegrees(-40), Angle.FromDegrees(40));
var endRotation = Angle.Zero;
var startScale = new Vector2(0.3f, 0.3f);
var endScale = new Vector2(1f, 1f);
var rotationLength = TimeSpan.FromMilliseconds(600);
var startOffset = new Vector2();
var offsetLength = TimeSpan.FromMilliseconds(200);
var offsetLength = TimeSpan.FromMilliseconds(250);
var startColor = color.WithAlpha(color.A * 0.9f);
var startColor = color.WithAlpha(color.A * 0.7f);
var endColor = color.WithAlpha(0f);
var colorLength = rotationLength + offsetLength;
@@ -89,7 +104,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(startRotation, 0f),
new AnimationTrackProperty.KeyFrame(endRotation, (float)rotationLength.TotalSeconds, Easings.OutBounce),
new AnimationTrackProperty.KeyFrame(endRotation, (float)rotationLength.TotalSeconds, Easings.OutBack),
},
},
new AnimationTrackComponentProperty()
@@ -99,7 +114,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(startScale, 0f),
new AnimationTrackProperty.KeyFrame(endScale, (float)rotationLength.TotalSeconds, Easings.OutBounce),
new AnimationTrackProperty.KeyFrame(endScale, (float)rotationLength.TotalSeconds, Easings.OutBack),
},
},
new AnimationTrackComponentProperty()
@@ -109,7 +124,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(startOffset, 0f),
new AnimationTrackProperty.KeyFrame(endOffset, (float)offsetLength.TotalSeconds, Easings.OutBounce),
new AnimationTrackProperty.KeyFrame(endOffset, (float)offsetLength.TotalSeconds, Easings.OutBack),
},
},
new AnimationTrackComponentProperty()
@@ -126,4 +141,44 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
},
};
}
private Animation GetPullAnimation(Vector2 endOffset, Color color)
{
var rotationLength = TimeSpan.FromMilliseconds(8f * (1000f / 12f));
var startOffset = new Vector2();
var offsetLength = TimeSpan.FromMilliseconds(4f * (1000f / 12f));
var endColor = color.WithAlpha(0f);
return new Animation
{
Length = rotationLength,
AnimationTracks =
{
new AnimationTrackComponentProperty()
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Offset),
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(startOffset, 0f),
new AnimationTrackProperty.KeyFrame(endOffset, (float)rotationLength.TotalSeconds, Easings.InOutCirc),
},
},
new AnimationTrackComponentProperty()
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Color),
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(color, 0f),
new AnimationTrackProperty.KeyFrame(color, (float)offsetLength.TotalSeconds),
new AnimationTrackProperty.KeyFrame(endColor, (float)rotationLength.TotalSeconds, Easings.InOutCirc),
},
},
},
};
}
}
@@ -245,6 +245,7 @@ namespace Content.IntegrationTests.Tests
"TimedDespawnDetailed", // DeltaV
// makes an announcement on mapInit.
"AnnounceOnSpawn",
"ESTimedDespawn" // DeltaV
};
Assert.That(server.CfgMan.GetCVar(CVars.NetPVS), Is.False);
@@ -24,7 +24,7 @@ public sealed class WizdenContentFreeze
var protoMan = server.ProtoMan;
var recipesCount = protoMan.Count<FoodRecipePrototype>();
var recipesLimit = 269; // DeltaV - was 218
var recipesLimit = 266; // DeltaV - was 218
if (recipesCount > recipesLimit)
{
@@ -6,6 +6,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Player; // DeltaV
namespace Content.Server.AlertLevel;
@@ -191,7 +192,8 @@ public sealed class AlertLevelSystem : EntitySystem
{
if (detail.Sound != null)
{
var filter = _stationSystem.GetInOwningStation(station);
//var filter = _stationSystem.GetInOwningStation(station); // DeltaV - Global Annoucements
var filter = Filter.Empty().AddAllPlayers(); // DeltaV - Global Annoucements
_audio.PlayGlobal(detail.Sound, filter, true, detail.Sound.Params);
}
else
@@ -202,7 +204,7 @@ public sealed class AlertLevelSystem : EntitySystem
if (announce)
{
_chatSystem.DispatchStationAnnouncement(station, announcementFull, playDefaultSound: playDefault,
_chatSystem.DispatchGlobalAnnouncement(announcementFull, playSound: playDefault, // DeltaV - Global Annoucements
colorOverride: detail.Color, sender: stationName);
}
+17 -4
View File
@@ -87,7 +87,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
if (anomaly.Comp.CurrentBehavior is not null)
RemoveBehavior(anomaly, anomaly.Comp.CurrentBehavior.Value);
EndAnomaly(anomaly, spawnCore: false);
EndAnomaly(anomaly, spawnCore: false, forced: true);
}
private void OnStartCollide(Entity<AnomalyComponent> anomaly, ref StartCollideEvent args)
@@ -143,7 +143,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
return 0;
var multiplier = 1f;
if (component.Stability > component.GrowthThreshold)
if (component.AlwaysGrow || component.Stability > component.GrowthThreshold) // DeltaV - Add AlwaysGrow
multiplier = component.GrowingPointMultiplier; //more points for unstable
//penalty of up to 50% based on health
@@ -253,7 +253,11 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
else
{
string stateLoc;
if (anomalyComp.Stability < anomalyComp.DecayThreshold)
// DeltaV - Colossus Additions START
if (anomalyComp.AlwaysGrow)
stateLoc = Loc.GetString("anomaly-scanner-stability-high");
// DeltaV - Colossus Additions END
else if (anomalyComp.Stability < anomalyComp.DecayThreshold) // DeltaV - Add else
stateLoc = Loc.GetString("anomaly-scanner-stability-low");
else if (anomalyComp.Stability > anomalyComp.GrowthThreshold)
stateLoc = Loc.GetString("anomaly-scanner-stability-high");
@@ -345,6 +349,15 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
msg.AddMarkupOrThrow(Loc.GetString("anomaly-behavior-unknown"));
else
{
// DeltaV - Colossus Additions START
if (anomalyComp.AlwaysGrow)
{
msg.AddMarkupOrThrow("- " + Loc.GetString("anomaly-behavior-always-grow"));
if (anomalyComp.CurrentBehavior != null)
msg.PushNewline();
}
// DeltaV - Colossus Additions END
if (anomalyComp.CurrentBehavior != null)
{
var behavior = _prototype.Index(anomalyComp.CurrentBehavior.Value);
@@ -354,7 +367,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
var mod = Math.Floor((behavior.EarnPointModifier) * 100);
msg.AddMarkupOrThrow("- " + Loc.GetString("anomaly-behavior-point", ("mod", mod)));
}
else
else if(!anomalyComp.AlwaysGrow) // DeltaV - Add condition, previously regular else
{
msg.AddMarkupOrThrow(Loc.GetString("anomaly-behavior-balanced"));
}
@@ -7,7 +7,7 @@ using Robust.Shared.Player;
namespace Content.Server.Audio;
public sealed class ServerGlobalSoundSystem : SharedGlobalSoundSystem
public sealed partial class ServerGlobalSoundSystem : SharedGlobalSoundSystem // DeltaV - Made Partial
{
[Dependency] private readonly IConsoleHost _conHost = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
@@ -184,6 +184,11 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
var rate = entry.MetabolismRate * group.MetabolismRateModifier;
// BEGIN DeltaV - fix FixedPoint2 truncation flooring low rateModifiers to zero
if (rate == FixedPoint2.Zero && entry.MetabolismRate > FixedPoint2.Zero && group.MetabolismRateModifier > 0f)
rate = FixedPoint2.Epsilon;
// END DeltaV
// Remove $rate, as long as there's enough reagent there to actually remove that much
mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
+49 -2
View File
@@ -5,6 +5,7 @@ using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
using Content.Server.Administration.Systems;
using Content.Server.Discord.DiscordLink;
using Content.Server.Ghost;
using Content.Server.Players.RateLimiting;
using Content.Server.Preferences.Managers;
using Content.Shared.Administration;
@@ -15,6 +16,7 @@ using Content.Shared.Mind;
using Content.Shared.Players; // DeltaV - OOC muting
using Content.Shared.Players.RateLimiting;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Replays;
@@ -47,6 +49,7 @@ internal sealed partial class ChatManager : IChatManager
[Dependency] private readonly ISharedPlayerManager _player = default!;
[Dependency] private readonly DiscordChatLink _discordLink = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
private ISawmill _sawmill = default!;
@@ -342,12 +345,35 @@ internal sealed partial class ChatManager : IChatManager
#region Utility
private bool IsValidWarpDestination(EntityUid source)
{
if (!source.Valid)
return false;
if (!_entityManager.TryGetComponent(source, out TransformComponent? transform))
return false;
return transform.MapID != MapId.Nullspace;
}
public string PrependFollowButtonIfAppropriate(string wrappedMessage, EntityUid source, INetChannel recipient)
{
if (IsValidWarpDestination(source) && ShouldShowFollowButton(recipient))
{
var btnText = _localizationManager.GetString("chat-manager-follow-button");
return $"[cmdlink=\"{btnText}\" command=\"{GhostFollowEntityCommand.CommandName} {_entityManager.GetNetEntity(source)}\" /] " + wrappedMessage;
}
return wrappedMessage;
}
public void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0, NetUserId? author = null)
{
var user = author == null ? null : EnsurePlayer(author);
var netSource = _entityManager.GetNetEntity(source);
user?.AddEntity(netSource);
wrappedMessage = PrependFollowButtonIfAppropriate(wrappedMessage, source, client);
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
_netManager.ServerSendMessage(new MsgChatMessage() { Message = msg }, client);
@@ -370,8 +396,12 @@ internal sealed partial class ChatManager : IChatManager
var netSource = _entityManager.GetNetEntity(source);
user?.AddEntity(netSource);
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
_netManager.ServerSendToMany(new MsgChatMessage() { Message = msg }, clients);
foreach (var client in clients)
{
var customWrapMessage = PrependFollowButtonIfAppropriate(wrappedMessage, source, client);
var msg = new ChatMessage(channel, message, customWrapMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
_netManager.ServerSendMessage(new MsgChatMessage { Message = msg }, client);
}
if (!recordReplay)
return;
@@ -379,6 +409,7 @@ internal sealed partial class ChatManager : IChatManager
if ((channel & ChatChannel.AdminRelated) == 0 ||
_configurationManager.GetCVar(CCVars.ReplayRecordAdminChat))
{
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
_replay.RecordServerMessage(msg);
}
}
@@ -439,6 +470,22 @@ internal sealed partial class ChatManager : IChatManager
}
#endregion
private bool ShouldShowFollowButton(INetChannel recipient)
{
if (!_player.TryGetSessionByChannel(recipient, out var session))
return false;
if (_entityManager.TrySystem(out GhostSystem? ghost))
{
if (!ghost.CanGhostWarp(session, out _))
{
return false;
}
}
return _netConfigManager.GetClientCVar(recipient, CCVars.InterfaceChatFollowButton);
}
}
public enum OOCChatType : byte
@@ -93,7 +93,6 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager
Entry("('=", "chatsan-tearfully-smiles"),
Entry("['=", "chatsan-tearfully-smiles"),
Entry("?", "chatsan-confused"), //DeltaV
Entry("!", "chatsan-surprised"), //DeltaV
Entry("…", "chatsan-sighs"), //DeltaV (note that ... doesn't work as that turns into a radio message)
];
@@ -49,5 +49,7 @@ namespace Content.Server.Chat.Managers
/// <param name="player">The player sending a chat message.</param>
/// <returns>False if the player has violated rate limits and should be blocked from sending further messages.</returns>
RateLimitStatus HandleRateLimit(ICommonSession player);
string PrependFollowButtonIfAppropriate(string wrappedMessage, EntityUid source, INetChannel recipient);
}
}
+90 -9
View File
@@ -8,6 +8,7 @@ using Content.Server.GameTicking;
using Content.Server.Speech.EntitySystems;
using Content.Shared.Speech.Hushing; // DeltaV
using Content.Server.Nyanotrasen.Chat;
using Content.Server.Speech.Components; // DeltaV
using Content.Server.Speech.Prototypes;
using Content.Server.Station.Systems;
using Content.Shared.ActionBlocker;
@@ -35,6 +36,7 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Replays;
using Robust.Shared.Utility;
using Content.Shared._DV.Chat; // DeltaV - chat enhancements
namespace Content.Server.Chat.Systems;
@@ -215,7 +217,7 @@ public sealed partial class ChatSystem : SharedChatSystem
// Was there an emote in the message? If so, send it.
if (player != null && emoteStr != message && emoteStr != null)
{
SendEntityEmote(source, emoteStr, range, nameOverride, ignoreActionBlocker);
SendEntityEmote(source, emoteStr, range, nameOverride, null, ignoreActionBlocker); // DeltaV - Had to change up for SendEntityEmote
}
// This can happen if the entire string is sanitized out.
@@ -252,9 +254,18 @@ public sealed partial class ChatSystem : SharedChatSystem
case InGameICChatType.Whisper:
SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker);
break;
case InGameICChatType.Emote:
SendEntityEmote(source, message, range, nameOverride, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
break;
case InGameICChatType.Emote: // DeltaV - Emote now has different types of emotes.
var type = ProcessEmoteMessage(source, message, out var modMessage);
if (type == EmoteType.Audible || type == EmoteType.AudiblePossessive)
{
if (checkRadioPrefix && TryProcessRadioMessage(source, modMessage, out var outputMessage, out var channel, capitalize: false))
SendAudibleEntityEmote(source, outputMessage, range, nameOverride, channel, type, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
else
SendAudibleEntityEmote(source, modMessage, range, nameOverride, null, type, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
}
else
SendEntityEmote(source, modMessage, range, nameOverride, type, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
break; // DeltaV - End
//Nyano - Summary: case adds the telepathic chat sending ability.
case InGameICChatType.Telepathic:
_nyanoChatSystem.SendTelepathicChat(source, message, range == ChatTransmitRange.HideChat);
@@ -629,6 +640,7 @@ public sealed partial class ChatSystem : SharedChatSystem
string action,
ChatTransmitRange range,
string? nameOverride,
EmoteType? emoteType, // DeltaV
bool hideLog = false,
bool checkEmote = true,
bool ignoreActionBlocker = false,
@@ -642,11 +654,21 @@ public sealed partial class ChatSystem : SharedChatSystem
var ent = Identity.Entity(source, EntityManager);
string name = FormattedMessage.EscapeText(nameOverride ?? Name(ent));
// Begin DeltaV
string wrappedMessage;
// Emotes use Identity.Name, since it doesn't actually involve your voice at all.
var wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message",
("entityName", name),
("entity", ent),
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
if (emoteType == EmoteType.Possessive) // DeltaV - Emote types now get checked.
wrappedMessage = Loc.GetString("chat-manager-entity-me-possessive-wrap-message",
("entityName", name),
("entity", ent),
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
else
wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message",
("entityName", name),
("entity", ent),
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
// End DeltaV
if (checkEmote &&
!TryEmoteChatInput(source, action))
@@ -660,6 +682,59 @@ public sealed partial class ChatSystem : SharedChatSystem
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source}: {action}");
}
// DeltaV - Added this to differentiate between emotes that can be heard over radio and those which can't.
protected override void SendAudibleEntityEmote(
EntityUid source,
string action,
ChatTransmitRange range,
string? nameOverride,
RadioChannelPrototype? channel,
EmoteType? emoteType,
bool hideLog = false,
bool checkEmote = true,
bool ignoreActionBlocker = false,
NetUserId? author = null
)
{
if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker)
return;
EmoteType type = emoteType ?? EmoteType.Audible;
// get the entity's apparent name (if no override provided).
var ent = Identity.Entity(source, EntityManager);
string name = FormattedMessage.EscapeText(nameOverride ?? Name(ent));
string wrappedMessage;
// Audible emotes use Identity.Name, since that is the status quo. Emotes like scream doesn't currently reveal identities so this won't either. [This may be changed with feedback as you can audibly emote over radios]
if (emoteType == EmoteType.AudiblePossessive)
wrappedMessage = Loc.GetString("chat-manager-entity-me-audible-possessive-wrap-message",
("entityName", name),
("entity", ent),
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
else
wrappedMessage = Loc.GetString("chat-manager-entity-me-audible-wrap-message",
("entityName", name),
("entity", ent),
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
if (checkEmote &&
!TryEmoteChatInput(source, action))
return;
SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, range, author);
var ev = new EntityAudiblyEmotedEvent(source, action, channel, type);
RaiseLocalEvent(source, ref ev, true);
if (!hideLog)
if (name != Name(source))
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source} as {name}: {action}");
else
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source}: {action}");
}
// DeltaV - End
// ReSharper disable once InconsistentNaming
private void SendLOOC(EntityUid source, ICommonSession player, string message, bool hideChat)
{
@@ -889,6 +964,7 @@ public sealed partial class ChatSystem : SharedChatSystem
var recipients = new Dictionary<ICommonSession, ICChatRecipientData>();
var ghostHearing = GetEntityQuery<GhostHearingComponent>();
var xforms = GetEntityQuery<TransformComponent>();
var blockListening = GetEntityQuery<BlockListeningComponent>(); // DeltaV - block listening
var transformSource = xforms.GetComponent(source);
var sourceMapId = transformSource.MapID;
@@ -904,6 +980,11 @@ public sealed partial class ChatSystem : SharedChatSystem
if (transformEntity.MapID != sourceMapId)
continue;
// Begin DeltaV - block listening
if (blockListening.HasComponent(playerEntity))
continue;
// End DeltaV - block listening
var observer = ghostHearing.HasComponent(playerEntity);
// even if they are a ghost hearer, in some situations we still need the range
@@ -971,4 +1052,4 @@ public sealed class CheckTargetedSpeechEvent : EntityEventArgs
{
public List<EntityUid> Targets = new List<EntityUid>();
}
// END Mono
// END Mono
@@ -51,11 +51,8 @@ public sealed class SignalTimerSystem : EntitySystem
if (_ui.HasUi(uid, SignalTimerUiKey.Key))
{
_ui.SetUiState(uid, SignalTimerUiKey.Key, new SignalTimerBoundUserInterfaceState(component.Label,
// Begin Monolith
// TimeSpan.FromSeconds(component.Delay).Minutes.ToString("D2"),
// TimeSpan.FromSeconds(component.Delay).Seconds.ToString("D2"),
TimeSpan.FromSeconds(component.Delay),
// End Monolith
TimeSpan.FromSeconds(component.Delay).Minutes.ToString("D2"),
TimeSpan.FromSeconds(component.Delay).Seconds.ToString("D2"),
component.CanEditLabel,
time,
active != null,
@@ -76,11 +73,8 @@ public sealed class SignalTimerSystem : EntitySystem
if (_ui.HasUi(uid, SignalTimerUiKey.Key))
{
_ui.SetUiState(uid, SignalTimerUiKey.Key, new SignalTimerBoundUserInterfaceState(signalTimer.Label,
// Begin Monolith
// TimeSpan.FromSeconds(signalTimer.Delay).Minutes.ToString("D2"),
// TimeSpan.FromSeconds(signalTimer.Delay).Seconds.ToString("D2"),
TimeSpan.FromSeconds(signalTimer.Delay),
// End Monolith
TimeSpan.FromSeconds(signalTimer.Delay).Minutes.ToString("D2"),
TimeSpan.FromSeconds(signalTimer.Delay).Seconds.ToString("D2"),
signalTimer.CanEditLabel,
TimeSpan.Zero,
false,
@@ -382,11 +382,6 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
var visualEnt = CreateExplosionVisualEntity(pos, queued.Proto.ID, spaceMatrix, spaceData, gridData.Values, iterationIntensity);
// camera shake
// ES START
// CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity);
// ES END
//For whatever bloody reason, sound system requires ENTITY coordinates.
var mapEntityCoords = _transformSystem.ToCoordinates(_map.GetMap(pos.MapId), pos);
@@ -416,12 +411,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
? queued.Proto.SmallSoundFar
: queued.Proto.SoundFar;
// ES START
var farTranslationShake = iterationIntensity.Count < queued.Proto.SmallSoundIterationThreshold
? new ESScreenshakeParameters() { Trauma = 0.4f, DecayRate = 0.2f, Frequency = 0.014f }
: new ESScreenshakeParameters() { Trauma = 0.6f, DecayRate = 0.05f, Frequency = 0.014f };
_shake.Screenshake(filter, farTranslationShake, null);
// ES END
CameraShake(iterationIntensity, pos, queued); // Starlight
_audio.PlayGlobal(farSound, farFilter, true, farSound.Params);
@@ -444,8 +434,13 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
_damageableSystem);
}
private void CameraShake(float range, MapCoordinates epicenter, float totalIntensity)
private void CameraShake(List<float> rangeList, MapCoordinates epicenter, QueuedExplosion queued) // Starlight - replace range with rangeList, totalIntensity with queued
{
// Starlight BEGIN
var range = rangeList.Count * 4f;
var totalIntensity = queued.TotalIntensity * 10f;
// Starlight END
var players = Filter.Empty();
players.AddInRange(epicenter, range, _playerManager, EntityManager);
@@ -463,7 +458,40 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
var distance = delta.Length();
var effect = 5 * MathF.Pow(totalIntensity, 0.5f) * (1 - distance / range);
if (effect > 0.01f)
_recoilSystem.KickCamera(uid, -delta.Normalized() * effect);
{
// DeltaV - Camera kick falloff START
// _recoilSystem.KickCamera(uid, -delta.Normalized() * effect);
// Exponential decay: N(t) = N₀ ⋅ e^(-λ ⋅ t)
// In this case, N = effect and we aren't decaying over time but with increasing distance from the epicenter,
// so t = distance / range. Higher values of λ lead to faster decay, lower values to slower decay.
//
// severity is a fixed factor used to decrease the overall severity of the camera kick,
// as the values were so high by default that decay was only becoming really noticeable near range.
//
// these changes are made here instead of directly assigning to the effect variable
// above to maintain the same overall effect range as before.
const float severity = 0.033f;
const float lambda = 4f;
_recoilSystem.KickCamera(uid, -delta.Normalized() * effect * severity * MathF.Exp(-lambda * (distance / range)));
// DeltaV END
// Starlight START
var shakeParams = rangeList.Count < queued.Proto.SmallSoundIterationThreshold
? new ESScreenshakeParameters() { Trauma = 0.4f, DecayRate = 0.2f, Frequency = 0.014f }
: new ESScreenshakeParameters() { Trauma = 0.6f, DecayRate = 0.05f, Frequency = 0.014f };
// DeltaV - Screenshake falloff START
// Linear falloff with increasing distance from epicenter,
// capped at certain values to avoid diminishing the effect completely near range.
shakeParams.DecayRate *= MathF.Min(1 + distance / range, 1.75f);
shakeParams.Frequency *= MathF.Max(1 - distance / range, 0.33f);
shakeParams.Trauma *= MathF.Max(1 - distance / range, 0.33f);
// DeltaV END
_shake.Screenshake(players, shakeParams, null);
// Starlight END
}
}
}
}
+20 -9
View File
@@ -5,6 +5,7 @@ using Content.Server.DeviceNetwork.Systems;
using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Server.Tools;
using Content.Shared._DV.Fax; // DeltaV - fax
using Content.Shared._DV.Pager; // DeltaV - pagers
using Content.Shared.Administration.Logs;
using Content.Shared.Containers.ItemSlots;
@@ -12,6 +13,7 @@ using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DeviceNetwork.Systems; // DeltaV - map init ordering
using Content.Shared.Emag.Systems;
using Content.Shared.Fax;
using Content.Shared.Fax.Components;
@@ -64,7 +66,7 @@ public sealed class FaxSystem : EntitySystem
// Hooks
SubscribeLocalEvent<FaxMachineComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<FaxMachineComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<FaxMachineComponent, MapInitEvent>(OnMapInit, after: [typeof(SharedDeviceNetworkSystem)]); // DeltaV - map init order, we need address assigned first
SubscribeLocalEvent<FaxMachineComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<FaxMachineComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
@@ -89,10 +91,10 @@ public sealed class FaxSystem : EntitySystem
{
base.Update(frameTime);
var query = EntityQueryEnumerator<FaxMachineComponent, ApcPowerReceiverComponent>();
while (query.MoveNext(out var uid, out var fax, out var receiver))
var query = EntityQueryEnumerator<FaxMachineComponent>();
while (query.MoveNext(out var uid, out var fax)) // DeltaV - faxes aren't necessarily powered
{
if (!receiver.Powered)
if (TryComp<ApcPowerReceiverComponent>(uid, out var receiver) && !receiver.Powered) // DeltaV - faxes aren't necessarily powered
continue;
ProcessPrintingAnimation(uid, frameTime, fax);
@@ -301,6 +303,11 @@ public sealed class FaxSystem : EntitySystem
if (!args.Data.TryGetValue(FaxConstants.FaxPaperNameData, out string? name) ||
!args.Data.TryGetValue(FaxConstants.FaxPaperContentData, out string? content))
return;
// Begin DeltaV - we removed the power requirement from device network but we still don't want
// unpowered faxes to happen
if (TryComp<ApcPowerReceiverComponent>(uid, out var receiver) && !receiver.Powered)
return;
// End DeltaV
args.Data.TryGetValue(FaxConstants.FaxPaperLabelData, out string? label);
args.Data.TryGetValue(FaxConstants.FaxPaperStampStateData, out string? stampState);
@@ -386,7 +393,7 @@ public sealed class FaxSystem : EntitySystem
var canCopy = isPaperInserted &&
component.SendTimeoutRemaining <= 0 &&
component.InsertingTimeRemaining <= 0;
var state = new FaxUiState(component.FaxName, component.KnownFaxes, canSend, canCopy, isPaperInserted, component.DestinationFaxAddress);
var state = new FaxUiState(component.FaxName, component.KnownFaxes, canSend, canCopy, isPaperInserted, component.DestinationFaxAddress, component.SendTimeoutRemaining <= 0); // DeltaV - AI fax
_userInterface.SetUiState(uid, FaxUiKey.Key, state);
}
@@ -455,7 +462,7 @@ public sealed class FaxSystem : EntitySystem
/// Copies the paper in the fax. A timeout is set after copying,
/// which is shared by the send button.
/// </summary>
public void Copy(EntityUid uid, FaxMachineComponent? component, FaxCopyMessage args)
public void Copy(EntityUid uid, FaxMachineComponent? component, BoundUserInterfaceMessage args, EntityUid? item = null) // DeltaV - station AI fax
{
if (!Resolve(uid, ref component))
return;
@@ -463,7 +470,7 @@ public sealed class FaxSystem : EntitySystem
if (component.SendTimeoutRemaining > 0)
return;
var sendEntity = component.PaperSlot.Item;
var sendEntity = item ?? component.PaperSlot.Item; // DeltaV - station AI fax
if (sendEntity == null)
return;
@@ -505,7 +512,7 @@ public sealed class FaxSystem : EntitySystem
/// Sends message to addressee if paper is set and a known fax is selected
/// A timeout is set after sending, which is shared by the copy button.
/// </summary>
public void Send(EntityUid uid, FaxMachineComponent? component, FaxSendMessage args)
public void Send(EntityUid uid, FaxMachineComponent? component, BoundUserInterfaceMessage args, EntityUid? item = null) // DeltaV - station AI fax
{
if (!Resolve(uid, ref component))
return;
@@ -513,7 +520,7 @@ public sealed class FaxSystem : EntitySystem
if (component.SendTimeoutRemaining > 0)
return;
var sendEntity = component.PaperSlot.Item;
var sendEntity = item ?? component.PaperSlot.Item; // DeltaV - station AI fax
if (sendEntity == null)
return;
@@ -629,6 +636,10 @@ public sealed class FaxSystem : EntitySystem
}
_adminLogger.Add(LogType.Action, LogImpact.Low, $"\"{component.FaxName}\" {ToPrettyString(uid):tool} printed {ToPrettyString(printed):subject}: {printout.Content}");
// Begin DeltaV - notify on fax printing
var evt = new FaxPrintedEvent(printed);
RaiseLocalEvent(uid, ref evt);
// End DeltaV - notify on fax printing
}
private void NotifyAdmins(string faxName)
@@ -35,17 +35,17 @@ public sealed partial class PuddleSystem
if (!entity.Comp.SpillWhenThrown || Openable.IsClosed(entity.Owner))
return;
// DeltaV - Beer Goggles Safe Throw
if ( args.User is { } user && _safeSolutionThrower.GetSafeThrow(user))
{
_physics.SetAngularVelocity(entity, 0);
Transform(entity).LocalRotation = Angle.Zero;
return;
}
// END DeltaV
if (TrySplashSpillAt(entity.Owner, Transform(entity).Coordinates, out _, out var solution) && args.User != null)
{
// DeltaV - Beer Goggles Safe Throw
if (_safeSolutionThrower.GetSafeThrow(args.User.Value))
{
_physics.SetAngularVelocity(entity, 0);
Transform(entity).LocalRotation = Angle.Zero;
return;
}
// END DeltaV
AdminLogger.Add(LogType.Landed,
$"{ToPrettyString(entity.Owner):entity} spilled a solution {SharedSolutionContainerSystem.ToPrettyString(solution):solution} on landing");
}
@@ -0,0 +1,26 @@
using Content.Shared.Administration;
using Robust.Shared.Console;
namespace Content.Server.Ghost;
[AnyCommand]
internal sealed partial class GhostFollowEntityCommand : LocalizedEntityCommands
{
public const string CommandName = "ghost_follow_entity";
[Dependency] private GhostSystem _ghost = null!;
public override string Command => CommandName;
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1 || shell.Player is not { } player)
return;
var target = args[0];
if (!NetEntity.TryParse(target, out var targetEnt))
return;
_ghost.GhostWarpRequest(player, targetEnt);
}
}
+35 -20
View File
@@ -299,10 +299,22 @@ namespace Content.Server.Ghost
#region Warp
public bool CanGhostWarp(ICommonSession session, out EntityUid entity)
{
if (session.AttachedEntity is not { Valid: true } sessionEntity
|| !_ghostQuery.HasComp(sessionEntity))
{
entity = default;
return false;
}
entity = sessionEntity;
return true;
}
private void OnGhostWarpsRequest(GhostWarpsRequestEvent msg, EntitySessionEventArgs args)
{
if (args.SenderSession.AttachedEntity is not {Valid: true} entity
|| !_ghostQuery.HasComp(entity))
if (!CanGhostWarp(args.SenderSession, out var entity))
{
Log.Warning($"User {args.SenderSession.Name} sent a {nameof(GhostWarpsRequestEvent)} without being a ghost.");
return;
@@ -312,30 +324,33 @@ namespace Content.Server.Ghost
RaiseNetworkEvent(response, args.SenderSession.Channel);
}
public void GhostWarpRequest(ICommonSession player, NetEntity target)
{
if (!CanGhostWarp(player, out var attached))
{
Log.Warning($"User {player.Name} tried to warp to {target} without being a ghost.");
return;
}
var realTarget = GetEntity(target);
if (!Exists(realTarget))
{
Log.Warning($"User {player.Name} tried to warp to an invalid entity id: {target}");
return;
}
WarpTo(attached, realTarget);
}
private void OnGhostWarpToTargetRequest(GhostWarpToTargetRequestEvent msg, EntitySessionEventArgs args)
{
if (args.SenderSession.AttachedEntity is not {Valid: true} attached
|| !_ghostQuery.HasComp(attached))
{
Log.Warning($"User {args.SenderSession.Name} tried to warp to {msg.Target} without being a ghost.");
return;
}
var target = GetEntity(msg.Target);
if (!Exists(target))
{
Log.Warning($"User {args.SenderSession.Name} tried to warp to an invalid entity id: {msg.Target}");
return;
}
WarpTo(attached, target);
GhostWarpRequest(args.SenderSession, msg.Target);
}
private void OnGhostnadoRequest(GhostnadoRequestEvent msg, EntitySessionEventArgs args)
{
if (args.SenderSession.AttachedEntity is not {} uid
|| !_ghostQuery.HasComp(uid))
if (CanGhostWarp(args.SenderSession, out var uid))
{
Log.Warning($"User {args.SenderSession.Name} tried to ghostnado without being a ghost.");
return;
@@ -211,7 +211,7 @@ namespace Content.Server.Medical.BiomassReclaimer
_solution.ResolveSolution(toProcess, stream.BloodSolutionName, ref stream.BloodSolution, out var solution))
{
component.BloodReagents = solution.Clone();
component.BloodReagents.ScaleSolution(50 / component.BloodReagents.Volume);
//component.BloodReagents.ScaleSolution(50 / component.BloodReagents.Volume); // Delta V - This doesn't need to be here. It just always makes the solution ~50u but also might divide by 0. Just use the current blood level so more blood = more mess.
}
if (TryComp<ButcherableComponent>(toProcess, out var butcherableComponent))
{
+9 -9
View File
@@ -327,15 +327,15 @@ public sealed class NukeSystem : EntitySystem
// should play
if (nuke.RemainingTime <= _nukeSongLength + nuke.AlertSoundTime + NukeSongBuffer && !nuke.PlayedNukeSong && !ResolvedSoundSpecifier.IsNullOrEmpty(_selectedNukeSong))
{
_sound.DispatchStationEventMusic(uid, _selectedNukeSong, StationEventMusicType.Nuke);
_sound.DispatchGlobalEventMusic(_selectedNukeSong, StationEventMusicType.Nuke); // DeltaV - Global Nuke Music
nuke.PlayedNukeSong = true;
}
// play alert sound if time is running out
if (nuke.RemainingTime <= nuke.AlertSoundTime && !nuke.PlayedAlertSound)
{
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(nuke.AlertSound), new AudioParams{Volume = -5f});
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
_sound.PlayGlobal(_audio.ResolveSound(nuke.AlertSound), new AudioParams { Volume = -5f }); // DeltaV - Global Nuke SFX
_sound.StopGlobalEventMusic(StationEventMusicType.Nuke); // DeltaV - Global Nuke Music
nuke.PlayedAlertSound = true;
UpdateAppearance(uid, nuke);
}
@@ -504,9 +504,9 @@ public sealed class NukeSystem : EntitySystem
("time", (int) component.RemainingTime),
("location", FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString((uid, nukeXform)))));
var sender = Loc.GetString("nuke-component-announcement-sender");
_chatSystem.DispatchStationAnnouncement(stationUid ?? uid, announcement, sender, false, null, Color.Red);
_chatSystem.DispatchGlobalAnnouncement(announcement, sender, false, null, Color.Red); // DeltaV - Global Nuke Annoucement
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(component.ArmSound));
_sound.PlayGlobal(_audio.ResolveSound(component.ArmSound)); // DeltaV - Global Nuke Music
_nukeSongLength = (float) _audio.GetAudioLength(_selectedNukeSong).TotalSeconds;
// turn on the spinny light
@@ -544,11 +544,11 @@ public sealed class NukeSystem : EntitySystem
// warn a crew
var announcement = Loc.GetString("nuke-component-announcement-unarmed");
var sender = Loc.GetString("nuke-component-announcement-sender");
_chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false);
_chatSystem.DispatchGlobalAnnouncement(announcement, sender, false); // DeltaV - Global Nuke Announcement
component.PlayedNukeSong = false;
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(component.DisarmSound));
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
_sound.PlayGlobal(_audio.ResolveSound(component.DisarmSound)); // DeltaV - Global Nuke SFX
_sound.StopGlobalEventMusic(StationEventMusicType.Nuke); // DeltaV - Global Nuke Music
// reset nuke remaining time to either itself or the minimum time, whichever is higher
component.RemainingTime = Math.Max(component.RemainingTime, component.MinimumTime);
@@ -610,7 +610,7 @@ public sealed class NukeSystem : EntitySystem
OwningStation = transform.GridUid,
});
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
_sound.StopGlobalEventMusic(StationEventMusicType.Nuke); // DeltaV - Global Nuke Music
Del(uid);
}
@@ -9,6 +9,7 @@ using Content.Shared.Radio.Components;
using Content.Shared.Radio.EntitySystems;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Content.Shared._DV.Chat;
namespace Content.Server.Radio.EntitySystems;
@@ -26,6 +27,7 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
SubscribeLocalEvent<HeadsetComponent, EncryptionChannelsChangedEvent>(OnKeysChanged);
SubscribeLocalEvent<WearingHeadsetComponent, EntitySpokeEvent>(OnSpeak);
SubscribeLocalEvent<WearingHeadsetComponent, EntityAudiblyEmotedEvent>(OnAudibleEmote); // DeltaV
}
private void OnKeysChanged(EntityUid uid, HeadsetComponent component, EncryptionChannelsChangedEvent args)
@@ -48,22 +50,43 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
EnsureComp<ActiveRadioComponent>(uid).Channels = new(keyHolder.Channels);
}
private void OnSpeak(EntityUid uid, WearingHeadsetComponent component, EntitySpokeEvent args)
// DeltaV
// WARNING - Be very careful when modifying this method.
// The implementation right now has null forgiving operatiors in OnSpeak() and OnAudibleEmote() since this only returns true if channel is not null
private bool CheckRadioCapable(EntityUid uid, WearingHeadsetComponent headset, RadioChannelPrototype? channel)
{
if (args.Channel != null
&& TryComp(component.Headset, out EncryptionKeyHolderComponent? keys)
&& keys.Channels.Contains(args.Channel.ID))
if (channel != null
&& TryComp(headset.Headset, out EncryptionKeyHolderComponent? keys)
&& keys.Channels.Contains(channel.ID))
{
// Begin DeltaV Additions: No using headsets if you lost your hands or are cuffed
if (!TryComp<HandsComponent>(uid, out var hands) || hands.Count < 1 ||
TryComp<CuffableComponent>(uid, out var cuffable) && _cuffable.IsCuffed((uid, cuffable)))
{
_popup.PopupEntity(Loc.GetString("headset-cant-reach"), uid, uid, PopupType.SmallCaution);
return;
return false;
}
// End DeltaV Additions
_radio.SendRadioMessage(uid, args.Message, args.Channel, component.Headset);
return true;
}
return false;
}
private void OnAudibleEmote(EntityUid uid, WearingHeadsetComponent component, EntityAudiblyEmotedEvent args)
{
if (CheckRadioCapable(uid, component, args.Channel))
{
_radio.SendRadioMessage(uid, args.Message, args.Channel!, component.Headset, emType: args.Type);
args.Channel = null;
}
}
// DeltaV - End
private void OnSpeak(EntityUid uid, WearingHeadsetComponent component, EntitySpokeEvent args)
{
if (CheckRadioCapable(uid, component, args.Channel)) // DeltaV - Put all the radio checks into the CheckRadioCapable() method.
{
_radio.SendRadioMessage(uid, args.Message, args.Channel!, component.Headset); // DeltaV - Made the args.Channel null ignorant as CheckRadioCapable() guarantees it not null.
args.Channel = null; // prevent duplicate messages from other listeners.
}
}
@@ -1,6 +1,9 @@
using Content.Server.Administration.Logs;
using Content.Server.Chat.Managers;
using Content.Server.Chat.Systems;
using Content.Server.Ghost;
using Content.Server.Power.Components;
using Content.Shared._DV.Chat;
using Content.Shared.Chat;
using Content.Shared.Database;
using Content.Shared.Radio;
@@ -27,6 +30,8 @@ public sealed class RadioSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly GhostSystem _ghost = default!;
// set used to prevent radio feedback loops.
private readonly HashSet<string> _messages = new();
@@ -38,6 +43,7 @@ public sealed class RadioSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<IntrinsicRadioReceiverComponent, RadioReceiveEvent>(OnIntrinsicReceive);
SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntitySpokeEvent>(OnIntrinsicSpeak);
SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntityAudiblyEmotedEvent>(OnIntrinsicAudibleEmote); // DeltaV - Robots should be allowed to emote over radio.
_exemptQuery = GetEntityQuery<TelecomExemptComponent>();
}
@@ -53,16 +59,43 @@ public sealed class RadioSystem : EntitySystem
private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, ref RadioReceiveEvent args)
{
if (TryComp(uid, out ActorComponent? actor))
_netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel);
if (!TryComp(uid, out ActorComponent? actor))
return;
var msg = args.ChatMsg;
if (_ghost.CanGhostWarp(actor.PlayerSession, out _))
{
msg = new MsgChatMessage
{
Message = new ChatMessage(args.ChatMsg.Message)
{
WrappedMessage = _chatManager.PrependFollowButtonIfAppropriate(
args.ChatMsg.Message.WrappedMessage,
args.MessageSource,
actor.PlayerSession.Channel),
},
};
}
_netMan.ServerSendMessage(msg, actor.PlayerSession.Channel);
}
// DeltaV
private void OnIntrinsicAudibleEmote(EntityUid uid, IntrinsicRadioTransmitterComponent component, EntityAudiblyEmotedEvent args)
{
if (args.Channel != null && component.Channels.Contains(args.Channel.ID))
{
SendRadioMessage(uid, args.Message, args.Channel, uid, emType: args.Type);
}
}
// DeltaV - End
/// <summary>
/// Send radio message to all active radio listeners
/// </summary>
public void SendRadioMessage(EntityUid messageSource, string message, ProtoId<RadioChannelPrototype> channel, EntityUid radioSource, bool escapeMarkup = true)
public void SendRadioMessage(EntityUid messageSource, string message, ProtoId<RadioChannelPrototype> channel, EntityUid radioSource, bool escapeMarkup = true, EmoteType? emType = null) // DeltaV - EmoteType? added.
{
SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup);
SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup, emType: emType);
}
/// <summary>
@@ -70,7 +103,7 @@ public sealed class RadioSystem : EntitySystem
/// </summary>
/// <param name="messageSource">Entity that spoke the message</param>
/// <param name="radioSource">Entity that picked up the message and will send it, e.g. headset</param>
public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, bool escapeMarkup = true)
public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, bool escapeMarkup = true, EmoteType? emType = null) // DeltaV - EmoteType? added.
{
// TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this.
if (!_messages.Add(message))
@@ -92,14 +125,31 @@ public sealed class RadioSystem : EntitySystem
? FormattedMessage.EscapeText(message)
: message;
var wrappedMessage = Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap",
("color", channel.Color),
("fontType", speech.FontId),
("fontSize", speech.FontSize),
("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))),
("channel", $"\\[{channel.LocalizedName}\\]"),
("name", name),
("message", content));
// DeltaV - This change is to change up how the messages are wrapped up. Basically changing the formatting depending on the emote type.
string wrappedMessage;
if (emType == EmoteType.Audible)
wrappedMessage = Loc.GetString("chat-radio-message-audible-emote-wrap",
("color", channel.Color),
("channel", $"\\[{channel.LocalizedName}\\]"),
("name", name),
("message", content));
else if (emType == EmoteType.AudiblePossessive)
wrappedMessage = Loc.GetString("chat-radio-message-audible-possessive-emote-wrap",
("color", channel.Color),
("channel", $"\\[{channel.LocalizedName}\\]"),
("name", name),
("message", content));
else
wrappedMessage = Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap",
("color", channel.Color),
("fontType", speech.FontId),
("fontSize", speech.FontSize),
("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))),
("channel", $"\\[{channel.LocalizedName}\\]"),
("name", name),
("message", content));
// DeltaV - End
// most radios are relayed to chat, so lets parse the chat message beforehand
var chat = new ChatMessage(
@@ -107,7 +157,8 @@ public sealed class RadioSystem : EntitySystem
message,
wrappedMessage,
NetEntity.Invalid,
null);
null,
radioChannelProto: channel.ID); // DeltaV - Add RadioChannel for committing sins
var chatMsg = new MsgChatMessage { Message = chat };
var ev = new RadioReceiveEvent(message, messageSource, channel, radioSource, chatMsg);
@@ -159,7 +210,7 @@ public sealed class RadioSystem : EntitySystem
}
/// <inheritdoc cref="TelecomServerComponent"/>
private bool HasActiveServer(MapId mapId, string channelId)
public bool HasActiveServer(MapId mapId, string channelId) // DeltaV - we need this
{
var servers = EntityQuery<TelecomServerComponent, EncryptionKeyHolderComponent, ApcPowerReceiverComponent, TransformComponent>();
foreach (var (_, keys, power, transform) in servers)
+24 -2
View File
@@ -1,3 +1,4 @@
using System.Linq; // DeltaV
using System.Numerics;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Chat.Systems;
@@ -8,6 +9,8 @@ using Content.Server.Popups;
using Content.Shared.Atmos;
using Content.Shared.Chat;
using Content.Shared.Dataset;
using Content.Shared.Mobs; // DeltaV
using Content.Shared.Mobs.Components; // DeltaV
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Pointing;
@@ -47,14 +50,33 @@ namespace Content.Server.RatKing
if (!TryComp<HungerComponent>(uid, out var hunger))
return;
// DeltaV - modify cost of Raise Army based on the amount of alive servants
// Check on how many servants are alive to calculate the cost of a new servant
var aliveServants = component.Servants.Count(servant
=> TryComp<MobStateComponent>(servant, out var mobState) && mobState.CurrentState == MobState.Alive);
// calculate the cost multiplier
var multiplier = aliveServants switch
{
< 5 => 1.0f, // 1-5 servants: 10 hunger
< 10 => 1.5f, // 6-10 servants: 15 hunger
< 15 => 2.5f, // 11-15 servants: 25 hunger
< 20 => 5.0f, // 16-20 servants: 50 hunger
< 25 => 10.0f, // 21-25 servants: 100 hunger
_ => 15.0f, // Above 25 servants: 150 hunger
};
var hungerPerArmyUseAdjusted = component.HungerPerArmyUse * multiplier;
//make sure the hunger doesn't go into the negatives
if (_hunger.GetHunger(hunger) < component.HungerPerArmyUse)
if (_hunger.GetHunger(hunger) < hungerPerArmyUseAdjusted)
{
_popup.PopupEntity(Loc.GetString("rat-king-too-hungry"), uid, uid);
return;
}
args.Handled = true;
_hunger.ModifyHunger(uid, -component.HungerPerArmyUse, hunger);
_hunger.ModifyHunger(uid, - hungerPerArmyUseAdjusted, hunger);
// DeltaV - end to the modified cost of Raise Army
var servant = Spawn(component.ArmyMobSpawnId, Transform(uid).Coordinates);
var comp = EnsureComp<RatKingServantComponent>(servant);
comp.King = uid;
@@ -27,6 +27,11 @@ public sealed partial class BorgSystem
var query = EntityQueryEnumerator<BorgTransponderComponent, BorgChassisComponent, DeviceNetworkComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out var comp, out var chassis, out var device, out var meta))
{
// DeltaV Begin
if (!comp.Active)
return;
// DeltaV End
if (comp.NextDisable is { } nextDisable && now >= nextDisable)
DoDisable((uid, comp, chassis, meta));
@@ -83,6 +88,11 @@ public sealed partial class BorgSystem
private void OnPacketReceived(Entity<BorgTransponderComponent> ent, ref DeviceNetworkPacketEvent args)
{
// DeltaV Begin
if (!ent.Comp.Active)
return;
// DeltaV End
var payload = args.Data;
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
return;
@@ -197,4 +207,17 @@ public sealed partial class BorgSystem
return true;
}
// DeltaV Begin
/// <summary>
/// DeltaV - sets if the borg transponder is active
/// </summary>
public void SetTransponderActive(Entity<BorgTransponderComponent?> ent, bool active)
{
if (!Resolve(ent, ref ent.Comp))
return;
ent.Comp.Active = active;
}
// DeltaV End
}
@@ -1,4 +1,5 @@
using Content.Server.StationEvents.Components;
using Content.Shared._DV.IonLaws; // DeltaV - ion laws
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Dataset;
@@ -20,6 +21,7 @@ public sealed class IonStormSystem : EntitySystem
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SiliconLawSystem _siliconLaw = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IonLawSystem _ionLaw = default!; // DeltaV - ion laws
// funny
private static readonly ProtoId<DatasetPrototype> Threats = "IonStormThreats";
@@ -93,7 +95,7 @@ public sealed class IonStormSystem : EntitySystem
}
// generate a new law...
var newLaw = GenerateLaw();
var newLaw = _ionLaw.GenerateLaw(_robustRandom); // GenerateLaw(); DeltaV - inject our own
// see if the law we add will replace a random existing law or be a new glitched order one
if (laws.Laws.Count > 0 && _robustRandom.Prob(target.ReplaceChance))
@@ -29,12 +29,14 @@ public sealed partial class SpeechWireAction : ComponentWireAction<SpeechCompone
public override bool Cut(EntityUid user, Wire wire, SpeechComponent component)
{
_speech.SetSpeech(wire.Owner, false, component);
EntityManager.GetComponentOrNull<Components.UnblockableSpeechComponent>(user)?.Active = false; // DeltaV - we need this to override unblockable speech
return true;
}
public override bool Mend(EntityUid user, Wire wire, SpeechComponent component)
{
_speech.SetSpeech(wire.Owner, true, component);
EntityManager.GetComponentOrNull<Components.UnblockableSpeechComponent>(user)?.Active = true; // DeltaV - we need this to override unblockable speech
return true;
}
@@ -3,5 +3,9 @@ namespace Content.Server.Speech.Components
[RegisterComponent]
public sealed partial class UnblockableSpeechComponent : Component
{
// Begin DeltaV
[DataField]
public bool Active = true;
// End DeltaV
}
}
@@ -12,7 +12,7 @@ namespace Content.Server.Speech.EntitySystems
private void OnCheck(EntityUid uid, UnblockableSpeechComponent component, CheckIgnoreSpeechBlockerEvent args)
{
args.IgnoreBlocker = true;
args.IgnoreBlocker = component.Active; // DeltaV
}
}
}
@@ -6,4 +6,41 @@ namespace Content.Server.StationEvents.Components;
[RegisterComponent]
public sealed partial class IonStormRuleComponent : Component
{
// DV start - Synthetic tweaks
/// <summary>
/// The chance that a synth gets electrocuted by the ion storm.
/// </summary>
[DataField]
public float SynthElectrocutionChance = 0.6f;
/// <summary>
/// The minimum delay before a synth affected by the ion storm gets electrocuted. (in seconds)
/// </summary>
[DataField]
public int SynthElectrocutionDelayMin = 3;
/// <summary>
/// The maximum delay before a synth affected by the ion storm gets electrocuted. (in seconds)
/// </summary>
[DataField]
public int SynthElectrocutionDelayMax = 10;
/// <summary>
/// The minimum damage dealt by the synth electrocution. (shock damage)
/// </summary>
[DataField]
public int SynthElectrocutionDamageMin = 5;
/// <summary>
/// The maximum damage dealt by the synth electrocution. (shock damage)
/// </summary>8
[DataField]
public int SynthElectrocutionDamageMax = 10;
/// <summary>
/// How long the synth is stunned for by the electrocution. (in seconds)
/// </summary>
[DataField]
public int SynthElectrocutionStunDuration = 3;
// DV end - synthetic tweaks
}
@@ -6,9 +6,12 @@ using Content.Shared.Silicons.Laws.Components;
using Content.Shared.Station.Components;
// CD - start synth trait
using Content.Server.Chat.Managers;
using Content.Server.Electrocution; // DV - added for synth trait
using Content.Shared.Chat;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Content.Shared._ES.Sparks; // Delta-V change - added for synth trait (where they get affected by ion storms)
using Timer = Robust.Shared.Timing.Timer; // DV - used for synth trait delayed damage
// CD - end synth trait
namespace Content.Server.StationEvents.Events;
@@ -17,6 +20,8 @@ public sealed class IonStormRule : StationEventSystem<IonStormRuleComponent>
{
[Dependency] private readonly IonStormSystem _ionStorm = default!;
[Dependency] private readonly IChatManager _chatManager = default!; // CD - Used for synth trait
[Dependency] private readonly ElectrocutionSystem _electrocution = default!; // DV - used for synth trait
[Dependency] private readonly ESSparksSystem _esSparks = default!; // DV - used for synth trait
protected override void Started(EntityUid uid, IonStormRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
@@ -26,18 +31,32 @@ public sealed class IonStormRule : StationEventSystem<IonStormRuleComponent>
return;
// CD - Go through everyone with the SynthComponent and inform them a storm is happening.
// DV - buuuuuut only if IonStormAffected is present so that not all synthetics are affected by this
// but they are still notified about incoming ion storms regardless.
var synthQuery = EntityQueryEnumerator<SynthComponent>();
while (synthQuery.MoveNext(out var ent, out var synthComp))
{
if (RobustRandom.Prob(synthComp.AlertChance))
continue;
if (!TryComp<ActorComponent>(ent, out var actor))
continue;
var msg = Loc.GetString("station-event-ion-storm-synth");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.Yellow);
if (RobustRandom.Prob(synthComp.AlertChance))
{
var msg = Loc.GetString("station-event-ion-storm-synth");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.Yellow);
}
// DV start - super duper fucked up thing for synths
if (synthComp.IonStormAffected && RobustRandom.Prob(comp.SynthElectrocutionChance))
{
var delay = RobustRandom.Next(TimeSpan.FromSeconds(comp.SynthElectrocutionDelayMin), TimeSpan.FromSeconds(comp.SynthElectrocutionDelayMax)); // they'll never know when it'll hit them
Timer.Spawn(delay, () =>
{
_electrocution.TryDoElectrocution(ent, null, RobustRandom.Next(comp.SynthElectrocutionDamageMin, comp.SynthElectrocutionDamageMax), TimeSpan.FromSeconds(comp.SynthElectrocutionStunDuration), true, ignoreInsulation: true);
_esSparks.DoSparks(ent);
});
}
// DV end
}
// CD - End of synth trait
@@ -1,4 +1,3 @@
using Content.Server._DV.Psionics.Systems;
using Content.Server.Administration.Managers;
using Content.Server.Atmos.Components;
using Content.Server.Body.Components;
@@ -14,7 +13,6 @@ using Content.Server.NPC.HTN;
using Content.Server.NPC.Systems;
using Content.Server.StationEvents.Components;
using Content.Server.Speech.Components;
using Content.Shared._DV.Psionics.Components; // DeltaV
using Content.Shared.Body.Components;
using Content.Shared.CombatMode;
using Content.Shared.CombatMode.Pacification;
@@ -27,7 +25,6 @@ using Content.Shared.Mobs.Components;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.NameModifier.EntitySystems;
using Content.Shared.NPC.Components; // DeltaV
using Content.Shared.NPC.Systems;
using Content.Shared.Nutrition.AnimalHusbandry;
using Content.Shared.Nutrition.Components;
@@ -72,7 +69,6 @@ public sealed partial class ZombieSystem
[Dependency] private readonly NPCSystem _npc = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly ISharedPlayerManager _player = default!;
[Dependency] private readonly PsionicSystem _psionic = default!; // DeltaV
private static readonly ProtoId<TagPrototype> InvalidForGlobalSpawnSpellTag = "InvalidForGlobalSpawnSpell";
private static readonly ProtoId<TagPrototype> CannotSuicideTag = "CannotSuicide";
@@ -134,6 +130,9 @@ public sealed partial class ZombieSystem
//you're a real zombie now, son.
var zombiecomp = AddComp<ZombieComponent>(target);
// DeltaV - Save factions, psionics, etc. before modifications are made.
PreserveEntityComponentState((target, zombiecomp));
//we need to basically remove all of these because zombies shouldn't
//get diseases, breath, be thirst, be hungry, die in space, get double sentience, have offspring or be paraplegic.
RemComp<RespiratorComponent>(target);
@@ -146,12 +145,6 @@ public sealed partial class ZombieSystem
RemComp<ComplexInteractionComponent>(target);
RemComp<SentienceTargetComponent>(target);
// DeltaV Start - Prevent Psionic Zombies
RemComp<PotentialPsionicComponent>(target);
if (HasComp<PsionicComponent>(target))
_psionic.MindBreakEntity(target, false, true);
// DeltaV End - Prevent Psionic Zombies
//funny voice
var accentType = "zombie";
if (TryComp<ZombieAccentOverrideComponent>(target, out var accent))
@@ -245,6 +238,9 @@ public sealed partial class ZombieSystem
//Should prevent instances of zombies using comms for information they shouldnt be able to have.
_inventory.TryUnequip(target, "ears", true, true);
// DeltaV - Extra zombification removals, etc.
ZombifyEntityDV((target, zombiecomp), mobState);
//popup
_popup.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, PopupType.LargeCaution);
@@ -261,7 +257,6 @@ public sealed partial class ZombieSystem
_faction.ClearFactions(target, dirty: false);
_faction.AddFaction(target, ZombieFaction);
EnsureComp<NoFriendlyFireComponent>(target); // DeltaV - prevent shitters biting other zombies
//gives it the funny "Zombie ___" name.
_nameMod.RefreshNameModifiers(target);
+3 -1
View File
@@ -78,6 +78,8 @@ namespace Content.Server.Zombies
SubscribeLocalEvent<IncurableZombieComponent, MapInitEvent>(OnPendingMapInit);
SubscribeLocalEvent<ZombifyOnDeathComponent, MobStateChangedEvent>(OnDamageChanged);
InitializeDV(); // DeltaV - Additional Subscriptions for ZombieComponent
}
private void OnBeforeRemoveAnomalyOnDeath(Entity<PendingZombieComponent> ent, ref BeforeRemoveAnomalyOnDeathEvent args)
@@ -325,7 +327,7 @@ namespace Content.Server.Zombies
// Remove the role when getting cloned, getting gibbed and borged, or leaving the body via any other method.
private void OnMindRemoved(Entity<ZombieComponent> ent, ref MindRemovedMessage args)
{
_role.MindRemoveRole<ZombieRoleComponent>((args.Mind.Owner, args.Mind.Comp));
_role.MindRemoveRole<ZombieRoleComponent>((args.Mind.Owner, args.Mind.Comp));
}
}
}
+10 -1
View File
@@ -7,7 +7,16 @@ namespace Content.Server._CD.Traits;
public sealed partial class SynthComponent : Component
{
/// <summary>
/// The chance that the synth is alerted of an ion storm
/// Whether this synthetic is affected by ion storms (electrocution/sparks and
/// alerts when one occurs). False by default. set true by the
/// "Susceptible to Ion Storms" meta trait.
/// </summary>
[DataField]
public bool IonStormAffected;
/// <summary>
/// The chance that the synth is alerted of an ion storm. Only relevant if
/// <see cref="IonStormAffected"/> is true.
/// </summary>
[DataField]
public float AlertChance = 0.3f;
+27 -2
View File
@@ -1,8 +1,12 @@
using Content.Server.Body.Systems;
using Content.Server.Database;
using Content.Server.Chat.Systems;
using Content.Shared.Body.Components;
using Content.Shared.Chat;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Chat.TypingIndicator;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Speech.Components;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
namespace Content.Server._CD.Traits;
@@ -12,15 +16,22 @@ public sealed class SynthSystem : EntitySystem
// Begin DeltaV - make strings static readonly
private static readonly ProtoId<TypingIndicatorPrototype> RobotTypingIndicator = "robot";
private static readonly ProtoId<ReagentPrototype> SynthBloodReagent = "SynthBlood";
private static readonly ProtoId<TagPrototype> SyntheticEmotesTag = "SyntheticEmotes";
private static readonly ProtoId<EmoteSoundsPrototype> SyntheticEmoteSounds = "SyntheticEmoteSounds";
// End DeltaV
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ChatSystem _chat = default!;
private EmoteSoundsPrototype? _syntheticEmoteSounds; // delta-v - proper synthe emote implementation
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SynthComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<SynthComponent, EmoteEvent>(OnEmote); // delta-v - proper synthe emote implementation
}
private void OnStartup(EntityUid uid, SynthComponent component, ComponentStartup args)
@@ -38,5 +49,19 @@ public sealed class SynthSystem : EntitySystem
_bloodstream.ChangeBloodReagents((uid, bloodstream), new([new(SynthBloodReagent, bloodstream.BloodReferenceSolution.Volume)]));
}
// End DeltaV
_tag.AddTag(uid, SyntheticEmotesTag); // delta-v - proper synthe emote implementation
}
}
// Start DeltaV - proper synthe emote implementation
private void OnEmote(EntityUid uid, SynthComponent component, ref EmoteEvent args)
{
if (args.Handled)
return;
_syntheticEmoteSounds ??= _proto.Index(SyntheticEmoteSounds);
args.Handled = _chat.TryPlayEmoteSound(uid, _syntheticEmoteSounds, args.Emote);
}
// End DeltaV
}
@@ -0,0 +1,73 @@
using Content.Shared.Audio;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Map;
namespace Content.Server.Audio;
/// <summary>
/// Extends upstream's Content.Server/Audio/ServerGlobalSoundSystem.cs.
/// </summary>
public sealed partial class ServerGlobalSoundSystem
{
/// <summary>
/// DeltaV - Plays a sound globally for all players, no matter where they are.
/// </summary>
/// <param name="specifier"></param>
/// <param name="audioParams"></param>
public void PlayGlobal(ResolvedSoundSpecifier specifier, AudioParams? audioParams = null)
{
var msg = new GameGlobalSoundEvent(specifier, audioParams);
RaiseNetworkEvent(msg);
}
public void StopGlobalEventMusic(StationEventMusicType type)
{
var msg = new StopStationEventMusic(type);
RaiseNetworkEvent(msg);
}
public void DispatchGlobalEventMusic(SoundSpecifier sound, StationEventMusicType type)
{
DispatchGlobalEventMusic(_audio.ResolveSound(sound), type);
}
public void DispatchGlobalEventMusic(ResolvedSoundSpecifier specifier, StationEventMusicType type)
{
var audio = AudioParams.Default.WithVolume(-8);
var msg = new StationEventMusicEvent(specifier, type, audio);
RaiseNetworkEvent(msg);
}
/// <summary>
/// DeltaV - Plays a sound globally for all players on a specified Map.
/// </summary>
/// <param name="specifier"></param>
/// <param name="audioParams"></param>
public void PlayGlobalOnMap(MapId map, ResolvedSoundSpecifier specifier, AudioParams? audioParams = null)
{
var msg = new GameGlobalSoundEvent(specifier, audioParams);
var filter = Filter.Empty().AddInMap(map);
RaiseNetworkEvent(msg, filter);
}
public void StopMapEventMusic(MapId map, StationEventMusicType type)
{
var msg = new StopStationEventMusic(type);
var filter = Filter.Empty().AddInMap(map);
RaiseNetworkEvent(msg, filter);
}
public void DispatchMapEventMusic(MapId map, SoundSpecifier sound, StationEventMusicType type)
{
DispatchMapEventMusic(map, _audio.ResolveSound(sound), type);
}
public void DispatchMapEventMusic(MapId map, ResolvedSoundSpecifier specifier, StationEventMusicType type)
{
var audio = AudioParams.Default.WithVolume(-8);
var msg = new StationEventMusicEvent(specifier, type, audio);
var filter = Filter.Empty().AddInMap(map);
RaiseNetworkEvent(msg, filter);
}
}
@@ -6,12 +6,6 @@ namespace Content.Server._DV.CartridgeLoader.Cartridges;
[RegisterComponent, Access(typeof(NanoChatCartridgeSystem))]
public sealed partial class NanoChatCartridgeComponent : Component
{
/// <summary>
/// Station entity to keep track of.
/// </summary>
[DataField]
public EntityUid? Station;
/// <summary>
/// The NanoChat card to keep track of.
/// </summary>
@@ -2,9 +2,8 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.CartridgeLoader;
using Content.Server.Power.Components;
using Content.Server.Radio;
using Content.Server.Station.Systems;
using Content.Server.Radio.EntitySystems;
using Content.Shared.Access.Components;
using Content.Shared.CartridgeLoader;
using Content.Shared.CCVar;
@@ -30,7 +29,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedNanoChatSystem _nanoChat = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly RadioSystem _radio = default!;
private EntityQuery<PdaComponent> _pdaQuery;
private EntityQuery<NanoChatCardComponent> _cardQuery;
@@ -456,9 +455,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
{
// First verify we can send from this device
var channel = _prototype.Index(sender.Comp.RadioChannel);
var sendAttemptEvent = new RadioSendAttemptEvent(channel, sender);
RaiseLocalEvent(ref sendAttemptEvent);
if (sendAttemptEvent.Cancelled)
if (!CanSend(sender))
return (true, new List<Entity<NanoChatCardComponent>>());
var foundRecipients = new List<Entity<NanoChatCardComponent>>();
@@ -487,26 +484,19 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
if (receiverCart.Card != recipient.Owner)
continue;
// Check if devices are on same station/map
var recipientStation = _station.GetOwningStation(receiverUid);
var senderStation = _station.GetOwningStation(sender);
var receiverMapId = Transform(receiverUid).MapID;
var senderMapId = Transform(sender).MapID;
// Both entities must be on a station
if (recipientStation == null || senderStation == null)
// Must be on the same map unless long range is allowed.
if (!channel.LongRange && receiverMapId != senderMapId)
continue;
// Must be on same map/station unless long range allowed
if (!channel.LongRange && recipientStation != senderStation)
continue;
// Needs telecomms
if (!HasActiveServer(senderStation.Value) || !HasActiveServer(recipientStation.Value))
// Check if telecommunications are active on both ends
if (!_radio.HasActiveServer(receiverMapId, receiverCart.RadioChannel) || !_radio.HasActiveServer(senderMapId, sender.Comp.RadioChannel))
continue;
// Check if recipient can receive
var receiveAttemptEv = new RadioReceiveAttemptEvent(channel, sender, receiverUid);
RaiseLocalEvent(ref receiveAttemptEv);
if (receiveAttemptEv.Cancelled)
if (!CanReceive(sender, receiverUid))
continue;
// Found valid cartridge that can receive
@@ -519,21 +509,26 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
}
/// <summary>
/// Checks if there are any active telecomms servers on the given station
/// Tests if a NanoChat cartridge can send messages
/// </summary>
private bool HasActiveServer(EntityUid station)
/// <param name="sender">The NanoChat cartridge trying to send</param>
private bool CanSend(Entity<NanoChatCartridgeComponent> sender)
{
// I have no idea why this isn't public in the RadioSystem
var query =
EntityQueryEnumerator<TelecomServerComponent, EncryptionKeyHolderComponent, ApcPowerReceiverComponent>();
var sendAttemptEvent = new RadioSendAttemptEvent(_prototype.Index(sender.Comp.RadioChannel), sender);
RaiseLocalEvent(ref sendAttemptEvent);
return !sendAttemptEvent.Cancelled;
}
while (query.MoveNext(out var uid, out _, out _, out var power))
{
if (_station.GetOwningStation(uid) == station && power.Powered)
return true;
}
return false;
/// <summary>
/// Tests if a receiver can receive from a given NanoChat cartridge
/// </summary>
/// <param name="sender">The NanoChat cartridge trying to send</param>
/// <param name="receiver">The receiver cartridge trying to receive</param>
private bool CanReceive(Entity<NanoChatCartridgeComponent> sender, EntityUid receiver)
{
var receiveAttemptEv = new RadioReceiveAttemptEvent(_prototype.Index(sender.Comp.RadioChannel), sender, receiver);
RaiseLocalEvent(ref receiveAttemptEv);
return !receiveAttemptEv.Cancelled;
}
/// <summary>
@@ -688,16 +683,15 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
private void UpdateUI(Entity<NanoChatCartridgeComponent> ent, EntityUid loader)
{
List<NanoChatRecipient>? contacts;
if (_station.GetOwningStation(loader) is { } station)
{
ent.Comp.Station = station;
if (CanSend(ent) && _radio.HasActiveServer(Transform(ent).MapID, ent.Comp.RadioChannel))
{
contacts = [];
var query = AllEntityQuery<NanoChatCardComponent, IdCardComponent>();
while (query.MoveNext(out var entityId, out var nanoChatCard, out var idCardComponent))
{
if (nanoChatCard.ListNumber && nanoChatCard.Number is uint nanoChatNumber && idCardComponent.FullName is string fullName && _station.GetOwningStation(entityId) == station)
if (nanoChatCard.ListNumber && nanoChatCard.Number is uint nanoChatNumber && idCardComponent.FullName is string fullName)
{
contacts.Add(new NanoChatRecipient(nanoChatNumber, fullName));
}
@@ -706,7 +700,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
var borgQuery = AllEntityQuery<NanoChatCardComponent, BorgChassisComponent>();
while (borgQuery.MoveNext(out var borgId, out var borgChatCard, out var _))
{
if (borgChatCard.ListNumber && borgChatCard.Number is uint nanoChatNumber && _station.GetOwningStation(borgId) == station)
if (borgChatCard.ListNumber && borgChatCard.Number is uint nanoChatNumber)
{
contacts.Add(new NanoChatRecipient(nanoChatNumber, MetaData(borgId).EntityName));
}
@@ -715,7 +709,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
var aiQuery = AllEntityQuery<NanoChatCardComponent, StationAiHeldComponent>();
while (aiQuery.MoveNext(out var aiId, out var aiChatCard, out var _))
{
if (aiChatCard.ListNumber && aiChatCard.Number is uint nanoChatNumber && _station.GetOwningStation(aiId) == station)
if (aiChatCard.ListNumber && aiChatCard.Number is uint nanoChatNumber)
{
contacts.Add(new NanoChatRecipient(nanoChatNumber, MetaData(aiId).EntityName));
}
@@ -0,0 +1,63 @@
using Content.Server._DV.CosmicCult.Components;
using Content.Shared._DV.CosmicCult.Components;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.Weapons.Melee;
using Robust.Shared.Prototypes;
namespace Content.Server._DV.CosmicCult.Abilities.Colossus;
public sealed class CosmicColossusBuffsSystem : EntitySystem
{
[Dependency] private readonly DamageableSystem _damage = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MultiplyAttackRateOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleAttackRate);
SubscribeLocalEvent<FlatAttackBonusOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleDamageBonus);
SubscribeLocalEvent<MultiplyCorruptingSpeedOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleCorruptingSpeed);
SubscribeLocalEvent<HealOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleHeal);
}
private void HandleAttackRate(Entity<MultiplyAttackRateOnSupercriticalComponent> ent,
ref CosmicColossusEffigySupercriticalEvent args)
{
if (TryComp<MeleeWeaponComponent>(ent, out var weapon))
{
weapon.AttackRate *= ent.Comp.Multiplier;
}
}
private void HandleDamageBonus(Entity<FlatAttackBonusOnSupercriticalComponent> ent,
ref CosmicColossusEffigySupercriticalEvent args)
{
if (!TryComp<CosmicColossusComponent>(ent, out var colossusComp))
return;
colossusComp.BonusDamage +=
new DamageSpecifier(_proto.Index(ent.Comp.BonusDamageType), ent.Comp.BonusDamage);
}
private void HandleCorruptingSpeed(Entity<MultiplyCorruptingSpeedOnSupercriticalComponent> ent,
ref CosmicColossusEffigySupercriticalEvent args)
{
if (TryComp<CosmicCorruptingComponent>(ent, out var corrupting))
{
corrupting.CorruptionSpeed *= ent.Comp.Multiplier;
}
}
private void HandleHeal(Entity<HealOnSupercriticalComponent> ent, ref CosmicColossusEffigySupercriticalEvent args)
{
if (TryComp<DamageableComponent>(ent, out var damageable))
{
_damage.TryChangeDamage(ent.Owner,
-damageable.Damage * Math.Abs(ent.Comp.DamageFractionHealed), // just in case someone sets a negative value in YML
true);
}
}
}
@@ -0,0 +1,8 @@
using Content.Server._DV.CosmicCult.Components;
namespace Content.Server._DV.CosmicCult.Abilities.Colossus;
[ByRefEvent]
public readonly record struct CosmicColossusEffigySupercriticalEvent(
Entity<CosmicEffigyComponent> Effigy
);
@@ -1,15 +1,24 @@
using System.Numerics;
using Content.Server._DV.CosmicCult.Abilities.Colossus;
using Content.Server._DV.CosmicCult.Components;
using Content.Server.Actions;
using Content.Server.Chat.Managers;
using Content.Server.Objectives.Components;
using Content.Server.Objectives.Systems;
using Content.Server.Popups;
using Content.Shared._DV.CosmicCult;
using Content.Shared._DV.CosmicCult.Components;
using Content.Shared.Anomaly.Components;
using Content.Shared.Maps;
using Content.Shared.Mind;
using Content.Shared.Popups;
using Content.Shared.Warps;
using Robust.Server.Audio;
using Robust.Server.Player;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Content.Server._DV.CosmicCult.Abilities;
@@ -23,12 +32,93 @@ public sealed class CosmicEffigySystem : EntitySystem
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly CosmicCultObjectiveSystem _cultObjective = default!;
[Dependency] private readonly IGameTiming _time = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IPlayerManager _player = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CosmicColossusComponent, EventCosmicColossusEffigy>(OnColossusEffigy);
SubscribeLocalEvent<CosmicEffigyComponent, AnomalySupercriticalEvent>(OnSupercritical);
SubscribeLocalEvent<CosmicEffigyComponent, AnomalyShutdownEvent>(OnAnomShutdown);
}
private void OnAnomShutdown(Entity<CosmicEffigyComponent> ent, ref AnomalyShutdownEvent args)
{
if (args.Forced || args.Supercritical || !Exists(ent.Comp.Colossus) || !TryComp<CosmicColossusComponent>(ent.Comp.Colossus, out var colossusComp))
return;
colossusComp.DeathTimer = _time.CurTime;
colossusComp.Timed = true;
}
private void OnSupercritical(Entity<CosmicEffigyComponent> ent, ref AnomalySupercriticalEvent args)
{
if (!Exists(ent.Comp.Colossus)
|| !TryComp<CosmicColossusComponent>(ent.Comp.Colossus, out var colossusComp)
|| !_mind.TryGetMind(ent.Comp.Colossus.Value, out _, out var mind))
return;
var colossus = ent.Comp.Colossus.Value;
var ev = new CosmicColossusEffigySupercriticalEvent(ent);
RaiseLocalEvent(colossus, ref ev);
var transform = Transform(ent.Comp.Colossus.Value);
Spawn(colossusComp.BuffVfx, transform.Coordinates);
if (colossusComp.CompletedEffigies == 0)
{
_audio.PlayStatic(colossusComp.ReawakenSfx,
Filter.BroadcastMap(transform.MapID),
transform.Coordinates,
true);
}
else
{
_audio.PlayPvs(colossusComp.ReawakenSfx, ent);
}
colossusComp.CompletedEffigies += 1;
if (colossusComp.CompletedEffigies >= colossusComp.MaxEffigies)
{
colossusComp.Timed = false;
_popup.PopupEntity(Loc.GetString("colossus-buff-final-popup"), colossus, PopupType.Large);
return;
}
_popup.PopupEntity(Loc.GetString("colossus-buff-popup"), colossus, PopupType.Large);
var objIndex = mind.Objectives.FindIndex(HasComp<CosmicEffigyConditionComponent>);
if (objIndex == -1 ||
!TryComp<CosmicEffigyConditionComponent>(mind.Objectives[objIndex], out var conditionComp))
{
Log.Error($"Failed to find effigy objective on {ToPrettyString(colossus)}!");
return;
}
var objective = mind.Objectives[objIndex];
if (_cultObjective.RandomizeEffigyTarget(objective, conditionComp, setDescription: true) is not {} nextTarget)
{
Log.Error("Failed to randomize effigy objective location!");
return;
}
if (mind.UserId is { } userId)
{
_chat.DispatchServerMessage(_player.GetSessionById(userId), Loc.GetString("colossus-next-target", ("location", nextTarget)));
}
_codeCondition.SetCompleted(objective, false);
colossusComp.EffigyPlaceActionEntity = _actions.AddAction(colossus, colossusComp.EffigyPlaceAction);
colossusComp.DeathTimer = _time.CurTime + colossusComp.DeathWaitEffigy;
colossusComp.Timed = true;
}
private void OnColossusEffigy(Entity<CosmicColossusComponent> ent, ref EventCosmicColossusEffigy args)
@@ -38,8 +128,16 @@ public sealed class CosmicEffigySystem : EntitySystem
_actions.RemoveAction(ent.Owner, ent.Comp.EffigyPlaceActionEntity);
_codeCondition.SetCompleted(ent.Owner, ent.Comp.EffigyObjective);
Spawn(ent.Comp.EffigyPrototype, pos);
var effigy = Spawn(ent.Comp.EffigyPrototype, pos);
ent.Comp.Timed = false;
if (!TryComp<CosmicEffigyComponent>(effigy, out var effigyComp))
{
Log.Error("Colossus tried to place Effigy prototype missing CosmicEffigyComponent!");
return;
}
effigyComp.Colossus = ent.Owner;
}
private bool VerifyPlacement(Entity<CosmicColossusComponent> ent, out EntityCoordinates outPos)
@@ -0,0 +1,56 @@
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server._DV.CosmicCult.Components;
/// <summary>
/// The owning colossus's attack rate will be multiplied by
/// the given multiplier once its effigy goes supercritical.
/// Component meant to be applied to Cosmic Colossus.
/// </summary>
[RegisterComponent]
public sealed partial class MultiplyAttackRateOnSupercriticalComponent : Component
{
[DataField]
public float Multiplier = 1.1f;
}
/// <summary>
/// The owning colossus's corrupting speed will be multiplied by
/// the given multiplier once its effigy goes supercritical.
/// Component meant to be applied to Cosmic Colossus.
/// </summary>
[RegisterComponent]
public sealed partial class MultiplyCorruptingSpeedOnSupercriticalComponent : Component
{
[DataField]
public float Multiplier = 0.9f;
}
/// <summary>
/// The owning colossus will receive a flat bonus to
/// its melee attack damage once its effigy goes supercritical.
/// Component meant to be applied to Cosmic Colossus.
/// </summary>
[RegisterComponent]
public sealed partial class FlatAttackBonusOnSupercriticalComponent : Component
{
[DataField]
public FixedPoint2 BonusDamage = 10;
[DataField]
public ProtoId<DamageTypePrototype> BonusDamageType = "Blunt";
}
/// <summary>
/// The owning colossus will be healed for `-CurrentDamage * Abs(DamageFractionHealed)` damage
/// once its effigy goes supercritical.
/// Component meant to be applied to Cosmic Colossus.
/// </summary>
[RegisterComponent]
public sealed partial class HealOnSupercriticalComponent : Component
{
[DataField]
public float DamageFractionHealed = 1.0f;
}
@@ -1,3 +1,4 @@
using Content.Server._DV.CosmicCult.Abilities.Colossus;
using Content.Server._DV.CosmicCult.EntitySystems;
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
@@ -5,7 +6,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server._DV.CosmicCult.Components;
[RegisterComponent, Access(typeof(CosmicCorruptingSystem))]
[RegisterComponent, Access(typeof(CosmicCorruptingSystem), typeof(CosmicColossusBuffsSystem))]
[AutoGenerateComponentPause]
public sealed partial class CosmicCorruptingComponent : Component
{
@@ -0,0 +1,19 @@
using Content.Server._DV.CosmicCult.Abilities;
using Content.Server._DV.CosmicCult.EntitySystems;
namespace Content.Server._DV.CosmicCult.Components;
/// <summary>
/// Component for cosmic effigy anomalies, spawned by Cosmic Colossi.
/// </summary>
/// <seelso cref="CosmicEffigySystem"/>.
/// <seelso cref="CosmicColossusSystem"/>.
[RegisterComponent]
public sealed partial class CosmicEffigyComponent : Component
{
/// <summary>
/// The colossus that placed this effigy.
/// </summary>
[DataField]
public EntityUid? Colossus;
}
@@ -35,6 +35,12 @@ public sealed class CosmicCultObjectiveSystem : EntitySystem
if (args.Cancelled || !_roles.MindHasRole<CosmicColossusRoleComponent>(args.MindId))
return;
if (RandomizeEffigyTarget(uid, comp) is null)
args.Cancelled = true;
}
public string? RandomizeEffigyTarget(EntityUid uid, CosmicEffigyConditionComponent comp, bool setDescription = false)
{
var warps = new List<EntityUid>();
var query = EntityQueryEnumerator<WarpPointComponent>();
var effigyBlacklist = comp.Blacklist;
@@ -51,10 +57,22 @@ public sealed class CosmicCultObjectiveSystem : EntitySystem
if (warps.Count <= 0)
{
args.Cancelled = true;
return;
return null;
}
comp.EffigyTarget = _random.Pick(warps);
var newWarp = _random.Pick(warps);
var warpComp = Comp<WarpPointComponent>(newWarp);
comp.EffigyTarget = newWarp;
if (setDescription)
{
_metaData.SetEntityDescription(uid,
warpComp.Location != null
? Loc.GetString("objective-condition-effigy", ("location", warpComp.Location))
: Loc.GetString("objective-condition-effigy-no-target"));
}
return warpComp.Location;
}
private void OnEffigyAfterAssign(EntityUid uid, CosmicEffigyConditionComponent comp, ref ObjectiveAfterAssignEvent args)
@@ -521,7 +521,7 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
{
finComp.CurrentState = FinaleState.Unavailable;
_popup.PopupCoordinates(Loc.GetString("cosmiccult-monument-powerdown"), Transform(gameruleMonument).Coordinates, PopupType.Large);
_sound.StopStationEventMusic(gameruleMonument, StationEventMusicType.CosmicCult);
_sound.StopGlobalEventMusic(StationEventMusicType.CosmicCult);
_monument.UpdateMonumentAppearance(gameruleMonument, false);
}
@@ -82,11 +82,9 @@ public sealed partial class CosmicCultSystem : SharedCosmicCultSystem
_corrupting.SetCorruptionTime((uid, corruptingComp), TimeSpan.FromSeconds(1));
_appearance.SetData(uid, MonumentVisuals.FinaleReached, 2);
_sound.DispatchStationEventMusic(uid, comp.SelectedSong, StationEventMusicType.CosmicCult);
_chatSystem.DispatchStationAnnouncement(uid,
Loc.GetString("cosmiccult-finale-location", ("location", indicatedLocation)),
null, false, null,
Color.FromHex("#cae8e8"));
_sound.DispatchGlobalEventMusic(comp.SelectedSong, StationEventMusicType.CosmicCult);
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("cosmiccult-finale-location", ("location", indicatedLocation)),
null, false, null, Color.FromHex("#cae8e8"));
var stationUid = _station.GetStationInMap(Transform(uid).MapID);
if (stationUid != null)
@@ -128,8 +126,8 @@ public sealed partial class CosmicCultSystem : SharedCosmicCultSystem
if (stationUid != null)
_alert.SetLevel(stationUid.Value, "green", true, true, true);
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(comp.CancelEventSound));
_sound.StopStationEventMusic(uid, StationEventMusicType.CosmicCult);
_sound.PlayGlobal(_audio.ResolveSound(comp.CancelEventSound));
_sound.StopGlobalEventMusic(StationEventMusicType.CosmicCult);
uid.Comp.CurrentState = FinaleState.ReadyFinale;
if (TryComp<CosmicCorruptingComponent>(uid, out var corruptingComp))
@@ -8,6 +8,7 @@ using Content.Shared.Mind;
using Content.Shared.Roles;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -25,6 +26,7 @@ public sealed class CosmicChantrySystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedRoleSystem _role = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly NavMapSystem _navMap = default!;
/// <summary>
@@ -79,11 +81,11 @@ public sealed class CosmicChantrySystem : EntitySystem
comp.SpawnTimer = _timing.CurTime + comp.SpawningTime;
comp.CountdownTimer = _timing.CurTime + comp.EventTime;
_sound.PlayGlobalOnStation(ent, _audio.ResolveSound(comp.ChantryAlarm));
_chatSystem.DispatchStationAnnouncement(ent,
Loc.GetString("cosmiccult-chantry-location", ("location", indicatedLocation)),
null, false, null,
Color.FromHex("#cae8e8"));
var targetMap = _transform.GetMapId(ent.Owner);
_sound.PlayGlobalOnMap(targetMap, _audio.ResolveSound(comp.ChantryAlarm));
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("cosmiccult-chantry-location",
("location", indicatedLocation)), null, false, null, Color.FromHex("#cae8e8"));
if (_mind.TryGetMind(comp.InternalVictim, out _, out var mind))
mind.PreventGhosting = true;
@@ -13,6 +13,7 @@ using Content.Shared.Popups;
using Content.Shared.Station.Components;
using Content.Shared.Throwing;
using Content.Shared.Warps;
using Content.Shared.Weapons.Melee.Events;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Physics.Components;
@@ -41,6 +42,7 @@ public sealed class CosmicColossusSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<CosmicColossusComponent, ComponentInit>(OnSpawn);
SubscribeLocalEvent<CosmicColossusComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<CosmicColossusComponent, MeleeHitEvent>(OnMeleeHit);
}
public override void Update(float frameTime)
@@ -82,7 +84,7 @@ public sealed class CosmicColossusSystem : EntitySystem
private void OnSpawn(Entity<CosmicColossusComponent> ent, ref ComponentInit args) // I WANT THIS BIG GUY HURLED TOWARDS THE STATION
{
ent.Comp.DeathTimer = _timing.CurTime + ent.Comp.DeathWait;
ent.Comp.DeathTimer = _timing.CurTime + ent.Comp.DeathWaitSpawn;
var station = _station.GetStationInMap(Transform(ent).MapID);
if (TryComp<StationDataComponent>(station, out var stationData))
{
@@ -115,4 +117,9 @@ public sealed class CosmicColossusSystem : EntitySystem
RemComp<WarpPointComponent>(ent);
RemComp<CosmicCorruptingComponent>(ent);
}
private void OnMeleeHit(Entity<CosmicColossusComponent> colossus, ref MeleeHitEvent args)
{
args.BonusDamage += colossus.Comp.BonusDamage;
}
}
@@ -79,13 +79,13 @@ public sealed class MonumentSystem : SharedMonumentSystem
{
comp.SongTimer = null;
if (comp.SelectedSong is { } song)
_sound.DispatchStationEventMusic(uid, song, StationEventMusicType.CosmicCult);
_sound.DispatchGlobalEventMusic(song, StationEventMusicType.CosmicCult);
}
if (comp.CurrentState == FinaleState.ActiveFinale && comp.FinaleAnnounceCheck && comp.FinaleTimer - _timing.CurTime < comp.VisualsThreshold)
{
_appearance.SetData(uid, MonumentVisuals.FinaleReached, 3);
_chatSystem.DispatchStationAnnouncement(uid, Loc.GetString("cosmiccult-announce-finale-warning"), null, false, null, Color.FromHex("#cae8e8"));
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("cosmiccult-announce-finale-warning"), null, false, null, Color.FromHex("#cae8e8"));
comp.FinaleAnnounceCheck = false;
}
@@ -1,5 +1,6 @@
using Content.Server._DV.Psionics.Systems;
using Content.Shared._DV.Psionics.Components;
using Content.Shared._DV.Traits.Assorted;
using Content.Shared.Mind;
using Content.Shared.Roles;
using Content.Shared.Roles.Components;
@@ -17,10 +18,12 @@ public sealed partial class ParadoxCloneRuleSystem
private void FilterTargets(HashSet<Entity<MindComponent>> minds)
{
// TODO: use generic IMindFilter
// no picking other antags or non-crew
// no picking other antags or non-crew and entities with no paradox clone trait
minds.RemoveWhere(mind => _role.MindIsAntagonist(mind) ||
!_role.MindHasRole<JobRoleComponent>((mind, mind), out var role) ||
role?.Comp1.JobPrototype == null);
role?.Comp1.JobPrototype == null ||
(mind.Comp.OwnedEntity is { } entity && HasComp<NoParadoxCloneComponent>(entity))
);
}
private void PostClone(EntityUid mob)
@@ -0,0 +1,26 @@
using Content.Server.Administration;
using Content.Shared._DV.IonLaws;
using Content.Shared.Administration;
using Robust.Shared.Random;
using Robust.Shared.Toolshed;
namespace Content.Server._DV.IonLaws;
[ToolshedCommand, AdminCommand(AdminFlags.Fun)]
public sealed class IonLawCommand : ToolshedCommand
{
[Dependency] private readonly IRobustRandom _random = default!;
private IonLawSystem? _ionLaw;
[CommandImplementation("generate")]
public IEnumerable<string> GenerateNames(int count)
{
_ionLaw ??= GetSys<IonLawSystem>();
for (var i = 0; i < count; i++)
{
yield return _ionLaw.GenerateLaw(_random);
}
}
}
@@ -90,6 +90,7 @@ public sealed class DeepFryerSystem : SharedDeepFryerSystem
private void OnPowerChanged(Entity<DeepFryerComponent> ent, ref PowerChangedEvent args)
{
UpdateAppearance(ent);
ResetCookingItemsStartTime(ent);
}
private void OnThrowHitBy(Entity<DeepFryerComponent> ent, ref ThrowHitByEvent args)
@@ -674,4 +675,26 @@ public sealed class DeepFryerSystem : SharedDeepFryerSystem
// Add the split solution to the food
Solution.AddSolution(foodSolutionEnt.Value, transferredSolution);
}
/// <summary>
/// Resets the starttime for cooking items (on power change)
/// </summary>
private void ResetCookingItemsStartTime(Entity<DeepFryerComponent> ent)
{
if (_power.IsPowered(ent.Owner) && !ent.Comp.WasPreviouslyPowered)
{
// Power just came back on - reset all cooking timers
foreach (var itemUid in ent.Comp.CookingItems.Keys.ToList())
{
var cookingItem = ent.Comp.CookingItems[itemUid];
cookingItem.TimeStarted = _timing.CurTime;
ent.Comp.CookingItems[itemUid] = cookingItem;
}
ent.Comp.WasPreviouslyPowered = true;
}
else if (!_power.IsPowered(ent.Owner) && ent.Comp.WasPreviouslyPowered)
{
ent.Comp.WasPreviouslyPowered = false;
}
}
}
@@ -0,0 +1,101 @@
using System.Numerics;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Systems;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Shared._DV.NodeCrawl;
using Content.Shared.Atmos;
using Content.Shared.NodeContainer;
namespace Content.Server._DV.NodeCrawl;
public sealed class NodeCrawlSystem : SharedNodeCrawlSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CrawlableNodeComponent, NodeGroupsRebuilt>(OnNodeGroupsRebuilt);
SubscribeLocalEvent<NodeCrawlerComponent, InhaleLocationEvent>(OnInhaleLocation);
SubscribeLocalEvent<NodeCrawlerComponent, ExhaleLocationEvent>(OnExhaleLocation);
SubscribeLocalEvent<NodeCrawlerComponent, AtmosExposedGetAirEvent>(OnGetAir);
}
private Entity<NodeContainerComponent>? GetNodeContainer(Entity<NodeCrawlerComponent> crawler)
{
if (!TryComp<NodeCrawlerMovementComponent>(crawler.Comp.Mover, out var mover) || mover.Node is not { } node)
return null;
if (!TryComp<NodeContainerComponent>(node, out var nodeContainer))
return null;
return (node, nodeContainer);
}
private GasMixture? GetAir(Entity<NodeCrawlerComponent> crawler)
{
if (GetNodeContainer(crawler) is not { } nodeContainer)
return null;
foreach (var containedNode in nodeContainer.Comp.Nodes.Values)
{
if (containedNode is not PipeNode pipe)
continue;
return pipe.Air;
}
return null;
}
private void OnInhaleLocation(Entity<NodeCrawlerComponent> ent, ref InhaleLocationEvent args)
{
if (GetAir(ent) is not { } air)
return;
args.Gas = air;
}
private void OnExhaleLocation(Entity<NodeCrawlerComponent> ent, ref ExhaleLocationEvent args)
{
if (GetAir(ent) is not { } air)
return;
args.Gas = air;
}
private void OnGetAir(Entity<NodeCrawlerComponent> ent, ref AtmosExposedGetAirEvent args)
{
if (args.Handled || GetAir(ent) is not { } air)
return;
args.Gas = air;
args.Handled = true;
}
private void OnNodeGroupsRebuilt(Entity<CrawlableNodeComponent> ent, ref NodeGroupsRebuilt args)
{
if (!TryComp<NodeContainerComponent>(ent, out var nodeContainer))
return;
ent.Comp.DeadEnd = false;
var set = new HashSet<EntityUid>();
foreach (var node in nodeContainer.Nodes.Values)
{
foreach (var reachable in node.ReachableNodes)
{
set.Add(reachable.Owner);
}
if (node is PipeNode pipeNode &&
node.ReachableNodes.Count != BitOperations.PopCount((uint)pipeNode.CurrentPipeDirection))
{
ent.Comp.DeadEnd = true;
}
}
ent.Comp.ReachableNodes = set;
Dirty(ent);
}
}
@@ -1,212 +0,0 @@
using Content.Server.Cloning;
using Content.Server.Mind;
using Content.Server.Station.Systems;
using Content.Shared._DV.Psionics.Components;
using Content.Shared._DV.Psionics.Components.PsionicPowers;
using Content.Shared._DV.Psionics.Systems.PsionicPowers;
using Content.Shared._DV.Species;
using Content.Shared.Bed.Sleep;
using Content.Shared.Body.Components;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Mobs.Components;
using Content.Shared.Popups;
using Content.Shared.Preferences;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Server.Player;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server._DV.Psionics.Systems.PsionicPowers;
public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly CloningSystem _cloning = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly PvsOverrideSystem _pvsOverride = default!;
// holy initialize performance? but better for it to happen once than the double dict lookup every tick!!
private EntityQuery<FracturedFormBodyComponent> _bodyQuery;
private EntityQuery<SleepingComponent> _sleepingQuery;
public override void Initialize()
{
base.Initialize();
_bodyQuery = GetEntityQuery<FracturedFormBodyComponent>();
_sleepingQuery = GetEntityQuery<SleepingComponent>();
}
protected override void OnPowerInit(Entity<FracturedFormPowerComponent> power, ref MapInitEvent args)
{
base.OnPowerInit(power, ref args);
// The next random swap is between 5 and 20 minutes.
var randomTime = Random.Next(power.Comp.NextSwapMinTime, power.Comp.NextSwapMaxTime);
power.Comp.NextSwap = Timing.CurTime + randomTime;
power.Comp.NextVoluntarySwap = Timing.CurTime + power.Comp.VoluntarySwapCooldown;
// Don't generate a new body if we're already part of a network.
if (HasComp<FracturedFormBodyComponent>(power))
return;
// Don't make bodies if there is no body. This is solely for test fails.
if (!HasComp<BodyComponent>(power))
return;
var bodyComp = AddComp<FracturedFormBodyComponent>(power);
bodyComp.ControllingForm = power.Owner;
power.Comp.Bodies.Add(power);
var body = GenerateForm(power);
// hide the SSD indicator.
if (SsdQuery.TryComp(body, out var ssdComp))
ssdComp.IsSSD = false;
}
private EntityUid GenerateForm(Entity<FracturedFormPowerComponent> original)
{
// Form:
// - Same appearance as original
// - Different apperance, still humanoid
// Equipment:
// - Same as original body
// - Nude and helpless
var xform = Transform(original);
var hasGear = Random.Prob(original.Comp.HasGearChance);
if (Random.Prob(original.Comp.DifferentSpeciesChance) || !_cloning.TryCloning(original, _transform.GetMapCoordinates(original), hasGear ? original.Comp.CopyClothed : original.Comp.CopyNaked, out var newBody)) // Slightly lower chance to copy the original body
{
// Either the dice rolled poorly, or the cloning failed. Either way, make a new body instead. (Or try to)
var validSpecies = new List<ProtoId<SpeciesPrototype>>();
var speciesPrototypes = _prototype.EnumeratePrototypes<SpeciesPrototype>();
foreach (var proto in speciesPrototypes)
{
var speciesEntityPrototype = _prototype.Index<EntityPrototype>(proto.Prototype);
// If they have the PotentialPsionicComponent, they can be psionic.
if (proto.RoundStart && speciesEntityPrototype.TryGetComponent<PotentialPsionicComponent>(out _, Factory) && !SpeciesHiderSystem.IsHidden(proto.ID))
validSpecies.Add(proto.ID);
}
var species = Random.Pick(validSpecies);
var character = HumanoidCharacterProfile.RandomWithSpecies(species);
newBody = _stationSpawning.SpawnPlayerMob(xform.Coordinates, hasGear ? original.Comp.VisitorJob : original.Comp.NakedJob, character, _station.GetOwningStation(original.Owner));
if (newBody is not { } bodyV || Deleted(bodyV))
{
Log.Error($"Failed to create a new body for {ToPrettyString(original)}. This is a bug.");
return EntityUid.Invalid;
}
}
if (newBody is not { } body || Deleted(body))
return default!;
var bodyComp = AddComp<FracturedFormBodyComponent>(body);
original.Comp.Bodies.Add(body);
bodyComp.ControllingForm = original.Owner;
if (_player.TryGetSessionByEntity(original, out var session))
_pvsOverride.AddSessionOverride(body, session);
Dirty(original);
return body;
}
private bool TryGetValidBody(Entity<FracturedFormPowerComponent> psionic, out EntityUid validBody)
{
foreach (var body in psionic.Comp.Bodies)
{
if (!IsValidBody(psionic, body))
continue;
validBody = body;
return true;
}
validBody = default;
return false;
}
private void Swap(Entity<FracturedFormPowerComponent> psionic)
{
if (!TryGetValidBody(psionic, out var targetBody))
return;
_audio.PlayPvs(psionic.Comp.SwapSound, psionic);
// Transfer mind if present
if (MindContainerQuery.TryComp(psionic, out var mindContainer) && mindContainer.Mind.HasValue)
_mind.TransferTo(mindContainer.Mind.Value, targetBody);
// Wake up the new body
Sleeping.TryWaking(targetBody);
// Remove the action.
Action.RemoveAction(psionic.Comp.ActionEntity);
// Create new component on target and copy data
var duplicate = EnsureComp<FracturedFormPowerComponent>(targetBody);
duplicate.Bodies = psionic.Comp.Bodies;
// Update all body references
foreach (var body in duplicate.Bodies)
{
if (_bodyQuery.TryComp(body, out var bodyComp))
bodyComp.ControllingForm = targetBody;
}
if (_player.TryGetSessionByEntity(targetBody, out var session))
{
_pvsOverride.AddSessionOverride(psionic, session);
_pvsOverride.RemoveSessionOverride(targetBody, session);
}
RemCompDeferred<FracturedFormPowerComponent>(psionic);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
List<Entity<FracturedFormPowerComponent>> swapTargets = [];
var entities = EntityQueryEnumerator<FracturedFormPowerComponent, MobStateComponent>();
while (entities.MoveNext(out var uid, out var comp, out var mobState))
{
// Check sleep warning
if (!comp.SleepWarned && Timing.CurTime > comp.NextSwap - comp.WarningTimeBeforeSleep)
{
comp.SleepWarned = true;
Popup.PopupEntity(Loc.GetString("psionic-power-fractured-form-sleepy"), uid, uid, PopupType.LargeCaution);
Chat.TryEmoteWithChat(uid, "Yawn");
}
// Swap check
if ((_sleepingQuery.HasComp(uid) || MobState.IsIncapacitated(uid, mobState)) && Timing.CurTime > comp.NextVoluntarySwap
|| Timing.CurTime > comp.NextSwap)
swapTargets.Add((uid, comp));
}
foreach (var target in swapTargets)
{
Swap(target);
}
// Process bodies
var bodies = EntityQueryEnumerator<FracturedFormBodyComponent, MobStateComponent>();
while (bodies.MoveNext(out var uid, out var comp, out var mobState))
{
// Put to sleep if no sleeping component and no mind
if (!_sleepingQuery.HasComp(uid) && !_mind.GetMind(uid).HasValue && !FracturedQuery.HasComp(uid))
Sleeping.TrySleeping((uid, mobState));
// Cleanup invalid bodies
if (!comp.ControllingForm.IsValid()
|| Deleted(comp.ControllingForm)
|| !FracturedQuery.HasComp(comp.ControllingForm))
{
RemCompDeferred<FracturedFormBodyComponent>(uid);
}
}
}
}
@@ -0,0 +1,39 @@
using Content.Server.Wires;
using Content.Shared._DV.Silicons.Borgs;
using Content.Shared._DV.Silicons;
using Content.Shared.Wires;
namespace Content.Server._DV.Silicons;
/// <summary>
/// Controls whether the cyborg's ID chip slot is active
/// </summary>
public sealed partial class BorgIdChipWireAction : ComponentWireAction<IdChipSlotComponent>
{
public override string Name { get; set; } = "wire-name-borg-id-chip";
public override Color Color { get; set; } = Color.Thistle;
public override object StatusKey => BorgWireActionKey.ChipKey;
public override StatusLightState? GetLightState(Wire wire, IdChipSlotComponent component)
{
return component.Active ? StatusLightState.On : StatusLightState.Off;
}
public override bool Cut(EntityUid user, Wire wire, IdChipSlotComponent component)
{
EntityManager.System<IdChipSlotSystem>()
.SetActive((wire.Owner, component), false);
return true;
}
public override bool Mend(EntityUid user, Wire wire, IdChipSlotComponent component)
{
EntityManager.System<IdChipSlotSystem>()
.SetActive((wire.Owner, component), true);
return true;
}
public override void Pulse(EntityUid user, Wire wire, IdChipSlotComponent component)
{
}
}
@@ -0,0 +1,40 @@
using Content.Server.Silicons.Borgs;
using Content.Server.Wires;
using Content.Shared._DV.Silicons;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Wires;
namespace Content.Server._DV.Silicons;
/// <summary>
/// Controls whether the cyborg's transponder is active
/// </summary>
public sealed partial class BorgTransponderWireAction : ComponentWireAction<BorgTransponderComponent>
{
public override string Name { get; set; } = "wire-name-borg-transponder";
public override Color Color { get; set; } = Color.YellowGreen;
public override object StatusKey => BorgWireActionKey.TransponderKey;
public override StatusLightState? GetLightState(Wire wire, BorgTransponderComponent component)
{
return component.Active ? StatusLightState.On : StatusLightState.Off;
}
public override bool Cut(EntityUid user, Wire wire, BorgTransponderComponent component)
{
EntityManager.System<BorgSystem>()
.SetTransponderActive((wire.Owner, component), false);
return true;
}
public override bool Mend(EntityUid user, Wire wire, BorgTransponderComponent component)
{
EntityManager.System<BorgSystem>()
.SetTransponderActive((wire.Owner, component), true);
return true;
}
public override void Pulse(EntityUid user, Wire wire, BorgTransponderComponent component)
{
}
}

Some files were not shown because too many files have changed in this diff Show More