Compare commits

..

250 Commits

Author SHA1 Message Date
comasqw
544a260f4b Update SpellStorage 2024-11-26 22:21:30 +04:00
Ed
8057fad4d3 Skeleton demiplane antag (#609)
* skeletons! ack

* nerf skeletons
2024-11-24 22:08:05 +03:00
Ed
191112a8a8 Tampestry (#607)
* Update T0_cure_heat.yml

* tampestry

* palisade wip
2024-11-24 18:27:18 +03:00
A.Ne.
406f22de3f closed door interact popup, resolve #564 (#600)
* closed door interact popup, resolve #564

* tweak

* uh

* removed old comment

* Update attributions.yml

* fix

* Update CP14SharedDoorInteractionPopupSystem.cs

* fixes

---------

Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
Co-authored-by: Ed <edwardxperia2000@gmail.com>
2024-11-24 15:54:42 +03:00
Nim
b72c2bfb7b Mosquitoes (#593)
* mos

* more fix

* spawner

* balance review

---------

Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
Co-authored-by: Ed <edwardxperia2000@gmail.com>
2024-11-24 15:22:35 +03:00
A.Ne.
dbc2fd689a follow on use inhand (#606) 2024-11-24 14:36:35 +03:00
Ed
897a9cf683 localization sync (#597)
* localization sync

* init

* Update entities.ftl

* внезапный респрайт лома
2024-11-24 14:33:02 +03:00
A.Ne.
b37113f7f2 add multiple parents supproting (#598)
* add multiple parents supproting

* change yaml path for parsing

* Update yaml_parser.py

* Update localization_helper.py

* fix
2024-11-24 13:40:20 +03:00
Ed
42127c4682 Guards roles and clothings (#608)
* white-blue chainmails for guard

* steal cloaks from mercenaryes to guards

* loadouts

* guard cloak resprite

* job spawners

* skills

* Update migration.yml

* Update crates.yml

* remove recipes

* Update jobs.yml

* Update migration.yml
2024-11-24 13:39:53 +03:00
Ed
ae01ebda0e Merge pull request #595 from crystallpunk-14/ed-20-11-2024-upstream-sync
Upstream sync
2024-11-23 13:44:10 +03:00
Ed
fff97fa819 adapt 2024-11-23 03:12:45 +03:00
Ed
677e5194d6 Merge remote-tracking branch 'upstream/master' into ed-20-11-2024-upstream-sync
# Conflicts:
#	Content.Server/Chemistry/EntitySystems/InjectorSystem.cs
#	Content.Server/Traits/TraitSystem.cs
#	Content.Shared/CCVar/CCVars.cs
#	Content.Shared/Inventory/InventorySystem.Relay.cs
#	Resources/Maps/box.yml
#	Resources/Maps/core.yml
2024-11-23 01:48:24 +03:00
slarticodefast
09ca45a621 Merge staging into master (#33462) 2024-11-22 18:32:30 +01:00
Pieter-Jan Briers
b4ec946bd9 Fix sandbox error with new HWID code. (#33461)
Oops
2024-11-22 18:14:46 +01:00
c4llv07e
646d41d3a7 Add telegram to the server info-links (#33459) 2024-11-22 16:38:41 +01:00
Ed
a6c7bc5993 haha 2024-11-22 14:49:48 +03:00
Ed
66fe15f8c7 Balance tweaks (#604)
* x2 damage

* Update CP14MagicWeaknessSystem.cs

* split cure burn

* split blood purification

* extinguish torch by interact

* Update torch.yml

* Update T0_cure_heat.yml

* fix fireSpread
2024-11-22 14:14:22 +03:00
Vasilis
94ac0b1399 Modern HWID integration (#33265) 2024-11-22 11:08:51 +00:00
PJBot
a3edf04dd3 Automatic changelog update 2024-11-22 03:47:17 +00:00
chromiumboy
403528cbf3 Gas pipe sensors (#33128)
* Initial commit

* Monitored pipe node is now referenced by name

* Review changes

* Simplified construction

* Tweaked deconstruction to match other binary atmos devices

* Helper function removal

* Updated attribution
2024-11-21 21:46:10 -06:00
PJBot
38c70d6c9b Automatic changelog update 2024-11-22 02:57:12 +00:00
DrSmugleaf
5a751a820a Fix admin ghosts not being able to see items in pockets or interact with them (#31076)
* Fix admin ghosts not being able to see items in pouches or interact with them

* fix

* oops

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-11-22 03:56:05 +01:00
Ed
8ad951183c Merge branch 'master' into ed-20-11-2024-upstream-sync 2024-11-21 22:50:28 +03:00
Ed
548433baca Torch craft (#603)
* fix

* Update crates.yml

* loadout torch, flint craft workbench
2024-11-21 22:48:14 +03:00
Ed
96fc06a443 Fire update (#602)
* fire spread fixes and optimization

* fix liquid drops nefty and suffix

* some sharedization

* melee fire extinguish

* clean up fireSpread system

* caution popup

* cuffable zombies

* fix zombie AI

* lighter

* torch integration attempt

* fix torch igniting

* yml tweaks

* bonus flammable damage
2024-11-21 22:01:14 +03:00
Vasilis
4f703ae9ce Fix approval labeler (#33440)
* Fix approval labeler

* Update labeler-review.yml

* Update labeler-review.yml
2024-11-21 17:51:18 +01:00
PJBot
11ee2f9a37 Automatic changelog update 2024-11-21 14:21:21 +00:00
IProduceWidgets
f5930bb566 Coal presents and chrimmas tree options. Presents no longer itemify (#33147)
* Dont ensure ItemComp because it could lead to weirds, and also PickupOrDrop handles non-items already.

* presents and tree

* woops

* reviews a
2024-11-21 15:20:11 +01:00
ThatGuyUSA
0f0b141f21 Syndicate item fix ups (#33435)
tweaks and fixes
2024-11-21 14:22:57 +01:00
Pieter-Jan Briers
75a096b6bd Merge remote-tracking branch 'upstream/master' into 24-10-29-modern-hwid 2024-11-21 01:26:01 +01:00
metalgearsloth
beeffdb5e1 Update to Robust v237.2.0 (#33436) 2024-11-21 11:16:55 +11:00
Pieter-Jan Briers
5c0a32b8b8 Update to Robust v237.2.0 2024-11-21 00:07:12 +01:00
A.Ne.
4fcfab972d demiplane crates fix #589 (#599)
* demiplane crates fix #589

* change component to MapGridComponent

* simplification

* Update CP14DemiplanSystem.Generation.cs

---------

Co-authored-by: Ed <edwardxperia2000@gmail.com>
2024-11-20 23:37:44 +03:00
A.Ne.
4abd793ef9 lockkey system tweak (#601)
* lockkey system tweak

* Update SharedCP14LockKeySystem.cs

---------

Co-authored-by: Ed <edwardxperia2000@gmail.com>
2024-11-20 23:16:00 +03:00
metalgearsloth
cb246f5d7d Set airlock unlit layers as invisible (#32484)
Doesn't really affect anything due to appearance bulldozing this but this aligns with their actual normal states so.
2024-11-20 11:10:05 +03:00
PJBot
f9533a637a Automatic changelog update 2024-11-20 07:56:20 +00:00
metalgearsloth
98caf50626 Ion storm refactor (#33311) 2024-11-20 18:55:12 +11:00
metalgearsloth
a9be561ea7 Merge branch 'master' into ion-storm-refactor 2024-11-20 18:22:08 +11:00
PJBot
4f3ac3ea68 Automatic changelog update 2024-11-20 07:19:45 +00:00
metalgearsloth
59b09383ca Capacitor Crafting Change (#31966) 2024-11-20 18:18:37 +11:00
PJBot
a7003acd77 Automatic changelog update 2024-11-20 05:57:05 +00:00
metalgearsloth
a818c2a134 Temporarily make singularity a bit harder to loose as non-antag (#33358) 2024-11-20 16:55:58 +11:00
metalgearsloth
8acbf87d8f Move PlayerBeforeSpawnEvent and PlayerSpawnCompleteEvent to Shared (#33428) 2024-11-20 16:53:18 +11:00
DrSmugleaf
f5d0e955e3 Fix imports 2024-11-19 21:16:49 -08:00
DrSmugleaf
b8b33b97af Move PlayerBeforeSpawnEvent and PlayerSpawnCompleteEvent to Shared 2024-11-19 21:15:15 -08:00
Saphire
44db676b24 Actually make the emagging popup work properly 2024-11-20 09:32:50 +06:00
PJBot
1b3672e095 Automatic changelog update 2024-11-20 02:06:21 +00:00
metalgearsloth
75acce0d62 Fix: Examine Damage now specifies no damage (#33064) 2024-11-20 13:05:15 +11:00
PJBot
a13a4f7a99 Automatic changelog update 2024-11-20 02:00:37 +00:00
metalgearsloth
b177a1d019 Coloured Light Cost Reduction (#33376) 2024-11-20 12:59:31 +11:00
metalgearsloth
0ec23362fe Merge into master: Increase softcap back to 80 (#33400) (#33419) 2024-11-20 12:56:29 +11:00
PJBot
7e8e2c7212 Automatic changelog update 2024-11-20 01:55:55 +00:00
qwerltaz
e98383d572 Construction menu grid view (#32577)
* button

* implement populate grid view

* tweak min width

* Make grid button toggle visible

* tweak min window size

* fix missing recipe button when mirroring item

* make grid buttons toggleable

* align button texture vertically

* selected grid item has plain color background

* tweak window width so all buttons look good

* rename select method, defer colouring

* get icon better

* whoops

* simpler button toggle

* spritesys frame0, move spritesys

* delete old sprite system refs
2024-11-20 12:54:49 +11:00
beck-thompson
1fa1975e60 Fix toggle verbs (#32138)
First commit

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-11-20 12:53:52 +11:00
Saphire
9c666457c2 Move some of the new singularity code into shared
Hopefully without explosions yay
2024-11-20 07:53:10 +06:00
SlamBamActionman
6e53cd98a4 Add emag functionality 2024-11-20 07:53:10 +06:00
PJBot
35e2c641c1 Automatic changelog update 2024-11-20 01:06:27 +00:00
Plykiya
fdf3df9fbd Crew monitoring crate updated to contain flatpacks, science access instead of engi (#33417)
* Make a crew monitoring crate with flatpacks

* fix image

* migration
2024-11-20 12:05:20 +11:00
Saphire Lattice
eebf06d9d6 Automatically add "Approved" to maintainer PRs (#33337)
* Add an Approved labeler for maintainer PRs

* Be extra safe with conditions
2024-11-20 12:03:52 +11:00
PJBot
ed1ae96fa2 Automatic changelog update 2024-11-20 01:01:45 +00:00
SlamBamActionman
89392e2424 Remove drag & drop dropping items from containers (#32706)
* Initial commit

* Update based on maintainer discussion

* Forgot to remove this woops
2024-11-20 12:00:38 +11:00
PJBot
efa28fc650 Automatic changelog update 2024-11-20 00:58:08 +00:00
MilenVolf
2002de9bb0 Localize planet dataset names (#33398)
* Localize planet names (borer)

* DatasetPrototype -> LocalizedDatasetPrototype

* Apply requested changes
2024-11-20 11:57:43 +11:00
Plykiya
7f5bae99bb Fix security riot crate (#33415)
* move riot crate from security to armory category

* Move riot crate to armory, actually make it require armory access to unlock
2024-11-20 11:57:01 +11:00
Pieter-Jan Briers
c4e2eb9d02 .NET 9 forward compatibility changes (#33421)
This doesn't switch the projects over to .NET 9, but it does make them work on .NET 9 when we decide to switch in the future.
2024-11-20 11:17:45 +11:00
Ed
ed638c94a8 Merge remote-tracking branch 'upstream/stable' into ed-20-11-2024-upstream-sync
# Conflicts:
#	Resources/Maps/cog.yml
2024-11-20 00:34:51 +03:00
Ed
e79b046c4a Magic redesign (#594)
* water spell textures

* water creation spell

* mana consume, and mana glove

* remove mana transfer ring

* Update migration.yml

* copy Wizden loadout PR

* add sprite component to all spells

* spell dummy loadouts

* delete spell traits

* really give spells from loadouts

* update crates fill and demiplane spawners

* beer creation spell, fix passivedamage

* Update PassiveDamageSystem.cs
2024-11-20 00:23:44 +03:00
A.Ne.
ec278dc98f LocaleHelperRefactor (#592)
* refactor

* refactor Prototype class init
2024-11-20 00:22:42 +03:00
PJBot
42ee90e53e Automatic changelog update 2024-11-19 20:32:47 +00:00
ArZarLordOfMango
a949cf33e9 Toggle clothing fix (#32826)
* toggle clothing fix

* some adding
2024-11-19 21:31:37 +01:00
nikthechampiongr
895648aa2c Increase softcap back to 80 (#33400) 2024-11-19 13:13:02 +01:00
PJBot
0e2e6a001f Automatic changelog update 2024-11-19 05:08:09 +00:00
ScarKy0
437a586906 Welded secret doors no longer say they are welded shut. (#33365)
Init
2024-11-18 23:07:02 -06:00
PJBot
10ee37a47c Automatic changelog update 2024-11-19 03:00:50 +00:00
Ilya246
909235cdbe fix viewing nav slowing shuttle down (#32381)
fix
2024-11-19 03:59:42 +01:00
Spessmann
dffece473a Cog update (#33410)
removed fun
2024-11-18 19:17:57 -07:00
Saphire
68eaf6ff25 Bump the failsafe timer down 2024-11-19 08:11:10 +06:00
Spanky
96d2fe477d Service Worker Job Icon Change (#33361)
* Changes the Server Worker job icon to a bowtie.

* Removes grey from icon to better fit existing art.

* Updated ID card sprite.

* Edit respective meta.json files.
2024-11-18 23:37:34 +01:00
faint
79ff990ddf Replace direct uses of GameTicker dictionary with TryGetValue (#33222)
Fix station events schedulers, antag selection and possibly other systems acting weird in a rare scenario
2024-11-18 21:57:50 +03:00
Ed
d67f7619c4 Edgefication (#590)
* bloat

* bloat 2

* bloat 3

* final bloat

* Update icon.ico

* Create integration_test_run.bat

* Arggh

* Disable some tests

* revert roomfill clearexisting changes
2024-11-18 14:40:52 +03:00
SpaceManiac
647db6aa87 Shift air alarm sprites to better reflect their direction (#33379)
* Shift Air Alarm sprites to better reflect their direction

* Fix two frames of west-facing sprite being one pixel off

* Indicate that sprites are no longer exactly tgstation's
2024-11-18 03:51:08 -06:00
PJBot
b0fd9d5a55 Automatic changelog update 2024-11-18 06:33:17 +00:00
Ubaser
824efd4b25 Dim light bulbs (#33383)
add
2024-11-17 23:32:07 -07:00
Justice League
97be261631 Reduced cost of coloured light fixtures 2024-11-17 18:55:57 -05:00
ThatGuyUSA
31d5a66866 Chemical synthesis kit tweak (#33345)
* butcherable surgery caps

* readded cardboard box

* butcherable now?

* butcherable now?

* one day ill figure this out

* changed label of hyperzine syringe

* removed the thing in hats.yml that wasn't supposed to be here

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

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-11-17 23:08:20 +01:00
ScarKy0
e290588624 Changes + Cleanup 2024-11-17 20:23:45 +01:00
ActiveMammmoth
22987fc77f Wizard Summon Guns/Magic (#32692)
* mostly done but there's a bug with spawning

* RandomGlobalSpawnSpellEvent now actually works

* Summon Guns/Magic is working

* Added sound, cap gun, and auto pick up

* Added all requested changes/fixes from reviews

* Halving cooldowns
2024-11-17 17:46:31 +01:00
Spanky
c7f83523ef Packed Update (Christmas Edition) (#33356)
Christmasified Packed station.
2024-11-16 23:35:57 -07:00
Spanky
f484118e2d Omega Update (Christmas Edition) (#33357)
* Christmasift Omega station.

* Add cryosleeper to bridge.
2024-11-16 23:35:41 -07:00
PJBot
96b9d1a714 Automatic changelog update 2024-11-17 03:28:38 +00:00
SpaceRox1244
d9c677e91b Adds paper label visuals to closets and lockers (#33318)
* Modifies label sprites and adds label visuals to closets

* Removes redundant GenericVisualizer component
2024-11-16 21:27:29 -06:00
github-actions[bot]
0991b6bbe8 Update Credits (#33360)
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
2024-11-17 13:03:59 +11:00
ThatGuyUSA
5cd92431b9 Throwing Knife description tweak (#33349)
changed description
2024-11-16 19:03:14 -06:00
Saphire
476f90df09 Fix the component defaults 2024-11-17 04:31:41 +06:00
Saphire
01d6df3d0a Fix Fluent string ID copypaste fail 2024-11-17 04:18:00 +06:00
Saphire
a68c6cb29e Temporarily make singularity a bit harder to loose as non-antag 2024-11-17 04:01:38 +06:00
Emisse
bdab41248d bagel christmas update (#33347) 2024-11-16 01:42:33 -07:00
Southbridge
f071bf65e0 Marathon holiday update (#33335)
* Added holiday decorations, and modified emergency lights to have a better layout.

* Added a couple more emergency lights after finding a couple spots that were wway too dark during testing, also gave the warden a crew monitor
2024-11-15 23:35:23 -07:00
Southbridge
f5b63b8393 Box Holiday Update (#33340)
Added holiday decorations with presents and various fixes
2024-11-15 23:29:23 -07:00
Spessmann
d9a5ffbef4 Cog christmas update (#33344)
christmas updoot
2024-11-15 23:29:09 -07:00
PJBot
47f94d1139 Automatic changelog update 2024-11-16 05:10:35 +00:00
dffdff2423
2c82a2dfc0 Add admin remarks button to lobby (#31761) 2024-11-15 23:09:29 -06:00
PJBot
7077b930f2 Automatic changelog update 2024-11-16 04:31:53 +00:00
K-Dynamic
4f659b9d6d Solar assembly crate buff (#33019)
* more flatpacks + glass

* solar crate price increase

* price increase

* 1250 spesos

* Update Resources/Prototypes/Catalog/Fills/Crates/engines.yml

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-11-15 22:30:47 -06:00
MossyGreySlope
11963e50b1 Fix server crash when the seed extractor is used on the dev map (#33312)
handle event when using seed extractor

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-11-15 21:57:33 -06:00
PJBot
6bcfe6fb3d Automatic changelog update 2024-11-16 03:40:25 +00:00
Saphire Lattice
1f5eb6a08b Fix utensils not being thrown away (#33326) 2024-11-15 21:39:19 -06:00
PJBot
c4e8751ee6 Automatic changelog update 2024-11-16 03:27:57 +00:00
Southbridge
862c2ac858 BRB sign in the Bureaucracy Crate (#33341)
Added the brb sign to the Bureaucracy Crate
2024-11-15 21:26:47 -06:00
PJBot
4426bbe784 Automatic changelog update 2024-11-16 03:26:15 +00:00
Saphire Lattice
e7e1d96051 Improve crayon UI to not be stuck in 1996 (#33101)
* Improve crayon UI to not be stuck in 1996

* Make a horrifying crayon spaghetti

* Crayon

* Undeprecate the crayon, describe the crayon
2024-11-15 21:25:06 -06:00
ScarKy0
3173a3461e S: Awaiting Changes 2024-11-16 02:06:52 +01:00
ScarKy0
7d82a7bf5c Merge branch 'space-wizards:master' into ion-storm-refactor 2024-11-16 00:50:48 +01:00
PJBot
abdefbd622 Automatic changelog update 2024-11-15 23:47:08 +00:00
beck-thompson
da4fa9bea9 Clumsy system refactor (#31147)
* First commit

* Fixes

* Added the noise

* Renames

* Timespan

* Fixed space

* entity -> ent

* This shouldn't work

* opps....

* Datafield name change

* Better comments

* small comment

* Personal skill issue

* Event renames and stuff

* Couple fixes

* Defib ref fixes (Silly me)

* Added clumsy back!

* no hard code clumsy!

* Identity fix

* Event name change

* Comment change

* Function name change

* opp

* Update names

* Damage stuff!

* Fixes!

* Fixes

* opps

* This was hidden away!!

* negative diff feeds me
2024-11-16 00:46:01 +01:00
SpaceRox1244
09d0565413 Adds gorilla gauntlet storage sprite and updates hit sound (#33167)
* Adds storage sprite for gorilla gauntlet

* Specifies a heavier hitsound for gorilla gauntlet

* Modifies gauntlet icon and storage sprite

* Updates credit to my new username
2024-11-15 16:48:28 -06:00
PJBot
6683dc9037 Automatic changelog update 2024-11-15 21:22:14 +00:00
lzk
089f190266 Add succumb action 10 sec delay (#32985)
* Add succumb action 10 sec delay

* add somthing

* add delay to last words as well

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-11-15 22:21:08 +01:00
PJBot
3a6ae97566 Automatic changelog update 2024-11-15 20:53:26 +00:00
RedBookcase
4fc7a4c56e Edited Snow White reaction to output proper amount of drink. (#33331)
Co-authored-by: RedBookcase <Usualmoves@gmail.com>
2024-11-16 02:52:19 +06:00
Ubaser
cf96679d0b New ruin variant (#33332)
add
2024-11-15 12:18:44 -07:00
Ubaser
97ce69fef6 Command external airlocks (#33333)
add
2024-11-15 14:40:52 +01:00
PJBot
e3b611085b Automatic changelog update 2024-11-15 06:56:00 +00:00
Preston Smith
465170f1e1 Prevent Digiboard recycling (#33315)
* add `HighRiskItem` tag

* Correct tags

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

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-11-15 13:54:53 +07:00
Ubaser
b4e0362ed4 Update Core (#33325)
add
2024-11-14 21:59:35 -07:00
Saphire
f75be07a05 Merge hotfix #33287 from "stable" into "master"
- Rule amendment - Remove role abandonment aHelp requirement. (#33287)
- Merges PR #33324
2024-11-15 10:11:54 +06:00
PJBot
606d44bcb0 Automatic changelog update 2024-11-15 03:25:36 +00:00
beck-thompson
dfda557d4b Note expiry time is now relative instead of using timestamps (#33262)
* Add the stuff

* Loc fix

* fixes

* Change
2024-11-15 10:24:27 +07:00
Repo
530a741b7b Rule amendment - Remove role abandonment aHelp requirement. (#33287)
* Role abandonment aHelp requirement.

* disable roundstart chat message

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-11-15 13:54:20 +11:00
scrivoy
d205d17ba3 Meta Station: Add a fully functional TEG room (#32941)
* initial commit

* delete WIP-marker.md

* add TEG room, move gas chambers up

* remove outside burn chamber button, add naming to APC, SMES, Substation

* add HV below TEG Substation

* removed invalids
2024-11-14 18:04:14 -07:00
scrivoy
2c9f2279d6 Marathon Station: Added air alarms to CMO, Surgery, Security Checkpoint (#33213)
* add air alarms to sec checkpoint, cmo and western surgery

* decals and cleanup
2024-11-14 14:30:16 -07:00
ScarKy0
3b9365160c or was it 2024-11-14 18:32:02 +01:00
ScarKy0
9a5c49b961 epic empty commit 2024-11-14 18:31:50 +01:00
ScarKy0
53ce812356 slash 2024-11-14 18:18:39 +01:00
PJBot
0437ec6d56 Automatic changelog update 2024-11-14 17:09:42 +00:00
Pieter-Jan Briers
1bebb3390c Borg type switching. (#32586)
* Borg type switching.

This allows borgs (new spawn or constructed) to select their chassis type on creation, like in SS13. This removes the need for the many different chassis types, and means round-start borgs can actually play the game immediately instead of waiting for science to unlock everything.

New borgs have an additional action that allows them to select their type. This opens a nice window with basic information about the borgs and a select button. Once a type has been selected it is permanent for that borg chassis.

These borg types also immediately start the borg with specific modules, so they do not need to be printed. Additional modules can still be inserted for upgrades, though this is now less critical. The built-in modules cannot be removed, but are shown in the UI.

The modules that each borg type starts with:

* Generic: tools
* Engineering: advanced tools, construction, RCD, cable
* Salvage: Grappling gun, appraisal, mining
* Janitor: cleaning, light replacer
* Medical: treatment
* Service: music, service, clowning

Specialized borgs have 3 additional module slots available on top of the ones listed above, generic borgs have 5.

Borg types are specified in a new BorgTypePrototype. These prototypes specify all information about the borg type. It is assigned to the borg entity through a mix of client side, server, and shared code. Some of the involved components were made networked, others are just ensured they're set on both sides of the wire.

The most gnarly change is the inventory template prototype, which needs to change purely to modify the borg hat offset. I managed to bodge this in with an API that *probably* won't explode for specifically for this use case, but it's still not the most clean of API designs.

Parts for specific borg chassis have been removed (so much deleted YAML) and specialized borg modules that are in the base set of a type have been removed from the exosuit fab as there's no point to printing those.

The ability to "downgrade" a borg so it can select a new chassis, like in SS13, is something that would be nice, but was not high enough priority for me to block the feature on. I did keep it in mind with some of the code, so it may be possible in the future.

There is no fancy animation when selecting borg types like in SS13, because I didn't think it was high priority, and it would add a lot of complex code.

* Fix sandbox failure due to collection expression.

* Module tweak

Fix salvage borg modules still having research/lathe recipes

Engie borg has regular tool module, not advanced.

* Fix inventory system breakage

* Fix migrations

Some things were missing

* Guidebook rewordings & review

* MinWidth on confirm selection button
2024-11-14 11:08:35 -06:00
PJBot
669bc148f9 Automatic changelog update 2024-11-14 16:57:29 +00:00
CheddaCheez
815e37e512 Fix mime broken vow alert (#33303)
Swap VowAlert and VowBrokenAlert on lines 149 and 150 so that the proper alerts are cleared and shown
2024-11-14 19:56:21 +03:00
ScarKy0
ace158df0e Yippee! 2024-11-14 17:53:15 +01:00
ScarKy0
0f30639cf2 progress 2024-11-14 17:21:03 +01:00
ScarKy0
5dbea42751 derelicn't for real 2024-11-14 15:05:14 +01:00
ScarKy0
c86201308a guh 2024-11-14 15:01:04 +01:00
SlamBamActionman
75ec546550 Update Label workflows to use new labels (#33310)
* Update labeler.yml

* Update labeler-needsreview.yml

* Update labeler-staging.yml

* Update labeler-stable.yml

* Update labeler-untriaged.yml

* Create labeler-size.yml

* Update labeler-size.yml

* Update labeler-size.yml

* Update conflict-labeler.yml

* Rename conflict-labeler.yml to labeler-conflict.yml
2024-11-14 14:57:05 +01:00
ScarKy0
e9c66cfe98 Merge branch 'ion-storm-refactor' of https://github.com/ScarKy0/space-station-14 into ion-storm-refactor 2024-11-14 14:55:24 +01:00
ScarKy0
4f754b814b derelictn't (for now) 2024-11-14 14:55:01 +01:00
ScarKy0
755f322e29 Merge branch 'master' into ion-storm-refactor 2024-11-14 14:45:55 +01:00
keronshb
5e54536141 Hotfix 33160 (#33302) 2024-11-14 00:22:10 -05:00
keronshb
f2b7743bdd Half Revert #31978 (#33160)
Hotfix
2024-11-13 23:54:31 -05:00
PJBot
b91c977f7a Automatic changelog update 2024-11-13 23:37:44 +00:00
keronshb
fa3a04a527 Ethereal Jaunt Spell for Wizard & Jaunt ECS (#33201)
* Act

* Adds Jaunt ECS and related prototypes

* Adds jaunt sounds

* Adds enter and exit sound support to polymorphs

* Updates jaunt description

* Adds jaunt action sprite and changes jaunt polymorph to use it

* Adds Jaunt and upgrade to the wizard grimoire

* Makes base mob jaunt parent off of incorporeal and basemob, adds blue ghost sprite for ethereal jaunt

* Update Resources/Locale/en-US/store/spellbook-catalog.ftl

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

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

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

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

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

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

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

* Update Content.Shared/Polymorph/PolymorphPrototype.cs

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

* Update Content.Shared/Polymorph/PolymorphPrototype.cs

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

* removes meta changes

* removes other meta changes

* adds context menu and a description to basemobjaunt

* comments for jaunt component and adds on component shutdown method

* Update Content.Shared/Jaunt/JauntComponent.cs

* Update Content.Shared/Jaunt/JauntComponent.cs

* Update Content.Shared/Jaunt/JauntComponent.cs

* Update Resources/Prototypes/Catalog/spellbook_catalog.yml

---------

Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-11-14 00:36:37 +01:00
PJBot
9643598c70 Automatic changelog update 2024-11-13 23:28:39 +00:00
dffdff2423
51d2b51ad0 tag:with toolshed command (#31751) 2024-11-14 06:27:31 +07:00
Vasilis
6f7066eda8 Separate CCVars into separate files (#33268) 2024-11-13 18:27:55 +00:00
PJBot
c978eefedb Automatic changelog update 2024-11-13 12:31:47 +00:00
Ubaser
8cf279e200 Window sprite tweaks (#33282)
* add

* yes
2024-11-13 13:30:40 +01:00
Spessmann
99b4604d50 Cog fixes (#33285)
fixed the map (for real this time)
2024-11-12 20:07:47 -07:00
PJBot
52c1708117 Automatic changelog update 2024-11-13 03:00:54 +00:00
August Sun
bd2d0ee5e5 Added the ability to microwave inert flesh anomaly cores to turn into an anomalous meat mass (#33223)
* First round of anomaly core functionalities added

* Added sliceTime to anom meat mass and cooked version

* Adds SmokeOnUse component, system and shared system, adds new functions to inert electrical anom core

* Added more functions

* Final touches to branch

* Cleaning up some of the metadata for sprites and component definitions

* PR_Changes_v2_rev.0_Final_FINALFORREALTHISTIME.yml

* Lol jk these goddamn tests why me

* Quick updates based on feedback

* more changes to improve

* additional fixes and edits

* Changed tech core functionality

* added magboot functionality to grav core

* fixed issue with bluespace core sizing

* Reverting changes per request

* extra file to be deleted

* File cleanup

* Update chemicals.ftl

* Update cores.yml

* Update cores.yml

* Update meta.json

* Update chemicals.yml

* Update Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml

Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com>

* Update meal_recipes.yml

* Update cores.yml

---------

Co-authored-by: august-sun <45527070+august.sun@users.noreply.github.com>
Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com>
2024-11-12 20:59:47 -06:00
cohanna
21074bd98d oops 2024-11-12 19:14:28 -07:00
cohanna
e9f6a02f18 un-reverted fixes 2024-11-12 19:05:56 -07:00
SpaceRox1244
af3593a3b7 Adds new sprites for shotgun shell boxes (#33176)
* Adds new sprites for shotgun shell boxes

* Adds second set of mag visuals for slug and uranium casings

* Fixes yaml that I messed up

* Changes credit to new username before merging happens
2024-11-12 17:45:59 -06:00
PJBot
cc3712be0c Automatic changelog update 2024-11-12 21:04:21 +00:00
Repo
ef51700094 Fix unban/editing role bans placed in groups. (#30659)
* On editing a roleban, get all the bans with the same time.

* forgoten newline

* Update to check for player ID too.
2024-11-12 22:03:13 +01:00
MilenVolf
8b154899b5 Allow editing angle of the fired projectile (#33254)
Add Angle datafield to `ProjectileComponent`. It allows to change the angle of the fired projectile
2024-11-12 20:11:02 +01:00
PJBot
8776c71e59 Automatic changelog update 2024-11-12 12:07:50 +00:00
Preston Smith
70e3650246 Make Droppers Respect Closed/Sealed Containers (#33011)
* Make droppers respect closed/sealed

* Combine nested

* Optimize conditions a bit
2024-11-12 19:06:43 +07:00
Ed
a1966d8671 improve BiomeDunGen (#33113)
* improve BiomeDunGen

* forgot lol

* Update DungeonJob.PostGenBiome.cs

* Update DungeonJob.PostGenBiome.cs
2024-11-11 23:19:54 -06:00
Simon
806e1e46cb Separate CCVars into separate files 2024-11-12 03:10:25 +01:00
Pieter-Jan Briers
4f3db43696 Integrate Modern HWID into content
This should be the primary changes for the future-proof "Modern HWID" system implemented into Robust and the auth server.

HWIDs in the database have been given an additional column representing their version, legacy or modern. This is implemented via an EF Core owned entity. By manually setting the column name of the main value column, we can keep DB compatibility and the migration is just adding some type columns.

This new HWID type has to be plumbed through everywhere, resulting in some breaking changes for the DB layer and such.

New bans and player records are placed with the new modern HWID. Old bans are still checked against legacy HWIDs.

Modern HWIDs are presented with a "V2-" prefix to admins, to allow distinguishing them. This is also integrated into the parsing logic for placing new bans.

There's also some code cleanup to reduce copy pasting around the place from my changes.

Requires latest engine to support ImmutableArray<byte> in NetSerializer.
2024-11-12 01:51:54 +01:00
Pieter-Jan Briers
36aceb178c Database SnakeCaseNaming fixes
Fixes formatting of owned entity type property names. These are normally named "FooBar_Baz" by EF Core, but the snake case thing was turning them into "foo_bar__baz". The double underscore is now fixed.

We don't *yet* have any EF Core owned entity in use, but I am planning to add one. I don't know if downstreams are using any so this should still be marked as a breaking change.

Also fixed it creating and dropping a Compiled Regex instance for every name, the regex is now cached (and pregenerated).
2024-11-12 01:51:54 +01:00
PJBot
37958378cb Automatic changelog update 2024-11-11 22:59:38 +00:00
Andrew Montagne
1136200dc8 BUGFIX: Fix APEs being able to be turned on without power (#32493)
Add a check to see the APC is powered before turning the emitter on.
2024-11-11 23:58:31 +01:00
Vasilis
bbdbad5691 Merge Shell Gun Hotfix into Master (#33260) 2024-11-11 18:14:15 +00:00
BramvanZijp
991bbc2d4f Merge branch 'stable' of https://github.com/space-wizards/space-station-14 into HotFixtoMaster 2024-11-11 18:52:49 +01:00
PJBot
b9c2b0c41b Automatic changelog update 2024-11-11 16:13:45 +00:00
BramvanZijp
a138fede2b Make the Flare Gun & Security Shell Gun be unbolted by default. (#33248) 2024-11-11 17:12:36 +01:00
BramvanZijp
197d9e68dc HOTFIX: Fix Security Shell Gun being uncraftable. (#33247)
* Sec Shell Gun Craftability Hotfix

* Capital Fix
2024-11-10 21:22:03 -06:00
leonidussaks
21979a7b5f Fix vape use without check if doafter cancelled (#33245)
vape small fix
2024-11-10 21:17:03 -06:00
Vasilis The Pikachu
63f2c8491c Merge remote-tracking branch 'upstream/staging' into stable-test 2024-11-10 14:10:53 +01:00
scrivoy
9b7200607b Omega Station: Fix Air Alarm in CMO office (#33216)
move air alarm and link devices
2024-11-10 04:40:02 -07:00
Shaddap1
1c8992ffbe Goliath rebalance (#31492)
Update asteroid.yml
2024-11-10 04:28:55 -07:00
Preston Smith
2801ebea8d Optimization! 2024-11-10 00:45:54 -06:00
PJBot
9396ce302a Automatic changelog update 2024-11-10 04:27:57 +00:00
IProduceWidgets
33b780fd1f tweak: weather command tooltip (#33130)
clear weather tip
2024-11-09 22:26:51 -06:00
Armok
d939e991bb Removed bola stam damage (#32989) 2024-11-09 22:32:54 -05:00
github-actions[bot]
d1c66d71e7 Update Credits (#33237)
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
2024-11-10 01:46:58 +01:00
PJBot
675e42df24 Automatic changelog update 2024-11-09 19:30:38 +00:00
ScarKy0
287a9a07de Intellicards now have a doAfter. (#33198)
* init

* cleanup

* Oops! Forgot something

* addressing changes

* guh

* guh 2.0

* some cleanup

* all bless the intellicard

* Yippee

* small locale thing

* changes + small bugfix

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-11-09 13:29:29 -06:00
Flareguy
40044203e6 The Jumpsuit Re-Detailening (#33096)
* jumpsuit detailening

* jumpskirt stuff

* meta.json

* update meta.json

* meta.json fix fix

* meta.json fix fix fix
2024-11-09 16:56:16 +11:00
SlamBamActionman
1e368ae300 Add a Walking alert (#32954)
* Initial commit

* Review feedback changes

* ProtoId

* TempCommit

* First attempt to have client alerts

* Review changes
2024-11-08 18:28:24 -06:00
Errant
b9685850fa Label workflow - staging (#33221) 2024-11-08 22:59:38 +01:00
Errant
41b84fc29d Label workflow - stable (#33220) 2024-11-08 22:59:27 +01:00
PJBot
6ed2ab9e85 Automatic changelog update 2024-11-08 14:51:26 +00:00
Boaz1111
fea5769cc5 dark green jumpsuit recolor, casual green jumpsuits added (#31710)
* green

* fix material arbitrage

* lighter
2024-11-08 15:50:14 +01:00
Jezithyr
84338686a3 Stable Merge (#33218) 2024-11-08 03:46:22 -08:00
deltanedas
80e148c265 cham projector fixes/rewrite (#27111)
* cant disguise to thing in a container

* copy cigarette visualiser

* prevent aghost throwing an error

* make disguises die in space

* fuck it rewrite it to not use polymorph

* fix action troll

* oop

* add vebr

* add access to the components

* 2/3

* fix

* relay damage from disguise to user

* fix integrity

* :trollface:

* :trollface:

* m

* kill integrity

* fix a bug

* review

* remove them from component

* relay flash effect to the disguise

* fix icon being weird

* change method since multiple systems cant handle same network event

* :trollface:

* actually network Disguise real

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-11-08 12:15:41 +01:00
deltanedas
667daa168f pass Actor to cartridge messages (#33210)
* pass Actor to cartridge messages

* NonSerialized gaming

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-11-08 10:38:41 +01:00
FN
012855475e Fix cursed mask bug (#33014)
* One line fix

* Removed redundant using

* Allocation improvement
2024-11-07 20:59:05 -06:00
SolStar
261c18f764 Fix research disk crash (#33205)
* Fix reserach disk crash

* remove extra whitespace

* removed args.handled where args are not handled
2024-11-07 18:29:03 -06:00
Emisse
6a1d631b14 fix invalids core (#33215)
fix invalids
2024-11-07 16:23:28 -07:00
scrivoy
9ee47b927b Core Station: Telecoms air alarm connection to atmos devices (#33214)
link air alarm in coms room
2024-11-07 16:18:12 -07:00
PJBot
03723afee6 Automatic changelog update 2024-11-07 22:03:35 +00:00
PopGamer46
d13765fa5c Fixes Lambordeere bolt buttons not being connected (#33065)
* fix

* oops, used savemap instead of savegrid
2024-11-07 15:02:24 -07:00
K-Dynamic
8257dc87de Meta map: remove arrivals chair, changed maints shotgun to beanbag rounds (#32981)
remove chair + changed maints db to rubber
2024-11-07 15:01:57 -07:00
Spessmann
fc0d85b487 Cog update (fixes) (#33202)
fixed cog again
2024-11-07 15:01:35 -07:00
ScarKy0
379fb4cb6a AI can now speak once more. (#33196)
Yippee
2024-11-07 11:25:16 +01:00
cohanna
6c7336b0a8 oops reverted too much 2024-11-06 22:01:10 -07:00
cohanna
18971f2705 Reverted #31978 2024-11-06 21:39:02 -07:00
PJBot
5b0761dab2 Automatic changelog update 2024-11-06 14:40:22 +00:00
SlamBamActionman
b15d5a7f27 Changes to "Burst" firemode; Drozd, WT550 and C20-r (#31292)
* Initial commit

* Change burst fire variable to be a set value rather than a multiplier
2024-11-06 15:39:16 +01:00
PJBot
e72d63e8a9 Automatic changelog update 2024-11-06 14:28:17 +00:00
BramvanZijp
d588409909 Rework the Flare Gun & add a Security Shell Gun. (#32829)
* Rework flaregun and add security shell gun

* Make flare gun twice as likely to appear in emergency lockers

* Security shell gun can now fire lethal shells like the flare gun used to be able to.

* Rebalance the sec shell gun material cost to primarily be steel instead of plastic

* Define the ShellShotgunLight tag in tags.yml

* Leave the no lethal shells for normal flareguns to a different PR.

* Move a comment to re-run checks.

* Bye bye lethal shells from plastic guns.

* Fix weird whitespace issue.

* Make the sec shell gun inherit the normal flare gun.

* Remove the rack verb and update the sec shell gun description

* Remove the ability to fire lethals from flare guns, pending blowing up the gun

---------

Co-authored-by: SlamBamActionman <slambamactionman@gmail.com>
2024-11-06 15:27:10 +01:00
Plykiya
ed865ae973 make admeme mouse eternally hungry and thirsty by increasing decay rate (#33153)
* make admeme mouse eternally hungry and thirsty by increasing decay rate

* no starving slowdown, fix unnecessary declarations

* REVERTUS DELETUS
2024-11-06 15:19:02 +01:00
Errant
3972a25258 HOTFIX latejoin traitor activations (#33180) 2024-11-05 19:03:14 +01:00
cohanna
69c0f8773f we hate powergaming 2024-11-04 03:10:29 -07:00
Errant
190d965c52 Hotfix add debug info to traitor activation (#33119)
* Add debug messages to traitor activation

* more debug
2024-11-02 17:49:44 +01:00
deathride58
a399c1ec7c Fixes tailthump breaking positional audio by making it mono (#33092) 2024-10-31 22:33:06 +01:00
Justice League
33516b77ed Fixed minor spelling mistake 2024-10-31 13:28:17 -04:00
Preston Smith
03843734e4 Add no damage phrase and logic 2024-10-29 22:25:42 -05:00
The Canned One
b0c5023fda Fix comments in StartIonStormedComponent.cs 2024-10-05 16:48:45 +02:00
The Canned One
b35d2902d4 Fixed cyborgs with the StartIonStormedComponent (which is just the Derelict Cyborg right now) not showing up as 'antag' in the admin player overlay. 2024-10-05 15:09:36 +02:00
The Canned One
d7ed5b4386 remove whitespace. 2024-10-04 08:35:16 +02:00
The Canned One
581a4d14fc minor Derelict Cyborg code changes. 2024-10-04 08:31:55 +02:00
The Canned One
d863e3c5ca Derelict Cyborg no longer appears on the endround 'Game Information' screen. It still appears in the Player Manifest. 2024-10-03 14:03:14 +02:00
The Canned One
00aaffbc00 removed whitespace 2024-10-03 13:51:38 +02:00
The Canned One
3aff20173c Removed 1 line of whitespace. 2024-10-03 13:26:29 +02:00
The Canned One
9dc90a258e Changed DerelictCyborgSpawn event's frequency from 6 to 5, even though i didn't want to. 2024-10-03 12:43:51 +02:00
The Canned One
4b633fde9c Fixed IonStorms sometimes affecting the laws of the current AI and future Cyborgs and AI's, including those in subsequent rounds. 2024-10-03 12:32:50 +02:00
The Canned One
963009a440 Changes IonStorm related code with no gameplay changes. 2024-10-03 11:34:24 +02:00
The Canned One
08de5aeae1 Derelict cyborg minor yaml changes. 2024-10-01 18:07:41 +02:00
The Canned One
d0114d9738 added a code summary 2024-10-01 14:04:33 +02:00
The Canned One
7169788e16 changed very minor stuff with no gameplay alterations. 2024-10-01 11:23:19 +02:00
The Canned One
36390b23d1 Small changes - hopefully good ones. 2024-10-01 11:11:58 +02:00
The Canned One
c6fe5682c2 changed almost nothing 2024-10-01 10:57:51 +02:00
The Canned One
1abc60b995 moved a bit of IonStorm code elsewhere 2024-10-01 10:42:52 +02:00
The Canned One
834b6ebaaa Cleaned up a bit of the Derelict Cyborg code. 2024-10-01 10:02:25 +02:00
Golden Can
eaa6017ada Update Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-09-30 18:32:47 +02:00
Golden Can
e75a71d7d3 Update Content.Server/Silicons/Laws/StartIonStormedSystem.cs
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-09-30 18:31:36 +02:00
Golden Can
0cc1f32b3b Update Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-09-30 18:28:39 +02:00
Golden Can
a4e7ad008c Update Resources/Prototypes/Entities/Mobs/Player/silicon.yml
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-09-30 18:24:38 +02:00
Golden Can
eb1168a831 Update Resources/Prototypes/Entities/Mobs/Player/silicon.yml
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-09-30 18:24:28 +02:00
The Canned One
4c8a235e61 Minor alterations to the Derelict Cyborg and its ghostrole description 2024-09-30 12:12:10 +02:00
The Canned One
f226f28e52 Derelict Cyborgs are now very likely to be affected by ion storms. 2024-09-30 10:03:21 +02:00
The Canned One
bad25e3397 Split part of IonStormRule into IonStormSystem. Added StartIonStormed which also uses IonStormSystem. Added StartIonStormedComponent. Changed stuff related to the Derelict Cyborg. Derelict Cyborg now spawns with a randomized lawset. 2024-09-29 19:01:59 +02:00
The Canned One
964ef33fc7 Fixed accidental removal of something from a meta.json file. 2024-09-28 10:50:33 +02:00
The Canned One
c3fa1b45d0 Added Derelict Cyborg midround event. 2024-09-28 10:18:40 +02:00
The Canned One
602541b548 minor changes to the Derelict Cyborg 2024-09-27 20:11:04 +02:00
The Canned One
30018a3ab1 added Derelict Cyborgs with basic functionality. 2024-09-27 18:10:51 +02:00
TheWaffleJesus
daf674e37b changed capacitor yaml for substation and memory cell 2024-09-08 10:01:08 +01:00
TheWaffleJesus
1f22dfda7b changed from tag to material and added icon for capacitor 2024-09-08 09:34:08 +01:00
1204 changed files with 43005 additions and 14497 deletions

2
.github/labeler.yml vendored
View File

@@ -16,7 +16,7 @@
- changed-files: - changed-files:
- any-glob-to-any-file: '**/*.swsl' - any-glob-to-any-file: '**/*.swsl'
"No C#": "Changes: No C#":
- changed-files: - changed-files:
# Equiv to any-glob-to-all as long as this has one matcher. If ALL changed files are not C# files, then apply label. # Equiv to any-glob-to-all as long as this has one matcher. If ALL changed files are not C# files, then apply label.
- all-globs-to-all-files: "!**/*.cs" - all-globs-to-all-files: "!**/*.cs"

View File

@@ -16,6 +16,6 @@ jobs:
- name: Check for Merge Conflicts - name: Check for Merge Conflicts
uses: eps1lon/actions-label-merge-conflict@v3.0.0 uses: eps1lon/actions-label-merge-conflict@v3.0.0
with: with:
dirtyLabel: "Merge Conflict" dirtyLabel: "S: Merge Conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}" repoToken: "${{ secrets.GITHUB_TOKEN }}"
commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request." commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."

View File

@@ -10,7 +10,7 @@ jobs:
steps: steps:
- uses: actions-ecosystem/action-add-labels@v1 - uses: actions-ecosystem/action-add-labels@v1
with: with:
labels: "Status: Needs Review" labels: "S: Needs Review"
- uses: actions-ecosystem/action-remove-labels@v1 - uses: actions-ecosystem/action-remove-labels@v1
with: with:
labels: "Status: Awaiting Changes" labels: "S: Awaiting Changes"

23
.github/workflows/labeler-review.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: "Labels: Approved"
on:
pull_request_review:
types: [submitted]
jobs:
add_label:
# Change the repository name after you've made sure the team name is correct for your fork!
if: ${{ (github.repository == 'space-wizards/space-station-14') && (github.event.review.state == 'APPROVED') }}
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: tspascoal/get-user-teams-membership@v3
id: checkUserMember
with:
username: ${{ github.actor }}
team: "content-maintainers,junior-maintainers"
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
- if: ${{ steps.checkUserMember.outputs.isTeamMember == 'true' }}
uses: actions-ecosystem/action-add-labels@v1
with:
labels: "S: Approved"

20
.github/workflows/labeler-size.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: "Labels: Size"
on: pull_request_target
jobs:
size-label:
runs-on: ubuntu-latest
steps:
- name: size-label
uses: "pascalgn/size-label-action@v0.5.5"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
with:
# Custom size configuration
sizes: >
{
"0": "XS",
"10": "S",
"30": "M",
"100": "L",
"1000": "XL"
}

16
.github/workflows/labeler-stable.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: "Labels: Branch stable"
on:
pull_request_target:
types:
- opened
branches:
- 'stable'
jobs:
add_label:
runs-on: ubuntu-latest
steps:
- uses: actions-ecosystem/action-add-labels@v1
with:
labels: "Branch: Stable"

16
.github/workflows/labeler-staging.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: "Labels: Branch staging"
on:
pull_request_target:
types:
- opened
branches:
- 'staging'
jobs:
add_label:
runs-on: ubuntu-latest
steps:
- uses: actions-ecosystem/action-add-labels@v1
with:
labels: "Branch: Staging"

View File

@@ -3,6 +3,8 @@
on: on:
issues: issues:
types: [opened] types: [opened]
pull_request_target:
types: [opened]
jobs: jobs:
add_label: add_label:
@@ -11,4 +13,4 @@ jobs:
- uses: actions-ecosystem/action-add-labels@v1 - uses: actions-ecosystem/action-add-labels@v1
if: join(github.event.issue.labels) == '' if: join(github.event.issue.labels) == ''
with: with:
labels: "Status: Untriaged" labels: "S: Untriaged"

View File

@@ -22,11 +22,11 @@ namespace Content.Client.Administration.UI.BanPanel;
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class BanPanel : DefaultWindow public sealed partial class BanPanel : DefaultWindow
{ {
public event Action<string?, (IPAddress, int)?, bool, byte[]?, bool, uint, string, NoteSeverity, string[]?, bool>? BanSubmitted; public event Action<string?, (IPAddress, int)?, bool, ImmutableTypedHwid?, bool, uint, string, NoteSeverity, string[]?, bool>? BanSubmitted;
public event Action<string>? PlayerChanged; public event Action<string>? PlayerChanged;
private string? PlayerUsername { get; set; } private string? PlayerUsername { get; set; }
private (IPAddress, int)? IpAddress { get; set; } private (IPAddress, int)? IpAddress { get; set; }
private byte[]? Hwid { get; set; } private ImmutableTypedHwid? Hwid { get; set; }
private double TimeEntered { get; set; } private double TimeEntered { get; set; }
private uint Multiplier { get; set; } private uint Multiplier { get; set; }
private bool HasBanFlag { get; set; } private bool HasBanFlag { get; set; }
@@ -371,9 +371,8 @@ public sealed partial class BanPanel : DefaultWindow
private void OnHwidChanged() private void OnHwidChanged()
{ {
var hwidString = HwidLine.Text; var hwidString = HwidLine.Text;
var length = 3 * (hwidString.Length / 4) - hwidString.TakeLast(2).Count(c => c == '='); ImmutableTypedHwid? hwid = null;
Hwid = new byte[length]; if (HwidCheckbox.Pressed && !(string.IsNullOrEmpty(hwidString) && LastConnCheckbox.Pressed) && !ImmutableTypedHwid.TryParse(hwidString, out hwid))
if (HwidCheckbox.Pressed && !(string.IsNullOrEmpty(hwidString) && LastConnCheckbox.Pressed) && !Convert.TryFromBase64String(hwidString, Hwid, out _))
{ {
ErrorLevel |= ErrorLevelEnum.Hwid; ErrorLevel |= ErrorLevelEnum.Hwid;
HwidLine.ModulateSelfOverride = Color.Red; HwidLine.ModulateSelfOverride = Color.Red;
@@ -390,7 +389,7 @@ public sealed partial class BanPanel : DefaultWindow
Hwid = null; Hwid = null;
return; return;
} }
Hwid = Convert.FromHexString(hwidString); Hwid = hwid;
} }
private void OnTypeChanged() private void OnTypeChanged()

View File

@@ -8,6 +8,7 @@
<Label Name="ExpiryLabel" Text="{Loc admin-note-editor-expiry-label}" Visible="False" /> <Label Name="ExpiryLabel" Text="{Loc admin-note-editor-expiry-label}" Visible="False" />
<HistoryLineEdit Name="ExpiryLineEdit" PlaceHolder="{Loc admin-note-editor-expiry-placeholder}" <HistoryLineEdit Name="ExpiryLineEdit" PlaceHolder="{Loc admin-note-editor-expiry-placeholder}"
Visible="False" HorizontalExpand="True" /> Visible="False" HorizontalExpand="True" />
<OptionButton Name="ExpiryLengthDropdown" Visible="False" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True"> <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<OptionButton Name="TypeOption" HorizontalAlignment="Center" /> <OptionButton Name="TypeOption" HorizontalAlignment="Center" />

View File

@@ -17,6 +17,17 @@ public sealed partial class NoteEdit : FancyWindow
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IClientConsoleHost _console = default!; [Dependency] private readonly IClientConsoleHost _console = default!;
private enum Multipliers
{
Minutes,
Hours,
Days,
Weeks,
Months,
Years,
Centuries
}
public event Action<int, NoteType, string, NoteSeverity?, bool, DateTime?>? SubmitPressed; public event Action<int, NoteType, string, NoteSeverity?, bool, DateTime?>? SubmitPressed;
public NoteEdit(SharedAdminNote? note, string playerName, bool canCreate, bool canEdit) public NoteEdit(SharedAdminNote? note, string playerName, bool canCreate, bool canEdit)
@@ -31,6 +42,20 @@ public sealed partial class NoteEdit : FancyWindow
ResetSubmitButton(); ResetSubmitButton();
// It's weird to use minutes as the IDs, but it works and makes sense kind of :)
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-minutes"), (int) Multipliers.Minutes);
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-hours"), (int) Multipliers.Hours);
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-days"), (int) Multipliers.Days);
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-weeks"), (int) Multipliers.Weeks);
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-months"), (int) Multipliers.Months);
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-years"), (int) Multipliers.Years);
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-centuries"), (int) Multipliers.Centuries);
ExpiryLengthDropdown.OnItemSelected += OnLengthChanged;
ExpiryLengthDropdown.SelectId((int) Multipliers.Weeks);
ExpiryLineEdit.OnTextChanged += OnTextChanged;
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-note"), (int) NoteType.Note); TypeOption.AddItem(Loc.GetString("admin-note-editor-type-note"), (int) NoteType.Note);
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-message"), (int) NoteType.Message); TypeOption.AddItem(Loc.GetString("admin-note-editor-type-message"), (int) NoteType.Message);
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-watchlist"), (int) NoteType.Watchlist); TypeOption.AddItem(Loc.GetString("admin-note-editor-type-watchlist"), (int) NoteType.Watchlist);
@@ -172,8 +197,9 @@ public sealed partial class NoteEdit : FancyWindow
{ {
ExpiryLabel.Visible = !PermanentCheckBox.Pressed; ExpiryLabel.Visible = !PermanentCheckBox.Pressed;
ExpiryLineEdit.Visible = !PermanentCheckBox.Pressed; ExpiryLineEdit.Visible = !PermanentCheckBox.Pressed;
ExpiryLengthDropdown.Visible = !PermanentCheckBox.Pressed;
ExpiryLineEdit.Text = !PermanentCheckBox.Pressed ? DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") : string.Empty; ExpiryLineEdit.Text = !PermanentCheckBox.Pressed ? 1.ToString() : string.Empty;
} }
private void OnSecretPressed(BaseButton.ButtonEventArgs _) private void OnSecretPressed(BaseButton.ButtonEventArgs _)
@@ -187,6 +213,16 @@ public sealed partial class NoteEdit : FancyWindow
SeverityOption.SelectId(args.Id); SeverityOption.SelectId(args.Id);
} }
private void OnLengthChanged(OptionButton.ItemSelectedEventArgs args)
{
ExpiryLengthDropdown.SelectId(args.Id);
}
private void OnTextChanged(HistoryLineEdit.LineEditEventArgs args)
{
ParseExpiryTime();
}
private void OnSubmitButtonPressed(BaseButton.ButtonEventArgs args) private void OnSubmitButtonPressed(BaseButton.ButtonEventArgs args)
{ {
if (!ParseExpiryTime()) if (!ParseExpiryTime())
@@ -263,13 +299,24 @@ public sealed partial class NoteEdit : FancyWindow
return true; return true;
} }
if (string.IsNullOrWhiteSpace(ExpiryLineEdit.Text) || !DateTime.TryParse(ExpiryLineEdit.Text, out var result) || DateTime.UtcNow > result) if (string.IsNullOrWhiteSpace(ExpiryLineEdit.Text) || !uint.TryParse(ExpiryLineEdit.Text, out var inputInt))
{ {
ExpiryLineEdit.ModulateSelfOverride = Color.Red; ExpiryLineEdit.ModulateSelfOverride = Color.Red;
return false; return false;
} }
ExpiryTime = result.ToUniversalTime(); var mult = ExpiryLengthDropdown.SelectedId switch
{
(int) Multipliers.Minutes => TimeSpan.FromMinutes(1).TotalMinutes,
(int) Multipliers.Hours => TimeSpan.FromHours(1).TotalMinutes,
(int) Multipliers.Days => TimeSpan.FromDays(1).TotalMinutes,
(int) Multipliers.Weeks => TimeSpan.FromDays(7).TotalMinutes,
(int) Multipliers.Months => TimeSpan.FromDays(30).TotalMinutes,
(int) Multipliers.Years => TimeSpan.FromDays(365).TotalMinutes,
(int) Multipliers.Centuries => TimeSpan.FromDays(36525).TotalMinutes,
_ => throw new ArgumentOutOfRangeException(nameof(ExpiryLengthDropdown.SelectedId), "Multiplier out of range :(")
};
ExpiryTime = DateTime.UtcNow.AddMinutes(inputInt * mult);
ExpiryLineEdit.ModulateSelfOverride = null; ExpiryLineEdit.ModulateSelfOverride = null;
return true; return true;
} }

View File

@@ -2,6 +2,7 @@ using System.Linq;
using Content.Shared.Alert; using Content.Shared.Alert;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Shared.GameStates;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -24,8 +25,7 @@ public sealed class ClientAlertsSystem : AlertsSystem
SubscribeLocalEvent<AlertsComponent, LocalPlayerAttachedEvent>(OnPlayerAttached); SubscribeLocalEvent<AlertsComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<AlertsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached); SubscribeLocalEvent<AlertsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<AlertsComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<AlertsComponent, AfterAutoHandleStateEvent>(ClientAlertsHandleState);
} }
protected override void LoadPrototypes() protected override void LoadPrototypes()
{ {
@@ -47,6 +47,16 @@ public sealed class ClientAlertsSystem : AlertsSystem
} }
} }
private void OnHandleState(Entity<AlertsComponent> alerts, ref ComponentHandleState args)
{
if (args.Current is not AlertComponentState cast)
return;
alerts.Comp.Alerts = cast.Alerts;
UpdateHud(alerts);
}
protected override void AfterShowAlert(Entity<AlertsComponent> alerts) protected override void AfterShowAlert(Entity<AlertsComponent> alerts)
{ {
UpdateHud(alerts); UpdateHud(alerts);
@@ -57,11 +67,6 @@ public sealed class ClientAlertsSystem : AlertsSystem
UpdateHud(alerts); UpdateHud(alerts);
} }
private void ClientAlertsHandleState(Entity<AlertsComponent> alerts, ref AfterAutoHandleStateEvent args)
{
UpdateHud(alerts);
}
private void UpdateHud(Entity<AlertsComponent> entity) private void UpdateHud(Entity<AlertsComponent> entity)
{ {
if (_playerManager.LocalEntity == entity.Owner) if (_playerManager.LocalEntity == entity.Owner)

View File

@@ -65,6 +65,7 @@ public sealed class ClientClothingSystem : ClothingSystem
base.Initialize(); base.Initialize();
SubscribeLocalEvent<ClothingComponent, GetEquipmentVisualsEvent>(OnGetVisuals); SubscribeLocalEvent<ClothingComponent, GetEquipmentVisualsEvent>(OnGetVisuals);
SubscribeLocalEvent<ClothingComponent, InventoryTemplateUpdated>(OnInventoryTemplateUpdated);
SubscribeLocalEvent<InventoryComponent, VisualsChangedEvent>(OnVisualsChanged); SubscribeLocalEvent<InventoryComponent, VisualsChangedEvent>(OnVisualsChanged);
SubscribeLocalEvent<SpriteComponent, DidUnequipEvent>(OnDidUnequip); SubscribeLocalEvent<SpriteComponent, DidUnequipEvent>(OnDidUnequip);
@@ -77,11 +78,7 @@ public sealed class ClientClothingSystem : ClothingSystem
if (args.Sprite == null) if (args.Sprite == null)
return; return;
var enumerator = _inventorySystem.GetSlotEnumerator((uid, component)); UpdateAllSlots(uid, component);
while (enumerator.NextItem(out var item, out var slot))
{
RenderEquipment(uid, item, slot.Name, component);
}
// No clothing equipped -> make sure the layer is hidden, though this should already be handled by on-unequip. // No clothing equipped -> make sure the layer is hidden, though this should already be handled by on-unequip.
if (args.Sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer)) if (args.Sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer))
@@ -91,6 +88,23 @@ public sealed class ClientClothingSystem : ClothingSystem
} }
} }
private void OnInventoryTemplateUpdated(Entity<ClothingComponent> ent, ref InventoryTemplateUpdated args)
{
UpdateAllSlots(ent.Owner, clothing: ent.Comp);
}
private void UpdateAllSlots(
EntityUid uid,
InventoryComponent? inventoryComponent = null,
ClothingComponent? clothing = null)
{
var enumerator = _inventorySystem.GetSlotEnumerator((uid, inventoryComponent));
while (enumerator.NextItem(out var item, out var slot))
{
RenderEquipment(uid, item, slot.Name, inventoryComponent, clothingComponent: clothing);
}
}
private void OnGetVisuals(EntityUid uid, ClothingComponent item, GetEquipmentVisualsEvent args) private void OnGetVisuals(EntityUid uid, ClothingComponent item, GetEquipmentVisualsEvent args)
{ {
if (!TryComp(args.Equipee, out InventoryComponent? inventory)) if (!TryComp(args.Equipee, out InventoryComponent? inventory))

View File

@@ -1,15 +1,20 @@
<DefaultWindow xmlns="https://spacestation14.io"> <DefaultWindow xmlns="https://spacestation14.io">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True"> <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.4" Margin="0 0 5 0"> <BoxContainer Orientation="Vertical" MinWidth="243" Margin="0 0 5 0">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 5"> <BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 5">
<LineEdit Name="SearchBar" PlaceHolder="Search" HorizontalExpand="True"/> <LineEdit Name="SearchBar" PlaceHolder="Search" HorizontalExpand="True"/>
<OptionButton Name="OptionCategories" Access="Public" MinSize="130 0"/> <OptionButton Name="OptionCategories" Access="Public" MinSize="130 0"/>
</BoxContainer> </BoxContainer>
<ItemList Name="Recipes" Access="Public" SelectMode="Single" VerticalExpand="True"/> <ItemList Name="Recipes" Access="Public" SelectMode="Single" VerticalExpand="True"/>
<ScrollContainer Name="RecipesGridScrollContainer" VerticalExpand="True" Access="Public" Visible="False">
<GridContainer Name="RecipesGrid" Columns="5" Access="Public"/>
</ScrollContainer>
</BoxContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<BoxContainer Orientation="Horizontal">
<Button Name="MenuGridViewButton" ToggleMode="True" Text="{Loc construction-menu-grid-view}"/>
<Button Name="FavoriteButton" Visible="false"/>
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.6">
<Button Name="FavoriteButton" Visible="false" HorizontalExpand="False"
HorizontalAlignment="Right" Margin="0 0 0 15"/>
<Control> <Control>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="0 0 0 5"> <BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="0 0 0 5">
<BoxContainer Orientation="Horizontal" Align="Center"> <BoxContainer Orientation="Horizontal" Align="Center">

View File

@@ -25,11 +25,16 @@ namespace Content.Client.Construction.UI
OptionButton OptionCategories { get; } OptionButton OptionCategories { get; }
bool EraseButtonPressed { get; set; } bool EraseButtonPressed { get; set; }
bool GridViewButtonPressed { get; set; }
bool BuildButtonPressed { get; set; } bool BuildButtonPressed { get; set; }
ItemList Recipes { get; } ItemList Recipes { get; }
ItemList RecipeStepList { get; } ItemList RecipeStepList { get; }
ScrollContainer RecipesGridScrollContainer { get; }
GridContainer RecipesGrid { get; }
event EventHandler<(string search, string catagory)> PopulateRecipes; event EventHandler<(string search, string catagory)> PopulateRecipes;
event EventHandler<ItemList.Item?> RecipeSelected; event EventHandler<ItemList.Item?> RecipeSelected;
event EventHandler RecipeFavorited; event EventHandler RecipeFavorited;
@@ -72,9 +77,16 @@ namespace Content.Client.Construction.UI
set => EraseButton.Pressed = value; set => EraseButton.Pressed = value;
} }
public bool GridViewButtonPressed
{
get => MenuGridViewButton.Pressed;
set => MenuGridViewButton.Pressed = value;
}
public ConstructionMenu() public ConstructionMenu()
{ {
SetSize = MinSize = new Vector2(720, 320); SetSize = new Vector2(560, 450);
MinSize = new Vector2(560, 320);
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -102,6 +114,9 @@ namespace Content.Client.Construction.UI
EraseButton.OnToggled += args => EraseButtonToggled?.Invoke(this, args.Pressed); EraseButton.OnToggled += args => EraseButtonToggled?.Invoke(this, args.Pressed);
FavoriteButton.OnPressed += args => RecipeFavorited?.Invoke(this, EventArgs.Empty); FavoriteButton.OnPressed += args => RecipeFavorited?.Invoke(this, EventArgs.Empty);
MenuGridViewButton.OnPressed += _ =>
PopulateRecipes?.Invoke(this, (SearchBar.Text, Categories[OptionCategories.SelectedId]));
} }
public event EventHandler? ClearAllGhosts; public event EventHandler? ClearAllGhosts;

View File

@@ -1,7 +1,8 @@
using System.Linq; using System.Linq;
using System.Numerics;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Systems.MenuBar.Widgets; using Content.Client.UserInterface.Systems.MenuBar.Widgets;
using Content.Shared.Construction.Prototypes; using Content.Shared.Construction.Prototypes;
using Content.Shared.Tag;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
@@ -11,7 +12,6 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.Utility; using Robust.Client.Utility;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BaseButton; using static Robust.Client.UserInterface.Controls.BaseButton;
@@ -33,10 +33,12 @@ namespace Content.Client.Construction.UI
private readonly IConstructionMenuView _constructionView; private readonly IConstructionMenuView _constructionView;
private readonly EntityWhitelistSystem _whitelistSystem; private readonly EntityWhitelistSystem _whitelistSystem;
private readonly SpriteSystem _spriteSystem;
private ConstructionSystem? _constructionSystem; private ConstructionSystem? _constructionSystem;
private ConstructionPrototype? _selected; private ConstructionPrototype? _selected;
private List<ConstructionPrototype> _favoritedRecipes = []; private List<ConstructionPrototype> _favoritedRecipes = [];
private Dictionary<string, TextureButton> _recipeButtons = new();
private string _selectedCategory = string.Empty; private string _selectedCategory = string.Empty;
private string _favoriteCatName = "construction-category-favorites"; private string _favoriteCatName = "construction-category-favorites";
private string _forAllCategoryName = "construction-category-all"; private string _forAllCategoryName = "construction-category-all";
@@ -85,6 +87,7 @@ namespace Content.Client.Construction.UI
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_constructionView = new ConstructionMenu(); _constructionView = new ConstructionMenu();
_whitelistSystem = _entManager.System<EntityWhitelistSystem>(); _whitelistSystem = _entManager.System<EntityWhitelistSystem>();
_spriteSystem = _entManager.System<SpriteSystem>();
// This is required so that if we load after the system is initialized, we can bind to it immediately // 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)) if (_systemManager.TryGetEntitySystem<ConstructionSystem>(out var constructionSystem))
@@ -150,12 +153,24 @@ namespace Content.Client.Construction.UI
PopulateInfo(_selected); PopulateInfo(_selected);
} }
private void OnGridViewRecipeSelected(object? sender, ConstructionPrototype? recipe)
{
if (recipe is null)
{
_selected = null;
_constructionView.ClearRecipeInfo();
return;
}
_selected = recipe;
if (_placementManager.IsActive && !_placementManager.Eraser) UpdateGhostPlacement();
PopulateInfo(_selected);
}
private void OnViewPopulateRecipes(object? sender, (string search, string catagory) args) private void OnViewPopulateRecipes(object? sender, (string search, string catagory) args)
{ {
var (search, category) = args; var (search, category) = args;
var recipesList = _constructionView.Recipes;
recipesList.Clear();
var recipes = new List<ConstructionPrototype>(); var recipes = new List<ConstructionPrototype>();
var isEmptyCategory = string.IsNullOrEmpty(category) || category == _forAllCategoryName; var isEmptyCategory = string.IsNullOrEmpty(category) || category == _forAllCategoryName;
@@ -170,7 +185,7 @@ namespace Content.Client.Construction.UI
if (recipe.Hide) if (recipe.Hide)
continue; continue;
if (!recipe.CrystallPunkAllowed) //CrystallPunk clearing recipes if (!recipe.CrystallPunkAllowed) //CrystallEdge clearing recipes
continue; continue;
if (_playerManager.LocalSession == null if (_playerManager.LocalSession == null
@@ -204,12 +219,73 @@ namespace Content.Client.Construction.UI
recipes.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCulture)); recipes.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCulture));
var recipesList = _constructionView.Recipes;
recipesList.Clear();
var recipesGrid = _constructionView.RecipesGrid;
recipesGrid.RemoveAllChildren();
_constructionView.RecipesGridScrollContainer.Visible = _constructionView.GridViewButtonPressed;
_constructionView.Recipes.Visible = !_constructionView.GridViewButtonPressed;
if (_constructionView.GridViewButtonPressed)
{
foreach (var recipe in recipes)
{
var itemButton = new TextureButton
{
TextureNormal = _spriteSystem.Frame0(recipe.Icon),
VerticalAlignment = Control.VAlignment.Center,
Name = recipe.Name,
ToolTip = recipe.Name,
Scale = new Vector2(1.35f),
ToggleMode = true,
};
var itemButtonPanelContainer = new PanelContainer
{
PanelOverride = new StyleBoxFlat { BackgroundColor = StyleNano.ButtonColorDefault },
Children = { itemButton },
};
itemButton.OnToggled += buttonToggledEventArgs =>
{
SelectGridButton(itemButton, buttonToggledEventArgs.Pressed);
if (buttonToggledEventArgs.Pressed &&
_selected != null &&
_recipeButtons.TryGetValue(_selected.Name, out var oldButton))
{
oldButton.Pressed = false;
SelectGridButton(oldButton, false);
}
OnGridViewRecipeSelected(this, buttonToggledEventArgs.Pressed ? recipe : null);
};
recipesGrid.AddChild(itemButtonPanelContainer);
_recipeButtons[recipe.Name] = itemButton;
var isCurrentButtonSelected = _selected == recipe;
itemButton.Pressed = isCurrentButtonSelected;
SelectGridButton(itemButton, isCurrentButtonSelected);
}
}
else
{
foreach (var recipe in recipes) foreach (var recipe in recipes)
{ {
recipesList.Add(GetItem(recipe, recipesList)); recipesList.Add(GetItem(recipe, recipesList));
} }
}
}
// There is apparently no way to set which private void SelectGridButton(TextureButton button, bool select)
{
if (button.Parent is not PanelContainer buttonPanel)
return;
button.Modulate = select ? Color.Green : Color.White;
var buttonColor = select ? StyleNano.ButtonColorDefault : Color.Transparent;
buttonPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = buttonColor };
} }
private void PopulateCategories(string? selectCategory = null) private void PopulateCategories(string? selectCategory = null)
@@ -260,11 +336,10 @@ namespace Content.Client.Construction.UI
private void PopulateInfo(ConstructionPrototype prototype) private void PopulateInfo(ConstructionPrototype prototype)
{ {
var spriteSys = _systemManager.GetEntitySystem<SpriteSystem>();
_constructionView.ClearRecipeInfo(); _constructionView.ClearRecipeInfo();
_constructionView.SetRecipeInfo( _constructionView.SetRecipeInfo(
prototype.Name, prototype.Description, spriteSys.Frame0(prototype.Icon), prototype.Name, prototype.Description, _spriteSystem.Frame0(prototype.Icon),
prototype.Type != ConstructionType.Item, prototype.Type != ConstructionType.Item,
!_favoritedRecipes.Contains(prototype)); !_favoritedRecipes.Contains(prototype));
@@ -277,7 +352,6 @@ namespace Content.Client.Construction.UI
if (_constructionSystem?.GetGuide(prototype) is not { } guide) if (_constructionSystem?.GetGuide(prototype) is not { } guide)
return; return;
var spriteSys = _systemManager.GetEntitySystem<SpriteSystem>();
foreach (var entry in guide.Entries) foreach (var entry in guide.Entries)
{ {
@@ -293,20 +367,20 @@ namespace Content.Client.Construction.UI
// The padding needs to be applied regardless of text length... (See PadLeft documentation) // The padding needs to be applied regardless of text length... (See PadLeft documentation)
text = text.PadLeft(text.Length + entry.Padding); text = text.PadLeft(text.Length + entry.Padding);
var icon = entry.Icon != null ? spriteSys.Frame0(entry.Icon) : Texture.Transparent; var icon = entry.Icon != null ? _spriteSystem.Frame0(entry.Icon) : Texture.Transparent;
stepList.AddItem(text, icon, false); stepList.AddItem(text, icon, false);
} }
} }
private static ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList) private ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList)
{ {
return new(itemList) return new(itemList)
{ {
Metadata = recipe, Metadata = recipe,
Text = recipe.Name, Text = recipe.Name,
Icon = recipe.Icon.Frame0(), Icon = _spriteSystem.Frame0(recipe.Icon),
TooltipEnabled = true, TooltipEnabled = true,
TooltipText = recipe.Description TooltipText = recipe.Description,
}; };
} }

View File

@@ -31,7 +31,7 @@ namespace Content.Client.Crayon.UI
private void PopulateCrayons() private void PopulateCrayons()
{ {
var crayonDecals = _protoManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("crayon")); var crayonDecals = _protoManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("crayon"));
_menu?.Populate(crayonDecals); _menu?.Populate(crayonDecals.ToList());
} }
public override void OnProtoReload(PrototypesReloadedEventArgs args) public override void OnProtoReload(PrototypesReloadedEventArgs args)
@@ -44,6 +44,16 @@ namespace Content.Client.Crayon.UI
PopulateCrayons(); PopulateCrayons();
} }
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
base.ReceiveMessage(message);
if (_menu is null || message is not CrayonUsedMessage crayonMessage)
return;
_menu.AdvanceState(crayonMessage.DrawnDecal);
}
protected override void UpdateState(BoundUserInterfaceState state) protected override void UpdateState(BoundUserInterfaceState state)
{ {
base.UpdateState(state); base.UpdateState(state);

View File

@@ -1,14 +1,13 @@
<DefaultWindow xmlns="https://spacestation14.io" <DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc 'crayon-window-title'}" Title="{Loc 'crayon-window-title'}"
MinSize="250 300" MinSize="450 500"
SetSize="250 300"> SetSize="450 500">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<ColorSelectorSliders Name="ColorSelector" Visible="False" /> <ColorSelectorSliders Name="ColorSelector" Visible="False" />
<LineEdit Name="Search" /> <LineEdit Name="Search" Margin="0 0 0 8" PlaceHolder="{Loc 'crayon-window-placeholder'}" />
<ScrollContainer VerticalExpand="True"> <ScrollContainer VerticalExpand="True">
<GridContainer Name="Grid" Columns="6"> <BoxContainer Name="Grids" Orientation="Vertical">
<!-- Crayon decals get added here by code --> </BoxContainer>
</GridContainer>
</ScrollContainer> </ScrollContainer>
</BoxContainer> </BoxContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -1,8 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Content.Shared.Crayon; using Content.Shared.Crayon;
using Content.Shared.Decals; using Content.Shared.Decals;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
@@ -18,7 +20,12 @@ namespace Content.Client.Crayon.UI
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class CrayonWindow : DefaultWindow public sealed partial class CrayonWindow : DefaultWindow
{ {
private Dictionary<string, Texture>? _decals; [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
private readonly SpriteSystem _spriteSystem = default!;
private Dictionary<string, List<(string Name, Texture Texture)>>? _decals;
private List<string>? _allDecals;
private string? _autoSelected;
private string? _selected; private string? _selected;
private Color _color; private Color _color;
@@ -28,8 +35,10 @@ namespace Content.Client.Crayon.UI
public CrayonWindow() public CrayonWindow()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();
Search.OnTextChanged += _ => RefreshList(); Search.OnTextChanged += SearchChanged;
ColorSelector.OnColorChanged += SelectColor; ColorSelector.OnColorChanged += SelectColor;
} }
@@ -44,25 +53,60 @@ namespace Content.Client.Crayon.UI
private void RefreshList() private void RefreshList()
{ {
// Clear // Clear
Grid.DisposeAllChildren(); Grids.DisposeAllChildren();
if (_decals == null)
if (_decals == null || _allDecals == null)
return; return;
var filter = Search.Text; var filter = Search.Text;
foreach (var (decal, tex) in _decals) var comma = filter.IndexOf(',');
var first = (comma == -1 ? filter : filter[..comma]).Trim();
var names = _decals.Keys.ToList();
names.Sort((a, b) => a == "random" ? 1 : b == "random" ? -1 : a.CompareTo(b));
if (_autoSelected != null && first != _autoSelected && _allDecals.Contains(first))
{ {
if (!decal.Contains(filter)) _selected = first;
_autoSelected = _selected;
OnSelected?.Invoke(_selected);
}
foreach (var categoryName in names)
{
var locName = Loc.GetString("crayon-category-" + categoryName);
var category = _decals[categoryName].Where(d => locName.Contains(first) || d.Name.Contains(first)).ToList();
if (category.Count == 0)
continue; continue;
var label = new Label
{
Text = locName
};
var grid = new GridContainer
{
Columns = 6,
Margin = new Thickness(0, 0, 0, 16)
};
Grids.AddChild(label);
Grids.AddChild(grid);
foreach (var (name, texture) in category)
{
var button = new TextureButton() var button = new TextureButton()
{ {
TextureNormal = tex, TextureNormal = texture,
Name = decal, Name = name,
ToolTip = decal, ToolTip = name,
Modulate = _color, Modulate = _color,
Scale = new System.Numerics.Vector2(2, 2)
}; };
button.OnPressed += ButtonOnPressed; button.OnPressed += ButtonOnPressed;
if (_selected == decal)
if (_selected == name)
{ {
var panelContainer = new PanelContainer() var panelContainer = new PanelContainer()
{ {
@@ -75,20 +119,28 @@ namespace Content.Client.Crayon.UI
button, button,
}, },
}; };
Grid.AddChild(panelContainer); grid.AddChild(panelContainer);
} }
else else
{ {
Grid.AddChild(button); grid.AddChild(button);
} }
} }
} }
}
private void SearchChanged(LineEdit.LineEditEventArgs obj)
{
_autoSelected = ""; // Placeholder to kick off the auto-select in refreshlist()
RefreshList();
}
private void ButtonOnPressed(ButtonEventArgs obj) private void ButtonOnPressed(ButtonEventArgs obj)
{ {
if (obj.Button.Name == null) return; if (obj.Button.Name == null) return;
_selected = obj.Button.Name; _selected = obj.Button.Name;
_autoSelected = null;
OnSelected?.Invoke(_selected); OnSelected?.Invoke(_selected);
RefreshList(); RefreshList();
} }
@@ -107,12 +159,38 @@ namespace Content.Client.Crayon.UI
RefreshList(); RefreshList();
} }
public void Populate(IEnumerable<DecalPrototype> prototypes) public void AdvanceState(string drawnDecal)
{ {
_decals = new Dictionary<string, Texture>(); var filter = Search.Text;
if (!filter.Contains(',') || !filter.Contains(drawnDecal))
return;
var first = filter[..filter.IndexOf(',')].Trim();
if (first.Equals(drawnDecal, StringComparison.InvariantCultureIgnoreCase))
{
Search.Text = filter[(filter.IndexOf(',') + 1)..].Trim();
_autoSelected = first;
}
RefreshList();
}
public void Populate(List<DecalPrototype> prototypes)
{
_decals = [];
_allDecals = [];
prototypes.Sort((a, b) => a.ID.CompareTo(b.ID));
foreach (var decalPrototype in prototypes) foreach (var decalPrototype in prototypes)
{ {
_decals.Add(decalPrototype.ID, decalPrototype.Sprite.Frame0()); var category = "random";
if (decalPrototype.Tags.Count > 1 && decalPrototype.Tags[1].StartsWith("crayon-"))
category = decalPrototype.Tags[1].Replace("crayon-", "");
var list = _decals.GetOrNew(category);
list.Add((decalPrototype.ID, _spriteSystem.Frame0(decalPrototype.Sprite)));
_allDecals.Add(decalPrototype.ID);
} }
RefreshList(); RefreshList();

View File

@@ -124,6 +124,10 @@ public sealed class ColorFlashEffectSystem : SharedColorFlashEffectSystem
continue; continue;
} }
var targetEv = new GetFlashEffectTargetEvent(ent);
RaiseLocalEvent(ent, ref targetEv);
ent = targetEv.Target;
EnsureComp<ColorFlashEffectComponent>(ent, out comp); EnsureComp<ColorFlashEffectComponent>(ent, out comp);
comp.NetSyncEnabled = false; comp.NetSyncEnabled = false;
comp.Color = sprite.Color; comp.Color = sprite.Color;
@@ -132,3 +136,9 @@ public sealed class ColorFlashEffectSystem : SharedColorFlashEffectSystem
} }
} }
} }
/// <summary>
/// Raised on an entity to change the target for a color flash effect.
/// </summary>
[ByRefEvent]
public record struct GetFlashEffectTargetEvent(EntityUid Target);

View File

@@ -165,7 +165,7 @@ namespace Content.Client.Entry
_clientPreferencesManager.Initialize(); _clientPreferencesManager.Initialize();
_euiManager.Initialize(); _euiManager.Initialize();
_voteManager.Initialize(); _voteManager.Initialize();
_userInterfaceManager.SetDefaultTheme(_configManager.GetCVar(CCVars.UIDefaultInterfaceTheme)); _userInterfaceManager.SetDefaultTheme("SS14DefaultTheme");
_userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme)); _userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme));
_documentParsingManager.Initialize(); _documentParsingManager.Initialize();
_titleWindowManager.Initialize(); _titleWindowManager.Initialize();

View File

@@ -168,8 +168,8 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
var parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries); var parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
foreach (var entry in GetSortedEntries(roots)) foreach (var entry in GetSortedEntries(roots))
{ {
if (!entry.CrystallPunkAllowed) continue; //CrystallPunk guidebook filter if (!entry.CrystallPunkAllowed) continue; //CrystallEdge guidebook filter
if (entry.LocFilter is not null && entry.LocFilter != ContentLocalizationManager.Culture) continue; //CrystallPunk guidebook filter if (entry.LocFilter is not null && entry.LocFilter != ContentLocalizationManager.Culture) continue; //CrystallEdge guidebook filter
AddEntry(entry.Id, parent, addedEntries); AddEntry(entry.Id, parent, addedEntries);
} }

View File

@@ -32,7 +32,7 @@ public sealed class GuidebookSystem : EntitySystem
[Dependency] private readonly RgbLightControllerSystem _rgbLightControllerSystem = default!; [Dependency] private readonly RgbLightControllerSystem _rgbLightControllerSystem = default!;
[Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!; [Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!;
[Dependency] private readonly TagSystem _tags = default!; [Dependency] private readonly TagSystem _tags = default!;
[Dependency] private readonly IPrototypeManager _proto = default!; //CrystallPunk guidebook filter [Dependency] private readonly IPrototypeManager _proto = default!; //CrystallEdge guidebook filter
public event Action<List<ProtoId<GuideEntryPrototype>>, public event Action<List<ProtoId<GuideEntryPrototype>>,
List<ProtoId<GuideEntryPrototype>>?, List<ProtoId<GuideEntryPrototype>>?,
@@ -47,7 +47,7 @@ public sealed class GuidebookSystem : EntitySystem
/// <inheritdoc/> /// <inheritdoc/>
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<GuideHelpComponent, MapInitEvent>(OnCrystallPunkMapInit); //CrystallPunk guidebook filter SubscribeLocalEvent<GuideHelpComponent, MapInitEvent>(OnCrystallEdgeMapInit); //CrystallEdge guidebook filter
SubscribeLocalEvent<GuideHelpComponent, GetVerbsEvent<ExamineVerb>>(OnGetVerbs); SubscribeLocalEvent<GuideHelpComponent, GetVerbsEvent<ExamineVerb>>(OnGetVerbs);
SubscribeLocalEvent<GuideHelpComponent, ActivateInWorldEvent>(OnInteract); SubscribeLocalEvent<GuideHelpComponent, ActivateInWorldEvent>(OnInteract);
@@ -57,12 +57,12 @@ public sealed class GuidebookSystem : EntitySystem
OnGuidebookControlsTestGetAlternateVerbs); OnGuidebookControlsTestGetAlternateVerbs);
} }
//CrystallPunk guidebook filter //CrystallEdge guidebook filter
private void OnCrystallPunkMapInit(Entity<GuideHelpComponent> ent, ref MapInitEvent args) private void OnCrystallEdgeMapInit(Entity<GuideHelpComponent> ent, ref MapInitEvent args)
{ {
foreach (var guide in ent.Comp.Guides) foreach (var guide in ent.Comp.Guides)
{ {
var guideProto = _proto.Index<GuideEntryPrototype>(guide); var guideProto = _proto.Index(guide);
if (!guideProto.CrystallPunkAllowed) //REMOVE unnecessary guidebook if (!guideProto.CrystallPunkAllowed) //REMOVE unnecessary guidebook
{ {
RemComp<GuideHelpComponent>(ent); RemComp<GuideHelpComponent>(ent);
@@ -70,7 +70,7 @@ public sealed class GuidebookSystem : EntitySystem
} }
} }
} }
//CrystallPunk guidebook filter end //CrystallEdge guidebook filter end
/// <summary> /// <summary>
/// Gets a user entity to use for verbs and examinations. If the player has no attached entity, this will use a /// Gets a user entity to use for verbs and examinations. If the player has no attached entity, this will use a

View File

@@ -34,6 +34,7 @@ namespace Content.Client.Info
AddInfoButton("server-info-website-button", CCVars.InfoLinksWebsite); AddInfoButton("server-info-website-button", CCVars.InfoLinksWebsite);
AddInfoButton("server-info-wiki-button", CCVars.InfoLinksWiki); AddInfoButton("server-info-wiki-button", CCVars.InfoLinksWiki);
AddInfoButton("server-info-forum-button", CCVars.InfoLinksForum); AddInfoButton("server-info-forum-button", CCVars.InfoLinksForum);
AddInfoButton("server-info-telegram-button", CCVars.InfoLinksTelegram);
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>(); var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
var guidebookButton = new Button() { Text = Loc.GetString("server-info-guidebook-button") }; var guidebookButton = new Button() { Text = Loc.GetString("server-info-guidebook-button") };

View File

@@ -235,9 +235,23 @@ namespace Content.Client.Inventory
EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(GetNetEntity(item.Value), altInteract: true)); EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(GetNetEntity(item.Value), altInteract: true));
} }
protected override void UpdateInventoryTemplate(Entity<InventoryComponent> ent)
{
base.UpdateInventoryTemplate(ent);
if (TryComp(ent, out InventorySlotsComponent? inventorySlots))
{
foreach (var slot in ent.Comp.Slots)
{
if (inventorySlots.SlotData.TryGetValue(slot.Name, out var slotData))
slotData.SlotDef = slot;
}
}
}
public sealed class SlotData public sealed class SlotData
{ {
public readonly SlotDefinition SlotDef; public SlotDefinition SlotDef;
public EntityUid? HeldEntity => Container?.ContainedEntity; public EntityUid? HeldEntity => Container?.ContainedEntity;
public bool Blocked; public bool Blocked;
public bool Highlighted; public bool Highlighted;

View File

@@ -17,6 +17,7 @@ using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Strip.Components; using Content.Shared.Strip.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input; using Robust.Shared.Input;
@@ -29,10 +30,13 @@ namespace Content.Client.Inventory
[UsedImplicitly] [UsedImplicitly]
public sealed class StrippableBoundUserInterface : BoundUserInterface public sealed class StrippableBoundUserInterface : BoundUserInterface
{ {
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!; [Dependency] private readonly IUserInterfaceManager _ui = default!;
private readonly ExamineSystem _examine; private readonly ExamineSystem _examine;
private readonly InventorySystem _inv; private readonly InventorySystem _inv;
private readonly SharedCuffableSystem _cuffable; private readonly SharedCuffableSystem _cuffable;
private readonly StrippableSystem _strippable;
[ViewVariables] [ViewVariables]
private const int ButtonSeparation = 4; private const int ButtonSeparation = 4;
@@ -51,6 +55,8 @@ namespace Content.Client.Inventory
_examine = EntMan.System<ExamineSystem>(); _examine = EntMan.System<ExamineSystem>();
_inv = EntMan.System<InventorySystem>(); _inv = EntMan.System<InventorySystem>();
_cuffable = EntMan.System<SharedCuffableSystem>(); _cuffable = EntMan.System<SharedCuffableSystem>();
_strippable = EntMan.System<StrippableSystem>();
_virtualHiddenEntity = EntMan.SpawnEntity(HiddenPocketEntityId, MapCoordinates.Nullspace); _virtualHiddenEntity = EntMan.SpawnEntity(HiddenPocketEntityId, MapCoordinates.Nullspace);
} }
@@ -198,7 +204,8 @@ namespace Content.Client.Inventory
var entity = container.ContainedEntity; var entity = container.ContainedEntity;
// If this is a full pocket, obscure the real entity // If this is a full pocket, obscure the real entity
if (entity != null && slotDef.StripHidden) // this does not work for modified clients because they are still sent the real entity
if (entity != null && _strippable.IsStripHidden(slotDef, _player.LocalEntity))
entity = _virtualHiddenEntity; entity = _virtualHiddenEntity;
var button = new SlotButton(new SlotData(slotDef, container)); var button = new SlotButton(new SlotData(slotDef, container));

View File

@@ -279,7 +279,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
_profileEditor.OnOpenGuidebook += _guide.OpenHelp; _profileEditor.OnOpenGuidebook += _guide.OpenHelp;
_characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor); _characterSetup = new CharacterSetupGui(_profileEditor);
_characterSetup.CloseButton.OnPressed += _ => _characterSetup.CloseButton.OnPressed += _ =>
{ {

View File

@@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client" xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:style="clr-namespace:Content.Client.Stylesheets" xmlns:style="clr-namespace:Content.Client.Stylesheets"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
VerticalExpand="True"> VerticalExpand="True">
<Control> <Control>
<PanelContainer Name="BackgroundPanel" /> <PanelContainer Name="BackgroundPanel" />
@@ -10,10 +11,15 @@
<Label Text="{Loc 'character-setup-gui-character-setup-label'}" <Label Text="{Loc 'character-setup-gui-character-setup-label'}"
Margin="8 0 0 0" VAlign="Center" Margin="8 0 0 0" VAlign="Center"
StyleClasses="LabelHeadingBigger" /> StyleClasses="LabelHeadingBigger" />
<Button Name="StatsButton" HorizontalExpand="True" <Button Name="StatsButton" HorizontalExpand="True"
Text="{Loc 'character-setup-gui-character-setup-stats-button'}" Text="{Loc 'character-setup-gui-character-setup-stats-button'}"
StyleClasses="ButtonBig" StyleClasses="ButtonBig"
HorizontalAlignment="Right" /> HorizontalAlignment="Right" />
<cc:CommandButton Name="AdminRemarksButton"
Command="adminremarks"
Text="{Loc 'character-setup-gui-character-setup-adminremarks-button'}"
StyleClasses="ButtonBig" />
<Button Name="RulesButton" <Button Name="RulesButton"
Text="{Loc 'character-setup-gui-character-setup-rules-button'}" Text="{Loc 'character-setup-gui-character-setup-rules-button'}"
StyleClasses="ButtonBig"/> StyleClasses="ButtonBig"/>

View File

@@ -1,6 +1,7 @@
using Content.Client.Info; using Content.Client.Info;
using Content.Client.Info.PlaytimeStats; using Content.Client.Info.PlaytimeStats;
using Content.Client.Resources; using Content.Client.Resources;
using Content.Shared.CCVar;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Graphics; using Robust.Client.Graphics;
@@ -8,6 +9,7 @@ using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Client.Lobby.UI namespace Content.Client.Lobby.UI
@@ -18,28 +20,23 @@ namespace Content.Client.Lobby.UI
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class CharacterSetupGui : Control public sealed partial class CharacterSetupGui : Control
{ {
private readonly IClientPreferencesManager _preferencesManager; [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
private readonly IEntityManager _entManager; [Dependency] private readonly IEntityManager _entManager = default!;
private readonly IPrototypeManager _protomanager; [Dependency] private readonly IPrototypeManager _protomanager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly Button _createNewCharacterButton; private readonly Button _createNewCharacterButton;
public event Action<int>? SelectCharacter; public event Action<int>? SelectCharacter;
public event Action<int>? DeleteCharacter; public event Action<int>? DeleteCharacter;
public CharacterSetupGui( public CharacterSetupGui(HumanoidProfileEditor profileEditor)
IEntityManager entManager,
IPrototypeManager protoManager,
IResourceCache resourceCache,
IClientPreferencesManager preferencesManager,
HumanoidProfileEditor profileEditor)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
_preferencesManager = preferencesManager; IoCManager.InjectDependencies(this);
_entManager = entManager;
_protomanager = protoManager;
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png"); var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture var back = new StyleBoxTexture
{ {
Texture = panelTex, Texture = panelTex,
@@ -56,7 +53,7 @@ namespace Content.Client.Lobby.UI
_createNewCharacterButton.OnPressed += args => _createNewCharacterButton.OnPressed += args =>
{ {
preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random()); _preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random());
ReloadCharacterPickers(); ReloadCharacterPickers();
args.Event.Handle(); args.Event.Handle();
}; };
@@ -65,6 +62,8 @@ namespace Content.Client.Lobby.UI
RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open(); RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open();
StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered(); StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered();
_cfg.OnValueChanged(CCVars.SeeOwnNotes, p => AdminRemarksButton.Visible = p, true);
} }
/// <summary> /// <summary>

View File

@@ -36,19 +36,20 @@ public sealed partial class LoadoutContainer : BoxContainer
if (_protoManager.TryIndex(proto, out var loadProto)) if (_protoManager.TryIndex(proto, out var loadProto))
{ {
var ent = _entManager.System<LoadoutSystem>().GetFirstOrNull(loadProto); var ent = loadProto.DummyEntity ?? _entManager.System<LoadoutSystem>().GetFirstOrNull(loadProto);
if (ent == null)
return;
if (ent != null)
{
_entity = _entManager.SpawnEntity(ent, MapCoordinates.Nullspace); _entity = _entManager.SpawnEntity(ent, MapCoordinates.Nullspace);
Sprite.SetEntity(_entity); Sprite.SetEntity(_entity);
var spriteTooltip = new Tooltip(); var spriteTooltip = new Tooltip();
spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent<MetaDataComponent>(_entity.Value).EntityDescription)); spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent<MetaDataComponent>(_entity.Value).EntityDescription));
TooltipSupplier = _ => spriteTooltip; TooltipSupplier = _ => spriteTooltip;
} }
} }
}
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {

View File

@@ -2,7 +2,6 @@ using Content.Client.Message;
using Content.Client.UserInterface.Systems.EscapeMenu; using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Console; using Robust.Client.Console;
using Robust.Client.State;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;

View File

@@ -14,6 +14,7 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
{ {
[Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPlayerManager _player = default!;
[ViewVariables]
protected bool IsActive; protected bool IsActive;
protected virtual SlotFlags TargetSlots => ~SlotFlags.POCKET; protected virtual SlotFlags TargetSlots => ~SlotFlags.POCKET;
@@ -102,7 +103,7 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
args.Components.Add(component); args.Components.Add(component);
} }
private void RefreshOverlay(EntityUid uid) protected void RefreshOverlay(EntityUid uid)
{ {
if (uid != _player.LocalSession?.AttachedEntity) if (uid != _player.LocalSession?.AttachedEntity)
return; return;

View File

@@ -21,9 +21,16 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<ShowHealthBarsComponent, AfterAutoHandleStateEvent>(OnHandleState);
_overlay = new(EntityManager, _prototype); _overlay = new(EntityManager, _prototype);
} }
private void OnHandleState(Entity<ShowHealthBarsComponent> ent, ref AfterAutoHandleStateEvent args)
{
RefreshOverlay(ent);
}
protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component) protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component)
{ {
base.UpdateInternal(component); base.UpdateInternal(component);

View File

@@ -17,6 +17,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
{ {
[Dependency] private readonly IPrototypeManager _prototypeMan = default!; [Dependency] private readonly IPrototypeManager _prototypeMan = default!;
[ViewVariables]
public HashSet<string> DamageContainers = new(); public HashSet<string> DamageContainers = new();
public override void Initialize() public override void Initialize()
@@ -24,6 +25,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
base.Initialize(); base.Initialize();
SubscribeLocalEvent<DamageableComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent); SubscribeLocalEvent<DamageableComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
SubscribeLocalEvent<ShowHealthIconsComponent, AfterAutoHandleStateEvent>(OnHandleState);
} }
protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthIconsComponent> component) protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthIconsComponent> component)
@@ -43,6 +45,11 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
DamageContainers.Clear(); DamageContainers.Clear();
} }
private void OnHandleState(Entity<ShowHealthIconsComponent> ent, ref AfterAutoHandleStateEvent args)
{
RefreshOverlay(ent);
}
private void OnGetStatusIconsEvent(Entity<DamageableComponent> entity, ref GetStatusIconsEvent args) private void OnGetStatusIconsEvent(Entity<DamageableComponent> entity, ref GetStatusIconsEvent args)
{ {
if (!IsActive) if (!IsActive)

View File

@@ -1,9 +1,12 @@
using Content.Shared.Alert;
using Content.Shared.CCVar;
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Physics; using Robust.Client.Physics;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -14,6 +17,8 @@ public sealed class MoverController : SharedMoverController
{ {
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -135,4 +140,15 @@ public sealed class MoverController : SharedMoverController
{ {
return _timing is { IsFirstTimePredicted: true, InSimulation: true }; return _timing is { IsFirstTimePredicted: true, InSimulation: true };
} }
public override void SetSprinting(Entity<InputMoverComponent> entity, ushort subTick, bool walking)
{
// Logger.Info($"[{_gameTiming.CurTick}/{subTick}] Sprint: {enabled}");
base.SetSprinting(entity, subTick, walking);
if (walking && _cfg.GetCVar(CCVars.ToggleWalk))
_alerts.ShowAlert(entity, WalkingAlert, showCooldown: false, autoRemove: false);
else
_alerts.ClearAlert(entity, WalkingAlert);
}
} }

View File

@@ -1,7 +1,10 @@
using Content.Client.Effects;
using Content.Client.Smoking;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Polymorph.Components; using Content.Shared.Polymorph.Components;
using Content.Shared.Polymorph.Systems; using Content.Shared.Polymorph.Systems;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.Player;
namespace Content.Client.Polymorph.Systems; namespace Content.Client.Polymorph.Systems;
@@ -10,14 +13,20 @@ public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
private EntityQuery<AppearanceComponent> _appearanceQuery; private EntityQuery<AppearanceComponent> _appearanceQuery;
private EntityQuery<SpriteComponent> _spriteQuery;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
_appearanceQuery = GetEntityQuery<AppearanceComponent>(); _appearanceQuery = GetEntityQuery<AppearanceComponent>();
_spriteQuery = GetEntityQuery<SpriteComponent>();
SubscribeLocalEvent<ChameleonDisguiseComponent, AfterAutoHandleStateEvent>(OnHandleState); SubscribeLocalEvent<ChameleonDisguiseComponent, AfterAutoHandleStateEvent>(OnHandleState);
SubscribeLocalEvent<ChameleonDisguisedComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<ChameleonDisguisedComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<ChameleonDisguisedComponent, GetFlashEffectTargetEvent>(OnGetFlashEffectTargetEvent);
} }
private void OnHandleState(Entity<ChameleonDisguiseComponent> ent, ref AfterAutoHandleStateEvent args) private void OnHandleState(Entity<ChameleonDisguiseComponent> ent, ref AfterAutoHandleStateEvent args)
@@ -25,9 +34,30 @@ public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
CopyComp<SpriteComponent>(ent); CopyComp<SpriteComponent>(ent);
CopyComp<GenericVisualizerComponent>(ent); CopyComp<GenericVisualizerComponent>(ent);
CopyComp<SolutionContainerVisualsComponent>(ent); CopyComp<SolutionContainerVisualsComponent>(ent);
CopyComp<BurnStateVisualsComponent>(ent);
// reload appearance to hopefully prevent any invisible layers // reload appearance to hopefully prevent any invisible layers
if (_appearanceQuery.TryComp(ent, out var appearance)) if (_appearanceQuery.TryComp(ent, out var appearance))
_appearance.QueueUpdate(ent, appearance); _appearance.QueueUpdate(ent, appearance);
} }
private void OnStartup(Entity<ChameleonDisguisedComponent> ent, ref ComponentStartup args)
{
if (!_spriteQuery.TryComp(ent, out var sprite))
return;
ent.Comp.WasVisible = sprite.Visible;
sprite.Visible = false;
}
private void OnShutdown(Entity<ChameleonDisguisedComponent> ent, ref ComponentShutdown args)
{
if (_spriteQuery.TryComp(ent, out var sprite))
sprite.Visible = ent.Comp.WasVisible;
}
private void OnGetFlashEffectTargetEvent(Entity<ChameleonDisguisedComponent> ent, ref GetFlashEffectTargetEvent args)
{
args.Target = ent.Comp.Disguise;
}
} }

View File

@@ -131,7 +131,8 @@ public sealed partial class BorgMenu : FancyWindow
_modules.Clear(); _modules.Clear();
foreach (var module in chassis.ModuleContainer.ContainedEntities) foreach (var module in chassis.ModuleContainer.ContainedEntities)
{ {
var control = new BorgModuleControl(module, _entity); var moduleComponent = _entity.GetComponent<BorgModuleComponent>(module);
var control = new BorgModuleControl(module, _entity, !moduleComponent.DefaultModule);
control.RemoveButtonPressed += () => control.RemoveButtonPressed += () =>
{ {
RemoveModuleButtonPressed?.Invoke(module); RemoveModuleButtonPressed?.Invoke(module);

View File

@@ -9,7 +9,7 @@ public sealed partial class BorgModuleControl : PanelContainer
{ {
public Action? RemoveButtonPressed; public Action? RemoveButtonPressed;
public BorgModuleControl(EntityUid entity, IEntityManager entityManager) public BorgModuleControl(EntityUid entity, IEntityManager entityManager, bool canRemove)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -20,6 +20,7 @@ public sealed partial class BorgModuleControl : PanelContainer
{ {
RemoveButtonPressed?.Invoke(); RemoveButtonPressed?.Invoke();
}; };
RemoveButton.Visible = canRemove;
} }
} }

View File

@@ -0,0 +1,43 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Title="{Loc 'borg-select-type-menu-title'}"
SetSize="550 300">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
<!-- Left pane: selection of borg type -->
<BoxContainer Orientation="Vertical" MinWidth="200" Margin="2 0">
<Label Text="{Loc 'borg-select-type-menu-available'}" StyleClasses="LabelHeading" />
<ScrollContainer HScrollEnabled="False" VerticalExpand="True">
<BoxContainer Name="SelectionsContainer" Orientation="Vertical" />
</ScrollContainer>
</BoxContainer>
<customControls:VSeparator />
<!-- Right pane: information about selected borg module, confirm button. -->
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="2 0">
<Label Text="{Loc 'borg-select-type-menu-information'}" StyleClasses="LabelHeading" />
<Control VerticalExpand="True">
<controls:Placeholder Name="InfoPlaceholder" PlaceholderText="{Loc 'borg-select-type-menu-select-type'}" />
<BoxContainer Name="InfoContents" Orientation="Vertical" Visible="False">
<BoxContainer Orientation="Horizontal" Margin="0 0 0 4">
<EntityPrototypeView Name="ChassisView" Scale="2,2" />
<Label Name="NameLabel" HorizontalExpand="True" />
</BoxContainer>
<RichTextLabel Name="DescriptionLabel" VerticalExpand="True" VerticalAlignment="Top" />
</BoxContainer>
</Control>
<controls:ConfirmButton Name="ConfirmTypeButton" Text="{Loc 'borg-select-type-menu-confirm'}"
Disabled="True" HorizontalAlignment="Right"
MinWidth="200" />
</BoxContainer>
</BoxContainer>
<controls:StripeBack Margin="0 0 0 4">
<Label Text="{Loc 'borg-select-type-menu-bottom-text'}" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 4 0 4"/>
</controls:StripeBack>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -0,0 +1,81 @@
using System.Linq;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.Guidebook;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Silicons.Borgs;
/// <summary>
/// Menu used by borgs to select their type.
/// </summary>
/// <seealso cref="BorgSelectTypeUserInterface"/>
/// <seealso cref="BorgSwitchableTypeComponent"/>
[GenerateTypedNameReferences]
public sealed partial class BorgSelectTypeMenu : FancyWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private BorgTypePrototype? _selectedBorgType;
public event Action<ProtoId<BorgTypePrototype>>? ConfirmedBorgType;
[ValidatePrototypeId<GuideEntryPrototype>]
private static readonly List<ProtoId<GuideEntryPrototype>> GuidebookEntries = new() { "Cyborgs", "Robotics" };
public BorgSelectTypeMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
var group = new ButtonGroup();
foreach (var borgType in _prototypeManager.EnumeratePrototypes<BorgTypePrototype>().OrderBy(PrototypeName))
{
var button = new Button
{
Text = PrototypeName(borgType),
Group = group,
};
button.OnPressed += _ =>
{
_selectedBorgType = borgType;
UpdateInformation(borgType);
};
SelectionsContainer.AddChild(button);
}
ConfirmTypeButton.OnPressed += ConfirmButtonPressed;
HelpGuidebookIds = GuidebookEntries;
}
private void UpdateInformation(BorgTypePrototype prototype)
{
_selectedBorgType = prototype;
InfoContents.Visible = true;
InfoPlaceholder.Visible = false;
ConfirmTypeButton.Disabled = false;
NameLabel.Text = PrototypeName(prototype);
DescriptionLabel.Text = Loc.GetString($"borg-type-{prototype.ID}-desc");
ChassisView.SetPrototype(prototype.DummyPrototype);
}
private void ConfirmButtonPressed(BaseButton.ButtonEventArgs obj)
{
if (_selectedBorgType == null)
return;
ConfirmedBorgType?.Invoke(_selectedBorgType);
}
private static string PrototypeName(BorgTypePrototype prototype)
{
return Loc.GetString($"borg-type-{prototype.ID}-name");
}
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.Silicons.Borgs.Components;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.Silicons.Borgs;
/// <summary>
/// User interface used by borgs to select their type.
/// </summary>
/// <seealso cref="BorgSelectTypeMenu"/>
/// <seealso cref="BorgSwitchableTypeComponent"/>
/// <seealso cref="BorgSwitchableTypeUiKey"/>
[UsedImplicitly]
public sealed class BorgSelectTypeUserInterface : BoundUserInterface
{
[ViewVariables]
private BorgSelectTypeMenu? _menu;
public BorgSelectTypeUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<BorgSelectTypeMenu>();
_menu.ConfirmedBorgType += prototype => SendMessage(new BorgSelectTypeMessage(prototype));
}
}

View File

@@ -0,0 +1,81 @@
using Content.Shared.Movement.Components;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Silicons.Borgs;
/// <summary>
/// Client side logic for borg type switching. Sets up primarily client-side visual information.
/// </summary>
/// <seealso cref="SharedBorgSwitchableTypeSystem"/>
/// <seealso cref="BorgSwitchableTypeComponent"/>
public sealed class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeSystem
{
[Dependency] private readonly BorgSystem _borgSystem = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BorgSwitchableTypeComponent, AfterAutoHandleStateEvent>(AfterStateHandler);
SubscribeLocalEvent<BorgSwitchableTypeComponent, ComponentStartup>(OnComponentStartup);
}
private void OnComponentStartup(Entity<BorgSwitchableTypeComponent> ent, ref ComponentStartup args)
{
UpdateEntityAppearance(ent);
}
private void AfterStateHandler(Entity<BorgSwitchableTypeComponent> ent, ref AfterAutoHandleStateEvent args)
{
UpdateEntityAppearance(ent);
}
protected override void UpdateEntityAppearance(
Entity<BorgSwitchableTypeComponent> entity,
BorgTypePrototype prototype)
{
if (TryComp(entity, out SpriteComponent? sprite))
{
sprite.LayerSetState(BorgVisualLayers.Body, prototype.SpriteBodyState);
sprite.LayerSetState(BorgVisualLayers.LightStatus, prototype.SpriteToggleLightState);
}
if (TryComp(entity, out BorgChassisComponent? chassis))
{
_borgSystem.SetMindStates(
(entity.Owner, chassis),
prototype.SpriteHasMindState,
prototype.SpriteNoMindState);
if (TryComp(entity, out AppearanceComponent? appearance))
{
// Queue update so state changes apply.
_appearance.QueueUpdate(entity, appearance);
}
}
if (prototype.SpriteBodyMovementState is { } movementState)
{
var spriteMovement = EnsureComp<SpriteMovementComponent>(entity);
spriteMovement.NoMovementLayers.Clear();
spriteMovement.NoMovementLayers["movement"] = new PrototypeLayerData
{
State = prototype.SpriteBodyState,
};
spriteMovement.MovementLayers.Clear();
spriteMovement.MovementLayers["movement"] = new PrototypeLayerData
{
State = movementState,
};
}
else
{
RemComp<SpriteMovementComponent>(entity);
}
base.UpdateEntityAppearance(entity, prototype);
}
}

View File

@@ -92,4 +92,18 @@ public sealed class BorgSystem : SharedBorgSystem
sprite.LayerSetState(MMIVisualLayers.Base, state); sprite.LayerSetState(MMIVisualLayers.Base, state);
} }
} }
/// <summary>
/// Sets the sprite states used for the borg "is there a mind or not" indication.
/// </summary>
/// <param name="borg">The entity and component to modify.</param>
/// <param name="hasMindState">The state to use if the borg has a mind.</param>
/// <param name="noMindState">The state to use if the borg has no mind.</param>
/// <seealso cref="BorgChassisComponent.HasMindState"/>
/// <seealso cref="BorgChassisComponent.NoMindState"/>
public void SetMindStates(Entity<BorgChassisComponent> borg, string hasMindState, string noMindState)
{
borg.Comp.HasMindState = hasMindState;
borg.Comp.NoMindState = noMindState;
}
} }

View File

@@ -1,7 +1,7 @@
using Content.Shared.Singularity.EntitySystems; using Content.Shared.Singularity.EntitySystems;
using Content.Shared.Singularity.Components; using Content.Shared.Singularity.Components;
namespace Content.Client.Singularity.EntitySystems; namespace Content.Client.Singularity.Systems;
/// <summary> /// <summary>
/// The client-side version of <see cref="SharedEventHorizonSystem"/>. /// The client-side version of <see cref="SharedEventHorizonSystem"/>.

View File

@@ -0,0 +1,12 @@
using Content.Shared.Singularity.EntitySystems;
using Content.Shared.Singularity.Components;
namespace Content.Client.Singularity.Systems;
/// <summary>
/// The client-side version of <see cref="SharedSingularityGeneratorSystem"/>.
/// Manages <see cref="SingularityGeneratorComponent"/>s.
/// Exists to make relevant signal handlers (ie: <see cref="SharedSingularityGeneratorSystem.OnEmagged"/>) work on the client.
/// </summary>
public sealed class SingularityGeneratorSystem : SharedSingularityGeneratorSystem
{}

View File

@@ -5,7 +5,7 @@ using Robust.Client.GameObjects;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Client.Singularity.EntitySystems; namespace Content.Client.Singularity.Systems;
/// <summary> /// <summary>
/// The client-side version of <see cref="SharedSingularitySystem"/>. /// The client-side version of <see cref="SharedSingularitySystem"/>.

View File

@@ -307,12 +307,6 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
_entity.GetNetEntity(storageEnt), _entity.GetNetEntity(storageEnt),
new ItemStorageLocation(DraggingRotation, position))); new ItemStorageLocation(DraggingRotation, position)));
} }
else
{
_entity.RaisePredictiveEvent(new StorageRemoveItemEvent(
_entity.GetNetEntity(draggingGhost.Entity),
_entity.GetNetEntity(storageEnt)));
}
_menuDragHelper.EndDrag(); _menuDragHelper.EndDrag();
_container?.BuildItemPieces(); _container?.BuildItemPieces();

View File

@@ -20,7 +20,7 @@ public enum WeaponArcAnimation : byte
None, None,
Thrust, Thrust,
Slash, Slash,
//CrystallPunk Melee upgrade //CrystallEdge Melee upgrade
CPSlash, CPSlash,
CPThrust CPThrust
} }

View File

@@ -43,8 +43,8 @@ public sealed partial class MeleeWeaponSystem
return; return;
} }
var length = 1f; //CrystallPunk Melee upgrade var length = 1f; //CrystallEdgeMelee upgrade
var offset = -1f; //CrystallPunk Melee upgrade var offset = -1f; //CrystallEdge Melee upgrade
var spriteRotation = Angle.Zero; var spriteRotation = Angle.Zero;
if (arcComponent.Animation != WeaponArcAnimation.None if (arcComponent.Animation != WeaponArcAnimation.None
@@ -59,8 +59,8 @@ public sealed partial class MeleeWeaponSystem
if (meleeWeaponComponent.SwingLeft) if (meleeWeaponComponent.SwingLeft)
angle *= -1; angle *= -1;
length = meleeWeaponComponent.CPAnimationLength; //CrystallPunk Melee upgrade length = meleeWeaponComponent.CPAnimationLength; //CrystallEdge Melee upgrade
offset = meleeWeaponComponent.CPAnimationOffset; //CrystallPunk Melee upgrade offset = meleeWeaponComponent.CPAnimationOffset; //CrystallEdge Melee upgrade
} }
sprite.Rotation = localPos.ToWorldAngle(); sprite.Rotation = localPos.ToWorldAngle();
var distance = Math.Clamp(localPos.Length() / 2f, 0.2f, 1f); var distance = Math.Clamp(localPos.Length() / 2f, 0.2f, 1f);
@@ -92,7 +92,7 @@ public sealed partial class MeleeWeaponSystem
if (arcComponent.Fadeout) if (arcComponent.Fadeout)
_animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey); _animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey);
break; break;
//CrystallPunk MeleeUpgrade //CrystallEdge MeleeUpgrade
case WeaponArcAnimation.CPSlash: case WeaponArcAnimation.CPSlash:
track = EnsureComp<TrackUserComponent>(animationUid); track = EnsureComp<TrackUserComponent>(animationUid);
track.User = user; track.User = user;
@@ -107,7 +107,7 @@ public sealed partial class MeleeWeaponSystem
if (arcComponent.Fadeout) if (arcComponent.Fadeout)
_animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey); _animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey);
break; break;
//CrystallPunk MeleeUpgrade end //CrystallEdge MeleeUpgrade end
} }
} }
@@ -207,7 +207,7 @@ public sealed partial class MeleeWeaponSystem
/// </summary> /// </summary>
private Animation GetLungeAnimation(Vector2 direction) private Animation GetLungeAnimation(Vector2 direction)
{ {
const float length = 0.2f; // 0.1 original, CrystallPunk update const float length = 0.2f; // 0.1 original, CrystallEdge update
return new Animation return new Animation
{ {
@@ -221,9 +221,9 @@ public sealed partial class MeleeWeaponSystem
InterpolationMode = AnimationInterpolationMode.Linear, InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames = KeyFrames =
{ {
new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0f), //CrystallPunk MeleeUpgrade new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0f), //CrystallEdge MeleeUpgrade
new AnimationTrackProperty.KeyFrame(direction.Normalized() * 0.15f, length*0.4f), //CrystallPunk MeleeUpgrade new AnimationTrackProperty.KeyFrame(direction.Normalized() * 0.15f, length*0.4f), //CrystallEdge MeleeUpgrade
new AnimationTrackProperty.KeyFrame(Vector2.Zero, length*0.6f) //CrystallPunk MeleeUpgrade new AnimationTrackProperty.KeyFrame(Vector2.Zero, length*0.6f) //CrystallEdge MeleeUpgrade
} }
} }
} }
@@ -253,7 +253,7 @@ public sealed partial class MeleeWeaponSystem
} }
} }
//CrystallPunk MeleeUpgrade start //CrystallEdge MeleeUpgrade start
private Animation CPGetSlashAnimation(SpriteComponent sprite, Angle arc, Angle spriteRotation, float length, float offset = -1f) private Animation CPGetSlashAnimation(SpriteComponent sprite, Angle arc, Angle spriteRotation, float length, float offset = -1f)
{ {
var startRotation = sprite.Rotation + (arc * 0.5f); var startRotation = sprite.Rotation + (arc * 0.5f);
@@ -324,6 +324,6 @@ public sealed partial class MeleeWeaponSystem
}; };
} }
//CrystallPunk MeleeUpgrade end //CrystallEdge MeleeUpgrade end
} }

View File

@@ -157,7 +157,7 @@ public sealed partial class GunSystem : SharedGunSystem
var useKey = gun.UseKey ? EngineKeyFunctions.Use : EngineKeyFunctions.UseSecondary; var useKey = gun.UseKey ? EngineKeyFunctions.Use : EngineKeyFunctions.UseSecondary;
if (_inputSystem.CmdStates.GetState(useKey) != BoundKeyState.Down) if (_inputSystem.CmdStates.GetState(useKey) != BoundKeyState.Down && !gun.BurstActivated)
{ {
if (gun.ShotCounter != 0) if (gun.ShotCounter != 0)
EntityManager.RaisePredictiveEvent(new RequestStopShootEvent { Gun = GetNetEntity(gunUid) }); EntityManager.RaisePredictiveEvent(new RequestStopShootEvent { Gun = GetNetEntity(gunUid) });

View File

@@ -0,0 +1,7 @@
using Content.Shared._CP14.Temperature;
namespace Content.Client._CP14.Temperature;
public sealed partial class CP14ClientFireSpreadSystem : CP14SharedFireSpreadSystem
{
}

View File

@@ -12,6 +12,7 @@ namespace Content.IntegrationTests.Tests.Access
[TestOf(typeof(AccessReaderComponent))] [TestOf(typeof(AccessReaderComponent))]
public sealed class AccessReaderTest public sealed class AccessReaderTest
{ {
/*
[Test] [Test]
public async Task TestProtoTags() public async Task TestProtoTags()
{ {
@@ -123,6 +124,6 @@ namespace Content.IntegrationTests.Tests.Access
}); });
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
} }
*/
} }
} }

View File

@@ -6,7 +6,7 @@ namespace Content.IntegrationTests.Tests.Atmos
[TestFixture] [TestFixture]
[TestOf(typeof(AtmosAlarmThreshold))] [TestOf(typeof(AtmosAlarmThreshold))]
public sealed class AlarmThresholdTest public sealed class AlarmThresholdTest
{ {/*
[TestPrototypes] [TestPrototypes]
private const string Prototypes = @" private const string Prototypes = @"
- type: alarmThreshold - type: alarmThreshold
@@ -135,6 +135,6 @@ namespace Content.IntegrationTests.Tests.Atmos
} }
}); });
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
} }*/
} }
} }

View File

@@ -8,7 +8,7 @@ namespace Content.IntegrationTests.Tests.Atmos
[TestFixture] [TestFixture]
[TestOf(typeof(Atmospherics))] [TestOf(typeof(Atmospherics))]
public sealed class ConstantsTest public sealed class ConstantsTest
{ {/*
[Test] [Test]
public async Task TotalGasesTest() public async Task TotalGasesTest()
{ {
@@ -27,6 +27,6 @@ namespace Content.IntegrationTests.Tests.Atmos
}); });
}); });
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
} }*/
} }
} }

View File

@@ -8,7 +8,7 @@ namespace Content.IntegrationTests.Tests.Atmos
[TestFixture] [TestFixture]
[TestOf(typeof(GasMixture))] [TestOf(typeof(GasMixture))]
public sealed class GasMixtureTest public sealed class GasMixtureTest
{ {/*
[Test] [Test]
public async Task TestMerge() public async Task TestMerge()
{ {
@@ -105,6 +105,6 @@ namespace Content.IntegrationTests.Tests.Atmos
}); });
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
} }*/
} }
} }

View File

@@ -8,7 +8,7 @@ namespace Content.IntegrationTests.Tests.Atmos;
[TestFixture] [TestFixture]
public sealed class GridJoinTest public sealed class GridJoinTest
{ {/*
private const string CanisterProtoId = "AirCanister"; private const string CanisterProtoId = "AirCanister";
[Test] [Test]
@@ -49,5 +49,5 @@ public sealed class GridJoinTest
}); });
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
} }*/
} }

View File

@@ -18,7 +18,7 @@ namespace Content.IntegrationTests.Tests;
[TestFixture] [TestFixture]
public sealed class CargoTest public sealed class CargoTest
{ {/*
private static readonly HashSet<ProtoId<CargoProductPrototype>> Ignored = private static readonly HashSet<ProtoId<CargoProductPrototype>> Ignored =
[ [
// This is ignored because it is explicitly intended to be able to sell for more than it costs. // This is ignored because it is explicitly intended to be able to sell for more than it costs.
@@ -265,5 +265,5 @@ public sealed class CargoTest
}); });
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
} }*/
} }

View File

@@ -32,9 +32,9 @@ namespace Content.IntegrationTests.Tests.Commands
// No bans on record // No bans on record
Assert.Multiple(async () => Assert.Multiple(async () =>
{ {
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null); Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null); Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null);
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Is.Empty); Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Is.Empty);
}); });
// Try to pardon a ban that does not exist // Try to pardon a ban that does not exist
@@ -43,9 +43,9 @@ namespace Content.IntegrationTests.Tests.Commands
// Still no bans on record // Still no bans on record
Assert.Multiple(async () => Assert.Multiple(async () =>
{ {
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null); Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null); Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null);
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Is.Empty); Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Is.Empty);
}); });
var banReason = "test"; var banReason = "test";
@@ -57,9 +57,9 @@ namespace Content.IntegrationTests.Tests.Commands
// Should have one ban on record now // Should have one ban on record now
Assert.Multiple(async () => Assert.Multiple(async () =>
{ {
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Not.Null); Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Not.Null);
Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null); Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null);
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1)); Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
}); });
await pair.RunTicksSync(5); await pair.RunTicksSync(5);
@@ -70,13 +70,13 @@ namespace Content.IntegrationTests.Tests.Commands
await server.WaitPost(() => sConsole.ExecuteCommand("pardon 2")); await server.WaitPost(() => sConsole.ExecuteCommand("pardon 2"));
// The existing ban is unaffected // The existing ban is unaffected
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Not.Null); Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Not.Null);
var ban = await sDatabase.GetServerBanAsync(1); var ban = await sDatabase.GetServerBanAsync(1);
Assert.Multiple(async () => Assert.Multiple(async () =>
{ {
Assert.That(ban, Is.Not.Null); Assert.That(ban, Is.Not.Null);
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1)); Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
// Check that it matches // Check that it matches
Assert.That(ban.Id, Is.EqualTo(1)); Assert.That(ban.Id, Is.EqualTo(1));
@@ -95,7 +95,7 @@ namespace Content.IntegrationTests.Tests.Commands
await server.WaitPost(() => sConsole.ExecuteCommand("pardon 1")); await server.WaitPost(() => sConsole.ExecuteCommand("pardon 1"));
// No bans should be returned // No bans should be returned
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null); Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
// Direct id lookup returns a pardoned ban // Direct id lookup returns a pardoned ban
var pardonedBan = await sDatabase.GetServerBanAsync(1); var pardonedBan = await sDatabase.GetServerBanAsync(1);
@@ -105,7 +105,7 @@ namespace Content.IntegrationTests.Tests.Commands
Assert.That(pardonedBan, Is.Not.Null); Assert.That(pardonedBan, Is.Not.Null);
// The list is still returned since that ignores pardons // The list is still returned since that ignores pardons
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1)); Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
Assert.That(pardonedBan.Id, Is.EqualTo(1)); Assert.That(pardonedBan.Id, Is.EqualTo(1));
Assert.That(pardonedBan.UserId, Is.EqualTo(clientId)); Assert.That(pardonedBan.UserId, Is.EqualTo(clientId));
@@ -133,13 +133,13 @@ namespace Content.IntegrationTests.Tests.Commands
Assert.Multiple(async () => Assert.Multiple(async () =>
{ {
// No bans should be returned // No bans should be returned
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null); Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
// Direct id lookup returns a pardoned ban // Direct id lookup returns a pardoned ban
Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null); Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null);
// The list is still returned since that ignores pardons // The list is still returned since that ignores pardons
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1)); Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
}); });
// Reconnect client. Slightly faster than dirtying the pair. // Reconnect client. Slightly faster than dirtying the pair.

View File

@@ -18,7 +18,7 @@ namespace Content.IntegrationTests.Tests.Disposal
[TestOf(typeof(DisposalEntryComponent))] [TestOf(typeof(DisposalEntryComponent))]
[TestOf(typeof(DisposalUnitComponent))] [TestOf(typeof(DisposalUnitComponent))]
public sealed class DisposalUnitTest public sealed class DisposalUnitTest
{ {/*
[Reflect(false)] [Reflect(false)]
private sealed class DisposalUnitTestSystem : EntitySystem private sealed class DisposalUnitTestSystem : EntitySystem
{ {
@@ -242,6 +242,6 @@ namespace Content.IntegrationTests.Tests.Disposal
}); });
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
} }*/
} }
} }

View File

@@ -6,7 +6,7 @@ using Content.Shared.Wires;
namespace Content.IntegrationTests.Tests.EncryptionKeys; namespace Content.IntegrationTests.Tests.EncryptionKeys;
public sealed class RemoveEncryptionKeys : InteractionTest public sealed class RemoveEncryptionKeys : InteractionTest
{ {/*
[Test] [Test]
public async Task HeadsetKeys() public async Task HeadsetKeys()
{ {
@@ -108,5 +108,5 @@ public sealed class RemoveEncryptionKeys : InteractionTest
AssertPrototype("TelecomServerFilled"); AssertPrototype("TelecomServerFilled");
await InteractUsing(Pry); await InteractUsing(Pry);
AssertPrototype("MachineFrame"); AssertPrototype("MachineFrame");
} }*/
} }

View File

@@ -8,7 +8,7 @@ namespace Content.IntegrationTests.Tests.Internals;
[TestFixture] [TestFixture]
[TestOf(typeof(InternalsSystem))] [TestOf(typeof(InternalsSystem))]
public sealed class AutoInternalsTests public sealed class AutoInternalsTests
{ {/*
[Test] [Test]
public async Task TestInternalsAutoActivateInSpaceForStationSpawn() public async Task TestInternalsAutoActivateInSpaceForStationSpawn()
{ {
@@ -81,5 +81,5 @@ public sealed class AutoInternalsTests
components: components:
- type: Loadout - type: Loadout
prototypes: [InternalsDummyGear] prototypes: [InternalsDummyGear]
"; ";*/
} }

View File

@@ -8,7 +8,7 @@ using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests; namespace Content.IntegrationTests.Tests;
public sealed class MachineBoardTest public sealed class MachineBoardTest
{ {/*
/// <summary> /// <summary>
/// A list of machine boards that can be ignored by this test. /// A list of machine boards that can be ignored by this test.
/// </summary> /// </summary>
@@ -138,5 +138,5 @@ public sealed class MachineBoardTest
}); });
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
} }*/
} }

View File

@@ -7,7 +7,7 @@ using Robust.Shared.GameObjects;
namespace Content.IntegrationTests.Tests.Payload; namespace Content.IntegrationTests.Tests.Payload;
public sealed class ModularGrenadeTests : InteractionTest public sealed class ModularGrenadeTests : InteractionTest
{ {/*
public const string Trigger = "TimerTrigger"; public const string Trigger = "TimerTrigger";
public const string Payload = "ExplosivePayload"; public const string Payload = "ExplosivePayload";
@@ -70,5 +70,5 @@ public sealed class ModularGrenadeTests : InteractionTest
// Grenade has exploded. // Grenade has exploded.
await RunTicks(30); await RunTicks(30);
AssertDeleted(); AssertDeleted();
} }*/
} }

View File

@@ -44,15 +44,15 @@ namespace Content.IntegrationTests.Tests
}; };
private static readonly string[] GameMaps = private static readonly string[] GameMaps =
{//CrystallPunk Map replacement {//CrystallEdge Map replacement
"Dev", "Dev",
"CentComm", "CentComm",
"MeteorArena", "MeteorArena",
//CrystallPunk maps //CrystallEdge maps
"Village", "Village",
"Island", "Island",
//CrystallPunk Map replacement end //CrystallEdge Map replacement end
}; };
/// <summary> /// <summary>

View File

@@ -15,7 +15,7 @@ namespace Content.IntegrationTests.Tests.Power
{ {
[TestFixture] [TestFixture]
public sealed class PowerTest public sealed class PowerTest
{ {/*
[TestPrototypes] [TestPrototypes]
private const string Prototypes = @" private const string Prototypes = @"
- type: entity - type: entity
@@ -1333,7 +1333,7 @@ namespace Content.IntegrationTests.Tests.Power
}); });
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
} }*/
} }
} }

View File

@@ -9,7 +9,7 @@ namespace Content.IntegrationTests.Tests;
[TestFixture] [TestFixture]
public sealed class ResearchTest public sealed class ResearchTest
{ {/*
[Test] [Test]
public async Task DisciplineValidTierPrerequesitesTest() public async Task DisciplineValidTierPrerequesitesTest()
{ {
@@ -117,5 +117,5 @@ public sealed class ResearchTest
}); });
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
} }*/
} }

View File

@@ -19,7 +19,7 @@ namespace Content.IntegrationTests.Tests
[TestOf(typeof(VendingMachineRestockComponent))] [TestOf(typeof(VendingMachineRestockComponent))]
[TestOf(typeof(VendingMachineSystem))] [TestOf(typeof(VendingMachineSystem))]
public sealed class VendingMachineRestockTest : EntitySystem public sealed class VendingMachineRestockTest : EntitySystem
{ {/*
[TestPrototypes] [TestPrototypes]
private const string Prototypes = @" private const string Prototypes = @"
- type: entity - type: entity
@@ -369,7 +369,7 @@ namespace Content.IntegrationTests.Tests
}); });
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
} }*/
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class ModernHwid : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "hwid_type",
table: "server_role_ban",
type: "integer",
nullable: true,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "hwid_type",
table: "server_ban",
type: "integer",
nullable: true,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "last_seen_hwid_type",
table: "player",
type: "integer",
nullable: true,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "hwid_type",
table: "connection_log",
type: "integer",
nullable: true,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "hwid_type",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "hwid_type",
table: "server_ban");
migrationBuilder.DropColumn(
name: "last_seen_hwid_type",
table: "player");
migrationBuilder.DropColumn(
name: "hwid_type",
table: "connection_log");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class ConnectionTrust : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<float>(
name: "trust",
table: "connection_log",
type: "real",
nullable: false,
defaultValue: 0f);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "trust",
table: "connection_log");
}
}
}

View File

@@ -512,20 +512,6 @@ namespace Content.Server.Database.Migrations.Postgres
b.ToTable("assigned_user_id", (string)null); b.ToTable("assigned_user_id", (string)null);
}); });
modelBuilder.Entity("Content.Server.Database.Blacklist",
b =>
{
b.Property<Guid>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("UserId")
.HasName("PK_blacklist");
b.ToTable("blacklist", (string) null);
});
modelBuilder.Entity("Content.Server.Database.BanTemplate", b => modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -571,6 +557,19 @@ namespace Content.Server.Database.Migrations.Postgres
b.ToTable("ban_template", (string)null); b.ToTable("ban_template", (string)null);
}); });
modelBuilder.Entity("Content.Server.Database.Blacklist", b =>
{
b.Property<Guid>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("UserId")
.HasName("PK_blacklist");
b.ToTable("blacklist", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -589,10 +588,6 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("smallint") .HasColumnType("smallint")
.HasColumnName("denied"); .HasColumnName("denied");
b.Property<byte[]>("HWId")
.HasColumnType("bytea")
.HasColumnName("hwid");
b.Property<int>("ServerId") b.Property<int>("ServerId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
@@ -603,6 +598,10 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("time"); .HasColumnName("time");
b.Property<float>("Trust")
.HasColumnType("real")
.HasColumnName("trust");
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasColumnName("user_id"); .HasColumnName("user_id");
@@ -718,10 +717,6 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("inet") .HasColumnType("inet")
.HasColumnName("last_seen_address"); .HasColumnName("last_seen_address");
b.Property<byte[]>("LastSeenHWId")
.HasColumnType("bytea")
.HasColumnName("last_seen_hwid");
b.Property<DateTime>("LastSeenTime") b.Property<DateTime>("LastSeenTime")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("last_seen_time"); .HasColumnName("last_seen_time");
@@ -1058,10 +1053,6 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("expiration_time"); .HasColumnName("expiration_time");
b.Property<byte[]>("HWId")
.HasColumnType("bytea")
.HasColumnName("hwid");
b.Property<bool>("Hidden") b.Property<bool>("Hidden")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("hidden"); .HasColumnName("hidden");
@@ -1192,10 +1183,6 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("expiration_time"); .HasColumnName("expiration_time");
b.Property<byte[]>("HWId")
.HasColumnType("bytea")
.HasColumnName("hwid");
b.Property<bool>("Hidden") b.Property<bool>("Hidden")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("hidden"); .HasColumnName("hidden");
@@ -1637,6 +1624,34 @@ namespace Content.Server.Database.Migrations.Postgres
.IsRequired() .IsRequired()
.HasConstraintName("FK_connection_log_server_server_id"); .HasConstraintName("FK_connection_log_server_server_id");
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
{
b1.Property<int>("ConnectionLogId")
.HasColumnType("integer")
.HasColumnName("connection_log_id");
b1.Property<byte[]>("Hwid")
.IsRequired()
.HasColumnType("bytea")
.HasColumnName("hwid");
b1.Property<int>("Type")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasColumnName("hwid_type");
b1.HasKey("ConnectionLogId");
b1.ToTable("connection_log");
b1.WithOwner()
.HasForeignKey("ConnectionLogId")
.HasConstraintName("FK_connection_log_connection_log_connection_log_id");
});
b.Navigation("HWId");
b.Navigation("Server"); b.Navigation("Server");
}); });
@@ -1652,6 +1667,37 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Profile"); b.Navigation("Profile");
}); });
modelBuilder.Entity("Content.Server.Database.Player", b =>
{
b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 =>
{
b1.Property<int>("PlayerId")
.HasColumnType("integer")
.HasColumnName("player_id");
b1.Property<byte[]>("Hwid")
.IsRequired()
.HasColumnType("bytea")
.HasColumnName("last_seen_hwid");
b1.Property<int>("Type")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasColumnName("last_seen_hwid_type");
b1.HasKey("PlayerId");
b1.ToTable("player");
b1.WithOwner()
.HasForeignKey("PlayerId")
.HasConstraintName("FK_player_player_player_id");
});
b.Navigation("LastSeenHWId");
});
modelBuilder.Entity("Content.Server.Database.Profile", b => modelBuilder.Entity("Content.Server.Database.Profile", b =>
{ {
b.HasOne("Content.Server.Database.Preference", "Preference") b.HasOne("Content.Server.Database.Preference", "Preference")
@@ -1746,8 +1792,36 @@ namespace Content.Server.Database.Migrations.Postgres
.HasForeignKey("RoundId") .HasForeignKey("RoundId")
.HasConstraintName("FK_server_ban_round_round_id"); .HasConstraintName("FK_server_ban_round_round_id");
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
{
b1.Property<int>("ServerBanId")
.HasColumnType("integer")
.HasColumnName("server_ban_id");
b1.Property<byte[]>("Hwid")
.IsRequired()
.HasColumnType("bytea")
.HasColumnName("hwid");
b1.Property<int>("Type")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasColumnName("hwid_type");
b1.HasKey("ServerBanId");
b1.ToTable("server_ban");
b1.WithOwner()
.HasForeignKey("ServerBanId")
.HasConstraintName("FK_server_ban_server_ban_server_ban_id");
});
b.Navigation("CreatedBy"); b.Navigation("CreatedBy");
b.Navigation("HWId");
b.Navigation("LastEditedBy"); b.Navigation("LastEditedBy");
b.Navigation("Round"); b.Navigation("Round");
@@ -1795,8 +1869,36 @@ namespace Content.Server.Database.Migrations.Postgres
.HasForeignKey("RoundId") .HasForeignKey("RoundId")
.HasConstraintName("FK_server_role_ban_round_round_id"); .HasConstraintName("FK_server_role_ban_round_round_id");
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
{
b1.Property<int>("ServerRoleBanId")
.HasColumnType("integer")
.HasColumnName("server_role_ban_id");
b1.Property<byte[]>("Hwid")
.IsRequired()
.HasColumnType("bytea")
.HasColumnName("hwid");
b1.Property<int>("Type")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasColumnName("hwid_type");
b1.HasKey("ServerRoleBanId");
b1.ToTable("server_role_ban");
b1.WithOwner()
.HasForeignKey("ServerRoleBanId")
.HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id");
});
b.Navigation("CreatedBy"); b.Navigation("CreatedBy");
b.Navigation("HWId");
b.Navigation("LastEditedBy"); b.Navigation("LastEditedBy");
b.Navigation("Round"); b.Navigation("Round");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class ModernHwid : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "hwid_type",
table: "server_role_ban",
type: "INTEGER",
nullable: true,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "hwid_type",
table: "server_ban",
type: "INTEGER",
nullable: true,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "last_seen_hwid_type",
table: "player",
type: "INTEGER",
nullable: true,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "hwid_type",
table: "connection_log",
type: "INTEGER",
nullable: true,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "hwid_type",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "hwid_type",
table: "server_ban");
migrationBuilder.DropColumn(
name: "last_seen_hwid_type",
table: "player");
migrationBuilder.DropColumn(
name: "hwid_type",
table: "connection_log");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class ConnectionTrust : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<float>(
name: "trust",
table: "connection_log",
type: "REAL",
nullable: false,
defaultValue: 0f);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "trust",
table: "connection_log");
}
}
}

