Compare commits

...
Author SHA1 Message Date
Delta-V bot ebc0340fc9 Automatic changelog update 2026-09-14 00:20:01 +02:00
KOTOB e8b6c42de4 Fix/Lower Asakim Weight (#6455)
* whoop

* nuke from antag table

* we love webedits in this household

Signed-off-by: KOTOB <59124164+kotobdev@users.noreply.github.com>

---------

Signed-off-by: KOTOB <59124164+kotobdev@users.noreply.github.com>
2026-09-13 15:19:39 -07:00
github-actions[bot]andDeltaV-Bot 5e0d668f60 Update Credits (#6505)
Co-authored-by: DeltaV-Bot <github@deltav.gay>
2026-09-13 04:36:32 +02:00
Delta-V bot 2c1084da78 Automatic changelog update 2026-09-11 01:40:04 +02:00
BarryNorfolk 9304aad604 Swap emergency shuttles to use NTNet (#6497) 2026-09-10 19:39:43 -04:00
Delta-V bot 5132e64a0f Automatic changelog update 2026-09-09 17:49:38 +02:00
SumofThreePartsandpre-commit-ci[bot] d2bf1e9a39 Adding the Anaconda (#6458)
* Creating the Anaconda

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

* hashtag balance

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

* Adding the anaconda sprites

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

* Delete anaconda directory

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

* Adding the anaconda sprites

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

* whoops!

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

* yuuuup. adding tha andaconda sprites. again

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

* doing the thing!!!

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

* part one of adding it to uplink 

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

* whoops!

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

* guh

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

* part 2 of adding anaconda to uplink

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

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

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

* doing the thang 

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

* adding the thang i was told to

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

* adding it but for real

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

* oh yeah the description

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

* grrrr grrr not correct

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

* guh

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

---------

Signed-off-by: SumofThreeParts <its.a.joke1000@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-09-09 15:49:17 +00:00
Delta-V bot 865d254cd4 Automatic changelog update 2026-09-09 16:55:05 +02:00
Astra 29ec2e659f Give shadekin hair (#6453)
Add shadekin hair
2026-09-09 09:54:44 -05:00
Delta-V bot 690f64d592 Automatic changelog update 2026-09-09 16:18:58 +02:00
pixelStrife b6e018eb18 Namespace drink metabolites (#6486)
* switched Silencer parent to BaseDrink to revert to pre-Metabolite behavior (non-alcoholic)

* Bring namespace drinks in line with upstream, using the new(-ish) Metabolites system in place of the AdjustReagent effect
2026-09-09 09:18:38 -05:00
Delta-V bot 8bcddf4333 Automatic changelog update 2026-09-09 16:15:50 +02:00
34940ecde7 The Pitbull Update of Doom and Despair (#6449)
* it begins.

* THEYRE IN THE VENTS

* Update pitbull.yml

* I AM THE ONE WHO COLLECTS THE PITBULLS

* Fuck you

* THAT IS BULLSHIT BLAZING

* Throw a small child into upper orbit

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

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

* 100 pitbull names

* changed one

* sorry one last pitbull name

* Update pitbull_names.yml

---------

Co-authored-by: Solaris <63768197+Solaris7518@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-09-09 09:15:30 -05:00
Delta-V bot dafd97e6e1 Automatic changelog update 2026-09-09 16:07:57 +02:00
einknusprigestoastandpre-commit-ci[bot] 593d35903d ERT Gamma changes + gun addition - The return of the fake balancing. (#6239)
* Added Jackdaw and Kammerer

* ERT hardsuit change

* Jackdaw changes

* [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-09-09 09:07:34 -05:00
Delta-V bot 1856f65a18 Automatic changelog update 2026-09-07 02:34:02 +02:00
brainslushie 9066c4af7f Surplus boots - cosmetic jackboots (#6472)
* +surplus boots

* surplus boots in vendor
2026-09-06 21:33:42 -03:00
Stxcking cf9a10af4e PORT: Thaven Markings and Organs from Macrocosm (#6162)
* Can't go wrong with Sprites

* .ftl and .yml files

* displacement + parts + organs

* marking and displacement yaml

* does arent real...yet

* Imp dupe nuke

* go my human+

* no shaders :<

* whoopsie

idk how
2026-09-06 21:18:44 -03:00
Delta-V bot 2d7af5622f Automatic changelog update 2026-09-07 02:17:42 +02:00
nlaky 974f5ff5fa Vulp alt tail wag + fade tip fix (#6480)
* Add wag markings for vulp alt tail

* Fix tip markings used for fade tails
2026-09-06 21:17:22 -03:00
Delta-V bot 88dc8af9a4 Automatic changelog update 2026-09-07 01:36:12 +02:00
BriBrooo 7c33759cbe Update Botanicals.xml (#6483)
Fixes a duplicate entry in the botanical reagent guidebook.
2026-09-06 20:35:52 -03:00
Delta-V bot baf009af3a Automatic changelog update 2026-09-05 16:03:30 +02:00
Field Command b59dfa8991 Central Command remake! (#6198)
* New Central Command

* Me forget

* typos and name change

* Fix and typo

* More polish

* Requested Fixes
2026-09-05 09:03:08 -05:00
github-actions[bot]andDeltaV-Bot e43a587ede Update Credits (#6465)
Co-authored-by: DeltaV-Bot <github@deltav.gay>
2026-08-30 05:00:27 +02:00
Delta-V bot 00d77213c6 Automatic changelog update 2026-08-29 01:54:53 +02:00
VapeandVanessa 389a616138 Ramping Scheduler adjustments and changes (#6440)
* Update roundstart.yml

* updakes

* Rnd 2 fight

* Changes done

* Apply suggestion from @ShepardToTheStars

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

* Apply batched suggestions from code review

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
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-08-28 23:54:33 +00:00
Delta-V bot 07b029ff2b Automatic changelog update 2026-08-28 18:38:29 +02:00
Carol 1043b40246 Added caseless shotgun shells (#6457)
added caseless shotgun shells
2026-08-28 11:38:08 -05:00
Delta-V bot d0385ab8bb Automatic changelog update 2026-08-27 21:26:28 +02:00
03347ed416 Inverse Labels and Handlabeler menu (#6432)
Merge pull request #1120 from oooielp/label-invert (#783)

Co-authored-by: Khaomi <46065349+Khaomi@users.noreply.github.com>
Co-authored-by: Superlagg <sprambersonson@gmail.com>
2026-08-27 14:26:07 -05:00
Delta-V bot e9f0148287 Automatic changelog update 2026-08-27 21:24:33 +02:00
SumofThreePartsandVanessa 0f7131d9e7 Buffing the Hushpup (#6438)
* Buffing the Hushpup

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

* Doing the thing

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Signed-off-by: SumofThreeParts <its.a.joke1000@gmail.com>

* doing the thang

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

* Changing it back

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

---------

Signed-off-by: SumofThreeParts <its.a.joke1000@gmail.com>
Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-08-27 16:24:13 -03:00
Delta-V bot bf9b42c76c Automatic changelog update 2026-08-27 20:38:01 +02:00
a635429d3e Communications console & screens overhaul (#5954)
* i'm going insane from screens

* some wiring

* screemes

* holy shit adhd lock-in

* incomplete

* its FUCKING DONE

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

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

* zeta

* mrrrp

* test fix

* fix arrivals

* fix misnetwork

* Fix iteration outline. Fixed all headers showing if all options where hidden. Added access requirement to keycard auth mechnism. Added countdown. Fixed ninja and fugitive. Fixed AI and aghost.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Vanessa <vanessalouwagie@gmail.com>
2026-08-27 13:37:40 -05:00
Delta-V bot bd1e4224cd Automatic changelog update 2026-08-27 20:32:18 +02:00
Vanessa 568621858e Trees are now actually rooted and cannot move. (#6456) 2026-08-27 13:31:58 -05:00
Delta-V bot 3df2209227 Automatic changelog update 2026-08-27 16:29:59 +02:00
1da052e36f Letting the patos be purchased (#6361)
* Making Patos a thing you can buy

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

* Update armory.yml

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

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

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

* Update cargo_armory.yml

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

* Update armory.yml

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

* Update armory.yml

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

* Update armory.yml

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

* Update magazine amount in armory.yml

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

* thankies

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Signed-off-by: SumofThreeParts <its.a.joke1000@gmail.com>

* thankies 2

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Signed-off-by: SumofThreeParts <its.a.joke1000@gmail.com>

---------

Signed-off-by: SumofThreeParts <its.a.joke1000@gmail.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-08-27 11:29:39 -03:00
Delta-V bot 66b95a46ed Automatic changelog update 2026-08-26 10:35:09 +02:00
Vanessa 92ba9edeee Small Midround Antag Balance Pass (#6410)
* Nerfed skia regen.

* Nerfed ninja dash

* Fix ninja agent id

* Skia tweak

* Add reveal to rev attack

* Apply suggestion from @ShepardToTheStars

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

---------

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-08-26 10:34:48 +02:00
Delta-V bot 680092719f Automatic changelog update 2026-08-24 18:23:27 +02:00
Vanessa 1afbadd404 Fix Health Analyzer Not Showing Traits (#6446)
* Fixed some alerts not showing in the health analyzer

* This is upstream now so removing comments
2026-08-24 11:23:07 -05:00
Delta-V bot 2c190cf2f2 Automatic changelog update 2026-08-24 17:14:11 +02:00
Vanessa 17d798a3e8 Stole Thief Gloves from the Uplink (#6425)
Stole thief gloves from the uplink.
2026-08-24 10:13:51 -05:00
Delta-V bot 287714d87b Automatic changelog update 2026-08-23 17:00:54 +02:00
Vanessa 029aaf8127 Fix Mothroach Grenades (#6447)
Fix mothroach grenades
2026-08-23 10:00:34 -05:00
Delta-V bot bbf1bec942 Automatic changelog update 2026-08-23 14:57:21 +02:00
Vanessaandpre-commit-ci[bot] bd34a94587 Embeddable Syndicate and Hitman Business Cards (#6439)
* make business cards embed

* [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-08-23 07:57:01 -05:00
Delta-V bot 86c54228f8 Automatic changelog update 2026-08-23 14:56:33 +02:00
Vanessa c66bf7c249 Remove coords for Arms Dealers, Asakim, and SRN (#6441)
* Remove coords for arms dealers, asakim, and SRN

* Also set a bunch of other syndicate uniforms to coords off

* forgot two
2026-08-23 07:56:12 -05:00
github-actions[bot]andDeltaV-Bot fd0f01e49a Update Credits (#6444)
Co-authored-by: DeltaV-Bot <github@deltav.gay>
2026-08-23 02:56:31 +02:00
Delta-V bot cfddb40c2f Automatic changelog update 2026-08-22 18:18:52 +02:00
DisposableCrewmember42 19d4eb8489 feat: Unorbitable component (#6435)
* feat: Unorbitable component

* feat: stealthmin adds/removes Unorbitable
2026-08-22 11:18:31 -05:00
Delta-V bot 9bb7925bd2 Automatic changelog update 2026-08-21 20:29:57 +02:00
Radezolid e340ddcec9 Kill combat medkits free storage (#6413)
* Kill!!

* Medkit size

* Removed from printable list + made resistant to explosions

* Make the test not angry at me

* I can't read

* I can't read v2
2026-08-21 13:29:37 -05:00
Delta-V bot e0b6261454 Automatic changelog update 2026-08-21 04:39:06 +02:00
Radezolid d741daca19 Syndie shuttles fixes + interdyne locker + new syndie evac pod (#6094)
* Interdyne locker

* Haibu

* strongbox

* synth

* Census

* hornet

* old evac pod to adming folder

* Add a syndie door and fix census

* Apply requested changes

---------

Signed-off-by: Radezolid <snappednexus@gmail.com>
2026-08-20 21:38:45 -05:00
Vanessa 970c8d71b5 Fix NodeCrawling ClientSide Errors (#6429)
fix some clientside errors with nodecrawling
2026-08-20 12:28:47 -05:00
Delta-V bot b6801b2fe5 Automatic changelog update 2026-08-20 19:20:06 +02:00
Vanessa 5cd26f877e Fix Hitman Targeting Marked as Protected (#6426)
Fix objective immunity.
2026-08-20 12:19:46 -05:00
Minerva 105ad32f98 Minor indentation change on pull request template (#5634) 2026-08-20 10:18:59 -05:00
Delta-V bot e3566a4cc9 Automatic changelog update 2026-08-20 16:25:25 +02:00
Vanessa 5e63549b8c Locale Ops (#6427)
* Fixed missing locale strings.

* Meant to revert this.
2026-08-20 09:24:59 -05:00
Delta-V bot 5b08961c35 Automatic changelog update 2026-08-18 20:15:03 +02:00
3c6ab7b676 Reduced the slowdown of the Advanced Riot Control Suit to 15% (#6353)
* ARCS slowdown 15% explosion resist 40%

* Comment on the explosion resistance of the ARCS

* Replaces the ARCS with the upstream riot suit, riot suit now has 40% explosion resist

* Apply suggestion from @ShepardToTheStars

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

---------

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Co-authored-by: john <sadadawd3@gmail.com>
Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-08-18 18:14:43 +00:00
23e55c9c40 Fix lack of justice access on AA (#6408)
* Add access, uncomment brig

* borgs

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

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

---------

Co-authored-by: Vanessa <vanessalouwagie@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-18 18:08:39 +00:00
Delta-V bot ffa442f844 Automatic changelog update 2026-08-18 19:51:56 +02:00
CorynandVanessa 6d75455dd1 Add Vulp Scream Sound Collection (#6348)
* Add Vulp Scream Sound Collection

* Fix mistake

---------

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
2026-08-18 12:51:36 -05:00
Delta-V bot 0d89320364 Automatic changelog update 2026-08-18 18:39:28 +02:00
Theo 1984cc2118 hristov rework (#5905)
* Add deviation, slowdown, projectile slower, bipod and stats

* add nt-3 piercing remove bullet slow

* fixed the piercing stuff

* ok
2026-08-18 11:39:07 -05:00
Delta-V bot 3248ee4715 Automatic changelog update 2026-08-17 22:26:29 +02:00
Vanessaandpre-commit-ci[bot] b2b08e698e Ramping Scheduler Fixes / Convert to Survival Fixes (#6416)
* Fixed nukie behavior check firing early

* Fixed ZombieOutbreak adding ramping schedulers

* RampingSchedulers now look at when they were added for keypoint times

* Xenoborgs no longer has a RampingScheduler roundstart.

* Added number of cuffed hands check

* Use GameRule activation time.

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

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

* silly compiler flag

* Apply suggestions from code review

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

* Fix other locale usages

* silly me

* PR feedback

---------

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>
2026-08-17 15:26:09 -05:00
Delta-V bot 3a612b9450 Automatic changelog update 2026-08-17 21:14:50 +02:00
Vanessaandpre-commit-ci[bot] 9746e92479 Adjust Traitor Objectives if 30TC (#6389)
* Adjust traitor objectives. More objectives. Increase objective variance a bit. Add new CMO steal target.

* Remove stealcondition blacklist from DAGD so its not just muderboning

* locale

* Fix DAGD steal objective again. Tweak LO steal objectives

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

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

* Updated super surplus

* Added migration.

* Locale tweak

* [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-08-17 14:14:31 -05:00
Delta-V bot 9d723af15d Automatic changelog update 2026-08-17 19:38:23 +02:00
Vanessa 8e925a02b9 Fix Kitsune Getting Posioned by Raw Meat (#6422)
fix kitsune
2026-08-17 19:38:02 +02:00
Delta-V bot d898300c5c Automatic changelog update 2026-08-16 17:05:00 +02:00
Vanessa 4294df8fd7 Fix Holy Water (#6421)
fix holy water
2026-08-16 10:04:40 -05:00
Delta-V bot 365209174b Automatic changelog update 2026-08-16 16:08:07 +02:00
Sir Warock 0c852409b3 Prioritize ejecting handgun from secbelt (#6418)
one commit
2026-08-16 09:07:45 -05:00
github-actions[bot]andDeltaV-Bot 9fb8bab84e Update Credits (#6420)
Co-authored-by: DeltaV-Bot <github@deltav.gay>
2026-08-16 02:55:39 +02:00
Delta-V bot c7e1c980b6 Automatic changelog update 2026-08-14 06:31:17 +02:00
Stop-Signs 0516e97461 Disallow evil fucked up brainwashing (#6414) 2026-08-13 23:30:57 -05:00
Delta-V bot fd5b16abb3 Automatic changelog update 2026-08-13 17:02:06 +02:00
Vanessa 1a9281ab63 Fixed Species Organs (#6409)
Fixed some inconsistent organs with species.
2026-08-13 10:01:47 -05:00
Delta-V bot c9038be25f Automatic changelog update 2026-08-13 03:17:23 +02:00
Vanessa b3cf460bda Fix and Tweak Random Shuttle Spawns (#6401)
* Fix and slightly alter random shuttle spawns

* Few more tweaks. Twice as many shuttles.

* Tweaked to be max every 90 min
2026-08-13 03:17:03 +02:00
Delta-V bot 7fddf52368 Automatic changelog update 2026-08-12 19:34:32 +02:00
pathetic meowmeow 4a96535892 Finetune ion law set (#6390) 2026-08-12 14:34:11 -03:00
Stop-Signs 93f614fdfc Add animation to the radiant sword (#6380)
* progress

* kermit

* donezo

* light
2026-08-12 14:15:26 -03:00
Delta-V bot 40997fb4a4 Automatic changelog update 2026-08-12 19:05:52 +02:00
freezer dog c3adbb32f1 a cowboy crate edit (#6363)
yeehaw
2026-08-12 14:05:32 -03:00
Delta-V bot 3974a0331b Automatic changelog update 2026-08-12 18:49:52 +02:00
SumofThreeParts 92c6c8cfa3 Making Universals Cheaper (#6366)
* Making Universals Cheaper

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

* Update cargo_armory.yml

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

* Update cargo_armory.yml

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

---------

Signed-off-by: SumofThreeParts <its.a.joke1000@gmail.com>
2026-08-12 13:49:31 -03:00
Vanessa ea313aae4a Update Configs (#6407)
* add configs that were not managed in git

* Add fallback preset

* removed map_enabled
2026-08-10 17:03:25 -05:00
Delta-V bot 272c38a6d0 Automatic changelog update 2026-08-10 18:15:15 +02:00
Coryn 7f11e1eebb Harpy Layers Fix (#6406)
* Harpy Layers Fix

* Remove unused Dependency

* Commented out Visual Subscribes, Add RArmExtension has a hideable layer for hardsuits

* Untouch Harpy Visuals
2026-08-10 11:14:55 -05:00
Delta-V bot 06429d050a Automatic changelog update 2026-08-09 20:24:45 +02:00
ed07a12d24 Gnomes (#6027)
* add gnomes, gnome hat, gnome gamerule, gnome voice

* inventory and hand fixed, speech half fix, hat embed, gamerule doesn't work

* fix audio, gamerule doesn't spawn correctly, need to fix dead sprite 90 degrees off

* add audio, add custom jaunt + polymorph, "fix" dead sprite

* add storage to hat, gnome fit into duffelbags

* make jaunt 2 seconds, hat store small items

* fix spawn

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

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

* jaunt 20 seconds

* fix stupid web merge, 20 second jaunt cooldown

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

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

* add pitch variation

* audio fixes and guidebook, make pulling need hands

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

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

* fix hands and polymorph inventory, make storage implant unremovable

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

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

* polymorph inventory make linter mad

* Update Resources/Prototypes/_DV/Polymorphs/polymorph.yml

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Signed-off-by: Theo <tad3@illinois.edu>

* Update Resources/Locale/en-US/_DV/abilities/psionic.ftl

Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Signed-off-by: Theo <tad3@illinois.edu>

* passive HTN and glimmer event

* fix comment

* random names

* PR Review Suggestions but it works this time

---------

Signed-off-by: Theo <rebe83a@gmail.com>
Signed-off-by: Theo <tad3@illinois.edu>
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>
Co-authored-by: Vanessa <vanessalouwagie@gmail.com>
2026-08-09 18:24:25 +00:00
Delta-V bot 493db6d129 Automatic changelog update 2026-08-09 20:14:34 +02:00
Vanessa d07f16f43f Allow curators to help out with people learning the game (#6374) 2026-08-09 13:14:15 -05:00
Delta-V bot f580f3b060 Automatic changelog update 2026-08-09 18:57:52 +02:00
Vanessa e7869d8221 Fix Jaunting/Polymorph while Nodecrawling (#6400)
* Disable polymorph while ventcrawling

* Actually, allow but remove from vent

* update comments
2026-08-09 11:57:32 -05:00
Vanessa 398e3eb75f Server Location Updates (#6405)
Server name updates (because we're in canada now)
2026-08-09 11:54:55 -05:00
Vanessa 0637a82e40 Update CODEOWNERS (#6404) 2026-08-09 11:41:59 -05:00
github-actions[bot]andDeltaV-Bot 206ded17c8 Update Credits (#6402)
Co-authored-by: DeltaV-Bot <github@deltav.gay>
2026-08-09 03:11:44 +02:00
Delta-V bot 847f534ff6 Automatic changelog update 2026-08-08 01:25:14 +02:00
VanessaandWhatstone b38f0b1a59 Fix parcel wrap transform parenting via SpawnAtPosition (#44679) (#6399)
ParcelWrap: spawn at position, use PopupEntity

Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com>
2026-08-07 23:24:53 +00:00
Delta-V bot efbeb5bf68 Automatic changelog update 2026-08-07 18:51:45 +02:00
VanessaandSir Warock 1f09c717f3 Fix Antags Being Crazy Smol (#6398)
* Fixed antags being crazy smol

* Update Content.Shared/Sprite/SharedScaleVisualsSystem.cs

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: Sir Warock <67167466+SirWarock@users.noreply.github.com>
2026-08-07 16:51:24 +00:00
Radezolid 52ed881e2c Extra mapping stuff for justice rework (#6397)
* Banner

* Beacons

* Warp

* Gun safe stuff
2026-08-07 09:04:06 -05:00
Radezolid 393e1951a2 More suffixes fixes for Justice (#6396)
Suffixes, suffixes!
2026-08-05 00:07:51 +00:00
Delta-V bot ca7c7daf6c Automatic changelog update 2026-08-05 00:19:07 +02:00
Stxcking 697da0679a Fix: Moproach Ghostrole + Critter Emoting Fixes (#6356)
yeah sure it works
2026-08-04 17:18:46 -05:00
Delta-V bot 1b75cad94f Automatic changelog update 2026-08-04 23:49:39 +02:00
Vanessa 82e5d9b924 Fix Shadekin Changelog (#6395)
* Fix changelog

* i can't spell
2026-08-04 21:49:18 +00:00
Radezolid e44717f262 Fix Justice Warden's suffixes for mapping (#6393)
* SUFFIXES

* I missed the PDA one.

* Justice warden's shit

* Holopad
2026-08-04 12:45:19 -05:00
Delta-V bot 9c83b91850 Automatic changelog update 2026-08-04 18:41:34 +02:00
VanessaandSir Warock f027ae4566 Fix Shadekin Inheritence (#6381)
* Make shadekin organic

* Removed shadekin lungs

* Update Content.Server/_Starlight/ShadekinSystem.cs

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

---------

Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com>
Co-authored-by: Sir Warock <67167466+SirWarock@users.noreply.github.com>
2026-08-04 16:41:14 +00:00
Delta-V bot 724cd1c9b8 Automatic changelog update 2026-08-04 18:28:16 +02:00
Vanessaandpre-commit-ci[bot] f44178bec3 Character Height Slider + Character Height Changes (#6320)
* Do the thing for everyone. Height is now +/- 20% for everyone.

* Make the height slider show height. Solve for special cases. Solve world hunger.

* Fixed some minor things.

* Adjust some densities

* Smaller character balance

* Comment

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

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

* Oops, that was from a failed thing.

* Size calc correction

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

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

* people are tall in the future

* I need to refactor a bit still.

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

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

* Refactor into a system

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

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

* smol

* Fixed remaining scale visual components and species

* Remove MockScale. Just add scale visuals for Allalulo to compensate.

* PR feedback

* Slight refactor. Moved profile/species scale out of the scale system and put it into the HumanoidProfileSystem. Removed some CD logic that is no longer needed.

* how'd this get in there

* Move the other motorkind.yml to AprilFools dir

* NO NO_PENTALTY lol

* Other PR stuff

* Other PR stuff

* Add a dirty

* Add some networking to the comp

* Add small character to cloning

* One more fixX

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-04 16:27:55 +00:00
Delta-V bot 877267cd31 Automatic changelog update 2026-08-03 04:45:31 +02:00
3572bf4739 Skia Shadow-Walk and DarkVision instead of VentCrawl (#6346)
* What if Skia were awesome instead of sucking

* Shhh Yaml Linter... Shhh

* Small change to make it feel more consistant

* Reworked shadowwalking to be lazy

Much faster, preserves most of the feel

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

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

* Update Content.Shared/_DV/Overlays/Components/DarkVisionComponent.cs

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

---------

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-08-02 22:45:11 -04:00
Delta-V bot 7867fb7f1f Automatic changelog update 2026-08-02 15:31:52 +02:00
a3b1059c21 PORT: Added Construction Bag and Bag of Holding from Frontier (#5846)
* Construction Bag (#1487)

* initial commit

* Un Tag

* fixes

* no tickets

* research thingy

* inflatable walls, shared.comp

---------

Co-authored-by: Dvir <dvirf01@gmail.com>

* DeltaV related

Don't kill me for fixing my own grammar mistake with the borg module please

* BorgModuleAdvancedConstruction

- I will fix the spelling in another PR I guess. GUH
- Some fixes to the bag according to linter.
Anything and everything you need for a borg module:
- Lathe recipe.
- Module.
- Icon/action sprite.

* Sprite fixes

* lathe fix I forgor

* nuke BorgModuleAdvancedConstruction

CR request

* why was this not merged uh whoops

* suggestions

* Update Resources/Prototypes/_NF/Recipes/Lathes/misc.yml

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

---------

Signed-off-by: Radezolid <snappednexus@gmail.com>
Co-authored-by: ErhardSteinhauer <65374927+ErhardSteinhauer@users.noreply.github.com>
Co-authored-by: Dvir <dvirf01@gmail.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
2026-08-02 13:31:30 +00:00
Stxckingandpre-commit-ci[bot] 076d960072 Justice Rework - Mapping (#6276)
* windoors, airlocks, faxes, dresser, beacon, warppoints

* Lockers and placeholder bedsheets

Most of this done by Rebe

* attorney as a separate role

idk why we decide to just modify upstream lawyer instead of making a separate role from the start sobs

* justice warden, armorer, marshal job roles

* linter fixes

* holy shit theres more

a mixture of everything as I had a list to go down
-JusticeDetective stuff because Det is now under justice
-More Armorer stuff
-Cameras
-Telecoms
-Prosec/Attorney lockers
-Secure windoors

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

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

* brig access as requested + admin config

also dupe beacon fix wop

* Reb

* Bypass ChameleonJobLoadoutTest

dear maint i pinky promise they will get added before rework launches

* suggestions and fixes

They don't need to be on the access config right now but should be fine.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-02 10:20:12 -03:00
github-actions[bot]andDeltaV-Bot acb6677aff Update Credits (#6392)
Co-authored-by: DeltaV-Bot <github@deltav.gay>
2026-08-02 04:08:22 +02:00
Delta-V bot c916f9a90b Automatic changelog update 2026-07-30 19:31:07 +02:00
71b6dfc0b1 Add Remote Medical Tracking to T3 Biochemical Tree (#6272)
* Added handheld monitors to the research tree.

* changed logi crate parent

* Added new board to dynamic pack

* Namespaced new entity

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

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

* Apply suggestions from code review

Co-authored-by: Radezolid <snappednexus@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: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
2026-07-30 17:30:47 +00:00
Delta-V bot 5fe8a394ba Automatic changelog update 2026-07-29 17:42:20 +02:00
Vanessaandpre-commit-ci[bot] 69d3d96d79 Reduce Sink Claws Delay + Sink Claws Alert (#6376)
* Reduced felinid rootable delay to tolerable levels

* Added the unused toggle icon to the alerts on the side when claws are protracted, instead of using the diona one.

* [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-07-29 17:42:01 +02:00
Delta-V bot 1e6f69361d Automatic changelog update 2026-07-29 14:19:14 +02:00
NollaDarkstarandjohn cb71e8159c Change resistances of the Durathread Vest and Plate Carrier (#6311)
* Change to durathread vest and plate carrier

* Increase heat resistance of the durathread to 20%, plate carrier remains at 20% slash

* Plate Carrier is back at 20% heat

---------

Co-authored-by: john <sadadawd3@gmail.com>
2026-07-29 12:18:53 +00:00
854 changed files with 149180 additions and 48250 deletions
+10 -19
View File
@@ -1,34 +1,25 @@
# Last match in file takes precedence.
# See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#example-of-a-codeowners-file
# C# code
/Content.*/ @DeltaV-Station/maintainers
# Any assets
/Resources/ @DeltaV-Station/maintainers
* @DeltaV-Station/maintainers
# Server config files
/Resources/ConfigPresets/ @Toby222
/Resources/ConfigPresets/ @DeltaV-Station/lead-maintainers
# Github workflows
/.github/ @DeltaV-Station/lead-maintainers
# Lobby art and music - automatically direction issues since its immediately visible to players
/Resources/Audio/Lobby/ @DeltaV-Station/direction
/Resources/Textures/LobbyScreens/ @DeltaV-Station/direction
/Resources/Audio/Lobby/ @DeltaV-Station/direction @DeltaV-Station/maintainers
/Resources/Textures/LobbyScreens/ @DeltaV-Station/direction @DeltaV-Station/maintainers
# Maps
/Resources/Maps/ @DeltaV-Station/maptainers
/Resources/Prototypes/Maps/ @DeltaV-Station/maptainers
/Content.IntegrationTests/Tests/PostMapInitTest.cs @DeltaV-Station/maptainers
/Content.IntegrationTests/Tests/PostMapInitTest.cs @DeltaV-Station/maptainers @DeltaV-Station/maintainers
# Server rules
/Resources/ServerInfo/Guidebook/_DV/Rules/ @DeltaV-Station/head-administrators
# Wiki
/.wiki/ @DeltaV-Station/direction
# Tools and scripts
/Tools/ @Toby222
# Workflows, codeowners, templates, etc.
/.github/ @Toby222
# Standalone files in the root repo
/* @Toby222
/.wiki/ @DeltaV-Station/direction
+1 -1
View File
@@ -26,7 +26,7 @@ This just saves them the trouble. Note that AGPL or MIT licensing is only applic
- [ ] I confirm that I am the creator of the code in this PR, and allow licensing it under the following license(s), or that the original author(s) has given me permission to do so:
- [X] AGPL (https://github.com/DeltaV-Station/Delta-v/blob/master/LICENSE-AGPLv3.txt) <!-- This one is required to contribute to DeltaV -->
- [X] MIT (https://github.com/DeltaV-Station/Delta-v/blob/master/LICENSE-MIT.txt) <!-- Optional - A lot of other SS14 forks use MIT -->
<!-- Feel free to add more licenses as you see fit -->
<!-- Feel free to add more licenses as you see fit -->
**Changelog**
<!-- Add a Changelog entry to make players aware of new features or changes that could affect gameplay.
+1 -1
View File
@@ -59,7 +59,7 @@ public sealed partial class BountyEntry : BoxContainer
var claimedByText = string.IsNullOrEmpty(bounty.ClaimedBy) ? Loc.GetString("bounty-console-claimed-by-none") : bounty.ClaimedBy;
ClaimedBylabel.SetMarkup(Loc.GetString("bounty-console-claimed-by", ("claimant", claimedByText)));
StatusLabel.SetMarkup(Loc.GetString($"bounty-console-formatted-status-{bounty.Status.ToString()}"));
StatusLabel.SetMarkup(Loc.GetString($"bounty-console-status-formatted-{bounty.Status.ToString()}"));
// End DeltaV bounty claiming
}
@@ -28,7 +28,6 @@ namespace Content.Client.Communications.UI
_menu.OnBroadcast += BroadcastButtonPressed;
_menu.OnAlertLevel += AlertLevelSelected;
_menu.OnEmergencyLevel += EmergencyShuttleButtonPressed;
_menu.OnExfiltrationLevel += ExfiltrationShuttleButtonPressed; // DeltaV - Exfiltration shuttle
}
public void AlertLevelSelected(string level)
@@ -48,13 +47,6 @@ namespace Content.Client.Communications.UI
CallShuttle();
}
// Begin DeltaV - Exfiltration Shuttle
public void ExfiltrationShuttleButtonPressed()
{
SendMessage(new CommunicationsConsoleExfiltrationShuttleMessage(!_menu!.CountdownStarted));
}
// End DeltaV - Exfiltration Shuttle
public void AnnounceButtonPressed(string message)
{
var maxLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength);
@@ -94,13 +86,6 @@ namespace Content.Client.Communications.UI
_menu.CurrentLevel = commsState.CurrentAlert;
_menu.CountdownEnd = commsState.ExpectedCountdownEnd;
// Begin DeltaV - Exfiltration Shuttle
_menu.CanExfiltrate = commsState.CanCall;
_menu.ExfiltrationCountdownEnd = commsState.ExpectedExfiltrationCountdownEnd;
_menu.ExfiltrationShuttleButton.Disabled = !_menu.CanExfiltrate;
// End DeltaV - Exfiltration Shuttle
_menu.UpdateCountdown();
_menu.UpdateAlertLevels(commsState.AlertLevels, _menu.CurrentLevel);
_menu.AlertLevelButton.Disabled = !_menu.AlertLevelSelectable;
@@ -52,19 +52,10 @@
<RichTextLabel Name="CountdownLabel"/>
<!-- DeltaV - Exfiltration Shuttle -->
<RichTextLabel Name="ExfiltrationCountdownLabel"/>
<Button Name="EmergencyShuttleButton"
Access="Public"
Text="Placeholder Text"
ToolTip="{Loc 'comms-console-menu-emergency-shuttle-button-tooltip'}"/>
<!-- DeltaV - Exfiltration Shuttle -->
<Button Name="ExfiltrationShuttleButton"
Access="Public"
Text="Placeholder Text"
ToolTip="{Loc 'comms-console-menu-exfiltration-shuttle-button-tooltip'}"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
@@ -29,12 +29,6 @@ namespace Content.Client.Communications.UI
public event Action<string>? OnAnnounce;
public event Action<string>? OnBroadcast;
// Begin DeltaV - Exfiltration Shuttle
public bool CanExfiltrate;
public TimeSpan? ExfiltrationCountdownEnd;
public event Action? OnExfiltrationLevel;
// End DeltaV - Exfiltration Shuttle
public CommunicationsConsoleMenu()
{
IoCManager.InjectDependencies(this);
@@ -78,18 +72,12 @@ namespace Content.Client.Communications.UI
EmergencyShuttleButton.OnPressed += _ => OnEmergencyLevel?.Invoke();
EmergencyShuttleButton.Disabled = !CanCall;
// Begin DeltaV - Exfiltration Shuttle
ExfiltrationShuttleButton.OnPressed += _ => OnExfiltrationLevel?.Invoke();
ExfiltrationShuttleButton.Disabled = !CanExfiltrate;
// End DeltaV - Exfiltration Shuttle
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdateCountdown();
UpdateExfiltrationCountdown(); // DeltaV - Exfiltration shuttle
}
// The current alert could make levels unselectable, so we need to ensure that the UI reacts properly.
@@ -129,25 +117,6 @@ namespace Content.Client.Communications.UI
}
}
// Begin DeltaV - Exfiltration Shuttle
public void UpdateExfiltrationCountdown()
{
if (ExfiltrationCountdownEnd is null)
{
ExfiltrationCountdownLabel.SetMessage(string.Empty);
ExfiltrationShuttleButton.Text = Loc.GetString("comms-console-menu-call-exfiltration");
return;
}
var exfiltrationDiff = MathHelper.Max((ExfiltrationCountdownEnd - _timing.CurTime) ?? TimeSpan.Zero, TimeSpan.Zero);
ExfiltrationShuttleButton.Text = Loc.GetString("comms-console-menu-recall-exfiltration");
var exfiltrationInfoText = Loc.GetString("comms-console-menu-exfiltration-time-remaining",
("time", exfiltrationDiff.ToString(@"hh\:mm\:ss", CultureInfo.CurrentCulture)));
ExfiltrationCountdownLabel.SetMessage(exfiltrationInfoText);
}
// End DeltaV - Exfiltration Shuttle
public void UpdateCountdown()
{
if (!CountdownStarted)
@@ -179,9 +179,12 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
var unborgable = _unborgable.IsUnborgable(target.Value);
var redshirt = _redshirt.IsRedshirt(target.Value) && mobStateComponent?.CurrentState == MobState.Dead; // DeltaV - Redshirt
var uncloneable = _uncloneable.IsUncloneable(target.Value) && mobStateComponent?.CurrentState == MobState.Dead; // DeltaV - Unclonable
// I'm just going to lump them all into 'extraTraitAlerts' to group our modifications as a single bool in the next check
var extraTraitAlerts = unborgable || redshirt || uncloneable;
// END DeltaV
var showAlerts = state.Unrevivable == true || state.Bleeding == true;
var showAlerts = state.Unrevivable == true || state.Bleeding == true || extraTraitAlerts; // DeltaV - extra alerts
AlertsDivider.Visible = showAlerts;
AlertsContainer.Visible = showAlerts;
@@ -32,10 +32,10 @@ namespace Content.Client.Labels.UI
}
_window.OnLabelChanged += OnLabelChanged;
_window.OnLabelSelected += OnLabelSelected; // Starlight
Reload();
_window.SetInitialLabelState(); // Must be after Reload() has set the label text
}
private void OnLabelChanged(string newLabel)
{
// Focus moment
@@ -45,7 +45,17 @@ namespace Content.Client.Labels.UI
SendPredictedMessage(new HandLabelerLabelChangedMessage(newLabel));
}
// Starlight start
private void OnLabelSelected(string label)
{
// Update UI and send message to sync with server
if (_window != null)
{
_window.SetCurrentLabel(label);
}
SendPredictedMessage(new HandLabelerLabelChangedMessage(label));
}
// Starlight End
public void Reload()
{
if (_window == null || !_entManager.TryGetComponent(Owner, out HandLabelerComponent? component))
@@ -1,8 +1,10 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc 'hand-labeler-ui-header'}"
SetWidth="400"
MinWidth="150">
MinWidth="150"
SetHeight="400"> <!-- Starlight - include gfx and set default height to include saved labels section nicely -->
<BoxContainer Orientation="Vertical" SeparationOverride="4">
<Label Name="CurrentTextLabel" Text="{Loc 'hand-labeler-current-text-label'}" />
<LineEdit Name="LabelLineEdit" />
@@ -10,5 +12,19 @@
<Button Name="ResetLabelButton" Text="{Loc 'hand-labeler-ui-reset-label-text'}" StyleClasses="OpenRight" />
<Button Name="ClearLabelButton" Text="{Loc 'hand-labeler-ui-clear-label-text'}" StyleClasses="OpenLeft" />
</BoxContainer>
<!-- Starlight Begin -->
<Label Text="{Loc 'hand-labeler-saved-labels'}" />
<PanelContainer VerticalExpand="True" SizeFlagsStretchRatio="1">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<ItemList Name="SavedLabelsList" VerticalExpand="True" SelectMode="Button" />
</PanelContainer>
<BoxContainer Orientation="Horizontal" Align="Center">
<Button Name="SaveLabelButton" Text="{Loc 'hand-labeler-save-label-text'}" StyleClasses="OpenRight" />
<Button Name="DeleteLabelButton" Text="{Loc 'hand-labeler-delete-label-text'}" StyleClasses="OpenLeft" />
</BoxContainer>
<!-- Starlight end -->
</BoxContainer>
</DefaultWindow>
@@ -1,13 +1,19 @@
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Client.UserInterface.Controls; // Starlight
using Robust.Shared.Configuration; // Starlight
using Content.Shared.Starlight.CCVar; // Starlight
namespace Content.Client.Labels.UI
{
[GenerateTypedNameReferences]
public sealed partial class HandLabelerWindow : DefaultWindow
{
[Dependency] private readonly IConfigurationManager _cfg = default!; // Starlight
public event Action<string>? OnLabelChanged;
public event Action<string>? OnLabelSelected; // Starlight
/// <summary>
/// Is the user currently entering text into the control?
@@ -17,10 +23,14 @@ namespace Content.Client.Labels.UI
private string _label = string.Empty;
private string _initialLabel = string.Empty;
private bool _deleteMode = false; // Starlight
private List<string> _savedLabels = []; // Starlight
public HandLabelerWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this); // Starlight
LoadSavedLabelsFromCVar(); // Starlight
LabelLineEdit.OnTextChanged += e =>
{
@@ -37,6 +47,42 @@ namespace Content.Client.Labels.UI
};
ResetLabelButton.OnPressed += _ => LabelLineEdit.SetText(_initialLabel, true);
ClearLabelButton.OnPressed += _ => LabelLineEdit.SetText("", true);
// Starlight Begin
SaveLabelButton.OnPressed += _ =>
{
if (!string.IsNullOrWhiteSpace(_label) && !_savedLabels.Contains(_label))
{
_savedLabels.Add(_label);
SaveSavedLabelsToCVar();
UpdateSavedLabels(_savedLabels);
}
};
DeleteLabelButton.OnPressed += _ =>
{
_deleteMode = !_deleteMode;
DeleteLabelButton.Pressed = _deleteMode;
UpdateButtons();
};
SavedLabelsList.OnItemSelected += args =>
{
if (_deleteMode)
{
_savedLabels.RemoveAt(args.ItemIndex);
SaveSavedLabelsToCVar();
UpdateSavedLabels(_savedLabels);
UpdateButtons();
}
else
{
if (SavedLabelsList[args.ItemIndex].Metadata is string label)
{
OnLabelSelected?.Invoke(label);
}
}
}; // Starlight End
}
protected override void Opened()
@@ -70,15 +116,68 @@ namespace Content.Client.Labels.UI
UpdateButtons();
}
// Starlight begin
public void UpdateSavedLabels(List<string> labels)
{
var items = new List<ItemList.Item>();
foreach (var label in labels)
{
var item = new ItemList.Item(SavedLabelsList)
{
Text = label,
Metadata = label
};
items.Add(item);
}
SavedLabelsList.SetItems(items);
UpdateButtons();
}
// Starlight end
public void UpdateButtons()
{
ResetLabelButton.Disabled = (LabelLineEdit.Text == _initialLabel);
ClearLabelButton.Disabled = (LabelLineEdit.Text == "");
}
// Starlight begin
var labelExists = false;
foreach (var item in SavedLabelsList)
{
if ((item.Metadata as string) == _label.Trim())
{
labelExists = true;
break;
}
}
SaveLabelButton.Disabled = string.IsNullOrWhiteSpace(_label) || labelExists;
DeleteLabelButton.Text = _deleteMode
? Loc.GetString("hand-labeler-delete-mode-text")
: Loc.GetString("hand-labeler-delete-label-text");
// Starlight end
}
public void SetMaxLabelLength(int maxLength)
{
LabelLineEdit.IsValid = s => s.Length <= maxLength;
}
// Starlight begin
private void LoadSavedLabelsFromCVar()
{
var savedLabelsStr = _cfg.GetCVar(StarlightCCVars.HandLabelerSavedLabels);
if (!string.IsNullOrEmpty(savedLabelsStr))
{
_savedLabels = new List<string>(savedLabelsStr.Split('\n', StringSplitOptions.RemoveEmptyEntries));
UpdateSavedLabels(_savedLabels);
}
}
private void SaveSavedLabelsToCVar()
{
var savedLabelsStr = string.Join('\n', _savedLabels);
_cfg.SetCVar(StarlightCCVars.HandLabelerSavedLabels, savedLabelsStr);
_cfg.SaveToFile();
}
// Starlight end
}
}
@@ -9,6 +9,8 @@ using Content.Shared.Preferences;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Content.Client._CD.Records.UI; // DeltaV
using Content.Shared._DV.Body.Systems; // DeltaV
namespace Content.Client.Lobby.UI;
@@ -193,6 +195,7 @@ public sealed partial class HumanoidProfileEditor
RefreshLoadouts();
UpdateSexControls(); // update sex for new species
UpdateSpeciesGuidebookIcon();
UpdateHeightControls(); // DeltaV
ReloadPreview();
}
@@ -204,23 +207,22 @@ public sealed partial class HumanoidProfileEditor
ReloadProfilePreview();
}
private void UpdateHeightControls()
{
if (Profile == null)
{
return;
}
var species = _species.Find(x => x.ID == Profile.Species);
if (species != null)
_defaultHeight = species.DefaultHeight;
var prototype = _prototypeManager.Index(Profile.Species);
_defaultHeight = prototype.DefaultHeight;
var prototype = _prototypeManager.Index<SpeciesPrototype>(Profile.Species);
var sliderPercent = (Profile.Height - prototype.MinHeight) /
(prototype.MaxHeight - prototype.MinHeight);
CDHeightSlider.Value = sliderPercent;
CDHeight.Text = Profile.Height.ToString(CultureInfo.InvariantCulture);
var scaleReference = _defaultHeight * prototype.BaseScale.Y;
var newHeight = MathF.Round(MathHelper.Lerp(prototype.MinHeight, prototype.MaxHeight, sliderPercent), 2);
CDHeightLabel.Text = UnitConversion.GetMetricAndImperialDisplayFromScale(scaleReference * newHeight);
CDPullSpeedReductionLabel.Text = SmallCharacterSystem.GetPullSpeedPenaltyDisplayFromScale(newHeight);
}
// End CD - Character Records
@@ -73,9 +73,13 @@
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-height-label'}" />
<Control HorizontalExpand="True" />
<Label Name="CDHeightLabel" />
<Slider Name="CDHeightSlider" HorizontalAlignment="Right" SetWidth="300" MinValue="0.0" MaxValue="1.0"/>
<LineEdit HorizontalAlignment="Right" Name="CDHeight" MinSize="60 0" Text="1.0" />
<Button Name="CDHeightReset" Text="{Loc 'humanoid-profile-editor-reset-height-button'}" HorizontalAlignment="Right"/>
</BoxContainer>
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-height-pull-speed-penalty-label'}" />
<Control HorizontalExpand="True" />
<Label Name="CDPullSpeedReductionLabel" />
</BoxContainer>
<!-- End CD - Character Records -->
<!-- Sex -->
@@ -2,9 +2,6 @@ using Content.Client.Humanoid;
using Content.Client.Message;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Sprite;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared._DV.Species; // DeltaV - Species hider
using Content.Shared.Body;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
@@ -22,13 +19,8 @@ using Robust.Shared.ContentPack;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Direction = Robust.Shared.Maths.Direction;
// Begin CD - Character Records
using System.Globalization;
using Content.Client._CD.Records.UI;
using Content.Shared._CD.Records;
// End CD - Character Records
using Content.Shared._DV.Traits;
using Content.Shared.Humanoid.Prototypes; // DV - Traits
using Content.Client._CD.Records.UI; // CD - Character Records
using Content.Shared._DV.Body.Systems; // DV - Traits
namespace Content.Client.Lobby.UI
{
@@ -221,34 +213,18 @@ namespace Content.Client.Lobby.UI
// Begin CD - Character Records
#region CDHeight
CDHeight.OnTextChanged += args =>
{
if (Profile is null || !float.TryParse(args.Text, out var newHeight))
return;
var prototype = _prototypeManager.Index<SpeciesPrototype>(Profile.Species);
newHeight = MathF.Round(Math.Clamp(newHeight, prototype.MinHeight, prototype.MaxHeight), 2);
// The percentage between the start and end numbers, aka "inverse lerp"
var sliderPercent = (newHeight - prototype.MinHeight) /
(prototype.MaxHeight - prototype.MinHeight);
CDHeightSlider.Value = sliderPercent;
SetProfileHeight(newHeight);
};
CDHeightReset.OnPressed += _ =>
{
CDHeight.SetText(_defaultHeight.ToString(CultureInfo.InvariantCulture), true);
};
CDHeightSlider.OnValueChanged += _ =>
{
if (Profile is null)
return;
var prototype = _prototypeManager.Index<SpeciesPrototype>(Profile.Species);
var prototype = _prototypeManager.Index(Profile.Species);
var newHeight = MathF.Round(MathHelper.Lerp(prototype.MinHeight, prototype.MaxHeight, CDHeightSlider.Value), 2);
CDHeight.Text = newHeight.ToString(CultureInfo.InvariantCulture);
var speciesScale = prototype.BaseScale.Y;
CDHeightLabel.Text = UnitConversion.GetMetricAndImperialDisplayFromScale(newHeight * speciesScale);
CDPullSpeedReductionLabel.Text = SmallCharacterSystem.GetPullSpeedPenaltyDisplayFromScale(newHeight);
SetProfileHeight(newHeight);
};
@@ -70,6 +70,11 @@ public sealed class ConfirmButton : Button
[ViewVariables]
public bool IsConfirming = false;
// Begin DeltaV
[ViewVariables]
public bool ForceDisabled = false;
// End DeltaV
public ConfirmButton()
{
IoCManager.InjectDependencies(this);
@@ -87,7 +92,7 @@ public sealed class ConfirmButton : Button
}
if (Disabled && _gameTiming.CurTime > _nextCooldown)
Disabled = false;
Disabled = ForceDisabled; // DeltaV
}
protected override void DrawModeChanged()
@@ -49,11 +49,6 @@
<Control HorizontalExpand="True" />
<Label Name="RecordContainerSpecies" Align="Right" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-cd-records-height'}"/>
<Control HorizontalExpand="True" />
<Label Name="RecordContainerHeight" Align="Right" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-cd-records-weight'}"/>
<Control HorizontalExpand="True" />
@@ -325,8 +325,7 @@ public sealed partial class CharacterRecordViewer : FancyWindow
RecordContainerJob.Text = record.JobTitle; /* At some point in the future we might want to display the icon */
RecordContainerGender.Text = record.Gender.ToString();
RecordContainerSpecies.Text = record.Species;
RecordContainerHeight.Text = cr.Height + " " + UnitConversion.GetImperialDisplayLength(cr.Height);
RecordContainerWeight.Text = cr.Weight + " " + UnitConversion.GetImperialDisplayMass(cr.Weight);
RecordContainerWeight.Text = $"{cr.Weight} ({UnitConversion.GetImperialDisplayMass(cr.Weight)})";
RecordContainerContactName.SetValue(cr.EmergencyContactName);
RecordContainerEmployment.Visible = false;
@@ -5,12 +5,6 @@
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="10">
<!-- Height, Weight -->
<GridContainer Columns="2">
<BoxContainer HorizontalExpand="True" SeparationOverride="2">
<Label Text="{Loc 'humanoid-profile-editor-cd-records-height'}" />
<Control HorizontalExpand="True" MinSize="5 0" />
<LineEdit Name="HeightEdit" HorizontalAlignment="Right" MinSize="60 0" />
<Label Name="HeightImperialLabel" MinWidth="60" />
</BoxContainer>
<BoxContainer HorizontalExpand="True" SeparationOverride="2">
<Label Text="{Loc 'humanoid-profile-editor-cd-records-weight'}" />
<Control HorizontalExpand="True" MinSize="5 0" />
@@ -25,14 +25,6 @@ public sealed partial class RecordEditorGui : Control
#region General
HeightEdit.OnTextChanged += args =>
{
if (!int.TryParse(args.Text, out var newHeight))
return;
UpdateImperialHeight(newHeight);
UpdateRecords(_records.WithHeight(newHeight));
};
WeightEdit.OnTextChanged += args =>
{
if (!int.TryParse(args.Text, out var newWeight))
@@ -128,8 +120,6 @@ public sealed partial class RecordEditorGui : Control
private void UpdateWidgets()
{
HeightEdit.SetText(_records.Height.ToString());
UpdateImperialHeight(_records.Height);
WeightEdit.SetText(_records.Weight.ToString());
UpdateImperialWeight(_records.Weight);
ContactNameEdit.SetText(_records.EmergencyContactName);
@@ -143,13 +133,8 @@ public sealed partial class RecordEditorGui : Control
PostmortemEdit.SetText(_records.PostmortemInstructions);
}
private void UpdateImperialHeight(int newHeight)
{
HeightImperialLabel.Text = UnitConversion.GetImperialDisplayLength(newHeight);
}
private void UpdateImperialWeight(int newWeight)
{
WeightImperialLabel.Text = UnitConversion.GetImperialDisplayMass(newWeight);
WeightImperialLabel.Text = $"({UnitConversion.GetImperialDisplayMass(newWeight)})";
}
}
@@ -2,15 +2,47 @@ namespace Content.Client._CD.Records.UI;
public static class UnitConversion
{
/// <summary>
/// DeltaV - The average height of a human in centimeters. According to the US CDC, its
/// 171 for men and 160 for women. So average of that is ~165cm.
///
/// Just kidding, we're going with EE's arbitrary standard of 175cm.
/// </summary>
private const int AVERAGE_HEIGHT_CM = 175;
/// <summary>
/// DeltaV - 1.0 scale is considered average for humans, so a scale of 1 will be 175cm.
/// Ensure that scale also includes the base species height AND the user-defined height.
/// </summary>
/// <param name="scale"></param>
/// <returns></returns>
private static int GetMetricHeightFromScale(float scale = 1)
{
// cast as int because we don't care about decimal
return (int)Math.Max(scale * AVERAGE_HEIGHT_CM, 1); // can't be shorter than 1cm I guess
}
/// <summary>
/// DeltaV - Gets nicely formatted string that contains both metric and imperial measurements.
/// With a scale of 1, it should look like... 175cm (5' 9")
/// </summary>
/// <param name="scale"></param>
/// <returns></returns>
public static string GetMetricAndImperialDisplayFromScale(float scale = 1)
{
var metricHeight = GetMetricHeightFromScale(scale);
return $"{metricHeight}cm ({GetImperialDisplayLength(metricHeight)})";
}
public static string GetImperialDisplayLength(int lengthCm)
{
var heightIn = (int) Math.Round(lengthCm * 0.3937007874 /* cm to in*/);
return $"({heightIn / 12}'{heightIn % 12}'')";
var heightIn = (int)Math.Round(lengthCm * 0.3937007874 /* cm to in*/);
return $"{heightIn / 12}'{heightIn % 12}\"";
}
public static string GetImperialDisplayMass(int massKg)
{
var weightLbs = (int) Math.Round(massKg * 2.2046226218 /* kg to lbs */);
return $"({weightLbs} lbs)";
var weightLbs = (int)Math.Round(massKg * 2.2046226218 /* kg to lbs */);
return $"{weightLbs} lbs";
}
}
@@ -0,0 +1,30 @@
using Content.Shared._DV.Communications;
using Robust.Client.UserInterface;
namespace Content.Client._DV.Communications;
public sealed class DVCommunicationsConsoleBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IEntityManager _entity = default!;
private DVCommunicationsConsoleMenu? _menu;
public DVCommunicationsConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<DVCommunicationsConsoleMenu>();
_menu.OnMessage += SendMessage;
if (_entity.TryGetComponent<DVCommunicationsConsoleComponent>(Owner, out var comp))
Update((Owner, comp));
}
public void Update(Entity<DVCommunicationsConsoleComponent> ent)
{
_menu?.Update(ent, PlayerManager.LocalEntity!.Value);
}
}
@@ -0,0 +1,117 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'comms-console-menu-title'}"
MinSize="400 300">
<BoxContainer Name="HomeScreen" Orientation="Vertical" Margin="8">
<Label Text="{Loc 'comms-console-menu-station-title'}" Name="StationHeaderLabel" StyleClasses="LabelKeyText" />
<Button Name="AnnouncementTab" Text="{Loc 'comms-console-menu-tab.Announcement'}" />
<Button Name="ScreenTab" Text="{Loc 'comms-console-menu-tab.Screen'}" />
<Button Name="AlertLevelTab" Text="{Loc 'comms-console-menu-tab.AlertLevel'}" />
<Label Text="{Loc 'comms-console-menu-transit-title'}" Name="TransitHeaderLabel" StyleClasses="LabelKeyText" Margin="0 8 0 0" />
<Button Name="ShuttlesTab" Text="{Loc 'comms-console-menu-tab.Shuttles'}" />
<Label Text="{Loc 'comms-console-menu-emergency-title'}" Name="EmergencyHeaderLabel" StyleClasses="LabelKeyText" Margin="0 8 0 0" />
<Button Name="KeycardAuthenticationDeviceTab" Text="{Loc 'comms-console-menu-tab.KeycardAuthenticationDevice'}" />
</BoxContainer>
<BoxContainer Orientation="Vertical">
<Button Name="HomeButton" Text="{Loc 'comms-console-menu-home'}" Visible="False" />
<BoxContainer Name="AnnouncementScreen" Visible="False" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" SeparationOverride="8" Margin="8">
<PanelContainer StyleClasses="PanelDark" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer SeparationOverride="8" Margin="8" HorizontalExpand="True" Orientation="Vertical">
<Label Text="{Loc 'comms-console-menu-announcement-text'}" StyleClasses="LabelKeyText" />
<TextEdit Name="AnnounceInput"
VerticalExpand="True"
HorizontalExpand="True"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
MinHeight="100" />
</BoxContainer>
</PanelContainer>
<PanelContainer StyleClasses="PanelDark" HorizontalExpand="True">
<Button Name="AnnounceButton" Margin="8" Text="{Loc 'comms-console-menu-send-announcement'}" />
</PanelContainer>
</BoxContainer>
<BoxContainer Name="ScreenScreen" Visible="False" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" SeparationOverride="8" Margin="8">
<PanelContainer StyleClasses="PanelDark" HorizontalExpand="True">
<BoxContainer SeparationOverride="8" Margin="8" HorizontalExpand="True" Orientation="Vertical">
<Label Text="{Loc 'comms-console-menu-screen-configuration'}" StyleClasses="LabelKeyText" />
<BoxContainer>
<Label Text="{Loc 'comms-console-menu-screen-contents'}" HorizontalExpand="True" />
<OptionButton Name="ScreenContentsButton" />
</BoxContainer>
<BoxContainer>
<Label Text="{Loc 'comms-console-menu-alert-level-border'}" HorizontalExpand="True" />
<Button Name="ScreenAlertBorder" Text="{Loc 'comms-console-menu-enable-border'}" ToggleMode="True" />
</BoxContainer>
</BoxContainer>
</PanelContainer>
<PanelContainer StyleClasses="PanelDark">
<BoxContainer Margin="8" Orientation="Vertical" SeparationOverride="4">
<Label Text="{Loc 'comms-console-menu-screen-text'}" StyleClasses="LabelKeyText" />
<GridContainer Columns="2" HorizontalExpand="True">
<Label Text="{Loc 'comms-console-menu-line-1'}" />
<LineEdit Name="ScreenLine1" HorizontalExpand="True" StyleClasses="comms-console-display" />
<Label Text="{Loc 'comms-console-menu-line-2'}" />
<LineEdit Name="ScreenLine2" HorizontalExpand="True" StyleClasses="comms-console-display" />
</GridContainer>
<Button Name="ScreenUpdateTextButton" Text="{Loc 'comms-console-menu-update-screen-text'}" HorizontalAlignment="Right" />
</BoxContainer>
</PanelContainer>
</BoxContainer>
<BoxContainer Name="ShuttlesScreen" Visible="False" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" SeparationOverride="8" Margin="8">
<PanelContainer StyleClasses="PanelDark" HorizontalExpand="True">
<BoxContainer Margin="8" HorizontalExpand="True" Orientation="Vertical">
<Label Text="{Loc 'comms-console-menu-emergency-shuttle'}" StyleClasses="LabelKeyText" />
<RichTextLabel Name="EmergencyStatus" Text="{Loc 'comms-console-menu-shuttle-not-coming'}" />
<Button Name="ShuttleEmergencyButton" Text="{Loc 'comms-console-menu-call-emergency.True'}" HorizontalAlignment="Right" Margin="0 8 0 0" />
</BoxContainer>
</PanelContainer>
<PanelContainer StyleClasses="PanelDark" HorizontalExpand="True">
<BoxContainer Margin="8" HorizontalExpand="True" Orientation="Vertical">
<Label Text="{Loc 'comms-console-menu-exfiltration-shuttle'}" StyleClasses="LabelKeyText" />
<RichTextLabel Name="ExfiltrationStatus" Text="{Loc 'comms-console-menu-shuttle-not-coming'}" />
<Button Name="ShuttleExfiltrationButton" Text="{Loc 'comms-console-menu-call-exfiltration.True'}" HorizontalAlignment="Right" Margin="0 8 0 0" />
</BoxContainer>
</PanelContainer>
</BoxContainer>
<BoxContainer Name="AlertLevelScreen" Visible="False" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" SeparationOverride="8" Margin="8">
<PanelContainer StyleClasses="PanelDark" HorizontalExpand="True" MaxWidth="400">
<BoxContainer Margin="8" HorizontalExpand="True" Orientation="Vertical">
<Label Text="{Loc 'comms-console-menu-alert-level'}" StyleClasses="LabelKeyText" />
<RichTextLabel Name="AlertLevel" HorizontalExpand="True" />
<Button Name="AlertLevelsDropdown" Text="{Loc 'comms-console-menu-change-alert-level'}" HorizontalAlignment="Right" Margin="0 8 0 0" />
</BoxContainer>
</PanelContainer>
</BoxContainer>
<BoxContainer Name="KeycardAuthenticationDeviceScreen" Visible="False" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" SeparationOverride="8" Margin="8">
<PanelContainer StyleClasses="PanelDark" HorizontalExpand="True" MaxWidth="400">
<BoxContainer Margin="8" HorizontalExpand="True" Orientation="Vertical">
<Label Text="{Loc 'comms-console-menu-declare-mayday.title'}" StyleClasses="LabelKeyText" />
<RichTextLabel Text="{Loc 'comms-console-menu-declare-mayday.desc'}" />
<controls:ConfirmButton Name="CallZeta" Text="{Loc 'comms-console-menu-declare-mayday.button'}" HorizontalAlignment="Stretch" Margin="0 8 0 0" />
</BoxContainer>
</PanelContainer>
<PanelContainer StyleClasses="PanelDark" HorizontalExpand="True" MaxWidth="400">
<BoxContainer Margin="8" HorizontalExpand="True" Orientation="Vertical">
<Label Text="{Loc 'comms-console-menu-request-codes.title'}" StyleClasses="LabelKeyText" />
<RichTextLabel Text="{Loc 'comms-console-menu-request-codes.desc'}" />
<controls:ConfirmButton Name="RequestCodes" Text="{Loc 'comms-console-menu-request-codes.button'}" HorizontalAlignment="Stretch" Margin="0 8 0 0" />
</BoxContainer>
</PanelContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -0,0 +1,320 @@
using System.Globalization;
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared._DV.Communications;
using Content.Shared._DV.KeycardAuthenticationDevice;
using Content.Shared._DV.Screens;
using Content.Shared.Humanoid;
using Content.Shared.Station;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client._DV.Communications;
[GenerateTypedNameReferences]
public sealed partial class DVCommunicationsConsoleMenu : FancyWindow
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IEntityManager _entity = default!;
private readonly SharedStationSystem _station;
private readonly BoxContainer[] _screens;
private readonly ButtonGroup _group;
public event Action<BoundUserInterfaceMessage>? OnMessage;
private Entity<DVCommunicationsConsoleComponent>? _console;
private Entity<DVStationKeycardAuthenticationDeviceComponent>? _stationKeycardAuthenticationDevice;
private EntityUid? _user;
private readonly Popup _alertLevelsPopup;
private readonly BoxContainer _alertLevels = new()
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
};
private string _lastConfiguredLine1 = string.Empty;
private string _lastConfiguredLine2 = string.Empty;
public DVCommunicationsConsoleMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_station = _entity.System<SharedStationSystem>();
_group = new();
_screens = new[]
{
HomeScreen, AnnouncementScreen, ScreenScreen, ShuttlesScreen, AlertLevelScreen,
KeycardAuthenticationDeviceScreen,
};
HomeButton.OnPressed += _ => SetScreen(HomeScreen);
AnnouncementTab.OnPressed += _ => SetScreen(AnnouncementScreen);
ScreenTab.OnPressed += _ => SetScreen(ScreenScreen);
ShuttlesTab.OnPressed += _ => SetScreen(ShuttlesScreen);
AlertLevelTab.OnPressed += _ => SetScreen(AlertLevelScreen);
KeycardAuthenticationDeviceTab.OnPressed += _ => SetScreen(KeycardAuthenticationDeviceScreen);
foreach (var value in Enum.GetValues<DVScreenContent>())
{
ScreenContentsButton.AddItem(Loc.GetString($"comms-console-menu-screen-content.{value}"), (int)value);
}
AnnounceButton.OnPressed += OnAnnouncePressed;
ScreenUpdateTextButton.OnPressed += OnUpdateScreenText;
ScreenContentsButton.OnItemSelected += OnScreenContentsSelected;
ScreenAlertBorder.OnToggled += OnScreenAlertBorderToggled;
ShuttleEmergencyButton.OnPressed += OnEmergencyPressed;
ShuttleExfiltrationButton.OnPressed += OnExfiltrationPressed;
AlertLevelsDropdown.OnPressed += OnAlertLevelsDropdownPressed;
CallZeta.OnPressed += OnCallZetaPressed;
RequestCodes.OnPressed += OnRequestCodesPressed;
_alertLevelsPopup = new()
{
Children = { _alertLevels },
};
_alertLevelsPopup.OnPopupHide += () => _alertLevelsPopup.Orphan();
AnnounceInput.Placeholder = new Rope.Leaf(Loc.GetString("comms-console-menu-announcement-placeholder"));
}
private void OnAlertLevelsDropdownPressed(BaseButton.ButtonEventArgs args)
{
var globalPos = AlertLevelsDropdown.GlobalPosition;
globalPos.Y += AlertLevelsDropdown.Size.Y + 1; // Place it below us, with a safety margin.
_alertLevels.Measure(Window?.Size ?? Vector2Helpers.Infinity);
var (minX, minY) = _alertLevels.DesiredSize;
var box = UIBox2.FromDimensions(globalPos, new Vector2(Math.Max(minX, AlertLevelsDropdown.Width), minY));
_alertLevelsPopup.Orphan();
Root?.ModalRoot.AddChild(_alertLevelsPopup);
_alertLevelsPopup.Open(box);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (_console is not { } console)
return;
AnnounceButton.Disabled = _timing.CurTime <= console.Comp.CanAnnounceAt;
AlertLevelsDropdown.Disabled = console.Comp.CanSetAlertAt is null || _timing.CurTime <= console.Comp.CanSetAlertAt;
if (AlertLevelsDropdown.Disabled)
_alertLevelsPopup.Close();
if (console.Comp.ExpectedEvacuationArrival is { } evacArrival)
{
var diff = MathHelper.Max(evacArrival - _timing.CurTime, TimeSpan.Zero);
var infoText = Loc.GetString($"comms-console-menu-shuttle-eta",
("time", diff.ToString(@"hh\:mm\:ss", CultureInfo.CurrentCulture)));
EmergencyStatus.Text = infoText;
}
else
{
EmergencyStatus.Text = Loc.GetString("comms-console-menu-shuttle-not-coming");
}
if (console.Comp.ExpectedExfiltrationArrival is { } exfiltrationArrival)
{
var diff = MathHelper.Max(exfiltrationArrival - _timing.CurTime, TimeSpan.Zero);
var infoText = Loc.GetString($"comms-console-menu-shuttle-eta",
("time", diff.ToString(@"hh\:mm\:ss", CultureInfo.CurrentCulture)));
ExfiltrationStatus.Text = infoText;
}
else
{
ExfiltrationStatus.Text = Loc.GetString("comms-console-menu-shuttle-not-coming");
}
UpdateKeycardAuthenticationDevice();
}
private void SetScreen(Control screen)
{
foreach (var other in _screens)
{
other.Visible = false;
}
screen.Visible = true;
HomeButton.Visible = screen != HomeScreen;
}
public void Update(Entity<DVCommunicationsConsoleComponent> console, EntityUid user)
{
_console = console;
_user = user;
if (_station.GetOwningStation(console) is { } station &&
_entity.TryGetComponent<DVStationKeycardAuthenticationDeviceComponent>(station,
out var keycardAuthenticationDevice))
{
_stationKeycardAuthenticationDevice = (station, keycardAuthenticationDevice);
}
// If they only have the ability to announce, there's no point in having a home button
if (console.Comp.CanAnnounce && !(console.Comp.CanAlertLevel || console.Comp.CanCallShuttles || console.Comp.CanConfigureScreens || console.Comp.CanKeycardAuthenticationDevice))
{
SetScreen(AnnouncementScreen);
HomeButton.Visible = false;
}
// Station
StationHeaderLabel.Visible = console.Comp.CanAnnounce || console.Comp.CanConfigureScreens || console.Comp.CanAlertLevel;
AnnouncementTab.Visible = console.Comp.CanAnnounce;
ScreenTab.Visible = console.Comp.CanConfigureScreens;
AlertLevelTab.Visible = console.Comp.CanAlertLevel;
// Shuttles
TransitHeaderLabel.Visible = console.Comp.CanCallShuttles; // Should be only hidden if all buttons under are also hidden.
ShuttlesTab.Visible = console.Comp.CanCallShuttles;
// Emergency
EmergencyHeaderLabel.Visible = console.Comp.CanKeycardAuthenticationDevice; // Should be only hidden if all buttons under are also hidden.
KeycardAuthenticationDeviceTab.Visible = console.Comp.CanKeycardAuthenticationDevice;
ShuttleEmergencyButton.Disabled = !console.Comp.ShuttlesCallable;
ShuttleExfiltrationButton.Disabled = !console.Comp.ShuttlesCallable;
ShuttleEmergencyButton.Text =
Loc.GetString($"comms-console-menu-call-emergency.{console.Comp.ExpectedEvacuationArrival is null}");
ShuttleExfiltrationButton.Text =
Loc.GetString($"comms-console-menu-call-exfiltration.{console.Comp.ExpectedExfiltrationArrival is null}");
_alertLevels.RemoveAllChildren();
foreach (var level in console.Comp.AlertLevels)
{
if (level.Id == console.Comp.CurrentAlertLevel)
{
AlertLevel.Text = Loc.GetString("comms-console-menu-current-alert-level",
("name", Loc.GetString(level.AlertLevel)),
("color", level.Color),
("description", Loc.GetString(level.Description)));
}
if (!level.CanSet)
continue;
var btn = new Button
{
Text = Loc.GetString(level.AlertLevel),
ToggleMode = true,
Group = _group,
Pressed = level.Id == console.Comp.CurrentAlertLevel,
};
_alertLevels.AddChild(btn);
btn.OnPressed += _ =>
{
_alertLevelsPopup.Close();
OnMessage?.Invoke(new DVCommunicationsConsoleAlertLevelMessage(level.Id));
};
}
if (console.Comp.LastConfiguredLine1 != _lastConfiguredLine1)
ScreenLine1.Text = console.Comp.LastConfiguredLine1;
if (console.Comp.LastConfiguredLine2 != _lastConfiguredLine2)
ScreenLine2.Text = console.Comp.LastConfiguredLine2;
ScreenContentsButton.SelectId((int)console.Comp.LastConfiguredContent);
ScreenAlertBorder.Pressed = console.Comp.LastConfiguredShowBorders;
_lastConfiguredLine1 = console.Comp.LastConfiguredLine1;
_lastConfiguredLine2 = console.Comp.LastConfiguredLine2;
UpdateKeycardAuthenticationDevice();
}
private void UpdateKeycardAuthenticationDevice()
{
if (_user is not { } user || _stationKeycardAuthenticationDevice is not { } station)
return;
var canCall = !_entity.HasComponent<DVStationKeycardAuthenticationDeviceAlreadySwipedComponent>(user)
&& _entity.HasComponent<HumanoidProfileComponent>(user) // Can't be AI or a borg
&& station.Comp.AccessibleAfter <= _timing.CurTime;
CallZeta.ForceDisabled = !(canCall && station.Comp.SwipingFor is null or DVStationKeycardAction.Mayday);
CallZeta.Disabled = CallZeta.ForceDisabled;
RequestCodes.ForceDisabled = !(canCall && station.Comp.SwipingFor is null or DVStationKeycardAction.Scuttling);
RequestCodes.Disabled = RequestCodes.ForceDisabled;
if (CallZeta.IsConfirming || RequestCodes.IsConfirming)
return;
// Kinda messy but it works
if (station.Comp.SwipesStartedAt is { } firstSwipeTime)
{
var countdown = firstSwipeTime + station.Comp.SwipeWindow;
var timeRemaining = countdown.Subtract(_timing.CurTime);
if (timeRemaining > TimeSpan.Zero && station.Comp.SwipingFor is DVStationKeycardAction.Mayday)
CallZeta.Text = $"{timeRemaining:ss\\.fff}";
if (timeRemaining > TimeSpan.Zero && station.Comp.SwipingFor is DVStationKeycardAction.Scuttling)
RequestCodes.Text = $"{timeRemaining:ss\\.fff}";
}
else
{
CallZeta.Text = Loc.GetString("comms-console-menu-declare-mayday.button");
RequestCodes.Text = Loc.GetString("comms-console-menu-request-codes.button");
}
}
private void OnAnnouncePressed(BaseButton.ButtonEventArgs args)
{
OnMessage?.Invoke(new DVCommunicationsConsoleAnnouncementMessage(Rope.Collapse(AnnounceInput.TextRope)));
}
private void UpdateScreens()
{
OnMessage?.Invoke(new DVCommunicationsConsoleScreenConfigurationMessage(
(DVScreenContent) ScreenContentsButton.SelectedId,
ScreenAlertBorder.Pressed,
ScreenLine1.Text,
ScreenLine2.Text));
}
private void OnUpdateScreenText(BaseButton.ButtonEventArgs buttonEventArgs)
{
UpdateScreens();
}
private void OnScreenContentsSelected(OptionButton.ItemSelectedEventArgs itemSelectedEventArgs)
{
ScreenContentsButton.SelectId(itemSelectedEventArgs.Id);
UpdateScreens();
}
private void OnScreenAlertBorderToggled(BaseButton.ButtonToggledEventArgs buttonToggledEventArgs)
{
UpdateScreens();
}
private void OnExfiltrationPressed(BaseButton.ButtonEventArgs args)
{
OnMessage?.Invoke(new DVCommunicationsConsoleExfiltrationShuttleMessage(_console?.Comp.ExpectedExfiltrationArrival is null));
}
private void OnEmergencyPressed(BaseButton.ButtonEventArgs args)
{
OnMessage?.Invoke(new DVCommunicationsConsoleEvacuationShuttleMessage(_console?.Comp.ExpectedEvacuationArrival is null));
}
private void OnCallZetaPressed(BaseButton.ButtonEventArgs obj)
{
OnMessage?.Invoke(new DVCommunicationsConsoleKeycardAuthenticationDeviceMessage(DVStationKeycardAction.Mayday));
}
private void OnRequestCodesPressed(BaseButton.ButtonEventArgs obj)
{
OnMessage?.Invoke(new DVCommunicationsConsoleKeycardAuthenticationDeviceMessage(DVStationKeycardAction.Scuttling));
}
}
@@ -0,0 +1,26 @@
using Content.Shared._DV.Communications;
using Robust.Client.GameObjects;
namespace Content.Client._DV.Communications;
public sealed class DVCommunicationsConsoleSystem : SharedDVCommunicationsConsoleSystem
{
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DVCommunicationsConsoleComponent, AfterAutoHandleStateEvent>(OnHandleState);
}
private void OnHandleState(Entity<DVCommunicationsConsoleComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (!_userInterface.TryGetOpenUi<DVCommunicationsConsoleBoundUserInterface>(ent.Owner,
DVCommunicationsConsoleUi.Key,
out var bui))
return;
bui.Update(ent);
}
}
@@ -0,0 +1,5 @@
using Content.Shared._DV.KeycardAuthenticationDevice;
namespace Content.Client._DV.KeycardAuthenticationDevice;
public sealed class DVStationKeycardAuthenticationDeviceSystem : SharedDVStationKeycardAuthenticationDeviceSystem;
@@ -0,0 +1,104 @@
using System.Numerics;
using Content.Client.Graphics;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
namespace Content.Client._DV.Overlays;
/// <summary>
/// Makes darkness visible, and bright lights painfully visible
/// Tweakable. Algo is max((light*gain)^exp, lightFloor)
/// </summary>
public sealed class DarkVisionOverlay : Overlay
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld;
private readonly ProtoId<ShaderPrototype> _shaderProto = "DarkVision";
public float LightFloor = 0.5f;
public float LightGain = 2f;
public float LightExp = 1f;
private readonly ShaderInstance _copyShader;
private readonly ShaderInstance _remapShader;
private readonly OverlayResourceCache<CachedResources> _resources = new();
public DarkVisionOverlay()
{
IoCManager.InjectDependencies(this);
var proto = _prototype.Index<ShaderPrototype>(_shaderProto);
_remapShader = proto.InstanceUnique();
// With floor 0, gain 1, exp 1 the shader is an exact blend-mode-none copy.
_copyShader = proto.InstanceUnique();
_copyShader.SetParameter("lightFloor", 0f);
_copyShader.SetParameter("lightGain", 1f);
_copyShader.SetParameter("lightExp", 1f);
}
protected override void Draw(in OverlayDrawArgs args)
{
var viewport = args.Viewport;
var worldHandle = args.WorldHandle;
if (viewport.Eye == null)
return;
var lightTarget = viewport.LightRenderTarget;
var res = _resources.GetForViewport(viewport, static _ => new CachedResources());
if (res.ScratchTarget?.Size != lightTarget.Size)
{
res.ScratchTarget?.Dispose();
res.ScratchTarget = _clyde.CreateLightRenderTarget(lightTarget.Size, "darkvision-scratch", depthStencil: false);
}
var bounds = args.WorldBounds;
var lightScale = lightTarget.Size / (Vector2) viewport.Size;
var scale = viewport.RenderScale / (Vector2.One / lightScale);
var localMatrix = lightTarget.GetWorldToLocalMatrix(viewport.Eye, scale);
// Copy the light buffer aside first: a texture can't be sampled while it is also the
// render target being drawn into.
worldHandle.RenderInRenderTarget(res.ScratchTarget, () =>
{
worldHandle.UseShader(_copyShader);
worldHandle.SetTransform(localMatrix);
worldHandle.DrawTextureRect(lightTarget.Texture, bounds);
worldHandle.UseShader(null);
}, Color.Black);
// Then write it back through the remap.
_remapShader.SetParameter("lightFloor", LightFloor);
_remapShader.SetParameter("lightGain", LightGain);
_remapShader.SetParameter("lightExp", LightExp);
worldHandle.RenderInRenderTarget(lightTarget, () =>
{
worldHandle.UseShader(_remapShader);
worldHandle.SetTransform(localMatrix);
worldHandle.DrawTextureRect(res.ScratchTarget.Texture, bounds);
worldHandle.UseShader(null);
}, null);
}
protected override void DisposeBehavior()
{
_resources.Dispose();
base.DisposeBehavior();
}
private sealed class CachedResources : IDisposable
{
public IRenderTexture? ScratchTarget;
public void Dispose()
{
ScratchTarget?.Dispose();
}
}
}
@@ -0,0 +1,56 @@
using Content.Shared._DV.Overlays.Components;
using Robust.Client.Graphics;
using Robust.Shared.Player;
namespace Content.Client._DV.Overlays;
public sealed class DarkVisionSystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly ISharedPlayerManager _playerMan = default!;
private DarkVisionOverlay _overlay = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DarkVisionComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<DarkVisionComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<DarkVisionComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<DarkVisionComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
_overlay = new();
}
private void OnInit(Entity<DarkVisionComponent> ent, ref ComponentInit args)
{
if (ent.Owner == _playerMan.LocalEntity)
EnableOverlay(ent.Comp);
}
private void OnShutdown(Entity<DarkVisionComponent> ent, ref ComponentShutdown args)
{
if (ent.Owner == _playerMan.LocalEntity)
_overlayMan.RemoveOverlay(_overlay);
}
private void OnPlayerAttached(Entity<DarkVisionComponent> ent, ref LocalPlayerAttachedEvent args)
{
EnableOverlay(ent.Comp);
}
private void OnPlayerDetached(Entity<DarkVisionComponent> ent, ref LocalPlayerDetachedEvent args)
{
_overlayMan.RemoveOverlay(_overlay);
}
private void EnableOverlay(DarkVisionComponent comp)
{
_overlay.LightFloor = comp.LightFloor;
_overlay.LightGain = comp.LightGain;
_overlay.LightExp = comp.LightExp;
if (!_overlayMan.HasOverlay<DarkVisionOverlay>())
_overlayMan.AddOverlay(_overlay);
}
}
@@ -0,0 +1,103 @@
using System.Globalization;
using Content.Client.GameTicking.Managers;
using Content.Client.Interactable.Components;
using Content.Client.TextScreen;
using Content.Shared._DV.Screens;
using Robust.Shared.Timing;
namespace Content.Client._DV.Screens;
public sealed class DVScreenSystem : DVSharedScreenSystem
{
[Dependency] private readonly DVTextVisualsSystem _textVisuals = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ClientGameTicker _ticker = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DVScreenComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<DVScreenComponent, AfterAutoHandleStateEvent>(OnScreenState);
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
var query = EntityQueryEnumerator<DVScreenComponent>();
while (query.MoveNext(out var uid, out var screen))
{
switch (screen.Content)
{
case DVScreenContent.Text:
break;
case DVScreenContent.CurrentTime:
CurrentTime((uid, screen));
break;
case DVScreenContent.EstimatedTimeOfArrival:
EstimatedTimeOfArrival((uid, screen));
break;
case DVScreenContent.AlertLevel:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
private void OnInit(Entity<DVScreenComponent> ent, ref ComponentInit args)
{
// Remove interaction outline because it outlines the offscreen text and looks bad
RemComp<InteractionOutlineComponent>(ent);
}
private void OnScreenState(Entity<DVScreenComponent> ent, ref AfterAutoHandleStateEvent args)
{
UpdateVisuals(ent);
switch (ent.Comp.Content)
{
case DVScreenContent.Text:
Text(ent);
break;
case DVScreenContent.CurrentTime:
CurrentTime(ent);
break;
case DVScreenContent.EstimatedTimeOfArrival:
EstimatedTimeOfArrival(ent);
break;
case DVScreenContent.AlertLevel:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void Text(Entity<DVScreenComponent> ent)
{
_textVisuals.SetText(ent.Owner, ent.Comp.Line1, ent.Comp.Line2);
}
private void CurrentTime(Entity<DVScreenComponent> ent)
{
var time = (_timing.CurTime - _ticker.RoundStartTimeSpan).Duration();
_textVisuals.SetText(ent.Owner, Loc.GetString("status-display-time"), time.ToString("hh\\:mm"));
}
private void EstimatedTimeOfArrival(Entity<DVScreenComponent> ent)
{
if (ent.Comp.TargetTime <= _timing.CurTime)
{
_textVisuals.SetText(ent.Owner, string.Empty, string.Empty);
return;
}
var time = (_timing.CurTime - ent.Comp.TargetTime).Duration();
var formatted = time.ToString("mm\\:ss");
Log.Debug($"{_timing.CurTime} - {ent.Comp.TargetTime} = {time}");
var title = ent.Comp.ScreenIsAtDestination ? Loc.GetString("status-display-etd") : Loc.GetString("status-display-eta");
_textVisuals.SetText(ent.Owner, title, formatted);
}
}
@@ -0,0 +1,140 @@
using System.Linq;
using System.Numerics;
using System.Threading;
using JetBrains.Annotations;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
namespace Content.Client._DV.Screens;
[UsedImplicitly]
public sealed class DVTextRenderingOverlay : Overlay
{
[Dependency] private readonly IClyde _clyde = default!;
private readonly SpriteSystem _sprite;
private readonly AnimationPlayerSystem _animationPlayer;
public override OverlaySpace Space => OverlaySpace.ScreenSpaceBelowWorld;
private readonly Queue<(Entity<DVTextVisualsComponent> Entity, Font Font, CancellationToken Cancellation)> _queue = new();
public const string MarqueeKey = "dv-text-screen-marquee";
public DVTextRenderingOverlay(SpriteSystem sprite, AnimationPlayerSystem animationPlayer)
{
IoCManager.InjectDependencies(this);
_sprite = sprite;
_animationPlayer = animationPlayer;
ZIndex = -100; // this needs to render before almost everything
}
public CancellationTokenSource QueueRender(
Entity<DVTextVisualsComponent> ent,
Font font)
{
var source = new CancellationTokenSource();
_queue.Enqueue((ent, font, source.Token));
return source;
}
protected override void Draw(in OverlayDrawArgs args)
{
var screenHandle = args.ScreenHandle;
while (_queue.TryDequeue(out var queued))
{
if (queued.Cancellation.IsCancellationRequested)
continue;
var font = queued.Font;
foreach (var row in queued.Entity.Comp.Rows)
{
if (row.Text == string.Empty)
{
_sprite.LayerSetTexture(queued.Entity.Owner, row.Layer, null);
continue;
}
var dimensions = screenHandle.GetDimensions(queued.Font, row.Text, 1f);
var dimensionsInt = new Vector2i((int)MathF.Round(dimensions.X), (int)MathF.Round(dimensions.Y));
if (row.Texture is null || row.Texture.Size != dimensionsInt)
{
row.Texture = _clyde.CreateRenderTarget(dimensionsInt,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8),
name: $"dv-text-visuals-{queued.Entity.Owner.Id}");
_sprite.LayerSetTexture(queued.Entity.Owner, row.Layer, row.Texture.Texture);
_sprite.LayerSetOffset(queued.Entity.Owner, row.Layer, row.Offset);
}
args.DrawingHandle.RenderInRenderTarget(row.Texture,
() =>
{
screenHandle.DrawString(font, Vector2.Zero, row.Text);
},
Color.Transparent);
}
_animationPlayer.Stop(queued.Entity.Owner, MarqueeKey);
if (CreateMarqueeAnimation(queued.Entity) is { } animation)
{
queued.Entity.Comp.Animation = animation;
_animationPlayer.Play(queued.Entity.Owner, animation, MarqueeKey);
}
}
}
private Animation? CreateMarqueeAnimation(Entity<DVTextVisualsComponent> ent)
{
var largestRowWidth = ent.Comp.Rows.Aggregate(0, (i, row) => Math.Max(i, row.Texture?.Size.X ?? 0));
var animationTime = ent.Comp.MarqueeRate * largestRowWidth;
var marqueeWidth = new Vector2((float)ent.Comp.MarqueeWidth / EyeManager.PixelsPerMeter, 0);
var animation = new Animation
{
Length = animationTime,
};
foreach (var row in ent.Comp.Rows)
{
if (row.Texture is null)
continue;
var rowHalfWidth = new Vector2(row.Texture.Size.X / 2f / EyeManager.PixelsPerMeter, 0f);
if (row.Texture.Size.X <= ent.Comp.MarqueeWidth)
continue;
animation.AnimationTracks.Add(new AnimationTrackLayerOffset()
{
Layer = row.Layer,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(row.Offset + rowHalfWidth + marqueeWidth, 0f),
new AnimationTrackProperty.KeyFrame(row.Offset - rowHalfWidth - marqueeWidth, (float)animationTime.TotalSeconds),
},
});
}
return animation.AnimationTracks.Count > 0 ? animation : null;
}
public sealed class AnimationTrackLayerOffset : AnimationTrackProperty
{
public required Enum Layer;
private readonly SpriteSystem _sprite = IoCManager.Resolve<IEntityManager>().System<SpriteSystem>();
protected override void ApplyProperty(object context, object value)
{
if (value is not Vector2 vector)
throw new InvalidOperationException("Value must be a .");
_sprite.LayerSetOffset((EntityUid) context, Layer, vector);
}
}
}
@@ -0,0 +1,43 @@
using System.Numerics;
using System.Threading;
using Content.Shared._DV.Screens;
using Robust.Client.Animations;
using Robust.Client.Graphics;
namespace Content.Client._DV.Screens;
[RegisterComponent]
[Access(typeof(DVTextVisualsSystem), typeof(DVTextRenderingOverlay))]
public sealed partial class DVTextVisualsComponent : Component
{
[DataField(required: true)]
public List<DVTextVisualsRow> Rows;
[DataField]
public TimeSpan MarqueeRate = TimeSpan.FromSeconds(0.045f);
[DataField]
public int MarqueeWidth = 24;
[DataField]
public int MarqueePadding = 8;
public Animation? Animation;
public CancellationTokenSource? Token;
}
[DataDefinition]
public sealed partial class DVTextVisualsRow
{
public IRenderTexture? Texture;
[DataField]
public string Text;
[DataField]
public Vector2 Offset;
[DataField(required: true)]
public Enum Layer = DVTextScreenVisualLayers.Line1;
}
@@ -0,0 +1,73 @@
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
namespace Content.Client._DV.Screens;
public sealed class DVTextVisualsSystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlay = default!;
[Dependency] private readonly IResourceCache _resource = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
private DVTextRenderingOverlay _textRendering = default!;
private Font _font = default!;
public override void Initialize()
{
base.Initialize();
_textRendering = new(_sprite, _animationPlayer);
_overlay.AddOverlay(_textRendering);
_font = new VectorFont(_resource.GetResource<FontResource>("/Fonts/_DV/TinyUnicode.ttf"), 12);
SubscribeLocalEvent<DVTextVisualsComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<DVTextVisualsComponent, ComponentShutdown>(OnComponentShutdown);
SubscribeLocalEvent<DVTextVisualsComponent, AnimationCompletedEvent>(OnAnimationComplete);
}
public override void Shutdown()
{
_overlay.RemoveOverlay(_textRendering);
}
private void OnComponentInit(Entity<DVTextVisualsComponent> ent, ref ComponentInit args)
{
ent.Comp.Token = _textRendering.QueueRender(ent, _font);
}
private void OnComponentShutdown(Entity<DVTextVisualsComponent> ent, ref ComponentShutdown args)
{
foreach (var row in ent.Comp.Rows)
{
row.Texture?.Dispose();
}
ent.Comp.Token?.Cancel();
}
private void OnAnimationComplete(Entity<DVTextVisualsComponent> ent, ref AnimationCompletedEvent args)
{
if (args.Key != DVTextRenderingOverlay.MarqueeKey || !args.Finished || ent.Comp.Animation is not { } animation)
return;
_animationPlayer.Play(ent.Owner, animation, DVTextRenderingOverlay.MarqueeKey);
}
public void SetText(Entity<DVTextVisualsComponent?> ent, params string[] rows)
{
if (!Resolve(ent, ref ent.Comp))
return;
var count = Math.Min(rows.Length, ent.Comp.Rows.Count);
for (var i = 0; i < count; i++)
{
ent.Comp.Rows[i].Text = rows[i];
}
ent.Comp.Token?.Cancel();
ent.Comp.Token = _textRendering.QueueRender((ent, ent.Comp), _font);
}
}
@@ -0,0 +1,23 @@
using Content.Client.Resources;
using Content.Client.Stylesheets;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using static Content.Client.Stylesheets.StylesheetHelpers;
namespace Content.Client._DV.Stylesheets.Sheetlets;
[CommonSheetlet]
public sealed class DVLineEditSheetlet : Sheetlet<PalettedStylesheet>
{
public override StyleRule[] GetRules(PalettedStylesheet sheet, object config)
{
var tinyUnicode = ResCache.GetFont("/Fonts/_DV/TinyUnicode.ttf", size: 24);
return
[
E<LineEdit>()
.Class("comms-console-display")
.Font(tinyUnicode),
];
}
}
@@ -1,4 +1,5 @@
using Content.Server.Administration.Managers;
using Content.Shared._DV.Administration.Components; // DeltaV - Unorbitable
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.Utility;
@@ -6,7 +7,7 @@ using Robust.Shared.Utility;
namespace Content.Server.Administration.Commands;
[AdminCommand(AdminFlags.Stealth)]
public sealed class StealthminCommand : LocalizedCommands
public sealed class StealthminCommand : LocalizedEntityCommands // DeltaV - Unorbitable, LocalizedCommands to LocalizedEntityCommands
{
[Dependency] private readonly IAdminManager _adminManager = default!;
@@ -26,8 +27,20 @@ public sealed class StealthminCommand : LocalizedCommands
DebugTools.AssertNotNull(adminData);
if (!adminData!.Stealth)
{
_adminManager.Stealth(player);
// DeltaV - Unorbitable START
if (player.AttachedEntity is { } attachedEntity)
EntityManager.EnsureComponent<UnorbitableComponent>(attachedEntity);
// DeltaV END
}
else
{
_adminManager.UnStealth(player);
// DeltaV - Unorbitable START
if (player.AttachedEntity is { } attachedEntity)
EntityManager.RemoveComponent<UnorbitableComponent>(attachedEntity);
// DeltaV END
}
}
}
@@ -1,6 +1,7 @@
using Content.Server.Chat.Systems;
using Content.Server.GameTicking;
using Content.Server.Ninja.Systems;
using Content.Shared._DV.Communications; // DeltaV Comms Console
using Content.Shared.Communications;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
@@ -35,7 +36,7 @@ public sealed class CommsHackerSystem : SharedCommsHackerSystem
/// </summary>
private void OnBeforeInteractHand(EntityUid uid, CommsHackerComponent comp, BeforeInteractHandEvent args)
{
if (args.Handled || !HasComp<CommunicationsConsoleComponent>(args.Target))
if (args.Handled || !HasComp<DVCommunicationsConsoleComponent>(args.Target)) // DeltaV - Comms Console
return;
// TODO: generic check event
@@ -23,7 +23,7 @@ using Robust.Shared.Configuration;
namespace Content.Server.Communications
{
public sealed partial class CommunicationsConsoleSystem : EntitySystem // DeltaV - Partial Class
public sealed class CommunicationsConsoleSystem : EntitySystem
{
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
[Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!;
@@ -53,8 +53,6 @@ namespace Content.Server.Communications
SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleCallEmergencyShuttleMessage>(OnCallShuttleMessage);
SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleRecallEmergencyShuttleMessage>(OnRecallShuttleMessage);
InitializeExfiltration(); // DeltaV - Exfiltration shuttle
// On console init, set cooldown
SubscribeLocalEvent<CommunicationsConsoleComponent, MapInitEvent>(OnCommunicationsConsoleMapInit);
}
@@ -138,7 +136,6 @@ namespace Content.Server.Communications
List<string>? levels = null;
string currentLevel = default!;
float currentDelay = 0;
TimeSpan? exfiltrationTime = null; // DeltaV - exfiltration shuttle
if (stationUid != null)
{
@@ -160,12 +157,6 @@ namespace Content.Server.Communications
currentLevel = alertComp.CurrentLevel;
currentDelay = _alertLevelSystem.GetAlertLevelDelay(stationUid.Value, alertComp);
}
// Begin DeltaV - exfiltration shuttle
if (TryComp<StationExfiltrationComponent>(stationUid, out var exfiltration))
{
exfiltrationTime = exfiltration.ArrivalTime;
}
// End DeltaV - exfiltration shuttle
}
_uiSystem.SetUiState(uid, CommunicationsConsoleUiKey.Key, new CommunicationsConsoleInterfaceState(
@@ -174,8 +165,7 @@ namespace Content.Server.Communications
levels,
currentLevel,
currentDelay,
_roundEndSystem.ExpectedCountdownEnd,
exfiltrationTime // DeltaV - exfiltration shuttle
_roundEndSystem.ExpectedCountdownEnd
));
}
@@ -1,3 +1,4 @@
using Content.Server.RoundEnd; // DeltaV
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.GameTicking.Rules.Components;
@@ -43,4 +44,16 @@ public sealed partial class XenoborgsRuleComponent : Component
/// </summary>
[DataField]
public bool XenoborgShuttleCalled = false;
/// <summary>
/// DeltaV - The behavior of the round if the mothership core is deleted.
/// </summary>
[DataField]
public RoundEndBehavior XenoborgRoundEndBehavior = RoundEndBehavior.BecomeSurvival;
/// <summary>
/// DeltaV - The amount of time before the evac shuttle will arrive if the XenoborgRoundEndBehavior is set to ShuttleCall.
/// </summary>
[DataField]
public TimeSpan XenoborgShuttleDelay = TimeSpan.FromMinutes(10);
}
@@ -30,10 +30,20 @@ public sealed partial class ZombieRuleComponent : Component
/// <summary>
/// DeltaV - The behavior of the round if all zombies are defeated.
/// </summary>
[DataField]
public RoundEndBehavior ZombieRoundEndBehavior = RoundEndBehavior.BecomeSurvival;
/// <summary>
/// DeltaV - The amount of time before the evac shuttle will arrive if the ZombieRoundEndBehavior is set to ShuttleCall.
/// </summary>
[DataField]
public TimeSpan ZombieShuttleDelay = TimeSpan.FromMinutes(10);
/// <summary>
/// DeltaV - If true, ZombieRuleSystem will do the ZombieRoundEndBehavior if zombies are defeated.
/// This is needed because ZombieOutbreak uses the ZombieRuleComponent but the system won't know if
/// it was roundstart or not.
/// </summary>
[DataField]
public bool IsRoundStartZombies = false;
}
@@ -520,18 +520,17 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
// BEGIN DeltaV - Detect Nukie Failure Better
// We need to use a EntityQueryEnumerator instead of a EntityQuery, because we need the uid to check for cuffs
var operatives = new List<Entity<NukeOperativeComponent, MobStateComponent, TransformComponent>>();
var operativesEnumerator = EntityQueryEnumerator<NukeOperativeComponent, MobStateComponent, TransformComponent>();
while (operativesEnumerator.MoveNext(out var uid, out var nukeOp, out var mobState, out var transform))
var operatives = new List<Entity<NukeOperativeComponent, MobStateComponent>>();
var operativesEnumerator = EntityQueryEnumerator<NukeOperativeComponent, MobStateComponent>();
while (operativesEnumerator.MoveNext(out var uid, out var nukeOp, out var mobState))
{
operatives.Add((uid, nukeOp, mobState, transform));
operatives.Add((uid, nukeOp, mobState));
}
var operativesAlive = operatives
.Where(op => op.Comp3.MapID == shuttleMapId
|| op.Comp3.MapID == targetStationMap)
.Any(op => op.Comp2.CurrentState == MobState.Alive && op.Comp1.Running &&
!TryComp<CuffableComponent>(op, out _)); // in the case the crew keeps one alive
.Any(op => op.Comp1.Running
&& op.Comp2.CurrentState == MobState.Alive
&& TryComp<CuffableComponent>(op, out var cuffable) && cuffable.CuffedHandCount == 0); // in the case the crew keeps one alive
// END DeltaV
if (operativesAlive)
@@ -42,6 +42,14 @@ public sealed class XenoborgsRuleSystem : GameRuleSystem<XenoborgsRuleComponent>
colorOverride: AnnouncmentColor);
ent.Comp.MothershipCoreDeathAnnouncmentSent = true;
// BEGIN DeltaV - Convert to Survival
if (ent.Comp.XenoborgRoundEndBehavior != RoundEndBehavior.Nothing)
{
_roundEnd.DoRoundEndBehavior(ent.Comp.XenoborgRoundEndBehavior, ent.Comp.XenoborgShuttleDelay);
ent.Comp.XenoborgRoundEndBehavior = RoundEndBehavior.Nothing;
}
// END DeltaV
}
// TODO: Refactor the end of round text
@@ -115,29 +115,30 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
/// </summary>
private void CheckRoundEnd(ZombieRuleComponent zombieRuleComponent)
{
// BEGIN DeltaV - Change mode to survival if zombies die
// Zombies have failed to launch and the mode has been switched to survival
if (zombieRuleComponent.ZombieRoundEndBehavior == RoundEndBehavior.Nothing)
return;
// END DeltaV
var healthy = GetHealthyHumans();
if (healthy.Count == 1) // Only one human left. spooky
_popup.PopupEntity(Loc.GetString("zombie-alone"), healthy[0], healthy[0]);
// BEGIN DeltaV - Change mode to survival if zombies die
var anyInitialInfectedAlive = EntityQuery<InitialInfectedComponent, MobStateComponent>(false)
.Any(x => x.Item2.CurrentState != MobState.Dead);
var anyLivingZombies = EntityQuery<ZombieComponent, MobStateComponent>(false)
.Any(x => x.Item2.CurrentState != MobState.Dead);
var anyPendingZombies = EntityQuery<PendingZombieComponent>(false).Any();
// All II turned/dead, all entities that have turned are dead, and there are no more pending zombies
if (!anyInitialInfectedAlive && !anyLivingZombies && !anyPendingZombies)
if (zombieRuleComponent.IsRoundStartZombies)
{
_roundEnd.DoRoundEndBehavior(zombieRuleComponent.ZombieRoundEndBehavior, zombieRuleComponent.ZombieShuttleDelay);
zombieRuleComponent.ZombieRoundEndBehavior = RoundEndBehavior.Nothing; // stop this check in the future
// Zombies have failed to launch and the mode has been switched to survival
if (zombieRuleComponent.ZombieRoundEndBehavior == RoundEndBehavior.Nothing)
return;
// BEGIN DeltaV - Change mode to survival if zombies die
var anyInitialInfectedAlive = EntityQuery<InitialInfectedComponent, MobStateComponent>(false)
.Any(x => x.Item2.CurrentState != MobState.Dead);
var anyLivingZombies = EntityQuery<ZombieComponent, MobStateComponent>(false)
.Any(x => x.Item2.CurrentState != MobState.Dead);
var anyPendingZombies = EntityQuery<PendingZombieComponent>(false).Any();
// All II turned/dead, all entities that have turned are dead, and there are no more pending zombies
if (!anyInitialInfectedAlive && !anyLivingZombies && !anyPendingZombies)
{
_roundEnd.DoRoundEndBehavior(zombieRuleComponent.ZombieRoundEndBehavior, zombieRuleComponent.ZombieShuttleDelay);
zombieRuleComponent.ZombieRoundEndBehavior = RoundEndBehavior.Nothing; // stop this check in the future
}
}
// END DeltaV
+7
View File
@@ -29,6 +29,7 @@ using Content.Shared.Movement.Systems;
using Content.Shared.NameModifier.EntitySystems;
using Content.Shared.Popups;
using Content.Server.Preferences.Managers; // DeltaV
using Content.Shared._DV.Administration.EntitySystems; // DeltaV - Unorbitable
using Content.Shared.Storage.Components;
using Content.Shared.Tag;
using Content.Shared.Warps;
@@ -75,6 +76,7 @@ namespace Content.Server.Ghost
[Dependency] private readonly GhostSpriteStateSystem _ghostState = default!;
[Dependency] private readonly IServerPreferencesManager _preferencesManager = default!; // DeltaV
[Dependency] private readonly IAdminManager _admin = default!; // DeltaV
[Dependency] private readonly UnorbitableSystem _unorbitable = default!; // DeltaV - Unorbitable
private EntityQuery<GhostComponent> _ghostQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
@@ -341,6 +343,11 @@ namespace Content.Server.Ghost
return;
}
// DeltaV - Unorbitable START
if (!_unorbitable.CanFollow(attached, realTarget))
return;
// DeltaV END
WarpTo(attached, realTarget);
}
@@ -30,6 +30,17 @@ public sealed partial class EmergencyLightComponent : SharedEmergencyLightCompon
[DataField("chargingEfficiency")]
public float ChargingEfficiency = 0.85f;
// Begin DeltaV Additions
[DataField]
public float MaydayRadius = 10f;
[DataField]
public float MaydayFalloff = 0f;
[DataField]
public float MaydayEnergy = 0.7f;
// End DeltaV Additions
public Dictionary<EmergencyLightState, string> BatteryStateText = new()
{
{ EmergencyLightState.Full, "emergency-light-component-light-state-full" },
@@ -85,11 +85,11 @@ public sealed partial class HealthAnalyzerSystem : EntitySystem // DeltaV - Made
if (component.MaxScanRange != null && !_transformSystem.InRange(patientCoordinates, transform.Coordinates, component.MaxScanRange.Value))
{
//Range too far, disable updates
PauseAnalyzingEntity((uid, component), patient); // DeltaV - Analyzer Reactivation
PauseAnalyzingEntity((uid, component), patient);
continue;
}
component.IsAnalyzerActive = true; // DeltaV - Analyzer Reactivation
component.IsAnalyzerActive = true;
UpdateScannedUser(uid, patient, true);
}
}
@@ -197,7 +197,7 @@ public sealed partial class HealthAnalyzerSystem : EntitySystem // DeltaV - Made
}
/// <summary>
/// DeltaV - If the scanner is active, sends one last update and sets it to inactive.
/// If the scanner is active, sends one last update and sets it to inactive.
/// </summary>
/// <param name="healthAnalyzer">The health analyzer that's receiving the updates</param>
/// <param name="target">The entity to analyze</param>
@@ -22,6 +22,7 @@ using Content.Shared.StatusEffect;
using Content.Shared.Store.Components;
using Content.Shared.Stunnable;
using Content.Shared.Tag;
using Content.Shared.Weapons.Melee.Events; // DeltaV
using Robust.Server.GameObjects;
using Robust.Shared.Physics.Components;
using Robust.Shared.Prototypes;
@@ -64,6 +65,7 @@ public sealed partial class RevenantSystem : EntitySystem
SubscribeLocalEvent<RevenantComponent, StatusEffectAddedEvent>(OnStatusAdded);
SubscribeLocalEvent<RevenantComponent, StatusEffectEndedEvent>(OnStatusEnded);
SubscribeLocalEvent<RoundEndTextAppendEvent>(_ => MakeVisible(true));
SubscribeLocalEvent<RevenantComponent, MeleeAttackEvent>(OnMeleeAttack); // DeltaV
SubscribeLocalEvent<RevenantComponent, GetVisMaskEvent>(OnRevenantGetVis);
@@ -131,6 +133,14 @@ public sealed partial class RevenantSystem : EntitySystem
ChangeEssenceAmount(uid, essenceDamage, component);
}
/// <summary>
/// DeltaV - No more attack spam and remaining invisible
/// </summary>
private void OnMeleeAttack(Entity<RevenantComponent> ent, ref MeleeAttackEvent args)
{
_statusEffects.TryAddStatusEffect<CorporealComponent>(ent, "Corporeal", ent.Comp.MeleeRevealTime, false);
}
public bool ChangeEssenceAmount(EntityUid uid, FixedPoint2 amount, RevenantComponent? component = null, bool allowDeath = true, bool regenCap = false)
{
if (!Resolve(uid, ref component))
@@ -41,7 +41,8 @@ namespace Content.Server.StationEvents
if (TryComp<NextEventComponent>(uid, out var nextEventComponent)
&& _event.TryGenerateRandomEvent(component.ScheduledGameRules, TimeSpan.FromSeconds(component.TimeUntilNextEvent)) is {} firstEvent)
{
_chatManager.SendAdminAlert(Loc.GetString("station-event-system-run-event-delayed", ("eventName", firstEvent), ("seconds", (int)component.TimeUntilNextEvent)));
var nextEventTime = GameTicker.RoundDuration() + TimeSpan.FromSeconds(component.TimeUntilNextEvent);
_chatManager.SendAdminAlert(Loc.GetString("station-event-system-run-event-delayed", ("eventName", firstEvent), ("time", nextEventTime.ToString(@"hh\:mm\:ss"))));
_next.UpdateNextEvent(nextEventComponent, firstEvent, GameTicker.RoundDuration() + TimeSpan.FromSeconds(component.TimeUntilNextEvent));
}
// End DeltaV Additions
@@ -81,7 +82,7 @@ namespace Content.Server.StationEvents
if (_event.TryGenerateRandomEvent(eventScheduler.ScheduledGameRules, nextEventTime) is not {} generatedEvent)
continue;
_chatManager.SendAdminAlert(Loc.GetString("station-event-system-run-event-delayed", ("eventName", generatedEvent), ("seconds", (int)eventScheduler.TimeUntilNextEvent)));
_chatManager.SendAdminAlert(Loc.GetString("station-event-system-run-event-delayed", ("eventName", generatedEvent), ("time", nextEventTime.ToString(@"hh\:mm\:ss"))));
// Cycle the stashed event with the new generated event and time.
var storedEvent = _next.UpdateNextEvent(nextEventComponent, generatedEvent, nextEventTime);
if (string.IsNullOrEmpty(storedEvent)) //If there was no stored event don't try to run it.
@@ -14,9 +14,12 @@ public sealed class RampingStationEventSchedulerSystem : GameRuleSystem<RampingS
[Dependency] private readonly IChatManager _chatManager = default!; // DeltaV
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly EventManagerSystem _event = default!;
#if DEBUG // DeltaV - Only used if built by Debug
[Dependency] private readonly GameTicker _gameTicker = default!;
#endif
[Dependency] private readonly NextEventSystem _next = default!; // DeltaV
[Dependency] private readonly IGameTiming _timing = default!; // DeltaV
/* DeltaV
/// <summary>
/// Returns the ChaosModifier which increases as round time increases to a point.
@@ -50,7 +53,8 @@ public sealed class RampingStationEventSchedulerSystem : GameRuleSystem<RampingS
if (TryComp<NextEventComponent>(uid, out var nextEventComponent)
&& _event.TryGenerateRandomEvent(component.ScheduledGameRules, TimeSpan.FromSeconds(component.TimeUntilNextEvent)) is {} firstEvent)
{
_chatManager.SendAdminAlert(Loc.GetString("station-event-system-run-event-delayed", ("eventName", firstEvent), ("seconds", (int)component.TimeUntilNextEvent)));
var nextEventTime = GameTicker.RoundDuration() + TimeSpan.FromSeconds(component.TimeUntilNextEvent);
_chatManager.SendAdminAlert(Loc.GetString("station-event-system-run-event-delayed", ("eventName", firstEvent), ("time", nextEventTime.ToString(@"hh\:mm\:ss"))));
_next.UpdateNextEvent(nextEventComponent, firstEvent, GameTicker.RoundDuration() + TimeSpan.FromSeconds(component.TimeUntilNextEvent));
}
// End DeltaV Additions: init NextEventComp
@@ -83,7 +87,7 @@ public sealed class RampingStationEventSchedulerSystem : GameRuleSystem<RampingS
if (_event.TryGenerateRandomEvent(scheduler.ScheduledGameRules, nextEventTime) is not {} generatedEvent)
continue;
_chatManager.SendAdminAlert(Loc.GetString("station-event-system-run-event-delayed", ("eventName", generatedEvent), ("seconds", (int)scheduler.TimeUntilNextEvent)));
_chatManager.SendAdminAlert(Loc.GetString("station-event-system-run-event-delayed", ("eventName", generatedEvent), ("time", nextEventTime.ToString(@"hh\:mm\:ss"))));
// Cycle the stashed event with the new generated event and time.
string? storedEvent = _next.UpdateNextEvent(nextEventComponent, generatedEvent, nextEventTime);
if (string.IsNullOrEmpty(storedEvent)) //If there was no stored event don't try to run it.
@@ -114,13 +118,28 @@ public sealed class RampingStationEventSchedulerSystem : GameRuleSystem<RampingS
// Begin DeltaV Additions
var averageTimeUntilNextEvent = component.TimeKeyPoints[0].Y;
var timeUntilNextEventDeviation = _random.NextFloat(-1f, 1f) * component.TimeDeviation;
var roundTime = (float)_gameTicker.RoundDuration().TotalMinutes;
var absoluteTimePoint = 0f;
var ruleActivated = TimeSpan.FromSeconds(0);
if (TryComp<GameRuleComponent>(uid, out var gameRule))
{
ruleActivated = gameRule.ActivatedAt;
}
var ruleTime = _timing.CurTime.Subtract(ruleActivated);
#if DEBUG
_chatManager.SendAdminAlert(Loc.GetString("station-event-system-debug-round-time", ("time", _gameTicker.RoundDuration())));
_chatManager.SendAdminAlert(Loc.GetString("station-event-system-debug-ramping-time", ("time", ruleActivated)));
// This will be what the keypoint system looks at to determine where to start ramping
_chatManager.SendAdminAlert(Loc.GetString("station-event-system-debug-keypoint-time", ("time", ruleTime)));
_chatManager.SendAdminAlert("_______________"); // Easier to read
#endif
var absoluteTimePoint = 0f;
foreach (var point in component.TimeKeyPoints)
{
absoluteTimePoint += point.X;
if (roundTime >= absoluteTimePoint)
if (ruleTime.TotalMinutes >= absoluteTimePoint)
averageTimeUntilNextEvent = point.Y;
}
@@ -1,32 +0,0 @@
using Content.Server._DV.Station.Components;
using Content.Server._DV.Station.Systems;
using Content.Shared._DV.Communications;
namespace Content.Server.Communications;
public sealed partial class CommunicationsConsoleSystem : EntitySystem
{
[Dependency] private readonly StationExfiltrationSystem _stationExfiltration = default!;
private void InitializeExfiltration()
{
SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleExfiltrationShuttleMessage>(OnExfiltrationMessage);
}
private void OnExfiltrationMessage(Entity<CommunicationsConsoleComponent> ent, ref CommunicationsConsoleExfiltrationShuttleMessage args)
{
if (_stationSystem.GetOwningStation(ent) is not { } station)
return;
if (!CanUse(args.Actor, ent))
{
_popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), ent, args.Actor);
return;
}
if (args.Call)
_stationExfiltration.Call(station);
else
_stationExfiltration.Recall(station);
}
}
@@ -0,0 +1,184 @@
using Content.Server._DV.Station.Components;
using Content.Server._DV.Station.Systems;
using Content.Server.AlertLevel;
using Content.Server.Communications;
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Systems;
using Content.Shared._DV.Communications;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Station;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
namespace Content.Server._DV.Communications;
public sealed class DVCommunicationsConsoleSystem : SharedDVCommunicationsConsoleSystem
{
[Dependency] private readonly SharedStationSystem _station = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly AlertLevelSystem _alertLevel = default!;
[Dependency] private readonly StationExfiltrationSystem _stationExfiltration = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RoundEndSystemChangedEvent>(OnRoundEndChanged);
SubscribeLocalEvent<StationExfiltrationChangedEvent>(OnExfiltrationChanged);
SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertLevelChanged);
}
private void OnAlertLevelChanged(AlertLevelChangedEvent ev)
{
var query = EntityQueryEnumerator<DVCommunicationsConsoleComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (_station.GetOwningStation(uid) != ev.Station)
continue;
var alertLevel = Comp<AlertLevelComponent>(ev.Station);
comp.CanSetAlertAt = Timing.CurTime + TimeSpan.FromSeconds(alertLevel.CurrentDelay);
comp.CurrentAlertLevel = ev.AlertLevel;
if (alertLevel.IsLevelLocked)
comp.CanSetAlertAt = null;
Dirty(uid, comp);
}
}
private void OnExfiltrationChanged(ref StationExfiltrationChangedEvent ev)
{
var query = EntityQueryEnumerator<DVCommunicationsConsoleComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (_station.GetOwningStation(uid) != ev.Station)
continue;
comp.ExpectedExfiltrationArrival = ev.Station.Comp.ArrivalTime;
Dirty(uid, comp);
}
}
private void OnRoundEndChanged(RoundEndSystemChangedEvent ev)
{
var query = EntityQueryEnumerator<DVCommunicationsConsoleComponent>();
while (query.MoveNext(out var uid, out var comp))
{
comp.ExpectedEvacuationArrival = _roundEnd.ExpectedCountdownEnd;
comp.ExpectedEvacuationDuration = _roundEnd.ExpectedShuttleLength;
comp.ShuttlesCallable = ShuttlesCallable();
Dirty(uid, comp);
}
}
protected override void OnMapInit(Entity<DVCommunicationsConsoleComponent> ent, ref MapInitEvent args)
{
base.OnMapInit(ent, ref args);
if (_station.GetOwningStation(ent) is not { } station)
return;
if (!TryComp<AlertLevelComponent>(station, out var alertLevel))
return;
ent.Comp.CurrentAlertLevel = alertLevel.CurrentLevel;
var proto = _prototype.Index<AlertLevelPrototype>(alertLevel.AlertLevelPrototype);
foreach (var (name, detail) in proto.Levels)
{
ent.Comp.AlertLevels.Add(new($"alert-level-{name}", $"alert-level-{name}-announcement", name, !detail.DisableSelection, detail.Color));
}
ent.Comp.CanSetAlertAt = Timing.CurTime + TimeSpan.FromSeconds(alertLevel.CurrentDelay);
if (alertLevel.IsLevelLocked)
ent.Comp.CanSetAlertAt = null;
ent.Comp.ShuttlesCallable = ShuttlesCallable();
ent.Comp.ExpectedEvacuationArrival = _roundEnd.ExpectedCountdownEnd;
ent.Comp.ExpectedEvacuationDuration = _roundEnd.ExpectedShuttleLength;
if (TryComp<StationExfiltrationComponent>(station, out var exfiltration))
ent.Comp.ExpectedExfiltrationArrival = exfiltration.ArrivalTime;
Dirty(ent);
}
private bool ShuttlesCallable()
{
// Defer to what the round end system thinks we should be able to do.
if (_emergencyShuttle.EmergencyShuttleArrived || !_roundEnd.CanCallOrRecall())
return false;
// Calling shuttle checks
if (_roundEnd.ExpectedCountdownEnd is null)
return true;
// Recalling shuttle checks
var recallThreshold = _configuration.GetCVar(CCVars.EmergencyRecallTurningPoint);
// shouldn't really be happening if we got here
if (_roundEnd.ShuttleTimeLeft is not { } left
|| _roundEnd.ExpectedShuttleLength is not { } expected)
return false;
return !(left.TotalSeconds / expected.TotalSeconds < recallThreshold);
}
protected override void OnAlertLevel(Entity<DVCommunicationsConsoleComponent> ent, ref DVCommunicationsConsoleAlertLevelMessage args)
{
base.OnAlertLevel(ent, ref args);
if (!AccessReader.IsAllowed(ent, args.Actor))
return;
if (_station.GetOwningStation(ent) is not { } station)
return;
_alertLevel.SetLevel(station, args.AlertLevel, true, true);
AdminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(args.Actor):player} has set the alert level to {args.AlertLevel:level} on {ToPrettyString(station):station} using {ToPrettyString(ent):console}");
}
protected override void OnEvacuationShuttle(Entity<DVCommunicationsConsoleComponent> ent, ref DVCommunicationsConsoleEvacuationShuttleMessage args)
{
base.OnEvacuationShuttle(ent, ref args);
if (!AccessReader.IsAllowed(ent, args.Actor))
return;
if (args.Call)
{
var ev = new CommunicationConsoleCallShuttleAttemptEvent(ent, default!, args.Actor);
RaiseLocalEvent(ref ev);
_roundEnd.RequestRoundEnd(args.Actor);
AdminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(args.Actor):player} has called the evacuation shuttle using {ToPrettyString(ent):console}");
}
else
{
_roundEnd.CancelRoundEndCountdown(args.Actor);
AdminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(args.Actor):player} has recalled the evacuation shuttle using {ToPrettyString(ent):console}");
}
}
protected override void OnExfiltrationShuttle(Entity<DVCommunicationsConsoleComponent> ent, ref DVCommunicationsConsoleExfiltrationShuttleMessage args)
{
base.OnExfiltrationShuttle(ent, ref args);
if (!AccessReader.IsAllowed(ent, args.Actor))
return;
if (_station.GetOwningStation(ent) is not { } station)
return;
if (args.Call)
{
_stationExfiltration.Call(station);
AdminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(args.Actor):player} has called the exfiltration shuttle using {ToPrettyString(ent):console}");
}
else
{
_stationExfiltration.Recall(station);
AdminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(args.Actor):player} has recalled the exfiltration shuttle using {ToPrettyString(ent):console}");
}
}
}
@@ -33,7 +33,7 @@ public sealed partial class GameTicker
public bool ConvertRoundToSurvival()
{
// Ramping scheduler is already added. Do nothing.
if (IsGameRuleActive(RampingSchedulerProto))
if (IsGameRuleAdded(RampingSchedulerProto))
{
_chatManager.SendAdminAlert("RampingStationEventScheduler detected. No rules added.");
return true;
@@ -44,7 +44,7 @@ public sealed partial class GameTicker
var rampingSchedulerStart = _gameTiming.CurTime.Add(GracePeriod);
EnsureComp<DelayedStartRuleComponent>(rampingScheduler).RuleStartTime = rampingSchedulerStart;
_chatManager.SendAdminAlert($"Major antag defeated. Converting to survival at {rampingSchedulerStart}.");
_chatManager.SendAdminAlert($"Major antag defeated. Converting to survival at {(RoundDuration() + GracePeriod).ToString(@"hh\:mm\:ss")}.");
// End Basic Rules
var basicRules = EntityQueryEnumerator<BasicStationEventSchedulerComponent>();
@@ -0,0 +1,88 @@
using Content.Server.AlertLevel;
using Content.Server.Audio.Jukebox;
using Content.Server.Instruments;
using Content.Server.Light.Components;
using Content.Server.Light.EntitySystems;
using Content.Server.Nuke;
using Content.Server.RoundEnd;
using Content.Shared._DV.KeycardAuthenticationDevice;
using Content.Shared.Audio.Jukebox;
using Content.Shared.Light.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Server._DV.KeycardAuthenticationDevice;
public sealed class DVStationKeycardAuthenticationDeviceSystem : SharedDVStationKeycardAuthenticationDeviceSystem
{
[Dependency] private readonly NukeCodePaperSystem _nukeCodePaper = default!;
[Dependency] private readonly AlertLevelSystem _alertLevel = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly PoweredLightSystem _poweredLight = default!;
[Dependency] private readonly PointLightSystem _pointLight = default!;
[Dependency] private readonly SharedUserInterfaceSystem _userInterface = default!;
[Dependency] private readonly JukeboxSystem _jukebox = default!;
protected override void Mayday(Entity<DVStationKeycardAuthenticationDeviceComponent> station)
{
base.Mayday(station);
_alertLevel.SetLevel(station, "zeta", true, true, true, true);
var alertLevel = Comp<AlertLevelComponent>(station);
var level = _prototype.Index<AlertLevelPrototype>(alertLevel.AlertLevelPrototype).Levels[alertLevel.CurrentLevel];
_roundEnd.RequestRoundEnd(level.ShuttleTime, null, null, false, cantRecall: true);
var bulbQuery = GetEntityQuery<LightBulbComponent>();
var tubeQuery = EntityQueryEnumerator<PoweredLightComponent>();
while (tubeQuery.MoveNext(out var uid, out var light))
{
if (Station.GetOwningStation(uid) != station.Owner)
continue;
if (_poweredLight.GetBulb(uid, light) is not { } bulb || !bulbQuery.TryComp(bulb, out var bulbComp))
continue;
bulbComp.LightEnergy /= 2;
bulbComp.PowerUse /= 2;
_poweredLight.SetState(uid, light.On, light);
}
var emergencyQuery = EntityQueryEnumerator<EmergencyLightComponent, PointLightComponent>();
while (emergencyQuery.MoveNext(out var uid, out var emergency, out var light))
{
if (Station.GetOwningStation(uid) != station.Owner)
continue;
_pointLight.SetEnergy(uid, emergency.MaydayEnergy, light);
_pointLight.SetRadius(uid, emergency.MaydayRadius, light);
_pointLight.SetFalloff(uid, emergency.MaydayFalloff, light);
}
var instrumentQuery = EntityQueryEnumerator<InstrumentComponent>();
while (instrumentQuery.MoveNext(out var uid, out _))
{
if (Station.GetOwningStation(uid) != station.Owner)
continue;
_userInterface.CloseUis(uid);
}
var jukeboxQuery = EntityQueryEnumerator<JukeboxComponent>();
while (jukeboxQuery.MoveNext(out var uid, out var jukebox))
{
if (Station.GetOwningStation(uid) != station.Owner)
continue;
_jukebox.Stop((uid, jukebox));
}
}
protected override void Scuttling(Entity<DVStationKeycardAuthenticationDeviceComponent> station)
{
base.Scuttling(station);
_nukeCodePaper.SendNukeCodes(station);
}
}
@@ -8,6 +8,7 @@ using Content.Server.NodeContainer.Nodes;
using Content.Shared._DV.NodeCrawl;
using Content.Shared.Atmos;
using Content.Shared.NodeContainer;
using Content.Shared.Polymorph;
using Content.Shared.Zombies;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
@@ -30,6 +31,7 @@ public sealed class NodeCrawlSystem : SharedNodeCrawlSystem
SubscribeLocalEvent<NodeCrawlerComponent, InhaleLocationEvent>(OnInhaleLocation);
SubscribeLocalEvent<NodeCrawlerComponent, ExhaleLocationEvent>(OnExhaleLocation);
SubscribeLocalEvent<NodeCrawlerComponent, AtmosExposedGetAirEvent>(OnGetAir);
SubscribeLocalEvent<NodeCrawlerComponent, PolymorphActionEvent>(OnPolymorph);
SubscribeLocalEvent<NodeCrawlerComponent, EntityZombifiedEvent>(OnZombify);
}
@@ -222,4 +224,15 @@ public sealed class NodeCrawlSystem : SharedNodeCrawlSystem
ent.Comp.EnterDelay = ent.Comp.ZombieEnterDelay;
Dirty(ent);
}
/// <summary>
/// Exit on polymorph or else the entity's movement bugs out.
/// </summary>
/// <param name="ent"></param>
/// <param name="args"></param>
private void OnPolymorph(Entity<NodeCrawlerComponent> ent, ref PolymorphActionEvent args)
{
if (ent.Comp.Mover.HasValue)
ExitNodeCrawl(ent);
}
}
@@ -0,0 +1,111 @@
using Content.Server.AlertLevel;
using Content.Server.Screens.Components;
using Content.Shared._DV.Communications;
using Content.Shared._DV.Screens;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Station;
using Robust.Shared.Timing;
namespace Content.Server._DV.Screens;
public sealed class DVScreenSystem : DVSharedScreenSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedStationSystem _station = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DVScreenComponent, DeviceNetworkPacketEvent>(OnPacket);
SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertLevelChanged);
}
private void OnPacket(Entity<DVScreenComponent> ent, ref DeviceNetworkPacketEvent args)
{
if (args.Data.TryGetValue(ShuttleTimerMasks.ShuttleMap, out _))
OnShuttlePacket(ent, ref args);
if (args.Data.TryGetValue(DVScreenPackets.Text, out (string, string)? text))
OnTextPacket(ent, text.Value, ref args);
if (args.Data.TryGetValue(DVScreenPackets.ShowBorders, out bool? showBorders))
OnBordersPacket(ent, showBorders.Value, ref args);
if (args.Data.TryGetValue(DVScreenPackets.Content, out DVScreenContent? content))
OnContentPacket(ent, content.Value, ref args);
}
private void OnShuttlePacket(Entity<DVScreenComponent> ent, ref DeviceNetworkPacketEvent args)
{
var xform = Transform(ent);
args.Data.TryGetValue(ShuttleTimerMasks.ShuttleMap, out EntityUid? shuttleMap);
args.Data.TryGetValue(ShuttleTimerMasks.SourceMap, out EntityUid? source);
args.Data.TryGetValue(ShuttleTimerMasks.DestMap, out EntityUid? dest);
args.Data.TryGetValue(ShuttleTimerMasks.Docked, out bool docked);
var screenIsAtDestination = docked;
string key;
switch (xform.MapUid)
{
// sometimes the timer transforms on FTL shuttles have a hyperspace mapuid, so matching by grid works as a fallback.
case var local when local == shuttleMap || xform.GridUid == shuttleMap:
key = ShuttleTimerMasks.ShuttleTime;
break;
case var origin when origin == source:
key = ShuttleTimerMasks.SourceTime;
break;
case var remote when remote == dest:
key = ShuttleTimerMasks.DestTime;
screenIsAtDestination = false;
break;
default:
return;
}
if (!args.Data.TryGetValue(key, out TimeSpan duration))
return;
ent.Comp.ScreenIsAtDestination = screenIsAtDestination;
ent.Comp.TargetTime = _timing.CurTime + duration;
Dirty(ent);
UpdateVisuals(ent);
}
private void OnTextPacket(Entity<DVScreenComponent> ent, (string, string) text, ref DeviceNetworkPacketEvent args)
{
ent.Comp.Line1 = text.Item1;
ent.Comp.Line2 = text.Item2;
Dirty(ent);
UpdateVisuals(ent);
}
private void OnBordersPacket(Entity<DVScreenComponent> ent, bool showBorders, ref DeviceNetworkPacketEvent args)
{
ent.Comp.ShowAlertBorder = showBorders;
Dirty(ent);
UpdateVisuals(ent);
}
private void OnContentPacket(Entity<DVScreenComponent> ent, DVScreenContent content, ref DeviceNetworkPacketEvent args)
{
ent.Comp.Content = content;
Dirty(ent);
UpdateVisuals(ent);
}
private void OnAlertLevelChanged(AlertLevelChangedEvent ev)
{
var query = EntityQueryEnumerator<DVScreenComponent>();
while (query.MoveNext(out var uid, out var screen))
{
if (_station.GetOwningStation(uid) != ev.Station)
continue;
screen.AlertLevel = ev.AlertLevel;
Dirty(uid, screen);
UpdateVisuals((uid, screen));
}
}
}
@@ -32,7 +32,6 @@ public sealed class StationExfiltrationSystem : EntitySystem
[Dependency] private readonly ShuttleSystem _shuttle = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly NavMapSystem _navMap = default!;
[Dependency] private readonly CommunicationsConsoleSystem _communicationsConsole = default!;
public override void Initialize()
{
@@ -169,7 +168,8 @@ public sealed class StationExfiltrationSystem : EntitySystem
_chat.DispatchStationAnnouncement(ent, Loc.GetString(ent.Comp.CalledAnnouncement, ("time", ent.Comp.TravelTime.TotalSeconds), ("station", Name(ent))), sender: Loc.GetString(ent.Comp.Sender), colorOverride: Color.Gold);
}
_communicationsConsole.UpdateCommsConsoleInterface();
var evt = new StationExfiltrationChangedEvent((ent, ent.Comp), true);
RaiseLocalEvent(ref evt);
}
public void Recall(Entity<StationExfiltrationComponent?> ent)
@@ -180,6 +180,10 @@ public sealed class StationExfiltrationSystem : EntitySystem
ent.Comp.ArrivalTime = null;
_chat.DispatchStationAnnouncement(ent, Loc.GetString(ent.Comp.RecalledAnnouncement, ("station", Name(ent))), sender: Loc.GetString(ent.Comp.Sender), colorOverride: Color.Gold);
_communicationsConsole.UpdateCommsConsoleInterface();
var evt = new StationExfiltrationChangedEvent((ent, ent.Comp), false);
RaiseLocalEvent(ref evt);
}
}
[ByRefEvent]
public readonly record struct StationExfiltrationChangedEvent(Entity<StationExfiltrationComponent> Station, bool Exfiltrating);
@@ -1,7 +1,7 @@
using Content.Server._DV.StationEvents.Components;
using Content.Server.Antag;
using Content.Server.Communications;
using Content.Server.StationEvents.Events;
using Content.Shared._DV.Communications;
using Content.Shared.Forensics.Components;
using Content.Shared.GameTicking.Components;
using Content.Shared.Ghost;
@@ -43,7 +43,7 @@ public sealed class FugitiveRule : StationEventSystem<FugitiveRuleComponent>
ChatSystem.DispatchGlobalAnnouncement(announcement, sender: sender, colorOverride: comp.Color);
// send the report to every comms console on the station
var query = EntityQueryEnumerator<TransformComponent, CommunicationsConsoleComponent>();
var query = EntityQueryEnumerator<TransformComponent, DVCommunicationsConsoleComponent>();
var consoles = new List<TransformComponent>();
while (query.MoveNext(out var console, out var xform, out _))
{
@@ -19,6 +19,7 @@ using Content.Shared._Goobstation.Flashbang;
using Content.Shared._Starlight.Flash.Components;
using Content.Shared.Body.Components;
using Content.Shared.Flash; // Delta V - Flash Work
using Content.Shared._DV.Body.Components; // Delta V - Remove various comps related to breathing
namespace Content.Server._Starlight;
@@ -68,6 +69,8 @@ public sealed class ShadekinSystem : EntitySystem
{
UpdateAlert(uid, component, (short)component.CurrentState);
RemComp<InternalsComponent>(uid);
RemComp<RespiratorComponent>(uid);
RemComp<AffectedByCPRComponent>(uid); // No lungs = no CPR
}
private void OnEyeColorChange(EntityUid uid, ShadekinComponent component, EyeColorInitEvent args)
+1 -32
View File
@@ -1,9 +1,7 @@
using System.Linq;
using System.Numerics;
using Content.Shared._DV.Humanoid;
using Content.Shared._DV.Humanoid; // DeltaV
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid;
using Content.Shared.Sprite;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -18,7 +16,6 @@ public abstract partial class SharedVisualBodySystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly MarkingManager _marking = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedScaleVisualsSystem _scaleVisualsSystem = default!;
public override void Initialize()
{
@@ -28,7 +25,6 @@ public abstract partial class SharedVisualBodySystem : EntitySystem
SubscribeLocalEvent<VisualOrganMarkingsComponent, BodyRelayedEvent<OrganCopyAppearanceEvent>>(OnMarkingsOrganCopyAppearance);
SubscribeLocalEvent<VisualOrganComponent, BodyRelayedEvent<ApplyOrganProfileDataEvent>>(OnVisualOrganApplyProfile);
SubscribeLocalEvent<VisualOrganMarkingsComponent, BodyRelayedEvent<ApplyOrganMarkingsEvent>>(OnMarkingsOrganApplyMarkings);
SubscribeLocalEvent<HumanoidProfileComponent, ApplyOrganProfileDataEvent>(OnApplyOrganProfileData); // Delta V - Taking the solution from CD
InitializeModifiers();
InitializeInitial();
@@ -100,36 +96,9 @@ public abstract partial class SharedVisualBodySystem : EntitySystem
if (!other.Layer.Equals(ent.Comp.Layer))
return;
// Delta V - Begin Fix Height for Cloning
var height = other.Profile.Height;
if (TryComp<HumanoidProfileComponent>(args.Body.Owner, out var component))
ScaleBody((args.Body.Owner, component), height, height);
// Delta V - End
SetOrganAppearance(ent, other.Data);
}
// Delta V - BEGIN CD Solution
private void OnApplyOrganProfileData(Entity<HumanoidProfileComponent> entity, ref ApplyOrganProfileDataEvent args)
{
var speciesPrototype = _prototype.Index(entity.Comp.Species);
if (args.Base == null)
return;
var height = Math.Clamp(MathF.Round(args.Base.Value.Height, 2), speciesPrototype.MinHeight, speciesPrototype.MaxHeight);
ScaleBody(entity, speciesPrototype.ScaleHeight ? height : 1f, height);
}
private void ScaleBody(Entity<HumanoidProfileComponent> entity, float heightX, float heightY)
{
_scaleVisualsSystem.SetSpriteScale(
entity.Owner,
new Vector2(heightX, heightY)
);
}
// Delta V - END
private void OnMarkingsOrganCopyAppearance(Entity<VisualOrganMarkingsComponent> ent, ref BodyRelayedEvent<OrganCopyAppearanceEvent> args)
{
@@ -18,9 +18,8 @@ namespace Content.Shared.Communications
public List<string>? AlertLevels;
public string CurrentAlert;
public float CurrentAlertDelay;
public readonly TimeSpan? ExpectedExfiltrationCountdownEnd;
public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canCall, List<string>? alertLevels, string currentAlert, float currentAlertDelay, TimeSpan? expectedCountdownEnd, TimeSpan? expectedExfiltrationCountdownEnd) // DeltaV - Exfiltration Shuttle
public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canCall, List<string>? alertLevels, string currentAlert, float currentAlertDelay, TimeSpan? expectedCountdownEnd)
{
CanAnnounce = canAnnounce;
CanCall = canCall;
@@ -29,7 +28,6 @@ namespace Content.Shared.Communications
AlertLevels = alertLevels;
CurrentAlert = currentAlert;
CurrentAlertDelay = currentAlertDelay;
ExpectedExfiltrationCountdownEnd = expectedExfiltrationCountdownEnd; // DeltaV - Exfiltration Shuttle
}
}
+12
View File
@@ -1,5 +1,6 @@
using System.Linq;
using System.Numerics;
using Content.Shared._DV.Administration.EntitySystems; // DeltaV - Unorbitable
using Content.Shared.Administration.Managers;
using Content.Shared.Database;
using Content.Shared.Follower.Components;
@@ -33,6 +34,7 @@ public sealed class FollowerSystem : EntitySystem
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly ISharedAdminManager _adminManager = default!;
[Dependency] private readonly UnorbitableSystem _unorbitable = default!; // DeltaV - Unorbitable
private static readonly ProtoId<TagPrototype> ForceableFollowTag = "ForceableFollow";
private static readonly ProtoId<TagPrototype> PreventGhostnadoWarpTag = "NotGhostnadoWarpable";
@@ -98,6 +100,11 @@ public sealed class FollowerSystem : EntitySystem
if (ev.User == ev.Target || IsClientSide(ev.Target))
return;
// DeltaV - Unorbitable START
if (!_unorbitable.CanFollow(ev.User, ev.Target))
return;
// DeltaV END
if (HasComp<GhostComponent>(ev.User))
{
var verb = new AlternativeVerb()
@@ -187,6 +194,11 @@ public sealed class FollowerSystem : EntitySystem
if (follower == entity || TerminatingOrDeleted(entity))
return;
// DeltaV - Unorbitable START
if (!_unorbitable.CanFollow(follower, entity))
return;
// DeltaV END
// No recursion for you
var targetXform = Transform(entity);
while (targetXform.ParentUid.IsValid())
@@ -1,7 +1,9 @@
using System.Numerics; // DeltaV
using Content.Shared.Examine;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.IdentityManagement;
using Content.Shared.Preferences;
using Content.Shared.Sprite; // DeltaV
using Robust.Shared.GameObjects.Components.Localization;
using Robust.Shared.Prototypes;
@@ -11,6 +13,7 @@ public sealed class HumanoidProfileSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly GrammarSystem _grammar = default!;
[Dependency] private readonly SharedScaleVisualsSystem _scale = default!; // DeltaV
public override void Initialize()
{
@@ -28,6 +31,7 @@ public sealed class HumanoidProfileSystem : EntitySystem
ent.Comp.Age = profile.Age;
ent.Comp.Species = profile.Species;
ent.Comp.Sex = profile.Sex;
ent.Comp.Height = profile.Height; // DeltaV
Dirty(ent);
var sexChanged = new SexChangedEvent(ent.Comp.Sex, profile.Sex);
@@ -37,6 +41,15 @@ public sealed class HumanoidProfileSystem : EntitySystem
{
_grammar.SetGender((ent, grammar), profile.Gender);
}
// START DeltaV - Apply profile/species size
// Assume 1.0 scale unless the original scale exists (blame Allulalo)
var scale = Vector2.One;
if (TryComp<ScaleVisualsComponent>(ent, out var scaledVisuals) && scaledVisuals.OriginalScale is { } originalScale)
scale = originalScale;
_scale.SetSpriteScale(ent, scale);
// END DeltaV
}
private void OnExamined(Entity<HumanoidProfileComponent> ent, ref ExaminedEvent args)
@@ -1,3 +1,4 @@
using System.Numerics; // DeltaV
using Content.Shared.Body;
using Content.Shared.Dataset;
using Content.Shared.Humanoid.Markings;
@@ -111,7 +112,7 @@ public sealed partial class SpeciesPrototype : IPrototype
/// The base height scale for this species
/// </summary>
[DataField("baseScale")]
public System.Numerics.Vector2 BaseScale = new(1f, 1f);
public Vector2 BaseScale = new(1f, 1f);
// End DV - CD Character Records shouldn't nuke species heights
// Begin CD - Character Records
@@ -119,13 +120,13 @@ public sealed partial class SpeciesPrototype : IPrototype
/// The minimum height for this species
/// </summary>
[DataField("minHeight")]
public float MinHeight = 0.9f; // DeltaV - less trolling with the heights
public float MinHeight = 0.8f; // DeltaV
/// <summary>
/// The maximum height for this species
/// </summary>
[DataField("maxHeight")]
public float MaxHeight = 1.1f; // DeltaV - less trolling with the heights
public float MaxHeight = 1.2f; // DeltaV
/// <summary>
/// The default height for this species
@@ -90,7 +90,7 @@ public sealed partial class LabelSystem : EntitySystem
private void OnRefreshNameModifiers(Entity<LabelComponent> entity, ref RefreshNameModifiersEvent args)
{
if (!string.IsNullOrEmpty(entity.Comp.CurrentLabel))
args.AddModifier("comp-label-format", extraArgs: ("label", entity.Comp.CurrentLabel));
args.AddModifier("comp-label-format", 100, extraArgs: ("label", entity.Comp.CurrentLabel)); // Coyote: add priority of 100 to reverse order of label and baseName
}
private void OnComponentInit(Entity<PaperLabelComponent> ent, ref ComponentInit args)
@@ -1,3 +1,5 @@
using Content.Shared._DV.Body.Components; // DeltaV
using Content.Shared._DV.Body.Systems; // DeltaV
using Content.Shared._ST.Interaction; // Stellar - interaction particles
using Content.Shared._Floof.OfferItem; // Floof
using Content.Shared.ActionBlocker;
@@ -10,6 +12,7 @@ using Content.Shared.Database;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid; // DeltaV
using Content.Shared.IdentityManagement;
using Content.Shared.Input;
using Content.Shared.Interaction;
@@ -55,6 +58,7 @@ public sealed class PullingSystem : EntitySystem
[Dependency] private readonly HeldSpeedModifierSystem _clothingMoveSpeed = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedVirtualItemSystem _virtual = default!;
[Dependency] private readonly SmallCharacterSystem _smallCharacter = default!; // DeltaV
public override void Initialize()
{
@@ -295,6 +299,14 @@ public sealed class PullingSystem : EntitySystem
private void OnRefreshMovespeed(EntityUid uid, PullerComponent component, RefreshMovementSpeedModifiersEvent args)
{
// BEGIN DeltaV - Slow if smaller puller
if (TryComp<SmallCharacterComponent>(uid, out var smol))
{
var sizePenalty = _smallCharacter.ApplyPullSpeedPenalty((uid, smol), component.Pulling);
args.ModifySpeed(sizePenalty, sizePenalty);
}
// END DeltaV
if (TryComp<HeldSpeedModifierComponent>(component.Pulling, out var heldMoveSpeed) && component.Pulling.HasValue)
{
var (walkMod, sprintMod) =
@@ -97,7 +97,7 @@ public sealed partial class ParcelWrappingSystem
if (target == user)
{
var selfMsg = Loc.GetString("parcel-wrap-popup-being-wrapped-self");
_popup.PopupClient(selfMsg, user, user);
_popup.PopupEntity(selfMsg, user, user);
}
else
{
@@ -143,7 +143,7 @@ public sealed partial class ParcelWrappingSystem
// Spawn the actual parcel entity.
var targetTransform = Transform(target);
var spawned = Spawn(GetParcelPrototype(wrapper, target), targetTransform.Coordinates);
var spawned = SpawnAtPosition(GetParcelPrototype(wrapper, target), targetTransform.Coordinates);
_transform.SetLocalRotation(spawned, targetTransform.LocalRotation);
// If the target is in a container, try to put the parcel in its place in the container.
@@ -177,7 +177,7 @@ namespace Content.Shared.Preferences
_traitPreferences = traitPreferences;
_loadouts = loadouts;
// Begin CD - Character Records
Height = height;
Height = height; // This is the user-set scale on the profile editor. Not actual height measurements.
CDCharacterRecords = cdCharacterRecords;
// End CD - Character Records
@@ -300,4 +300,12 @@ public sealed partial class RevenantComponent : Component
public string HarvestingState = "harvesting";
#endregion
[DataField] public EntityUid? HauntAction; // Imp
#region Melee
/// <summary>
/// DeltaV - The amount of time a rev is revealed after making a melee attack.
/// </summary>
[DataField]
public TimeSpan MeleeRevealTime = TimeSpan.FromSeconds(2);
#endregion
}
+19 -3
View File
@@ -25,11 +25,27 @@ public sealed partial class ScaleVisualsComponent : Component
[ViewVariables]
public Vector2? OriginalScale;
// Delta V Addition
/// <summary>
/// Base Scale of the Species, which we use to set a new height relative to this.
/// DeltaV - Contains the species scale. Set dynamically by
/// baseScale in the Species prototype.
/// </summary>
[DataField, AutoNetworkedField]
[ViewVariables]
public Vector2 SpeciesScale = Vector2.One;
public Vector2 SpeciesScale = new(1f, 1f);
/// <summary>
/// DeltaV - Contains the user-defined scale from the character creation
/// screen.
/// </summary>
[DataField, AutoNetworkedField]
[ViewVariables]
public Vector2 ProfileScale = new(1f, 1f);
/// <summary>
/// DeltaV - Contains the computer scale from applying Scale, SpeciesScale, and ProfileScale.
/// This will contain the actual scale after all modifiers are applied.
/// </summary>
[DataField, AutoNetworkedField]
[ViewVariables]
public Vector2 ComputedScale;
}
@@ -1,4 +1,6 @@
using System.Numerics;
using Content.Shared.Humanoid; // DeltaV
using Robust.Shared.Prototypes; // DeltaV
using Robust.Shared.Serialization;
namespace Content.Shared.Sprite;
@@ -6,6 +8,7 @@ namespace Content.Shared.Sprite;
public abstract class SharedScaleVisualsSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IPrototypeManager _proto = default!; // DeltaV
public override void Initialize()
{
@@ -39,15 +42,28 @@ public abstract class SharedScaleVisualsSystem : EntitySystem
{
var comp = EnsureComp<ScaleVisualsComponent>(uid);
comp.Scale = scale;
// BEGIN DeltaV - Apply species and profile height
// We have to ensure this is idempotent so that if it gets applied more than once
// the sprite size at the end is the same.
if (TryComp<HumanoidProfileComponent>(uid, out var profile))
{
var speciesProto = _proto.Index(profile.Species);
comp.SpeciesScale = speciesProto.BaseScale;
scale *= comp.SpeciesScale; // Apply both species scale and character-defined height
Vector2 profileHeight = new(profile.Height, profile.Height);
comp.ProfileScale = profileHeight;
scale *= comp.ProfileScale;
}
comp.ComputedScale = scale; // Nice to know what the computed size is in case of bugs
// END DeltaV
Dirty(uid, comp);
// Delta V - Begin Species Scaling
// 120% species scale => add 0.2 to scale
var newScale = scale + comp.SpeciesScale - Vector2.One;
// Delta V - End Species Scaling
var appearanceComponent = EnsureComp<AppearanceComponent>(uid);
_appearance.SetData(uid, ScaleVisuals.Scale, newScale /* Delta V - Custom Species Scale */, appearanceComponent);
_appearance.SetData(uid, ScaleVisuals.Scale, scale, appearanceComponent);
// Raise an event for content use.
var ev = new ScaleEntityEvent(uid, scale);
@@ -0,0 +1,12 @@
using Robust.Shared.GameStates;
namespace Content.Shared._DV.Administration.Components;
/// This component prevents an entity from being followed by ghosts.
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class UnorbitableComponent : Component
{
/// Whether to allow adminned players to follow the entity.
[DataField, AutoNetworkedField]
public bool AllowAdmins = false;
}
@@ -0,0 +1,36 @@
using Content.Shared._DV.Administration.Components;
using Content.Shared.Administration.Managers;
using Content.Shared.Follower;
using Content.Shared.Follower.Components;
namespace Content.Shared._DV.Administration.EntitySystems;
public sealed class UnorbitableSystem : EntitySystem
{
[Dependency] private readonly FollowerSystem _followerSystem = default!;
[Dependency] private readonly ISharedAdminManager _admin = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<UnorbitableComponent, ComponentInit>(OnInit);
}
private void OnInit(Entity<UnorbitableComponent> ent, ref ComponentInit args)
{
if (!HasComp<FollowedComponent>(ent))
return;
_followerSystem.StopAllFollowers(ent);
}
public bool CanFollow(EntityUid follower, EntityUid target)
{
if (TryComp<UnorbitableComponent>(target, out var unorbitable))
{
return unorbitable.AllowAdmins && _admin.IsAdmin(follower);
}
return true;
}
}
@@ -0,0 +1,23 @@
using Content.Shared._DV.Body.Systems;
using Robust.Shared.GameStates;
namespace Content.Shared._DV.Body.Components;
/// <summary>
/// If an entity has this, if a small character has penalties (such as pull speed),
/// the small character will ignore the penalties associated with their size.
///
/// Mostly used for things like wheeled/floating objects.
/// </summary>
[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState]
[Access(typeof(SmallCharacterSystem))]
public sealed partial class SmallCharacterComponent : Component
{
/// <summary>
/// The speed of which to scale the small character's pull speed by if the
/// object is big enough to warrant a pull-speed slowdown.
/// </summary>
[DataField, AutoNetworkedField]
public float PullSpeedPenalty = 1f;
}
@@ -0,0 +1,12 @@
namespace Content.Shared._DV.Body.Components;
/// <summary>
/// If an entity has this, if a small character has penalties (such as pull speed),
/// the small character will ignore the penalties associated with their size.
///
/// Mostly used for things like wheeled/floating objects.
///
/// See <see cref="Systems.SmallCharacterSystem"/>
/// </summary>
[RegisterComponent]
public sealed partial class UnaffectedBySizePenaltyComponent : Component;
@@ -0,0 +1,97 @@
using Content.Shared._DV.Body.Components;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Item;
using JetBrains.Annotations;
using Robust.Shared.Physics.Components;
namespace Content.Shared._DV.Body.Systems;
/// <summary>
/// Used to relay or subscribe to events if a character's scale is 1.0 or below.
/// This is only used for the height slider scale.
/// </summary>
public sealed partial class SmallCharacterSystem : EntitySystem
{
private const float NO_PENALTY = 1.0f;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnSpawn);
}
private void OnSpawn(PlayerSpawnCompleteEvent ev)
{
if (TryComp<HumanoidProfileComponent>(ev.Mob, out var profile))
ApplySmallCharacter(ev.Mob, profile.Height);
}
[PublicAPI]
public float ApplyPullSpeedPenalty(Entity<SmallCharacterComponent?> puller, EntityUid? pulledEntity)
{
// Ignore if they aren't pulling anything...
if (!pulledEntity.HasValue)
return NO_PENALTY;
// Ignore if they aren't a small character in the first place
if (!Resolve(puller, ref puller.Comp, false))
return NO_PENALTY;
// If the pulled entity has the component that ignores the penalty
if (HasComp<UnaffectedBySizePenaltyComponent>(pulledEntity))
return NO_PENALTY;
// Ignore if it's an item that can be held or stored. It would be weird to
// slow by X% from pulling a piece of paper or a gun when you can just hold it
// and not suffer from a penalty.
if (HasComp<ItemComponent>(pulledEntity))
return NO_PENALTY;
// Ignore if the object is floating in the air.
if (TryComp<PhysicsComponent>(pulledEntity, out var pulledPhysics)
&& pulledPhysics.BodyStatus == BodyStatus.InAir)
return NO_PENALTY;
return puller.Comp.PullSpeedPenalty;
}
#region Static Members
/// <summary>
/// Gets the move-speed penalty as a float. Should be applied multiplicatively.
/// Caps at 1 so we don't make bigger characters faster when pulling.
/// </summary>
/// <returns></returns>
[PublicAPI]
public static float GetPullSpeedPenaltyFromScale(float scale = 1.0f)
{
return Math.Min(scale * scale, 1);
}
/// <summary>
/// Calculates a well-formed display string of the pull speed penalty.
/// Used primarily in the character editor to get the well-formed percent
/// without having to duplicate formulas.
/// </summary>
/// <param name="scale"></param>
/// <returns></returns>
[PublicAPI]
public static string GetPullSpeedPenaltyDisplayFromScale(float scale = 1.0f)
{
return $"{Math.Round((1 - GetPullSpeedPenaltyFromScale(scale)) * 100)}%";
}
#endregion
#region Private Members
private void ApplySmallCharacter(EntityUid uid, float scale = 1)
{
if (scale >= 1)
return;
// The character scale is stored in the HumanoidProfileComponent if you ever
// need it.
var comp = EnsureComp<SmallCharacterComponent>(uid);
comp.PullSpeedPenalty = GetPullSpeedPenaltyFromScale(scale);
Dirty(uid, comp);
}
#endregion
}
@@ -1,9 +0,0 @@
using Robust.Shared.Serialization;
namespace Content.Shared._DV.Communications;
[Serializable, NetSerializable]
public sealed class CommunicationsConsoleExfiltrationShuttleMessage(bool call) : BoundUserInterfaceMessage
{
public readonly bool Call = call;
}
@@ -0,0 +1,136 @@
using Content.Shared._DV.KeycardAuthenticationDevice;
using Content.Shared._DV.Screens;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared._DV.Communications;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
[Access(typeof(SharedDVCommunicationsConsoleSystem))]
public sealed partial class DVCommunicationsConsoleComponent : Component
{
[DataField]
public LocId AnnouncementTitle = "comms-console-announcement-title-station";
[DataField]
public Color AnnouncementColor = Color.Gold;
[DataField]
public SoundSpecifier AnnouncementSound = new SoundPathSpecifier("/Audio/Announcements/announce.ogg");
[DataField, AutoNetworkedField]
public bool GlobalAnnouncements;
[DataField, AutoNetworkedField]
public bool CanAnnounce = true;
[DataField, AutoNetworkedField]
public bool CanAlertLevel = true;
[DataField, AutoNetworkedField]
public bool CanCallShuttles = true;
[DataField, AutoNetworkedField]
public bool CanConfigureScreens = true;
[DataField, AutoNetworkedField]
public bool CanKeycardAuthenticationDevice = true;
[DataField, AutoNetworkedField]
public TimeSpan CanAnnounceAt = TimeSpan.Zero;
[DataField]
public TimeSpan AnnouncementInterval = TimeSpan.FromSeconds(90f);
[DataField]
public TimeSpan InitialAnnouncementDelay = TimeSpan.FromSeconds(30f);
[DataField, AutoNetworkedField]
public string CurrentAlertLevel = string.Empty;
[DataField, AutoNetworkedField]
public List<DVCommunicationsConsoleAlertLevel> AlertLevels = new();
[DataField, AutoNetworkedField]
public TimeSpan? CanSetAlertAt = TimeSpan.Zero;
[DataField, AutoNetworkedField]
public bool ShuttlesCallable = true;
[DataField, AutoNetworkedField]
public TimeSpan? ExpectedEvacuationArrival;
[DataField, AutoNetworkedField]
public TimeSpan? ExpectedEvacuationDuration;
[DataField, AutoNetworkedField]
public TimeSpan? ExpectedExfiltrationArrival;
[DataField, AutoNetworkedField]
public DVScreenContent LastConfiguredContent = DVScreenContent.Text;
[DataField, AutoNetworkedField]
public bool LastConfiguredShowBorders;
[DataField, AutoNetworkedField]
public string LastConfiguredLine1 = string.Empty;
[DataField, AutoNetworkedField]
public string LastConfiguredLine2 = string.Empty;
}
[Serializable, NetSerializable]
public readonly record struct DVCommunicationsConsoleAlertLevel(LocId AlertLevel, LocId Description, string Id, bool CanSet, Color Color);
[Serializable, NetSerializable]
public sealed class DVCommunicationsConsoleEvacuationShuttleMessage(bool call) : BoundUserInterfaceMessage
{
public readonly bool Call = call;
}
[Serializable, NetSerializable]
public sealed class DVCommunicationsConsoleExfiltrationShuttleMessage(bool call) : BoundUserInterfaceMessage
{
public readonly bool Call = call;
}
[Serializable, NetSerializable]
public sealed class DVCommunicationsConsoleKeycardAuthenticationDeviceMessage(DVStationKeycardAction action) : BoundUserInterfaceMessage
{
public readonly DVStationKeycardAction Action = action;
}
[Serializable, NetSerializable]
public sealed class DVCommunicationsConsoleAnnouncementMessage(string announcement) : BoundUserInterfaceMessage
{
public readonly string Announcement = announcement;
}
[Serializable, NetSerializable]
public sealed class DVCommunicationsConsoleAlertLevelMessage(string alertLevel) : BoundUserInterfaceMessage
{
public readonly string AlertLevel = alertLevel;
}
[Serializable, NetSerializable]
public sealed class DVCommunicationsConsoleScreenConfigurationMessage(DVScreenContent content, bool showBorder, string line1, string line2) : BoundUserInterfaceMessage
{
public readonly DVScreenContent Content = content;
public readonly bool ShowBorder = showBorder;
public readonly string Line1 = line1;
public readonly string Line2 = line2;
}
[Serializable, NetSerializable]
public enum DVCommunicationsConsoleUi : byte
{
Key,
}
public static class DVScreenPackets
{
public const string Content = "dv-screen-content";
public const string ShowBorders = "dv-screen-borders";
public const string Text = "dv-screen-text";
}
@@ -0,0 +1,148 @@
using Content.Shared._DV.KeycardAuthenticationDevice;
using Content.Shared._DV.Screens;
using Content.Shared.Access.Systems;
using Content.Shared.Administration.Logs;
using Content.Shared.Chat;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DeviceNetwork.Systems;
using Content.Shared.IdentityManagement;
using Robust.Shared.Timing;
namespace Content.Shared._DV.Communications;
public abstract class SharedDVCommunicationsConsoleSystem : EntitySystem
{
[Dependency] private readonly SharedDeviceNetworkSystem _deviceNetwork = default!;
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly SharedChatSystem _chat = default!;
[Dependency] protected readonly AccessReaderSystem AccessReader = default!;
[Dependency] protected readonly ISharedAdminLogManager AdminLog = default!;
[Dependency] private readonly SharedDVStationKeycardAuthenticationDeviceSystem _stationKeycardAuthenticationDevice = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DVCommunicationsConsoleComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<DVCommunicationsConsoleComponent, DeviceNetworkPacketEvent>(OnPacketReceive);
Subs.BuiEvents<DVCommunicationsConsoleComponent>(DVCommunicationsConsoleUi.Key,
subs =>
{
subs.Event<DVCommunicationsConsoleEvacuationShuttleMessage>(OnEvacuationShuttle);
subs.Event<DVCommunicationsConsoleExfiltrationShuttleMessage>(OnExfiltrationShuttle);
subs.Event<DVCommunicationsConsoleScreenConfigurationMessage>(OnConfiguration);
subs.Event<DVCommunicationsConsoleAnnouncementMessage>(OnAnnouncement);
subs.Event<DVCommunicationsConsoleAlertLevelMessage>(OnAlertLevel);
subs.Event<DVCommunicationsConsoleKeycardAuthenticationDeviceMessage>(OnKeycardAuthenticationDevice);
});
}
private void OnPacketReceive(Entity<DVCommunicationsConsoleComponent> ent, ref DeviceNetworkPacketEvent args)
{
if (args.Data.TryGetValue(DVScreenPackets.Text, out (string, string)? text))
{
ent.Comp.LastConfiguredLine1 = text.Value.Item1;
ent.Comp.LastConfiguredLine2 = text.Value.Item2;
Dirty(ent);
}
if (args.Data.TryGetValue(DVScreenPackets.ShowBorders, out bool? showBorders))
{
ent.Comp.LastConfiguredShowBorders = showBorders.Value;
Dirty(ent);
}
if (args.Data.TryGetValue(DVScreenPackets.Content, out DVScreenContent? content))
{
ent.Comp.LastConfiguredContent = content.Value;
Dirty(ent);
}
}
protected virtual void OnMapInit(Entity<DVCommunicationsConsoleComponent> ent, ref MapInitEvent args)
{
ent.Comp.CanAnnounceAt = Timing.CurTime + ent.Comp.InitialAnnouncementDelay;
Dirty(ent);
}
private void OnAnnouncement(Entity<DVCommunicationsConsoleComponent> ent, ref DVCommunicationsConsoleAnnouncementMessage args)
{
if (!ent.Comp.CanAnnounce)
return;
if (Timing.CurTime <= ent.Comp.CanAnnounceAt)
return;
if (!AccessReader.IsAllowed(args.Actor, ent))
return;
var identity = new TryGetIdentityShortInfoEvent(ent, args.Actor);
RaiseLocalEvent(identity);
Loc.TryGetString(ent.Comp.AnnouncementTitle, out var title);
title ??= ent.Comp.AnnouncementTitle;
var msg = args.Announcement;
msg += "\n" + Loc.GetString("comms-console-announcement-sent-by") + " " + identity.Title;
if (ent.Comp.GlobalAnnouncements)
{
_chat.DispatchGlobalAnnouncement(msg, title, announcementSound: ent.Comp.AnnouncementSound, colorOverride: ent.Comp.AnnouncementColor);
AdminLog.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Actor):player} sent the following global announcement using {ToPrettyString(ent):console}: {msg:message}");
}
else
{
_chat.DispatchStationAnnouncement(ent, msg, title, announcementSound: ent.Comp.AnnouncementSound, colorOverride: ent.Comp.AnnouncementColor);
AdminLog.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Actor):player} sent the following station announcement using {ToPrettyString(ent):console}: {msg:message}");
}
ent.Comp.CanAnnounceAt = Timing.CurTime + ent.Comp.AnnouncementInterval;
Dirty(ent);
}
private void OnConfiguration(Entity<DVCommunicationsConsoleComponent> ent, ref DVCommunicationsConsoleScreenConfigurationMessage args)
{
if (!ent.Comp.CanConfigureScreens)
return;
if (!AccessReader.IsAllowed(args.Actor, ent))
return;
ent.Comp.LastConfiguredLine1 = args.Line1;
ent.Comp.LastConfiguredLine2 = args.Line2;
ent.Comp.LastConfiguredShowBorders = args.ShowBorder;
ent.Comp.LastConfiguredContent = args.Content;
Dirty(ent);
var payload = new NetworkPayload
{
[DVScreenPackets.Content] = args.Content,
[DVScreenPackets.ShowBorders] = args.ShowBorder,
[DVScreenPackets.Text] = (args.Line1, args.Line2),
};
AdminLog.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Actor):player} configured the following text using {ToPrettyString(ent):console}: {args.Line1:line1} {args.Line2:line2}");
_deviceNetwork.QueuePacket(ent, null, payload);
}
protected virtual void OnExfiltrationShuttle(Entity<DVCommunicationsConsoleComponent> ent,
ref DVCommunicationsConsoleExfiltrationShuttleMessage args)
{
}
protected virtual void OnEvacuationShuttle(Entity<DVCommunicationsConsoleComponent> ent,
ref DVCommunicationsConsoleEvacuationShuttleMessage args)
{
}
protected virtual void OnAlertLevel(Entity<DVCommunicationsConsoleComponent> ent,
ref DVCommunicationsConsoleAlertLevelMessage args)
{
}
private void OnKeycardAuthenticationDevice(Entity<DVCommunicationsConsoleComponent> ent,
ref DVCommunicationsConsoleKeycardAuthenticationDeviceMessage args)
{
_stationKeycardAuthenticationDevice.TrySwipe(args.Actor, ent, args.Action);
}
}
@@ -0,0 +1,7 @@
using Robust.Shared.GameStates;
namespace Content.Shared._DV.KeycardAuthenticationDevice;
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedDVStationKeycardAuthenticationDeviceSystem))]
public sealed partial class DVStationKeycardAuthenticationDeviceAlreadySwipedComponent : Component;
@@ -0,0 +1,50 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared._DV.KeycardAuthenticationDevice;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
[Access(typeof(SharedDVStationKeycardAuthenticationDeviceSystem))]
public sealed partial class DVStationKeycardAuthenticationDeviceComponent : Component
{
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
public TimeSpan AccessibleAfter = TimeSpan.Zero;
[DataField]
public TimeSpan FailureDelay = TimeSpan.FromMinutes(2);
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
public TimeSpan? SwipesStartedAt = null;
[DataField]
public TimeSpan SwipeWindow = TimeSpan.FromSeconds(20);
[DataField(required: true)]
public Dictionary<DVStationKeycardAction, int> ActionThresholds;
[DataField, AutoNetworkedField]
public int Swipes;
[DataField, AutoNetworkedField]
public DVStationKeycardAction? SwipingFor;
[DataField]
public LocId FailureAnnouncementSender = "keycard-authentication-device-sender";
[DataField]
public LocId FailureAnnouncement = "keycard-authentication-device-warning";
[DataField]
public Color FailureAnnouncementColor = Color.FromHex("#e93a9a");
[DataField]
public TimeSpan FailureElectrocutionDuration = TimeSpan.FromSeconds(5);
}
[Serializable, NetSerializable]
public enum DVStationKeycardAction
{
Mayday,
Scuttling,
}
@@ -0,0 +1,124 @@
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Chat;
using Content.Shared.Electrocution;
using Content.Shared.Station;
using Robust.Shared.Audio;
using Robust.Shared.Timing;
namespace Content.Shared._DV.KeycardAuthenticationDevice;
public abstract class SharedDVStationKeycardAuthenticationDeviceSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] protected readonly SharedStationSystem Station = default!;
[Dependency] private readonly SharedElectrocutionSystem _electrocution = default!;
[Dependency] private readonly SharedChatSystem _chat = default!;
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<DVStationKeycardAuthenticationDeviceComponent>();
while (query.MoveNext(out var uid, out var keycard))
{
if (keycard.SwipesStartedAt is not { } startedAt || _timing.CurTime <= startedAt + keycard.SwipeWindow)
continue;
StopSwipes((uid, keycard), true);
}
}
private void StopSwipes(Entity<DVStationKeycardAuthenticationDeviceComponent> station, bool failed)
{
station.Comp.SwipesStartedAt = null;
station.Comp.Swipes = 0;
station.Comp.SwipingFor = null;
if (failed)
{
station.Comp.AccessibleAfter = _timing.CurTime + station.Comp.FailureDelay;
_chat.DispatchStationAnnouncement(station,
Loc.GetString(station.Comp.FailureAnnouncement),
Loc.GetString(station.Comp.FailureAnnouncementSender),
announcementSound: new SoundPathSpecifier("/Audio/_DV/Announcements/attention.ogg"),
colorOverride: station.Comp.FailureAnnouncementColor);
}
Dirty(station);
var query = EntityQueryEnumerator<DVStationKeycardAuthenticationDeviceAlreadySwipedComponent>();
while (query.MoveNext(out var uid, out _))
{
if (failed)
{
_electrocution.TryDoElectrocution(uid,
null,
10,
station.Comp.FailureElectrocutionDuration,
true,
ignoreInsulation: true);
}
RemCompDeferred<DVStationKeycardAuthenticationDeviceAlreadySwipedComponent>(uid);
}
}
private void Swipe(Entity<DVStationKeycardAuthenticationDeviceComponent> station, EntityUid user, DVStationKeycardAction action)
{
if (!station.Comp.ActionThresholds.TryGetValue(action, out var threshold))
return;
station.Comp.SwipesStartedAt ??= _timing.CurTime;
station.Comp.SwipingFor = action;
station.Comp.Swipes++;
AddComp<DVStationKeycardAuthenticationDeviceAlreadySwipedComponent>(user);
Dirty(station);
if (station.Comp.Swipes >= threshold)
{
StopSwipes(station, false);
DoAction(station, action);
}
}
private void DoAction(Entity<DVStationKeycardAuthenticationDeviceComponent> station, DVStationKeycardAction action)
{
switch (action)
{
case DVStationKeycardAction.Mayday:
Mayday(station);
break;
case DVStationKeycardAction.Scuttling:
Scuttling(station);
break;
}
}
protected virtual void Mayday(Entity<DVStationKeycardAuthenticationDeviceComponent> station)
{
}
protected virtual void Scuttling(Entity<DVStationKeycardAuthenticationDeviceComponent> station)
{
}
public void TrySwipe(EntityUid user, EntityUid console, DVStationKeycardAction action)
{
if (HasComp<DVStationKeycardAuthenticationDeviceAlreadySwipedComponent>(console))
return;
if (Station.GetOwningStation(user) is not { } station || !TryComp<DVStationKeycardAuthenticationDeviceComponent>(station, out var keycard))
return;
if (TryComp<AccessReaderComponent>(console, out var accessReader) && !_accessReader.IsAllowed(user, console, accessReader))
return;
if (keycard.SwipingFor is { } swipingFor && swipingFor != action)
return;
if (_timing.CurTime <= keycard.AccessibleAfter)
return;
Swipe((station, keycard), user, action);
}
}
@@ -70,13 +70,23 @@ public abstract class SharedLightReactiveSystem : EntitySystem
/// Avoid calling this too often, as it can be expensive.
/// </summary>
public float GetLightLevelForPoint(EntityUid uid, TransformComponent? xform = null)
{
return GetLightLevelAtPosition(uid, _transform.GetWorldPosition(uid), xform);
}
/// <summary>
/// Gets the light level at an arbitrary world position, using <paramref name="uid"/> for the
/// light lookup and map resolution. Lets callers sample somewhere other than the entity's
/// centre — e.g. a point just outside a wall, so the wall's own body can occlude the ray.
/// Avoid calling this too often, as it can be expensive.
/// </summary>
public float GetLightLevelAtPosition(EntityUid uid, Vector2 pos, TransformComponent? xform = null)
{
float val = 0.0f;
// Get the current map entity so we can get a MapLightComponent from it if it has one
var map = _transform.GetMap((uid, xform));
if (TryComp(map, out MapLightComponent? mapLight))
val += (mapLight.AmbientLightColor.R + mapLight.AmbientLightColor.G + mapLight.AmbientLightColor.B) / 3f;
var pos = _transform.GetWorldPosition(uid);
foreach (var (lightUid, lightComp) in GetLights(uid))
{
@@ -187,14 +187,14 @@ public abstract class SharedNodeCrawlSystem : EntitySystem
private void OnMovementShutdown(Entity<NodeCrawlerMovementComponent> ent, ref ComponentShutdown args)
{
if (ent.Comp.Node is { } node)
if (ent.Comp.Node is { } node && node.IsValid())
{
var nodeComp = Comp<CrawlableNodeComponent>(node);
nodeComp.Crawlers.Remove(ent);
Dirty(node, nodeComp);
}
if (ent.Comp.HeldCrawler is { } crawler && !TerminatingOrDeleted(crawler) && TryComp<NodeCrawlerComponent>(crawler, out var nodeCrawler))
if (ent.Comp.HeldCrawler is { } crawler && crawler.IsValid() && !TerminatingOrDeleted(crawler) && TryComp<NodeCrawlerComponent>(crawler, out var nodeCrawler))
{
ExitNodeCrawl((crawler, nodeCrawler));
}
@@ -0,0 +1,30 @@
using Robust.Shared.GameStates;
namespace Content.Shared._DV.Overlays.Components;
/// <summary>
/// Gives the owner darkvision: lighting still renders, but total darkness is raised to
/// <see cref="LightFloor"/> brightness instead of pitch black. Unlike night vision this keeps
/// the whole lighting gradient visible, so creatures like the Skia can judge what is and isn't dark enough for them.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class DarkVisionComponent : Component
{
/// <summary>
/// Brightness that full darkness renders at, 0-1. Rendered light is clamped to a minimum of this value.
/// </summary>
[DataField, AutoNetworkedField]
public float LightFloor = 0.2f;
/// <summary>
/// Multiplier applied to actual light on top of the floor. Values above 1 overbrighten lit areas so they are unmistakable next to the grey darkness floor.
/// </summary>
[DataField, AutoNetworkedField]
public float LightGain = 8f;
/// <summary>
/// Exponent applied to lights, to make brighter areas look notably brighter
/// </summary>
[DataField, AutoNetworkedField]
public float LightExp = 2f;
}
@@ -0,0 +1,55 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared._DV.Screens;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), AutoGenerateComponentPause]
[Access(typeof(DVSharedScreenSystem))]
public sealed partial class DVScreenComponent : Component
{
[DataField, AutoNetworkedField]
public string? AlertLevel; // I don't like this but uhhh the prototype isn't client-accessible
[DataField, AutoNetworkedField]
public bool ShowAlertBorder;
[DataField, AutoNetworkedField]
public DVScreenContent Content = DVScreenContent.Text;
#region Text Screens
[DataField, AutoNetworkedField]
public string Line1 = string.Empty;
[DataField, AutoNetworkedField]
public string Line2 = string.Empty;
#endregion
#region ETA Screens
[DataField, AutoNetworkedField]
public bool ScreenIsAtDestination;
[DataField, AutoNetworkedField, AutoPausedField]
public TimeSpan TargetTime = TimeSpan.Zero;
#endregion
}
[Serializable, NetSerializable]
public enum DVScreenVisuals : byte
{
AlertLevel,
ShowAlertBorder,
Content,
}
[Serializable, NetSerializable]
public enum DVScreenContent : byte
{
Text,
CurrentTime,
EstimatedTimeOfArrival,
AlertLevel,
}
@@ -0,0 +1,13 @@
namespace Content.Shared._DV.Screens;
public abstract class DVSharedScreenSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
protected void UpdateVisuals(Entity<DVScreenComponent> ent)
{
_appearance.SetData(ent.Owner, DVScreenVisuals.AlertLevel, ent.Comp.AlertLevel ?? string.Empty);
_appearance.SetData(ent.Owner, DVScreenVisuals.ShowAlertBorder, ent.Comp.ShowAlertBorder);
_appearance.SetData(ent.Owner, DVScreenVisuals.Content, ent.Comp.Content);
}
}
@@ -0,0 +1,10 @@
using Robust.Shared.Serialization;
namespace Content.Shared._DV.Screens;
[Serializable, NetSerializable]
public enum DVTextScreenVisualLayers : byte
{
Line1,
Line2,
}
@@ -0,0 +1,36 @@
using Robust.Shared.GameStates;
using Robust.Shared.Timing;
namespace Content.Shared._DV.ShadowWalk;
/// <summary>
/// Lets this entity walk straight through solid static objects (walls, doors, windows...)
/// while the entity itself is bathed in darkness (the same light level it heals in.)
/// Mobs and projectiles always stay solid.
/// <para>
/// On collision, checks our light level. Objects we're stuck in are tagged in <see cref="PassableEntities"/>
/// </para>
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class ShadowWalkerComponent : Component
{
/// <summary>
/// Light level below which an object counts as bathed in darkness.
/// If the entity has a <c>LightLevelHealthComponent</c> its DarkThreshold is used
/// instead, so objects are passable exactly where the entity would heal.
/// </summary>
[DataField]
public float DarkThreshold = 0.3f;
/// <summary>
/// Objects we're currently in. Objects in this list are never solid until we fully leave.
/// </summary>
public HashSet<EntityUid> PassableEntities = new();
/// <summary>
/// Light level for this tick, to avoid re-calculating for more than one collision a tick.
/// </summary>
public GameTick LastLightCheckTick = GameTick.Zero;
public float LastLightLevel;
}
@@ -0,0 +1,139 @@
using Content.Shared._DV.Body;
using Content.Shared._DV.Light;
using Content.Shared.Projectiles;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Timing;
namespace Content.Shared._DV.ShadowWalk;
/// <summary>
/// Can walk through darkness freely.
/// </summary>
public sealed partial class SharedShadowWalkSystem : EntitySystem
{
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedLightReactiveSystem _lightReactive = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
/// <summary>
/// How far outside a tagged object's AABB the walker's centre must be before the object is untagged (and so becomes solid again on the next collision).
/// At least the walker's collision radius, so an object is never made solid while it still overlaps the walker.
/// </summary>
private const float UnstickMargin = 0.45f;
/// <summary>
/// Gamefeel. Non-walls get a bigger unstick margin so they stay unstick even if you clip into a wall. Prevents getting stuck in walls.
/// </summary>
private const float MovableUnstickMargin = 1f;
private EntityQuery<LightLevelHealthComponent> _lightHealthQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<ProjectileComponent> _projectileQuery;
private readonly List<EntityUid> _toRemove = [];
public override void Initialize()
{
base.Initialize();
_lightHealthQuery = GetEntityQuery<LightLevelHealthComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_projectileQuery = GetEntityQuery<ProjectileComponent>();
SubscribeLocalEvent<ShadowWalkerComponent, PreventCollideEvent>(OnPreventCollide);
}
public override void Update(float frameTime)
{
var query = EntityQueryEnumerator<ShadowWalkerComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (comp.PassableEntities.Count == 0)
continue;
var worldPos = _transform.GetWorldPosition(uid);
_toRemove.Clear();
foreach (var other in comp.PassableEntities)
{
// Untag anything we've deleted or fully walked clear of; the next collision with it will re-check the light level from scratch.
if (Deleted(other))
{
_toRemove.Add(other);
continue;
}
var margin = UnstickMargin;
// Non-statics get a bigger margin :)
if (_physicsQuery.TryComp(other, out var body) && body.BodyType != BodyType.Static)
margin += MovableUnstickMargin;
if (!_lookup.GetWorldAABB(other).Enlarged(margin).Contains(worldPos))
_toRemove.Add(other);
}
foreach (var other in _toRemove)
comp.PassableEntities.Remove(other);
}
}
private void OnPreventCollide(Entity<ShadowWalkerComponent> ent, ref PreventCollideEvent args)
{
if (args.Cancelled)
return;
// Only phase through hard blockers; sensor fixtures must keep triggering.
if (!args.OurFixture.Hard || !args.OtherFixture.Hard)
return;
// Already phasing through this one: keep it passable until we've left it (pruned in
// Update), so a light change mid-overlap can never trap us inside it.
if (ent.Comp.PassableEntities.Contains(args.OtherEntity))
{
args.Cancelled = true;
return;
}
if (!CanPhaseThrough(args.OtherEntity, args.OtherBody))
return;
// A fresh collision: only phase if the walker itself is currently in darkness.
if (!InDarkness(ent))
return;
args.Cancelled = true;
ent.Comp.PassableEntities.Add(args.OtherEntity);
}
private bool CanPhaseThrough(EntityUid other, PhysicsComponent otherBody)
{
if (otherBody.BodyType == BodyType.KinematicController)
return false;
// Bullets never pass or hit based on collision timing.
if (_projectileQuery.HasComp(other))
return false;
return true;
}
private bool InDarkness(Entity<ShadowWalkerComponent> ent)
{
// Darkness is whatever the walker heals in, if it heals in darkness at all.
var threshold = _lightHealthQuery.TryComp(ent, out var lightHealth)
? lightHealth.DarkThreshold
: ent.Comp.DarkThreshold;
var curTick = _timing.CurTick;
if (ent.Comp.LastLightCheckTick != curTick)
{
ent.Comp.LastLightLevel = _lightReactive.GetLightLevelForPoint(ent.Owner);
ent.Comp.LastLightCheckTick = curTick;
}
return ent.Comp.LastLightLevel < threshold;
}
}
@@ -0,0 +1,10 @@
using Robust.Shared.Configuration;
using Content.Shared.CCVar.CVarAccess;
using Content.Shared.Administration;
namespace Content.Shared.Starlight.CCVar;
[CVarDefs]
public sealed partial class StarlightCCVars
{
}
@@ -0,0 +1,13 @@
using Robust.Shared.Configuration;
namespace Content.Shared.Starlight.CCVar;
public sealed partial class StarlightCCVars
{
/// <summary>
/// A newline-separated list of saved labels for the hand labeler tool
/// </summary>
public static readonly CVarDef<string> HandLabelerSavedLabels =
CVarDef.Create("interface.hand_labeler_saved_labels", "", CVar.CLIENTONLY | CVar.ARCHIVE);
}
@@ -0,0 +1,4 @@
- files: ["gnome_scream.ogg", "gnomedeath.ogg", "gnomelaugh.ogg", "gnome1.ogg"]
license: "CC-BY-SA-4.0"
copyright: "Audio recorded by Cepelinas and edited by rebe83"
source: "https://github.com/DeltaV-Station/Delta-v/pull/6027"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+24
View File
@@ -356,5 +356,29 @@ Entries:
id: 39
time: '2026-07-24T17:27:48.0000000+00:00'
url: https://github.com/DeltaV-Station/Delta-v/pull/6351
- author: ShepardToTheStars
changes:
- message: Curators, get off your lazy asses and help out the newbies.
type: Tweak
id: 40
time: '2026-08-09T18:14:15.0000000+00:00'
url: https://github.com/DeltaV-Station/Delta-v/pull/6374
- author: DisposableCrewmember42
changes:
- message: Added Unorbitable component, which prevents entities from being followed/warped
to.
type: Add
id: 41
time: '2026-08-22T16:18:32.0000000+00:00'
url: https://github.com/DeltaV-Station/Delta-v/pull/6435
- author: einknusprigestoast
changes:
- message: ERT elite hardsuits now have different values
type: Tweak
- message: added new guns to ERT gamma and amber loadouts
type: Add
id: 42
time: '2026-09-09T14:07:35.0000000+00:00'
url: https://github.com/DeltaV-Station/Delta-v/pull/6239
Name: DeltaVAdmin
Order: 5
File diff suppressed because it is too large Load Diff
+7
View File
@@ -1087,4 +1087,11 @@
id: 125
time: '2026-07-14T00:30:06.0000000+00:00'
url: https://github.com/DeltaV-Station/Delta-v/pull/6242
- author: Field Command
changes:
- message: 'Central Command: New CC is here!'
type: Add
id: 126
time: '2026-09-05T14:03:09.0000000+00:00'
url: https://github.com/DeltaV-Station/Delta-v/pull/6198
Order: 3
+4 -1
View File
@@ -1,7 +1,10 @@
[game]
hostname = "[EN][MRP] Delta-v (Ψ) | Apoapsis [US East 1]"
hostname = "[EN][MRP] Delta-V (Ψ) | Apoapsis [NA East 1]"
soft_max_players = 100
[adminlogs]
server_name = "Apoapsis"
[hub]
tags = "lang:en-US,region:am_n_e,rp:med,no_tag_infer"
@@ -108,6 +108,7 @@ time = 600
[chat]
max_announcement_length = 512
rate_limit_announce_admins_delay = -1
[audio]
attenuation = 4 # inversedistanceclamped 1 << 2, see audioparams.cs
+4 -1
View File
@@ -1,7 +1,10 @@
[game]
hostname = "[EN][MRP] Delta-v (Ψ) | Horizon [US East 3]"
hostname = "[EN][MRP] Delta-V (Ψ) | Horizon [NA East 3]"
soft_max_players = 80
[adminlogs]
server_name = "Horizon"
[vote]
preset_enabled = true
map_enabled = true

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