Compare commits

...

372 Commits

Author SHA1 Message Date
RedTerror
5aa7e2b91b update sprite vox&vulp(#3504) 2026-02-14 00:09:32 +07:00
Jerry
49794858f2 Fix Content.Packaging doesn't include secrets into a final server package (#3502) 2026-02-07 22:06:45 +05:00
Pok
66d45285fe Фикс СРП (#3501) 2026-02-07 23:32:33 +07:00
Pok
d2ee564b8f Перенос СРП в Guidebook (#3499) 2026-02-06 15:03:25 +07:00
Svist666s
333bc80458 paper fix (#3492) 2026-02-05 07:07:16 +07:00
Svist666s
35aebb7de3 Nodecision fix (#3496) 2026-02-05 07:05:11 +07:00
Pok
80acc36543 [Wiki] workflows (#3490) 2026-01-21 10:55:32 +07:00
Dmitry
18d2ddac70 Минор апдейт (#3489) 2026-01-20 21:20:10 +07:00
Голубь
ee8c0beff2 Мапперские штуки дрюки (#3438) 2026-01-20 20:35:33 +07:00
kosticia
cf6469fb62 lobby art update(#3485) 2026-01-20 20:26:08 +07:00
lzk
c1282f2e5e paper interface translate (#3312) 2026-01-20 13:13:57 +07:00
JrInventor05Next
1c771c6709 tweak NukeOpstTest.cs (#3486) 2026-01-20 13:08:56 +07:00
Dmitry
7ee4e66e77 Merge pull request #3488 from DIMMoon1/upstream01.2
Upstream01.2
2026-01-20 12:46:48 +07:00
Pok
b462533f82 [Wiki] Подгрузка ВСЕГО на вики (#3487) 2026-01-20 03:28:44 +07:00
Dmitry
e88b41e637 ok 2026-01-19 06:26:19 +07:00
Dmitry
bd135d7604 merge remote wizden/stable 2026-01-19 05:58:27 +07:00
Myra
a9e4370d25 Stable merge (#42505) 2026-01-18 22:28:07 +01:00
Vasilis The Pikachu
7b73811d47 Revert final changelogs for reverted prs 2026-01-18 21:53:21 +01:00
Vasilis The Pikachu
ca07d6be49 Revert "Allow station tiles to be placed on solid ground and other platings. (#38898)"
This reverts commit 418b2b70b0cade0d73b46d4c4230dfcbba6abced.

Maintainer vote decided to vote this out due to code quality issues. The original contributor is aware and will get help from a maintainer to reintroduce it
2026-01-18 21:53:21 +01:00
Vasilis The Pikachu
e247ea5850 Revert "Fix RCD light spam, bypass of indestructible tiles and some plating fixes (#42432)"
This reverts commit 7d58e42ade391a61183145d271cb4e76b683bc22.

Required to revert 418b2b70b0cade0d73b46d4c4230dfcbba6abced via maintainer vote due to code quality issues.
2026-01-18 21:53:21 +01:00
ScarKy0
1a5d13fcac [Hotfix] Delivery console unanchorable (#42499) 2026-01-18 16:30:55 +01:00
Vasilis The Pikachu
9d3f2219b9 Remove changelog for https://github.com/space-wizards/space-station-14/pull/42390 2026-01-17 21:17:13 +01:00
Dmitry
09db1f5b77 upstreamfix
1
2026-01-17 02:43:19 +07:00
Dmitry
f501ebec66 merge remote master wizden 2026-01-16 14:30:48 +07:00
PJBot
ab9cf3b5cc Automatic changelog update 2026-01-16 02:10:08 +00:00
pathetic meowmeow
0d75154385 Fix core pinpointer pieces having a 5-pointer recipe (#42446) 2026-01-16 01:53:25 +00:00
PJBot
57e9a64d27 Automatic changelog update 2026-01-16 00:53:43 +00:00
Nemanja
d2ac15c76f Fix flatpacker exploit ignoring board costs (#42445)
Fix flatpacks ignoring costs and board requirements
2026-01-16 00:22:24 +00:00
PJBot
57ac7bbe4f Automatic changelog update 2026-01-16 00:36:41 +00:00
ScarKy0
897a2d40bc Add Mortar and Handheld Juicer (#42019)
* init

* API

* testing

* review

* return

* good enough, fix later

TODO:
Proper prototype
DoAfter
Sounds

* "proper" prototype

TODO
DoAfter
Sprite

* proper protos, mortar sprite

* juicer sprites

TODO:
Juicer sounds
Makeshift crafting recipes
Add regular to vendors

* sprite tweak

* juicing sound, cleanup, construction

* vendors

* line end

* attribution newline

* small balance tweak

* Let it be known id never webedit

* meta

* item size

* review

* handhelds

* partial review

* cache solution, looping

* graph

* review

* popup

---------

Co-authored-by: Janet Blackquill <uhhadd@gmail.com>
2026-01-16 00:19:42 +00:00
PJBot
6df3ed9682 Automatic changelog update 2026-01-15 21:38:28 +00:00
B_Kirill
b14964398b Camera map (#39684)
* Camera map

* I hope this helps

* Review 1

* Review 2

* Review 3

* Review 4

* Review 5

* Colorblind mode support

* Review 6

* Change design

* Map wire

* Logic fix

* Fix a terrible mistake

* Fix

* Fix 2

* Small rename

* More fix

* Better removal

* And another fix

* Will it work?

* It is literally pointless

* some small things
2026-01-15 21:21:55 +00:00
PJBot
8fb3e138a9 Automatic changelog update 2026-01-15 20:40:10 +00:00
Velken
7d58e42ade Fix RCD light spam, bypass of indestructible tiles and some plating fixes (#42432)
* No more light spam, and some plating fixes

* fixed test
2026-01-15 20:22:54 +00:00
PJBot
cd6c521b37 Automatic changelog update 2026-01-15 20:02:13 +00:00
ScarKy0
c7e8bbbf87 Add Paper Centrifuge (#42040)
* init

* sound

* sprite, half functional construction

* proper recipe

* oops

* loop sound

* inhands

* review

* review squared
2026-01-15 19:45:20 +00:00
Kyle Tyo
84ca0ebe9c Add attribution to Tippy.rsi (#42346)
Update meta.json
2026-01-15 19:20:00 +00:00
PJBot
ab2a4ebd93 Automatic changelog update 2026-01-15 19:01:35 +00:00
āda
fdeb5a736d Rebase vials to DrinkBase, closeable vials, mini vials (#36132)
* .rsi

* mini vial

* try stop TryStopNukeOpsFromConstantlyFailing from failing

* slight sprite change

* mail and lathe recipe

* real test fail

* resolve arbitrage

* cleanup

* always forgetting something

* always forgetting something

* drink no more

* remove integrated vial

* initial port

* Rename the rsi states

* Rename the rsi states

* up to standards

* finish

* testfail

* minor touchup

* arby

* op

* mix meta

* small changes to the vials

* commit

* commit

* style nit

---------

Co-authored-by: iaada <iaada@users.noreply.github.com>
Co-authored-by: Janet Blackquill <uhhadd@gmail.com>
2026-01-15 18:32:37 +00:00
PJBot
610881db82 Automatic changelog update 2026-01-15 18:43:40 +00:00
Orsoniks
619672a089 Improved Health Examination Coloring (#38231)
* better colors

* fix worst offenders of contrast

---------

Co-authored-by: Janet Blackquill <uhhadd@gmail.com>
2026-01-15 18:26:28 +00:00
slarticodefast
28a4a548b6 Add integration test for drains (#41190)
* drain test

* fix linter fail
2026-01-15 17:53:30 +00:00
PJBot
b723d7e49e Automatic changelog update 2026-01-15 18:09:08 +00:00
rumaks
766f429fd9 Make chemicals not react inside pills (and stomachs) (#41457)
no reactions in pills
2026-01-15 17:52:24 +00:00
Fruitsalad
4f997f2069 Cryo pod UI (#41850)
* Add CryoPodWindow (placeholder)

* Change HealthAnalyzerWindow: split off reusable HealthAnalyzerControl for cryo pod UI

* Improve CryoPodWindow: add health analyzer

* Improve CryoPodWindow: add eject button

This wasn't requested in the issue but I implemented it as practice with the UI system.

* Rewrote GasAnalyzerWindow, split off reusable gas mix viewer for cryo pod

* Change GasAnalyzerWindow: change back to three columns

With two rows you get a layouting bug when there's a lot of different gases, which looks somewhat bad. I didn't feel like fixing the layouting bug (it's an engine issue) so we're going back to three columns. That way you don't ever get two rows in practice.

* Change GasAnalyzerWindow: simplify by disabling Resizable

I added a lot of complexity to make resizable work nicely with a derived max & min size, but it's not necessary.

* Change GasAnalyzerWindow: file-wide namespace

* Change GasAnalyzerSystem: add GenerateGasMixEntry

* Split HealthAnalyzerUiState from HealthAnalyzerScannedUserMessage

* Rewrote CryoPodWindow, add atmos info

* Improve CryoPodWindow: add loading placeholder

* Improve CryoPodWindow: add internationalization support

* Fix GasAnalyzerControl: add missing translation

* Improve CryoPodWindow: add beaker info, high temperature warning

* Improve CryoPodWindow/System: inject button in window + necessary system changes

* Fix CryoPodWindow: Entering cryopod now closes window

This way you can't heal yourself with a cryopod.

* Change CryoPodWindow: add & update comments

* Change HealthAnalyzerComponent: remove `uiKey` property (no longer necessary)

* Tiny fixes

* Improve CryoPodUiMessage: replace string with enum

* Change GasAnalyzerWindow: simplify Measure code

* Change CryoPodComponent: rename Injecting to InjectionBuffer

* Change CryoPodBUI: tiny code simplification

* Fix HealthAnalyzerComponent: Removed stray import

* Improve CryoPodWindow: Prettier, concise atmos

* Improve CryoPodWindow: Chemicals bar chart

* Improve CryoPodWindow: Add Ruler to reagents

* Change CryoPodWindow: More horizontal layout

* Improve CryoPodWindow: Reduce height jiggling

The health analyzer's height changes a lot, which can be annoying with the buttons (for example when the oxygen damage label is popping in and out)

* Improve CryoPodWindow: Add setup checklist

This is mostly here to fill vertical space in the new horizontal layout.

* Improve CryoPodWindow: Eject beaker button

* Improve CryoPodWindow: Localization

* Improve CryoPodWindow: Add BeakerBarChart

An animated version of the chemicals chart

* Fix CryoPodSystem: Ejecting beaker no longer clears injection buffer

* Improve BeakerBarChart: Not animated on first frame

* Fix CryoPodWindow: Fix broken translation

* Improve CryoPodWindow: Reorder sections

* Fix BeakerBarChart: Tooltips now show up

* Change BeakerBarChart: Reorder functions

* Change CryoPodWindow: Reorder sections, change margins

* Change CryoPodWindow: Edit flavor text

* Revert changes to GasAnalyzerWindow

Since GasAnalyzerControl is no longer used in CryoPodWindow, these changes are no longer relevant to this PR.

* Tidy CryoPodWindow: Remove old workarounds

These are old layouting bug workarounds from the older version of CryoPodWindow that had a ScrollContainer in it. They're no longer necessary. Less ScrollContainers less problems.

* Tidy up: Remove unused imports

* Remove LabelledSplitBar

It was replaced by BeakerBarChart, which is a lot fancier.

* Tidy up: Tiny code style fix

* Change CryoPodSystem: Move code from server to shared

This is still without adding UI prediction

* move a ton of stuff to shared.

* one last thing

* Improve BeakerBarChart: Keep visual entry width when swapping beakers

* Improve BeakerBarChart: Respect beaker order of reagents

* Improve CryoPodWindow: Ensure space for injection buffer

 We need to keep space on the chart for the injection buffer after swapping to a full beaker.

* Improve CryoPodWindow: Prettier ejection error

* Improve CryoPodWindow: Add "Cooling patient" status

* BeakerBarChart: Fix UI scale bug

* BeakerBarChart: Fix bluespace beaker ugliness

* BeakerBarChart: Add more pod status strings

* HealthAnalyzerControl: Filewide namespace, sort imports

* Style fix: Replace `bool x = y` with `var x = y`

* CryoPodUiMessage: Split off separate class for inject

* SharedCryoPodSystem: Move message-related code into Subs.BuiEvents

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-15 17:52:03 +00:00
slarticodefast
5cda60f2f9 Predict defibrillators and add an integration test for them (#41572)
* cleanup

* fix fixtures

* prediction

* fix test

* review

* fix svalinn visuals

* fix chargers

* fix portable recharger and its unlit visuals

* fix borgs

* oomba review

* fix examination prediction

* predict

* readd zapping interacting mobs
2026-01-15 17:43:32 +00:00
slarticodefast
499e9f9a0f Predict TransferAmountBoundUserInterface (#42358)
prediction!!!
2026-01-15 17:17:00 +00:00
PJBot
241b0930bc Automatic changelog update 2026-01-15 17:12:06 +00:00
Samuka
bd096a044b Make heavy xenoborg able to "swim" in space (#42415)
MovementAlwaysTouching
2026-01-15 16:54:20 +00:00
PJBot
4920404994 Automatic changelog update 2026-01-15 16:47:54 +00:00
Connor Huffine
aa8a61b6ae Make cancer mice actually hurt (#42298)
* Update animals.yml

* That was a bit too much
2026-01-15 16:30:07 +00:00
B_Kirill
07076a5a32 Cleanup warnings: CS0414 (#42429)
cleanup
2026-01-15 13:37:11 +00:00
Pok
eff710e312 [Wiki] loc fix 2 (#3484) 2026-01-15 13:44:45 +07:00
PJBot
17997984ac Automatic changelog update 2026-01-15 05:41:30 +00:00
ArtisticRoomba
7532171090 Increase TEG power generation by 75% (#42421)
increase TEG power by 75%
2026-01-15 05:25:07 +00:00
SnappingOpossum
5d929533fc Move artifact random spawners to entity table spawners. (#42422)
* Move artifact random spawners to entity tables

* That's a pretty big thing to miss

* This is embarassing

* Apply review
2026-01-15 04:27:16 +00:00
War__Prophet
4219bca74b Put arrows on all the single-directional pipes (#42408)
k
2026-01-15 03:27:24 +00:00
PJBot
e196d37841 Automatic changelog update 2026-01-15 02:47:41 +00:00
TriviaSolari
d6377862c1 Reduce unnecessary ComponentInit work for airtight entities (#42390)
Refactor AirtightSystem to skip rotation checks for omnidirectional blocks on init

Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
2026-01-15 02:31:05 +00:00
DrSmugleaf
1f80b6a95d Fix TryAllReactionsTest reacting early and not checking priority (#42412) 2026-01-14 23:39:50 +00:00
ArtisticRoomba
f702dc8f2d Atmos GasSpecificHeats in shared (#42136) 2026-01-14 15:21:04 -08:00
Charlie Morley
14b867dbe1 allow shuttle to Scan for Objects while FTL is on cooldown (#42283)
* allow shuttle to Scan for Objects while FTL is on cooldown

* cleanup

---------

Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
2026-01-14 21:38:19 +00:00
PJBot
48cbd020a8 Automatic changelog update 2026-01-14 21:41:45 +00:00
TheFlyingSentry
d857acfc07 Fixed Containment Generators not updating pointlight correctly (#42289)
oh my god i've been overcomplicating this you don't want to know...
2026-01-14 21:19:59 +00:00
PJBot
dc47295d24 Automatic changelog update 2026-01-14 21:27:47 +00:00
korczoczek
acdeac6172 Make lathes refund materials when recipe gets cancelled (#42416)
30 day free refund
2026-01-14 21:12:30 +00:00
PJBot
6bc617ca07 Automatic changelog update 2026-01-14 20:37:20 +00:00
Samuka
e5ce73a471 Xenoborgs now drop pieces of pinpointer (#42295)
* add sprites

* update sprite

* repaired sprite

* repaired pinpointer

* different id

* piece of pinpointer core

* borgs drop the piece

* end of file new line

* typo

Co-authored-by: āda <ss.adasts@gmail.com>

* make the tape darker

* four variations

* same variation in the crafting menu

---------

Co-authored-by: āda <ss.adasts@gmail.com>
2026-01-14 20:22:28 +00:00
PJBot
b220631278 Automatic changelog update 2026-01-14 20:20:26 +00:00
Kowlin
b5fb3d4bdb Replace the Reach DoorRemoteAll with DoorRemoteCustom (#42385)
Replace DoorRemoteAll with DoorRemoteCustom
2026-01-14 19:54:40 +00:00
PJBot
95496c8d2c Automatic changelog update 2026-01-14 20:05:45 +00:00
SlamBamActionman
91dd9f7be2 Add a target station map to the LoneOp shuttle (#42376)
Initial commit
2026-01-14 19:51:19 +00:00
Pok
d326420204 [Wiki] loc fix (#3483) 2026-01-14 23:52:54 +07:00
PJBot
7ebca1d8cc Automatic changelog update 2026-01-14 15:04:12 +00:00
Vy
9979a08225 Maid uniform sprite change. (#38335)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload
2026-01-14 14:49:22 +00:00
pathetic meowmeow
fb133494cc Decouple gibbing from the body system (#42405)
* Decouple gibbing from the body system

* allow gibs that don't drop giblets

* pass through user

* prediction gon

* comment

* destructible

* playpvs

* very very very very very very very minor cleanup

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-14 05:57:08 +00:00
Pok
1cc0f83350 [Wiki] Подгрузка всех ключей локализации (#3482) 2026-01-14 12:55:26 +07:00
PJBot
0af56cefcb Automatic changelog update 2026-01-14 05:22:29 +00:00
ArtisticRoomba
6cae5d9c4a Fix TritiumFireReaction low fuel limiting behavior (#42407)
fix fuel burn limiting logic incorrectly taking max instead of min
2026-01-14 05:08:25 +00:00
ScarKy0
2399b61ca7 EmpResistance cleanup (#42402)
* init

* yeah

* Update SharedEmpSystem.cs
2026-01-14 00:59:19 +00:00
PJBot
1fdc70aa3d Automatic changelog update 2026-01-13 23:42:43 +00:00
ArtisticRoomba
b5f0dd81fc Increase trit-to-frezon ratio from 1:8 to 1:50 (#42400)
roll back to old trit -> frez ratio
2026-01-13 23:28:51 +00:00
ArtisticRoomba
60e172e128 AirtightSystem Tests (#42190) 2026-01-13 15:06:59 -08:00
PJBot
ac1870a25f Automatic changelog update 2026-01-13 22:37:04 +00:00
Sarah C
c7e4f20f02 Fix tritium fires breaking conservation of mass (#41870)
* i can't believe this went unnoticed for so long

* i may be stupid
2026-01-13 22:16:09 +00:00
PJBot
0b27da57f4 Automatic changelog update 2026-01-13 22:23:02 +00:00
Sir Warock
7540c8f152 Pry open critical Borgs (#42319)
* One commit ops

* Please the maintainer gods

* More requested changes

* review

* actually this is probably a good idea

---------

Co-authored-by: ScarKy0 <scarky0@onet.eu>
2026-01-13 22:08:45 +00:00
PJBot
3cec0aa476 Automatic changelog update 2026-01-13 18:31:52 +00:00
ScarKy0
c860502e66 Viper High Capacity Ammo (#42392)
* yeah

* Fix sprite & let mk58 use the mag

---------

Co-authored-by: SlamBamActionman <slambamactionman@gmail.com>
2026-01-13 18:17:22 +00:00
PJBot
4d1843f5e4 Automatic changelog update 2026-01-13 17:20:02 +00:00
ScarKy0
a18fc33724 Fix scram allowing you to bring someone along (#42393)
1 line bugfix
2026-01-13 16:56:05 +00:00
PJBot
d723054860 Automatic changelog update 2026-01-13 17:05:51 +00:00
ScarKy0
f8a6a79928 Buff throwing knives kit (#42391)
init
2026-01-13 16:50:30 +00:00
ConstantlyConfused
4ae961babb A handful of typo fixes (#42396)
fix a handful of typos
2026-01-13 14:11:44 +00:00
War__Prophet
f0f0871609 WYA to Where you at (#42350)
* god is dead

* bro

* Update speech-chatsan.ftl
2026-01-13 14:07:20 +00:00
PJBot
69d2ddd8bf Automatic changelog update 2026-01-13 14:06:35 +00:00
Velken
418b2b70b0 Allow station tiles to be placed on solid ground and other platings. (#38898)
* WORK IN PROGRESS 1

* ITS ALIVE, ALIVE!!!!

* clean up

* WIP 1

* fix small oversight

* big diff of doom

* added CVAR to tile history stack size

* component time

* filescoped namespaces + remove redundant nametag

* fix silly little mistakes

* typo

* TileStacksTest

* bweeeeeeh :P

* nuke cvar

* :3

* WIP2025

* Fix submodule

* It's beginning to look a lot like Christmas

* It's the Most Wonderful Time of the Year

* tiny fix

* fixed extra spacing on yaml

* slightly improve tilestacking test

* Part 1 out of 2 (part 2 tomorrow)

* Part 2

* add a simple tile construction test for tilestacking

* guh

* address reviews (no documentation yet)

* documentation be upon ye

* remove extra spaces

* prediction fix

* dirt

* oops :p

---------

Co-authored-by: Killerqu00 <killerqueen1777@gmail.com>
Co-authored-by: Killerqu00 <47712032+Killerqu00@users.noreply.github.com>
Co-authored-by: ScarKy0 <scarky0@onet.eu>
2026-01-13 13:51:40 +00:00
PJBot
53607b8ca1 Automatic changelog update 2026-01-13 13:45:08 +00:00
alexalexmax
738f55c456 Adds EMP Resistance component, gives it to ninja suit and headset (#42334)
* add comp and apply to ninja gear

* cleanup

* requested changes

---------

Co-authored-by: seanpimble <149889301+seanpimble@users.noreply.github.com>
2026-01-13 13:30:53 +00:00
Errant
04bda3ad59 Role time tracking support for admins (#31776)
role time tracking support for admined players

Co-authored-by: Milon <milonpl.git@proton.me>
Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
2026-01-13 10:22:04 +00:00
PJBot
69330e5752 Automatic changelog update 2026-01-13 10:17:09 +00:00
ScarKy0
4d16565c2a Lower smuggler's satchel price to 1TC (#42381)
Update uplink_catalog.yml
2026-01-13 10:02:29 +00:00
PJBot
ea131f7368 Automatic changelog update 2026-01-13 08:13:51 +00:00
SlamBamActionman
29c68e467a Add the Syndicate Delivery Console to the Nukie planet + target station maps (#42337)
* Initial commit

* Add station maps
2026-01-13 07:57:39 +00:00
PJBot
f24e1dba62 Automatic changelog update 2026-01-13 07:58:54 +00:00
ScarKy0
6cbd19adfa Lower hyperzine injector cost (#42383)
I love TC
2026-01-13 07:37:27 +00:00
PJBot
f3c40aa46c Automatic changelog update 2026-01-13 07:43:59 +00:00
Princess Cheeseballs
4cd5d115bf Balance swing at Vestine (#42302)
* AAAAAAAAAAAAAAAAAAAAAAA

* whhops

* full range whoop

* less diff any%

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-13 07:29:34 +00:00
PJBot
de9d8334d1 Automatic changelog update 2026-01-13 01:03:13 +00:00
Kowlin
de10a3a948 Allow the admin door remote to toggle overcharge (#42370)
* Allow the admin door remote to toggle overcharge

* Apply suggestions from code review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-01-13 00:48:41 +00:00
PJBot
84a21039d6 Automatic changelog update 2026-01-12 22:31:33 +00:00
Quantum-cross
d06b18a8f0 Allow late join from arrivals to be considered for antagonist. (#39837)
* Allow late join from arrivals to be considered for antagonist.

* Don't use `PendingClockInComponent` to block late join antag selection, instead do an arrivals grid transform check with new helper function `IsOnArrivals`.

* Minor formatting fixes

* missing using

---------

Co-authored-by: SlamBamActionman <slambamactionman@gmail.com>
2026-01-12 22:17:27 +00:00
PicklOH
7f4bc8f7d1 Reworks destruction Space Law to include Silicons (#42317)
* Reworks destruction laws to include silicons

* Destruction of Vital Infra

* oops

* Last one?

* Non-Hostile Borgos

* formatting

---------

Co-authored-by: ScarKy0 <scarky0@onet.eu>
2026-01-12 22:08:06 +00:00
PJBot
45b3609b8a Automatic changelog update 2026-01-12 22:16:04 +00:00
chaisftw
360bfd6e1c Spray bottles with visible reagent contents (#42155)
* Spray bottles with visible reagent contents

* Fixed cargo catalog to correctly reference the new spray bottle rsi

* Fixed indentation on rsi meta files

* Updated copyright in spray_bottle.rsi meta

* Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml

Co-authored-by: āda <ss.adasts@gmail.com>

* Updated to follow yml convention, modified to inherit from DrinkBase and renamed fill textures

* Fixed solution names

* Updated solution name in BorgMegaSprayBottle

* de-hardcode solution name

* less breaking

---------

Co-authored-by: āda <ss.adasts@gmail.com>
Co-authored-by: iaada <iaada@users.noreply.github.com>
2026-01-12 22:01:35 +00:00
PJBot
11f308729d Automatic changelog update 2026-01-12 21:11:15 +00:00
Princess Cheeseballs
c9ec5e81f0 Medical Cyborg Modules Rework. (#42123)
* PUSH!!!

* Femtanyl

* eh swap em back

* My PR is ruined!!!

* review 2 electric boogaloo

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-12 20:56:37 +00:00
Kyle Tyo
06a962559a Fix holywater locale string usage. (#42378)
Update chemistry-bottles.yml
2026-01-12 14:24:32 +00:00
slarticodefast
94071a6350 Fix GenpopLockerBoundUserInterface prediction (#42365)
fix genpop prediction
2026-01-12 02:58:56 +00:00
PJBot
71040149a1 Automatic changelog update 2026-01-12 03:06:05 +00:00
Velken
98647f1f0f Admin: fixes description for "help osay" (#42368)
2 diff 2 me
2026-01-12 02:51:49 +00:00
SlamBamActionman
435b7d5cf8 Add the ability for station maps to track grids they are not on (#41248)
* Initial commit

* Accidentally included the nukie map changes

* Fix the gridcheck

* Addressing review

* Review change

* Review comments
2026-01-12 02:47:47 +00:00
slarticodefast
4fafb55477 Predict BarSignBoundUserinterface (#42364)
fix bar sign prediction
2026-01-11 22:48:01 +00:00
PJBot
a92702e780 Automatic changelog update 2026-01-11 21:48:04 +00:00
SlamBamActionman
9338834b1b Add admin logs for connecting/disconnecting players (#42363)
* Initial commit

* small tweak

---------

Co-authored-by: ScarKy0 <scarky0@onet.eu>
2026-01-11 21:34:08 +00:00
slarticodefast
716e5ace87 Fix action tooltip warnings (#42361)
fix action examine warnings
2026-01-11 20:05:32 +00:00
Perry Fraser
b707110dea fix: clear health bar/icon overlay damage containers on update (#39288)
* fix: clear health overlay damage containers on update

* linqn't

* import

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-01-11 16:39:21 +00:00
PJBot
3a0049e534 Automatic changelog update 2026-01-11 04:20:56 +00:00
ScarKy0
5025e0d286 Janiborg Module Cleanup (#42330)
init
2026-01-11 04:06:06 +00:00
Pieter-Jan Briers
c0fbaf1228 Fix warning spam from ShortKeyName (#42351) 2026-01-11 00:46:54 +00:00
github-actions[bot]
f0ae5896b7 Update Credits (#42352)
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
2026-01-11 01:43:20 +01:00
PJBot
46e86149e9 Automatic changelog update 2026-01-10 23:17:59 +00:00
ScarKy0
5d9371931a Predict Mind State Examine (#42253)
* init

* review

* i might be stupid

* docs

* datafieldn't

* update comments
2026-01-10 23:02:56 +00:00
PJBot
c3d7652620 Automatic changelog update 2026-01-10 20:10:45 +00:00
BarryNorfolk
319617f6ba Use NextByte to properly construct colours (#42335) 2026-01-10 19:56:17 +00:00
KillanGenifer
aad5613341 playtime flag tweaks (#3481) 2026-01-11 02:35:35 +07:00
PJBot
4ebdbff86b Automatic changelog update 2026-01-10 17:02:43 +00:00
SolidSyn
32dafcf2ea Foldable wig on clowns mask (#42208)
* Clowns base mask now has the ability to toggle the wig on and off.

* Changes the clown mask to have a foldable wig.

* Adds my credit for the wigless sprites in the meta.json

* Redid the description.

* Added an a to the description. Smiles.

* Resolved changes, thanks beck!
2026-01-10 16:48:33 +00:00
SlamBamActionman
a7fc17dfc4 Add the Syndicate Delivery Console + Corpsman Medicine Bundle (#41201)
* Initial commit

* Add a note to not abuse the darrrrn machine

* Remove nukie planet (hopefully) and fix duffelbag

* jug solution name

---------

Co-authored-by: iaada <iaada@users.noreply.github.com>
2026-01-10 02:07:35 +00:00
KillanGenifer
671eca79c1 Playtime adminflag (#3480) 2026-01-10 07:18:03 +07:00
PJBot
9256f3f2a1 Automatic changelog update 2026-01-09 23:25:35 +00:00
Hitlinemoss
d7fcb03336 BUGFIX: Cabbage placed on taco shells no longer turns into a carrot (#42326)
BUGFIX: Cabbage places on tacos no longer looks like a carrot
2026-01-09 23:11:08 +00:00
PJBot
72b022349f Automatic changelog update 2026-01-09 18:46:27 +00:00
B_Kirill
96d2339345 Fix projectile deceleration (#42320) 2026-01-09 18:31:49 +00:00
ScarKy0
85b3dcc9cc Stake Admin Alert (#42324)
i forgot this is a thing
2026-01-09 17:48:46 +00:00
PJBot
7586c8017a Automatic changelog update 2026-01-09 15:03:45 +00:00
Perry Fraser
78343b2dbb feat: allow removing empty smart fridge entries (#39195)
* feat: allow removing empty smart fridge entries

* review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-01-09 14:48:38 +00:00
Pok
51e7a39bad Predict DrainSystem (#41711)
* DrainSystem-move-to-shared

* random

* review

* review 2

* Apply suggestions from code review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-01-09 14:18:07 +00:00
PJBot
16c9cfe899 Automatic changelog update 2026-01-09 06:59:29 +00:00
mikey
ec024001e7 Increase shuttle FTL cooldown to prevent FTL spamming (#42209)
* seperate out shuttle cooldowns

* fix

* feedback

* fix spacing

* update to TimeSpan

* GOAT float

* return to TimeSpan

* add var

* clarify with seconds

* clarifying some things
2026-01-09 06:44:22 +00:00
PJBot
22682fccc3 Automatic changelog update 2026-01-09 04:15:15 +00:00
B_Kirill
386aca70c7 Add craft for bonfire and bonfire with stake (#42211)
* Add craft for bonfire and bonfire with stake

* review

* review

* nullable MaxFireStacks

* retry

* retry

* review

* I will change it and hope that they will agree with me

* Revert "I will change it and hope that they will agree with me"

This reverts commit 83823692d0116bf9aa9eceb85a10e95c88b51fb9.

* construction

Merged the graphs
Changed the bonfire stake ID to follow the proper naming scheme

* add destroy sound

* planks Instead of logs

* of course I forgot about ftl

* Slarti review + Princess review

Yes I sneaked an admeme abuse change here

* a small fix

* clamp firestacks

* This was on purpose, leave as is

This reverts commit 7d63e38b66cb63e5e50b7fac5030013e2ef508b5.

* irrelevant

* Fixtures

* cleanup

---------

Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-09 04:00:36 +00:00
Princess Cheeseballs
8ec4669bf9 Allow items spawned in the smart fridge to show up as an entry. (#42268)
* Allow items spawned in the smart fridge to show up in the view

* AAAAAAAAAAAAAAAAAA

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-09 00:35:39 +00:00
PJBot
b406193372 Automatic changelog update 2026-01-09 00:04:20 +00:00
Alex
d0e9816261 Fland: Fix atmos right side apc (#42314)
fland apc lv
2026-01-08 23:49:53 +00:00
Tayrtahn
f8ff3a92aa Fix broken state when attempting to escape a locker while cuffed (#42313)
Check that the DoAfter starts successfully instead of just assuming it does.
2026-01-08 21:18:23 +00:00
PJBot
acc95fae5e Automatic changelog update 2026-01-08 19:54:05 +00:00
LevitatingTree
5d5c61fefc Bring back shrug sanitization in a different form (#41236)
* Re-add chat-san & add o//

* changed o// to :?
2026-01-08 19:29:44 +00:00
PJBot
a42eb5695c Automatic changelog update 2026-01-08 19:39:32 +00:00
Perry Fraser
e4ac948dec fix: respect AllowedSlots for gogo hat (#39189) 2026-01-08 19:25:43 +00:00
āda
019268b056 Remove battery from the handheld health analyzer (#42292)
* commit

* review

* review 2

---------

Co-authored-by: iaada <iaada@users.noreply.github.com>
2026-01-08 19:25:15 +00:00
Tayrtahn
28e830f8b4 Fix forced vaping checking if the user's mouth is blocked instead of the target's. (#42311)
Swap user and target arguments of IngestionSystem.HasMouthAvailable call.
2026-01-08 19:09:23 +00:00
JrInventor05Next
617680fb87 fix NukeOpsTest (#3472) 2026-01-09 01:13:30 +07:00
Dmitry
8ef9ac26e9 Порт локализации (#3479)
Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com>
Co-authored-by: kosticia <kosticia46@gmail.com>
Co-authored-by: cfif126 <94059374+cfif126@users.noreply.github.com>
Co-authored-by: lapatison <100279397+lapatison@users.noreply.github.com>
Co-authored-by: lzk228 <alosamarcuk@gmail.com>
2026-01-09 01:11:43 +07:00
Dmitry
ad382dcc23 Merge pull request #3478 from DIMMoon1/upstream01.1
Upstream01.1
2026-01-08 21:53:23 +07:00
lunarcomets
80d38c51b3 fix electrify sound effects being reversed (#42294)
* The Fix

* The Fix Part 2
2026-01-08 14:35:39 +00:00
Dmitry
37c4e15d32 0 sourse fix 2026-01-08 20:01:57 +07:00
Dmitry
f9469a5f38 ipcfix 2026-01-08 18:43:20 +07:00
Dmitry
1a9a79cbba upstream codefix
kek
2026-01-08 18:11:46 +07:00
Princess Cheeseballs
e27ae3d428 Goliath Hardsuit Fixes (#42303)
goliath hardsuit fixes

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-08 10:51:47 +00:00
PJBot
7aba244b38 Automatic changelog update 2026-01-08 11:04:18 +00:00
Princess Cheeseballs
350c67c73e Fix Internals Sounds not working. (#42304)
ern

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-08 10:49:50 +00:00
JackspajfMain
142ce2a59b Fix Capitalization on HoP's Fountain Pen (#42300)
capitalization
2026-01-08 05:50:59 +00:00
Tiniest Shark
da7bbe5918 Warden Suit Tail Fix (#42276)
tail is right now
2026-01-08 05:08:07 +00:00
Dmitry
6187a5a7bd merge remote stable wizden 2026-01-08 10:32:52 +07:00
Perry Fraser
74ead53ceb Remove yaml'd non-existent components + test for that (#38878)
* chore: remove some unregistered components

* feat: test for components being ignored on client + server
2026-01-07 23:48:17 +00:00
Connor Huffine
a9b953cdfe Add origin member to class (#41250)
* Add origin member to class

* whitespace

* Add comments

Added some doc polish while I was here.

* Update comments

Clarification and accuracy

* Apply suggestions from code review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-01-07 22:31:13 +00:00
PJBot
3633cdb537 Automatic changelog update 2026-01-07 22:20:48 +00:00
Connor Huffine
b267bad990 Ninja bomb planting tweak (#41208)
* Anchorable to Animatable

* Change to whitelist

Windows and walls only

* Update guidebook.
2026-01-07 22:05:39 +00:00
PJBot
95bdc66f10 Automatic changelog update 2026-01-07 22:04:34 +00:00
āda
8b9801a5bb Reorganize and clean Fun yml (#42184)
* don't look at this commit size i'm not proud

* self review

* more final changes

* more comments

* review

* review

---------

Co-authored-by: iaada <iaada@users.noreply.github.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-01-07 21:49:24 +00:00
PJBot
8be191ab8c Automatic changelog update 2026-01-07 19:39:01 +00:00
SomegnihT
03b7788774 Vox now say they become fried chicken upon taking enough heat dmg (#42280)
* Made it so that vox now say they become fried chicken upon taking enough heat dmg

* updated a comment to be more clear

* changed the name of bodyburnvox-text-other to bodyburn-vox-text-other

* forgot that I needed to also update the text in vox.yml to go with the previous commit change

* BurnBodyBehavior now takes bodyburn-text-others as default

* fixed suggested changes

* Relized I forgot to do a suggested change, I have now done it

* Update Content.Server/Destructible/Thresholds/Behaviors/BurnBodyBehavior.cs

* Update Content.Server/Destructible/Thresholds/Behaviors/BurnBodyBehavior.cs

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-01-07 19:24:19 +00:00
PJBot
66c1a989fd Automatic changelog update 2026-01-07 16:39:09 +00:00
Samuka
41f91a9207 Xenoborg camera monitor now shows xenoborgs names (#42205)
* update camera id

* revert code changes in SharedSurveillanceCameraSystem

* why change camera id if you can just send the entity name
2026-01-07 16:24:35 +00:00
āda
d65aa07a84 Grappling gun rope visual change (#42207)
* sprite changes

* system changes

---------

Co-authored-by: iaada <iaada@users.noreply.github.com>
2026-01-07 16:05:58 +00:00
ScarKy0
a8469ca509 Predict Rotting Examine (#42254)
* init

* review

* test

* Apply suggestions from code review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-01-07 15:58:28 +00:00
Kyle Tyo
20d1b2c6cb Fix attributions for /Resources/Audio/Misc/ (#42230)
* commit

* cleanup
2026-01-07 15:33:22 +00:00
PJBot
e3419b159e Automatic changelog update 2026-01-07 06:06:18 +00:00
ScarKy0
590dc948ee Chameleon Projector Battery, Price Decrease (#42271)
* init

* fuck using

* glowup

* unused

* meta

* whuh

* review

* tests

* Update SharedChameleonProjectorSystem.cs
2026-01-07 05:51:44 +00:00
PJBot
4ed0d37efc Automatic changelog update 2026-01-07 01:33:45 +00:00
0-Anon
de672944e0 Guarantee glue and lube in toybox (#42146)
Update fun.yml
2026-01-07 01:19:22 +00:00
Svist666s
68d4cfe53b Red alert ftl fix (#3477) 2026-01-07 05:49:42 +07:00
Princess Cheeseballs
279dabd889 Merge stable into master (#42274)
Fix admin loc spam 2 (#42265)

Co-authored-by: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com>
2026-01-06 13:51:13 -08:00
Princess Cheeseballs
5207c0694a Fix admin loc spam 2 (#42265) 2026-01-06 20:29:10 +01:00
SnappingOpossum
2176f00f19 Replace recently added StorageFill with EntityTableContainerFill (#42269)
I HATE STORAGE FILL
2026-01-06 10:56:57 +00:00
PJBot
c81e671a74 Automatic changelog update 2026-01-06 10:42:41 +00:00
ThatGuyUSA
f92ed8418b [FEATURE] More icons (#42200)
* innit bruv, but also done!

* added comment to signify why it's like that

* init, but done

* init commit

* you think you got everythin, until you didn't

* credits and one last touch up

* i fucking hate git

* KILL OLD COMMITS

This reverts commit 6f834a51de611f215ede02f291a9d834777884a5.

* KILLING OLD COMMITS

This reverts commit 36e1f4a476b873f2326723740eccf455060520d8.

* I am going to become the joker

* one day i'll learn how to delete old commits properly

* god fucking damn it

* capitalization!
2026-01-06 10:28:04 +00:00
Princess Cheeseballs
e2baaa1c31 Revert Closable Jugs (#42267)
* remove lid

* remove changelog

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-06 07:02:44 +00:00
Velcroboy
cdd990ba56 Adds sky blue curtains/tables to their respective spawners (#42266)
Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
2026-01-06 06:47:28 +00:00
PJBot
a287d5c3f7 Automatic changelog update 2026-01-06 04:40:05 +00:00
PicklOH
ae414ac94b Fix da rulez (#42264) 2026-01-06 04:25:08 +00:00
SnappingOpossum
2a71253f57 Move some miscellaneous random spawners to entity tables (#42245)
* Move some miscellaneous RandomSpawners to entity tables

* Fix a parenting issue and some conventions
2026-01-06 00:14:02 +00:00
PJBot
19b1f4787f Automatic changelog update 2026-01-05 23:32:53 +00:00
IProduceWidgets
4b7aaa3a46 jugs closeable, move chemistry entities into chemistry directory (#29413)
* jugs closeable, move chemitsry entities into chemsitry directory

* forgor to stage json. I hope i didnt stage robust :anxious:

* Who likes cargo? Not me. Lets remove it.

* Remove seal, make the amount examinable regardless of open or not, update jug icon

* apply iaada's parenting

* fix issues tdw parenting

* Review changes

* Fix sprite rename

* small touchup

---------

Co-authored-by: SlamBamActionman <slambamactionman@gmail.com>
Co-authored-by: iaada <iaada@users.noreply.github.com>
2026-01-05 23:18:23 +00:00
ScarKy0
54d7f2b736 Cleanup Toolshed Locale (#42259)
* it begins

* god help me

* FINALLY
2026-01-05 23:01:15 +00:00
PJBot
d3137c2d38 Automatic changelog update 2026-01-05 21:14:49 +00:00
ScarKy0
122feda215 Msg Toolshed Command (#41936)
* init

* subfolder

* note
2026-01-05 21:00:15 +00:00
slarticodefast
71c3fa8fd7 Predict thieving beacon (#39610)
predict thieving beacon
2026-01-05 12:17:17 +00:00
slarticodefast
e572d75f04 STABLE -> MASTER (#42251)
Fix localization missing spam from Admin Overlay (#42244)

playerInfo.StartingJob is already localized

Co-authored-by: Centronias <me@centronias.com>
2026-01-05 13:17:40 +01:00
Centronias
4d9c25098b Fix localization missing spam from Admin Overlay (#42244)
playerInfo.StartingJob is already localized
2026-01-05 03:01:00 -08:00
John
62c1302a55 fixed typo/duplicate adjective (#42249)
fixed typo/duplicate
2026-01-05 06:54:20 +00:00
PJBot
d3d35000e1 Automatic changelog update 2026-01-05 00:43:37 +00:00
TheFlyingSentry
fa7c2be164 Dragon rift no longer deletes all rifts when destroyed (#42234)
Maintaining the status quo
2026-01-05 00:28:35 +00:00
Myra
fe6a2f0708 Stable to master (#42238)
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2026-01-04 18:07:51 +01:00
Myra
3ad57230f4 Stable merge 2026-01-03 (#42237) 2026-01-04 17:50:01 +01:00
Vasilis The Pikachu
f007c75794 Changelog removal of reverted PRs 2026-01-04 17:30:59 +01:00
Vasilis The Pikachu
0ab3b1a075 Revert "feat: RnD tech research console now have reroll feature (#32931)"
This reverts commit 1f2d80297cb81e8dbbd1c1f46aeb531a2624204c.

Discussed during the maintainer meeting and voted to be reverted at this time.
2026-01-04 17:28:09 +01:00
Vasilis The Pikachu
a5977efab6 Revert "Santa anomaly back! (#41654)"
This reverts commit 21d039318e.

Christmas, no longer a week away. Unwoo

PS: Please review https://github.com/space-wizards/space-station-14/pull/38940
2026-01-04 17:25:36 +01:00
Vasilis The Pikachu
7c28daf1ed Revert "Rename LOOC chat to Help chat (#41933)"
This reverts commit 7750e3ca2e54fde0fb10d2456c41f6b121112a9e.

This will have another vulture round, it will be reapplied later after stable is merged back into master again
2026-01-04 17:20:13 +01:00
github-actions[bot]
d7219bd499 Update Credits (#42228)
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
2026-01-04 05:27:04 +01:00
Princess Cheeseballs
3ab84443f9 [STAGING] Staging Chat Fix (#42206)
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-04 01:24:18 +01:00
Princess Cheeseballs
e86cc06781 Cherrypick engine 270.1 to staging (#42226) 2026-01-03 15:56:54 -08:00
PJBot
4d19496dbd Automatic changelog update 2026-01-03 21:44:08 +00:00
Red
da4a488197 Melee weapons animations upgrade (#41425)
* upgrading

* Update MeleeWeaponSystem.Effects.cs

* Easing
2026-01-03 21:29:48 +00:00
Pieter-Jan Briers
9bd24023a7 Update RT to 270.1.0 (#42198) 2026-01-03 20:57:49 +01:00
Princess Cheeseballs
6cefe4299d [Staging] Let admins ignore the laws of physics again. (#42221)
admin ignore physics

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-03 20:39:17 +01:00
Princess Cheeseballs
bbc519c523 [STAGING] Fix Disabler SMG bolts going through walls (#42195)
* RAH RAH RASPUTIN LOVER OF THE RUSSIAN QUEEN!

* delete if we do 0 damage

* actually change that

* dont get soaped into cleaning things up challenge impossible

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-03 20:33:28 +01:00
PJBot
269bd56844 Automatic changelog update 2026-01-03 07:16:17 +00:00
Aearo-Deepwater
fc995820df Ironsands Structures (#39793)
* Add 6 new ironsand tiles

Ironsand plating and ironsand tile are based on the asteroid plating and
asteroid tile tiles. Ironsand concrete tiles are based on the concrete
and gray concrete tiles. Packed ironsand is slightly based on smooth gray
concrete, but mostly original.

* Add ironsand cobble wall

* Finish adding tile objects for ironsand concrete tiles

Includes inhands, even though the other concrete tiles don't even have
'em.

* Add cutter recipes for ironsand concrete

I'm sad that I can't require iron ore for these recipes. Iron ore can't
go in a cutter, though, and changing that seems like it'd be more
complicated than it's worth. Assuming I don't just make the thing accept
any and all ores, which... um.

* Delete temporary .swp file that shouldn't have been in the repo to begin with

* Add ironsand pavement tile

* Add ironsand stone door

* Add ironsandstone walls

* Improve packed ironsand texture

* Add ironsand step

* Add ironsand step corner

* Add three mysterious, runed pillars

* Lighten ironsand wall border

* Replace art for paved ironsand

* Make steps more closely match paved ironsand tiles

* Increase border contrast of ironstone door

* Add fake "astro-" versions of ironsand tiles

* Add second tall pillar sprite

* Add prototype for second tall statue

* Make astro-ironsand researchable and latheable

* Update corner step texture to match straight steps

* Add corner steps that go the other way

* Add door variant to match cobbled ironsand wall

* Fix license for ironsand step sprites

* Darken ironsandstone walls for better contrast

* Update ironstone doors to match walls and be more distinct

* Remove merge conflict detritus

* Fix duplicate component

* Update tile stack prototypes

* Add detail to copyright info

* Improve ironstone wall and door destruction

* Normalize ironsand statue prototypes

* Normalize ironsand tile object prototypes

* Move ironsand plating in with the rest of the plating tiles

* Remove .gitignore additions

I might just make a new PR just for this, though. I'm very tempted.

* Add sound effect to ironsand step destruction

* Add abstract base class to stairs and steps

* Remove redundant destruction trigger behavior from cardboard door

* Add destruction behavior to ironsand statues

* Fix up ironsand step prototypes

* Remove redundant bits from ironsandstone wall prototypes

* Remove ironstone door crush behavior

* Add individual ironstone statue bounding boxes

* Make ironstone statues shootable

* Tweak ironsand statue bounding boxes
2026-01-03 06:54:00 +00:00
PJBot
bdbc148054 Automatic changelog update 2026-01-03 07:02:13 +00:00
K-Dynamic
bdb710270a Intercom resprite (#41962)
* intercom resprite

* fix typo

* change copyright
2026-01-03 06:48:37 +00:00
eoineoineoin
d366c67baf Fix style classes used on monotone labels (#41969)
* Fix style classes used on monotone labels

* Heading>SubHeading
2026-01-03 06:19:35 +00:00
PJBot
856ad11640 Automatic changelog update 2026-01-03 01:34:53 +00:00
Ignaz "Ian" Kraft
9754944f11 expanded FillLevelSpriteTest test and fixed found issues (#34165)
* fix clustersoap eaten sprite

* also assure that every entity with the SolutionContainerVisualsComponent has a AppearanceComponent

* use the new sprite system + fix the fill for cardboard arrows and the mosin

* fix merge issue
2026-01-03 01:20:41 +00:00
ArtisticRoomba
c796eb372f Guard against div/0 for HeatContainerHelpers (#42213)
Guard against div/0 for HeatContainers
2026-01-02 12:22:56 +00:00
PJBot
732db1921b Automatic changelog update 2026-01-02 00:06:17 +00:00
Spessmann
4b9ef4749c Snowball fixes (#42124)
* snowball update

* snowball update
2026-01-01 17:05:09 -07:00
PJBot
e5d8800b42 Automatic changelog update 2026-01-01 21:38:07 +00:00
Samuka
e1b790eecd Make xenoborg thrusters anti-easy-sabotagge (#42201)
* code

* cant be toggled

* can't unwrench

* no rotating it

* comments

* change from PreventToggle to CanToggle

* commentary

* Update Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-01-01 21:23:34 +00:00
Pieter-Jan Briers
4f1a1118b1 Update RT to 270.1.0 (#42198) 2026-01-01 20:34:05 +00:00
Emisse
6de41e8051 Revert "Christmas-ifed Packed Station!" (#42202)
Revert "Christmas-ifed Packed Station! (#41665)"

This reverts commit b77a0d6368.
2026-01-01 13:19:52 -07:00
Emisse
74d482c5b2 Revert "Exo - Exomas Version (revertable)" (#42203)
Revert "Exo - Exomas Version (revertable) (#41715)"

This reverts commit a0e7fe8233.
2026-01-01 13:19:42 -07:00
Princess Cheeseballs
267357a4b0 Fix all currently known markup issues (#42032)
* fix 1

* fafa

* remove

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-01 21:17:43 +01:00
mq
4920c9e907 Update (MOST) instances of EntityUid, Component in GunSystem to Entity<T> (#41966)
* Entity<T>, skipping Magazine and ChamberMagazine

* missed some

* AUGH!!

* ballistic examine

* dotnet hates me

* WHY ARE YOU CALLED THAT!!!!

* cheers aada
2026-01-01 19:00:49 +00:00
CrazyPhantom779
445d1b673b Fix RCDDeconstructableComponent filename (#42180) 2026-01-01 17:31:44 +00:00
ArtisticRoomba
6e55a7bac4 Make some HeatContainerHelpers methods byref (#42197)
* make some HeatContainerHelpers methods byref

* all of them
2026-01-01 12:05:09 +00:00
PJBot
67da176eb9 Automatic changelog update 2026-01-01 05:44:04 +00:00
Princess Cheeseballs
24005e3e93 Jet Injector Tweaks and Cleanup. (#42158)
* delete metabolismmovespeedmodifiersystem

* Revert "delete metabolismmovespeedmodifiersystem"

This reverts commit 19572fa0858bfb9385f4717fc77c8956bdbc56c0.

* misc cleanup

* math shows I should do this

* prevent popups

* fix handling as well

* this too

* actually these can create popups so just always handle em...

* remove comment

* final fixes

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-01 05:29:36 +00:00
Ted Lukin
e9932ec0ea Happy 2026 (#42186) 2025-12-31 10:59:47 +00:00
PJBot
540f4e4c61 Automatic changelog update 2025-12-31 01:53:30 +00:00
Connor Huffine
1801f47418 Fix broken FTL references (#42181)
Add broken FTL links
2025-12-31 01:27:59 +00:00
PJBot
4eab48fe35 Automatic changelog update 2025-12-31 01:38:43 +00:00
YoungThug
2c5b67fc3f Ninjas now get a custom bag! (#42112)
* Ninjas now recieve a custom satchel

* Original Size

* Requested Changes + Making bag silent

* Add code comment
2025-12-31 01:23:59 +00:00
Princess Cheeseballs
97a75f49c6 Damageable Cleanup + Bugfix (#42076)
* Does not build do not PR

* using blah blah blah rider hates me

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-31 00:42:49 +00:00
PJBot
8514405ca9 Automatic changelog update 2025-12-31 00:24:27 +00:00
FotY
360e43b9e7 (Fix) Make paper extinguishable with fire extinguisher (#42142)
Make paper extinguishable with fire extinguisher
2025-12-31 00:10:59 +00:00
Crotalus
dc9d4accfd Fix warnings (#42175)
* Fix warnings

* Update Content.Server/Medical/DefibrillatorSystem.cs

* Update Content.Server/Medical/DefibrillatorSystem.cs

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2025-12-30 23:49:32 +00:00
PJBot
cdf6461796 Automatic changelog update 2025-12-30 23:27:35 +00:00
Jessica M
41234b7eb1 Move borg module remove button to the left side (#42119)
Move module remove button to the left side

Co-authored-by: Jessica M <jessica@maybe.sh>
2025-12-30 23:13:59 +00:00
Princess Cheeseballs
0b1e8a4bbc Status Effects Toolshed (#41670)
* toolshed :)

* Yeah they call me the gamer

* Fix test fails

* refactor: extract method ZeroAsNull

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
2025-12-30 21:41:57 +00:00
PJBot
a41101e8da Automatic changelog update 2025-12-30 21:53:29 +00:00
OnyxTheBrave
3cc79c223a Chemmaster Pill Source (#40121)
* Buttons and basic internal data

* The buttons DO something

* it works?!!

* I hate predictions

* 5000 monkeys on typewritters

* who let the monkeys code?

* Localizations

* waiter, more commits please

* Not going insane (this is a lie)

* last one I SWEAR

* Some improvements ported from Moff

* clean it up a little

* one more cleanup

* The chemmaster is not a mime

* Fix my mistakes + address the other review

* Point to what chemmaster is broken, and why it's broken

* ChemMasterComponent changes

* Margin for packaging source

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-30 21:34:54 +00:00
mikey
d61ecd3d50 Align detective stamp with rest of stamps (lower by 2 pixels) (#42177)
move detective stamp down 2 pixels
2025-12-30 21:05:13 +00:00
PJBot
56462d0cb1 Automatic changelog update 2025-12-29 22:50:34 +00:00
0-Anon
fb17257562 Ammonia restores Rat King Bloodlevel (#42167)
* Ammonia restores Rat King Bloodlevel

Adds a saline effect to ammonia for creatures with the rat metabolizer type condition (aka, rat kings)

* error fix

code did not like amount under condition so fixing that, and restoring amount to 1 to be identical to how it was working in the video. amount 6 was comical levels of blood restoration
2025-12-29 22:36:01 +00:00
PJBot
c6c84ef17d Automatic changelog update 2025-12-29 11:00:25 +00:00
GeneralGaws
beb4d5f584 Remove syndicate bomb restock time (#42114)
* no restock time

* add timer
2025-12-29 10:45:56 +00:00
PJBot
df7473a058 Automatic changelog update 2025-12-29 10:43:39 +00:00
PAFFhassoocks
552938cda2 puts Space ninja survival box contents into their bag (#42102)
* removes extended survival box, but adds most contents into the ninja's satchel

* major essential bugfix
2025-12-29 10:29:13 +00:00
EchoOfNothing
ac3a91eac1 Fix possible bug in my fix of IFF console. Add documentation to HideOnInit. (#42122)
* Refactor OnIFFShow and OnInitIFFConsole by extracting AddAllSupportedIFFFlags method. Fix possible addition of unallowed flags.

Fix posible addition of unallowed flags in OnInitIFFConsole by performing AllowedFlags check in the extracted function.

* Add documentation to HideOnInit

* Update IFFConsoleComponent.cs

---------

Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
2025-12-29 08:53:18 +00:00
PJBot
be7653c131 Automatic changelog update 2025-12-29 04:07:04 +00:00
EchoOfNothing
131108b018 Fix plasma station comms apc overloaded by default (#42144) 2025-12-29 03:52:44 +00:00
YoungThug
951f13fd69 Add antag control for the space ninja (#42133)
* Add antag control for the space ninja

* Remove whitespace

---------

Co-authored-by: beck-thompson <beck314159@hotmail.com>
2025-12-29 00:01:35 +00:00
Princess Cheeseballs
077dceeb2d Delete MetabolismMovespeedModifierSystem (#42134)
* delete metabolismmovespeedmodifiersystem

* Revert "delete metabolismmovespeedmodifiersystem"

This reverts commit 19572fa0858bfb9385f4717fc77c8956bdbc56c0.

* delete metabolismmovespeedmodifiersystem and component

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-28 23:22:04 +00:00
ArtisticRoomba
0dcb2756c7 Add AtmosTest test assertion for a valid grid (#42139)
add test assertion for a valid grid
2025-12-28 13:35:07 +00:00
PJBot
4cf18a222b Automatic changelog update 2025-12-28 07:31:15 +00:00
Ilya246
645c2494ec optimise shuttle collision entity throwing (#40984)
* optimise

* reconstruct

* fix

* very mild change

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-28 07:16:07 +00:00
PJBot
129c56544e Automatic changelog update 2025-12-28 06:37:57 +00:00
SnappingOpossum
45fa411485 Add crayon box to Big Bite meals (#42077)
Shuffle things, use table for big bite
2025-12-28 06:23:46 +00:00
PJBot
e8dab47f89 Automatic changelog update 2025-12-28 01:11:46 +00:00
B_Kirill
01e583f500 Fix broken vending machine UI behavior (#42110)
* Fix broken vending machine UI behavior

* begone

---------

Co-authored-by: beck-thompson <beck314159@hotmail.com>
2025-12-28 00:57:02 +00:00
github-actions[bot]
6506c7786f Update Credits (#42127)
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
2025-12-28 01:45:02 +01:00
ScarKy0
662d2ee964 ReagentGrinder Comp and API to shared (#41956)
* init

* API

* review

* return

* review

* I tend to be stupid
2025-12-27 17:09:33 +00:00
Leah
cf25961186 update communicator kit description for voice mask implanter (#42115)
update thief backpack description for voice mask implanter
2025-12-27 16:05:38 +00:00
PJBot
e1da70ebf7 Automatic changelog update 2025-12-27 14:33:14 +00:00
EchoOfNothing
ee2f1da8c2 Merge IFF controls into one control. Make syndicate IFF turned off by default. (#42104)
* Merge IFF controls into one control.

* Implement logic to hide IFF of sydicate IFF console on map load. Add hideOnInit property to IFFConsoleComponent

* DataField

---------

Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
2025-12-27 14:18:45 +00:00
B_Kirill
cd8d5a6a9c Cleanup warnings: CS0414, CS0618 (#42068)
Cleanup
2025-12-27 14:14:42 +00:00
PJBot
1b3047644a Automatic changelog update 2025-12-27 14:15:16 +00:00
SonarZeBat
589b187499 Lowered Xenoborgs MinPlayers From 40 To 30 (#42111)
Update subgamemodes.yml
2025-12-27 14:00:29 +00:00
ArtisticRoomba
8313a4e310 Atmospherics/Temperature HeatContainers (#39997)
* Initial HeatContainer logic

* comment fixes

* Comment changes + ChangeHeatCapacity

* highly intelligent specimen

* n-body full heat exchange methods

* extract to partials

* highly intelligent specimen

* fixes + ChangeHeatCapacityKeepTemperature

* Divide and merge methods

* even divide

* different merge signature

* forgot one little thing

* address review

* missing docs

* addr review

* oops

* review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2025-12-27 02:05:10 +00:00
PJBot
339b28740a Automatic changelog update 2025-12-26 21:48:41 +00:00
Nox
abeeb910fb ERT Overhaul 3/3: Loadouts (#38481)
* Initial commit: Added ERT web vest and sprites, added ERT magboots.

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added duffle bag and magboots

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Half finished updating the web vest

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Removed unused items

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added ERT chest rig, changed sprites of backpacks

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Parented all ERT hardsuit stats to nukie hardsuits

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* fixed naming

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added equipment to be used by ERT.

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added flamethrower suffix

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added hydra filled with frag grenades

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Made basic grenades centcomm restricted

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Made energy swords central command contraband

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added ERT chest rig, changed sprites of backpacks

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Parented all ERT hardsuit stats to nukie hardsuits

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* fixed naming

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added equipment to be used by ERT.

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added flamethrower suffix

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added hydra filled with frag grenades

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Made basic grenades centcomm restricted

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Made energy swords central command contraband

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* First implementation of humanoid.yml

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added leader, chaplain, and half implemented paladin

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Bit more work on chaplain

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Parented ERT backpacks to syndicate ones

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Revert "Parented ERT backpacks to syndicate ones"

This reverts commit 615526512d90ea17c0e452ed24fdb351f54740bc.

:wq#

* Parented ERT backpacks to syndicate ones

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* changed size of ERT backpacks

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Started adding engineer

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Unparented ERT duffels from syndicate ones

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Finished ERT security, Leader, and Chaplain. Issue remaining with the fills and shotgun ammo, rifleman half done.

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added all loadouts

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Updated labels and containers

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added names, finished loadouts.

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Tested loadouts, added chaplain job icon (finally), ready to go!

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added ERT survival box with double emergency tank

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* ERT now use double tanks by default

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Misc fixes

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Fixed belt

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Fixed belt sprites

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Fixed test fails

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Minor spelling mistake:

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Fixed all known issues with the PR, ready for review!

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Fixed ERT medic backpack sprite

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Fixed random metadata

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Fixed test fail

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Fixed test fail I hope?

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* defintely fixed test fails

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* removed flamethrower

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Slight loadout fixes to bring everything up to date.

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* fix

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Made C4 CC contra, reverted Incen to syni contra

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* reverted contraband

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* revert the rest of the contraband

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* revert contraband

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Finished reverting contraband

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Heisentest

* Replaced the melee ert security with a marksman

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added explosion resistance to backpacks

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added label component to spray bottles.

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Added suffixes to the space cleaner bottles

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Removed ERT duffel bag, made engi loadouts work without a duffel (still finishing medic loadouts)

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Removed ERT duffel bag.

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Finished updating loadouts to account for removed duffel bag.

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* Replaced armed engineer's C4 with explosive cord

* Replaced leader's XL8 with a standard Lecter

* Updated leader loadout

* Minor fix to ERT leader

Signed-off-by: Nox38 <nebulousnox38@gmail.com>

* fix formatting

* Fixed rifleman, armed engineer, and test fail

* Finished vanguard, chaplain.

* Fixed minor spelling mistake

* Gave ERT medics back their omnizine

* Added ERT security breacher, made ERT survival boxes 2x2.

* Finished implementing security shotgunner

* one small thing

---------

Signed-off-by: Nox38 <nebulousnox38@gmail.com>
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-26 21:33:43 +00:00
GeneralGaws
2aa29de1ee Energy guns' fire mode text formating fix (#42103)
issue
2025-12-26 16:28:02 +00:00
Princess Cheeseballs
8b33f4734f Fix Kitchen Spike Paralysis by removing an unused subscription. (#42078)
* remove

* ACK ACK ACK ACK

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-26 07:41:27 +00:00
PJBot
caebc10c5d Automatic changelog update 2025-12-26 03:01:32 +00:00
alexalexmax
4ff7411fb7 Voice mask effects are toggleable and hide your accent (#41965)
* apply negate accents system

* add toggle to voice mask ui

* roll negateaccents into voice mask system, delete negate accents comp&system, update yml entries

* convert button to ToggleButton and some cleanup

* retry for heisenfail

* accent toggle

* update names and add mask active check for accent hiding
2025-12-26 02:47:05 +00:00
PJBot
2182c7be70 Automatic changelog update 2025-12-25 23:35:14 +00:00
TheFlyingSentry
2f0d347612 Fixed Xeno air alarms warning/danger sprites not showing (#41590)
Fixed Xeno air alarms (someone didn't copy paste correctly :3)
2025-12-25 23:21:40 +00:00
Errant
8fab0ccb58 Remove reverted shuttle event change from the changelog (#42065)
revert changelog
2025-12-25 21:02:05 +00:00
lzk
6129fbe98e make comp-repairable-repair sane (#42048) 2025-12-25 20:53:14 +00:00
PJBot
3ecc3cb295 Automatic changelog update 2025-12-25 19:58:54 +00:00
Sir Warock
2d77e48b4c Add jet injectors (#40076)
* Added Jet Injectors

* Small fixes

* YML Linter fix

* Requested Adjustments

* Better Sprites for the Jet Injector

* Actually forgor to give credit

* Fix merge conflicts and refactor

* Undo Oversight

* Introduction of Advanced Jet Injectors

* minor oversight

* Adhere to requests

* Remove Loadout, add Lockers

* harder better faster stronger

* vend shortage

* Sound effect

* will this work or do I just gotta choose one at random???

* alright fine I'll do it this way

* quiet the hissing, raise the pitch

* Merge new sprites

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-25 19:44:06 +00:00
EchoOfNothing
9241325506 Update dotnet sdk from 9 to 10 for nix devl shell. (#42041)
Update dotnet sdk from 9 to 10 in shell.nix
2025-12-24 20:17:51 +00:00
TVK-04
0444987d50 Fixed Voice Mask and Ripley APU interaction (#42023)
Would show player's real identity instead of assumed identity

Co-authored-by: TVK-04 <>
2025-12-24 17:19:08 +00:00
ArtisticRoomba
cdc0c35f3f AddMolsToMixture atmos helper (#42033) 2025-12-24 00:15:10 -08:00
Princess Cheeseballs
e197b7f9ad stable to master (#42038)
[HOTFIX] Fix MMI mind transfer (#41941)

Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
2025-12-23 22:57:41 -08:00
TemporalOroboros
6f38eed9d9 Splits temperature damage processing into its own component (#30515)
* Creates TemperatureDamageThresholdsComponent

* Obsolete TemperatureComponent fields

* Use TemperatureDamageThresholdsComponent
Moves all the uses of the relocated TemperatureComponent fields to use the TDTC versions

* Removes the obsolete TemperatureComponent fields

* Update YAML definitions

* Update doc comments

* Split TemperatureSystem
Creates TemperatureDamageSystem and moves the damage handling from TemperatureSystem

* Cull unused using statements

* Use component-based damage tick scheduling

* Fix temperature damage processing
Check was inverted resulting in things never starting to take temperature damage

* Poke tests

* Add TemperatureDamageThresholds to new prototypes

* Move TemperatureDamageThresholdsComponent to Shared
Parity with TemperatureComponent

* While I'm here
Fixes warning regarding obsolete ProtoId validator attribute

* Fix YAML errors

* Fix merge errors

* Rename TemperatureDamageThresholdsComponent -> TemperatureDamageComponent

* Use ContentHelpers.RoundToLevels for temperature alerts

* Fix YML

* A fuckton of cleanup

* working cleanup

* fix

* misc additions

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
2025-12-24 06:37:11 +00:00
ScarKy0
9212f261ea [HOTFIX] Fix MMI mind transfer (#41941) 2025-12-23 22:34:10 -08:00
B_Kirill
c6a4d3f7d8 Clarify checkbox formatting in PR template (#42035) 2025-12-24 06:17:16 +00:00
Stefano Pigozzi
7ac84d1acb Fix greytide terms in Italian accent (#42020)
* Use singular form for greytider in Italian accent

* Use "curva" for "greytide" instead of "curvisti" in Italian accent
2025-12-24 05:15:12 +00:00
Stefano Pigozzi
dd22d58f2d Change "pappa" (food) to "papà" (dad) in Italian accent (#42018) 2025-12-24 03:10:25 +00:00
Pieter-Jan Briers
9511285508 Fix NanoTask and bounty print formatting (#42030)
Markup text was erroneously being appended via AddText().
2025-12-24 01:56:31 +00:00
Pieter-Jan Briers
92ee561f4b Update RT to v270.0.0 (#42029)
Fix audio loading issues
2025-12-24 01:16:20 +00:00
PJBot
ff1cba2949 Automatic changelog update 2025-12-24 00:44:26 +00:00
Jessica M
428df6a58a Add botany equipment to marathon brig (#42028)
add botany tools to marathon

Co-authored-by: Jessica M <jessica@maybe.sh>
2025-12-24 00:29:55 +00:00
ahandleman
dbda861cad Change Botany Minimum Quantity For Random Chems (#41955)
* Initial change to fix minimums

* Switch to clamp for setting min
2025-12-23 22:15:51 +00:00
Nemanja
503052bca7 Fix spreaders not re-spreading on deletion (#42016)
* Fix spreaders not re-spreading on deletion

* Rename another variable for clarity
2025-12-23 21:52:42 +00:00
Stefano Pigozzi
402cc65477 Change "mafioso" (singular) to "mafiosi" (plural) in the Italian accent. (#42026)
Change "mafioso" (singular) to "mafiosi" (plural)
2025-12-23 20:42:32 +00:00
Princess Cheeseballs
229c08c560 Fix the Infinite Spill (#42022)
i hate solutions i hate solutions

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-23 19:47:46 +00:00
B_Kirill
860f1418cd Fix incorrect table breakage sound (#41968)
* Fix incorrect table breakage sound

* review

* retry

* are you kidding me
2025-12-23 13:13:39 +00:00
PJBot
ad6644afd4 Automatic changelog update 2025-12-23 10:02:44 +00:00
imatsoup
d601ed5f4a Make donk co. microwave syndicate contraband (#41960)
add BaseSyndicateContraband parentages to donk co microwave and donk co microwave machineboard
2025-12-23 09:47:46 +00:00
ArtisticRoomba
9f84b24733 Use cached Atmospherics AirtightData when applicable (#41390) 2025-12-23 01:44:31 -08:00
ArtisticRoomba
38d6b7a119 Fix DeltaPressureTest race condition when using LINDA (#41388) 2025-12-23 01:10:36 -08:00
PJBot
c47f3ca906 Automatic changelog update 2025-12-23 08:14:01 +00:00
ArtisticRoomba
0ed5619e8b Fix atmos devices not correctly reffing the changed atmos (#41585) 2025-12-23 00:12:52 -08:00
Kowlin
3a3707d2a2 Fix Setgamepreset (#41963) 2025-12-23 05:59:36 +00:00
imatsoup
5363a9f2fa Update debug backpacks to use the proper suffix (#41959)
Update backpacks.yml
2025-12-23 04:30:22 +00:00
PJBot
cabf9d5124 Automatic changelog update 2025-12-22 21:16:46 +00:00
Crude Oil
760463a67a Port FTL arrival effect fix from https://github.com/new-frontiers-14/frontier-station-14/pull/3495 (#41951)
Port FTL arrival fix from https://github.com/new-frontiers-14/frontier-station-14/pull/3495
2025-12-22 21:01:56 +00:00
PJBot
28fd00b7ea Automatic changelog update 2025-12-22 17:12:41 +00:00
JohnJohn
8b8f621b8c Allow cable coils to be destroyed (#41279)
Add damageable and destructible types to cable coils
2025-12-22 16:57:51 +00:00
ArtisticRoomba
dde01f746f Basic Dynamic Power Consumption Systems (#41885)
* init commit

* Addr reviews
2025-12-22 16:43:02 +00:00
PJBot
347a728ab7 Automatic changelog update 2025-12-22 16:55:39 +00:00
eoineoineoin
b436e2a937 Fix missing scrollbars in Admin Player List window (#40525)
* Fix missing scrollbars in Admin Player List window

* Revert "Fix missing scrollbars in Admin Player List window"

This reverts commit c5aea1a0550deb1d1d7aae4e2dec964e93d2d8ae.

* Invalidate cached item height when item generation callback changes
2025-12-22 16:41:02 +00:00
PJBot
853570662e Automatic changelog update 2025-12-22 08:13:45 +00:00
Milon
f59ef4b986 fix solution contents duplication on spill behavior (#33231)
* I’M SCREAMING INTO THE VOID AND IT’S NOT LISTENING

* review

* explodes pancakes with mind

* graaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

* Meteors RAAAAAAAAAAH

* I'm so tired of solutions

* whhop

* revert

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-22 07:59:30 +00:00
Princess Cheeseballs
eb41d5010b Physics Assert in SharedMoverController (#37970)
* Physics asserts and Xenoarch fixes

* Fix blocking asserts

* Alright ready for the test fails

* Fix whitespace issues

* Fix whitespace

* Okay fix whitespace issues for real

* Fix test fails

* Temp fix

* Fix

* Whitespace

* Added a big ass comment

* Right

* A

* Should work

* Debug performance

* Mothership

* fix test fails real

* push

* fix

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-22 02:42:39 +00:00
Pieter-Jan Briers
787330f5c6 v269.0.0 RT update - .NET 10 (#41855)
* Make ServerPackaging automatically get extra server assemblies

* Make the switch

* Use Content.Server.deps.json instead

* Remove debug

* Rewrite

Now recursively fetches dependencies from Content.Server

Only copies dependencies not covered by Robust

This removes the need to manually specify most of the dependencies, even the content ones!

Also look at runtime key properly to figure out the proper dll name.

This actually removes some assemblies that were duplicated between the main directory and assemblies (various Microsoft.Extensions stuff)

* Fix test compile errors when updating dependencies

Ran across this while updating dependencies on the RT .NET 10 update. Should be fine to merge immediately.

* More .NET 10 prep

* Convert to SLNX

Hell yeah

* slnx now has size-2 indents

* Update SLNX with new RT system

* Remove reference to RT test in toolshed test

* Remove accidental usage of transitive RT dependencies

* Move Robust project references to RobustApi

* Update solution file

* Fix warnings in pow3r

* Fix nullable warnings in integration tests

idk where these came from

* gitignore binlog files

* Fix transitive dependency warnings in Content.Benchmarks

* Update slnx

* Okay, the Robust API thing didn't pan out. New plan.

It apparently broke clean builds, as the dependencies aren't in the project asset list or something anymore. I tried to fix this, but it seems impossible to do without relying on .NET SDK internals, as there's no point in the NuGet graph walk process that seems cleanly extensible.

Instead let's just do the much dumber thing: a bunch of .props files for content to import. Hooray!

This also means that I have to go through and *explicitly* disable transitive dependencies everywhere in RT. This thankfully isn't too hard.

* Update RT to 269.0.0

* One last solution update

* Fix more data definition issues

* Update RT to 269.0.1

* Fix it again

---------

Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
2025-12-22 01:24:24 +00:00
PJBot
fab0fe14cc Automatic changelog update 2025-12-22 00:47:34 +00:00
SlamBamActionman
7750e3ca2e Rename LOOC chat to Help chat (#41933)
Initial commit
2025-12-22 00:34:06 +00:00
PJBot
0a6ad5dcff Automatic changelog update 2025-12-21 20:34:17 +00:00
Sir Warock
f3f91e3f6b Miscellaneous Injector fixes + BorgHypo fill sprites. (#41932)
* Various fixes

* Fix Gorlex Hypo not showing visuals

* Give Borg Hypo Fill sprites

* Bluespace Syringe speed increase

* fix

* one whitespace change

* Undo debug change

* Replaced String Message with better

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-21 20:20:43 +00:00
Myra
eefecdcf2f Stable release for 2025-12-20 (#41934) (#41935) 2025-12-21 16:19:04 +01:00
Samuka
56bff9aee9 Fix the mothership again (again) (#41924)
* fix the mothership again

* renamed that lever
2025-12-21 08:11:11 +00:00
github-actions[bot]
2cb8e9b7fe Update Credits (#41931)
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
2025-12-21 01:47:22 +01:00
PJBot
85060d96cf Automatic changelog update 2025-12-21 00:12:04 +00:00
Sir Warock
6932f28191 Merge Injector & Hypospray Systems & Components (#41833)
* Merge Injector & Hyposprays

* Fixes

* Requested Changes

* Preview

* Inclusion of Prototypes

* Fix

* small oversight

* Further fixes

* A few more fixes & Bluespacesyringe buff

Co-Authored-By: āda <177162775+iaada@users.noreply.github.com>

* Final Commit, hopefully

* Merge conflict no more

* YML fix

* Add required changes

Co-Authored-By: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com>

* cleanup warnings removal

* Bug fix & Maintainer Requests

Co-Authored-By: āda <177162775+iaada@users.noreply.github.com>

* Adhere to requested changes

Co-Authored-By: āda <177162775+iaada@users.noreply.github.com>

---------

Co-authored-by: āda <177162775+iaada@users.noreply.github.com>
Co-authored-by: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com>
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-20 23:58:26 +00:00
ArtisticRoomba
517b37698d Staging -> Master (#41929)
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
2025-12-20 13:45:52 -08:00
Perry Fraser
79f58a0314 Don't process paused MoverControllers (#39444)
* refactor: make MoverController use more queries

* perf: don't process paused MoverControllers

* perf: track active input movers via events

* Revert "place stored changeling identities next to each other (#39452)"

This reverts commit 9b5d2ff11b.

* perf: keep around the seen movers hashset

* fix: don't reintroduce wild wild west ordering

* style: use virtual method

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* docs: better ActiveInputMoverComponent motiviation

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* fix: pass through known comp

* fix: properly order relay movers for real

* perf: use proxy Transform() and inline it

Actually this might be a slight performance improvement since it avoids

the dictionary lookup until the case that its body status is on ground.

* style: switch an event handler to Entity<T>

* fix: just-in-case track for relay loops

* merg conflix

* borger

* whitespace moment

* whoops

* empty

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
2025-12-20 19:24:04 +00:00
PJBot
c179445ec9 Automatic changelog update 2025-12-20 18:38:29 +00:00
korczoczek
77036e8cdd Added sprites for openable ingredients (#41923)
* dont you hate it when you open your bag, but it stays closed?

* linting my yml
2025-12-20 18:21:49 +00:00
PJBot
000c2e9b5d Automatic changelog update 2025-12-20 18:24:50 +00:00
ScarKy0
e2ef727096 Log Station AI radial actions (#41911)
* fuck you and your anonymous zombie shocking

* ToUglyString

* because
2025-12-20 18:11:07 +00:00
PJBot
dcd083a25b Automatic changelog update 2025-12-20 14:35:47 +00:00
chromiumboy
a9bb4921a2 Station AI ghost role (#40607)
* Initial commit

* API

* review

---------

Co-authored-by: ScarKy0 <scarky0@onet.eu>
2025-12-20 14:21:54 +00:00
PJBot
2c5b023dc1 Automatic changelog update 2025-12-20 08:33:24 +00:00
Spessmann
092f0f8b4a Snowball update (#41908)
snowball update
2025-12-20 08:20:14 +00:00
PJBot
386115a575 Automatic changelog update 2025-12-20 01:51:25 +00:00
Sir Warock
e552736422 Shield QoL + buff (#41326)
* Add Damage Examine to Shields

* Make Repairs repeat when not fully repaired

* Make some Shields repairable

* Please the Grammar Gods
2025-12-20 01:37:11 +00:00
slarticodefast
3266c94eac Unify BatteryComponent and PredictedBatteryComponent (#41867)
* unify

* cleanup and merge conflicts

* floating points

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-19 18:18:12 +00:00
PJBot
c97ffb006e Automatic changelog update 2025-12-19 07:05:17 +00:00
Hitlinemoss
444991fbd0 Cleanup of circuit tote / stamp box prototypes + added small cardboard boxes as a general item (#41335)
* Cleanup of circuit tote / stamp box prototypes

* New BoxCardboardSmall abstract prototype, stamp box uses this as a parent now

* Removed stamp box whitelist

* Whitelist fix

* We love scope creep! Small cardboard boxes are a general thing now.

* Box description updates

* Box description updates 2!

* Prototype order cleanup

* Comment updates

* Desc touchup

* Review updates

* StorageFill -> ContainerFill

* code  comment update

* decapitalized labels

* Code comment update

* Whoops! Forgot to update one ContainerFill to EntityTableContainerFill when fixing merge conflict

* Whoops! EntityTableContainerFill items were all formatted wrong

* Departmental box names

* Misc fixes

* Whoops, forgot to remove some labels
2025-12-19 06:51:27 +00:00
PJBot
d88bc489ae Automatic changelog update 2025-12-18 21:23:53 +00:00
Fildrance
1f2d80297c feat: RnD tech research console now have reroll feature (#32931)
* feat: RnD tech research console now have reroll feature

* fix: disable Rediscover button when there is not enough currency or user have no access

* refactor: xml-doc, extract method, minor simplify xaml

* minor cleanup after review

* refactor: change sending research server points amount into BUI from state to  ResearchServerComponent (using AfterAutoHandleStateEvent)

* feat: now tech rerolls will have cooldown to ensure no one can spam-spend all dept budget instantly

* refactor: revert unneeded code

* refactor: whitespaces

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
2025-12-18 21:06:24 +00:00
PJBot
fcf8207219 Automatic changelog update 2025-12-18 21:09:59 +00:00
BruhIsaac
2b356f64bd Rebalance the Ghost Role Raffles (#33157)
* Up raffle time/no-add time

* upd

---------

Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
2025-12-18 20:39:41 +00:00
PJBot
24887dc7d5 Automatic changelog update 2025-12-18 20:56:23 +00:00
Unkn0wn_Gh0st
a21983d5aa Syndicate Wall Lockers and Secure Storage (#33251)
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
2025-12-18 20:37:31 +00:00
PJBot
a095c61ba4 Automatic changelog update 2025-12-18 20:38:33 +00:00
Minemoder5000
ccc70aef07 Re-work Arrivals Shuttle to have un-interactable substation and APC (#41884)
* power fixes

* no interactions

* fix

---------

Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
2025-12-18 20:25:16 +00:00
1548 changed files with 31219 additions and 20244 deletions

View File

@@ -351,7 +351,7 @@ resharper_csharp_qualified_using_at_nested_scope = false
resharper_csharp_prefer_qualified_reference = false
resharper_csharp_allow_alias = false
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}]
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props,slnx}]
indent_size = 2
[nuget.config]

View File

@@ -14,7 +14,7 @@
Небольшие исправления/рефакторинг освобождаются от этого требования. -->
## Требования
<!-- Подтвердите следующее, поставив X в скобках [X]: -->
<!-- Подтвердите следующее, поставив X в скобках без пробелов [X]: -->
- [ ] Я прочитал(а) и следую [Рекомендациям по оформлению Pull Request и Changelog](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html).
- [ ] Я добавил(а) медиафайлы к этому PR или он не требует демонстрации в игре.
<!-- Вы должны понимать, что несоблюдение вышеуказанного может привести к закрытию вашего PR по усмотрению сопровождающего -->

View File

@@ -21,7 +21,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore

View File

@@ -36,7 +36,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore

View File

@@ -36,7 +36,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore

View File

@@ -20,7 +20,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Get Engine Tag
run: |

View File

@@ -47,7 +47,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Get Engine Tag
run: |

View File

@@ -65,7 +65,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore

View File

@@ -6,13 +6,10 @@ on:
branches: [ master, jsondump ]
paths:
- '.github/workflows/update-wiki.yml'
- 'Content.Shared/Chemistry/**.cs'
- 'Content.Server/Chemistry/**.cs'
- 'Content.Server/GuideGenerator/**.cs'
- 'Content.Server/Corvax/GuideGenerator/**.cs'
- 'Resources/Prototypes/Reagents/**.yml'
- 'Resources/Prototypes/Chemistry/**.yml'
- 'Resources/Prototypes/Recipes/Reactions/**.yml'
- 'Content.Shared/**'
- 'Content.Server/**'
- 'Content.Client/**'
- 'Resources/**'
- 'RobustToolbox/'
jobs:
@@ -51,42 +48,50 @@ jobs:
run: dotnet ./bin/Content.Server/Content.Server.dll --cvar autogen.destination_file=prototypes.json
continue-on-error: true
- name: Upload chem_prototypes.json to wiki
uses: jtmullen/mediawiki-edit-action@v0.1.1
with:
wiki_text_file: ./bin/Content.Server/data/chem_prototypes.json
edit_summary: Update chem_prototypes.json via GitHub Actions
page_name: "${{ secrets.WIKI_PAGE_ROOT }}/chem_prototypes.json"
api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
username: ${{ secrets.WIKI_BOT_USER }}
password: ${{ secrets.WIKI_BOT_PASS }}
# Проходит по всем JSON-файлам в директории BASE и загружает каждый файл как страницу в MediaWiki.
# Имя страницы формируется из относительного пути к файлу.
- name: Upload JSON files to wiki
shell: bash
run: |
set -euo pipefail
- name: Upload react_prototypes.json to wiki
uses: jtmullen/mediawiki-edit-action@v0.1.1
with:
wiki_text_file: ./bin/Content.Server/data/react_prototypes.json
edit_summary: Update react_prototypes.json via GitHub Actions
page_name: "${{ secrets.WIKI_PAGE_ROOT }}/react_prototypes.json"
api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
username: ${{ secrets.WIKI_BOT_USER }}
password: ${{ secrets.WIKI_BOT_PASS }}
BASE="./bin/Content.Server/data"
ROOT="${{ secrets.WIKI_PAGE_ROOT }}"
API="${{ secrets.WIKI_ROOT_URL }}/api.php"
USER="${{ secrets.WIKI_BOT_USER }}"
PASS="${{ secrets.WIKI_BOT_PASS }}"
- name: Upload entity_prototypes.json to wiki
uses: jtmullen/mediawiki-edit-action@v0.1.1
with:
wiki_text_file: ./bin/Content.Server/data/entity_prototypes.json
edit_summary: Update entity_prototypes.json via GitHub Actions
page_name: "${{ secrets.WIKI_PAGE_ROOT }}/entity_prototypes.json"
api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
username: ${{ secrets.WIKI_BOT_USER }}
password: ${{ secrets.WIKI_BOT_PASS }}
API="$(printf "%s" "$API" | tr -d '\r\n' | sed 's/[[:space:]]*$//')"
USER="$(printf "%s" "$USER" | tr -d '\r\n')"
PASS="$(printf "%s" "$PASS" | tr -d '\r\n')"
ROOT="$(printf "%s" "$ROOT" | tr -d '\r\n' | sed 's/[[:space:]]*$//')"
- name: Upload mealrecipes_prototypes.json to wiki
uses: jtmullen/mediawiki-edit-action@v0.1.1
with:
wiki_text_file: ./bin/Content.Server/data/mealrecipes_prototypes.json
edit_summary: Update mealrecipes_prototypes.json via GitHub Actions
page_name: "${{ secrets.WIKI_PAGE_ROOT }}/mealrecipes_prototypes.json"
api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
username: ${{ secrets.WIKI_BOT_USER }}
password: ${{ secrets.WIKI_BOT_PASS }}
cookiejar="$(mktemp)"
trap 'rm -f "$cookiejar"' EXIT
login_token=$(curl -sS -c "$cookiejar" --data "action=query&meta=tokens&type=login&format=json" "$API" | jq -r '.query.tokens.logintoken')
curl -sS -c "$cookiejar" -b "$cookiejar" \
--data-urlencode "action=login" \
--data-urlencode "lgname=$USER" \
--data-urlencode "lgpassword=$PASS" \
--data-urlencode "lgtoken=$login_token" \
--data-urlencode "format=json" \
"$API" > /dev/null
find "$BASE" -type f -name '*.json' | while IFS= read -r file; do
rel="${file#$BASE/}"
rel="$(printf "%s" "$rel" | tr -d '\r\n' | sed 's/:/_/g')"
page="$ROOT/$rel"
echo "Uploading $rel → $page"
token=$(curl -sS -b "$cookiejar" --data "action=query&meta=tokens&format=json" "$API" | jq -r '.query.tokens.csrftoken')
curl -sS -b "$cookiejar" \
--data-urlencode "action=edit" \
--data-urlencode "title=$page" \
--data-urlencode "summary=Update $rel via GitHub Actions" \
--data-urlencode "text@${file}" \
--data-urlencode "token=$token" \
--data-urlencode "format=json" \
"$API" | jq -r '.'
done

View File

@@ -26,7 +26,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore
- name: Build

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
# MSbuild binlog files
*.binlog
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

View File

@@ -11,7 +11,7 @@ import time
from pathlib import Path
from typing import List
SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
SOLUTION_PATH = Path("..") / "SpaceStation14.slnx"
# If this doesn't match the saved version we overwrite them all.
CURRENT_HOOKS_VERSION = "4"
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"

View File

@@ -1,17 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<OutputPath>..\bin\Content.Benchmarks\</OutputPath>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>12</LangVersion>
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
<Nullable>disable</Nullable>
</PropertyGroup>
<Import Project="../MSBuild/Content.props" />
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
<!-- pin transitive deps -->
<PackageReference Include="System.Management" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Content.Client\Content.Client.csproj" />
@@ -19,10 +22,12 @@
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
<ProjectReference Include="..\Content.Tests\Content.Tests.csproj" />
<ProjectReference Include="..\Content.IntegrationTests\Content.IntegrationTests.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Benchmarks\Robust.Benchmarks.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
<Import Project="..\RobustToolbox\Imports\Client.props" />
<Import Project="..\RobustToolbox\Imports\Server.props" />
<Import Project="..\RobustToolbox\Imports\Shared.props" />
<Import Project="..\RobustToolbox\Imports\Benchmarks.props" />
<Import Project="..\RobustToolbox\Imports\Testing.props" />
</Project>

View File

@@ -0,0 +1,83 @@
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
namespace Content.Benchmarks;
[Virtual]
[GcServer(true)]
[MemoryDiagnoser]
public class HeatCapacityBenchmark
{
private TestPair _pair = default!;
private IEntityManager _sEntMan = default!;
private IEntityManager _cEntMan = default!;
private Client.Atmos.EntitySystems.AtmosphereSystem _cAtmos = default!;
private AtmosphereSystem _sAtmos = default!;
private GasMixture _mix;
[GlobalSetup]
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient();
await _pair.Connect();
_cEntMan = _pair.Client.ResolveDependency<IEntityManager>();
_sEntMan = _pair.Server.ResolveDependency<IEntityManager>();
_cAtmos = _cEntMan.System<Client.Atmos.EntitySystems.AtmosphereSystem>();
_sAtmos = _sEntMan.System<AtmosphereSystem>();
const float volume = 2500f;
const float temperature = 293.15f;
const float o2 = 12.3f;
const float n2 = 45.6f;
const float co2 = 0.42f;
const float plasma = 0.05f;
_mix = new GasMixture(volume) { Temperature = temperature };
_mix.AdjustMoles(Gas.Oxygen, o2);
_mix.AdjustMoles(Gas.Nitrogen, n2);
_mix.AdjustMoles(Gas.CarbonDioxide, co2);
_mix.AdjustMoles(Gas.Plasma, plasma);
}
[Benchmark]
public async Task ClientHeatCapacityBenchmark()
{
await _pair.Client.WaitPost(delegate
{
for (var i = 0; i < 10000; i++)
{
_cAtmos.GetHeatCapacity(_mix, applyScaling: true);
}
});
}
[Benchmark]
public async Task ServerHeatCapacityBenchmark()
{
await _pair.Server.WaitPost(delegate
{
for (var i = 0; i < 10000; i++)
{
_sAtmos.GetHeatCapacity(_mix, applyScaling: true);
}
});
}
[GlobalCleanup]
public async Task CleanupAsync()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
}

View File

@@ -134,7 +134,7 @@ internal sealed class AdminNameOverlay : Overlay
? null
: _prototypeManager.Index(playerInfo.RoleProto.Value);
var roleName = Loc.GetString(rolePrototype?.Name ?? RoleTypePrototype.FallbackName);
var roleName = rolePrototype?.Name ?? RoleTypePrototype.FallbackName;
var roleColor = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
var roleSymbol = rolePrototype?.Symbol ?? RoleTypePrototype.FallbackSymbol;
@@ -213,7 +213,7 @@ internal sealed class AdminNameOverlay : Overlay
{
color = Color.GreenYellow;
color.A = alpha;
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, Loc.GetString(playerInfo.StartingJob), uiScale, playerInfo.Connected ? color : colorDisconnected);
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.StartingJob, uiScale, playerInfo.Connected ? color : colorDisconnected);
currentOffset += lineoffset;
}
@@ -241,7 +241,7 @@ internal sealed class AdminNameOverlay : Overlay
color = roleColor;
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
text = IsFiltered(playerInfo.RoleProto)
? roleName.ToUpper()
? Loc.GetString(roleName).ToUpper()
: string.Empty;
break;
case AdminOverlayAntagFormat.Subtype:

View File

@@ -0,0 +1,35 @@
using System.Runtime.CompilerServices;
using Content.Shared.Atmos;
namespace Content.Client.Atmos.EntitySystems;
public sealed partial class AtmosphereSystem
{
/*
Partial class for operations involving GasMixtures.
Any method that is overridden here is usually because the server-sided implementation contains
code that would escape sandbox. As such these methods are overridden here with a safe
implementation.
*/
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override float GetHeatCapacityCalculation(float[] moles, bool space)
{
// Little hack to make space gas mixtures have heat capacity, therefore allowing them to cool down rooms.
if (space && MathHelper.CloseTo(NumericsHelpers.HorizontalAdd(moles), 0f))
{
return Atmospherics.SpaceHeatCapacity;
}
// explicit stackalloc call is banned on client tragically.
// the JIT does not stackalloc this during runtime,
// though this isnt the hottest code path so it should be fine
// the gc can eat a little as a treat
var tmp = new float[moles.Length];
NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp);
// Adjust heat capacity by speedup, because this is primarily what
// determines how quickly gases heat up/cool.
return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity);
}
}

View File

@@ -5,7 +5,7 @@ using Robust.Shared.GameStates;
namespace Content.Client.Atmos.EntitySystems;
public sealed class AtmosphereSystem : SharedAtmosphereSystem
public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
{
public override void Initialize()
{

View File

@@ -1,53 +0,0 @@
using Content.Client.BarSign.Ui;
using Content.Shared.BarSign;
using Content.Shared.Power;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client.BarSign;
public sealed class BarSignSystem : VisualizerSystem<BarSignComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BarSignComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
}
private void OnAfterAutoHandleState(EntityUid uid, BarSignComponent component, ref AfterAutoHandleStateEvent args)
{
if (_ui.TryGetOpenUi<BarSignBoundUserInterface>(uid, BarSignUiKey.Key, out var bui))
bui.Update(component.Current);
UpdateAppearance(uid, component);
}
protected override void OnAppearanceChange(EntityUid uid, BarSignComponent component, ref AppearanceChangeEvent args)
{
UpdateAppearance(uid, component, args.Component, args.Sprite);
}
private void UpdateAppearance(EntityUid id, BarSignComponent sign, AppearanceComponent? appearance = null, SpriteComponent? sprite = null)
{
if (!Resolve(id, ref appearance, ref sprite))
return;
AppearanceSystem.TryGetData<bool>(id, PowerDeviceVisuals.Powered, out var powered, appearance);
if (powered
&& sign.Current != null
&& _prototypeManager.Resolve(sign.Current, out var proto))
{
SpriteSystem.LayerSetSprite((id, sprite), 0, proto.Icon);
sprite.LayerSetShader(0, "unshaded");
}
else
{
SpriteSystem.LayerSetRsiState((id, sprite), 0, "empty");
sprite.LayerSetShader(0, null, null);
}
}
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.BarSign;
using Content.Shared.Power;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client.BarSign;
public sealed class BarSignVisualizerSystem : VisualizerSystem<BarSignComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
protected override void OnAppearanceChange(EntityUid uid, BarSignComponent component, ref AppearanceChangeEvent args)
{
AppearanceSystem.TryGetData<bool>(uid, PowerDeviceVisuals.Powered, out var powered, args.Component);
AppearanceSystem.TryGetData<string>(uid, BarSignVisuals.BarSignPrototype, out var currentSign, args.Component);
if (powered
&& currentSign != null
&& _prototypeManager.Resolve<BarSignPrototype>(currentSign, out var proto))
{
SpriteSystem.LayerSetSprite((uid, args.Sprite), 0, proto.Icon);
args.Sprite?.LayerSetShader(0, "unshaded");
}
else
{
SpriteSystem.LayerSetRsiState((uid, args.Sprite), 0, "empty");
args.Sprite?.LayerSetShader(0, null, null);
}
}
}

View File

@@ -19,32 +19,27 @@ public sealed class BarSignBoundUserInterface(EntityUid owner, Enum uiKey) : Bou
var sign = EntMan.GetComponentOrNull<BarSignComponent>(Owner)?.Current is { } current
? _prototype.Index(current)
: null;
var allSigns = Shared.BarSign.BarSignSystem.GetAllBarSigns(_prototype)
var allSigns = BarSignSystem.GetAllBarSigns(_prototype)
.OrderBy(p => Loc.GetString(p.Name))
.ToList();
_menu = new(sign, allSigns);
_menu.OnSignSelected += id =>
{
SendMessage(new SetBarSignMessage(id));
SendPredictedMessage(new SetBarSignMessage(id));
};
_menu.OnClose += Close;
_menu.OpenCentered();
}
public void Update(ProtoId<BarSignPrototype>? sign)
public override void Update()
{
if (_prototype.Resolve(sign, out var signPrototype))
_menu?.UpdateState(signPrototype);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
if (!EntMan.TryGetComponent<BarSignComponent>(Owner, out var signComp))
return;
_menu?.Dispose();
if (_prototype.Resolve(signComp.Current, out var signPrototype))
_menu?.UpdateState(signPrototype);
}
}

View File

@@ -7,7 +7,6 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Serilog;
namespace Content.Client.Cargo.UI;

View File

@@ -1,16 +0,0 @@
using Content.Client.Chemistry.UI;
using Content.Client.Items;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
namespace Content.Client.Chemistry.EntitySystems;
public sealed class HyposprayStatusControlSystem : EntitySystem
{
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainers = default!;
public override void Initialize()
{
base.Initialize();
Subs.ItemStatus<HyposprayComponent>(ent => new HyposprayStatusControl(ent, _solutionContainers));
}
}

View File

@@ -0,0 +1,20 @@
using Content.Client.Chemistry.UI;
using Content.Client.Items;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.Prototypes;
namespace Content.Client.Chemistry.EntitySystems;
public sealed class InjectorStatusControlSystem : EntitySystem
{
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainers = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override void Initialize()
{
base.Initialize();
Subs.ItemStatus<InjectorComponent>(injector => new InjectorStatusControl(injector, _solutionContainers, _prototypeManager));
}
}

View File

@@ -1,16 +0,0 @@
using Content.Client.Chemistry.UI;
using Content.Client.Items;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
namespace Content.Client.Chemistry.EntitySystems;
public sealed class InjectorSystem : SharedInjectorSystem
{
public override void Initialize()
{
base.Initialize();
Subs.ItemStatus<InjectorComponent>(ent => new InjectorStatusControl(ent, SolutionContainer));
}
}

View File

@@ -48,6 +48,10 @@ namespace Content.Client.Chemistry.UI
(uint) _window.BottleDosage.Value, _window.LabelLine));
_window.BufferSortButton.OnPressed += _ => SendMessage(
new ChemMasterSortingTypeCycleMessage());
_window.OutputBufferDraw.OnPressed += _ => SendMessage(
new ChemMasterOutputDrawSourceMessage(ChemMasterDrawSource.Internal));
_window.OutputBeakerDraw.OnPressed += _ => SendMessage(
new ChemMasterOutputDrawSourceMessage(ChemMasterDrawSource.External));
for (uint i = 0; i < _window.PillTypeButtons.Length; i++)
{

View File

@@ -79,10 +79,13 @@
<!-- Packaging -->
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'chem-master-window-packaging-text'}" />
<Label Text="{Loc 'chem-master-output-source'}" StyleClasses="LabelSecondaryColor" Margin="0 0 5 0"/>
<Button MinSize="80 0" Name="OutputBufferDraw" Access="Public" Text="{Loc 'chem-master-output-buffer-draw'}" ToggleMode="True" StyleClasses="OpenRight" />
<Button MinSize="80 0" Name="OutputBeakerDraw" Access="Public" Text="{Loc 'chem-master-output-beaker-draw'}" ToggleMode="True" StyleClasses="OpenLeft" />
<Control HorizontalExpand="True"/>
<Label Text="{Loc 'chem-master-window-buffer-label'}" />
<Label Name="BufferCurrentVolume" StyleClasses="LabelWeak" />
<!-- Output Draw Source -->
<Label Name="DrawSource"/>
<Label Name="BufferCurrentVolume" StyleClasses="LabelSecondaryColor" />
</BoxContainer>
<!-- Wrap the packaging info-->

View File

@@ -150,7 +150,17 @@ namespace Content.Client.Chemistry.UI
// Ensure the Panel Info is updated, including UI elements for Buffer Volume, Output Container and so on
UpdatePanelInfo(castState);
BufferCurrentVolume.Text = $" {castState.BufferCurrentVolume?.Int() ?? 0}u";
switch (castState.DrawSource)
{
case ChemMasterDrawSource.Internal:
SetBufferText(castState.BufferCurrentVolume, "chem-master-output-buffer-draw");
break;
case ChemMasterDrawSource.External:
SetBufferText(castState.InputContainerInfo?.CurrentVolume, "chem-master-output-beaker-draw");
break;
default:
throw new($"Chemmaster {castState.OutputContainerInfo} draw source is not set");
}
InputEjectButton.Disabled = castState.InputContainerInfo is null;
OutputEjectButton.Disabled = castState.OutputContainerInfo is null;
@@ -168,9 +178,14 @@ namespace Content.Client.Chemistry.UI
var holdsReagents = output?.Reagents != null;
var pillNumberMax = holdsReagents ? 0 : remainingCapacity;
var bottleAmountMax = holdsReagents ? remainingCapacity : 0;
var bufferVolume = castState.BufferCurrentVolume?.Int() ?? 0;
var outputVolume = castState.DrawSource switch
{
ChemMasterDrawSource.Internal => castState.BufferCurrentVolume?.Int() ?? 0,
ChemMasterDrawSource.External => castState.InputContainerInfo?.CurrentVolume.Int() ?? 0,
_ => 0,
};
PillDosage.Value = (int)Math.Min(bufferVolume, castState.PillDosageLimit);
PillDosage.Value = (int)Math.Min(outputVolume, castState.PillDosageLimit);
PillTypeButtons[castState.SelectedPillType].Pressed = true;
@@ -186,25 +201,35 @@ namespace Content.Client.Chemistry.UI
// Avoid division by zero
if (PillDosage.Value > 0)
{
PillNumber.Value = Math.Min(bufferVolume / PillDosage.Value, pillNumberMax);
PillNumber.Value = Math.Min(outputVolume / PillDosage.Value, pillNumberMax);
}
else
{
PillNumber.Value = 0;
}
BottleDosage.Value = Math.Min(bottleAmountMax, bufferVolume);
BottleDosage.Value = Math.Min(bottleAmountMax, outputVolume);
}
/// <summary>
/// Generate a product label based on reagents in the buffer.
/// Generate a product label based on reagents in the buffer or beaker.
/// </summary>
/// <param name="state">State data sent by the server.</param>
private string GenerateLabel(ChemMasterBoundUserInterfaceState state)
{
if (state.BufferCurrentVolume == 0)
if (
state.BufferCurrentVolume == 0 && state.DrawSource == ChemMasterDrawSource.Internal ||
state.InputContainerInfo?.CurrentVolume == 0 && state.DrawSource == ChemMasterDrawSource.External ||
state.InputContainerInfo?.Reagents == null
)
return "";
var reagent = state.BufferReagents.OrderBy(r => r.Quantity).First().Reagent;
var reagent = (state.DrawSource switch
{
ChemMasterDrawSource.Internal => state.BufferReagents,
ChemMasterDrawSource.External => state.InputContainerInfo.Reagents ?? [],
_ => throw new($"Chemmaster {state.OutputContainerInfo} draw source is not set"),
}).MinBy(r => r.Quantity)
.Reagent;
_prototypeManager.TryIndex(reagent.Prototype, out ReagentPrototype? proto);
return proto?.LocalizedName ?? "";
}
@@ -233,6 +258,8 @@ namespace Content.Client.Chemistry.UI
_ => Loc.GetString("chem-master-window-sort-type-none")
};
OutputBufferDraw.Pressed = state.DrawSource == ChemMasterDrawSource.Internal;
OutputBeakerDraw.Pressed = state.DrawSource == ChemMasterDrawSource.External;
if (!state.BufferReagents.Any())
{
@@ -414,6 +441,12 @@ namespace Content.Client.Chemistry.UI
get => LabelLineEdit.Text;
set => LabelLineEdit.Text = value;
}
private void SetBufferText(FixedPoint2? volume, string text)
{
BufferCurrentVolume.Text = $" {volume ?? FixedPoint2.Zero}u";
DrawSource.Text = Loc.GetString(text);
}
}
public sealed class ReagentButton : Button

View File

@@ -1,58 +0,0 @@
using Content.Client.Message;
using Content.Client.Stylesheets;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.FixedPoint;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
namespace Content.Client.Chemistry.UI;
public sealed class HyposprayStatusControl : Control
{
private readonly Entity<HyposprayComponent> _parent;
private readonly RichTextLabel _label;
private readonly SharedSolutionContainerSystem _solutionContainers;
private FixedPoint2 PrevVolume;
private FixedPoint2 PrevMaxVolume;
private bool PrevOnlyAffectsMobs;
public HyposprayStatusControl(Entity<HyposprayComponent> parent, SharedSolutionContainerSystem solutionContainers)
{
_parent = parent;
_solutionContainers = solutionContainers;
_label = new RichTextLabel { StyleClasses = { StyleClass.ItemStatus } };
AddChild(_label);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution))
return;
// only updates the UI if any of the details are different than they previously were
if (PrevVolume == solution.Volume
&& PrevMaxVolume == solution.MaxVolume
&& PrevOnlyAffectsMobs == _parent.Comp.OnlyAffectsMobs)
return;
PrevVolume = solution.Volume;
PrevMaxVolume = solution.MaxVolume;
PrevOnlyAffectsMobs = _parent.Comp.OnlyAffectsMobs;
var modeStringLocalized = Loc.GetString((_parent.Comp.OnlyAffectsMobs && _parent.Comp.CanContainerDraw) switch
{
false => "hypospray-all-mode-text",
true => "hypospray-mobs-only-mode-text",
});
_label.SetMarkup(Loc.GetString("hypospray-volume-label",
("currentVolume", solution.Volume),
("totalVolume", solution.MaxVolume),
("modeString", modeStringLocalized)));
}
}

View File

@@ -2,26 +2,32 @@ using Content.Client.Message;
using Content.Client.Stylesheets;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Chemistry.UI;
public sealed class InjectorStatusControl : Control
{
private readonly IPrototypeManager _prototypeManager;
private readonly Entity<InjectorComponent> _parent;
private readonly SharedSolutionContainerSystem _solutionContainers;
private readonly RichTextLabel _label;
private FixedPoint2 PrevVolume;
private FixedPoint2 PrevMaxVolume;
private FixedPoint2 PrevTransferAmount;
private InjectorToggleMode PrevToggleState;
private FixedPoint2 _prevVolume;
private FixedPoint2 _prevMaxVolume;
private FixedPoint2? _prevTransferAmount;
private InjectorBehavior _prevBehavior;
public InjectorStatusControl(Entity<InjectorComponent> parent, SharedSolutionContainerSystem solutionContainers)
public InjectorStatusControl(Entity<InjectorComponent> parent, SharedSolutionContainerSystem solutionContainers, IPrototypeManager prototypeManager)
{
_prototypeManager = prototypeManager;
_parent = parent;
_solutionContainers = solutionContainers;
_label = new RichTextLabel { StyleClasses = { StyleClass.ItemStatus } };
@@ -32,33 +38,38 @@ public sealed class InjectorStatusControl : Control
{
base.FrameUpdate(args);
if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution))
if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution)
|| !_prototypeManager.Resolve(_parent.Comp.ActiveModeProtoId, out var activeMode))
return;
// only updates the UI if any of the details are different than they previously were
if (PrevVolume == solution.Volume
&& PrevMaxVolume == solution.MaxVolume
&& PrevTransferAmount == _parent.Comp.CurrentTransferAmount
&& PrevToggleState == _parent.Comp.ToggleState)
if (_prevVolume == solution.Volume
&& _prevMaxVolume == solution.MaxVolume
&& _prevTransferAmount == _parent.Comp.CurrentTransferAmount
&& _prevBehavior == activeMode.Behavior)
return;
PrevVolume = solution.Volume;
PrevMaxVolume = solution.MaxVolume;
PrevTransferAmount = _parent.Comp.CurrentTransferAmount;
PrevToggleState = _parent.Comp.ToggleState;
_prevVolume = solution.Volume;
_prevMaxVolume = solution.MaxVolume;
_prevTransferAmount = _parent.Comp.CurrentTransferAmount;
_prevBehavior = activeMode.Behavior;
// Update current volume and injector state
var modeStringLocalized = Loc.GetString(_parent.Comp.ToggleState switch
// Seeing transfer volume is only important for injectors that can change it.
if (activeMode.TransferAmounts.Count > 1 && _parent.Comp.CurrentTransferAmount.HasValue)
{
InjectorToggleMode.Draw => "injector-draw-text",
InjectorToggleMode.Inject => "injector-inject-text",
_ => "injector-invalid-injector-toggle-mode"
});
_label.SetMarkup(Loc.GetString("injector-volume-label",
("currentVolume", solution.Volume),
("totalVolume", solution.MaxVolume),
("modeString", modeStringLocalized),
("transferVolume", _parent.Comp.CurrentTransferAmount)));
_label.SetMarkup(Loc.GetString("injector-volume-transfer-label",
("currentVolume", solution.Volume),
("totalVolume", solution.MaxVolume),
("modeString", Loc.GetString(activeMode.Name)),
("transferVolume", _parent.Comp.CurrentTransferAmount.Value)));
}
else
{
_label.SetMarkup(Loc.GetString("injector-volume-label",
("currentVolume", solution.Volume),
("totalVolume", solution.MaxVolume),
("modeString", Loc.GetString(activeMode.Name))));
}
}
}

View File

@@ -2,41 +2,31 @@ using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
namespace Content.Client.Chemistry.UI
namespace Content.Client.Chemistry.UI;
[UsedImplicitly]
public sealed class TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[UsedImplicitly]
public sealed class TransferAmountBoundUserInterface : BoundUserInterface
[ViewVariables]
private TransferAmountWindow? _window;
protected override void Open()
{
private IEntityManager _entManager;
private EntityUid _owner;
[ViewVariables]
private TransferAmountWindow? _window;
base.Open();
_window = this.CreateWindow<TransferAmountWindow>();
public TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
if (EntMan.TryGetComponent<SolutionTransferComponent>(Owner, out var comp))
_window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
_window.ApplyButton.OnPressed += _ =>
{
_owner = owner;
_entManager = IoCManager.Resolve<IEntityManager>();
}
protected override void Open()
{
base.Open();
_window = this.CreateWindow<TransferAmountWindow>();
if (_entManager.TryGetComponent<SolutionTransferComponent>(_owner, out var comp))
_window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
_window.ApplyButton.OnPressed += _ =>
if (int.TryParse(_window.AmountLineEdit.Text, out var i))
{
if (int.TryParse(_window.AmountLineEdit.Text, out var i))
{
SendMessage(new TransferAmountSetValueMessage(FixedPoint2.New(i)));
_window.Close();
}
};
}
SendPredictedMessage(new TransferAmountSetValueMessage(FixedPoint2.New(i)));
_window.Close();
}
};
}
}

View File

@@ -3,34 +3,33 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Chemistry.UI
namespace Content.Client.Chemistry.UI;
[GenerateTypedNameReferences]
public sealed partial class TransferAmountWindow : DefaultWindow
{
[GenerateTypedNameReferences]
public sealed partial class TransferAmountWindow : DefaultWindow
private int _max = Int32.MaxValue;
private int _min = 1;
public TransferAmountWindow()
{
private int _max = Int32.MaxValue;
private int _min = 1;
RobustXamlLoader.Load(this);
AmountLineEdit.OnTextChanged += OnValueChanged;
}
public TransferAmountWindow()
{
RobustXamlLoader.Load(this);
AmountLineEdit.OnTextChanged += OnValueChanged;
}
public void SetBounds(int min, int max)
{
_min = min;
_max = max;
MinimumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-min", ("amount", _min));
MaximumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-max", ("amount", _max));
}
public void SetBounds(int min, int max)
{
_min = min;
_max = max;
MinimumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-min", ("amount", _min));
MaximumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-max", ("amount", _max));
}
private void OnValueChanged(LineEdit.LineEditEventArgs args)
{
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount > _max || amount < _min)
ApplyButton.Disabled = true;
else
ApplyButton.Disabled = false;
}
private void OnValueChanged(LineEdit.LineEditEventArgs args)
{
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount > _max || amount < _min)
ApplyButton.Disabled = true;
else
ApplyButton.Disabled = false;
}
}

View File

@@ -4,10 +4,8 @@ using Content.Shared.Weapons.Ranged.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Serialization;
using Robust.Client.UserInterface;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Utility;
namespace Content.Client.CombatMode;

View File

@@ -30,7 +30,10 @@ namespace Content.Client.Construction.UI
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private readonly SpriteSystem _spriteSystem;
private readonly ISawmill _sawmill;
private readonly IConstructionMenuView _constructionView;
private readonly EntityWhitelistSystem _whitelistSystem;
@@ -90,6 +93,7 @@ namespace Content.Client.Construction.UI
_constructionView = new ConstructionMenu();
_whitelistSystem = _entManager.System<EntityWhitelistSystem>();
_spriteSystem = _entManager.System<SpriteSystem>();
_sawmill = _logManager.GetSawmill("construction.ui");
// This is required so that if we load after the system is initialized, we can bind to it immediately
if (_systemManager.TryGetEntitySystem<ConstructionSystem>(out var constructionSystem))
@@ -284,7 +288,7 @@ namespace Content.Client.Construction.UI
if (!_constructionSystem!.TryGetRecipePrototype(recipe.ID, out var targetProtoId))
{
Logger.Error("Cannot find the target prototype in the recipe cache with the id \"{0}\" of {1}.",
_sawmill.Error("Cannot find the target prototype in the recipe cache with the id \"{0}\" of {1}.",
recipe.ID,
nameof(ConstructionPrototype));
continue;

View File

@@ -1,13 +1,11 @@
using System.Linq;
using Content.Client.Materials;
using Content.Client.Materials.UI;
using Content.Client.Message;
using Content.Client.UserInterface.Controls;
using Content.Shared.Construction.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Materials;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -61,57 +59,48 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
!_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
return;
var flatpackerEnt = (_owner, flatpacker);
if (flatpacker.Packing)
{
PackButton.Disabled = true;
}
else if (_currentBoard != null)
{
Dictionary<string, int> cost;
if (_entityManager.TryGetComponent<MachineBoardComponent>(_currentBoard, out var machineBoardComp))
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
else
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
PackButton.Disabled = !_materialStorage.CanChangeMaterialAmount(_owner, cost);
PackButton.Disabled = !_flatpack.TryGetFlatpackCreationCost(flatpackerEnt, _currentBoard.Value, out var curCost)
|| !_materialStorage.CanChangeMaterialAmount(_owner, curCost);
}
if (_currentBoard == itemSlot.Item)
return;
_currentBoard = itemSlot.Item;
CostHeaderLabel.Visible = _currentBoard != null;
CostHeaderLabel.Visible = false;
InsertLabel.Visible = _currentBoard == null;
if (_currentBoard is not null)
if (_currentBoard is null)
{
string? prototype = null;
Dictionary<string, int>? cost = null;
MachineSprite.SetPrototype(NoBoardEffectId);
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
MachineNameLabel.SetMessage(string.Empty);
PackButton.Disabled = true;
return;
}
if (_entityManager.TryGetComponent<MachineBoardComponent>(_currentBoard, out var newMachineBoardComp))
{
prototype = newMachineBoardComp.Prototype;
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, newMachineBoardComp));
}
else if (_entityManager.TryGetComponent<ComputerBoardComponent>(_currentBoard, out var computerBoard))
{
prototype = computerBoard.Prototype;
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
}
if (prototype is not null && cost is not null)
{
var proto = _prototypeManager.Index<EntityPrototype>(prototype);
MachineSprite.SetPrototype(prototype);
MachineNameLabel.SetMessage(proto.Name);
CostLabel.SetMarkup(GetCostString(cost));
}
if (_flatpack.TryGetFlatpackResultPrototype(_currentBoard.Value, out var prototype) &&
_flatpack.TryGetFlatpackCreationCost(flatpackerEnt, _currentBoard.Value, out var cost))
{
var proto = _prototypeManager.Index<EntityPrototype>(prototype);
MachineSprite.SetPrototype(prototype);
MachineNameLabel.SetMessage(proto.Name);
CostLabel.SetMarkup(GetCostString(cost));
CostHeaderLabel.Visible = true;
}
else
{
MachineSprite.SetPrototype(NoBoardEffectId);
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
MachineNameLabel.SetMessage(" ");
CostLabel.SetMarkup(Loc.GetString("flatpacker-ui-board-invalid-label"));
MachineNameLabel.SetMessage(string.Empty);
PackButton.Disabled = true;
}
}

View File

@@ -1,26 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<LangVersion>12</LangVersion>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>..\bin\Content.Client\</OutputPath>
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
<WarningsAsErrors>RA0032;nullable</WarningsAsErrors>
<Nullable>enable</Nullable>
<Configurations>Debug;Release;Tools;DebugOpt</Configurations>
<Platforms>AnyCPU</Platforms>
</PropertyGroup>
<Import Project="../MSBuild/Content.props" />
<ItemGroup>
<PackageReference Include="Nett" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
<PackageReference Include="SixLabors.ImageSharp" />
<PackageReference Include="Pidgin" />
<PackageReference Include="Robust.Shared.AuthLib" />
</ItemGroup>
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
<Import Project="..\RobustToolbox\Imports\Client.props" />
<Import Project="..\RobustToolbox\Imports\Shared.props" />
<ItemGroup>
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
<ProjectReference Include="..\Corvax\Content.Corvax.Interfaces.Shared\Content.Corvax.Interfaces.Shared.csproj" />
<ProjectReference Include="..\Corvax\Content.Corvax.Interfaces.Client\Content.Corvax.Interfaces.Client.csproj" />

View File

@@ -0,0 +1 @@
<RichTextLabel xmlns="https://spacestation14.io"></RichTextLabel>

View File

@@ -0,0 +1,56 @@
using System.Diagnostics.CodeAnalysis;
using Content.Client.Guidebook;
using Content.Client.Guidebook.Richtext;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Corvax.Guidebook.Controls;
/// <summary>
/// Control for embedding text with fluent support into guidebook/document
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class FTLTextpart : RichTextLabel, IDocumentTag
{
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
[Dependency] private readonly DocumentParsingManager _documentParsingManager = default!;
private readonly ISawmill _sawmill;
public FTLTextpart()
{
RobustXamlLoader.Load(this);
_sawmill = _logManager.GetSawmill("guidebook.loc");
MouseFilter = MouseFilterMode.Stop;
}
public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
{
if (!args.TryGetValue("Key", out var key))
{
_sawmill.Error("Fluent tag cannot be found");
control = null;
return false;
}
if (_loc.TryGetString(key, out var fluentString))
{
var doc = new Document();
if (_documentParsingManager.TryAddMarkup(doc, fluentString))
{
control = doc;
return true;
}
control = null;
return false;
}
_sawmill.Error($"Fluent key {key} cannot be found");
control = null;
return false;
}
}

View File

@@ -0,0 +1,44 @@
using System.Diagnostics.CodeAnalysis;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.RichText;
using Robust.Shared.Utility;
namespace Content.Client.Corvax.Markup;
public sealed class TooltipTag : IMarkupTagHandler
{
[Dependency] private readonly ILocalizationManager _loc = default!;
public string Name => "tooltip";
public bool TryCreateControl(MarkupNode node, [NotNullWhen(true)] out Control? control)
{
if (!node.Value.TryGetString(out var tooltipKey) || string.IsNullOrWhiteSpace(tooltipKey))
{
control = null;
return false;
}
if (!_loc.TryGetString(tooltipKey, out var tooltipText))
tooltipText = tooltipKey;
var visibleText = tooltipText;
if (node.Attributes.TryGetValue("text", out var textParam) && textParam.TryGetString(out var explicitText)
&& !string.IsNullOrEmpty(explicitText))
{
visibleText = explicitText;
}
var label = new Label
{
Text = visibleText,
MouseFilter = Control.MouseFilterMode.Stop,
ToolTip = tooltipText,
FontColorOverride = Color.LightYellow
};
control = label;
return true;
}
}

View File

@@ -87,9 +87,14 @@ public sealed class TTSSystem : EntitySystem
if (ev.SourceUid != null)
{
if (!TryGetEntity(ev.SourceUid.Value, out _))
return;
var sourceUid = GetEntity(ev.SourceUid.Value);
if (!Exists(sourceUid) || Deleted(sourceUid))
{
_contentRoot.RemoveFile(filePath);
return;
}
_audio.PlayEntity(audioResource.AudioStream, sourceUid, soundSpecifier, audioParams);
}
else

View File

@@ -73,7 +73,19 @@ public sealed class PuddleSystem : SharedPuddleSystem
// Maybe someday we'll have clientside prediction for entity spawning, but not today.
// Until then, these methods do nothing on the client.
/// <inheritdoc/>
public override bool TrySplashSpillAt(EntityUid uid, EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true, EntityUid? user = null)
public override bool TrySplashSpillAt(Entity<SpillableComponent?> entity, EntityCoordinates coordinates, out EntityUid puddleUid, out Solution solution, bool sound = true, EntityUid? user = null)
{
puddleUid = EntityUid.Invalid;
solution = new Solution();
return false;
}
public override bool TrySplashSpillAt(EntityUid entity,
EntityCoordinates coordinates,
Solution spilled,
out EntityUid puddleUid,
bool sound = true,
EntityUid? user = null)
{
puddleUid = EntityUid.Invalid;
return false;

View File

@@ -42,7 +42,7 @@ public sealed class GuidebookRichPrototypeLink : Control, IPrototypeLinkControl
public void SetMessage(FormattedMessage message)
{
_message = message;
_richTextLabel.SetMessage(_message);
_richTextLabel.SetMessage(_message, tagsAllowed: null);
}
public IPrototype? LinkedPrototype { get; set; }

View File

@@ -39,6 +39,13 @@ public sealed partial class DocumentParsingManager
var whitespaceAndCommentParser = SkipWhitespaces.Then(Try(String("<!--").Then(Parser<char>.Any.SkipUntil(Try(String("-->"))))).SkipMany());
_controlParser = OneOf(_tagParser, TryHeaderControl, ListControlParser, TextControlParser)
.Select(control =>
{
if (!_lastControlWasList)
_numberedListCounter = 0;
_lastControlWasList = false;
return control;
}) // Corvax-Guidebook
.Before(whitespaceAndCommentParser);
foreach (var typ in _reflectionManager.GetAllChildren<IDocumentTag>())
@@ -70,6 +77,8 @@ public sealed partial class DocumentParsingManager
{
try
{
_numberedListCounter = 0; // Corvax-Guidebook
_lastControlWasList = false; // Corvax-Guidebook
foreach (var child in ControlParser.ParseOrThrow(text))
{
control.AddChild(child);

View File

@@ -48,13 +48,16 @@ public sealed partial class DocumentParsingManager
private static readonly Parser<char, Unit> TryStartList =
Try(SkipNewline.Then(SkipWhitespaces).Then(Char('-'))).Then(SkipWhitespaces);
private static readonly Parser<char, Unit> TryStartNumber =
Try(SkipNewline.Then(SkipWhitespaces).Then(Token(char.IsDigit).AtLeastOnceString().Before(Char('.')))).Then(SkipWhitespaces); // Corvax-Guidebook
private static readonly Parser<char, Unit> TryStartTag = Try(Char('<')).Then(SkipWhitespaces);
private static readonly Parser<char, Unit> TryStartParagraph =
Try(SkipNewline.Then(SkipNewline)).Then(SkipWhitespaces);
private static readonly Parser<char, Unit> TryLookTextEnd =
Lookahead(OneOf(TryStartTag, TryStartList, TryStartParagraph, Try(Whitespace.SkipUntil(End))));
Lookahead(OneOf(TryStartTag, TryStartList, TryStartNumber, TryStartParagraph, Try(Whitespace.SkipUntil(End)))); // Corvax-Guidebook: TryStartNumber
private static readonly Parser<char, string> TextParser =
TextChar.AtLeastOnceUntil(TryLookTextEnd).Select(string.Concat);
@@ -82,7 +85,7 @@ public sealed partial class DocumentParsingManager
}
msg.Pop();
rt.SetMessage(msg);
rt.SetMessage(msg, tagsAllowed: null);
return rt;
},
TextParser)
@@ -121,17 +124,46 @@ public sealed partial class DocumentParsingManager
private static readonly Parser<char, Control> TryHeaderControl = OneOf(TertiaryHeaderControlParser, SubHeaderControlParser, HeaderControlParser);
private static readonly Parser<char, Control> ListControlParser = Try(Char('-'))
// Corvax-Guidebook-Start
private static readonly Parser<char, Control> BulletListControlParser = Try(Char('-'))
.Then(SkipWhitespaces)
.Then(Map(
control => new BoxContainer
control =>
{
Children = { new Label { Text = ListBullet, VerticalAlignment = VAlignment.Top }, control },
Orientation = LayoutOrientation.Horizontal
_lastControlWasList = true;
return new BoxContainer
{
Children = { new Label { Text = ListBullet, VerticalAlignment = VAlignment.Top }, control },
Orientation = LayoutOrientation.Horizontal
};
},
TextControlParser)
.Cast<Control>())
.Labelled("list");
.Labelled("bulletlist");
private static int _numberedListCounter;
private static bool _lastControlWasList;
// Parser for numbered lists like "1. text". The displayed number is auto-incremented across consecutive items.
private static readonly Parser<char, Control> NumberedListControlParser = Try(Token(char.IsDigit).AtLeastOnceString().Before(Char('.')))
.Then(SkipWhitespaces)
.Then(Map(control =>
{
_numberedListCounter++;
_lastControlWasList = true;
var label = new Label { Text = $" {_numberedListCounter}. ", VerticalAlignment = VAlignment.Top };
return new BoxContainer
{
Children = { label, control },
Orientation = LayoutOrientation.Horizontal
};
},
TextControlParser)
.Cast<Control>())
.Labelled("numberedlist");
private static readonly Parser<char, Control> ListControlParser = OneOf(NumberedListControlParser, BulletListControlParser);
// Corvax-Guidebook-End
#region Text Parsing

View File

@@ -0,0 +1,53 @@
<BoxContainer
xmlns="https://spacestation14.io"
VerticalExpand="True"
Orientation="Vertical">
<Label
Name="NoPatientDataText"
Text="{Loc health-analyzer-window-no-patient-data-text}" />
<BoxContainer
Name="PatientDataContainer"
Margin="0 0 0 5"
Orientation="Vertical">
<BoxContainer Orientation="Horizontal" Margin="0 0 0 5">
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64" />
<TextureRect Name="NoDataTex" Access="Public" SetSize="64 64" Visible="false" Stretch="KeepAspectCentered" TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/>
<BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top">
<RichTextLabel Name="NameLabel" SetWidth="150" />
<Label Name="SpeciesLabel" VerticalAlignment="Top" StyleClasses="LabelSubText" />
</BoxContainer>
<Label Margin="0 0 5 0" HorizontalExpand="True" HorizontalAlignment="Right" VerticalExpand="True"
VerticalAlignment="Top" Name="ScanModeLabel"
Text="{Loc 'health-analyzer-window-entity-unknown-text'}" />
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<GridContainer Margin="0 5 0 0" Columns="2">
<Label Text="{Loc 'health-analyzer-window-entity-status-text'}" />
<Label Name="StatusLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}" />
<Label Name="TemperatureLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}" />
<Label Name="BloodLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}" />
<Label Name="DamageLabel" />
</GridContainer>
</BoxContainer>
<PanelContainer Name="AlertsDivider" Visible="False" StyleClasses="LowDivider" />
<BoxContainer Name="AlertsContainer" Visible="False" Margin="0 5" Orientation="Vertical" HorizontalAlignment="Center">
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer
Name="GroupsContainer"
Margin="0 5 0 5"
Orientation="Vertical">
</BoxContainer>
</BoxContainer>

View File

@@ -0,0 +1,241 @@
using System.Linq;
using System.Numerics;
using Content.Shared.Atmos;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.IdentityManagement;
using Content.Shared.MedicalScanner;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.HealthAnalyzer.UI;
// Health analyzer UI is split from its window because it's used by both the
// health analyzer item and the cryo pod UI.
[GenerateTypedNameReferences]
public sealed partial class HealthAnalyzerControl : BoxContainer
{
private readonly IEntityManager _entityManager;
private readonly SpriteSystem _spriteSystem;
private readonly IPrototypeManager _prototypes;
private readonly IResourceCache _cache;
public HealthAnalyzerControl()
{
RobustXamlLoader.Load(this);
var dependencies = IoCManager.Instance!;
_entityManager = dependencies.Resolve<IEntityManager>();
_spriteSystem = _entityManager.System<SpriteSystem>();
_prototypes = dependencies.Resolve<IPrototypeManager>();
_cache = dependencies.Resolve<IResourceCache>();
}
public void Populate(HealthAnalyzerUiState state)
{
var target = _entityManager.GetEntity(state.TargetEntity);
if (target == null
|| !_entityManager.TryGetComponent<DamageableComponent>(target, out var damageable))
{
NoPatientDataText.Visible = true;
return;
}
NoPatientDataText.Visible = false;
// Scan Mode
ScanModeLabel.Text = state.ScanMode.HasValue
? state.ScanMode.Value
? Loc.GetString("health-analyzer-window-scan-mode-active")
: Loc.GetString("health-analyzer-window-scan-mode-inactive")
: Loc.GetString("health-analyzer-window-entity-unknown-text");
ScanModeLabel.FontColorOverride = state.ScanMode.HasValue && state.ScanMode.Value ? Color.Green : Color.Red;
// Patient Information
SpriteView.SetEntity(target.Value);
SpriteView.Visible = state.ScanMode.HasValue && state.ScanMode.Value;
NoDataTex.Visible = !SpriteView.Visible;
var name = new FormattedMessage();
name.PushColor(Color.White);
name.AddText(_entityManager.HasComponent<MetaDataComponent>(target.Value)
? Identity.Name(target.Value, _entityManager)
: Loc.GetString("health-analyzer-window-entity-unknown-text"));
NameLabel.SetMessage(name);
SpeciesLabel.Text =
_entityManager.TryGetComponent<HumanoidAppearanceComponent>(target.Value,
out var humanoidAppearanceComponent)
? Loc.GetString(_prototypes.Index<SpeciesPrototype>(humanoidAppearanceComponent.Species).Name)
: Loc.GetString("health-analyzer-window-entity-unknown-species-text");
// Basic Diagnostic
TemperatureLabel.Text = !float.IsNaN(state.Temperature)
? $"{state.Temperature - Atmospherics.T0C:F1} °C ({state.Temperature:F1} K)"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
BloodLabel.Text = !float.IsNaN(state.BloodLevel)
? $"{state.BloodLevel * 100:F1} %"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
StatusLabel.Text =
_entityManager.TryGetComponent<MobStateComponent>(target.Value, out var mobStateComponent)
? GetStatus(mobStateComponent.CurrentState)
: Loc.GetString("health-analyzer-window-entity-unknown-text");
// Total Damage
DamageLabel.Text = damageable.TotalDamage.ToString();
// Alerts
var showAlerts = state.Unrevivable == true || state.Bleeding == true;
AlertsDivider.Visible = showAlerts;
AlertsContainer.Visible = showAlerts;
if (showAlerts)
AlertsContainer.RemoveAllChildren();
if (state.Unrevivable == true)
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-unrevivable-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
if (state.Bleeding == true)
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
// Damage Groups
var damageSortedGroups =
damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
.ToDictionary(x => x.Key, x => x.Value);
IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
}
private static string GetStatus(MobState mobState)
{
return mobState switch
{
MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
_ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
};
}
private void DrawDiagnosticGroups(
Dictionary<string, FixedPoint2> groups,
IReadOnlyDictionary<string, FixedPoint2> damageDict)
{
GroupsContainer.RemoveAllChildren();
foreach (var (damageGroupId, damageAmount) in groups)
{
if (damageAmount == 0)
continue;
var groupTitleText = $"{Loc.GetString(
"health-analyzer-window-damage-group-text",
("damageGroup", _prototypes.Index<DamageGroupPrototype>(damageGroupId).LocalizedName),
("amount", damageAmount)
)}";
var groupContainer = new BoxContainer
{
Align = AlignMode.Begin,
Orientation = LayoutOrientation.Vertical,
};
groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId));
GroupsContainer.AddChild(groupContainer);
// Show the damage for each type in that group.
var group = _prototypes.Index<DamageGroupPrototype>(damageGroupId);
foreach (var type in group.DamageTypes)
{
if (!damageDict.TryGetValue(type, out var typeAmount) || typeAmount <= 0)
continue;
var damageString = Loc.GetString(
"health-analyzer-window-damage-type-text",
("damageType", _prototypes.Index<DamageTypePrototype>(type).LocalizedName),
("amount", typeAmount)
);
groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
}
}
}
private Texture GetTexture(string texture)
{
var rsiPath = new ResPath("/Textures/Objects/Devices/health_analyzer.rsi");
var rsiSprite = new SpriteSpecifier.Rsi(rsiPath, texture);
var rsi = _cache.GetResource<RSIResource>(rsiSprite.RsiPath).RSI;
if (!rsi.TryGetState(rsiSprite.RsiState, out var state))
{
rsiSprite = new SpriteSpecifier.Rsi(rsiPath, "unknown");
}
return _spriteSystem.Frame0(rsiSprite);
}
private static Label CreateDiagnosticItemLabel(string text)
{
return new Label
{
Text = text,
};
}
private BoxContainer CreateDiagnosticGroupTitle(string text, string id)
{
var rootContainer = new BoxContainer
{
Margin = new Thickness(0, 6, 0, 0),
VerticalAlignment = VAlignment.Bottom,
Orientation = LayoutOrientation.Horizontal,
};
rootContainer.AddChild(new TextureRect
{
SetSize = new Vector2(30, 30),
Texture = GetTexture(id.ToLower())
});
rootContainer.AddChild(CreateDiagnosticItemLabel(text));
return rootContainer;
}
}

View File

@@ -1,64 +1,15 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:ui="clr-namespace:Content.Client.HealthAnalyzer.UI"
MaxHeight="525"
MinWidth="300">
<ScrollContainer
Margin="5 5 5 5"
ReturnMeasure="True"
VerticalExpand="True">
<BoxContainer
Name="RootContainer"
VerticalExpand="True"
Orientation="Vertical">
<Label
Name="NoPatientDataText"
Text="{Loc health-analyzer-window-no-patient-data-text}" />
<BoxContainer
Name="PatientDataContainer"
Margin="0 0 0 5"
Orientation="Vertical">
<BoxContainer Orientation="Horizontal" Margin="0 0 0 5">
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64" />
<TextureRect Name="NoDataTex" Access="Public" SetSize="64 64" Visible="false" Stretch="KeepAspectCentered" TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/>
<BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top">
<RichTextLabel Name="NameLabel" SetWidth="150" />
<Label Name="SpeciesLabel" VerticalAlignment="Top" StyleClasses="LabelSubText" />
</BoxContainer>
<Label Margin="0 0 5 0" HorizontalExpand="True" HorizontalAlignment="Right" VerticalExpand="True"
VerticalAlignment="Top" Name="ScanModeLabel"
Text="{Loc 'health-analyzer-window-entity-unknown-text'}" />
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<GridContainer Margin="0 5 0 0" Columns="2">
<Label Text="{Loc 'health-analyzer-window-entity-status-text'}" />
<Label Name="StatusLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}" />
<Label Name="TemperatureLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}" />
<Label Name="BloodLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}" />
<Label Name="DamageLabel" />
</GridContainer>
</BoxContainer>
<PanelContainer Name="AlertsDivider" Visible="False" StyleClasses="LowDivider" />
<BoxContainer Name="AlertsContainer" Visible="False" Margin="0 5" Orientation="Vertical" HorizontalAlignment="Center">
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer
Name="GroupsContainer"
Margin="0 5 0 5"
Orientation="Vertical">
</BoxContainer>
</BoxContainer>
<ui:HealthAnalyzerControl
Name="HealthAnalyzer"/>
</ScrollContainer>
</controls:FancyWindow>

View File

@@ -1,241 +1,20 @@
using System.Linq;
using System.Numerics;
using Content.Shared.Atmos;
using Content.Client.UserInterface.Controls;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.IdentityManagement;
using Content.Shared.MedicalScanner;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.ResourceManagement;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.HealthAnalyzer.UI
namespace Content.Client.HealthAnalyzer.UI;
[GenerateTypedNameReferences]
public sealed partial class HealthAnalyzerWindow : FancyWindow
{
[GenerateTypedNameReferences]
public sealed partial class HealthAnalyzerWindow : FancyWindow
public HealthAnalyzerWindow()
{
private readonly IEntityManager _entityManager;
private readonly SpriteSystem _spriteSystem;
private readonly IPrototypeManager _prototypes;
private readonly IResourceCache _cache;
RobustXamlLoader.Load(this);
}
public HealthAnalyzerWindow()
{
RobustXamlLoader.Load(this);
var dependencies = IoCManager.Instance!;
_entityManager = dependencies.Resolve<IEntityManager>();
_spriteSystem = _entityManager.System<SpriteSystem>();
_prototypes = dependencies.Resolve<IPrototypeManager>();
_cache = dependencies.Resolve<IResourceCache>();
}
public void Populate(HealthAnalyzerScannedUserMessage msg)
{
var target = _entityManager.GetEntity(msg.TargetEntity);
if (target == null
|| !_entityManager.TryGetComponent<DamageableComponent>(target, out var damageable))
{
NoPatientDataText.Visible = true;
return;
}
NoPatientDataText.Visible = false;
// Scan Mode
ScanModeLabel.Text = msg.ScanMode.HasValue
? msg.ScanMode.Value
? Loc.GetString("health-analyzer-window-scan-mode-active")
: Loc.GetString("health-analyzer-window-scan-mode-inactive")
: Loc.GetString("health-analyzer-window-entity-unknown-text");
ScanModeLabel.FontColorOverride = msg.ScanMode.HasValue && msg.ScanMode.Value ? Color.Green : Color.Red;
// Patient Information
SpriteView.SetEntity(target.Value);
SpriteView.Visible = msg.ScanMode.HasValue && msg.ScanMode.Value;
NoDataTex.Visible = !SpriteView.Visible;
var name = new FormattedMessage();
name.PushColor(Color.White);
name.AddText(_entityManager.HasComponent<MetaDataComponent>(target.Value)
? Identity.Name(target.Value, _entityManager)
: Loc.GetString("health-analyzer-window-entity-unknown-text"));
NameLabel.SetMessage(name);
SpeciesLabel.Text =
_entityManager.TryGetComponent<HumanoidAppearanceComponent>(target.Value,
out var humanoidAppearanceComponent)
? Loc.GetString(_prototypes.Index<SpeciesPrototype>(humanoidAppearanceComponent.Species).Name)
: Loc.GetString("health-analyzer-window-entity-unknown-species-text");
// Basic Diagnostic
TemperatureLabel.Text = !float.IsNaN(msg.Temperature)
? $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
BloodLabel.Text = !float.IsNaN(msg.BloodLevel)
? $"{msg.BloodLevel * 100:F1} %"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
StatusLabel.Text =
_entityManager.TryGetComponent<MobStateComponent>(target.Value, out var mobStateComponent)
? GetStatus(mobStateComponent.CurrentState)
: Loc.GetString("health-analyzer-window-entity-unknown-text");
// Total Damage
DamageLabel.Text = damageable.TotalDamage.ToString();
// Alerts
var showAlerts = msg.Unrevivable == true || msg.Bleeding == true;
AlertsDivider.Visible = showAlerts;
AlertsContainer.Visible = showAlerts;
if (showAlerts)
AlertsContainer.RemoveAllChildren();
if (msg.Unrevivable == true)
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-unrevivable-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
if (msg.Bleeding == true)
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
// Damage Groups
var damageSortedGroups =
damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
.ToDictionary(x => x.Key, x => x.Value);
IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
}
private static string GetStatus(MobState mobState)
{
return mobState switch
{
MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
_ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
};
}
private void DrawDiagnosticGroups(
Dictionary<string, FixedPoint2> groups,
IReadOnlyDictionary<string, FixedPoint2> damageDict)
{
GroupsContainer.RemoveAllChildren();
foreach (var (damageGroupId, damageAmount) in groups)
{
if (damageAmount == 0)
continue;
var groupTitleText = $"{Loc.GetString(
"health-analyzer-window-damage-group-text",
("damageGroup", _prototypes.Index<DamageGroupPrototype>(damageGroupId).LocalizedName),
("amount", damageAmount)
)}";
var groupContainer = new BoxContainer
{
Align = BoxContainer.AlignMode.Begin,
Orientation = BoxContainer.LayoutOrientation.Vertical,
};
groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId));
GroupsContainer.AddChild(groupContainer);
// Show the damage for each type in that group.
var group = _prototypes.Index<DamageGroupPrototype>(damageGroupId);
foreach (var type in group.DamageTypes)
{
if (!damageDict.TryGetValue(type, out var typeAmount) || typeAmount <= 0)
continue;
var damageString = Loc.GetString(
"health-analyzer-window-damage-type-text",
("damageType", _prototypes.Index<DamageTypePrototype>(type).LocalizedName),
("amount", typeAmount)
);
groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
}
}
}
private Texture GetTexture(string texture)
{
var rsiPath = new ResPath("/Textures/Objects/Devices/health_analyzer.rsi");
var rsiSprite = new SpriteSpecifier.Rsi(rsiPath, texture);
var rsi = _cache.GetResource<RSIResource>(rsiSprite.RsiPath).RSI;
if (!rsi.TryGetState(rsiSprite.RsiState, out var state))
{
rsiSprite = new SpriteSpecifier.Rsi(rsiPath, "unknown");
}
return _spriteSystem.Frame0(rsiSprite);
}
private static Label CreateDiagnosticItemLabel(string text)
{
return new Label
{
Text = text,
};
}
private BoxContainer CreateDiagnosticGroupTitle(string text, string id)
{
var rootContainer = new BoxContainer
{
Margin = new Thickness(0, 6, 0, 0),
VerticalAlignment = VAlignment.Bottom,
Orientation = BoxContainer.LayoutOrientation.Horizontal,
};
rootContainer.AddChild(new TextureRect
{
SetSize = new Vector2(30, 30),
Texture = GetTexture(id.ToLower())
});
rootContainer.AddChild(CreateDiagnosticItemLabel(text));
return rootContainer;
}
public void Populate(HealthAnalyzerScannedUserMessage msg)
{
HealthAnalyzer.Populate(msg.State);
}
}

View File

@@ -18,7 +18,7 @@ public sealed partial class InfoSection : BoxContainer
{
TitleLabel.Text = title;
if (markup)
Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()), tagsAllowed: null);
else
Content.SetMessage(text);
}

View File

@@ -24,7 +24,7 @@ namespace Content.Client.Info
}
public void SetInfoBlob(string markup)
{
_richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup));
_richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup), tagsAllowed: null);
}
}
}

View File

@@ -0,0 +1,7 @@
using Content.Shared.Kitchen.EntitySystems;
using JetBrains.Annotations;
namespace Content.Client.Kitchen.EntitySystems;
[UsedImplicitly]
public sealed class ReagentGrinderSystem : SharedReagentGrinderSystem;

View File

@@ -20,8 +20,10 @@ namespace Content.Client.Launcher
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IClipboardManager _clipboard = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private LauncherConnectingGui? _control;
private ISawmill _sawmill = default!;
private Page _currentPage;
private string? _connectFailReason;
@@ -61,6 +63,8 @@ namespace Content.Client.Launcher
{
_control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg, _clipboard);
_sawmill = _logManager.GetSawmill("launcher-ui");
_userInterfaceManager.StateRoot.AddChild(_control);
_clientNetManager.ConnectFailed += OnConnectFailed;
@@ -115,12 +119,12 @@ namespace Content.Client.Launcher
}
else
{
Logger.InfoS("launcher-ui", $"Redial not possible, no Ss14Address");
_sawmill.Info($"Redial not possible, no Ss14Address");
}
}
catch (Exception ex)
{
Logger.ErrorS("launcher-ui", $"Redial exception: {ex}");
_sawmill.Error($"Redial exception: {ex}");
}
return false;
}

View File

@@ -1,6 +1,5 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Prometheus;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;

View File

@@ -0,0 +1,285 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
// ReSharper disable CompareOfFloatsByEqualityOperator
namespace Content.Client.Medical.Cryogenics;
public sealed class BeakerBarChart : Control
{
private sealed class Entry
{
public float WidthFraction; // This entry's width as a fraction of the chart's total width (between 0 and 1)
public float TargetAmount;
public string Uid; // This UID is used to track entries between frames, for animation.
public string? Tooltip;
public Color Color;
public Label Label;
public Entry(string uid, Label label)
{
Uid = uid;
Label = label;
}
}
public float Capacity = 50;
public Color NotchColor = new(1, 1, 1, 0.25f);
public Color BackgroundColor = new(0.1f, 0.1f, 0.1f);
public int MediumNotchInterval = 5;
public int BigNotchInterval = 10;
// When we have a very large beaker (i.e. bluespace beaker) we might need to increase the distance between notches.
// The distance between notches is increased by ScaleMultiplier when the distance between notches is less than
// MinSmallNotchScreenDistance in UI units.
public int MinSmallNotchScreenDistance = 2;
public int ScaleMultiplier = 10;
public float SmallNotchHeight = 0.1f;
public float MediumNotchHeight = 0.25f;
public float BigNotchHeight = 1f;
// We don't animate new entries until this control has been drawn at least once.
private bool _hasBeenDrawn = false;
// This is used to keep the segments of the chart in the same order as the SetEntry calls.
// For example: In update 1 we might get cryox, alox, bic (in that order), and in update 2 we get alox, cryox, bic.
// To keep the order of the entries the same as the order of the SetEntry calls, we let the old cryox entry
// disappear and create a new cryox entry behind the alox entry.
private int _nextUpdateableEntry = 0;
private readonly List<Entry> _entries = new();
public BeakerBarChart()
{
MouseFilter = MouseFilterMode.Pass;
TooltipSupplier = SupplyTooltip;
}
public void Clear()
{
foreach (var entry in _entries)
{
entry.TargetAmount = 0;
}
_nextUpdateableEntry = 0;
}
/// <summary>
/// Either adds a new entry to the chart if the UID doesn't appear yet, or updates the amount of an existing entry.
/// </summary>
public void SetEntry(
string uid,
string label,
float amount,
Color color,
Color? textColor = null,
string? tooltip = null)
{
// If we can find an old entry we're allowed to update, update that one.
if (TryFindUpdateableEntry(uid, out var index))
{
_entries[index].TargetAmount = amount;
_entries[index].Tooltip = tooltip;
_entries[index].Label.Text = label;
_nextUpdateableEntry = index + 1;
return;
}
// Otherwise create a new entry.
if (amount <= 0)
return;
// If no text color is provided, use either white or black depending on how dark the background is.
textColor ??= (color.R + color.G + color.B < 1.5f ? Color.White : Color.Black);
var childLabel = new Label
{
Text = label,
ClipText = true,
FontColorOverride = textColor,
Margin = new Thickness(4, 0, 0, 0)
};
AddChild(childLabel);
_entries.Insert(
_nextUpdateableEntry,
new Entry(uid, childLabel)
{
WidthFraction = (_hasBeenDrawn ? 0 : amount / Capacity),
TargetAmount = amount,
Tooltip = tooltip,
Color = color
}
);
_nextUpdateableEntry += 1;
}
private bool TryFindUpdateableEntry(string uid, out int index)
{
for (int i = _nextUpdateableEntry; i < _entries.Count; i++)
{
if (_entries[i].Uid == uid)
{
index = i;
return true;
}
}
index = -1;
return false;
}
private IEnumerable<(Entry, float xMin, float xMax)> EntryRanges(float? pixelWidth = null)
{
float chartWidth = pixelWidth ?? PixelWidth;
var xStart = 0f;
foreach (var entry in _entries)
{
var entryWidth = entry.WidthFraction * chartWidth;
var xEnd = MathF.Min(xStart + entryWidth, chartWidth);
yield return (entry, xStart, xEnd);
xStart = xEnd;
}
}
private bool TryFindEntry(float x, [NotNullWhen(true)] out Entry? entry)
{
foreach (var (currentEntry, xMin, xMax) in EntryRanges())
{
if (xMin <= x && x < xMax)
{
entry = currentEntry;
return true;
}
}
entry = null;
return false;
}
protected override void FrameUpdate(FrameEventArgs args)
{
// Tween the amounts to their target amounts.
const float tweenInverseHalfLife = 8; // Half life of tween is 1/n
var hasChanged = false;
foreach (var entry in _entries)
{
var targetWidthFraction = entry.TargetAmount / Capacity;
if (entry.WidthFraction == targetWidthFraction)
continue;
// Tween with lerp abuse interpolation
entry.WidthFraction = MathHelper.Lerp(
entry.WidthFraction,
targetWidthFraction,
MathHelper.Clamp01(tweenInverseHalfLife * args.DeltaSeconds)
);
hasChanged = true;
if (MathF.Abs(entry.WidthFraction - targetWidthFraction) < 0.0001f)
entry.WidthFraction = targetWidthFraction;
}
if (!hasChanged)
return;
InvalidateArrange();
// Remove old entries whose animations have finished.
foreach (var entry in _entries)
{
if (entry.WidthFraction == 0 && entry.TargetAmount == 0)
RemoveChild(entry.Label);
}
_entries.RemoveAll(entry => entry.WidthFraction == 0 && entry.TargetAmount == 0);
}
protected override void MouseMove(GUIMouseMoveEventArgs args)
{
HideTooltip();
}
protected override void Draw(DrawingHandleScreen handle)
{
handle.DrawRect(PixelSizeBox, BackgroundColor);
// Draw the entry backgrounds
foreach (var (entry, xMin, xMax) in EntryRanges())
{
if (xMin != xMax)
handle.DrawRect(new(xMin, 0, xMax, PixelHeight), entry.Color);
}
// Draw notches
var unitWidth = PixelWidth / Capacity;
var unitsPerNotch = 1;
while (unitWidth < MinSmallNotchScreenDistance)
{
// This is here for 1000u bluespace beakers. If the distance between small notches is so small that it would
// be very ugly, we reduce the amount of notches by ScaleMultiplier (currently a factor of 10).
// (I could use an analytical algorithm here, but it would be more difficult to read with pretty much no
// performance benefit, since it loops zero times normally and one time for the bluespace beaker)
unitWidth *= ScaleMultiplier;
unitsPerNotch *= ScaleMultiplier;
}
for (int i = 0; i <= Capacity / unitsPerNotch; i++)
{
var x = i * unitWidth;
var height = (i % BigNotchInterval == 0 ? BigNotchHeight :
i % MediumNotchInterval == 0 ? MediumNotchHeight :
SmallNotchHeight) * PixelHeight;
var start = new Vector2(x, PixelHeight);
var end = new Vector2(x, PixelHeight - height);
handle.DrawLine(start, end, NotchColor);
}
_hasBeenDrawn = true;
}
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
foreach (var (entry, xMin, xMax) in EntryRanges(finalSize.X))
{
entry.Label.Arrange(new((int)xMin, 0, (int)xMax, (int)finalSize.Y));
}
return finalSize;
}
private Control? SupplyTooltip(Control sender)
{
var globalMousePos = UserInterfaceManager.MousePositionScaled.Position;
var mousePos = globalMousePos - GlobalPosition;
if (!TryFindEntry(mousePos.X, out var entry) || entry.Tooltip == null)
return null;
var msg = new FormattedMessage();
msg.AddText(entry.Tooltip);
var tooltip = new Tooltip();
tooltip.SetMessage(msg);
return tooltip;
}
}

View File

@@ -0,0 +1,53 @@
using Content.Shared.FixedPoint;
using Content.Shared.Medical.Cryogenics;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.Medical.Cryogenics;
[UsedImplicitly]
public sealed class CryoPodBoundUserInterface : BoundUserInterface
{
private CryoPodWindow? _window;
public CryoPodBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_window = this.CreateWindowCenteredLeft<CryoPodWindow>();
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
_window.OnEjectPatientPressed += EjectPatientPressed;
_window.OnEjectBeakerPressed += EjectBeakerPressed;
_window.OnInjectPressed += InjectPressed;
}
private void EjectPatientPressed()
{
var isLocked =
EntMan.TryGetComponent<CryoPodComponent>(Owner, out var cryoComp)
&& cryoComp.Locked;
_window?.SetEjectErrorVisible(isLocked);
SendMessage(new CryoPodSimpleUiMessage(CryoPodSimpleUiMessage.MessageType.EjectPatient));
}
private void EjectBeakerPressed()
{
SendMessage(new CryoPodSimpleUiMessage(CryoPodSimpleUiMessage.MessageType.EjectBeaker));
}
private void InjectPressed(FixedPoint2 transferAmount)
{
SendMessage(new CryoPodInjectUiMessage(transferAmount));
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
if (_window != null && message is CryoPodUserMessage cryoMsg)
{
_window.Populate(cryoMsg);
}
}
}

View File

@@ -6,7 +6,6 @@ namespace Content.Client.Medical.Cryogenics;
public sealed class CryoPodSystem : SharedCryoPodSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
@@ -46,8 +45,8 @@ public sealed class CryoPodSystem : SharedCryoPodSystem
return;
}
if (!_appearance.TryGetData<bool>(uid, CryoPodVisuals.ContainsEntity, out var isOpen, args.Component)
|| !_appearance.TryGetData<bool>(uid, CryoPodVisuals.IsOn, out var isOn, args.Component))
if (!Appearance.TryGetData<bool>(uid, CryoPodVisuals.ContainsEntity, out var isOpen, args.Component)
|| !Appearance.TryGetData<bool>(uid, CryoPodVisuals.IsOn, out var isOn, args.Component))
{
return;
}
@@ -64,6 +63,11 @@ public sealed class CryoPodSystem : SharedCryoPodSystem
_sprite.LayerSetVisible((uid, args.Sprite), CryoPodVisualLayers.Cover, true);
}
}
protected override void UpdateUi(Entity<CryoPodComponent> cryoPod)
{
// Atmos and health scanner aren't predicted currently...
}
}
public enum CryoPodVisualLayers : byte

View File

@@ -0,0 +1,232 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:health="clr-namespace:Content.Client.HealthAnalyzer.UI"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:cryogenics="clr-namespace:Content.Client.Medical.Cryogenics"
MinSize="250 300"
Resizable="False">
<Label Name="LoadingPlaceHolder"
Text="{Loc 'cryo-pod-window-loading'}"
Align="Center"
HorizontalExpand="True"
VerticalExpand="True"/>
<BoxContainer Name="Sections"
Orientation="Horizontal"
Visible="False"
Margin="10"
SeparationOverride="16">
<BoxContainer Name="CryoSection"
VerticalExpand="True"
Orientation="Vertical"
MinWidth="250"
MaxWidth="250">
<!-- Flavor text -->
<BoxContainer Orientation="Horizontal"
SeparationOverride="10"
Margin="8 0 0 8">
<TextureRect StyleClasses="NTLogoDark"
VerticalExpand="True"
Stretch="KeepAspectCentered"
SetSize="32 32"/>
<BoxContainer Orientation="Vertical"
SeparationOverride="-4">
<Label Text="{Loc 'cryo-pod-window-product-name'}"
StyleClasses="FontLarge"/>
<Label Text="{Loc 'cryo-pod-window-product-subtitle'}"
StyleClasses="LabelSubText"/>
</BoxContainer>
</BoxContainer>
<!-- Atmos info -->
<BoxContainer Orientation="Horizontal"
SeparationOverride="20"
Margin="0 0 0 4">
<!-- Pressure -->
<BoxContainer Orientation="Vertical">
<Label Text="{Loc 'gas-analyzer-window-pressure-text'}"
StyleClasses="LabelSubText"/>
<Label Name="Pressure"/>
</BoxContainer>
<!-- Temperature -->
<BoxContainer Orientation="Vertical">
<Label Text="{Loc 'gas-analyzer-window-temperature-text'}"
StyleClasses="LabelSubText"/>
<Label Name="Temperature"/>
</BoxContainer>
</BoxContainer>
<!-- Gas mix -->
<Control Margin="0 0 0 22">
<controls:SplitBar Name="GasMixChart"
MinHeight="8"
MaxHeight="8"/>
</Control>
<!-- Warnings & status -->
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True"
Align="Center"
Margin="0 0 0 14"
SeparationOverride="20">
<!-- Ejection error (if the pod is locked) -->
<PanelContainer Name="EjectError"
Visible="False"
HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="orange"/>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical"
Margin="6">
<Label Text="{Loc 'cryo-pod-window-error-header'}"
FontColorOverride="orange"
Align="Center"/>
<RichTextLabel Text="{Loc 'cryo-pod-window-eject-error'}"/>
</BoxContainer>
</PanelContainer>
<!-- Pressure warning -->
<PanelContainer Name="LowPressureWarning"
Visible="False"
HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="orange"/>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical"
Margin="6">
<Label Text="{Loc 'cryo-pod-window-warning-header'}"
FontColorOverride="orange"
Align="Center"/>
<RichTextLabel Text="{Loc 'cryo-pod-window-low-pressure-warning'}"/>
</BoxContainer>
</PanelContainer>
<!-- Temperature warning -->
<PanelContainer Name="HighTemperatureWarning"
Visible="False"
HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="orange"/>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical"
Margin="6">
<Label Text="{Loc 'cryo-pod-window-warning-header'}"
FontColorOverride="orange"
Align="Center"/>
<!-- Note: This placeholder text should never be visible. -->
<RichTextLabel Name="HighTemperatureWarningText"
Text="Temperature too high."/>
</BoxContainer>
</PanelContainer>
<!-- Status checklist -->
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal"
SeparationOverride="8">
<Label Text="{Loc 'cryo-pod-window-status'}"/>
<Label Name="StatusLabel"
Text="{Loc 'cryo-pod-window-status-not-ready'}"
FontColorOverride="Orange"/>
</BoxContainer>
<GridContainer Columns="2"
HSeparationOverride="0"
VSeparationOverride="6"
Margin="6 3 0 0">
<Label Text="⋄"
StyleClasses="LabelSubText"/>
<Label Name="PressureCheck"
Text="{Loc 'cryo-pod-window-checklist-pressure'}"
StyleClasses="LabelSubText"/>
<Label Text="⋄"
StyleClasses="LabelSubText"/>
<Label Name="ChemicalsCheck"
Text="{Loc 'cryo-pod-window-checklist-chemicals'}"
StyleClasses="LabelSubText"
FontColorOverride="Orange"/>
<Label Text="⋄"
StyleClasses="LabelSubText"/>
<Label Name="TemperatureCheck"
Text="{Loc 'cryo-pod-window-checklist-temperature'}"
StyleClasses="LabelSubText"/>
</GridContainer>
</BoxContainer>
</BoxContainer>
<!-- Reagents -->
<Control HorizontalExpand="True"
MinHeight="30">
<Label Name="NoBeakerText"
Text="{Loc 'cryo-pod-window-chems-no-beaker'}"
FontColorOverride="Gray"
VerticalExpand="True"
VAlign="Center"/>
<cryogenics:BeakerBarChart Name="ChemicalsChart"
HorizontalExpand="True"
VerticalExpand="True"/>
</Control>
<!-- Buttons -->
<BoxContainer Orientation="Vertical"
Margin="-2 2 -2 0">
<BoxContainer Orientation="Horizontal">
<Button Name="Inject1"
Text="{Loc 'cryo-pod-window-inject-1u'}"
Disabled="True"
HorizontalExpand="True"
StyleClasses="OpenBoth"/>
<Button Name="Inject5"
Text="{Loc 'cryo-pod-window-inject-5u'}"
Disabled="True"
HorizontalExpand="True"
StyleClasses="OpenBoth"/>
<Button Name="Inject10"
Text="{Loc 'cryo-pod-window-inject-10u'}"
Disabled="True"
HorizontalExpand="True"
StyleClasses="OpenBoth"/>
<Button Name="Inject20"
Text="{Loc 'cryo-pod-window-inject-20u'}"
Disabled="True"
HorizontalExpand="True"
StyleClasses="OpenBoth"/>
<Button Name="EjectBeakerButton"
Text="{Loc 'cryo-pod-window-eject-beaker'}"
Disabled="True"
StyleClasses="OpenBoth"/>
</BoxContainer>
<Button Name="EjectPatientButton"
Text="{Loc 'cryo-pod-window-eject-patient'}"
Disabled="True"
HorizontalExpand="True"
StyleClasses="OpenRight"/>
</BoxContainer>
</BoxContainer>
<BoxContainer Name="HealthSection"
VerticalExpand="True"
Orientation="Vertical">
<health:HealthAnalyzerControl Name="HealthAnalyzer"/>
<!-- This label is used to deal with a stray hline at the end of the health analyzer UI -->
<Label Name="NoDamageText"
Text="{Loc 'cryo-pod-window-health-no-damage'}"
FontColorOverride="DeepSkyBlue"/>
<Control VerticalExpand="True"/>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -0,0 +1,260 @@
using System.Linq;
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Atmos;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage.Components;
using Content.Shared.EntityConditions.Conditions;
using Content.Shared.FixedPoint;
using Content.Shared.Medical.Cryogenics;
using Content.Shared.Temperature;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Medical.Cryogenics;
[GenerateTypedNameReferences]
public sealed partial class CryoPodWindow : FancyWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public event Action? OnEjectPatientPressed;
public event Action? OnEjectBeakerPressed;
public event Action<FixedPoint2>? OnInjectPressed;
public CryoPodWindow()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
EjectPatientButton.OnPressed += _ => OnEjectPatientPressed?.Invoke();
EjectBeakerButton.OnPressed += _ => OnEjectBeakerPressed?.Invoke();
Inject1.OnPressed += _ => OnInjectPressed?.Invoke(1);
Inject5.OnPressed += _ => OnInjectPressed?.Invoke(5);
Inject10.OnPressed += _ => OnInjectPressed?.Invoke(10);
Inject20.OnPressed += _ => OnInjectPressed?.Invoke(20);
}
public void Populate(CryoPodUserMessage msg)
{
// Loading screen
if (LoadingPlaceHolder.Visible)
{
LoadingPlaceHolder.Visible = false;
Sections.Visible = true;
}
// Atmosphere
var hasCorrectPressure = (msg.GasMix.Pressure > Atmospherics.WarningLowPressure);
var hasGas = (msg.GasMix.Pressure > Atmospherics.GasMinMoles);
var showsPressureWarning = !hasCorrectPressure;
LowPressureWarning.Visible = showsPressureWarning;
Pressure.Text = Loc.GetString("gas-analyzer-window-pressure-val-text",
("pressure", $"{msg.GasMix.Pressure:0.00}"));
Temperature.Text = Loc.GetString("generic-not-available-shorthand");
if (hasGas)
{
var celsius = TemperatureHelpers.KelvinToCelsius(msg.GasMix.Temperature);
Temperature.Text = Loc.GetString("gas-analyzer-window-temperature-val-text",
("tempK", $"{msg.GasMix.Temperature:0.0}"),
("tempC", $"{celsius:0.0}"));
}
// Gas mix segmented bar chart
GasMixChart.Clear();
GasMixChart.Visible = hasGas;
if (msg.GasMix.Gases != null)
{
var totalGasAmount = msg.GasMix.Gases.Sum(gas => gas.Amount);
foreach (var gas in msg.GasMix.Gases)
{
var color = Color.FromHex($"#{gas.Color}", Color.White);
var percent = gas.Amount / totalGasAmount * 100;
var localizedName = Loc.GetString(gas.Name);
var tooltip = Loc.GetString("gas-analyzer-window-molarity-percentage-text",
("gasName", localizedName),
("amount", $"{gas.Amount:0.##}"),
("percentage", $"{percent:0.#}"));
GasMixChart.AddEntry(gas.Amount, color, tooltip: tooltip);
}
}
// Health analyzer
var maybePatient = _entityManager.GetEntity(msg.Health.TargetEntity);
var hasPatient = msg.Health.TargetEntity.HasValue;
var hasDamage = (hasPatient
&& _entityManager.TryGetComponent(maybePatient, out DamageableComponent? damageable)
&& damageable.TotalDamage > 0);
NoDamageText.Visible = (hasPatient && !hasDamage);
HealthSection.Visible = hasPatient;
EjectPatientButton.Disabled = !hasPatient;
if (hasPatient)
HealthAnalyzer.Populate(msg.Health);
// Reagents
float? lowestTempRequirement = null;
ReagentId? lowestTempReagent = null;
var totalBeakerCapacity = msg.BeakerCapacity ?? 0;
var availableQuantity = new FixedPoint2();
var injectingQuantity =
msg.Injecting?.Aggregate(new FixedPoint2(), (sum, r) => sum + r.Quantity)
?? new FixedPoint2(); // Either the sum of the reagent quantities in `msg.Injecting` or zero.
var hasBeaker = (msg.Beaker != null);
ChemicalsChart.Clear();
ChemicalsChart.Capacity = (totalBeakerCapacity < 1 ? 50 : (int)totalBeakerCapacity);
var chartMaxChemsQuantity = ChemicalsChart.Capacity - injectingQuantity; // Ensure space for injection buffer
if (hasBeaker)
{
foreach (var (reagent, quantity) in msg.Beaker!)
{
availableQuantity += quantity;
// Make sure we don't add too many chemicals to the chart, so that there's still enough space to
// visualize the injection buffer.
var chemsQuantityOvershoot = FixedPoint2.Max(0, availableQuantity - chartMaxChemsQuantity);
var chartQuantity = FixedPoint2.Max(0, quantity - chemsQuantityOvershoot);
var reagentProto = _prototypeManager.Index<ReagentPrototype>(reagent.Prototype);
ChemicalsChart.SetEntry(
reagent.Prototype,
reagentProto.LocalizedName,
(float)chartQuantity,
reagentProto.SubstanceColor,
tooltip: $"{quantity}u {reagentProto.LocalizedName}"
);
var temp = TryFindMaxTemperatureRequirement(reagent);
if (lowestTempRequirement == null
|| temp < lowestTempRequirement)
{
lowestTempRequirement = temp;
lowestTempReagent = reagent;
}
}
}
if (injectingQuantity != 0)
{
var injectingText = (injectingQuantity > 1 ? $"{injectingQuantity}u" : "");
ChemicalsChart.SetEntry(
"injecting",
injectingText,
(float)injectingQuantity,
Color.MediumSpringGreen,
tooltip: Loc.GetString("cryo-pod-window-chems-injecting-tooltip",
("quantity", injectingQuantity))
);
}
var isBeakerEmpty = (injectingQuantity + availableQuantity == 0);
var isChemicalsChartVisible = (hasBeaker || injectingQuantity != 0);
NoBeakerText.Visible = !isChemicalsChartVisible;
ChemicalsChart.Visible = isChemicalsChartVisible;
Inject1.Disabled = (!hasPatient || availableQuantity < 0.1f);
Inject5.Disabled = (!hasPatient || availableQuantity <= 1);
Inject10.Disabled = (!hasPatient || availableQuantity <= 5);
Inject20.Disabled = (!hasPatient || availableQuantity <= 10);
EjectBeakerButton.Disabled = !hasBeaker;
// Temperature warning
var hasCorrectTemperature = (lowestTempRequirement == null || lowestTempRequirement > msg.GasMix.Temperature);
var showsTemperatureWarning = (!showsPressureWarning && !hasCorrectTemperature);
HighTemperatureWarning.Visible = showsTemperatureWarning;
if (showsTemperatureWarning)
{
var reagentName = _prototypeManager.Index<ReagentPrototype>(lowestTempReagent!.Value.Prototype)
.LocalizedName;
HighTemperatureWarningText.Text = Loc.GetString("cryo-pod-window-high-temperature-warning",
("reagent", reagentName),
("temperature", lowestTempRequirement!));
}
// Status checklist
const float fallbackTemperatureRequirement = 213;
var hasTemperatureCheck = (hasGas && hasCorrectTemperature
&& (lowestTempRequirement != null || msg.GasMix.Temperature < fallbackTemperatureRequirement));
var hasChemicals = (hasBeaker && !isBeakerEmpty);
UpdateChecklistItem(PressureCheck, Loc.GetString("cryo-pod-window-checklist-pressure"), hasCorrectPressure);
UpdateChecklistItem(ChemicalsCheck, Loc.GetString("cryo-pod-window-checklist-chemicals"), hasChemicals);
UpdateChecklistItem(TemperatureCheck, Loc.GetString("cryo-pod-window-checklist-temperature"), hasTemperatureCheck);
var isReady = (hasCorrectPressure && hasChemicals && hasTemperatureCheck);
var isCooling = (lowestTempRequirement != null && hasPatient
&& msg.Health.Temperature > lowestTempRequirement);
var isInjecting = (injectingQuantity > 0);
StatusLabel.Text = (!isReady ? Loc.GetString("cryo-pod-window-status-not-ready") :
isCooling ? Loc.GetString("cryo-pod-window-status-cooling") :
isInjecting ? Loc.GetString("cryo-pod-window-status-injecting") :
hasPatient ? Loc.GetString("cryo-pod-window-status-ready-to-inject") :
Loc.GetString("cryo-pod-window-status-ready-for-patient"));
StatusLabel.FontColorOverride = (isReady ? Color.DeepSkyBlue : Color.Orange);
}
private void UpdateChecklistItem(Label label, string text, bool isOkay)
{
label.Text = (isOkay ? text : Loc.GetString("cryo-pod-window-checklist-fail", ("item", text)));
label.FontColorOverride = (isOkay ? null : Color.Orange);
}
private float? TryFindMaxTemperatureRequirement(ReagentId reagent)
{
var reagentProto = _prototypeManager.Index<ReagentPrototype>(reagent.Prototype);
if (reagentProto.Metabolisms == null)
return null;
float? result = null;
foreach (var (_, metabolism) in reagentProto.Metabolisms)
{
foreach (var effect in metabolism.Effects)
{
if (effect.Conditions == null)
continue;
foreach (var condition in effect.Conditions)
{
// If there are multiple temperature conditions in the same reagent (which could hypothetically
// happen, although it currently doesn't), we return the lowest max temperature.
if (condition is TemperatureCondition tempCondition
&& float.IsFinite(tempCondition.Max)
&& (result == null || tempCondition.Max < result))
{
result = tempCondition.Max;
}
}
}
}
return result;
}
public void SetEjectErrorVisible(bool isVisible)
{
EjectError.Visible = isVisible;
}
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
const float antiJiggleSlackSpace = 80;
var oldSize = DesiredSize;
var newSize = base.MeasureOverride(availableSize);
// Reduce how often the height of the window jiggles
if (newSize.Y < oldSize.Y && newSize.Y + antiJiggleSlackSpace > oldSize.Y)
newSize.Y = oldSize.Y;
return newSize;
}
}

View File

@@ -0,0 +1,5 @@
using Content.Shared.Medical;
namespace Content.Client.Medical;
public sealed class DefibrillatorSystem : SharedDefibrillatorSystem;

View File

@@ -41,9 +41,9 @@ public sealed class NetworkConfiguratorLinkOverlay : Overlay
if (!Colors.TryGetValue(uid, out var color))
{
color = new Color(
_random.Next(0, 255),
_random.Next(0, 255),
_random.Next(0, 255));
_random.NextByte(0, 255),
_random.NextByte(0, 255),
_random.NextByte(0, 255));
Colors.Add(uid, color);
}

View File

@@ -1,8 +1,6 @@
using Content.Shared.Inventory.Events;
using Content.Shared.Overlays;
using Robust.Client.Graphics;
using System.Linq;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Overlays;
@@ -35,6 +33,9 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
{
base.UpdateInternal(component);
_overlay.DamageContainers.Clear();
_overlay.StatusIcon = null;
foreach (var comp in component.Components)
{
foreach (var damageContainerId in comp.DamageContainers)

View File

@@ -5,7 +5,6 @@ using Content.Shared.Overlays;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Robust.Shared.Prototypes;
using System.Linq;
using Content.Shared.Damage.Components;
namespace Content.Client.Overlays;
@@ -32,9 +31,13 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
{
base.UpdateInternal(component);
foreach (var damageContainerId in component.Components.SelectMany(x => x.DamageContainers))
DamageContainers.Clear();
foreach (var comp in component.Components)
{
DamageContainers.Add(damageContainerId);
foreach (var damageContainerId in comp.DamageContainers)
{
DamageContainers.Add(damageContainerId);
}
}
}

View File

@@ -17,7 +17,11 @@ public sealed class StationMapBoundUserInterface : BoundUserInterface
base.Open();
EntityUid? gridUid = null;
if (EntMan.TryGetComponent<TransformComponent>(Owner, out var xform))
if (EntMan.TryGetComponent<StationMapComponent>(Owner, out var comp) && comp.TargetGrid != null)
{
gridUid = comp.TargetGrid;
}
else if (EntMan.TryGetComponent<TransformComponent>(Owner, out var xform))
{
gridUid = xform.GridUid;
}
@@ -30,8 +34,8 @@ public sealed class StationMapBoundUserInterface : BoundUserInterface
{
stationName = gridMetaData.EntityName;
}
if (EntMan.TryGetComponent<StationMapComponent>(Owner, out var comp) && comp.ShowLocation)
if (comp != null && comp.ShowLocation)
_window.Set(stationName, gridUid, Owner);
else
_window.Set(stationName, gridUid, null);

View File

@@ -3,5 +3,6 @@ namespace Content.Client.Power;
/// Remains in use by portable scrubbers and lathes.
public enum PowerDeviceVisualLayers : byte
{
Powered
Powered,
Charging
}

View File

@@ -32,8 +32,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
_cfg.GetCVar(CCVars.ICShowSSDIndicator) &&
!_mobState.IsDead(uid) &&
!HasComp<ActiveNPCComponent>(uid) &&
TryComp<MindContainerComponent>(uid, out var mindContainer) &&
mindContainer.ShowExamineInfo)
HasComp<MindExaminableComponent>(uid))
{
args.StatusIcons.Add(_prototype.Index(component.Icon));
}

View File

@@ -16,7 +16,7 @@ public sealed class GenpopLockerBoundUserInterface(EntityUid owner, Enum uiKey)
_menu.OnConfigurationComplete += (name, time, crime) =>
{
SendMessage(new GenpopLockerIdConfiguredMessage(name, time, crime));
SendPredictedMessage(new GenpopLockerIdConfiguredMessage(name, time, crime));
Close();
};

View File

@@ -23,7 +23,6 @@ public sealed class IFFConsoleBoundUserInterface : BoundUserInterface
_window = this.CreateWindowCenteredLeft<IFFConsoleWindow>();
_window.ShowIFF += SendIFFMessage;
_window.ShowVessel += SendVesselMessage;
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -44,14 +43,6 @@ public sealed class IFFConsoleBoundUserInterface : BoundUserInterface
});
}
private void SendVesselMessage(bool obj)
{
SendMessage(new IFFShowVesselMessage()
{
Show = obj,
});
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);

View File

@@ -9,12 +9,6 @@
<Button Name="ShowIFFOnButton" Text="{Loc 'iff-console-on'}" StyleClasses="OpenRight" />
<Button Name="ShowIFFOffButton" Text="{Loc 'iff-console-off'}" StyleClasses="OpenLeft" />
</BoxContainer>
<Label Name="ShowVesselLabel" Text="{Loc 'iff-console-show-vessel-label'}" HorizontalExpand="True" StyleClasses="highlight" />
<BoxContainer Orientation="Horizontal" MinWidth="120">
<Button Name="ShowVesselOnButton" Text="{Loc 'iff-console-on'}" StyleClasses="OpenRight" />
<Button Name="ShowVesselOffButton" Text="{Loc 'iff-console-off'}" StyleClasses="OpenLeft" />
</BoxContainer>
</GridContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -13,9 +13,7 @@ public sealed partial class IFFConsoleWindow : FancyWindow,
IComputerWindow<IFFConsoleBoundUserInterfaceState>
{
private readonly ButtonGroup _showIFFButtonGroup = new();
private readonly ButtonGroup _showVesselButtonGroup = new();
public event Action<bool>? ShowIFF;
public event Action<bool>? ShowVessel;
public IFFConsoleWindow()
{
@@ -24,11 +22,6 @@ public sealed partial class IFFConsoleWindow : FancyWindow,
ShowIFFOnButton.Group = _showIFFButtonGroup;
ShowIFFOnButton.OnPressed += args => ShowIFFPressed(true);
ShowIFFOffButton.OnPressed += args => ShowIFFPressed(false);
ShowVesselOffButton.Group = _showVesselButtonGroup;
ShowVesselOnButton.Group = _showVesselButtonGroup;
ShowVesselOnButton.OnPressed += args => ShowVesselPressed(true);
ShowVesselOffButton.OnPressed += args => ShowVesselPressed(false);
}
private void ShowIFFPressed(bool pressed)
@@ -36,19 +29,14 @@ public sealed partial class IFFConsoleWindow : FancyWindow,
ShowIFF?.Invoke(pressed);
}
private void ShowVesselPressed(bool pressed)
{
ShowVessel?.Invoke(pressed);
}
public void UpdateState(IFFConsoleBoundUserInterfaceState state)
{
if ((state.AllowedFlags & IFFFlags.HideLabel) != 0x0)
if ((state.AllowedFlags & IFFFlags.HideLabel) != 0x0 || (state.AllowedFlags & IFFFlags.Hide) != 0x0)
{
ShowIFFOffButton.Disabled = false;
ShowIFFOnButton.Disabled = false;
if ((state.Flags & IFFFlags.HideLabel) != 0x0)
if ((state.Flags & IFFFlags.HideLabel) != 0x0 || (state.Flags & IFFFlags.Hide) != 0x0)
{
ShowIFFOffButton.Pressed = true;
}
@@ -62,25 +50,5 @@ public sealed partial class IFFConsoleWindow : FancyWindow,
ShowIFFOffButton.Disabled = true;
ShowIFFOnButton.Disabled = true;
}
if ((state.AllowedFlags & IFFFlags.Hide) != 0x0)
{
ShowVesselOffButton.Disabled = false;
ShowVesselOnButton.Disabled = false;
if ((state.Flags & IFFFlags.Hide) != 0x0)
{
ShowVesselOffButton.Pressed = true;
}
else
{
ShowVesselOnButton.Pressed = true;
}
}
else
{
ShowVesselOffButton.Disabled = true;
ShowVesselOnButton.Disabled = true;
}
}
}

View File

@@ -153,7 +153,7 @@ public sealed partial class MapScreen : BoxContainer
break;
}
if (IsFTLBlocked())
if (IsPingBlocked())
{
MapRebuildButton.Disabled = true;
ClearMapObjects();
@@ -408,9 +408,21 @@ public sealed partial class MapScreen : BoxContainer
}
}
/// <summary>
/// Returns true if we shouldn't be able to select the Scan for Objects button.
/// </summary>
private bool IsPingBlocked()
{
return _state switch
{
FTLState.Available or FTLState.Cooldown => false,
_ => true,
};
}
private void OnMapObjectPress(IMapObject mapObject)
{
if (IsFTLBlocked())
if (IsPingBlocked())
return;
var coordinates = _shuttles.GetMapCoordinates(mapObject);
@@ -506,7 +518,7 @@ public sealed partial class MapScreen : BoxContainer
BumpMapDequeue();
}
if (!IsFTLBlocked() && _nextPing < curTime)
if (!IsPingBlocked() && _nextPing < curTime)
{
MapRebuildButton.Disabled = false;
}

View File

@@ -21,7 +21,7 @@ public sealed partial class BorgMenu : FancyWindow
[Dependency] private readonly IEntityManager _entity = default!;
private readonly NameModifierSystem _nameModifier;
private readonly PowerCellSystem _powerCell;
private readonly PredictedBatterySystem _battery;
private readonly SharedBatterySystem _battery;
public Action? BrainButtonPressed;
public Action? EjectBatteryButtonPressed;
@@ -44,7 +44,7 @@ public sealed partial class BorgMenu : FancyWindow
_nameModifier = _entity.System<NameModifierSystem>();
_powerCell = _entity.System<PowerCellSystem>();
_battery = _entity.System<PredictedBatterySystem>();
_battery = _entity.System<SharedBatterySystem>();
_maxNameLength = _cfgManager.GetCVar(CCVars.MaxNameLength);

View File

@@ -3,10 +3,10 @@
StyleClasses="PanelLight"
Margin="5 5 5 0">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5 5 0 5">
<TextureButton Name="RemoveButton" VerticalAlignment="Top" HorizontalAlignment="Left" Scale="0.5 0.5"/>
<SpriteView Name="ModuleView" Margin="0 0 5 0"/>
<BoxContainer RectClipContent="True" HorizontalExpand="True">
<Label Name="ModuleName" HorizontalExpand="True" HorizontalAlignment="Center"/>
</BoxContainer>
<TextureButton Name="RemoveButton" VerticalAlignment="Top" HorizontalAlignment="Right" Scale="0.5 0.5"/>
</BoxContainer>
</PanelContainer>

View File

@@ -18,7 +18,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly PredictedBatterySystem _battery = default!;
[Dependency] private readonly SharedBatterySystem _battery = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!;

View File

@@ -19,12 +19,13 @@ public sealed class SmartFridgeBoundUserInterface : BoundUserInterface
_menu = this.CreateWindow<SmartFridgeMenu>();
_menu.OnItemSelected += OnItemSelected;
_menu.OnRemoveButtonPressed += data => SendPredictedMessage(new SmartFridgeRemoveEntryMessage(data.Entry));
Refresh();
}
public void Refresh()
{
if (_menu is not {} menu || !EntMan.TryGetComponent(Owner, out SmartFridgeComponent? fridge))
if (_menu is not { } menu || !EntMan.TryGetComponent(Owner, out SmartFridgeComponent? fridge))
return;
menu.SetFlavorText(Loc.GetString(fridge.FlavorText));

View File

@@ -13,4 +13,10 @@
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"/>
<TextureButton Name="RemoveButton"
StyleClasses="CrossButtonRed"
VerticalAlignment="Center"
Margin="0 0 10 0"
Scale="0.75 0.75"
Visible="True" />
</BoxContainer>

View File

@@ -8,11 +8,18 @@ namespace Content.Client.SmartFridge;
[GenerateTypedNameReferences]
public sealed partial class SmartFridgeItem : BoxContainer
{
public Action? RemoveButtonPressed;
public SmartFridgeItem(EntityUid uid, string text)
{
RobustXamlLoader.Load(this);
EntityView.SetEntity(uid);
NameLabel.Text = text;
RemoveButton.OnPressed += _ => RemoveButtonPressed?.Invoke();
if (uid.IsValid())
RemoveButton.Visible = false;
}
}

View File

@@ -17,6 +17,7 @@ public sealed partial class SmartFridgeMenu : FancyWindow
[Dependency] private readonly IEntityManager _entityManager = default!;
public event Action<GUIBoundKeyEventArgs, ListData>? OnItemSelected;
public event Action<SmartFridgeListData>? OnRemoveButtonPressed;
private readonly StyleBoxFlat _styleBox = new() { BackgroundColor = new Color(70, 73, 102) };
@@ -48,8 +49,10 @@ public sealed partial class SmartFridgeMenu : FancyWindow
return;
var label = Loc.GetString("smart-fridge-list-item", ("item", entry.Entry.Name), ("amount", entry.Amount));
button.AddChild(new SmartFridgeItem(entry.Representative, label));
var item = new SmartFridgeItem(entry.Representative, label);
item.RemoveButtonPressed += () => OnRemoveButtonPressed?.Invoke(entry);
button.AddChild(item);
button.ToolTip = label;
button.StyleBoxOverride = _styleBox;
}

View File

@@ -0,0 +1,18 @@
using Content.Shared.SmartFridge;
namespace Content.Client.SmartFridge;
public sealed class SmartFridgeSystem : SharedSmartFridgeSystem
{
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
protected override void UpdateUI(Entity<SmartFridgeComponent> ent)
{
base.UpdateUI(ent);
if (!_uiSystem.TryGetOpenUi<SmartFridgeBoundUserInterface>(ent.Owner, SmartFridgeUiKey.Key, out var bui))
return;
bui.Refresh();
}
}

View File

@@ -1,24 +0,0 @@
using Content.Shared.SmartFridge;
using Robust.Shared.Analyzers;
namespace Content.Client.SmartFridge;
public sealed class SmartFridgeUISystem : EntitySystem
{
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SmartFridgeComponent, AfterAutoHandleStateEvent>(OnSmartFridgeAfterState);
}
private void OnSmartFridgeAfterState(Entity<SmartFridgeComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (!_uiSystem.TryGetOpenUi<SmartFridgeBoundUserInterface>(ent.Owner, SmartFridgeUiKey.Key, out var bui))
return;
bui.Refresh();
}
}

View File

@@ -69,10 +69,10 @@ public sealed class LabelSheetlet : Sheetlet<PalettedStylesheet>
.Class(StyleClass.LabelMonospaceText)
.Prop(Label.StylePropertyFont, robotoMonoBold11),
E<Label>()
.Class(StyleClass.LabelMonospaceHeading)
.Class(StyleClass.LabelMonospaceSubHeading)
.Prop(Label.StylePropertyFont, robotoMonoBold12),
E<Label>()
.Class(StyleClass.LabelMonospaceSubHeading)
.Class(StyleClass.LabelMonospaceHeading)
.Prop(Label.StylePropertyFont, robotoMonoBold14),
];
}

View File

@@ -41,8 +41,8 @@ public static class StyleClass
public const string LabelKeyText = "LabelKeyText";
public const string LabelWeak = "LabelWeak"; // replaces `LabelSecondaryColor`
public const string LabelMonospaceText = "ConsoleText";
public const string LabelMonospaceHeading = "ConsoleText";
public const string LabelMonospaceSubHeading = "ConsoleText";
public const string LabelMonospaceHeading = "ConsoleHeading";
public const string LabelMonospaceSubHeading = "ConsoleSubHeading";
public const string BackgroundPanel = "BackgroundPanel"; // replaces `AngleRect`
public const string BackgroundPanelOpenLeft = "BackgroundPanelOpenLeft"; // replaces `BackgroundOpenLeft`

View File

@@ -34,11 +34,17 @@ public sealed class SurveillanceCameraMonitorBoundUserInterface : BoundUserInter
_window.SubnetRefresh += OnSubnetRefresh;
_window.CameraSwitchTimer += OnCameraSwitchTimer;
_window.CameraDisconnect += OnCameraDisconnect;
var xform = EntMan.GetComponent<TransformComponent>(Owner);
var gridUid = xform.GridUid ?? xform.MapUid;
if (gridUid is not null)
_window?.SetMap(gridUid.Value);
}
private void OnCameraSelected(string address)
private void OnCameraSelected(string address, string? subnet)
{
SendMessage(new SurveillanceCameraMonitorSwitchMessage(address));
SendMessage(new SurveillanceCameraMonitorSwitchMessage(address, subnet));
}
private void OnSubnetRequest(string subnet)

View File

@@ -1,25 +1,71 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:viewport="clr-namespace:Content.Client.Viewport"
xmlns:local="clr-namespace:Content.Client.SurveillanceCamera.UI"
Title="{Loc 'surveillance-camera-monitor-ui-window'}">
<BoxContainer Orientation="Horizontal">
<BoxContainer Orientation="Vertical" MinWidth="350" VerticalExpand="True">
<!-- lazy -->
<OptionButton Name="SubnetSelector" />
<Button Name="SubnetRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-subnets'}" />
<ScrollContainer VerticalExpand="True">
<ItemList Name="SubnetList" />
</ScrollContainer>
<Button Name="CameraRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-cameras'}" />
<Button Name="CameraDisconnectButton" Text="{Loc 'surveillance-camera-monitor-ui-disconnect'}" />
<Label Name="CameraStatus" />
<BoxContainer>
<!-- Panel with tabs -->
<BoxContainer Orientation="Vertical" MinWidth="350">
<TabContainer Name="ViewModeTabs" VerticalExpand="True">
<!-- Camera list tab -->
<BoxContainer Name="{Loc 'surveillance-camera-monitor-ui-tab-list'}" Orientation="Vertical">
<OptionButton Name="SubnetSelector"/>
<Button Name="SubnetRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-subnets'}"/>
<ScrollContainer VerticalExpand="True">
<ItemList Name="SubnetList"/>
</ScrollContainer>
<Button Name="CameraRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-cameras'}"/>
</BoxContainer>
<!-- Map view tab -->
<BoxContainer Name="{Loc 'surveillance-camera-monitor-ui-tab-map'}" Orientation="Vertical" VerticalExpand="True">
<local:SurveillanceCameraNavMapControl Name="CameraMap"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="350 350"/>
<!-- Map legend -->
<BoxContainer Name="LegendContainer" Margin="0 10">
<TextureRect Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/NavMap/beveled_triangle.png"
Modulate="#FF00FF"
SetSize="20 20"
Margin="10 0 5 0"/>
<Label Text="{Loc 'surveillance-camera-monitor-ui-legend-active'}"/>
<TextureRect Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/NavMap/beveled_triangle.png"
SetSize="20 20"
Modulate="#fbff19ff"
Margin="10 0 5 0"/>
<Label Text="{Loc 'surveillance-camera-monitor-ui-legend-selected'}"/>
<TextureRect Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/NavMap/beveled_circle.png"
SetSize="20 20"
Modulate="#a09f9fff"
Margin="10 0 5 0"/>
<Label Text="{Loc 'surveillance-camera-monitor-ui-legend-inactive'}"/>
<TextureRect Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/NavMap/beveled_square.png"
SetSize="20 20"
Modulate="#fa1f1fff"
Margin="10 0 5 0"/>
<Label Text="{Loc 'surveillance-camera-monitor-ui-legend-invalid'}"/>
</BoxContainer>
<Button Name="SubnetRefreshButtonMap" Text="{Loc 'surveillance-camera-monitor-ui-refresh-subnets'}"/>
</BoxContainer>
</TabContainer>
<Button Name="CameraDisconnectButton" Text="{Loc 'surveillance-camera-monitor-ui-disconnect'}"/>
</BoxContainer>
<!-- Right panel with camera view -->
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<Label Name="CameraStatus"/>
<Control VerticalExpand="True" Margin="5" Name="CameraViewBox">
<viewport:ScalingViewport Name="CameraView" MinSize="500 500" MouseFilter="Ignore"/>
<TextureRect MinSize="500 500" Name="CameraViewBackground" />
</Control>
</BoxContainer>
<Control VerticalExpand="True" HorizontalExpand="True" Margin="5 5 5 5" Name="CameraViewBox">
<viewport:ScalingViewport Name="CameraView"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="500 500"
MouseFilter="Ignore" />
<TextureRect VerticalExpand="True" HorizontalExpand="True" MinSize="500 500" Name="CameraViewBackground" />
</Control>
</BoxContainer>
</DefaultWindow>

View File

@@ -3,6 +3,7 @@ using Content.Client.Resources;
using Content.Client.Viewport;
using Content.Shared.DeviceNetwork;
using Content.Shared.SurveillanceCamera;
using Content.Shared.SurveillanceCamera.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@@ -21,8 +22,15 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
/// <summary>
/// Triggered when a camera is selected.
/// First parameter contains the camera's address.
/// Second optional parameter contains a subnet - if possible, the monitor will switch to this subnet.
/// </summary>
public event Action<string, string?>? CameraSelected;
public event Action<string>? CameraSelected;
public event Action<string>? SubnetOpened;
public event Action? CameraRefresh;
public event Action? SubnetRefresh;
@@ -33,6 +41,7 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
private bool _isSwitching;
private readonly FixedEye _defaultEye = new();
private readonly Dictionary<string, int> _subnetMap = new();
private EntityUid? _mapUid;
private string? SelectedSubnet
{
@@ -68,11 +77,15 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
SubnetSelector.OnItemSelected += args =>
{
// piss
SubnetOpened!((string) args.Button.GetItemMetadata(args.Id)!);
SubnetOpened?.Invoke((string) args.Button.GetItemMetadata(args.Id)!);
};
SubnetRefreshButton.OnPressed += _ => SubnetRefresh!();
CameraRefreshButton.OnPressed += _ => CameraRefresh!();
CameraDisconnectButton.OnPressed += _ => CameraDisconnect!();
SubnetRefreshButton.OnPressed += _ => SubnetRefresh?.Invoke();
SubnetRefreshButtonMap.OnPressed += _ => SubnetRefresh?.Invoke();
CameraRefreshButton.OnPressed += _ => CameraRefresh?.Invoke();
CameraDisconnectButton.OnPressed += _ => CameraDisconnect?.Invoke();
CameraMap.EnableCameraSelection = true;
CameraMap.CameraSelected += OnCameraMapSelected;
}
@@ -80,6 +93,9 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
// pass it here so that the UI can change its view.
public void UpdateState(IEye? eye, HashSet<string> subnets, string activeAddress, string activeSubnet, Dictionary<string, string> cameras)
{
CameraMap.SetActiveCameraAddress(activeAddress);
CameraMap.SetAvailableSubnets(subnets);
_currentAddress = activeAddress;
SetCameraView(eye);
@@ -189,6 +205,25 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
private void OnSubnetListSelect(ItemList.ItemListSelectedEventArgs args)
{
CameraSelected!((string) SubnetList[args.ItemIndex].Metadata!);
CameraSelected!((string) SubnetList[args.ItemIndex].Metadata!, null);
}
public void SetMap(EntityUid mapUid)
{
CameraMap.MapUid = _mapUid = mapUid;
}
private void OnCameraMapSelected(NetEntity netEntity)
{
if (_mapUid is null || !_entityManager.TryGetComponent<SurveillanceCameraMapComponent>(_mapUid.Value, out var mapComp))
return;
if (!mapComp.Cameras.TryGetValue(netEntity, out var marker) || !marker.Active)
return;
if (!string.IsNullOrEmpty(marker.Address))
CameraSelected?.Invoke(marker.Address, marker.Subnet);
else
_entityManager.RaisePredictiveEvent(new RequestCameraMarkerUpdateMessage(netEntity));
}
}

View File

@@ -0,0 +1,130 @@
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Map;
using Content.Client.Pinpointer.UI;
using Content.Client.Resources;
using Content.Shared.SurveillanceCamera.Components;
namespace Content.Client.SurveillanceCamera.UI;
public sealed class SurveillanceCameraNavMapControl : NavMapControl
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
private static readonly Color CameraActiveColor = Color.FromHex("#FF00FF");
private static readonly Color CameraInactiveColor = Color.FromHex("#a09f9fff");
private static readonly Color CameraSelectedColor = Color.FromHex("#fbff19ff");
private static readonly Color CameraInvalidColor = Color.FromHex("#fa1f1fff");
private readonly Texture _activeTexture;
private readonly Texture _inactiveTexture;
private readonly Texture _selectedTexture;
private readonly Texture _invalidTexture;
private string _activeCameraAddress = string.Empty;
private HashSet<string> _availableSubnets = new();
private (Dictionary<NetEntity, CameraMarker> Cameras, string ActiveAddress, HashSet<string> AvailableSubnets) _lastState;
public bool EnableCameraSelection { get; set; }
public event Action<NetEntity>? CameraSelected;
public SurveillanceCameraNavMapControl()
{
IoCManager.InjectDependencies(this);
_activeTexture = _resourceCache.GetTexture("/Textures/Interface/NavMap/beveled_triangle.png");
_selectedTexture = _activeTexture;
_inactiveTexture = _resourceCache.GetTexture("/Textures/Interface/NavMap/beveled_circle.png");
_invalidTexture = _resourceCache.GetTexture("/Textures/Interface/NavMap/beveled_square.png");
TrackedEntitySelectedAction += entity =>
{
if (entity.HasValue)
CameraSelected?.Invoke(entity.Value);
};
}
public void SetActiveCameraAddress(string address)
{
if (_activeCameraAddress == address)
return;
_activeCameraAddress = address;
ForceNavMapUpdate();
}
public void SetAvailableSubnets(HashSet<string> subnets)
{
if (_availableSubnets.SetEquals(subnets))
return;
_availableSubnets = subnets;
ForceNavMapUpdate();
}
protected override void UpdateNavMap()
{
base.UpdateNavMap();
if (MapUid is null || !_entityManager.TryGetComponent<SurveillanceCameraMapComponent>(MapUid, out var mapComp))
return;
var currentState = (mapComp.Cameras, _activeCameraAddress, _availableSubnets);
if (_lastState.Equals(currentState))
return;
_lastState = currentState;
UpdateCameraMarkers(mapComp);
}
private void UpdateCameraMarkers(SurveillanceCameraMapComponent mapComp)
{
TrackedEntities.Clear();
if (MapUid is null)
return;
foreach (var (netEntity, marker) in mapComp.Cameras)
{
if (!marker.Visible || !_availableSubnets.Contains(marker.Subnet))
continue;
var coords = new EntityCoordinates(MapUid.Value, marker.Position);
Texture texture;
Color color;
if (string.IsNullOrEmpty(marker.Address))
{
color = CameraInvalidColor;
texture = _invalidTexture;
}
else if (marker.Address == _activeCameraAddress)
{
color = CameraSelectedColor;
texture = _selectedTexture;
}
else if (marker.Active)
{
color = CameraActiveColor;
texture = _activeTexture;
}
else
{
color = CameraInactiveColor;
texture = _inactiveTexture;
}
TrackedEntities[netEntity] = new NavMapBlip(
coords,
texture,
color,
false,
EnableCameraSelection
);
}
}
}

View File

@@ -10,7 +10,7 @@ public static class BoundKeyHelper
public static string ShortKeyName(BoundKeyFunction keyFunction)
{
// need to use shortened key names so they fit in the buttons.
return TryGetShortKeyName(keyFunction, out var name) ? Loc.GetString(name) : " ";
return TryGetShortKeyName(keyFunction, out var name) ? name : " ";
}
public static bool IsBound(BoundKeyFunction keyFunction)

View File

@@ -28,7 +28,16 @@ public class ListContainer : Control
/// Called when creating a button on the UI.
/// The provided <see cref="ListContainerButton"/> is the generated button that Controls should be parented to.
/// </summary>
public Action<ListData, ListContainerButton>? GenerateItem;
public Action<ListData, ListContainerButton>? GenerateItem
{
get => _generateItem;
set {
_generateItem = value;
// Invalidate _itemHeight so we recalculate the size of children the next
// time PopulateList() is called
_itemHeight = 0;
}
}
/// <inheritdoc cref="BaseButton.OnPressed"/>
public Action<BaseButton.ButtonEventArgs, ListData>? ItemPressed;
@@ -59,6 +68,7 @@ public class ListContainer : Control
private bool _updateChildren = false;
private bool _suppressScrollValueChanged;
private ButtonGroup? _buttonGroup;
public Action<ListData, ListContainerButton>? _generateItem;
public int ScrollSpeedY { get; set; } = 50;

View File

@@ -198,8 +198,8 @@ public sealed class ActionButton : Control, IEntityControl
if (!_entities.TryGetComponent(Action, out MetaDataComponent? metadata))
return null;
var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName));
var desc = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription));
var name = FormattedMessage.FromMarkupPermissive(metadata.EntityName);
var desc = FormattedMessage.FromMarkupPermissive(metadata.EntityDescription);
if (_player.LocalEntity is null)
return null;

View File

@@ -117,7 +117,7 @@ public partial class ChatBox : UIWidget
formatted.PushColor(color);
formatted.AddMarkupOrThrow(message);
formatted.Pop();
Contents.AddMessage(formatted);
Contents.AddMessage(formatted, tagsAllowed: null);
}
public void Focus(ChatSelectChannel? channel = null)

View File

@@ -26,6 +26,8 @@ public sealed class VoiceMaskBoundUserInterface : BoundUserInterface
_window.OnNameChange += OnNameSelected;
_window.OnVerbChange += verb => SendMessage(new VoiceMaskChangeVerbMessage(verb));
_window.OnToggle += OnToggle;
_window.OnAccentToggle += OnAccentToggle;
_window.OnVoiceChange += voice => SendMessage(new VoiceMaskChangeVoiceMessage(voice)); // Corvax-TTS
}
@@ -34,6 +36,16 @@ public sealed class VoiceMaskBoundUserInterface : BoundUserInterface
SendMessage(new VoiceMaskChangeNameMessage(name));
}
private void OnToggle()
{
SendMessage(new VoiceMaskToggleMessage());
}
private void OnAccentToggle()
{
SendMessage(new VoiceMaskAccentToggleMessage());
}
protected override void UpdateState(BoundUserInterfaceState state)
{
if (state is not VoiceMaskBuiState cast || _window == null)
@@ -41,7 +53,7 @@ public sealed class VoiceMaskBoundUserInterface : BoundUserInterface
return;
}
_window.UpdateState(cast.Name, cast.Voice, cast.Verb); // Corvax-TTS
_window.UpdateState(cast.Name, cast.Verb, cast.Active, cast.AccentHide, cast.Voice);//cast.Voice Corvax-TTS
}
protected override void Dispose(bool disposing)

View File

@@ -12,6 +12,8 @@
<Label Text="{Loc 'voice-mask-name-change-speech-style'}" />
<OptionButton Name="SpeechVerbSelector" /> <!-- Populated in LoadVerbs -->
</BoxContainer>
<Button Name="ToggleAccentButton" Text="{Loc 'voice-mask-name-change-accent-toggle'}" HorizontalExpand="True" ToggleMode="True" Margin="5"/>
<Button Name="ToggleButton" Text="{Loc 'voice-mask-name-change-toggle'}" HorizontalExpand="True" ToggleMode="True" Margin="5"/>
<!-- Corvax-TTS-Start -->
<BoxContainer Orientation="Horizontal" Margin="5" Visible="False" Name="TTSContainer">
<Label Text="{Loc 'voice-mask-voice-change-info'}" />

View File

@@ -15,6 +15,8 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
{
public Action<string>? OnNameChange;
public Action<string?>? OnVerbChange;
public Action? OnToggle;
public Action? OnAccentToggle;
public Action<string>? OnVoiceChange; // Corvax-TTS
private List<(string, string)> _verbs = new();
@@ -37,6 +39,9 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
SpeechVerbSelector.SelectId(args.Id);
};
ToggleButton.OnPressed += args => OnToggle?.Invoke();
ToggleAccentButton.OnPressed += args => OnAccentToggle?.Invoke();
// Corvax-TTS-Start
if (IoCManager.Resolve<IConfigurationManager>().GetCVar(CCCVars.TTSEnabled))
{
@@ -100,10 +105,12 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
}
// Corvax-TTS-End
public void UpdateState(string name, string voice, string? verb) // Corvax-TTS
public void UpdateState(string name, string? verb, bool active, bool accentHide, string voice) // Corvax-TTS
{
NameSelector.Text = name;
_verb = verb;
ToggleButton.Pressed = active;
ToggleAccentButton.Pressed = accentHide;
for (int id = 0; id < SpeechVerbSelector.ItemCount; id++)
{

View File

@@ -43,6 +43,9 @@ public sealed partial class MeleeWeaponSystem
return;
}
var length = 1f;
var offset = 1f;
var spriteRotation = Angle.Zero;
if (arcComponent.Animation != WeaponArcAnimation.None
&& TryComp(weapon, out MeleeWeaponComponent? meleeWeaponComponent))
@@ -55,9 +58,11 @@ public sealed partial class MeleeWeaponSystem
if (meleeWeaponComponent.SwingLeft)
angle *= -1;
length = (1 / meleeWeaponComponent.AttackRate) * 0.6f;
offset = meleeWeaponComponent.AnimationOffset;
}
_sprite.SetRotation((animationUid, sprite), localPos.ToWorldAngle());
var distance = Math.Clamp(localPos.Length() / 2f, 0.2f, 1f);
var xform = _xformQuery.GetComponent(animationUid);
TrackUserComponent track;
@@ -67,16 +72,16 @@ public sealed partial class MeleeWeaponSystem
case WeaponArcAnimation.Slash:
track = EnsureComp<TrackUserComponent>(animationUid);
track.User = user;
_animation.Play(animationUid, GetSlashAnimation(sprite, angle, spriteRotation), SlashAnimationKey);
_animation.Play(animationUid, GetSlashAnimation((animationUid, sprite), angle, spriteRotation, length, offset), SlashAnimationKey);
if (arcComponent.Fadeout)
_animation.Play(animationUid, GetFadeAnimation(sprite, 0.065f, 0.065f + 0.05f), FadeAnimationKey);
_animation.Play(animationUid, GetFadeAnimation(sprite, length * 0.5f, length + 0.15f), FadeAnimationKey);
break;
case WeaponArcAnimation.Thrust:
track = EnsureComp<TrackUserComponent>(animationUid);
track.User = user;
_animation.Play(animationUid, GetThrustAnimation((animationUid, sprite), distance, spriteRotation), ThrustAnimationKey);
_animation.Play(animationUid, GetThrustAnimation((animationUid, sprite), offset, spriteRotation, length), ThrustAnimationKey);
if (arcComponent.Fadeout)
_animation.Play(animationUid, GetFadeAnimation(sprite, 0.05f, 0.15f), FadeAnimationKey);
_animation.Play(animationUid, GetFadeAnimation(sprite, length * 0.5f, length + 0.15f), FadeAnimationKey);
break;
case WeaponArcAnimation.None:
var (mapPos, mapRot) = TransformSystem.GetWorldPositionRotation(userXform);
@@ -89,21 +94,22 @@ public sealed partial class MeleeWeaponSystem
}
}
private Animation GetSlashAnimation(SpriteComponent sprite, Angle arc, Angle spriteRotation)
private Animation GetSlashAnimation(Entity<SpriteComponent> sprite, Angle arc, Angle spriteRotation, float length, float offset)
{
const float slashStart = 0.03f;
const float slashEnd = 0.065f;
const float length = slashEnd + 0.05f;
var startRotation = sprite.Rotation + arc / 2;
var endRotation = sprite.Rotation - arc / 2;
var startRotationOffset = startRotation.RotateVec(new Vector2(0f, -1f));
var endRotationOffset = endRotation.RotateVec(new Vector2(0f, -1f));
var startRotation = sprite.Comp.Rotation + (arc * 0.5f);
var endRotation = sprite.Comp.Rotation - (arc * 0.5f);
var startRotationOffset = startRotation.RotateVec(new Vector2(0f, -offset * 0.9f));
var minRotationOffset = sprite.Comp.Rotation.RotateVec(new Vector2(0f, -offset * 1.1f));
var endRotationOffset = endRotation.RotateVec(new Vector2(0f, -offset * 0.9f));
startRotation += spriteRotation;
endRotation += spriteRotation;
sprite.Comp.NoRotation = true;
return new Animation()
{
Length = TimeSpan.FromSeconds(length),
Length = TimeSpan.FromSeconds(length + 0.05f),
AnimationTracks =
{
new AnimationTrackComponentProperty()
@@ -112,10 +118,12 @@ public sealed partial class MeleeWeaponSystem
Property = nameof(SpriteComponent.Rotation),
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(startRotation, 0f),
new AnimationTrackProperty.KeyFrame(startRotation, slashStart),
new AnimationTrackProperty.KeyFrame(endRotation, slashEnd)
}
new AnimationTrackProperty.KeyFrame(Angle.Lerp(startRotation,endRotation,0.0f), length * 0.0f),
new AnimationTrackProperty.KeyFrame(Angle.Lerp(startRotation,endRotation,0.5f), length * 0.10f),
new AnimationTrackProperty.KeyFrame(Angle.Lerp(startRotation,endRotation,1.0f), length * 0.15f),
new AnimationTrackProperty.KeyFrame(Angle.Lerp(startRotation,endRotation,0.9f), length * 0.20f),
new AnimationTrackProperty.KeyFrame(Angle.Lerp(startRotation,endRotation,0.80f), length * 0.6f, Easings.OutQuart)
},
},
new AnimationTrackComponentProperty()
{
@@ -123,21 +131,21 @@ public sealed partial class MeleeWeaponSystem
Property = nameof(SpriteComponent.Offset),
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(startRotationOffset, 0f),
new AnimationTrackProperty.KeyFrame(startRotationOffset, slashStart),
new AnimationTrackProperty.KeyFrame(endRotationOffset, slashEnd)
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startRotationOffset,endRotationOffset,0.0f), length * 0.0f),
new AnimationTrackProperty.KeyFrame(minRotationOffset, length * 0.10f),
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startRotationOffset,endRotationOffset,1.0f), length * 0.15f),
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startRotationOffset,endRotationOffset,0.80f), length * 0.6f, Easings.OutQuart)
}
},
}
};
}
private Animation GetThrustAnimation(Entity<SpriteComponent> sprite, float distance, Angle spriteRotation)
private Animation GetThrustAnimation(Entity<SpriteComponent> sprite, float offset, Angle spriteRotation, float length)
{
const float thrustEnd = 0.05f;
const float length = 0.15f;
var startOffset = sprite.Comp.Rotation.RotateVec(new Vector2(0f, -distance / 5f));
var endOffset = sprite.Comp.Rotation.RotateVec(new Vector2(0f, -distance));
var startOffset = sprite.Comp.Rotation.RotateVec(new Vector2(0f, 0f));
var endOffset = sprite.Comp.Rotation.RotateVec(new Vector2(0f, -offset * 1.2f));
_sprite.SetRotation(sprite.AsNullable(), sprite.Comp.Rotation + spriteRotation);
return new Animation()
@@ -151,9 +159,11 @@ public sealed partial class MeleeWeaponSystem
Property = nameof(SpriteComponent.Offset),
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(startOffset, 0f),
new AnimationTrackProperty.KeyFrame(endOffset, thrustEnd),
new AnimationTrackProperty.KeyFrame(endOffset, length),
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startOffset, endOffset, 0f), length * 0f),
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startOffset, endOffset, 0.65f), length * 0.10f),
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startOffset, endOffset, 1f), length * 0.20f),
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startOffset, endOffset, 0.9f), length * 0.30f),
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startOffset, endOffset, 0.7f), length * 0.60f, Easings.OutQuart)
}
},
}
@@ -200,11 +210,12 @@ public sealed partial class MeleeWeaponSystem
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(direction.Normalized() * 0.15f, 0f),
new AnimationTrackProperty.KeyFrame(Vector2.Zero, length)
}
}
}
new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0f),
new AnimationTrackProperty.KeyFrame(direction.Normalized() * 0.15f, length*0.4f),
new AnimationTrackProperty.KeyFrame(Vector2.Zero, length*0.6f),
},
},
},
};
}

View File

@@ -11,17 +11,20 @@ public sealed partial class MagazineVisualsComponent : Component
/// <summary>
/// What RsiState we use.
/// </summary>
[DataField("magState")] public string? MagState;
[DataField]
public string? MagState;
/// <summary>
/// How many steps there are
/// </summary>
[DataField("steps")] public int MagSteps;
[DataField("steps")]
public int MagSteps;
/// <summary>
/// Should we hide when the count is 0
/// </summary>
[DataField("zeroVisible")] public bool ZeroVisible;
[DataField]
public bool ZeroVisible;
}
public enum GunVisualLayers : byte

View File

@@ -8,9 +8,10 @@ public sealed partial class SpentAmmoVisualsComponent : Component
/// <summary>
/// Should we do "{_state}-spent" or just "spent"
/// </summary>
[DataField("suffix")] public bool Suffix = true;
[DataField]
public bool Suffix = true;
[DataField("state")]
[DataField]
public string State = "base";
}

View File

@@ -48,7 +48,7 @@ public sealed class GunSpreadOverlay : Overlay
if (mapPos.MapId == MapId.Nullspace)
return;
if (!_guns.TryGetGun(player.Value, out var gunUid, out var gun))
if (!_guns.TryGetGun(player.Value, out var gun))
return;
var mouseScreenPos = _input.MouseScreenPosition;
@@ -58,12 +58,12 @@ public sealed class GunSpreadOverlay : Overlay
return;
// (☞゚ヮ゚)☞
var maxSpread = gun.MaxAngleModified;
var minSpread = gun.MinAngleModified;
var timeSinceLastFire = (_timing.CurTime - gun.NextFire).TotalSeconds;
var currentAngle = new Angle(MathHelper.Clamp(gun.CurrentAngle.Theta - gun.AngleDecayModified.Theta * timeSinceLastFire,
gun.MinAngleModified.Theta, gun.MaxAngleModified.Theta));
var direction = (mousePos.Position - mapPos.Position);
var maxSpread = gun.Comp.MaxAngleModified;
var minSpread = gun.Comp.MinAngleModified;
var timeSinceLastFire = (_timing.CurTime - gun.Comp.NextFire).TotalSeconds;
var currentAngle = new Angle(MathHelper.Clamp(gun.Comp.CurrentAngle.Theta - gun.Comp.AngleDecayModified.Theta * timeSinceLastFire,
gun.Comp.MinAngleModified.Theta, gun.Comp.MaxAngleModified.Theta));
var direction = mousePos.Position - mapPos.Position;
worldHandle.DrawLine(mapPos.Position, mousePos.Position + direction, Color.Orange);

View File

@@ -41,7 +41,7 @@ public abstract class BaseBulletRenderer : Control
{
var countPerRow = Math.Min(Capacity, CountPerRow(availableSize.X));
var rows = Math.Min((int) MathF.Ceiling(Capacity / (float) countPerRow), Rows);
var rows = Math.Min((int)MathF.Ceiling(Capacity / (float)countPerRow), Rows);
var height = _params.ItemHeight * rows + (_params.VerticalSeparation * rows - 1);
var width = RowWidth(countPerRow);
@@ -110,7 +110,7 @@ public abstract class BaseBulletRenderer : Control
private int CountPerRow(float width)
{
return (int) ((width - _params.ItemWidth + _params.ItemSeparation) / _params.ItemSeparation);
return (int)((width - _params.ItemWidth + _params.ItemSeparation) / _params.ItemSeparation);
}
private int RowWidth(int count)

View File

@@ -2,10 +2,8 @@ using Content.Shared.Projectiles;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Client.Player;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Physics.Events;
using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Client.Weapons.Ranged.Systems;
@@ -22,26 +20,26 @@ public sealed class FlyBySoundSystem : SharedFlyBySoundSystem
SubscribeLocalEvent<FlyBySoundComponent, StartCollideEvent>(OnCollide);
}
private void OnCollide(EntityUid uid, FlyBySoundComponent component, ref StartCollideEvent args)
private void OnCollide(Entity<FlyBySoundComponent> ent, ref StartCollideEvent args)
{
var attachedEnt = _player.LocalEntity;
// If it's not our ent or we shot it.
if (attachedEnt == null ||
args.OtherEntity != attachedEnt ||
TryComp<ProjectileComponent>(uid, out var projectile) &&
TryComp<ProjectileComponent>(ent, out var projectile) &&
projectile.Shooter == attachedEnt)
{
return;
}
if (args.OurFixtureId != FlyByFixture ||
!_random.Prob(component.Prob))
!_random.Prob(ent.Comp.Prob))
{
return;
}
// Play attached to our entity because the projectile may immediately delete or the likes.
_audio.PlayPredicted(component.Sound, attachedEnt.Value, attachedEnt.Value);
_audio.PlayPredicted(ent.Comp.Sound, attachedEnt.Value, attachedEnt.Value);
}
}

View File

@@ -14,12 +14,12 @@ namespace Content.Client.Weapons.Ranged.Systems;
public sealed partial class GunSystem
{
private void OnAmmoCounterCollect(EntityUid uid, AmmoCounterComponent component, ItemStatusCollectMessage args)
private void OnAmmoCounterCollect(Entity<AmmoCounterComponent> ent, ref ItemStatusCollectMessage args)
{
RefreshControl(uid, component);
RefreshControl(ent);
if (component.Control != null)
args.Controls.Add(component.Control);
if (ent.Comp.Control != null)
args.Controls.Add(ent.Comp.Control);
}
/// <summary>
@@ -27,35 +27,32 @@ public sealed partial class GunSystem
/// </summary>
/// <param name="uid"></param>
/// <param name="component"></param>
private void RefreshControl(EntityUid uid, AmmoCounterComponent? component = null)
private void RefreshControl(Entity<AmmoCounterComponent> ent)
{
if (!Resolve(uid, ref component, false))
return;
component.Control?.Dispose();
component.Control = null;
ent.Comp.Control?.Dispose();
ent.Comp.Control = null;
var ev = new AmmoCounterControlEvent();
RaiseLocalEvent(uid, ev, false);
RaiseLocalEvent(ent, ev, false);
// Fallback to default if none specified
ev.Control ??= new DefaultStatusControl();
component.Control = ev.Control;
UpdateAmmoCount(uid, component);
ent.Comp.Control = ev.Control;
UpdateAmmoCount(ent);
}
private void UpdateAmmoCount(EntityUid uid, AmmoCounterComponent component)
private void UpdateAmmoCount(Entity<AmmoCounterComponent> ent)
{
if (component.Control == null)
if (ent.Comp.Control == null)
return;
var ev = new UpdateAmmoCounterEvent()
{
Control = component.Control
Control = ent.Comp.Control
};
RaiseLocalEvent(uid, ev, false);
RaiseLocalEvent(ent, ev, false);
}
protected override void UpdateAmmoCount(EntityUid uid, bool prediction = true)
@@ -68,7 +65,7 @@ public sealed partial class GunSystem
return;
}
UpdateAmmoCount(uid, clientComp);
UpdateAmmoCount((uid, clientComp));
}
/// <summary>

View File

@@ -12,41 +12,41 @@ public sealed partial class GunSystem
SubscribeLocalEvent<BallisticAmmoProviderComponent, UpdateAmmoCounterEvent>(OnBallisticAmmoCount);
}
private void OnBallisticAmmoCount(EntityUid uid, BallisticAmmoProviderComponent component, UpdateAmmoCounterEvent args)
private void OnBallisticAmmoCount(Entity<BallisticAmmoProviderComponent> ent, ref UpdateAmmoCounterEvent args)
{
if (args.Control is DefaultStatusControl control)
{
control.Update(GetBallisticShots(component), component.Capacity);
control.Update(GetBallisticShots(ent.Comp), ent.Comp.Capacity);
}
}
protected override void Cycle(EntityUid uid, BallisticAmmoProviderComponent component, MapCoordinates coordinates)
protected override void Cycle(Entity<BallisticAmmoProviderComponent> ent, MapCoordinates coordinates)
{
if (!Timing.IsFirstTimePredicted)
return;
EntityUid? ent = null;
EntityUid? ammoEnt = null;
// TODO: Combine with TakeAmmo
if (component.Entities.Count > 0)
if (ent.Comp.Entities.Count > 0)
{
var existing = component.Entities[^1];
component.Entities.RemoveAt(component.Entities.Count - 1);
var existing = ent.Comp.Entities[^1];
ent.Comp.Entities.RemoveAt(ent.Comp.Entities.Count - 1);
Containers.Remove(existing, component.Container);
Containers.Remove(existing, ent.Comp.Container);
EnsureShootable(existing);
}
else if (component.UnspawnedCount > 0)
else if (ent.Comp.UnspawnedCount > 0)
{
component.UnspawnedCount--;
ent = Spawn(component.Proto, coordinates);
EnsureShootable(ent.Value);
ent.Comp.UnspawnedCount--;
ammoEnt = Spawn(ent.Comp.Proto, coordinates);
EnsureShootable(ammoEnt.Value);
}
if (ent != null && IsClientSide(ent.Value))
Del(ent.Value);
if (ammoEnt != null && IsClientSide(ammoEnt.Value))
Del(ammoEnt.Value);
var cycledEvent = new GunCycledEvent();
RaiseLocalEvent(uid, ref cycledEvent);
RaiseLocalEvent(ent, ref cycledEvent);
}
}

View File

@@ -10,11 +10,11 @@ public partial class GunSystem
SubscribeLocalEvent<BasicEntityAmmoProviderComponent, UpdateAmmoCounterEvent>(OnBasicEntityAmmoCount);
}
private void OnBasicEntityAmmoCount(EntityUid uid, BasicEntityAmmoProviderComponent component, UpdateAmmoCounterEvent args)
private void OnBasicEntityAmmoCount(Entity<BasicEntityAmmoProviderComponent> ent, ref UpdateAmmoCounterEvent args)
{
if (args.Control is DefaultStatusControl control && component.Count != null && component.Capacity != null)
if (args.Control is DefaultStatusControl control && ent.Comp.Count != null && ent.Comp.Capacity != null)
{
control.Update(component.Count.Value, component.Capacity.Value);
control.Update(ent.Comp.Count.Value, ent.Comp.Capacity.Value);
}
}
}

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