View File

@@ -483,19 +483,6 @@ namespace Content.Server.Database.Migrations.Sqlite
b.ToTable("assigned_user_id", (string)null); b.ToTable("assigned_user_id", (string)null);
}); });
modelBuilder.Entity("Content.Server.Database.Blacklist",
b =>
{
b.Property<Guid>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("UserId")
.HasName("PK_blacklist");
b.ToTable("blacklist", (string) null);
});
modelBuilder.Entity("Content.Server.Database.BanTemplate", b => modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -539,6 +526,19 @@ namespace Content.Server.Database.Migrations.Sqlite
b.ToTable("ban_template", (string)null); b.ToTable("ban_template", (string)null);
}); });
modelBuilder.Entity("Content.Server.Database.Blacklist", b =>
{
b.Property<Guid>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("UserId")
.HasName("PK_blacklist");
b.ToTable("blacklist", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -555,10 +555,6 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("INTEGER") .HasColumnType("INTEGER")
.HasColumnName("denied"); .HasColumnName("denied");
b.Property<byte[]>("HWId")
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<int>("ServerId") b.Property<int>("ServerId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER") .HasColumnType("INTEGER")
@@ -569,6 +565,10 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT") .HasColumnType("TEXT")
.HasColumnName("time"); .HasColumnName("time");
b.Property<float>("Trust")
.HasColumnType("REAL")
.HasColumnName("trust");
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("TEXT") .HasColumnType("TEXT")
.HasColumnName("user_id"); .HasColumnName("user_id");
@@ -675,10 +675,6 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT") .HasColumnType("TEXT")
.HasColumnName("last_seen_address"); .HasColumnName("last_seen_address");
b.Property<byte[]>("LastSeenHWId")
.HasColumnType("BLOB")
.HasColumnName("last_seen_hwid");
b.Property<DateTime>("LastSeenTime") b.Property<DateTime>("LastSeenTime")
.HasColumnType("TEXT") .HasColumnType("TEXT")
.HasColumnName("last_seen_time"); .HasColumnName("last_seen_time");
@@ -996,10 +992,6 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT") .HasColumnType("TEXT")
.HasColumnName("expiration_time"); .HasColumnName("expiration_time");
b.Property<byte[]>("HWId")
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<bool>("Hidden") b.Property<bool>("Hidden")
.HasColumnType("INTEGER") .HasColumnType("INTEGER")
.HasColumnName("hidden"); .HasColumnName("hidden");
@@ -1124,10 +1116,6 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT") .HasColumnType("TEXT")
.HasColumnName("expiration_time"); .HasColumnName("expiration_time");
b.Property<byte[]>("HWId")
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<bool>("Hidden") b.Property<bool>("Hidden")
.HasColumnType("INTEGER") .HasColumnType("INTEGER")
.HasColumnName("hidden"); .HasColumnName("hidden");
@@ -1559,6 +1547,34 @@ namespace Content.Server.Database.Migrations.Sqlite
.IsRequired() .IsRequired()
.HasConstraintName("FK_connection_log_server_server_id"); .HasConstraintName("FK_connection_log_server_server_id");
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
{
b1.Property<int>("ConnectionLogId")
.HasColumnType("INTEGER")
.HasColumnName("connection_log_id");
b1.Property<byte[]>("Hwid")
.IsRequired()
.HasColumnType("BLOB")
.HasColumnName("hwid");
b1.Property<int>("Type")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("hwid_type");
b1.HasKey("ConnectionLogId");
b1.ToTable("connection_log");
b1.WithOwner()
.HasForeignKey("ConnectionLogId")
.HasConstraintName("FK_connection_log_connection_log_connection_log_id");
});
b.Navigation("HWId");
b.Navigation("Server"); b.Navigation("Server");
}); });
@@ -1574,6 +1590,37 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Profile"); b.Navigation("Profile");
}); });
modelBuilder.Entity("Content.Server.Database.Player", b =>
{
b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 =>
{
b1.Property<int>("PlayerId")
.HasColumnType("INTEGER")
.HasColumnName("player_id");
b1.Property<byte[]>("Hwid")
.IsRequired()
.HasColumnType("BLOB")
.HasColumnName("last_seen_hwid");
b1.Property<int>("Type")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("last_seen_hwid_type");
b1.HasKey("PlayerId");
b1.ToTable("player");
b1.WithOwner()
.HasForeignKey("PlayerId")
.HasConstraintName("FK_player_player_player_id");
});
b.Navigation("LastSeenHWId");
});
modelBuilder.Entity("Content.Server.Database.Profile", b => modelBuilder.Entity("Content.Server.Database.Profile", b =>
{ {
b.HasOne("Content.Server.Database.Preference", "Preference") b.HasOne("Content.Server.Database.Preference", "Preference")
@@ -1668,8 +1715,36 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasForeignKey("RoundId") .HasForeignKey("RoundId")
.HasConstraintName("FK_server_ban_round_round_id"); .HasConstraintName("FK_server_ban_round_round_id");
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
{
b1.Property<int>("ServerBanId")
.HasColumnType("INTEGER")
.HasColumnName("server_ban_id");
b1.Property<byte[]>("Hwid")
.IsRequired()
.HasColumnType("BLOB")
.HasColumnName("hwid");
b1.Property<int>("Type")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("hwid_type");
b1.HasKey("ServerBanId");
b1.ToTable("server_ban");
b1.WithOwner()
.HasForeignKey("ServerBanId")
.HasConstraintName("FK_server_ban_server_ban_server_ban_id");
});
b.Navigation("CreatedBy"); b.Navigation("CreatedBy");
b.Navigation("HWId");
b.Navigation("LastEditedBy"); b.Navigation("LastEditedBy");
b.Navigation("Round"); b.Navigation("Round");
@@ -1717,8 +1792,36 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasForeignKey("RoundId") .HasForeignKey("RoundId")
.HasConstraintName("FK_server_role_ban_round_round_id"); .HasConstraintName("FK_server_role_ban_round_round_id");
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
{
b1.Property<int>("ServerRoleBanId")
.HasColumnType("INTEGER")
.HasColumnName("server_role_ban_id");
b1.Property<byte[]>("Hwid")
.IsRequired()
.HasColumnType("BLOB")
.HasColumnName("hwid");
b1.Property<int>("Type")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("hwid_type");
b1.HasKey("ServerRoleBanId");
b1.ToTable("server_role_ban");
b1.WithOwner()
.HasForeignKey("ServerRoleBanId")
.HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id");
});
b.Navigation("CreatedBy"); b.Navigation("CreatedBy");
b.Navigation("HWId");
b.Navigation("LastEditedBy"); b.Navigation("LastEditedBy");
b.Navigation("Round"); b.Navigation("Round");

View File

@@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text.Json; using System.Text.Json;
@@ -327,6 +329,47 @@ namespace Content.Server.Database
.HasForeignKey(w => w.PlayerUserId) .HasForeignKey(w => w.PlayerUserId)
.HasPrincipalKey(p => p.UserId) .HasPrincipalKey(p => p.UserId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
// Changes for modern HWID integration
modelBuilder.Entity<Player>()
.OwnsOne(p => p.LastSeenHWId)
.Property(p => p.Hwid)
.HasColumnName("last_seen_hwid");
modelBuilder.Entity<Player>()
.OwnsOne(p => p.LastSeenHWId)
.Property(p => p.Type)
.HasDefaultValue(HwidType.Legacy);
modelBuilder.Entity<ServerBan>()
.OwnsOne(p => p.HWId)
.Property(p => p.Hwid)
.HasColumnName("hwid");
modelBuilder.Entity<ServerBan>()
.OwnsOne(p => p.HWId)
.Property(p => p.Type)
.HasDefaultValue(HwidType.Legacy);
modelBuilder.Entity<ServerRoleBan>()
.OwnsOne(p => p.HWId)
.Property(p => p.Hwid)
.HasColumnName("hwid");
modelBuilder.Entity<ServerRoleBan>()
.OwnsOne(p => p.HWId)
.Property(p => p.Type)
.HasDefaultValue(HwidType.Legacy);
modelBuilder.Entity<ConnectionLog>()
.OwnsOne(p => p.HWId)
.Property(p => p.Hwid)
.HasColumnName("hwid");
modelBuilder.Entity<ConnectionLog>()
.OwnsOne(p => p.HWId)
.Property(p => p.Type)
.HasDefaultValue(HwidType.Legacy);
} }
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText) public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
@@ -519,7 +562,7 @@ namespace Content.Server.Database
public string LastSeenUserName { get; set; } = null!; public string LastSeenUserName { get; set; } = null!;
public DateTime LastSeenTime { get; set; } public DateTime LastSeenTime { get; set; }
public IPAddress LastSeenAddress { get; set; } = null!; public IPAddress LastSeenAddress { get; set; } = null!;
public byte[]? LastSeenHWId { get; set; } public TypedHwid? LastSeenHWId { get; set; }
// Data that changes with each round // Data that changes with each round
public List<Round> Rounds { get; set; } = null!; public List<Round> Rounds { get; set; } = null!;
@@ -668,7 +711,7 @@ namespace Content.Server.Database
int Id { get; set; } int Id { get; set; }
Guid? PlayerUserId { get; set; } Guid? PlayerUserId { get; set; }
NpgsqlInet? Address { get; set; } NpgsqlInet? Address { get; set; }
byte[]? HWId { get; set; } TypedHwid? HWId { get; set; }
DateTime BanTime { get; set; } DateTime BanTime { get; set; }
DateTime? ExpirationTime { get; set; } DateTime? ExpirationTime { get; set; }
string Reason { get; set; } string Reason { get; set; }
@@ -753,7 +796,7 @@ namespace Content.Server.Database
/// <summary> /// <summary>
/// Hardware ID of the banned player. /// Hardware ID of the banned player.
/// </summary> /// </summary>
public byte[]? HWId { get; set; } public TypedHwid? HWId { get; set; }
/// <summary> /// <summary>
/// The time when the ban was applied by an administrator. /// The time when the ban was applied by an administrator.
@@ -891,7 +934,7 @@ namespace Content.Server.Database
public DateTime Time { get; set; } public DateTime Time { get; set; }
public IPAddress Address { get; set; } = null!; public IPAddress Address { get; set; } = null!;
public byte[]? HWId { get; set; } public TypedHwid? HWId { get; set; }
public ConnectionDenyReason? Denied { get; set; } public ConnectionDenyReason? Denied { get; set; }
@@ -908,6 +951,8 @@ namespace Content.Server.Database
public List<ServerBanHit> BanHits { get; set; } = null!; public List<ServerBanHit> BanHits { get; set; } = null!;
public Server Server { get; set; } = null!; public Server Server { get; set; } = null!;
public float Trust { get; set; }
} }
public enum ConnectionDenyReason : byte public enum ConnectionDenyReason : byte
@@ -945,7 +990,7 @@ namespace Content.Server.Database
public Guid? PlayerUserId { get; set; } public Guid? PlayerUserId { get; set; }
[Required] public TimeSpan PlaytimeAtNote { get; set; } [Required] public TimeSpan PlaytimeAtNote { get; set; }
public NpgsqlInet? Address { get; set; } public NpgsqlInet? Address { get; set; }
public byte[]? HWId { get; set; } public TypedHwid? HWId { get; set; }
public DateTime BanTime { get; set; } public DateTime BanTime { get; set; }
@@ -1206,4 +1251,37 @@ namespace Content.Server.Database
/// <seealso cref="ServerBan.Hidden"/> /// <seealso cref="ServerBan.Hidden"/>
public bool Hidden { get; set; } public bool Hidden { get; set; }
} }
/// <summary>
/// A hardware ID value together with its <see cref="HwidType"/>.
/// </summary>
/// <seealso cref="ImmutableTypedHwid"/>
[Owned]
public sealed class TypedHwid
{
public byte[] Hwid { get; set; } = default!;
public HwidType Type { get; set; }
[return: NotNullIfNotNull(nameof(immutable))]
public static implicit operator TypedHwid?(ImmutableTypedHwid? immutable)
{
if (immutable == null)
return null;
return new TypedHwid
{
Hwid = immutable.Hwid.ToArray(),
Type = immutable.Type,
};
}
[return: NotNullIfNotNull(nameof(hwid))]
public static implicit operator ImmutableTypedHwid?(TypedHwid? hwid)
{
if (hwid == null)
return null;
return new ImmutableTypedHwid(hwid.Hwid.ToImmutableArray(), hwid.Type);
}
}
} }

View File

@@ -82,7 +82,7 @@ namespace Content.Server.Database
} }
} }
public class SnakeCaseConvention : public partial class SnakeCaseConvention :
IEntityTypeAddedConvention, IEntityTypeAddedConvention,
IEntityTypeAnnotationChangedConvention, IEntityTypeAnnotationChangedConvention,
IPropertyAddedConvention, IPropertyAddedConvention,
@@ -99,8 +99,8 @@ namespace Content.Server.Database
public static string RewriteName(string name) public static string RewriteName(string name)
{ {
var regex = new Regex("[A-Z]+", RegexOptions.Compiled); return UpperCaseLocator()
return regex.Replace( .Replace(
name, name,
(Match match) => { (Match match) => {
if (match.Index == 0 && (match.Value == "FK" || match.Value == "PK" || match.Value == "IX")) { if (match.Index == 0 && (match.Value == "FK" || match.Value == "PK" || match.Value == "IX")) {
@@ -112,6 +112,11 @@ namespace Content.Server.Database
return match.Value.ToLower(); return match.Value.ToLower();
if (match.Length > 1) if (match.Length > 1)
return $"_{match.Value[..^1].ToLower()}_{match.Value[^1..^0].ToLower()}"; return $"_{match.Value[..^1].ToLower()}_{match.Value[^1..^0].ToLower()}";
// Do not add a _ if there is already one before this. This happens with owned entities.
if (name[match.Index - 1] == '_')
return match.Value.ToLower();
return "_" + match.Value.ToLower(); return "_" + match.Value.ToLower();
} }
); );
@@ -332,5 +337,8 @@ namespace Content.Server.Database
} }
} }
} }
[GeneratedRegex("[A-Z]+", RegexOptions.Compiled)]
private static partial Regex UpperCaseLocator();
} }
} }

View File

@@ -146,8 +146,8 @@ namespace Content.Server.Abilities.Mime
mimePowers.ReadyToRepent = false; mimePowers.ReadyToRepent = false;
mimePowers.VowBroken = false; mimePowers.VowBroken = false;
AddComp<MutedComponent>(uid); AddComp<MutedComponent>(uid);
_alertsSystem.ClearAlert(uid, mimePowers.VowAlert); _alertsSystem.ClearAlert(uid, mimePowers.VowBrokenAlert);
_alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert); _alertsSystem.ShowAlert(uid, mimePowers.VowAlert);
_actionsSystem.AddAction(uid, ref mimePowers.InvisibleWallActionEntity, mimePowers.InvisibleWallAction, uid); _actionsSystem.AddAction(uid, ref mimePowers.InvisibleWallActionEntity, mimePowers.InvisibleWallAction, uid);
} }
} }

View File

@@ -54,7 +54,7 @@ public sealed class BanListEui : BaseEui
private async Task LoadBans(NetUserId userId) private async Task LoadBans(NetUserId userId)
{ {
foreach (var ban in await _db.GetServerBansAsync(null, userId, null)) foreach (var ban in await _db.GetServerBansAsync(null, userId, null, null))
{ {
SharedServerUnban? unban = null; SharedServerUnban? unban = null;
if (ban.Unban is { } unbanDef) if (ban.Unban is { } unbanDef)
@@ -74,7 +74,7 @@ public sealed class BanListEui : BaseEui
? (address.address.ToString(), address.cidrMask) ? (address.address.ToString(), address.cidrMask)
: null; : null;
hwid = ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan()); hwid = ban.HWId?.ToString();
} }
Bans.Add(new SharedServerBan( Bans.Add(new SharedServerBan(
@@ -95,7 +95,7 @@ public sealed class BanListEui : BaseEui
private async Task LoadRoleBans(NetUserId userId) private async Task LoadRoleBans(NetUserId userId)
{ {
foreach (var ban in await _db.GetServerRoleBansAsync(null, userId, null)) foreach (var ban in await _db.GetServerRoleBansAsync(null, userId, null, null))
{ {
SharedServerUnban? unban = null; SharedServerUnban? unban = null;
if (ban.Unban is { } unbanDef) if (ban.Unban is { } unbanDef)
@@ -115,7 +115,7 @@ public sealed class BanListEui : BaseEui
? (address.address.ToString(), address.cidrMask) ? (address.address.ToString(), address.cidrMask)
: null; : null;
hwid = ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan()); hwid = ban.HWId?.ToString();
} }
RoleBans.Add(new SharedServerRoleBan( RoleBans.Add(new SharedServerRoleBan(
ban.Id, ban.Id,

View File

@@ -1,4 +1,3 @@
using System.Collections.Immutable;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using Content.Server.Administration.Managers; using Content.Server.Administration.Managers;
@@ -8,7 +7,6 @@ using Content.Server.EUI;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Eui; using Content.Shared.Eui;
using Robust.Server.Player;
using Robust.Shared.Network; using Robust.Shared.Network;
namespace Content.Server.Administration; namespace Content.Server.Administration;
@@ -27,7 +25,7 @@ public sealed class BanPanelEui : BaseEui
private NetUserId? PlayerId { get; set; } private NetUserId? PlayerId { get; set; }
private string PlayerName { get; set; } = string.Empty; private string PlayerName { get; set; } = string.Empty;
private IPAddress? LastAddress { get; set; } private IPAddress? LastAddress { get; set; }
private ImmutableArray<byte>? LastHwid { get; set; } private ImmutableTypedHwid? LastHwid { get; set; }
private const int Ipv4_CIDR = 32; private const int Ipv4_CIDR = 32;
private const int Ipv6_CIDR = 64; private const int Ipv6_CIDR = 64;
@@ -51,7 +49,7 @@ public sealed class BanPanelEui : BaseEui
switch (msg) switch (msg)
{ {
case BanPanelEuiStateMsg.CreateBanRequest r: case BanPanelEuiStateMsg.CreateBanRequest r:
BanPlayer(r.Player, r.IpAddress, r.UseLastIp, r.Hwid?.ToImmutableArray(), r.UseLastHwid, r.Minutes, r.Severity, r.Reason, r.Roles, r.Erase); BanPlayer(r.Player, r.IpAddress, r.UseLastIp, r.Hwid, r.UseLastHwid, r.Minutes, r.Severity, r.Reason, r.Roles, r.Erase);
break; break;
case BanPanelEuiStateMsg.GetPlayerInfoRequest r: case BanPanelEuiStateMsg.GetPlayerInfoRequest r:
ChangePlayer(r.PlayerUsername); ChangePlayer(r.PlayerUsername);
@@ -59,7 +57,7 @@ public sealed class BanPanelEui : BaseEui
} }
} }
private async void BanPlayer(string? target, string? ipAddressString, bool useLastIp, ImmutableArray<byte>? hwid, bool useLastHwid, uint minutes, NoteSeverity severity, string reason, IReadOnlyCollection<string>? roles, bool erase) private async void BanPlayer(string? target, string? ipAddressString, bool useLastIp, ImmutableTypedHwid? hwid, bool useLastHwid, uint minutes, NoteSeverity severity, string reason, IReadOnlyCollection<string>? roles, bool erase)
{ {
if (!_admins.HasAdminFlag(Player, AdminFlags.Ban)) if (!_admins.HasAdminFlag(Player, AdminFlags.Ban))
{ {
@@ -155,7 +153,7 @@ public sealed class BanPanelEui : BaseEui
ChangePlayer(located?.UserId, located?.Username ?? string.Empty, located?.LastAddress, located?.LastHWId); ChangePlayer(located?.UserId, located?.Username ?? string.Empty, located?.LastAddress, located?.LastHWId);
} }
public void ChangePlayer(NetUserId? playerId, string playerName, IPAddress? lastAddress, ImmutableArray<byte>? lastHwid) public void ChangePlayer(NetUserId? playerId, string playerName, IPAddress? lastAddress, ImmutableTypedHwid? lastHwid)
{ {
PlayerId = playerId; PlayerId = playerId;
PlayerName = playerName; PlayerName = playerName;

View File

@@ -38,7 +38,7 @@ public sealed class BanListCommand : LocalizedCommands
if (shell.Player is not { } player) if (shell.Player is not { } player)
{ {
var bans = await _dbManager.GetServerBansAsync(data.LastAddress, data.UserId, data.LastHWId, false); var bans = await _dbManager.GetServerBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, false);
if (bans.Count == 0) if (bans.Count == 0)
{ {

View File

@@ -48,7 +48,7 @@ public sealed class RoleBanListCommand : IConsoleCommand
if (shell.Player is not { } player) if (shell.Player is not { } player)
{ {
var bans = await _dbManager.GetServerRoleBansAsync(data.LastAddress, data.UserId, data.LastHWId, includeUnbanned); var bans = await _dbManager.GetServerRoleBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, includeUnbanned);
if (bans.Count == 0) if (bans.Count == 0)
{ {

View File

@@ -65,7 +65,8 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
var netChannel = player.Channel; var netChannel = player.Channel;
ImmutableArray<byte>? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId; ImmutableArray<byte>? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId;
var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, false); var modernHwids = netChannel.UserData.ModernHWIds;
var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, modernHwids, false);
var userRoleBans = new List<ServerRoleBanDef>(); var userRoleBans = new List<ServerRoleBanDef>();
foreach (var ban in roleBans) foreach (var ban in roleBans)
@@ -132,7 +133,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
} }
#region Server Bans #region Server Bans
public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, uint? minutes, NoteSeverity severity, string reason) public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason)
{ {
DateTimeOffset? expires = null; DateTimeOffset? expires = null;
if (minutes > 0) if (minutes > 0)
@@ -166,9 +167,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
var addressRangeString = addressRange != null var addressRangeString = addressRange != null
? $"{addressRange.Value.Item1}/{addressRange.Value.Item2}" ? $"{addressRange.Value.Item1}/{addressRange.Value.Item2}"
: "null"; : "null";
var hwidString = hwid != null var hwidString = hwid?.ToString() ?? "null";
? string.Concat(hwid.Value.Select(x => x.ToString("x2")))
: "null";
var expiresString = expires == null ? Loc.GetString("server-ban-string-never") : $"{expires}"; var expiresString = expires == null ? Loc.GetString("server-ban-string-never") : $"{expires}";
var key = _cfg.GetCVar(CCVars.AdminShowPIIOnBan) ? "server-ban-string" : "server-ban-string-no-pii"; var key = _cfg.GetCVar(CCVars.AdminShowPIIOnBan) ? "server-ban-string" : "server-ban-string-no-pii";
@@ -208,6 +207,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
UserId = player.UserId, UserId = player.UserId,
Address = player.Channel.RemoteEndPoint.Address, Address = player.Channel.RemoteEndPoint.Address,
HWId = player.Channel.UserData.HWId, HWId = player.Channel.UserData.HWId,
ModernHWIds = player.Channel.UserData.ModernHWIds,
// It's possible for the player to not have cached data loading yet due to coincidental timing. // It's possible for the player to not have cached data loading yet due to coincidental timing.
// If this is the case, we assume they have all flags to avoid false-positives. // If this is the case, we assume they have all flags to avoid false-positives.
ExemptFlags = _cachedBanExemptions.GetValueOrDefault(player, ServerBanExemptFlags.All), ExemptFlags = _cachedBanExemptions.GetValueOrDefault(player, ServerBanExemptFlags.All),
@@ -228,7 +228,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
#region Job Bans #region Job Bans
// If you are trying to remove timeOfBan, please don't. It's there because the note system groups role bans by time, reason and banning admin. // If you are trying to remove timeOfBan, please don't. It's there because the note system groups role bans by time, reason and banning admin.
// Removing it will clutter the note list. Please also make sure that department bans are applied to roles with the same DateTimeOffset. // Removing it will clutter the note list. Please also make sure that department bans are applied to roles with the same DateTimeOffset.
public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan) public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan)
{ {
if (!_prototypeManager.TryIndex(role, out JobPrototype? _)) if (!_prototypeManager.TryIndex(role, out JobPrototype? _))
{ {

View File

@@ -24,7 +24,7 @@ public interface IBanManager
/// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param> /// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
/// <param name="severity">Severity of the resulting ban note</param> /// <param name="severity">Severity of the resulting ban note</param>
/// <param name="reason">Reason for the ban</param> /// <param name="reason">Reason for the ban</param>
public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, uint? minutes, NoteSeverity severity, string reason); public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason);
public HashSet<string>? GetRoleBans(NetUserId playerUserId); public HashSet<string>? GetRoleBans(NetUserId playerUserId);
public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId); public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId);
@@ -37,7 +37,7 @@ public interface IBanManager
/// <param name="reason">Reason for the ban</param> /// <param name="reason">Reason for the ban</param>
/// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param> /// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
/// <param name="timeOfBan">Time when the ban was applied, used for grouping role bans</param> /// <param name="timeOfBan">Time when the ban was applied, used for grouping role bans</param>
public void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan); public void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan);
/// <summary> /// <summary>
/// Pardons a role ban for the specified target, username or GUID /// Pardons a role ban for the specified target, username or GUID

View File

@@ -5,16 +5,42 @@ using System.Net.Http.Headers;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Connection;
using Content.Server.Database; using Content.Server.Database;
using Content.Shared.Database;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared; using Robust.Shared;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Player;
namespace Content.Server.Administration namespace Content.Server.Administration
{ {
public sealed record LocatedPlayerData(NetUserId UserId, IPAddress? LastAddress, ImmutableArray<byte>? LastHWId, string Username); /// <summary>
/// Contains data resolved via <see cref="IPlayerLocator"/>.
/// </summary>
/// <param name="UserId">The ID of the located user.</param>
/// <param name="LastAddress">The last known IP address that the user connected with.</param>
/// <param name="LastHWId">
/// The last known HWID that the user connected with.
/// This should be used for placing new records involving HWIDs, such as bans.
/// For looking up data based on HWID, use combined <see cref="LastLegacyHWId"/> and <see cref="LastModernHWIds"/>.
/// </param>
/// <param name="Username">The last known username for the user connected with.</param>
/// <param name="LastLegacyHWId">
/// The last known legacy HWID value this user connected with. Only use for old lookups!
/// </param>
/// <param name="LastModernHWIds">
/// The set of last known modern HWIDs the user connected with.
/// </param>
public sealed record LocatedPlayerData(
NetUserId UserId,
IPAddress? LastAddress,
ImmutableTypedHwid? LastHWId,
string Username,
ImmutableArray<byte>? LastLegacyHWId,
ImmutableArray<ImmutableArray<byte>> LastModernHWIds);
/// <summary> /// <summary>
/// Utilities for finding user IDs that extend to more than the server database. /// Utilities for finding user IDs that extend to more than the server database.
@@ -67,63 +93,42 @@ namespace Content.Server.Administration
{ {
// Check people currently on the server, the easiest case. // Check people currently on the server, the easiest case.
if (_playerManager.TryGetSessionByUsername(playerName, out var session)) if (_playerManager.TryGetSessionByUsername(playerName, out var session))
{ return ReturnForSession(session);
var userId = session.UserId;
var address = session.Channel.RemoteEndPoint.Address;
var hwId = session.Channel.UserData.HWId;
return new LocatedPlayerData(userId, address, hwId, session.Name);
}
// Check database for past players. // Check database for past players.
var record = await _db.GetPlayerRecordByUserName(playerName, cancel); var record = await _db.GetPlayerRecordByUserName(playerName, cancel);
if (record != null) if (record != null)
return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId, record.LastSeenUserName); return ReturnForPlayerRecord(record);
// If all else fails, ask the auth server. // If all else fails, ask the auth server.
var authServer = _configurationManager.GetCVar(CVars.AuthServer); var authServer = _configurationManager.GetCVar(CVars.AuthServer);
var requestUri = $"{authServer}api/query/name?name={WebUtility.UrlEncode(playerName)}"; var requestUri = $"{authServer}api/query/name?name={WebUtility.UrlEncode(playerName)}";
using var resp = await _httpClient.GetAsync(requestUri, cancel); using var resp = await _httpClient.GetAsync(requestUri, cancel);
if (resp.StatusCode == HttpStatusCode.NotFound) return await HandleAuthServerResponse(resp, cancel);
return null;
if (!resp.IsSuccessStatusCode)
{
_sawmill.Error("Auth server returned bad response {StatusCode}!", resp.StatusCode);
return null;
}
var responseData = await resp.Content.ReadFromJsonAsync<UserDataResponse>(cancellationToken: cancel);
if (responseData == null)
{
_sawmill.Error("Auth server returned null response!");
return null;
}
return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName);
} }
public async Task<LocatedPlayerData?> LookupIdAsync(NetUserId userId, CancellationToken cancel = default) public async Task<LocatedPlayerData?> LookupIdAsync(NetUserId userId, CancellationToken cancel = default)
{ {
// Check people currently on the server, the easiest case. // Check people currently on the server, the easiest case.
if (_playerManager.TryGetSessionById(userId, out var session)) if (_playerManager.TryGetSessionById(userId, out var session))
{ return ReturnForSession(session);
var address = session.Channel.RemoteEndPoint.Address;
var hwId = session.Channel.UserData.HWId;
return new LocatedPlayerData(userId, address, hwId, session.Name);
}
// Check database for past players. // Check database for past players.
var record = await _db.GetPlayerRecordByUserId(userId, cancel); var record = await _db.GetPlayerRecordByUserId(userId, cancel);
if (record != null) if (record != null)
return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId, record.LastSeenUserName); return ReturnForPlayerRecord(record);
// If all else fails, ask the auth server. // If all else fails, ask the auth server.
var authServer = _configurationManager.GetCVar(CVars.AuthServer); var authServer = _configurationManager.GetCVar(CVars.AuthServer);
var requestUri = $"{authServer}api/query/userid?userid={WebUtility.UrlEncode(userId.UserId.ToString())}"; var requestUri = $"{authServer}api/query/userid?userid={WebUtility.UrlEncode(userId.UserId.ToString())}";
using var resp = await _httpClient.GetAsync(requestUri, cancel); using var resp = await _httpClient.GetAsync(requestUri, cancel);
return await HandleAuthServerResponse(resp, cancel);
}
private async Task<LocatedPlayerData?> HandleAuthServerResponse(HttpResponseMessage resp, CancellationToken cancel)
{
if (resp.StatusCode == HttpStatusCode.NotFound) if (resp.StatusCode == HttpStatusCode.NotFound)
return null; return null;
@@ -134,14 +139,40 @@ namespace Content.Server.Administration
} }
var responseData = await resp.Content.ReadFromJsonAsync<UserDataResponse>(cancellationToken: cancel); var responseData = await resp.Content.ReadFromJsonAsync<UserDataResponse>(cancellationToken: cancel);
if (responseData == null) if (responseData == null)
{ {
_sawmill.Error("Auth server returned null response!"); _sawmill.Error("Auth server returned null response!");
return null; return null;
} }
return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName); return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName, null, []);
}
private static LocatedPlayerData ReturnForSession(ICommonSession session)
{
var userId = session.UserId;
var address = session.Channel.RemoteEndPoint.Address;
var hwId = session.Channel.UserData.GetModernHwid();
return new LocatedPlayerData(
userId,
address,
hwId,
session.Name,
session.Channel.UserData.HWId,
session.Channel.UserData.ModernHWIds);
}
private static LocatedPlayerData ReturnForPlayerRecord(PlayerRecord record)
{
var hwid = record.HWId;
return new LocatedPlayerData(
record.UserId,
record.LastSeenAddress,
hwid,
record.LastSeenUserName,
hwid is { Type: HwidType.Legacy } ? hwid.Hwid : null,
hwid is { Type: HwidType.Modern } ? [hwid.Hwid] : []);
} }
public async Task<LocatedPlayerData?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default) public async Task<LocatedPlayerData?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default)

View File

@@ -173,11 +173,11 @@ public sealed class PlayerPanelEui : BaseEui
{ {
_whitelisted = await _db.GetWhitelistStatusAsync(_targetPlayer.UserId); _whitelisted = await _db.GetWhitelistStatusAsync(_targetPlayer.UserId);
// This won't get associated ip or hwid bans but they were not placed on this account anyways // This won't get associated ip or hwid bans but they were not placed on this account anyways
_bans = (await _db.GetServerBansAsync(null, _targetPlayer.UserId, null)).Count; _bans = (await _db.GetServerBansAsync(null, _targetPlayer.UserId, null, null)).Count;
// Unfortunately role bans for departments and stuff are issued individually. This means that a single role ban can have many individual role bans internally // Unfortunately role bans for departments and stuff are issued individually. This means that a single role ban can have many individual role bans internally
// The only way to distinguish whether a role ban is the same is to compare the ban time. // The only way to distinguish whether a role ban is the same is to compare the ban time.
// This is horrible and I would love to just erase the database and start from scratch instead but that's what I can do for now. // This is horrible and I would love to just erase the database and start from scratch instead but that's what I can do for now.
_roleBans = (await _db.GetServerRoleBansAsync(null, _targetPlayer.UserId, null)).DistinctBy(rb => rb.BanTime).Count(); _roleBans = (await _db.GetServerRoleBansAsync(null, _targetPlayer.UserId, null, null)).DistinctBy(rb => rb.BanTime).Count();
} }
else else
{ {

View File

@@ -22,6 +22,7 @@ using Content.Shared.Administration;
using Content.Shared.Administration.Components; using Content.Shared.Administration.Components;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Body.Part; using Content.Shared.Body.Part;
using Content.Shared.Clumsy;
using Content.Shared.Clothing.Components; using Content.Shared.Clothing.Components;
using Content.Shared.Cluwne; using Content.Shared.Cluwne;
using Content.Shared.Damage; using Content.Shared.Damage;

View File

@@ -172,7 +172,7 @@ namespace Content.Server.Administration.Systems
} }
// Check if the user has been banned // Check if the user has been banned
var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null); var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null, null);
if (ban != null) if (ban != null)
{ {
var banMessage = Loc.GetString("bwoink-system-player-banned", ("banReason", ban.Reason)); var banMessage = Loc.GetString("bwoink-system-player-banned", ("banReason", ban.Reason));

View File

@@ -1,24 +1,24 @@
using Content.Server.Administration.Components; using Content.Server.Administration.Components;
using Content.Shared.Climbing.Components; using Content.Shared.Climbing.Components;
using Content.Shared.Climbing.Events; using Content.Shared.Clumsy;
using Content.Shared.Climbing.Systems;
using Content.Shared.Interaction.Components;
using Content.Shared.Mobs; using Content.Shared.Mobs;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Robust.Shared.Audio.Systems;
namespace Content.Server.Administration.Systems; namespace Content.Server.Administration.Systems;
public sealed class SuperBonkSystem : EntitySystem public sealed class SuperBonkSystem : EntitySystem
{ {
[Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly BonkSystem _bonkSystem = default!; [Dependency] private readonly ClumsySystem _clumsySystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<SuperBonkComponent, ComponentShutdown>(OnBonkShutdown);
SubscribeLocalEvent<SuperBonkComponent, MobStateChangedEvent>(OnMobStateChanged); SubscribeLocalEvent<SuperBonkComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<SuperBonkComponent, ComponentShutdown>(OnBonkShutdown);
} }
public void StartSuperBonk(EntityUid target, float delay = 0.1f, bool stopWhenDead = false) public void StartSuperBonk(EntityUid target, float delay = 0.1f, bool stopWhenDead = false)
@@ -31,7 +31,6 @@ public sealed class SuperBonkSystem: EntitySystem
return; return;
} }
var hadClumsy = EnsureComp<ClumsyComponent>(target, out _); var hadClumsy = EnsureComp<ClumsyComponent>(target, out _);
var tables = EntityQueryEnumerator<BonkableComponent>(); var tables = EntityQueryEnumerator<BonkableComponent>();
@@ -79,16 +78,17 @@ public sealed class SuperBonkSystem: EntitySystem
private void Bonk(SuperBonkComponent comp) private void Bonk(SuperBonkComponent comp)
{ {
var uid = comp.Tables.Current.Key; var uid = comp.Tables.Current.Key;
var bonkComp = comp.Tables.Current.Value;
// It would be very weird for something without a transform component to have a bonk component // It would be very weird for something without a transform component to have a bonk component
// but just in case because I don't want to crash the server. // but just in case because I don't want to crash the server.
if (!HasComp<TransformComponent>(uid)) if (!HasComp<TransformComponent>(uid) || !TryComp<ClumsyComponent>(comp.Target, out var clumsyComp))
return; return;
_transformSystem.SetCoordinates(comp.Target, Transform(uid).Coordinates); _transformSystem.SetCoordinates(comp.Target, Transform(uid).Coordinates);
_bonkSystem.TryBonk(comp.Target, uid, bonkComp); _clumsySystem.HitHeadClumsy((comp.Target, clumsyComp), uid);
_audioSystem.PlayPvs(clumsyComp.TableBonkSound, comp.Target);
} }
private void OnMobStateChanged(EntityUid uid, SuperBonkComponent comp, MobStateChangedEvent args) private void OnMobStateChanged(EntityUid uid, SuperBonkComponent comp, MobStateChangedEvent args)

View File

@@ -25,6 +25,16 @@ public sealed class TagCommand : ToolshedCommand
}); });
} }
[CommandImplementation("with")]
public IEnumerable<EntityUid> With(
[CommandInvocationContext] IInvocationContext ctx,
[PipedArgument] IEnumerable<EntityUid> entities,
[CommandArgument] ValueRef<string, Prototype<TagPrototype>> tag)
{
_tag ??= GetSys<TagSystem>();
return entities.Where(e => _tag.HasTag(e, tag.Evaluate(ctx)!));
}
[CommandImplementation("add")] [CommandImplementation("add")]
public EntityUid Add( public EntityUid Add(
[CommandInvocationContext] IInvocationContext ctx, [CommandInvocationContext] IInvocationContext ctx,

View File

@@ -1,7 +1,19 @@
using Content.Shared.Alert; using Content.Shared.Alert;
using Robust.Shared.GameStates;
namespace Content.Server.Alert; namespace Content.Server.Alert;
internal sealed class ServerAlertsSystem : AlertsSystem internal sealed class ServerAlertsSystem : AlertsSystem
{ {
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AlertsComponent, ComponentGetState>(OnGetState);
}
private void OnGetState(Entity<AlertsComponent> alerts, ref ComponentGetState args)
{
args.State = new AlertComponentState(alerts.Comp.Alerts);
}
} }

View File

@@ -28,7 +28,8 @@ namespace Content.Server.Announcements
} }
else else
{ {
var message = string.Join(' ', new ArraySegment<string>(args, 1, args.Length-1)); // Explicit IEnumerable<string> due to overload ambiguity on .NET 9
var message = string.Join(' ', (IEnumerable<string>)new ArraySegment<string>(args, 1, args.Length-1));
chat.DispatchGlobalAnnouncement(message, args[0], colorOverride: Color.Gold); chat.DispatchGlobalAnnouncement(message, args[0], colorOverride: Color.Gold);
} }
shell.WriteLine("Sent!"); shell.WriteLine("Sent!");

View File

@@ -55,6 +55,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
{ {
base.Initialize(); base.Initialize();
Log.Level = LogLevel.Debug;
SubscribeLocalEvent<GhostRoleAntagSpawnerComponent, TakeGhostRoleEvent>(OnTakeGhostRole); SubscribeLocalEvent<GhostRoleAntagSpawnerComponent, TakeGhostRoleEvent>(OnTakeGhostRole);
SubscribeLocalEvent<AntagSelectionComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo); SubscribeLocalEvent<AntagSelectionComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
@@ -182,7 +184,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
return; return;
var players = _playerManager.Sessions var players = _playerManager.Sessions
.Where(x => GameTicker.PlayerGameStatuses[x.UserId] == PlayerGameStatus.JoinedGame) .Where(x => GameTicker.PlayerGameStatuses.TryGetValue(x.UserId, out var status) && status == PlayerGameStatus.JoinedGame)
.ToList(); .ToList();
ChooseAntags((uid, component), players, midround: true); ChooseAntags((uid, component), players, midround: true);
@@ -360,6 +362,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
_role.MindAddRoles(curMind.Value, def.MindRoles, null, true); _role.MindAddRoles(curMind.Value, def.MindRoles, null, true);
ent.Comp.SelectedMinds.Add((curMind.Value, Name(player))); ent.Comp.SelectedMinds.Add((curMind.Value, Name(player)));
SendBriefing(session, def.Briefing); SendBriefing(session, def.Briefing);
Log.Debug($"Selected {ToPrettyString(curMind)} as antagonist: {ToPrettyString(ent)}");
} }
var afterEv = new AfterAntagEntitySelectedEvent(session, player, ent, def); var afterEv = new AfterAntagEntitySelectedEvent(session, player, ent, def);

View File

@@ -84,9 +84,15 @@ namespace Content.Server.Atmos.Components
public ProtoId<AlertPrototype> FireAlert = "Fire"; public ProtoId<AlertPrototype> FireAlert = "Fire";
/// <summary> /// <summary>
/// CrystallPunk fireplace fuel /// CrystallEdge fireplace fuel
/// </summary> /// </summary>
[DataField] [DataField]
public float CP14FireplaceFuel = 10f; public float CP14FireplaceFuel = 10f;
/// <summary>
/// the value is cached to check if it has changed
/// </summary>
[DataField]
public bool OnFireOld = false;
} }
} }

View File

@@ -1,4 +1,3 @@
using Content.Server._CP14.Temperature;
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Server.IgnitionSource; using Content.Server.IgnitionSource;
@@ -6,6 +5,7 @@ using Content.Server.Stunnable;
using Content.Server.Temperature.Components; using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems; using Content.Server.Temperature.Systems;
using Content.Server.Damage.Components; using Content.Server.Damage.Components;
using Content.Shared._CP14.Temperature;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Alert; using Content.Shared.Alert;
using Content.Shared.Atmos; using Content.Shared.Atmos;
@@ -144,15 +144,16 @@ namespace Content.Server.Atmos.EntitySystems
{ {
if (args.Handled) if (args.Handled)
return; return;
var isHotEvent = new IsHotEvent(); var isHotEvent = new IsHotEvent();
RaiseLocalEvent(args.Used, isHotEvent); RaiseLocalEvent(args.Used, isHotEvent);
if (!isHotEvent.IsHot) if (!isHotEvent.IsHot)
return; return;
/* //CP14 disabling igniting via direact interact. Only from DelayedIgnitionSource
Ignite(uid, args.Used, flammable, args.User); Ignite(uid, args.Used, flammable, args.User);
args.Handled = true; args.Handled = true;
*/
} }
private void OnExtinguishActivateInWorld(EntityUid uid, ExtinguishOnInteractComponent component, ActivateInWorldEvent args) private void OnExtinguishActivateInWorld(EntityUid uid, ExtinguishOnInteractComponent component, ActivateInWorldEvent args)
@@ -263,6 +264,19 @@ namespace Content.Server.Atmos.EntitySystems
public void UpdateAppearance(EntityUid uid, FlammableComponent? flammable = null, AppearanceComponent? appearance = null) public void UpdateAppearance(EntityUid uid, FlammableComponent? flammable = null, AppearanceComponent? appearance = null)
{ {
//CrystallEdge bonfire moment
if (!Resolve(uid, ref flammable))
return;
if (flammable.OnFireOld != flammable.OnFire)
{
var ev = new OnFireChangedEvent(flammable.OnFire);
RaiseLocalEvent(uid, ref ev);
flammable.OnFireOld = flammable.OnFire;
}
//CrystallEdge bonfire moment end
if (!Resolve(uid, ref flammable, ref appearance)) if (!Resolve(uid, ref flammable, ref appearance))
return; return;
@@ -316,12 +330,6 @@ namespace Content.Server.Atmos.EntitySystems
_ignitionSourceSystem.SetIgnited(uid, false); _ignitionSourceSystem.SetIgnited(uid, false);
//CrystallPunk bonfire moment
var ev = new OnFireChangedEvent(flammable.OnFire);
RaiseLocalEvent(uid, ref ev);
//CrystallPunk bonfire moment end
UpdateAppearance(uid, flammable); UpdateAppearance(uid, flammable);
} }
@@ -343,11 +351,6 @@ namespace Content.Server.Atmos.EntitySystems
else else
_adminLogger.Add(LogType.Flammable, $"{ToPrettyString(uid):target} set on fire by {ToPrettyString(ignitionSource):actor}"); _adminLogger.Add(LogType.Flammable, $"{ToPrettyString(uid):target} set on fire by {ToPrettyString(ignitionSource):actor}");
flammable.OnFire = true; flammable.OnFire = true;
//CrystallPunk fireplace moment
var ev = new OnFireChangedEvent(flammable.OnFire);
RaiseLocalEvent(uid, ref ev);
//CrystallPunk fireplace moment end
} }
UpdateAppearance(uid, flammable); UpdateAppearance(uid, flammable);

View File

@@ -48,7 +48,9 @@ public sealed partial class AtmosMonitorComponent : Component
[DataField("gasThresholds")] [DataField("gasThresholds")]
public Dictionary<Gas, AtmosAlarmThreshold>? GasThresholds; public Dictionary<Gas, AtmosAlarmThreshold>? GasThresholds;
// Stores a reference to the gas on the tile this is on. /// <summary>
/// Stores a reference to the gas on the tile this entity is on (or the pipe network it monitors; see <see cref="MonitorsPipeNet"/>).
/// </summary>
[ViewVariables] [ViewVariables]
public GasMixture? TileGas; public GasMixture? TileGas;
@@ -65,4 +67,19 @@ public sealed partial class AtmosMonitorComponent : Component
/// </summary> /// </summary>
[DataField("registeredDevices")] [DataField("registeredDevices")]
public HashSet<string> RegisteredDevices = new(); public HashSet<string> RegisteredDevices = new();
/// <summary>
/// Specifies whether this device monitors its own internal pipe network rather than the surrounding atmosphere.
/// </summary>
/// <remarks>
/// If 'true', the entity will require a NodeContainerComponent with one or more PipeNodes to function.
/// </remarks>
[DataField]
public bool MonitorsPipeNet = false;
/// <summary>
/// Specifies the name of the pipe node that this device is monitoring.
/// </summary>
[DataField]
public string NodeNameMonitoredPipe = "monitored";
} }

View File

@@ -4,6 +4,9 @@ using Content.Server.Atmos.Piping.Components;
using Content.Server.Atmos.Piping.EntitySystems; using Content.Server.Atmos.Piping.EntitySystems;
using Content.Server.DeviceNetwork; using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Systems; using Content.Server.DeviceNetwork.Systems;
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.Atmos; using Content.Shared.Atmos;
@@ -25,6 +28,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
[Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!; [Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly NodeContainerSystem _nodeContainerSystem = default!;
// Commands // Commands
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold"; public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
@@ -56,8 +60,15 @@ public sealed class AtmosMonitorSystem : EntitySystem
private void OnAtmosDeviceEnterAtmosphere(EntityUid uid, AtmosMonitorComponent atmosMonitor, ref AtmosDeviceEnabledEvent args) private void OnAtmosDeviceEnterAtmosphere(EntityUid uid, AtmosMonitorComponent atmosMonitor, ref AtmosDeviceEnabledEvent args)
{ {
if (atmosMonitor.MonitorsPipeNet && _nodeContainerSystem.TryGetNode<PipeNode>(uid, atmosMonitor.NodeNameMonitoredPipe, out var pipeNode))
{
atmosMonitor.TileGas = pipeNode.Air;
return;
}
atmosMonitor.TileGas = _atmosphereSystem.GetContainingMixture(uid, true); atmosMonitor.TileGas = _atmosphereSystem.GetContainingMixture(uid, true);
} }
private void OnMapInit(EntityUid uid, AtmosMonitorComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, AtmosMonitorComponent component, MapInitEvent args)
{ {
if (component.TemperatureThresholdId != null) if (component.TemperatureThresholdId != null)
@@ -215,6 +226,10 @@ public sealed class AtmosMonitorSystem : EntitySystem
&& component.GasThresholds == null) && component.GasThresholds == null)
return; return;
// If monitoring a pipe network, get its most recent gas mixture
if (component.MonitorsPipeNet && _nodeContainerSystem.TryGetNode<PipeNode>(uid, component.NodeNameMonitoredPipe, out var pipeNode))
component.TileGas = pipeNode.Air;
UpdateState(uid, component.TileGas, component); UpdateState(uid, component.TileGas, component);
} }

View File

@@ -1,6 +1,7 @@
using Content.Server._CP14.Temperature; using Content.Server._CP14.Temperature;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared._CP14.Temperature;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Mobs; using Content.Shared.Mobs;
using Content.Shared.Power; using Content.Shared.Power;
@@ -14,7 +15,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
base.Initialize(); base.Initialize();
SubscribeLocalEvent<AmbientOnPoweredComponent, PowerChangedEvent>(HandlePowerChange); SubscribeLocalEvent<AmbientOnPoweredComponent, PowerChangedEvent>(HandlePowerChange);
SubscribeLocalEvent<AmbientOnPoweredComponent, PowerNetBatterySupplyEvent>(HandlePowerSupply); SubscribeLocalEvent<AmbientOnPoweredComponent, PowerNetBatterySupplyEvent>(HandlePowerSupply);
SubscribeLocalEvent<CP14FlammableAmbientSoundComponent, OnFireChangedEvent>(OnFireChanged); //CrystallPunk bonfire moment SubscribeLocalEvent<CP14FlammableAmbientSoundComponent, OnFireChangedEvent>(OnFireChanged); //CrystallEdge bonfire moment
} }
private void HandlePowerSupply(EntityUid uid, AmbientOnPoweredComponent component, ref PowerNetBatterySupplyEvent args) private void HandlePowerSupply(EntityUid uid, AmbientOnPoweredComponent component, ref PowerNetBatterySupplyEvent args)
@@ -27,10 +28,10 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
SetAmbience(uid, args.Powered); SetAmbience(uid, args.Powered);
} }
//CrystallPunk bonfire moment //CrystallEdge bonfire moment
private void OnFireChanged(Entity<CP14FlammableAmbientSoundComponent> ent, ref OnFireChangedEvent args) private void OnFireChanged(Entity<CP14FlammableAmbientSoundComponent> ent, ref OnFireChangedEvent args)
{ {
SetAmbience(ent, args.OnFire); SetAmbience(ent, args.OnFire);
} }
//CrystallPunk bonfire moment end //CrystallEdge bonfire moment end
} }

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