Compare commits

...

380 Commits

Author SHA1 Message Date
Tornado Tech
0f6e73831a Дистанционный гибер из реал 2024-05-08 20:15:05 +10:00
Tornado Tech
a0a2e8fabf Ну оно работает 2024-05-03 20:27:12 +10:00
Tornado Tech
765d226a9d Бля 2024-04-28 01:27:41 +10:00
Tornado Tech
25c174af41 Добавил немного базы для заклинаний (Она не работает) 2024-04-28 01:24:55 +10:00
Ed
45d351b2b2 Merge pull request #98 from crystallpunk-14/17-04-2024-upstream
17 04 2024 upstream
2024-04-17 11:58:34 +03:00
Ed
05d6e621d3 Update loadout_groups.yml 2024-04-17 11:44:37 +03:00
Ed
cbf766780b adapt loadouts 2024-04-17 11:32:54 +03:00
Ed
0285c3896c Merge remote-tracking branch 'space-station-14/master' into 17-04-2024-upstream
# Conflicts:
#	Content.Client/Movement/Systems/WaddleAnimationSystem.cs
#	Content.Server/IoC/ServerContentIoC.cs
#	Resources/Changelog/Changelog.yml
#	Resources/Prototypes/Maps/europa.yml
#	Resources/Prototypes/Maps/train.yml
#	Resources/Textures/_CP14/Clothing/Masks/pluto-mask.rsi/meta.json
#	Resources/Textures/_CP14/Objects/Weapons/Melee/Dagger/dagger.rsi/meta.json
#	Resources/Textures/_CP14/Objects/Weapons/Melee/HandheldAxe/handheldAxe.rsi/meta.json
#	Resources/Textures/_CP14/Objects/Weapons/Melee/Sickle/sickle.rsi/meta.json
#	Resources/Textures/_CP14/Objects/Weapons/Melee/TwoHandedSword/zweichhender.rsi/meta.json
2024-04-17 11:16:24 +03:00
Mr. 27
5742dee84a add ancient jumpsuit to passenger loadout (#27035)
inital
2024-04-17 01:37:50 -06:00
PJBot
f825e5e38b Automatic changelog update 2024-04-17 03:37:49 +00:00
Bellwether
7114b1939c Add Nun Hood to Chaplain loadout options (#27025)
Adds Nun Hood to starting Chaplain loadout options
2024-04-17 13:36:44 +10:00
PJBot
5ee597d98c Automatic changelog update 2024-04-17 03:20:36 +00:00
MACMAN2003
c31b3fdf5a Lower player requirements for nukies back down to 20 (#27036)
Update roundstart.yml
2024-04-16 20:19:29 -07:00
PJBot
4fa245f723 Automatic changelog update 2024-04-17 02:56:00 +00:00
metalgearsloth
fcd6c25242 LobbyUI fixes (#27033)
* LobbyUI fixes

I have no idea which were bugs prior but anyway fix stuff.

* More fixes

* Test moment
2024-04-17 12:54:54 +10:00
PJBot
30f73cfb6c Automatic changelog update 2024-04-17 02:51:00 +00:00
Mr. 27
34fa48bff9 Add winter coats and shoes to loadouts (#27022)
* inital

* Update loadout-groups.ftl

* fix order

* add winter boots

* fix test fails
2024-04-17 12:49:53 +10:00
Doctor-Cpu
d5b7e4baf2 Add autism pins to loadout (#27034)
add autism pins to loadout
2024-04-17 11:10:52 +10:00
Pieter-Jan Briers
bbf0505fdc Re-add IAdminRemarksCommon to DB model for SS14.Admin (#27028)
This was removed in #25280 as the relevant DB entities didn't go outside the DB layer anymore. SS14.Admin however still uses them directly (as it only supports Postgres), so the interface is still useful there.
2024-04-17 00:19:36 +02:00
PJBot
5e7f2244fc Automatic changelog update 2024-04-16 20:18:12 +00:00
Mr. 27
e2be85bc52 Fix senior ID cards and other loadout shit (#27017)
* remove senior backpacks

* fix ID cards

* Update atmospheric_technician.yml
2024-04-16 22:17:06 +02:00
PJBot
33888b64d6 Automatic changelog update 2024-04-16 19:57:43 +00:00
Vasilis
46cfd63c4f Add changelog for loadouts (#27020) 2024-04-16 21:56:37 +02:00
PJBot
9102a065a9 Automatic changelog update 2024-04-16 18:49:45 +00:00
tosatur
11207a0649 Reduce clown snore volume (#27012)
* reduced gain by 25dB

* changed volume again

---------

Co-authored-by: Martin Petkovski <63034378+martin69420@users.noreply.github.com>
2024-04-16 20:48:38 +02:00
Agoichi
040497805f Adding the sinner's mask (#97)
* adding sinner's mask

* changing sinner's mask

* changing sinner's mask

* changing sinner's mask

* changing sinner's mask

* changing sinner's mask
2024-04-16 17:51:11 +03:00
metalgearsloth
9bc3e07628 Fix starting gear (#27008)
Slight blunder on the loadout prototype being used and all the names aligning means playtesting didn't catch it earlier.

Ideally player spawning code wouldn't have sucked so I could add tests like I wanted but it is what it is.
2024-04-17 00:44:16 +10:00
metalgearsloth
12766fe6e3 Loadouts redux (#25715)
* Loadouts redux

* Loadout window mockup

* More workout

* rent

* validation

* Developments

* bcs

* More cleanup

* Rebuild working

* Fix model and loading

* obsession

* efcore

* We got a stew goin

* Cleanup

* Optional + SeniorEngineering fix

* Fixes

* Update science.yml

* add

add

* Automatic naming

* Update nukeops

* Coming together

* Right now

* stargate

* rejig the UI

* weh

* Loadouts tweaks

* Merge conflicts + ordering fix

* yerba mate

* chocolat

* More updates

* Add multi-selection support

* test

h

* fikss

* a

* add tech assistant and hazard suit

* huh

* Latest changes

* add medical loadouts

* and science

* finish security loadouts

* cargo

* service done

* added wildcards

* add command

* Move restrictions

* Finalising

* Fix existing work

* Localise next batch

* clothing fix

* Fix storage names

* review

* the scooping room

* Test fixes

* Xamlify

* Xamlify this too

* Update Resources/Prototypes/Loadouts/Jobs/Medical/paramedic.yml

Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>

* Update Resources/Prototypes/Loadouts/loadout_groups.yml

Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>

* Update Resources/Prototypes/Loadouts/Jobs/Civilian/clown.yml

Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>

* Update Resources/Prototypes/Loadouts/Jobs/Civilian/clown.yml

Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>

* Update Resources/Prototypes/Loadouts/loadout_groups.yml

Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>

* Update Resources/Prototypes/Loadouts/Jobs/Security/detective.yml

Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>

* Update Resources/Prototypes/Loadouts/loadout_groups.yml

Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>

* ben

* Margins

---------

Co-authored-by: Firewatch <54725557+musicmanvr@users.noreply.github.com>
Co-authored-by: Mr. 27 <koolthunder019@gmail.com>
Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>
2024-04-16 22:57:43 +10:00
metalgearsloth
fff3fe2a24 Update submodule to 218.1.0 (#26997) 2024-04-16 22:42:53 +10:00
Velcroboy
b23ef00d37 Add QM maintenance airlock (#26982)
Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
2024-04-16 07:34:35 +03:00
Leon Friedrich
faec39ced4 Give names to solution & identity entities (#26993) 2024-04-16 14:26:47 +10:00
Leon Friedrich
229caa10bf Fix some TryGetMind overrides relying on player data (#26992)
* Fix some TryGetMind overrides relying on player data

* A

* Rider has bamboozled me

* Update `data.Mind` before attaching to entity.
2024-04-16 14:26:32 +10:00
PJBot
faba129e78 Automatic changelog update 2024-04-15 22:23:23 +00:00
Velcroboy
cd46282e51 Fix some airlocks with multiple access types (#26980)
Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
2024-04-15 15:22:16 -07:00
MilenVolf
34fbd2874e Fix dragon slowdown on damage (#26975)
Fix dragon slow on damage
2024-04-15 13:53:22 +02:00
Whisper
4a6cf480cc Mobs burn to ashes on excessive heat damage (#26971)
* mobs burn to ashes on excessive heat damage

* remove comment, remove random lines I didn't mean to add

* combine code into behavior

* clean unused

* fix namespace

* drop next to

* fix spawn entities behavior spawning entities outside container
2024-04-15 15:04:15 +10:00
Hannah Giovanna Dawson
3d0fc10673 SS14-26950 Fix Waddling During Improper States (#26965)
* SS14-26950 Fix Waddling During Improper States

Fix some states when a clown can waddle when no clown should be able to waddle, no-matter their clowning powers.

1. You cannot waddle whilst weightless
2. You cannot waddle whilst stunned
3. You cannot waddle whilst slowed down due to stam damage
4. You cannot waddle whilst you're knocked down
5. You cannot waddle whilst you're buckled
6. You cannot waddle whilst crit
7. You cannot waddle whilst dead

There's some argument for being able to waddle whilst on the floor
and doing some bizarre floor-humping exercise but I'm not coding an animation layer system just to handle clowns doing the worm.

* Use a nicer "can move" check
2024-04-14 21:53:22 -04:00
PJBot
637fc2d475 Automatic changelog update 2024-04-15 01:29:04 +00:00
no
7810cbe411 Fix StepTrigger blacklist not working (#26968) 2024-04-14 21:27:58 -04:00
HS
41d2f06ffc meatWall incorrect node fixed (#26966)
changed node in construction meatWall
2024-04-14 17:38:13 -07:00
deltanedas
516f5f3161 nerf incendiary grenade (#26959)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-04-14 20:11:28 -04:00
PJBot
563f304ac5 Automatic changelog update 2024-04-14 22:27:52 +00:00
Token
7a6067989f Make lockers can be deconstructed only when unlocked now (#26961)
* Fix lockers are not deconstrucable now

Lockers are was deconstructable event when they closed and you didn't have access to them. In short - get stuff by 5 seconds, 5 sec it's time to screw down any locker, except LockerBaseSecure one

* Revert un-destructable lockers fix

Make lockers destructable again

* Fix lockers that deconstructable only when unlocked now
2024-04-15 00:26:46 +02:00
lzk
cf8f68c7e5 Fix rubber hammer being unshaded (#26956) 2024-04-14 23:53:45 +02:00
PJBot
5270e6f5f9 Automatic changelog update 2024-04-14 20:05:12 +00:00
beck-thompson
0a29508f43 Fixed cybersun pen attacking noise (#26951)
* Uupdated the cyberpen

* Updated noise

* Removed flashlight
2024-04-14 22:04:05 +02:00
Ed
76afac43f9 minutiae grinding (#96)
* add floor crafting, add crafting lcoalization

* add chest construction

* add chest localization

* Update chests.yml
2024-04-14 22:02:04 +03:00
Ed
684ec72541 Upstrearm sync (#94)
* Fix for the salvage ice labs map. (#26928)

* done

* more work

* Automatic changelog update

* Update Credits (#26938)

Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>

* Fix cryostorage identifying unknown characters as captain (#26927)

Fixed cryostorage getting captain's record for unknown jobs.
Also localized Unknown job string.

* Automatic changelog update

* Fixed Honkbot/jonkbot honking like crazy, gave honkbot/jonkbot standard idle ai. (#26939)

* Fixed Honkbot/jonkbot honking like crazy, gave honkbot/jonkbot standard idle ai.

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

---------

Co-authored-by: Tayrtahn <tayrtahn@gmail.com>

* Automatic changelog update

* Bug fix: Force cancellation of RCD constructions if the construction type is changed (#26935)

Force cancellation of RCD constructions if the construction type is changed

* Fix standart -> standard and dressfilled test fail (#26942)

Fix standart -> standard

* Add Ability to stop sound when MobState is Dead (#26905)

* Add stopsWhenEntityDead to sound components

* Convert component

* Review

* Fix dupe sub

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Automatic changelog update

* Fix rockets and lasers looking like they have nothing loaded (#26933)

* Automatic changelog update

* You can now see paper on crates (with color!) (#26834)

* Implement changes on not-cooked branch

* Made it work

* Fix update appearance calls

* Fix extra indents, clean-up code, fix tests hopefully

* Fix hammy cagecrate

* Fix messing up the yml, add artifact crate specific labels back in

* Visual Studio hates yml, sad

* Seperate the colors for cargonia

* sorry json

* make label move with artifact door

* Apply suggestion changes

Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>

* Fix remaining crate offsets, add a few for livestock and graves (why are you labeling graves) and coffin label sprites (why are you labeling coffins??)

---------

Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>

* Make UtensilSystem and SharpSystem not run AfterInteract if it has already been handled (#25826)

* Make UtensilSystem and SharpSystem not run AfterInteract if it has already been handled

* merge conflicts

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Automatic changelog update

* Add two-message overload to PopupPredicted (#26907)

Added two-message overload to PopupPredicted

* Update submodule to 218.0.0 (#26945)

* Autism pins! (#25597)

* hee hee he ha ha

* added gold varients, forgive me for my spritework

* maints loot, copying from past PRs

* Trying to fix RSI

* speedran these sprites in break time, pictures will be later

* Fixed/Tweaked glows

* consensus

* gregregation

* dam copiryte

* oops i forgot to delete 2 fields hope this works

* Automatic changelog update

* Fix database round start date issues (#26838)

How can ONE DATABASE COLUMN have so many cursed issues I don't know, but it certainly pissed off the devil in its previous life.

The start_date column on round entities in the database was added by https://github.com/space-wizards/space-station-14/pull/21153. For some reason, this PR gave the column a nonsensical default value instead of making it nullable. This default value causes the code from #25280 to break. It actually trips an assert though that's not what the original issue report ran into.

This didn't get noticed on wizden servers because we at some point backfilled the start_date column based on the stored admin logs.

So I change the database model to make this column nullable, updated the C# code to match, and made the existing migration set the invalid values to be NULL instead. Cool.

Wait how's SQLite handle in this scenario anyways? Well actually turns out the column was *completely broken* in the first place!

The code for inserting into the round table was copy pasted between SQLite and PostgreSQL, with the only difference being that the SQLite key manually assigned the primary key instead of letting SQLite AUTOINCREMENT it. And then the code to give a start_date value was only added to the PostgreSQL version (which is actually in the base class already). So for SQLite that column's been filled up with the same invalid default the whole time.

Why was the code manually assigning a PK? I checked the SQLite docs for AUTOINCREMENT[1], and the behavior seems appropriate.

I removed the SQLite-specific code path and it just seems to work regardless. The migration just sets the old values to NULL too.

BUT WAIT, THERE'S MORE!

Turns out just doing the migration on SQLite is a pain in the ass! EF Core has to create a new table to apply the nullability change, because SQLite doesn't support proper ALTER COLUMN. This causes the generated SQL commands to be weird and the UPDATE for the migration goes BEFORE the nullability change... I ended up having to make TWO migrations for SQLite. Yay.

Fixes #26800

[1]: https://www.sqlite.org/autoinc.html

* Fix options menu crashing in replays (#26911)

Not having the nullable set properly is annoying but fixing that would probably be a significant amount of work.

* Greyscale color clothing (#26943)

* greyscales color gloves, color jumpsuits, and shoes

* remove popbob

* fix test fails

* Automatic changelog update

* WT550 Buffs + Burst Mode for WT550 & C-20R (#26886)

* Slightly increased WT550 Firerate, drastically reduced recoil, and given it the option to fire in 5 round bursts.

* Given the C-20 a 5 round burst aswell

* Automatic changelog update

* make holoparasites actually holographic (#26862)

it's over

* Automatic changelog update

* Add character sheets to board game crate (#26926)

add character sheets to board game crate

* Automatic changelog update

* Game server admin API (#26880)

* Reapply "Game server api" (#26871)

This reverts commit 3aee197923.

* Rewrite 75% of the code it's good now

* Wield recoil components (#26915)

* WieldRecoilComponents

* WieldRecoilComponents

* Update Content.Shared/Weapons/Ranged/Components/GunWieldBonusComponent.cs

Co-authored-by: Whisper <121047731+QuietlyWhisper@users.noreply.github.com>

* Update Content.Shared/Weapons/Ranged/Components/GunWieldBonusComponent.cs

---------

Co-authored-by: Whisper <121047731+QuietlyWhisper@users.noreply.github.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Clown shoes make you waddle, as God intended (#26338)

* Clown shoes make you waddle, as God intended

* OOPS

* Toned down, client system name fix

* Tidy namespacing for @deltanedas

* Refactor to handle prediction better, etc.

* Resolve PR comments.

* Automatic changelog update

* Use round time instead of server time for criminal history (#26949)

make criminal records computer use round time for history instead of the server time

* Rotate and Offset station CCVar nuke (#26175)

* no content

* add noRot to Europa

* bruh. and this

* yay

* fix

* Update debug.yml

---------

Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com>
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
Co-authored-by: superjj18 <gagnonjake@gmail.com>
Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: GreaseMonk <1354802+GreaseMonk@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Co-authored-by: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com>
Co-authored-by: Verm <32827189+Vermidia@users.noreply.github.com>
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
Co-authored-by: Terraspark4941 <terraspark4941@gmail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com>
Co-authored-by: BramvanZijp <56019239+BramvanZijp@users.noreply.github.com>
Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com>
Co-authored-by: Tyzemol <85772526+Tyzemol@users.noreply.github.com>
Co-authored-by: Froffy025 <78222136+Froffy025@users.noreply.github.com>
Co-authored-by: Whisper <121047731+QuietlyWhisper@users.noreply.github.com>
Co-authored-by: Hannah Giovanna Dawson <karakkaraz@gmail.com>
Co-authored-by: ilya.mikheev.coder <imc-ext+github@ilyamikcoder.com>
2024-04-14 20:34:16 +03:00
Ed
3bc8114498 Update README.md 2024-04-14 18:40:55 +03:00
Ed
635c0ce99c Fire spreading (#92)
* fire update

* add flammable to more ent, add floors

* add cool fire sprites, noSpawn tiles

* fix floor collider

* floor resprite

* Update floors.yml

* fixes

* Update tables.yml
2024-04-14 18:31:23 +03:00
Ed
44b20f60ff Rotate and Offset station CCVar nuke (#26175)
* no content

* add noRot to Europa

* bruh. and this

* yay

* fix
2024-04-15 00:26:28 +10:00
ilya.mikheev.coder
fbec5d18cf Use round time instead of server time for criminal history (#26949)
make criminal records computer use round time for history instead of the server time
2024-04-14 23:33:04 +10:00
PJBot
055c5ab323 Automatic changelog update 2024-04-14 12:14:00 +00:00
Hannah Giovanna Dawson
ef42fb3806 Clown shoes make you waddle, as God intended (#26338)
* Clown shoes make you waddle, as God intended

* OOPS

* Toned down, client system name fix

* Tidy namespacing for @deltanedas

* Refactor to handle prediction better, etc.

* Resolve PR comments.
2024-04-14 08:12:54 -04:00
Froffy025
a7fad5d439 Wield recoil components (#26915)
* WieldRecoilComponents

* WieldRecoilComponents

* Update Content.Shared/Weapons/Ranged/Components/GunWieldBonusComponent.cs

Co-authored-by: Whisper <121047731+QuietlyWhisper@users.noreply.github.com>

* Update Content.Shared/Weapons/Ranged/Components/GunWieldBonusComponent.cs

---------

Co-authored-by: Whisper <121047731+QuietlyWhisper@users.noreply.github.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-04-14 20:16:23 +10:00
Pieter-Jan Briers
9d0dfcf2b9 Game server admin API (#26880)
* Reapply "Game server api" (#26871)

This reverts commit 3aee197923.

* Rewrite 75% of the code it's good now
2024-04-14 20:13:29 +10:00
PJBot
9752746775 Automatic changelog update 2024-04-14 08:55:46 +00:00
Tyzemol
7a86b1d097 Add character sheets to board game crate (#26926)
add character sheets to board game crate
2024-04-14 02:54:40 -06:00
PJBot
734b6f321d Automatic changelog update 2024-04-14 08:54:03 +00:00
Mr. 27
c6ef37cc5f make holoparasites actually holographic (#26862)
it's over
2024-04-14 02:52:57 -06:00
PJBot
e0ff7f7625 Automatic changelog update 2024-04-14 08:52:13 +00:00
BramvanZijp
7b0dd31b1f WT550 Buffs + Burst Mode for WT550 & C-20R (#26886)
* Slightly increased WT550 Firerate, drastically reduced recoil, and given it the option to fire in 5 round bursts.

* Given the C-20 a 5 round burst aswell
2024-04-14 02:51:07 -06:00
PJBot
7787a82d03 Automatic changelog update 2024-04-14 08:42:45 +00:00
Flareguy
2b8e26fa2e Greyscale color clothing (#26943)
* greyscales color gloves, color jumpsuits, and shoes

* remove popbob

* fix test fails
2024-04-14 02:41:39 -06:00
metalgearsloth
4cb344cc30 Fix options menu crashing in replays (#26911)
Not having the nullable set properly is annoying but fixing that would probably be a significant amount of work.
2024-04-14 09:10:37 +02:00
Pieter-Jan Briers
d3ac3d06bb Fix database round start date issues (#26838)
How can ONE DATABASE COLUMN have so many cursed issues I don't know, but it certainly pissed off the devil in its previous life.

The start_date column on round entities in the database was added by https://github.com/space-wizards/space-station-14/pull/21153. For some reason, this PR gave the column a nonsensical default value instead of making it nullable. This default value causes the code from #25280 to break. It actually trips an assert though that's not what the original issue report ran into.

This didn't get noticed on wizden servers because we at some point backfilled the start_date column based on the stored admin logs.

So I change the database model to make this column nullable, updated the C# code to match, and made the existing migration set the invalid values to be NULL instead. Cool.

Wait how's SQLite handle in this scenario anyways? Well actually turns out the column was *completely broken* in the first place!

The code for inserting into the round table was copy pasted between SQLite and PostgreSQL, with the only difference being that the SQLite key manually assigned the primary key instead of letting SQLite AUTOINCREMENT it. And then the code to give a start_date value was only added to the PostgreSQL version (which is actually in the base class already). So for SQLite that column's been filled up with the same invalid default the whole time.

Why was the code manually assigning a PK? I checked the SQLite docs for AUTOINCREMENT[1], and the behavior seems appropriate.

I removed the SQLite-specific code path and it just seems to work regardless. The migration just sets the old values to NULL too.

BUT WAIT, THERE'S MORE!

Turns out just doing the migration on SQLite is a pain in the ass! EF Core has to create a new table to apply the nullability change, because SQLite doesn't support proper ALTER COLUMN. This causes the generated SQL commands to be weird and the UPDATE for the migration goes BEFORE the nullability change... I ended up having to make TWO migrations for SQLite. Yay.

Fixes #26800

[1]: https://www.sqlite.org/autoinc.html
2024-04-14 07:39:43 +02:00
PJBot
dbf8a036ec Automatic changelog update 2024-04-14 05:36:41 +00:00
Terraspark4941
d2d62b97ac Autism pins! (#25597)
* hee hee he ha ha

* added gold varients, forgive me for my spritework

* maints loot, copying from past PRs

* Trying to fix RSI

* speedran these sprites in break time, pictures will be later

* Fixed/Tweaked glows

* consensus

* gregregation

* dam copiryte

* oops i forgot to delete 2 fields hope this works
2024-04-14 15:35:35 +10:00
metalgearsloth
54b3d7fe45 Update submodule to 218.0.0 (#26945) 2024-04-14 15:05:35 +10:00
Tayrtahn
9107d421bd Add two-message overload to PopupPredicted (#26907)
Added two-message overload to PopupPredicted
2024-04-14 13:42:45 +10:00
PJBot
f1d1e6c6fd Automatic changelog update 2024-04-14 03:40:08 +00:00
DrSmugleaf
c67948407e Make UtensilSystem and SharpSystem not run AfterInteract if it has already been handled (#25826)
* Make UtensilSystem and SharpSystem not run AfterInteract if it has already been handled

* merge conflicts

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-14 13:39:22 +10:00
Verm
96ad9002f1 You can now see paper on crates (with color!) (#26834)
* Implement changes on not-cooked branch

* Made it work

* Fix update appearance calls

* Fix extra indents, clean-up code, fix tests hopefully

* Fix hammy cagecrate

* Fix messing up the yml, add artifact crate specific labels back in

* Visual Studio hates yml, sad

* Seperate the colors for cargonia

* sorry json

* make label move with artifact door

* Apply suggestion changes

Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>

* Fix remaining crate offsets, add a few for livestock and graves (why are you labeling graves) and coffin label sprites (why are you labeling coffins??)

---------

Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
2024-04-14 13:39:02 +10:00
PJBot
cb4561fe96 Automatic changelog update 2024-04-14 03:18:23 +00:00
DrSmugleaf
13cef85a6e Fix rockets and lasers looking like they have nothing loaded (#26933) 2024-04-14 13:17:17 +10:00
PJBot
bbff00cd2a Automatic changelog update 2024-04-14 03:13:44 +00:00
GreaseMonk
da618d791a Add Ability to stop sound when MobState is Dead (#26905)
* Add stopsWhenEntityDead to sound components

* Convert component

* Review

* Fix dupe sub

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-14 13:12:38 +10:00
metalgearsloth
dc19964d84 Fix standart -> standard and dressfilled test fail (#26942)
Fix standart -> standard
2024-04-14 13:07:14 +10:00
chromiumboy
33e5e4e581 Bug fix: Force cancellation of RCD constructions if the construction type is changed (#26935)
Force cancellation of RCD constructions if the construction type is changed
2024-04-14 13:01:01 +10:00
PJBot
ba9091ff59 Automatic changelog update 2024-04-14 02:52:30 +00:00
superjj18
8272d7a345 Fixed Honkbot/jonkbot honking like crazy, gave honkbot/jonkbot standard idle ai. (#26939)
* Fixed Honkbot/jonkbot honking like crazy, gave honkbot/jonkbot standard idle ai.

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

---------

Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
2024-04-13 22:51:24 -04:00
PJBot
d44db87bfb Automatic changelog update 2024-04-14 02:20:49 +00:00
Tayrtahn
9b97a2e05d Fix cryostorage identifying unknown characters as captain (#26927)
Fixed cryostorage getting captain's record for unknown jobs.
Also localized Unknown job string.
2024-04-14 12:19:42 +10:00
github-actions[bot]
1f4a01aa38 Update Credits (#26938)
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
2024-04-13 21:46:55 -04:00
PJBot
ed065e8a3d Automatic changelog update 2024-04-13 20:50:08 +00:00
Boaz1111
1bf97c94ee Fix for the salvage ice labs map. (#26928)
* done

* more work
2024-04-13 23:49:02 +03:00
Ed
3dff87b3e1 NoSpawn markers and weapons (#90)
* markers noSpawn: true

* weapons noSpawn
2024-04-13 23:22:33 +03:00
Agoichi
a96c32df11 wooded chestb (#89)
* wooded chestb

* Update Resources/Textures/_CP14/Structures/Storage/Crates/woodenchest.rsi/meta.json

Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>

* Update base_chest.yml

* Update chests.yml

---------

Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
2024-04-13 23:22:25 +03:00
Ed
aeb4772c14 Пачка несвязанных обновлений (#88)
* add coins, start ftl

* localization patch

* fixes

* localization patch

* tiefling

* Update structures.ftl

* add tag

* Update LICENSE.TXT

* Update README.md

* move dagger and sickle to belt slot

* fix license

* Update equipped-CLOAK.png
2024-04-13 19:13:16 +03:00
Ed
a841fc9fcd Merge remote-tracking branch 'space-station-14/master' 2024-04-13 18:59:09 +03:00
PJBot
154b8606f9 Automatic changelog update 2024-04-13 15:37:11 +00:00
TsjipTsjip
4833074514 Replace SetDamage call with TryChangeDamage in ImmovableRodSystem.cs (#26902) 2024-04-13 11:36:33 -04:00
ShadowCommander
037a7d7d3d Fix pulling a new entity when already pulling an entity (#26499)
Fix pulling when already pulling

The TryStopPull were failing due to wrong arguments provided.
Replacing the virtual item in hand with a different pull was failing due to the hand not being cleared.

Fix stop pulling checks that had the wrong variables provided.

VirtualItems are already queue deleted at the end of HandleEntityRemoved.
2024-04-13 11:36:05 -04:00
FungiFellow
eeb460fb29 Removed Salv Borg Crusher Dagger (#26865) 2024-04-13 11:35:10 -04:00
PJBot
b086779165 Automatic changelog update 2024-04-13 15:26:59 +00:00
keronshb
d4b7bc5aa3 Fixes polymorph cooldowns (#26914)
fixes polymorph cooldowns
2024-04-13 11:26:51 -04:00
DrSmugleaf
2d53cfeabc Fix guns that spawn without a magazine looking like they have one (#26922) 2024-04-13 11:26:25 -04:00
DrSmugleaf
b51482f51a Fix incorrect "Cycled" and "Bolted" popups when (un)wielding a gun (#26924) 2024-04-13 11:25:53 -04:00
Ed
f6a28d4afc Добавлены деньги. Добавлена локализация на англ для всех прототипов. (#86)
* add coins, start ftl

* localization patch

* fixes

* localization patch

* tiefling

* Update structures.ftl
2024-04-13 18:00:14 +03:00
PJBot
5d00305a22 Automatic changelog update 2024-04-13 02:37:34 +00:00
Vasilis
a49a576b03 Uncooked animal proteins is safe for animal stomachs only (#26906)
Uncooked animal proteins is safe for animal stomachs
2024-04-13 12:36:28 +10:00
PJBot
2360376b40 Automatic changelog update 2024-04-13 01:47:08 +00:00
osjarw
0a1ce9dd43 Fixed magboot activation distance (#26912) 2024-04-12 21:46:00 -04:00
DrSmugleaf
24b6456735 Fix the stripping menu being openable without StrippingComponent (#26908) 2024-04-12 16:22:07 -07:00
Ed
0f3ec01281 Content pack (#83)
* content pack

* d

* update

* Update meta.json

* +2 clothing update

* +more clothings

* remove canvas

* blacksmith arpon

* +hairs and marking localization
2024-04-13 01:05:58 +03:00
TsjipTsjip
998bf45368 Health analyzer UI unit correction (#26903)
Correct Kelvin displayed on health analyzer UI, use T0C constant.
2024-04-12 16:47:44 -04:00
Ed
485873839b Работа для кузнеца (#80)
* add content

* Update CPSharpenedComponent.cs

* Update twoHandedSword.yml

* update
2024-04-12 21:09:54 +03:00
PJBot
c8f75d9918 Automatic changelog update 2024-04-12 08:03:12 +00:00
Ko4ergaPunk
279e01c3d2 Strobes added (#26083)
* Done

* Adds new

* empty

* attributions

* empty

* strobe admin deleted
2024-04-12 11:02:06 +03:00
PJBot
2a5d23f3f1 Automatic changelog update 2024-04-12 07:16:10 +00:00
deltanedas
3a00e8c59c Sterile swab dispenser instead of box (#24986)
* sterile swab dispenser

* trust

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-04-12 17:15:04 +10:00
Nemanja
9d5a3992fa uplink and store freshening (#26444)
* uplink and store freshening

* more

* im gonna POOOOOOGGGGGGG

* we love it
2024-04-12 17:07:25 +10:00
Velcroboy
7d480acb0c Add drink container suffixes (#26835)
Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
2024-04-12 17:04:35 +10:00
PJBot
85aef16954 Automatic changelog update 2024-04-12 06:59:08 +00:00
Token
e88b2467ca NoticeBoard is craftable now (#26847)
* NoticeBoard is craftable now

* Fix notice board to proper name capitalization

* Fix notice board proper name in description

* Update Resources/Prototypes/Recipes/Construction/furniture.yml

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-04-12 16:58:02 +10:00
Brandon Hu
261e5354d3 Fix grammar in changelog (#26894)
Grammar

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-12 16:56:35 +10:00
PJBot
8f17bf1a3d Automatic changelog update 2024-04-12 06:51:51 +00:00
liltenhead
e12223c355 Remove reagent slimes from ghost role pool (#26840)
reagentslimeghostrole
2024-04-12 16:50:45 +10:00
Token
882aeb0314 Update .editorconfig to correspond Code Conventions (#26824)
Update editorconfig to Code Style

End of line is: CRLF (suggestion)
Namespace declarations are: file scoped (suggestion). Instead of block scoped
2024-04-12 16:50:10 +10:00
PJBot
4627c7c859 Automatic changelog update 2024-04-12 06:45:53 +00:00
Verm
b895e557d4 Fix shaker sprites (#26899)
* Change basefoodshaker to parent from basefoodcondiment instead

* Make them still refillable
2024-04-12 16:44:47 +10:00
Tayrtahn
264bf7199d Allow advertisement timers to prewarm (#26900)
Allow advertisement timers to prewarm.
2024-04-12 16:42:20 +10:00
Tayrtahn
8e9d2744f3 Fix potted plant popup/sfx spam (#26901)
Fixed potted plant hide popup/sfx spam.
2024-04-12 16:30:34 +10:00
PJBot
6fa90e06c7 Automatic changelog update 2024-04-11 20:49:54 +00:00
lunarcomets
9d62b3c3e6 Cryogenic storage tweaks (#26813)
* make cryo remove crewmember's station record when going to cryo

* Revert "make cryo remove crewmember's station record when going to cryo"

This reverts commit 9ac9707289b5e553e3015c8c3ef88a78439977c6.

* make cryo remove crewmember from station records when the mind is removed from the body

* add stationwide announcement for people cryoing (remember to change pr title and desc)

* minor changes

* announcement actually shows job now

* requested changes

* get outta here derivative
2024-04-11 16:48:46 -04:00
Mr. 27
75d3502d26 fix evil roleplay changelog (#26893)
agh
2024-04-11 21:11:13 +02:00
keronshb
036abacbb7 Immovable Rod changes (#26757) 2024-04-11 13:40:02 -04:00
chromiumboy
fc5a90be0d Bug fix for deconstructing tiles and lattice with RCDs (#26863)
* Fixed mixed deconstruction times for tiles and lattice

* Lattice and power cables can be deconstructed instantly
2024-04-11 22:26:34 +10:00
PJBot
00dc99769c Automatic changelog update 2024-04-11 12:22:24 +00:00
Pieter-Jan Briers
210ed3ece4 Fix TEG assert (#26881)
It's possible to trigger this by stacking it weirdly with the spawn panel. Just make it bail instead.
2024-04-11 22:22:21 +10:00
Jark255
2bcdb608a3 Fix door electronics configurator usage (#26888)
* allow usage of network configurator for door electronics

* add checks for "allowed" items
2024-04-11 22:21:15 +10:00
Ed
f33bc2bd7a Merge pull request #75 from crystallpunk-14/new-player-inventory
New player inventory
2024-04-11 11:13:02 +03:00
Ed
272882fdfd move to _CP14 2024-04-11 10:45:59 +03:00
Ghagliiarghii
9eb1e12022 Update ashtray to allow all cigarettes / cigars (#26864)
* Update ashtray to allow all cigarettes / cigars

This also includes joints (as they are technically cigarettes)

* ?
2024-04-10 20:22:29 -04:00
PJBot
8c16b46613 Automatic changelog update 2024-04-10 21:21:11 +00:00
Flareguy
4017f9bd28 Add emergency nitrogen lockers (#26752) 2024-04-10 14:20:05 -07:00
Ed
89342d658d try2 2024-04-11 00:18:33 +03:00
Ed
ebe404e449 some test fix 2024-04-10 23:52:50 +03:00
PJBot
f1cbf934b0 Automatic changelog update 2024-04-10 20:07:37 +00:00
deltanedas
1cdf05a7a7 fix lots of door access (#26858)
* dirty after calling SetAccesses

* fix door access

* D

* pro ops

* nukeop

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-04-10 14:06:31 -06:00
Ed
b65c8d4e65 tiefling + ui update 2024-04-10 23:05:33 +03:00
Ed
adb9c62ca1 Merge branch 'master' into new-player-inventory 2024-04-10 22:07:02 +03:00
Ed
69da114202 Merge pull request #79 from crystallpunk-14/armor
fix eyes + armor
2024-04-10 21:34:18 +03:00
Ed
a66f9d976f fix eyes + armor 2024-04-10 21:31:39 +03:00
PJBot
57911975c7 Automatic changelog update 2024-04-10 17:52:33 +00:00
botanySupremist
58e4b5fe4c Clipping a harvestable plant yields undamaged seeds (#25541)
Clipping a plant in any condition currently causes it and its clippings to be damaged.

Make clipping harvestable (already eligible for seed extractor) plants yield seeds at full health.
2024-04-10 10:51:25 -07:00
PJBot
7d599a7199 Automatic changelog update 2024-04-10 17:29:11 +00:00
Джексон Миссиссиппи
935127f25f Give botanists droppers (#26839)
Start botanists with droppers so that they can better dose robust harvest or mutagen.
2024-04-10 10:28:03 -07:00
Ed
e04725f7ed Merge pull request #76 from crystallpunk-14/races
Тифлинги!
2024-04-10 16:48:24 +03:00
Pieter-Jan Briers
3aee197923 Revert "Game server api" (#26871)
Revert "Game server api (#24015)"

This reverts commit 297853929b.
2024-04-10 14:19:32 +02:00
Tornado Tech
4e47d31f4e Тифлинги готовы 2024-04-10 21:24:25 +10:00
Tornado Tech
3c2d9d04c6 Merge branch 'master' into races 2024-04-10 20:25:25 +10:00
Simon
297853929b Game server api (#24015)
* Revert "Revert "Game server api (#23129)""

* Review pt.1

* Reviews pt.2

* Reviews pt. 3

* Reviews pt. 4
2024-04-10 02:37:16 -07:00
Ed
5a5224a577 Merge pull request #74 from crystallpunk-14/upstream2
Upstream sync
2024-04-10 11:54:51 +03:00
Ed
d0e2e15d8c Merge remote-tracking branch 'space-station-14/master' into upstream2 2024-04-10 11:38:32 +03:00
Ed
7d0bb3d1d1 Merge pull request #73 from crystallpunk-14/new-upstream
New upstream?
2024-04-10 11:34:18 +03:00
Tornado Tech
685f85b1ad Флаги в систему одежды 2024-04-10 13:33:39 +10:00
Tornado Tech
4f0d11144c Добавлены флаги для слотов и их отображение 2024-04-10 13:28:06 +10:00
PJBot
28e5db7be5 Automatic changelog update 2024-04-09 22:22:02 +00:00
SkaldetSkaeg
aa2e1facbd Flippolighter_fix (#26846)
Flippolighter has realy loud sound, no UseDelay and server errors
2024-04-09 18:20:57 -04:00
PJBot
3ae4b5884b Automatic changelog update 2024-04-09 22:19:13 +00:00
Killerqu00
fd067731b5 Skipping bounties (#26537)
* add button to menu

* networking and component work

* try to add access stuff

* main functionality done

* add access lock? I think?

* remove extra line

* fix access system

* move SkipTime to StationCargoBountyDatabaseComponent

* Disable/Enable skip button based on cooldown

* remove debugging

* add access denied sound

* remove DataField tags

* dynamic timer
2024-04-09 18:18:07 -04:00
Wrexbe (Josh)
682afd4ae4 Improve access overlay (#26667)
* Improve access overlay

* review changes

---------

Co-authored-by: wrexbe <wrexbe@protonmail.com>
2024-04-09 18:16:42 -04:00
PJBot
862f820708 Automatic changelog update 2024-04-09 15:26:27 +00:00
Pieter-Jan Briers
d879665b52 Add new "grant_connect_bypass" admin command (#26771)
This command allows you to grant a player temporary privilege to join regardless of player cap, whitelist, etc. It does not bypass bans.

The API for this is IConnectionManager.AddTemporaryConnectBypass().

I shuffled around the logic inside ConnectionManager. Bans are now checked before panic bunker.
2024-04-09 17:25:21 +02:00
deltanedas
6d695dd326 dirty after calling SetAccesses (#26849)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-04-09 09:17:31 -04:00
Ed
d3c147ae28 Update meta.json 2024-04-09 12:17:59 +03:00
Ed
9ca762564c Merge remote-tracking branch 'space-station-14/master' into new-upstream 2024-04-09 12:15:46 +03:00
PJBot
694ae001fd Automatic changelog update 2024-04-09 08:05:58 +00:00
KittenColony
a1fcfed7d9 Gauze Markings 3 - Revenge of the Wrap (#25481)
* Insectoid Gauze, Added racial marks to variants

* removed excess r from gauze_moth_lowerleg_r

* Update gauze.ftl

* moved all markings to Overlay Category

* fixed localization error

* a
2024-04-09 10:04:51 +02:00
Tayrtahn
a0de0ab1f8 Server-only component YAML cleanup (#26836)
* First pass cleaning up server-only YAML errors.

* Second pass
2024-04-09 00:20:49 -04:00
Mr. 27
e64288a8fe add ratvar to ion storm laws (#26833)
Praise Ratvar
2024-04-08 18:37:13 -04:00
Hannah Giovanna Dawson
2cc4098406 Ion Storm Laws Review (#26703)
* Ion Storm Laws Review

Have reviewed the Ion Storm law dictionary
and tidied it up.

Specifically I've tried to keep the same number
of entries in each section, or slightly
increased them, where I've removed or
altered a string.

Of note, specific colours and "rainbow" have been
removed from adjectives and replaced with words
like "Cheese-Eating", as colours are kinda boring
themselves and they've been a cause of a few
accidentally-racist laws.

* Resolve some feedback.

* Remove the pull request joke, noooooo

* Re-add big bite burgers as they are actually a thing

* Append some more suggestions
2024-04-08 16:18:11 -04:00
PJBot
14866173c0 Automatic changelog update 2024-04-08 15:35:41 +00:00
Hanz
263469dffa Spears equippable to suit storage (#26724)
* meat and potatoes

hahaha

* DAMAGE

yes

* come on

* Check again

* Guhhhhh

guh
2024-04-08 11:34:35 -04:00
deltanedas
2f5c639a15 predict humanoid identity examine (#26769)
* predict humanoid identity examine

* actually server doesnt need proto anymore

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-04-08 11:16:21 -04:00
c4llv07e
3d6d7820d6 Fix tray scanner not updating it's range. (#26789)
Fix tray scanner not updating it's range on change.

Add range value to the tray scanner state.to synchronize it between
client and server.
2024-04-08 11:12:39 -04:00
PJBot
7f56ba0155 Automatic changelog update 2024-04-08 15:12:04 +00:00
lzk
6ce53709bd Make clothing cheaper and split clothing restock (#26805)
* Make clothing cheaper

* bueah

* proper price

* :trollface:
2024-04-08 11:10:58 -04:00
PJBot
2bbae3ecc8 Automatic changelog update 2024-04-08 15:07:04 +00:00
lzk
4a7aa300c8 Make bombsuits similar (#26806) 2024-04-08 11:05:57 -04:00
lzk
7188b509df fix bodybag id case (#26823)
* Fix body bag id

* migration
2024-04-08 10:27:26 -04:00
Crotalus
a9df8cf18a Fix lathe materials list bug (#26826)
Fix lathe material list bug
2024-04-08 09:46:42 -04:00
deepdarkdepths
3851fcbc32 Fixes elite operative figurine description (#26814)
desc
2024-04-08 06:14:23 -04:00
PJBot
5e2f1e735d Automatic changelog update 2024-04-08 03:18:35 +00:00
chromiumboy
f784e9ca4e Bug fixes for RCD (#26792)
Various fixes
2024-04-08 13:17:28 +10:00
PJBot
9811173a1a Automatic changelog update 2024-04-08 03:17:18 +00:00
Crotalus
a178754980 Show missing materials in lathes tooltip (#26795)
* Lathes: Show missing materials amount in tooltip

* Use AppendLine and remove the last newline at the end
2024-04-07 23:16:11 -04:00
BriBrooo
356c2799cf fixed cigarette sprites (#26801) 2024-04-07 23:08:33 -04:00
Nemanja
c95bbce4a6 prevent placing dead bodies in cryostorage (#26810) 2024-04-08 13:07:23 +10:00
deepdarkdepths
5ecff8a04b Sanitizes "tbf" into "to be fair" (#26811)
add
2024-04-08 13:02:46 +10:00
Ed
9597d6a84a Приподнимаем стены (#68)
* remake debug wall

* wood wall

* Update walls.yml
2024-04-07 11:23:10 +03:00
Ubaser
f4ca034075 Update Core (#26791)
* add

* invalid
2024-04-06 23:54:25 -06:00
Tornado Tech
a78b9103a7 Сделана нормальная раскладка слотов кражи 2024-04-07 12:25:55 +10:00
Tornado Tech
fc9596a101 Сделана нормальная раскладка слотов 2024-04-07 12:13:58 +10:00
Tornado Tech
daf300af28 Новый шаблончик инвентаря для нашего человека 2024-04-07 11:37:22 +10:00
Tornado Tech
0dea6f5c9c Добавил новую тему 2024-04-07 11:35:55 +10:00
Tornado Tech
f4c0a21b7b Переименовал папку с новым интерфейсом 2024-04-07 11:34:29 +10:00
github-actions[bot]
857f29f583 Update Credits (#26787)
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
2024-04-06 21:23:39 -04:00
PJBot
637526773f Automatic changelog update 2024-04-07 00:00:03 +00:00
Verm
7face4638e Fix Water cooler visuals (#26784)
Fix watercooler visuals
2024-04-06 17:58:57 -06:00
PJBot
98d3ee293c Automatic changelog update 2024-04-06 22:28:22 +00:00
osjarw
1b8fc71dd6 temporarily remove broken anom behavior (#26775)
Removed `Moving` anom behavior
2024-04-07 01:27:16 +03:00
Ed
ec6e33e839 Кастомная музыка лобби. Пак неадаптированной локализации. (#59)
* add music, add lobby localization

* delete unused ftl

* add localization variable

* move to _CP14
2024-04-06 23:48:30 +03:00
PJBot
53de870407 Automatic changelog update 2024-04-06 20:17:54 +00:00
PursuitInAshes
8adfa19181 Add sake bottle (#26776) 2024-04-06 13:16:47 -07:00
Ed
9720dcffc5 spears and mace (#66) 2024-04-06 22:58:08 +03:00
Ed
d0b2666454 tieflings horns and tails (#61)
* add horns and tails

* fix meta

* elf ears

* broken
2024-04-06 22:44:46 +03:00
Ed
bc7fa2954c Новый стиль UI, под новые слоты. (#60)
* reskin

* storage skin
2024-04-06 18:52:54 +03:00
Ed
c7b2c19dd1 Upstream suka (#45)
* Make BaseMedicalPDA abstract (#26567)

* Fix GasMixers/Filters not working (#26568)

* Fix GasMixers/Filters not working

* OKAY GAS FILTERS TOO

---------

Co-authored-by: Plykiya <plykiya@protonmail.com>

* Industrial Reagent Grinder Hotfix (#26571)

fixed

* Give stores the ability to check for owner only (#26573)

adds a check if the store belongs to the user

* Fix round start crash (causing instant restart) (#26579)

* Fix round start crash

* Make `TryCreateObjective` more error tolerant

* Update engine to v217.1.0 (#26588)

* Fix initial infected icon hiding (#26585)

* Fix Meta evac shuttle name (#26587)

* Make timer ignore client predict setting (#26554)

* Make timer ignore client predict setting

* making tests run

---------

Co-authored-by: wrexbe <wrexbe@protonmail.com>

* Make advertise system survive no map inits (#26553)

* Make advertise system survive no map inits

* Add comment to try prevent future bugs

* Update Credits (#26589)

Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>

* Fix fox spawn on reach (#26584)

Co-authored-by: wrexbe <wrexbe@protonmail.com>

* Removes SCAF armor (#26566)

* removes scaf armor

* replace maint loot spawner spot with basic helmet

* Update Patrons.yml (#26578)

* Automatic changelog update

* Make aghost command work on other players using optional argument (#26546)

* Translations

* Make aghost command work on other players using optional argument

* Reviews

* Automatic changelog update

* Add new component to Make sound on interact (#26523)

* Adds new Component: EmitSoundOnInteractUsing

* Missed an import

* File-scoping

* Replace ID check with Prototype check

* Moved component and system to shared. Set prediction to true.

* Removed impoper imports and changed namespace of component to reflect changed folder.

* Following function naming theme

* All this code is basically deltanedas's, but it was a learning experience for me

* Update Content.Shared/Sound/Components/EmitSoundOnInteractUsingComponent.cs

* Update Content.Shared/Sound/Components/EmitSoundOnInteractUsingComponent.cs

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Increase syndi duffelbag storage (#26565)

* Increase syndi duffelbag storage

* weh

* Automatic changelog update

* Adds construction/decon graphs for plastic flaps (#26341)

* Adds construction/decon graphs for plastic flaps

* Dang arbitrage

* undo conflict

---------

Co-authored-by: Velcroboy <velcroboy333@hotmail.com>

* Automatic changelog update

* Makes secglasses roundstart (#26487)

* makes secglasses roundstart

* fix epic fail

* fix tests questionmark?

* Update Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml

Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>

---------

Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>

* Automatic changelog update

* Toilet Upgrade (needs review) (#22133)

* Toilet Draft

* fixes

* toilets now have secret stash to place items in cistern.

* fixes

* plungers now unblock toilets.

* fix sprite

* new sprites and fix

* fixes

* improve seat sprites.

* fix

* removed visualisersystem changed to genericvisualizers

* flush sound for toilets and copyright for toilet sprites.

* fix atrributions

* fixes

* fix datafield flushtime

* sprite improvements

* fixes

* multiple changes

* fix

* fix

* fixes remove vv

* moved stash related functions to secret stash system from toilet.

* fix

* fix

* changes for recent review.

* fix

* fix

* Automatic changelog update

* Uplink store interface searchable with a searchbar. (#24287)

* Can now search the uplink store interface with a searchbar.

* Search text updates no longer send server messages. Persists listings locally.

* Formatting fixes and tidying.

* Added helper method to get localised name and description (or otherwise, entity name and description) of store listing items.

* Update Content.Client/Store/Ui/StoreMenu.xaml

* Review change; moved localisation helper functions to their own class.

* Prevent thread-unsafe behaviour as-per review.

* Remove dummy boxcontainer

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Automatic changelog update

* Improved RCDs (#22799)

* Initial radial menu prototyping for the RCD

* Radial UI buttons can send messages to the server

* Beginning to update RCDSystem

* RCD building system in progress

* Further updates

* Added extra effects, RCDSystem now reads RCD prototype data

* Replacing tiles is instant, multiple constructions are allowed, deconstruction is broken

* Added extra functionality to RadialContainers plus documentation

* Fixed localization of RCD UI strings

* Menu opens near cursor, added basic RCD

* Avoiding merge conflict

* Implemented atomized construction / deconstruction rules

* Increased RCD ammo base charges

* Moved input context definition to content

* Removed obsoleted code

* Updates to system

* Switch machine and computer frames for electrical cabling

* Added construction ghosts

* Fixed issue with keybind detection code

* Fixed RCD construction ghost mispredications

* Code clean up

* Updated deconstruction effects

* RCDs effects don't rotate

* Code clean up

* Balancing for ammo counts

* Code clean up

* Added missing localized strings

* More clean up

* Made directional window handling more robust

* Added documentation to radial menus and made them no longer dependent on Content

* Made radial containers more robust

* Further robustness to the radial menu

* The RCD submenu buttons are only shown when the destination layer has at least one children

* Expanded upon deconstructing plus construction balance

* Fixed line endings

* Updated list of RCD deconstructable entities. Now needs a component to deconstruct instead of a tag

* Bug fixes

* Revert unnecessary change

* Updated RCD strings

* Fixed bug

* More fixes

* Deconstructed tiles/subflooring convert to lattice instead

* Fixed failed tests (Linux doesn't like invalid spritespecifer paths)

* Fixing merge conflict

* Updated airlock assembly

* Fixing merge conflict

* Fixing merge conflict

* More fixing...

* Removed erroneous project file change

* Fixed string handling issue

* Trying to fix merge conflict

* Still fixing merge conflicts

* Balancing

* Hidden RCD construction ghosts when in 'build' mode

* Fixing merge conflict

* Implemented requested changes (Part 1)

* Added more requested changes

* Fix for failed test. Removed sussy null suppression

* Made requested changes - custom construction ghost system was replaced

* Fixing merge conflict

* Fixed merge conflict

* Fixed bug in RCD construction ghost validation

* Fixing merge conflict

* Merge conflict fixed

* Made required update

* Removed lingering RCD deconstruct tag

* Fixing merge conflict

* Merge conflict fixed

* Made requested changes

* Bug fixes and balancing

* Made string names more consistent

* Can no longer stack catwalks

* Automatic changelog update

* Update submodule to 217.2.0 (#26592)

* Southern accent (#26543)

* created the AccentComponent and the AccentSystem

* word replacement schtuhff

* made it a trait fr ongg!!1

* Update Content.Server/Speech/EntitySystems/SouthernAccentSystem.cs

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Prevent storing liquids in equipped buckets (#24412)

* Block access to solutions in equipped spillables.

* Stop Drink verb appearing if the solution can't be accessed.

* Automatic changelog update

* Fix 'Hypopen shouldn't display solution examine text' (#26453)

* stealthy hypo

* ExaminableSolution hand check when in covert implement.

ExaminableSolution now has 'hidden' datafield to enable chemical inspection only in hand.

* cleaning code

* more cleaning

* Hidden datafield renamed to HeldOnly

* review

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Automatic changelog update

* Revert Paint (#26593)

* Revert "Fix build (#26258)"

This reverts commit 6de5fbfafb.

* Revert "Spray Paint (Review Ready) (#23003)"

This reverts commit e4d5e7f1ae.

# Conflicts:
#	Resources/Prototypes/Entities/Structures/Holographic/projections.yml

* Fix: Prevent single-use hyposprays from getting the toggle draw verb (#26595)

Prevent single-use hyposprays from getting the toggle draw verb

Co-authored-by: Plykiya <plykiya@protonmail.com>

* MeleeHitSoundSystem (#25005)

* Began work to unscrew melee noises

* finished

* cleanup

* cleanup

* Update Content.Server/Weapons/Melee/MeleeWeaponSystem.cs

Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>

* _Style

* Fix merge

---------

Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Remove physics comp from VendingMachineWallmount (#25632)

* Remove physics comp from VendingMachineWallmount

* Fixtures removal

---------

Co-authored-by: Jeff <velcroboy333@hotmail.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Remake hairflowers (#25475)

* Add more lily usage (orange hairflower and flowercrown)

* comit 2

* ee

* more fixes

* w

* im stupid

* bring poppy in authodrobe

* weh

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Automatic changelog update

* Injector UI shows TransferAmount change, Spilling liquid changes Injector mode (#26596)

* Injector UI shows TransferAmount change, spill changes mode

* Update Content.Shared/Fluids/SharedPuddleSystem.Spillable.cs

* Update Content.Shared/Fluids/SharedPuddleSystem.Spillable.cs

---------

Co-authored-by: Plykiya <plykiya@protonmail.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Update submodule to 217.2.1 (#26599)

* disallow unanchoring or opening panels on locked emitters/APEs (#26600)

* disallow unanchoring or opening panels on locked emitters/APEs

* no locking open panels

* oops

* needback feedback

* Update Content.Shared/Lock/LockSystem.cs

* Update Content.Shared/Lock/LockSystem.cs

* Update Content.Shared/Lock/LockSystem.cs

* sanity

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Automatic changelog update

* Fix grave digging sound indefinitely playing if dug by aghost. (#26420)

Admins bypass doafters. As such, the code that runs on doafter
completion is ran before the sound is actually created. This then leads
to the sound never being stopped, and as such it would infinitely play.

This commit gets around the issue by manually stopping the sound should
the doafter fail to start. If we could be sure that the doafter would
never fail, then we could just move the call to StartDigging above
starting the doafter but this is currently not possible.

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Make the buttons on the map ui not squished (#26604)

Make the map ui work

Co-authored-by: wrexbe <wrexbe@protonmail.com>

* Combine flower crown and wreath (#26605)

* Combine flower crown and wreath

* huh

* huuh :trollface:

* Automatic changelog update

* Add AP damage to throwing knives (#26380)

* add

* ap

* no more stam dmg

* Automatic changelog update

* cancelable brig timers (#26557)

brig timers now cancelable. also some screensystem yakshave

* Fix orientation of roller skate sprites (#26627)

Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>

* Automatic changelog update

* Fix GastTileOverlay sending redundant data (#26623)

Fix GastTileOverlay not updating properly

* Auto DeAdmin sooner (#26551)

Co-authored-by: wrexbe <wrexbe@protonmail.com>

* Add briefcase to curadrobe and lawdrobe, and some briefcases cleanup (#26527)

* Add briefcase to curadrobe and some briefcases cleanup

* also add to lawdrobe

* Automatic changelog update

* Fix some text overflow bugs in HUD (#26615)

* Don't clip text in item status

* Fix overflow in examine tooltip

---------

Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>

* Adds two milk cartons to the BoozeOMat (#26635)

* Automatic changelog update

* made the hover text less vague (sorry) (#26630)

* blacklisted throwing knifes from pneumatic cannon (#26628)

* Fix radio jammer not blocking suit sensors. (#26632)

As it turns out, they are not in fact on their own netid. They are
actually just on wireless. The way I had tested my previous pr led to
this mistake being made. I originally had the radio jammer block
wireless as well, but decided to take out under the flase assumption
that it suit sensors were actually on their own netid and did not
require the ability to block all wireless packets at the last moment.

* Fix dirt decals in reach not being cleanable (#26636)

made all dirt decals cleanable

Co-authored-by: hamurlik <renoDeath@protonmail.com>

* Automatic changelog update

* Replace drill_hit.ogg and drill_use.ogg with better sounds (#26622)

* Replace drill_hit.ogg and drill_use.ogg with better sounds

* Fix attribution source for drill_hit.ogg

* Update Resources/Audio/Items/attributions.yml

Co-authored-by: Kara <lunarautomaton6@gmail.com>

* Update Resources/Audio/Items/attributions.yml

Co-authored-by: Kara <lunarautomaton6@gmail.com>

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: Kara <lunarautomaton6@gmail.com>

* Gave Blast door access permissions (#26606)

Added access reader to all blast doors. Added pre configured blast doors for engineering and science.

* Gives all wheeled objects low friction (#26601)

* gives all wheeled objects friction

* adjustments to sum stuff

* Automatic changelog update

* Combine solution injection systems; Fix embeddable injectors (#26268)

* Combine injection systems

* Update Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Automatic changelog update

* Add ValueList import (#26640)

* Change assault borg modules texture (#26502)

* Update borg_modules.yml

* Fix borg_modules.yml?

* Uh

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Automatic changelog update

* Add Cyborg Emote Sounds (#26594)

* Hal 9000's first emote

* Add Chime emote & Change variation to 0.05

* Modify Buzz emote

* Add Buzz-two emote

* modified Horn

* add ping emote

* add slowclap emote

* Convert slowclap.ogg to mono, reflect change in attribution.yml

* fix capitalization for all chatMessages && change all catagory to category

* remove all traces of slowclap.ogg

* forgor one file smh

* collating copywrite

* spelling mistakes will be the death of me

* more spelling mistakes

* change yml string to list

* Automatic changelog update

* Coordinates Disks & Shuttle FTL Travel (#23240)

* Adds the CentComm Disk and configures it to work with direct-use shuttles

* Added functionality for drone shuttles (i.e. cargo shuttle)

* Adds support for pods, and a disk console object for disks to be inserted into. Also sprites.

* Added the disk to HoP's locker

* Removed leftover logs & comments

* Fix for integration test

* Apply suggestions from code review (formatting & proper DataField)

Co-authored-by: 0x6273 <0x40@keemail.me>

* Fix integration test & changes based on code review

* Includes Disk Cases to contain Coordinate Disks, which are now CDs instead of Floppy Disks

* Check pods & non-evac shuttles for CentCom travel, even in FTL

* Import

* Remove CentCom travel restrictions & pod disk consoles

* Major changes that changes the coordinates disk system to work with salvage expeditions

* Missed CC diskcase removal

* Fix build

* Review suggestions and changes

* Major additional changes after merge

* Minor tag miss

* Integration test fix

* review

---------

Co-authored-by: 0x6273 <0x40@keemail.me>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Automatic changelog update

* Add door electronics access configuration menu (#17778)

* Add door electronics configuration menu

* Use file-scoped namespaces

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Open door electronics configuration menu only with network configurator

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Doors will now try to move their AccessReaderComponent to their door electronics when the map is initialized

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Make the access list in the id card computer a separate control

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Fix merge conflict

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove DoorElectronics tag

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Integrate doors with #17927

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Move door electornics ui stuff to the right place

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Some review fixes

Signed-off-by: c4llv07e <kseandi@gmail.com>

* More fixes

Signed-off-by: c4llv07e <kseandi@gmail.com>

* review fix

Signed-off-by: c4llv07e <kseandi@gmail.com>

* move all accesses from airlock prototypes to door electronics

Signed-off-by: c4llv07e <kseandi@gmail.com>

* rework door electronics config access list

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove Linq from the door electronics user interface

* [WIP] Add EntityWhitelist to the activatable ui component

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Better interaction system

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Refactor

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Fix some door electronics not working without AccessReaderComponent

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Move AccessReaderComponent update code to the AccessReaderSystem

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove unnecesary newlines in the door access prototypes

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove unused variables in access level control

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove unnecessary method from the door electronics configuration menu

Signed-off-by: c4llv07e <kseandi@gmail.com>

* [WIP] change access type from string to ProtoId<AccessLevelPrototype>

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove unused methods

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Newline fix

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Restored to a functional state

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Fix access configurator not working with door electronics AccessReaderComponent

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Replace all string access fields with ProtoId

Signed-off-by: c4llv07e <kseandi@gmail.com>

* move access level control initialization into Populate method

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Review

---------

Signed-off-by: c4llv07e <kseandi@gmail.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Automatic changelog update

* scoopable ash and foam, solution transfer prediction (#25832)

* move SolutionTransfer to shared and predict as much as possible

* fully move OpenableSystem to shared now that SolutionTransfer is

* fix imports for everything

* doc for solution transfer system

* trolling

* add scoopable system

* make ash and foam scoopable

* untroll

* untroll real

* make clickable it work

* troll

* the scooping room

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Replace the teleportation logic on the SCRAM implant! (#26429)

* Replace the teleportation logic on the SCRAM implant!

Now instead of just trying to pick a random tile in range 20 times, the
scram teleportation logic now:

- Gets a list of grids in range
- Until a suitable tile is picked it picks a random grid
- From that grid it picks a random tile.
- If the tile is suitable, then it is set as the target and the user
  will be teleported there.
- Grids and tiles are randomly picked as outlined above until a valid
  tile is found, or all valid grids and tiles are exhausted.
- Should no suitable tile be found then they get teleported to the same
  position they are at. Effectively not teleporting them.

* Actually make the defaults sane which I forgor in the last commit

* Extract tile section to its own function. Bias selection for current grid. Use proper coords for box.

* Address reviews as much as possible

* Address reviews

* Refactored AdvertiseComponent (#26598)

* Made it better

* ok

* alright

---------

Co-authored-by: wrexbe <wrexbe@protonmail.com>

* Bartender "Essentials" (#25367)

* drinks round 1

saving my progress before my hard drive explodes

* test 2

please work

* name fixes

whoops

* Update drinks.yml

* various fixes

am dumb

* add sol dry to vends

more fixes and changes, yippee!

* more fixes & ingame testing

shrimple tests

* last fixes :trollface:

should be ready for pr now

* Update soda.yml

sate thirst

* Automatic changelog update

* Add ERT Chaplain (#25956)

* ERT Chaplain

* Make BibleUser

* It was not intended

* reword my poor words

* 1984 a comment that I decided was unnecessary.

* Update Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Changes in chemicals page in guidebook (#25831)

* Added pages to chemical categories

The chemical categories have their own page now. Added the "Chemical Tabs" in /ServerInfo/Guidebook. Moved the Chemicals code from shiftsandjobs.yml to its own .yml file which is "chemicals.yml".

* Update guides.ftl

* Update chemicals.yml

Changed the guide entry's ID for the medical tab from Medicine to Medicinal.
Hope this works...

* Update Resources/ServerInfo/Guidebook/Chemical Tabs/Biological.xml

Co-authored-by: exincore <me@exin.xyz>

* Update Resources/ServerInfo/Guidebook/Chemical Tabs/Foods.xml

Co-authored-by: exincore <me@exin.xyz>

* Update Resources/ServerInfo/Guidebook/Chemical Tabs/Elements.xml

Co-authored-by: exincore <me@exin.xyz>

* Update Resources/ServerInfo/Guidebook/Chemical Tabs/Narcotics.xml

Co-authored-by: exincore <me@exin.xyz>

* Update Resources/ServerInfo/Guidebook/Chemical Tabs/Toxins.xml

Co-authored-by: exincore <me@exin.xyz>

* Fixed a few errors and stuff!

A few typos have been fixed thanks to exincore. Added dedicated .xml files to be used for the dedicated category pages (Medicinal and Botanical pages). Made it so it doesn't use any duplicated IDs anymore.
If there's more problems, please do tell so I can fix it!

* Update settings.json

* Fix?

---------

Co-authored-by: exincore <me@exin.xyz>

* Automatic changelog update

* Anomalies behaviours (#24683)

* Added new anomaly particle

* Add basic anomaly behaviour

* +2 parametres

* add functional to new particle

* add components to behaviours

* big content

* add shuffle, moved thing to server

* clean up

* fixes

* random pick redo

* bonjour behavioUr

* fix AJCM

* fix

* add some new behaviours

* power modifier behaviour

* rmeove timer

* new event for update ui fix

* refactor!

* fixes

* enum

* Fix mapinit

* Minor touches

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>

* Automatic changelog update

* Fix clipping/overlap in lathe machine UIs (#26646)

* Add scrollbars to lathe material list when necessary

* Fix bug where shrinking window would cause elements to overlap

---------

Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>

* Added chat window transparency slider to options (#24990)

* Adds a new slider to the misc tab in options that lets the player set chat window transparency

* Tweaked variable names

* Fixed order to match UI

* Renamed set chat window transparency function

* Changed and refactored to opacity instead of transparency

* Remove unnecessary int to float conversions

Slider used to be 0-100 while the CCVar was 0.0-1.0f. This is confusing and was only used for rounding to 2 decimal points.

* Round the value to two decimal points

* Remove rounding for now

* Rename

* Unhardcode chat color by moving to stylesheet

* Fix indent

* Make opacity slider only change opacity

---------

Co-authored-by: Your Name <you@example.com>
Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

* Automatic changelog update

* Infinity books (#25840)

* setup text data

* roundstart reshuffling keywords with gibberish words

* saved data categorized

* add book with hints

* start redrawing books

* +4 book design

* +books +random visual upgrade

* finish first file

* finish lore file

* finish with books.rsi now authorbooks.rsi...

* aurora! and some fix

* nuke author books

* speelbuke update

* finish respriting work

* fix scientist guide visual

* setup datasets

* setup stupid funny random story

* restore author books, upgrade hint generation

* add variety to story generator

* add learning system

* minor textgen edit

* file restruct, hint count variation

* more restruct

* more renaming
add basis learning system logic. Spears locked for special book for test.

* nuke all systems, for splitting PR gods

* typo fix

* update migration with deleted books

* add random story books to maint

* Update construction-system.ftl

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com>

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* typo fix

* interchangeably

* final

* Update Resources/Prototypes/Datasets/Names/books.yml

Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com>

* "."

* Update Content.Server/Paper/PaperRandomStorySystem.cs

Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com>

* Ubazer fix

* inadequate

* localized

* Update meta.json

* fuck merge conflicts

* fix jani book

---------

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

* Automatic changelog update

* Resprite ambuzol plus pills (#26651)

* Automatic changelog update

* Fixed air injector visuals (#26654)

* Make cyborgs hands explosion proof. (#26515)

* Make the advanced treatment modules beakers explosion-proof.

* undo changes

* Epic rename fail

* Explosion recursion data field

* Logic for data field

* Automatic changelog update

* Automatic changelog update

* Make typing indicator shaded (#26678)

* Automatic changelog update

* Validate wire layout prototypes and remove invalid WiresComponents (#26682)

Validate wire layout prototypes; delete invalid wirescomponents.

* Increase time inbetween anomaly pulses (#26677)

nerf anomaly pulse delays

* Automatic changelog update

* Fix for items dropped being rotated to world north (#26662)

* Fix rotation of dropped items

* combined world position rotation function for dumpable

* scuffed implementation?

* less scuffed?

* even less scuffed... I guess

* capital D

---------

Co-authored-by: Plykiya <plykiya@protonmail.com>

* Automatic changelog update

* fix double interaction popup (#26684)

change popentity to popupclient

* disable foam scooping (#26686)

Co-authored-by: deltanedas <@deltanedas:kde.org>

* Automatic changelog update

* Little disk printer sprite tweaks (#26711)

* Little disk printer sprite tweaks

* ill change this aswell

* fixed white_box.png (#26714)

* Delete Resources/Textures/Decals/bricktile.rsi/white_box.png

* Readded fixed version

one pixel change

* New lobby art: TerminalStation (#26505)

woop woop

* Automatic changelog update

* Unidentified corpses respect gender pronouns (#26715)

fix: LGBT erasure /j

* Things that can't go in disposals now don't "Miss" (#26716)

* Moved is canInsert check to before miss check

* Update Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Clean up YAML issues in animals.yml (#26696)

* Cleaned up YAML issues in animals.yml

* Cleaned up TimedSpawnerComponent

* fix health analyzer crash (#26700)

Co-authored-by: deltanedas <@deltanedas:kde.org>

* Automatic changelog update

* Make the station start with random broken wiring (#26695)

Random wire cutting on round start

* Fix turned off thrusters consume power (#26690)

* Mail Unit Fix (whitelist) (#26688)

Fix Mail Unit

* Automatic changelog update

* OOC Patron Color Toggle (#26653)

* Adds the option for you to toggle your OOC Patron color visibility to yourself and others.

* Makes the button magically disappear if you arent a patron

* Automatic changelog update

* Fix random clothing slots being able to hide character's nose and hair (#26708)

Fix bug and formatting

* Automatic changelog update

* Make Zombie, Initial Infected fix (#26665)

Make zombie fix

* Suit Sensors No Longer Use a Hardcoded 'Total Health' (#26658)

* Suit sensors now know the 'total health' of an entity

* Missed the constructor 😔

* Stop mop buckets from spilling when you push them (#26706)

* Automatic changelog update

* Robotists technology icon fix (#26723)

fix

Co-authored-by: GeneralGaws <limonmessi@mail.ru>

* Make ducks more viable as an alternative to chickens. (#26729)

Quick tweak to make ducks on par with chickens at cargo

* Automatic changelog update

* Make the nutribrick one bite smaller (#26719)

Update snacks.yml

* Task/fix nightvision huds (#26726)

* StatusIcon: add field to set if icon should be rendered with shading

* set/unset shader based on icon field

* set new field to true for hud icons

* re-shade health bars

* Automatic changelog update

* Rework Identifier Overrides to prevent showing Law Priority (#26680)

Does-The-Fix

Co-authored-by: Mephisto72 <Mephisto.Respectator@proton.me>

* Make practice projectiles consistent in damage (#26731)

* Make practice weapon damage consistent to 1

* Add book reference to description

* Automatic changelog update

* fix mopbucket water level (#26740)

* Damage popup type can now be changed with a left click if allowed via component boolean. (#26734)

* Update DamagePopupSystem.cs

* Update DamagePopupSystem.cs

* Add ability to allow or deny type change via component bool

* Automatic changelog update

* CCVars.cs: Minor inconsistency fixes. (#26744)

Update CCVars.cs

* Fixes one file format inconsistency.
* Adds missing </summary> closing tag.

* Make baseball bat crafting require a slicing tool (#26742)

Make baseball bat crafting harder

* make fulton recipe faster and require cloth (#26747)

Co-authored-by: deltanedas <@deltanedas:kde.org>

* Automatic changelog update

* -fixed Broadcast button never enabling (#26750)

* Automatic changelog update

* Let Mindshields be effected by statusIcon shading (#26754)

Phone Webedit Ops

Original PR author forgot about mindshields for making status icons shaded. 

This can be done with other antag icons as well, I remember people mentioning revs being able to see each other in the dark was lame.

* Automatic changelog update

* Dionae now bleed sap, and this can be used to make syrup. (#25748)

* SapAndSyrup

* centrifug

* morewatervapor

* whyisitnotpushing

* nymphs

* lessrealmorefun

* Automatic changelog update

* Alerts crash fix (#26602)

- If the client tries to call ShowAlert while applying gamestates (e.g. initialising an entity) then this will cause problems. I need to double-check the initial PR before I'd be comfortable with this being merged.

* made thin firelocks constructable/deconstructable (#26745)

* Automatic changelog update

* Fire sprite change for mice (#26758)

* Add new fire sprite for mice that fits them better

* Add the sprite change to rats as well

* Moffroach and hamsters now also have more fitting fire sprites

* made the meta.json easier to read

* Automatic changelog update

* Change speed threshold for barefeet walking on glass shards and D4 (#26763)

Allow walking over glass shards and D4

Co-authored-by: Plykiya <plykiya@protonmail.com>

* Automatic changelog update

---------

Signed-off-by: c4llv07e <kseandi@gmail.com>
Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com>
Co-authored-by: Plykiya <58439124+Plykiya@users.noreply.github.com>
Co-authored-by: Plykiya <plykiya@protonmail.com>
Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com>
Co-authored-by: keronshb <54602815+keronshb@users.noreply.github.com>
Co-authored-by: Wrexbe (Josh) <81056464+wrexbe@users.noreply.github.com>
Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
Co-authored-by: wrexbe <wrexbe@protonmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: Simon <63975668+Simyon264@users.noreply.github.com>
Co-authored-by: blueDev2 <89804215+blueDev2@users.noreply.github.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com>
Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
Co-authored-by: brainfood1183 <113240905+brainfood1183@users.noreply.github.com>
Co-authored-by: J. Brown <DrMelon@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com>
Co-authored-by: UBlueberry <161545003+UBlueberry@users.noreply.github.com>
Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
Co-authored-by: drteaspoon420 <87363733+drteaspoon420@users.noreply.github.com>
Co-authored-by: Bixkitts <72874643+Bixkitts@users.noreply.github.com>
Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
Co-authored-by: Ubaser <134914314+UbaserB@users.noreply.github.com>
Co-authored-by: avery <51971268+graevy@users.noreply.github.com>
Co-authored-by: eoineoineoin <github@eoinrul.es>
Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
Co-authored-by: RiceMar1244 <138547931+RiceMar1244@users.noreply.github.com>
Co-authored-by: Zealith-Gamer <61980908+Zealith-Gamer@users.noreply.github.com>
Co-authored-by: hamurlik <75280571+hamurlik@users.noreply.github.com>
Co-authored-by: hamurlik <renoDeath@protonmail.com>
Co-authored-by: no <165581243+pissdemon@users.noreply.github.com>
Co-authored-by: Kara <lunarautomaton6@gmail.com>
Co-authored-by: SoulFN <164462467+SoulFN@users.noreply.github.com>
Co-authored-by: Keer-Sar <144283718+Keer-Sar@users.noreply.github.com>
Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Co-authored-by: 0x6273 <0x40@keemail.me>
Co-authored-by: c4llv07e <38111072+c4llv07e@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: Firewatch <54725557+musicmanvr@users.noreply.github.com>
Co-authored-by: IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com>
Co-authored-by: f0x-n3rd <150924715+f0x-n3rd@users.noreply.github.com>
Co-authored-by: exincore <me@exin.xyz>
Co-authored-by: Sk1tch <ben.peter.smith@gmail.com>
Co-authored-by: Your Name <you@example.com>
Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com>
Co-authored-by: osjarw <62134478+osjarw@users.noreply.github.com>
Co-authored-by: lunarcomets <140772713+lunarcomets@users.noreply.github.com>
Co-authored-by: ThatOneGoblin25 <145570657+ThatOneGoblin25@users.noreply.github.com>
Co-authored-by: Terraspark4941 <terraspark4941@gmail.com>
Co-authored-by: Brandon Hu <103440971+Brandon-Huu@users.noreply.github.com>
Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com>
Co-authored-by: Aexxie <codyfox.077@gmail.com>
Co-authored-by: DinoWattz <116862698+DinoWattz@users.noreply.github.com>
Co-authored-by: Pspritechologist <81725545+Pspritechologist@users.noreply.github.com>
Co-authored-by: Vasilis <vasilis@pikachu.systems>
Co-authored-by: GeneralGaws <122978178+GeneralGaws@users.noreply.github.com>
Co-authored-by: GeneralGaws <limonmessi@mail.ru>
Co-authored-by: Dae <60460608+ZeroDayDaemon@users.noreply.github.com>
Co-authored-by: potato1234_x <79580518+potato1234x@users.noreply.github.com>
Co-authored-by: PrPleGoo <PrPleGoo@users.noreply.github.com>
Co-authored-by: Mephisto72 <66994453+Mephisto72@users.noreply.github.com>
Co-authored-by: Mephisto72 <Mephisto.Respectator@proton.me>
Co-authored-by: TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com>
Co-authored-by: Verm <32827189+Vermidia@users.noreply.github.com>
Co-authored-by: superjj18 <gagnonjake@gmail.com>
Co-authored-by: Golinth <amh2023@gmail.com>
Co-authored-by: Alzore <140123969+Blackern5000@users.noreply.github.com>
Co-authored-by: BITTERLYNX <166083655+PaigeMaeForrest@users.noreply.github.com>
2024-04-06 13:13:22 +03:00
Ed
80958e4d71 Hairs init + move textures to _CP14 folder (#43)
* move textures to _CP14 folder

* hairs

* maps update
2024-04-06 10:01:45 +03:00
PJBot
813a0b7b7f Automatic changelog update 2024-04-06 04:50:20 +00:00
Plykiya
265337d93f Change speed threshold for barefeet walking on glass shards and D4 (#26763)
Allow walking over glass shards and D4

Co-authored-by: Plykiya <plykiya@protonmail.com>
2024-04-06 00:49:14 -04:00
PJBot
47d4a0a411 Automatic changelog update 2024-04-06 04:42:29 +00:00
BITTERLYNX
78dd193c25 Fire sprite change for mice (#26758)
* Add new fire sprite for mice that fits them better

* Add the sprite change to rats as well

* Moffroach and hamsters now also have more fitting fire sprites

* made the meta.json easier to read
2024-04-06 00:41:23 -04:00
PJBot
5e20c18cb8 Automatic changelog update 2024-04-06 01:56:37 +00:00
osjarw
107dc0e036 made thin firelocks constructable/deconstructable (#26745) 2024-04-05 21:55:31 -04:00
metalgearsloth
dd03612d80 Alerts crash fix (#26602)
- If the client tries to call ShowAlert while applying gamestates (e.g. initialising an entity) then this will cause problems. I need to double-check the initial PR before I'd be comfortable with this being merged.
2024-04-06 00:49:44 +02:00
PJBot
34980efdb5 Automatic changelog update 2024-04-05 21:07:17 +00:00
Alzore
5523e016f4 Dionae now bleed sap, and this can be used to make syrup. (#25748)
* SapAndSyrup

* centrifug

* morewatervapor

* whyisitnotpushing

* nymphs

* lessrealmorefun
2024-04-05 14:06:12 -07:00
PJBot
d94eba8c52 Automatic changelog update 2024-04-05 20:36:38 +00:00
Golinth
26f41e9fd5 Let Mindshields be effected by statusIcon shading (#26754)
Phone Webedit Ops

Original PR author forgot about mindshields for making status icons shaded. 

This can be done with other antag icons as well, I remember people mentioning revs being able to see each other in the dark was lame.
2024-04-05 22:35:32 +02:00
PJBot
e0ac00b256 Automatic changelog update 2024-04-05 17:30:27 +00:00
superjj18
c5441a1b01 -fixed Broadcast button never enabling (#26750) 2024-04-05 13:29:21 -04:00
PJBot
ec77a161d9 Automatic changelog update 2024-04-05 15:43:18 +00:00
deltanedas
f433e2d9f8 make fulton recipe faster and require cloth (#26747)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-04-05 11:42:12 -04:00
Verm
d802928610 Make baseball bat crafting require a slicing tool (#26742)
Make baseball bat crafting harder
2024-04-05 11:41:35 -04:00
TsjipTsjip
3817133d92 CCVars.cs: Minor inconsistency fixes. (#26744)
Update CCVars.cs

* Fixes one file format inconsistency.
* Adds missing </summary> closing tag.
2024-04-05 09:42:06 +02:00
PJBot
0afb3337b0 Automatic changelog update 2024-04-05 07:20:48 +00:00
Dae
e7fda78a18 Damage popup type can now be changed with a left click if allowed via component boolean. (#26734)
* Update DamagePopupSystem.cs

* Update DamagePopupSystem.cs

* Add ability to allow or deny type change via component bool
2024-04-05 03:19:41 -04:00
osjarw
25bcb0c880 fix mopbucket water level (#26740) 2024-04-05 00:01:04 -06:00
PJBot
3d88b8ced2 Automatic changelog update 2024-04-05 03:16:07 +00:00
Dae
77c4e40fc6 Make practice projectiles consistent in damage (#26731)
* Make practice weapon damage consistent to 1

* Add book reference to description
2024-04-04 23:15:01 -04:00
Mephisto72
ad0f046f0c Rework Identifier Overrides to prevent showing Law Priority (#26680)
Does-The-Fix

Co-authored-by: Mephisto72 <Mephisto.Respectator@proton.me>
2024-04-04 22:48:24 -04:00
PJBot
38f9c1c316 Automatic changelog update 2024-04-04 23:06:07 +00:00
PrPleGoo
d314a41e4f Task/fix nightvision huds (#26726)
* StatusIcon: add field to set if icon should be rendered with shading

* set/unset shader based on icon field

* set new field to true for hud icons

* re-shade health bars
2024-04-05 01:05:01 +02:00
potato1234_x
bb1cb18aaf Make the nutribrick one bite smaller (#26719)
Update snacks.yml
2024-04-05 00:54:46 +02:00
PJBot
6e2ab06edd Automatic changelog update 2024-04-04 19:31:18 +00:00
Dae
57b16f5ba2 Make ducks more viable as an alternative to chickens. (#26729)
Quick tweak to make ducks on par with chickens at cargo
2024-04-04 21:30:12 +02:00
GeneralGaws
8cd0cce22e Robotists technology icon fix (#26723)
fix

Co-authored-by: GeneralGaws <limonmessi@mail.ru>
2024-04-04 09:49:01 -04:00
Tornado Tech
f07d2658f2 ПОБЕДА! 2024-04-04 18:49:48 +10:00
PJBot
8dc53346d3 Automatic changelog update 2024-04-04 08:41:00 +00:00
Vasilis
d4982bad31 Stop mop buckets from spilling when you push them (#26706) 2024-04-04 02:39:54 -06:00
Pspritechologist
f5d9d3c458 Suit Sensors No Longer Use a Hardcoded 'Total Health' (#26658)
* Suit sensors now know the 'total health' of an entity

* Missed the constructor 😔
2024-04-04 02:38:27 -06:00
Wrexbe (Josh)
d9c1130d8c Make Zombie, Initial Infected fix (#26665)
Make zombie fix
2024-04-04 02:37:50 -06:00
PJBot
827cd0f6e5 Automatic changelog update 2024-04-04 08:36:50 +00:00
DinoWattz
fb0e8ad24d Fix random clothing slots being able to hide character's nose and hair (#26708)
Fix bug and formatting
2024-04-04 02:35:44 -06:00
Tornado Tech
219d1d1f4d Delete tiefling.yml 2024-04-04 18:13:03 +10:00
Tornado Tech
3ef8a5e4f5 Почему оно блять не работает 2024-04-04 17:53:16 +10:00
PJBot
b331228d1c Automatic changelog update 2024-04-04 07:21:11 +00:00
Aexxie
7ced9d42f0 OOC Patron Color Toggle (#26653)
* Adds the option for you to toggle your OOC Patron color visibility to yourself and others.

* Makes the button magically disappear if you arent a patron
2024-04-04 18:20:06 +11:00
PJBot
d8af6a0d14 Automatic changelog update 2024-04-04 06:29:39 +00:00
brainfood1183
f69f395dfb Mail Unit Fix (whitelist) (#26688)
Fix Mail Unit
2024-04-04 17:29:09 +11:00
lzk
70959e7bb0 Fix turned off thrusters consume power (#26690) 2024-04-04 17:28:33 +11:00
Tayrtahn
a1a12194a5 Make the station start with random broken wiring (#26695)
Random wire cutting on round start
2024-04-04 17:28:09 +11:00
PJBot
705f029424 Automatic changelog update 2024-04-04 06:26:59 +00:00
deltanedas
170e13b7aa fix health analyzer crash (#26700)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-04-04 17:26:54 +11:00
Tayrtahn
1e43980482 Clean up YAML issues in animals.yml (#26696)
* Cleaned up YAML issues in animals.yml

* Cleaned up TimedSpawnerComponent
2024-04-04 17:26:21 +11:00
beck-thompson
b709e24d87 Things that can't go in disposals now don't "Miss" (#26716)
* Moved is canInsert check to before miss check

* Update Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-04-04 17:25:47 +11:00
Brandon Hu
36fefb99f0 Unidentified corpses respect gender pronouns (#26715)
fix: LGBT erasure /j
2024-04-04 07:31:54 +02:00
PJBot
ab3e46a32a Automatic changelog update 2024-04-04 05:29:37 +00:00
Terraspark4941
9c50ac32ee New lobby art: TerminalStation (#26505)
woop woop
2024-04-04 07:28:30 +02:00
ThatOneGoblin25
d797ee7da7 fixed white_box.png (#26714)
* Delete Resources/Textures/Decals/bricktile.rsi/white_box.png

* Readded fixed version

one pixel change
2024-04-04 07:23:37 +02:00
Tornado Tech
fc7f92b50d Merge branch 'master' into races 2024-04-04 11:09:52 +10:00
lzk
cc99bbe49e Little disk printer sprite tweaks (#26711)
* Little disk printer sprite tweaks

* ill change this aswell
2024-04-03 20:58:17 -04:00
PJBot
35134fcda6 Automatic changelog update 2024-04-03 13:42:29 +00:00
deltanedas
b909c2533d disable foam scooping (#26686)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-04-03 09:41:23 -04:00
lunarcomets
6b84a04cde fix double interaction popup (#26684)
change popentity to popupclient
2024-04-03 09:19:43 -04:00
PJBot
70ec12f4f7 Automatic changelog update 2024-04-03 05:33:04 +00:00
Plykiya
5aadb17be7 Fix for items dropped being rotated to world north (#26662)
* Fix rotation of dropped items

* combined world position rotation function for dumpable

* scuffed implementation?

* less scuffed?

* even less scuffed... I guess

* capital D

---------

Co-authored-by: Plykiya <plykiya@protonmail.com>
2024-04-03 16:31:57 +11:00
PJBot
e6a090419c Automatic changelog update 2024-04-03 03:17:03 +00:00
Nemanja
c87ba22d3e Increase time inbetween anomaly pulses (#26677)
nerf anomaly pulse delays
2024-04-02 23:15:57 -04:00
Tayrtahn
74c45da718 Validate wire layout prototypes and remove invalid WiresComponents (#26682)
Validate wire layout prototypes; delete invalid wirescomponents.
2024-04-02 23:13:59 -04:00
PJBot
c5fe41c119 Automatic changelog update 2024-04-03 02:13:53 +00:00
lzk
025107ff5f Make typing indicator shaded (#26678) 2024-04-02 22:12:47 -04:00
Tornado Tech
3f765f36fd Добавлен сырой прототип тифлинга 2024-04-02 23:05:58 +10:00
Tornado Tech
56a0d9b761 Большинство рас не доступны в начале раунда 2024-04-02 21:16:01 +10:00
PJBot
bd5031fd9e Automatic changelog update 2024-04-02 05:19:43 +00:00
PJBot
c9374969b1 Automatic changelog update 2024-04-02 05:18:38 +00:00
Simon
307a1c534d Make cyborgs hands explosion proof. (#26515)
* Make the advanced treatment modules beakers explosion-proof.

* undo changes

* Epic rename fail

* Explosion recursion data field

* Logic for data field
2024-04-01 22:18:31 -07:00
osjarw
5d31335f98 Fixed air injector visuals (#26654) 2024-04-01 22:17:26 -07:00
PJBot
a4d46091e0 Automatic changelog update 2024-04-02 00:51:49 +00:00
osjarw
4b0f47c95b Resprite ambuzol plus pills (#26651) 2024-04-01 20:50:43 -04:00
PJBot
144798ba75 Automatic changelog update 2024-04-01 21:01:16 +00:00
Ed
7464d8284c Infinity books (#25840)
* setup text data

* roundstart reshuffling keywords with gibberish words

* saved data categorized

* add book with hints

* start redrawing books

* +4 book design

* +books +random visual upgrade

* finish first file

* finish lore file

* finish with books.rsi now authorbooks.rsi...

* aurora! and some fix

* nuke author books

* speelbuke update

* finish respriting work

* fix scientist guide visual

* setup datasets

* setup stupid funny random story

* restore author books, upgrade hint generation

* add variety to story generator

* add learning system

* minor textgen edit

* file restruct, hint count variation

* more restruct

* more renaming
add basis learning system logic. Spears locked for special book for test.

* nuke all systems, for splitting PR gods

* typo fix

* update migration with deleted books

* add random story books to maint

* Update construction-system.ftl

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com>

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* Update Resources/Prototypes/Datasets/Names/books.yml

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

* typo fix

* interchangeably

* final

* Update Resources/Prototypes/Datasets/Names/books.yml

Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com>

* "."

* Update Content.Server/Paper/PaperRandomStorySystem.cs

Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com>

* Ubazer fix

* inadequate

* localized

* Update meta.json

* fuck merge conflicts

* fix jani book

---------

Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com>
Co-authored-by: Hrosts <35345601+Hrosts@users.noreply.github.com>
2024-04-01 14:00:10 -07:00
PJBot
eace2378e7 Automatic changelog update 2024-04-01 20:50:07 +00:00
Sk1tch
bc31c193c2 Added chat window transparency slider to options (#24990)
* Adds a new slider to the misc tab in options that lets the player set chat window transparency

* Tweaked variable names

* Fixed order to match UI

* Renamed set chat window transparency function

* Changed and refactored to opacity instead of transparency

* Remove unnecessary int to float conversions

Slider used to be 0-100 while the CCVar was 0.0-1.0f. This is confusing and was only used for rounding to 2 decimal points.

* Round the value to two decimal points

* Remove rounding for now

* Rename

* Unhardcode chat color by moving to stylesheet

* Fix indent

* Make opacity slider only change opacity

---------

Co-authored-by: Your Name <you@example.com>
Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
2024-04-01 13:48:02 -07:00
eoineoineoin
a05f95269f Fix clipping/overlap in lathe machine UIs (#26646)
* Add scrollbars to lathe material list when necessary

* Fix bug where shrinking window would cause elements to overlap

---------

Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-04-01 19:36:07 +11:00
PJBot
5e15abc5ed Automatic changelog update 2024-04-01 08:30:19 +00:00
Ed
a4ec01d471 Anomalies behaviours (#24683)
* Added new anomaly particle

* Add basic anomaly behaviour

* +2 parametres

* add functional to new particle

* add components to behaviours

* big content

* add shuffle, moved thing to server

* clean up

* fixes

* random pick redo

* bonjour behavioUr

* fix AJCM

* fix

* add some new behaviours

* power modifier behaviour

* rmeove timer

* new event for update ui fix

* refactor!

* fixes

* enum

* Fix mapinit

* Minor touches

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-01 19:29:13 +11:00
PJBot
de8b788856 Automatic changelog update 2024-04-01 07:21:43 +00:00
f0x-n3rd
085a71eac8 Changes in chemicals page in guidebook (#25831)
* Added pages to chemical categories

The chemical categories have their own page now. Added the "Chemical Tabs" in /ServerInfo/Guidebook. Moved the Chemicals code from shiftsandjobs.yml to its own .yml file which is "chemicals.yml".

* Update guides.ftl

* Update chemicals.yml

Changed the guide entry's ID for the medical tab from Medicine to Medicinal.
Hope this works...

* Update Resources/ServerInfo/Guidebook/Chemical Tabs/Biological.xml

Co-authored-by: exincore <me@exin.xyz>

* Update Resources/ServerInfo/Guidebook/Chemical Tabs/Foods.xml

Co-authored-by: exincore <me@exin.xyz>

* Update Resources/ServerInfo/Guidebook/Chemical Tabs/Elements.xml

Co-authored-by: exincore <me@exin.xyz>

* Update Resources/ServerInfo/Guidebook/Chemical Tabs/Narcotics.xml

Co-authored-by: exincore <me@exin.xyz>

* Update Resources/ServerInfo/Guidebook/Chemical Tabs/Toxins.xml

Co-authored-by: exincore <me@exin.xyz>

* Fixed a few errors and stuff!

A few typos have been fixed thanks to exincore. Added dedicated .xml files to be used for the dedicated category pages (Medicinal and Botanical pages). Made it so it doesn't use any duplicated IDs anymore.
If there's more problems, please do tell so I can fix it!

* Update settings.json

* Fix?

---------

Co-authored-by: exincore <me@exin.xyz>
2024-04-01 07:20:37 +00:00
IProduceWidgets
da69b0ab49 Add ERT Chaplain (#25956)
* ERT Chaplain

* Make BibleUser

* It was not intended

* reword my poor words

* 1984 a comment that I decided was unnecessary.

* Update Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-04-01 17:48:49 +11:00
PJBot
16e3aed249 Automatic changelog update 2024-04-01 06:42:20 +00:00
Firewatch
ea3e98e120 Bartender "Essentials" (#25367)
* drinks round 1

saving my progress before my hard drive explodes

* test 2

please work

* name fixes

whoops

* Update drinks.yml

* various fixes

am dumb

* add sol dry to vends

more fixes and changes, yippee!

* more fixes & ingame testing

shrimple tests

* last fixes :trollface:

should be ready for pr now

* Update soda.yml

sate thirst
2024-04-01 17:41:14 +11:00
Wrexbe (Josh)
5613c3d3dd Refactored AdvertiseComponent (#26598)
* Made it better

* ok

* alright

---------

Co-authored-by: wrexbe <wrexbe@protonmail.com>
2024-04-01 17:36:31 +11:00
nikthechampiongr
2ffd616c41 Replace the teleportation logic on the SCRAM implant! (#26429)
* Replace the teleportation logic on the SCRAM implant!

Now instead of just trying to pick a random tile in range 20 times, the
scram teleportation logic now:

- Gets a list of grids in range
- Until a suitable tile is picked it picks a random grid
- From that grid it picks a random tile.
- If the tile is suitable, then it is set as the target and the user
  will be teleported there.
- Grids and tiles are randomly picked as outlined above until a valid
  tile is found, or all valid grids and tiles are exhausted.
- Should no suitable tile be found then they get teleported to the same
  position they are at. Effectively not teleporting them.

* Actually make the defaults sane which I forgor in the last commit

* Extract tile section to its own function. Bias selection for current grid. Use proper coords for box.

* Address reviews as much as possible

* Address reviews
2024-04-01 17:31:36 +11:00
deltanedas
1db178b632 scoopable ash and foam, solution transfer prediction (#25832)
* move SolutionTransfer to shared and predict as much as possible

* fully move OpenableSystem to shared now that SolutionTransfer is

* fix imports for everything

* doc for solution transfer system

* trolling

* add scoopable system

* make ash and foam scoopable

* untroll

* untroll real

* make clickable it work

* troll

* the scooping room

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-01 17:27:39 +11:00
PJBot
d9b6e9f127 Automatic changelog update 2024-04-01 06:07:20 +00:00
c4llv07e
64bb8dbdd5 Add door electronics access configuration menu (#17778)
* Add door electronics configuration menu

* Use file-scoped namespaces

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Open door electronics configuration menu only with network configurator

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Doors will now try to move their AccessReaderComponent to their door electronics when the map is initialized

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Make the access list in the id card computer a separate control

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Fix merge conflict

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove DoorElectronics tag

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Integrate doors with #17927

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Move door electornics ui stuff to the right place

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Some review fixes

Signed-off-by: c4llv07e <kseandi@gmail.com>

* More fixes

Signed-off-by: c4llv07e <kseandi@gmail.com>

* review fix

Signed-off-by: c4llv07e <kseandi@gmail.com>

* move all accesses from airlock prototypes to door electronics

Signed-off-by: c4llv07e <kseandi@gmail.com>

* rework door electronics config access list

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove Linq from the door electronics user interface

* [WIP] Add EntityWhitelist to the activatable ui component

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Better interaction system

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Refactor

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Fix some door electronics not working without AccessReaderComponent

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Move AccessReaderComponent update code to the AccessReaderSystem

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove unnecesary newlines in the door access prototypes

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove unused variables in access level control

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove unnecessary method from the door electronics configuration menu

Signed-off-by: c4llv07e <kseandi@gmail.com>

* [WIP] change access type from string to ProtoId<AccessLevelPrototype>

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove unused methods

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Newline fix

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Restored to a functional state

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Fix access configurator not working with door electronics AccessReaderComponent

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Replace all string access fields with ProtoId

Signed-off-by: c4llv07e <kseandi@gmail.com>

* move access level control initialization into Populate method

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Review

---------

Signed-off-by: c4llv07e <kseandi@gmail.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-01 17:06:13 +11:00
PJBot
72bdcac1e2 Automatic changelog update 2024-04-01 04:51:06 +00:00
SlamBamActionman
bed9e9ac6a Coordinates Disks & Shuttle FTL Travel (#23240)
* Adds the CentComm Disk and configures it to work with direct-use shuttles

* Added functionality for drone shuttles (i.e. cargo shuttle)

* Adds support for pods, and a disk console object for disks to be inserted into. Also sprites.

* Added the disk to HoP's locker

* Removed leftover logs & comments

* Fix for integration test

* Apply suggestions from code review (formatting & proper DataField)

Co-authored-by: 0x6273 <0x40@keemail.me>

* Fix integration test & changes based on code review

* Includes Disk Cases to contain Coordinate Disks, which are now CDs instead of Floppy Disks

* Check pods & non-evac shuttles for CentCom travel, even in FTL

* Import

* Remove CentCom travel restrictions & pod disk consoles

* Major changes that changes the coordinates disk system to work with salvage expeditions

* Missed CC diskcase removal

* Fix build

* Review suggestions and changes

* Major additional changes after merge

* Minor tag miss

* Integration test fix

* review

---------

Co-authored-by: 0x6273 <0x40@keemail.me>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-01 15:50:00 +11:00
PJBot
cc35f16839 Automatic changelog update 2024-04-01 04:36:27 +00:00
Keer-Sar
cf7567d073 Add Cyborg Emote Sounds (#26594)
* Hal 9000's first emote

* Add Chime emote & Change variation to 0.05

* Modify Buzz emote

* Add Buzz-two emote

* modified Horn

* add ping emote

* add slowclap emote

* Convert slowclap.ogg to mono, reflect change in attribution.yml

* fix capitalization for all chatMessages && change all catagory to category

* remove all traces of slowclap.ogg

* forgor one file smh

* collating copywrite

* spelling mistakes will be the death of me

* more spelling mistakes

* change yml string to list
2024-04-01 15:35:21 +11:00
PJBot
8c5e236de4 Automatic changelog update 2024-04-01 04:22:17 +00:00
SoulFN
83766b5d20 Change assault borg modules texture (#26502)
* Update borg_modules.yml

* Fix borg_modules.yml?

* Uh

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-01 15:21:11 +11:00
metalgearsloth
b7a6fb991f Add ValueList import (#26640) 2024-04-01 14:41:57 +11:00
PJBot
246155bd37 Automatic changelog update 2024-04-01 03:40:40 +00:00
Tayrtahn
d0d12760a8 Combine solution injection systems; Fix embeddable injectors (#26268)
* Combine injection systems

* Update Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-04-01 14:39:34 +11:00
PJBot
d8d4feec38 Automatic changelog update 2024-04-01 03:25:06 +00:00
Flareguy
29d7b73da7 Gives all wheeled objects low friction (#26601)
* gives all wheeled objects friction

* adjustments to sum stuff
2024-03-31 20:23:59 -07:00
Zealith-Gamer
5bb0179c25 Gave Blast door access permissions (#26606)
Added access reader to all blast doors. Added pre configured blast doors for engineering and science.
2024-04-01 13:28:14 +11:00
no
c4f383c9ba Replace drill_hit.ogg and drill_use.ogg with better sounds (#26622)
* Replace drill_hit.ogg and drill_use.ogg with better sounds

* Fix attribution source for drill_hit.ogg

* Update Resources/Audio/Items/attributions.yml

Co-authored-by: Kara <lunarautomaton6@gmail.com>

* Update Resources/Audio/Items/attributions.yml

Co-authored-by: Kara <lunarautomaton6@gmail.com>

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: Kara <lunarautomaton6@gmail.com>
2024-04-01 13:25:36 +11:00
PJBot
1d2bf51488 Automatic changelog update 2024-04-01 02:14:59 +00:00
hamurlik
311ad83f05 Fix dirt decals in reach not being cleanable (#26636)
made all dirt decals cleanable

Co-authored-by: hamurlik <renoDeath@protonmail.com>
2024-04-01 13:14:28 +11:00
nikthechampiongr
29c81bcc05 Fix radio jammer not blocking suit sensors. (#26632)
As it turns out, they are not in fact on their own netid. They are
actually just on wireless. The way I had tested my previous pr led to
this mistake being made. I originally had the radio jammer block
wireless as well, but decided to take out under the flase assumption
that it suit sensors were actually on their own netid and did not
require the ability to block all wireless packets at the last moment.
2024-04-01 13:13:51 +11:00
Zealith-Gamer
adaaf0fefc blacklisted throwing knifes from pneumatic cannon (#26628) 2024-04-01 13:12:46 +11:00
UBlueberry
ce6b6fd428 made the hover text less vague (sorry) (#26630) 2024-04-01 12:58:27 +11:00
PJBot
e53bfed589 Automatic changelog update 2024-04-01 00:08:36 +00:00
RiceMar1244
652de4d321 Adds two milk cartons to the BoozeOMat (#26635) 2024-03-31 20:07:30 -04:00
eoineoineoin
9b08c2c908 Fix some text overflow bugs in HUD (#26615)
* Don't clip text in item status

* Fix overflow in examine tooltip

---------

Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-03-31 20:07:13 -04:00
PJBot
c62e90afb3 Automatic changelog update 2024-03-31 20:53:55 +00:00
lzk
0602e64300 Add briefcase to curadrobe and lawdrobe, and some briefcases cleanup (#26527)
* Add briefcase to curadrobe and some briefcases cleanup

* also add to lawdrobe
2024-03-31 16:52:49 -04:00
Wrexbe (Josh)
6d1511124f Auto DeAdmin sooner (#26551)
Co-authored-by: wrexbe <wrexbe@protonmail.com>
2024-03-31 16:49:51 -04:00
Leon Friedrich
d2bee7ec91 Fix GastTileOverlay sending redundant data (#26623)
Fix GastTileOverlay not updating properly
2024-03-31 16:45:40 -04:00
PJBot
3adf6c1ecc Automatic changelog update 2024-03-31 20:45:08 +00:00
eoineoineoin
7e950ea1d5 Fix orientation of roller skate sprites (#26627)
Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-03-31 16:44:24 -04:00
avery
5eff7f169e cancelable brig timers (#26557)
brig timers now cancelable. also some screensystem yakshave
2024-03-31 16:44:02 -04:00
PJBot
7f2e6ccbb8 Automatic changelog update 2024-03-31 11:49:42 +00:00
Ubaser
2a1903dae0 Add AP damage to throwing knives (#26380)
* add

* ap

* no more stam dmg
2024-03-31 13:48:36 +02:00
PJBot
241b153f6a Automatic changelog update 2024-03-31 08:53:58 +00:00
lzk
55c77af33c Combine flower crown and wreath (#26605)
* Combine flower crown and wreath

* huh

* huuh :trollface:
2024-03-31 19:52:52 +11:00
Wrexbe (Josh)
ad438a7ac2 Make the buttons on the map ui not squished (#26604)
Make the map ui work

Co-authored-by: wrexbe <wrexbe@protonmail.com>
2024-03-31 19:51:02 +11:00
nikthechampiongr
8337493526 Fix grave digging sound indefinitely playing if dug by aghost. (#26420)
Admins bypass doafters. As such, the code that runs on doafter
completion is ran before the sound is actually created. This then leads
to the sound never being stopped, and as such it would infinitely play.

This commit gets around the issue by manually stopping the sound should
the doafter fail to start. If we could be sure that the doafter would
never fail, then we could just move the call to StartDigging above
starting the doafter but this is currently not possible.

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-03-31 19:49:46 +11:00
PJBot
d1ad6d9126 Automatic changelog update 2024-03-31 06:35:23 +00:00
Nemanja
1b69762816 disallow unanchoring or opening panels on locked emitters/APEs (#26600)
* disallow unanchoring or opening panels on locked emitters/APEs

* no locking open panels

* oops

* needback feedback

* Update Content.Shared/Lock/LockSystem.cs

* Update Content.Shared/Lock/LockSystem.cs

* Update Content.Shared/Lock/LockSystem.cs

* sanity

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-03-31 17:34:17 +11:00
metalgearsloth
d512bc141a Update submodule to 217.2.1 (#26599) 2024-03-31 17:03:52 +11:00
Plykiya
d71062a64c Injector UI shows TransferAmount change, Spilling liquid changes Injector mode (#26596)
* Injector UI shows TransferAmount change, spill changes mode

* Update Content.Shared/Fluids/SharedPuddleSystem.Spillable.cs

* Update Content.Shared/Fluids/SharedPuddleSystem.Spillable.cs

---------

Co-authored-by: Plykiya <plykiya@protonmail.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-03-31 16:37:33 +11:00
PJBot
7c7ff5abf6 Automatic changelog update 2024-03-31 05:34:29 +00:00
lzk
d5052697aa Remake hairflowers (#25475)
* Add more lily usage (orange hairflower and flowercrown)

* comit 2

* ee

* more fixes

* w

* im stupid

* bring poppy in authodrobe

* weh

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-03-31 16:33:23 +11:00
Velcroboy
f5e5b6b095 Remove physics comp from VendingMachineWallmount (#25632)
* Remove physics comp from VendingMachineWallmount

* Fixtures removal

---------

Co-authored-by: Jeff <velcroboy333@hotmail.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-03-31 16:30:48 +11:00
Bixkitts
ae8a68b7cd MeleeHitSoundSystem (#25005)
* Began work to unscrew melee noises

* finished

* cleanup

* cleanup

* Update Content.Server/Weapons/Melee/MeleeWeaponSystem.cs

Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>

* _Style

* Fix merge

---------

Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-03-31 16:21:01 +11:00
Plykiya
90a880a9be Fix: Prevent single-use hyposprays from getting the toggle draw verb (#26595)
Prevent single-use hyposprays from getting the toggle draw verb

Co-authored-by: Plykiya <plykiya@protonmail.com>
2024-03-31 16:20:48 +11:00
metalgearsloth
c91ed96853 Revert Paint (#26593)
* Revert "Fix build (#26258)"

This reverts commit 6de5fbfafb.

* Revert "Spray Paint (Review Ready) (#23003)"

This reverts commit e4d5e7f1ae.

# Conflicts:
#	Resources/Prototypes/Entities/Structures/Holographic/projections.yml
2024-03-31 16:12:52 +11:00
PJBot
213c075e13 Automatic changelog update 2024-03-31 05:00:42 +00:00
drteaspoon420
4e618e9387 Fix 'Hypopen shouldn't display solution examine text' (#26453)
* stealthy hypo

* ExaminableSolution hand check when in covert implement.

ExaminableSolution now has 'hidden' datafield to enable chemical inspection only in hand.

* cleaning code

* more cleaning

* Hidden datafield renamed to HeldOnly

* review

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-03-31 15:59:36 +11:00
PJBot
1ad509173d Automatic changelog update 2024-03-31 04:41:28 +00:00
Tayrtahn
1b94e01563 Prevent storing liquids in equipped buckets (#24412)
* Block access to solutions in equipped spillables.

* Stop Drink verb appearing if the solution can't be accessed.
2024-03-31 15:40:22 +11:00
UBlueberry
6b7427e3ee Southern accent (#26543)
* created the AccentComponent and the AccentSystem

* word replacement schtuhff

* made it a trait fr ongg!!1

* Update Content.Server/Speech/EntitySystems/SouthernAccentSystem.cs

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-03-31 15:39:40 +11:00
metalgearsloth
32bd6630ef Update submodule to 217.2.0 (#26592) 2024-03-31 15:36:02 +11:00
PJBot
93bda6f593 Automatic changelog update 2024-03-31 04:30:53 +00:00
chromiumboy
02273ca0e7 Improved RCDs (#22799)
* Initial radial menu prototyping for the RCD

* Radial UI buttons can send messages to the server

* Beginning to update RCDSystem

* RCD building system in progress

* Further updates

* Added extra effects, RCDSystem now reads RCD prototype data

* Replacing tiles is instant, multiple constructions are allowed, deconstruction is broken

* Added extra functionality to RadialContainers plus documentation

* Fixed localization of RCD UI strings

* Menu opens near cursor, added basic RCD

* Avoiding merge conflict

* Implemented atomized construction / deconstruction rules

* Increased RCD ammo base charges

* Moved input context definition to content

* Removed obsoleted code

* Updates to system

* Switch machine and computer frames for electrical cabling

* Added construction ghosts

* Fixed issue with keybind detection code

* Fixed RCD construction ghost mispredications

* Code clean up

* Updated deconstruction effects

* RCDs effects don't rotate

* Code clean up

* Balancing for ammo counts

* Code clean up

* Added missing localized strings

* More clean up

* Made directional window handling more robust

* Added documentation to radial menus and made them no longer dependent on Content

* Made radial containers more robust

* Further robustness to the radial menu

* The RCD submenu buttons are only shown when the destination layer has at least one children

* Expanded upon deconstructing plus construction balance

* Fixed line endings

* Updated list of RCD deconstructable entities. Now needs a component to deconstruct instead of a tag

* Bug fixes

* Revert unnecessary change

* Updated RCD strings

* Fixed bug

* More fixes

* Deconstructed tiles/subflooring convert to lattice instead

* Fixed failed tests (Linux doesn't like invalid spritespecifer paths)

* Fixing merge conflict

* Updated airlock assembly

* Fixing merge conflict

* Fixing merge conflict

* More fixing...

* Removed erroneous project file change

* Fixed string handling issue

* Trying to fix merge conflict

* Still fixing merge conflicts

* Balancing

* Hidden RCD construction ghosts when in 'build' mode

* Fixing merge conflict

* Implemented requested changes (Part 1)

* Added more requested changes

* Fix for failed test. Removed sussy null suppression

* Made requested changes - custom construction ghost system was replaced

* Fixing merge conflict

* Fixed merge conflict

* Fixed bug in RCD construction ghost validation

* Fixing merge conflict

* Merge conflict fixed

* Made required update

* Removed lingering RCD deconstruct tag

* Fixing merge conflict

* Merge conflict fixed

* Made requested changes

* Bug fixes and balancing

* Made string names more consistent

* Can no longer stack catwalks
2024-03-30 23:29:47 -05:00
PJBot
4d2aa1a70a Automatic changelog update 2024-03-31 04:10:21 +00:00
J. Brown
de62ec204b Uplink store interface searchable with a searchbar. (#24287)
* Can now search the uplink store interface with a searchbar.

* Search text updates no longer send server messages. Persists listings locally.

* Formatting fixes and tidying.

* Added helper method to get localised name and description (or otherwise, entity name and description) of store listing items.

* Update Content.Client/Store/Ui/StoreMenu.xaml

* Review change; moved localisation helper functions to their own class.

* Prevent thread-unsafe behaviour as-per review.

* Remove dummy boxcontainer

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-03-31 15:09:15 +11:00
PJBot
ce71cde429 Automatic changelog update 2024-03-31 03:22:24 +00:00
brainfood1183
5f063d2d6d Toilet Upgrade (needs review) (#22133)
* Toilet Draft

* fixes

* toilets now have secret stash to place items in cistern.

* fixes

* plungers now unblock toilets.

* fix sprite

* new sprites and fix

* fixes

* improve seat sprites.

* fix

* removed visualisersystem changed to genericvisualizers

* flush sound for toilets and copyright for toilet sprites.

* fix atrributions

* fixes

* fix datafield flushtime

* sprite improvements

* fixes

* multiple changes

* fix

* fix

* fixes remove vv

* moved stash related functions to secret stash system from toilet.

* fix

* fix

* changes for recent review.

* fix

* fix
2024-03-31 14:21:18 +11:00
PJBot
80c4d3ea0f Automatic changelog update 2024-03-31 03:01:52 +00:00
Flareguy
a98d0cfe2e Makes secglasses roundstart (#26487)
* makes secglasses roundstart

* fix epic fail

* fix tests questionmark?

* Update Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml

Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>

---------

Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
2024-03-30 23:00:45 -04:00
PJBot
950a6448bb Automatic changelog update 2024-03-31 02:25:44 +00:00
Velcroboy
48e5c3cc8d Adds construction/decon graphs for plastic flaps (#26341)
* Adds construction/decon graphs for plastic flaps

* Dang arbitrage

* undo conflict

---------

Co-authored-by: Velcroboy <velcroboy333@hotmail.com>
2024-03-31 13:24:38 +11:00
PJBot
9d1d5de4a7 Automatic changelog update 2024-03-31 02:22:37 +00:00
lzk
b38547df53 Increase syndi duffelbag storage (#26565)
* Increase syndi duffelbag storage

* weh
2024-03-31 13:21:31 +11:00
blueDev2
daaa7c6de0 Add new component to Make sound on interact (#26523)
* Adds new Component: EmitSoundOnInteractUsing

* Missed an import

* File-scoping

* Replace ID check with Prototype check

* Moved component and system to shared. Set prediction to true.

* Removed impoper imports and changed namespace of component to reflect changed folder.

* Following function naming theme

* All this code is basically deltanedas's, but it was a learning experience for me

* Update Content.Shared/Sound/Components/EmitSoundOnInteractUsingComponent.cs

* Update Content.Shared/Sound/Components/EmitSoundOnInteractUsingComponent.cs

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-03-31 13:20:44 +11:00
PJBot
ef8b16af97 Automatic changelog update 2024-03-31 02:06:49 +00:00
Simon
87a56b25c3 Make aghost command work on other players using optional argument (#26546)
* Translations

* Make aghost command work on other players using optional argument

* Reviews
2024-03-31 13:05:44 +11:00
PJBot
c1b5576cc2 Automatic changelog update 2024-03-31 02:02:35 +00:00
DrSmugleaf
7a4a0bcc84 Update Patrons.yml (#26578) 2024-03-31 13:01:52 +11:00
Flareguy
1f3f1d7d97 Removes SCAF armor (#26566)
* removes scaf armor

* replace maint loot spawner spot with basic helmet
2024-03-31 13:01:28 +11:00
Wrexbe (Josh)
e7af28d22a Fix fox spawn on reach (#26584)
Co-authored-by: wrexbe <wrexbe@protonmail.com>
2024-03-31 12:59:06 +11:00
github-actions[bot]
602d30c908 Update Credits (#26589)
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
2024-03-31 12:58:47 +11:00
Wrexbe (Josh)
175f8205c0 Make advertise system survive no map inits (#26553)
* Make advertise system survive no map inits

* Add comment to try prevent future bugs
2024-03-31 12:34:31 +11:00
Wrexbe (Josh)
8676aad583 Make timer ignore client predict setting (#26554)
* Make timer ignore client predict setting

* making tests run

---------

Co-authored-by: wrexbe <wrexbe@protonmail.com>
2024-03-31 12:26:41 +11:00
Wrexbe (Josh)
7130d1ca2f Fix Meta evac shuttle name (#26587) 2024-03-31 12:24:26 +11:00
Wrexbe (Josh)
0edd0a74f4 Fix initial infected icon hiding (#26585) 2024-03-31 12:20:45 +11:00
Leon Friedrich
69cacf6dc8 Update engine to v217.1.0 (#26588) 2024-03-31 12:18:50 +11:00
Wrexbe (Josh)
a23ff527d4 Fix round start crash (causing instant restart) (#26579)
* Fix round start crash

* Make `TryCreateObjective` more error tolerant
2024-03-31 10:50:29 +11:00
keronshb
b8363cd82a Give stores the ability to check for owner only (#26573)
adds a check if the store belongs to the user
2024-03-30 19:25:36 -04:00
Boaz1111
c9e19445b4 Industrial Reagent Grinder Hotfix (#26571)
fixed
2024-03-30 16:25:50 -04:00
Plykiya
7638252df3 Fix GasMixers/Filters not working (#26568)
* Fix GasMixers/Filters not working

* OKAY GAS FILTERS TOO

---------

Co-authored-by: Plykiya <plykiya@protonmail.com>
2024-03-30 14:11:44 -04:00
lzk
1ffa5c28d8 Make BaseMedicalPDA abstract (#26567) 2024-03-30 12:38:38 -04:00
1562 changed files with 39359 additions and 13699 deletions

View File

@@ -9,7 +9,7 @@ indent_style = space
tab_width = 4
# New line preferences
#end_of_line = crlf
end_of_line = crlf:suggestion
insert_final_newline = true
trim_trailing_whitespace = true
@@ -104,6 +104,7 @@ csharp_preferred_modifier_order = public, private, protected, internal, new, abs
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
csharp_style_namespace_declarations = file_scoped:suggestion
#### C# Formatting Rules ####

View File

@@ -58,7 +58,7 @@ public class SpawnEquipDeleteBenchmark
for (var i = 0; i < N; i++)
{
_entity = server.EntMan.SpawnAttachedTo(Mob, _coords);
_spawnSys.EquipStartingGear(_entity, _gear, null);
_spawnSys.EquipStartingGear(_entity, _gear);
server.EntMan.DeleteEntity(_entity);
}
});

View File

@@ -9,20 +9,20 @@ namespace Content.Client.Access;
public sealed class AccessOverlay : Overlay
{
private const string TextFontPath = "/Fonts/NotoSans/NotoSans-Regular.ttf";
private const int TextFontSize = 12;
private readonly IEntityManager _entityManager;
private readonly EntityLookupSystem _lookup;
private readonly SharedTransformSystem _xform;
private readonly SharedTransformSystem _transformSystem;
private readonly Font _font;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
public AccessOverlay(IEntityManager entManager, IResourceCache cache, EntityLookupSystem lookup, SharedTransformSystem xform)
public AccessOverlay(IEntityManager entityManager, IResourceCache resourceCache, SharedTransformSystem transformSystem)
{
_entityManager = entManager;
_lookup = lookup;
_xform = xform;
_font = cache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12);
_entityManager = entityManager;
_transformSystem = transformSystem;
_font = resourceCache.GetFont(TextFontPath, TextFontSize);
}
protected override void Draw(in OverlayDrawArgs args)
@@ -30,52 +30,65 @@ public sealed class AccessOverlay : Overlay
if (args.ViewportControl == null)
return;
var readerQuery = _entityManager.GetEntityQuery<AccessReaderComponent>();
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var ent in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldAABB,
LookupFlags.Static | LookupFlags.Approximate))
var textBuffer = new StringBuilder();
var query = _entityManager.EntityQueryEnumerator<AccessReaderComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var accessReader, out var transform))
{
if (!readerQuery.TryGetComponent(ent, out var reader) ||
!xformQuery.TryGetComponent(ent, out var xform))
textBuffer.Clear();
var entityName = _entityManager.ToPrettyString(uid);
textBuffer.AppendLine(entityName.Prototype);
textBuffer.Append("UID: ");
textBuffer.Append(entityName.Uid.Id);
textBuffer.Append(", NUID: ");
textBuffer.Append(entityName.Nuid.Id);
textBuffer.AppendLine();
if (!accessReader.Enabled)
{
textBuffer.AppendLine("-Disabled");
continue;
}
var text = new StringBuilder();
var index = 0;
var a = $"{_entityManager.ToPrettyString(ent)}";
text.Append(a);
foreach (var list in reader.AccessLists)
if (accessReader.AccessLists.Count > 0)
{
a = $"Tag {index}";
text.AppendLine(a);
foreach (var entry in list)
var groupNumber = 0;
foreach (var accessList in accessReader.AccessLists)
{
a = $"- {entry}";
text.AppendLine(a);
groupNumber++;
foreach (var entry in accessList)
{
textBuffer.Append("+Set ");
textBuffer.Append(groupNumber);
textBuffer.Append(": ");
textBuffer.Append(entry.Id);
textBuffer.AppendLine();
}
}
index++;
}
string textStr;
if (text.Length >= 2)
{
textStr = text.ToString();
textStr = textStr[..^2];
}
else
{
textStr = "";
textBuffer.AppendLine("+Unrestricted");
}
var screenPos = args.ViewportControl.WorldToScreen(_xform.GetWorldPosition(xform));
foreach (var key in accessReader.AccessKeys)
{
textBuffer.Append("+Key ");
textBuffer.Append(key.OriginStation);
textBuffer.Append(": ");
textBuffer.Append(key.Id);
textBuffer.AppendLine();
}
args.ScreenHandle.DrawString(_font, screenPos, textStr, Color.Gold);
foreach (var tag in accessReader.DenyTags)
{
textBuffer.Append("-Tag ");
textBuffer.AppendLine(tag.Id);
}
var accessInfoText = textBuffer.ToString();
var screenPos = args.ViewportControl.WorldToScreen(_transformSystem.GetWorldPosition(transform));
args.ScreenHandle.DrawString(_font, screenPos, accessInfoText, Color.Gold);
}
}
}

View File

@@ -7,8 +7,16 @@ namespace Content.Client.Access.Commands;
public sealed class ShowAccessReadersCommand : IConsoleCommand
{
public string Command => "showaccessreaders";
public string Description => "Shows all access readers in the viewport";
public string Help => $"{Command}";
public string Description => "Toggles showing access reader permissions on the map";
public string Help => """
Overlay Info:
-Disabled | The access reader is disabled
+Unrestricted | The access reader has no restrictions
+Set [Index]: [Tag Name]| A tag in an access set (accessor needs all tags in the set to be allowed by the set)
+Key [StationUid]: [StationRecordKeyId] | A StationRecordKey that is allowed
-Tag [Tag Name] | A tag that is not allowed (takes priority over other allows)
""";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var collection = IoCManager.Instance;
@@ -26,10 +34,9 @@ public sealed class ShowAccessReadersCommand : IConsoleCommand
var entManager = collection.Resolve<IEntityManager>();
var cache = collection.Resolve<IResourceCache>();
var lookup = entManager.System<EntityLookupSystem>();
var xform = entManager.System<SharedTransformSystem>();
overlay.AddOverlay(new AccessOverlay(entManager, cache, lookup, xform));
overlay.AddOverlay(new AccessOverlay(entManager, cache, xform));
shell.WriteLine($"Set access reader debug overlay to true");
}
}

View File

@@ -27,6 +27,11 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
SendMessage(new BountyPrintLabelMessage(id));
};
_menu.OnSkipButtonPressed += id =>
{
SendMessage(new BountySkipMessage(id));
};
_menu.OpenCentered();
}
@@ -37,7 +42,7 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
if (message is not CargoBountyConsoleState state)
return;
_menu?.UpdateEntries(state.Bounties);
_menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
}
protected override void Dispose(bool disposing)

View File

@@ -13,7 +13,18 @@
</BoxContainer>
<Control MinWidth="10"/>
<BoxContainer Orientation="Vertical" MinWidth="120">
<Button Name="PrintButton" Text="{Loc 'bounty-console-label-button-text'}" HorizontalExpand="False" HorizontalAlignment="Right"/>
<BoxContainer Orientation="Horizontal" MinWidth="120">
<Button Name="PrintButton"
Text="{Loc 'bounty-console-label-button-text'}"
HorizontalExpand="False"
HorizontalAlignment="Right"
StyleClasses="OpenRight"/>
<Button Name="SkipButton"
Text="{Loc 'bounty-console-skip-button-text'}"
HorizontalExpand="False"
HorizontalAlignment="Right"
StyleClasses="OpenLeft"/>
</BoxContainer>
<RichTextLabel Name="IdLabel" HorizontalAlignment="Right" Margin="0 0 5 0"/>
</BoxContainer>
</BoxContainer>

View File

@@ -1,11 +1,13 @@
using Content.Client.Message;
using Content.Shared.Cargo;
using Content.Shared.Cargo.Prototypes;
using Content.Shared.Random;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Serilog;
namespace Content.Client.Cargo.UI;
@@ -14,15 +16,19 @@ public sealed partial class BountyEntry : BoxContainer
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
public Action? OnButtonPressed;
public Action? OnLabelButtonPressed;
public Action? OnSkipButtonPressed;
public TimeSpan EndTime;
public TimeSpan UntilNextSkip;
public BountyEntry(CargoBountyData bounty)
public BountyEntry(CargoBountyData bounty, TimeSpan untilNextSkip)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
UntilNextSkip = untilNextSkip;
if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
return;
@@ -38,6 +44,27 @@ public sealed partial class BountyEntry : BoxContainer
DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description))));
IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
PrintButton.OnPressed += _ => OnButtonPressed?.Invoke();
PrintButton.OnPressed += _ => OnLabelButtonPressed?.Invoke();
SkipButton.OnPressed += _ => OnSkipButtonPressed?.Invoke();
}
private void UpdateSkipButton(float deltaSeconds)
{
UntilNextSkip -= TimeSpan.FromSeconds(deltaSeconds);
if (UntilNextSkip > TimeSpan.Zero)
{
SkipButton.Label.Text = UntilNextSkip.ToString("mm\\:ss");
SkipButton.Disabled = true;
return;
}
SkipButton.Label.Text = Loc.GetString("bounty-console-skip-button-text");
SkipButton.Disabled = false;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdateSkipButton(args.DeltaSeconds);
}
}

View File

@@ -10,19 +10,21 @@ namespace Content.Client.Cargo.UI;
public sealed partial class CargoBountyMenu : FancyWindow
{
public Action<string>? OnLabelButtonPressed;
public Action<string>? OnSkipButtonPressed;
public CargoBountyMenu()
{
RobustXamlLoader.Load(this);
}
public void UpdateEntries(List<CargoBountyData> bounties)
public void UpdateEntries(List<CargoBountyData> bounties, TimeSpan untilNextSkip)
{
BountyEntriesContainer.Children.Clear();
foreach (var b in bounties)
{
var entry = new BountyEntry(b);
entry.OnButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
var entry = new BountyEntry(b, untilNextSkip);
entry.OnLabelButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
entry.OnSkipButtonPressed += () => OnSkipButtonPressed?.Invoke(b.Id);
BountyEntriesContainer.AddChild(entry);
}

View File

@@ -36,13 +36,20 @@ public sealed class ClientClothingSystem : ClothingSystem
{Jumpsuit, "INNERCLOTHING"},
{"neck", "NECK"},
{"back", "BACKPACK"},
{"belt", "BELT"},
{"belt1", "BELT1"},
{"belt2", "BELT2"},
{"gloves", "HAND"},
{"shoes", "FEET"},
{"id", "IDCARD"},
{"pocket1", "POCKET1"},
{"pocket2", "POCKET2"},
{"suitstorage", "SUITSTORAGE"},
{"ring1", "RING1"},
{"ring2", "RING2"},
{"pants", "PANTS"},
{"shirt", "SHIRT"},
{"cloak", "CLOAK"},
{"keys", "KEYS"},
};
[Dependency] private readonly IResourceCache _cache = default!;

View File

@@ -0,0 +1,31 @@
using Content.Shared.Clothing.Components;
using Content.Shared.Movement.Components;
using Content.Shared.Inventory.Events;
namespace Content.Client.Clothing.Systems;
public sealed class WaddleClothingSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<WaddleWhenWornComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<WaddleWhenWornComponent, GotUnequippedEvent>(OnGotUnequipped);
}
private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, GotEquippedEvent args)
{
var waddleAnimComp = EnsureComp<WaddleAnimationComponent>(args.Equipee);
waddleAnimComp.AnimationLength = comp.AnimationLength;
waddleAnimComp.HopIntensity = comp.HopIntensity;
waddleAnimComp.RunAnimationLengthMultiplier = comp.RunAnimationLengthMultiplier;
waddleAnimComp.TumbleIntensity = comp.TumbleIntensity;
}
private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, GotUnequippedEvent args)
{
RemComp<WaddleAnimationComponent>(args.Equipee);
}
}

View File

@@ -22,6 +22,7 @@ using Content.Client.Stylesheets;
using Content.Client.Viewport;
using Content.Client.Voting;
using Content.Shared.Ame.Components;
using Content.Shared.CCVar;
using Content.Shared.Gravity;
using Content.Shared.Localizations;
using Robust.Client;
@@ -157,7 +158,7 @@ namespace Content.Client.Entry
_clientPreferencesManager.Initialize();
_euiManager.Initialize();
_voteManager.Initialize();
_userInterfaceManager.SetDefaultTheme("SS14DefaultTheme");
_userInterfaceManager.SetDefaultTheme(_configManager.GetCVar(CCVars.UIDefaultInterfaceTheme));
_userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme));
_documentParsingManager.Initialize();

View File

@@ -1,5 +1,6 @@
using System.Linq;
using System.Numerics;
using Content.Shared.Atmos;
using Content.Client.UserInterface.Controls;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
@@ -79,7 +80,7 @@ namespace Content.Client.HealthAnalyzer.UI
);
Temperature.Text = Loc.GetString("health-analyzer-window-entity-temperature-text",
("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - 273f:F1} °C ({msg.Temperature:F1} °K)")
("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)")
);
BloodLevel.Text = Loc.GetString("health-analyzer-window-entity-blood-level-text",

View File

@@ -21,6 +21,7 @@ using Content.Shared.Module;
using Content.Client.Guidebook;
using Content.Client.Replay;
using Content.Shared.Administration.Managers;
using Content.Shared.Players.PlayTimeTracking;
namespace Content.Client.IoC
@@ -29,26 +30,29 @@ namespace Content.Client.IoC
{
public static void Register()
{
IoCManager.Register<IParallaxManager, ParallaxManager>();
IoCManager.Register<IChatManager, ChatManager>();
IoCManager.Register<IClientPreferencesManager, ClientPreferencesManager>();
IoCManager.Register<IStylesheetManager, StylesheetManager>();
IoCManager.Register<IScreenshotHook, ScreenshotHook>();
IoCManager.Register<FullscreenHook, FullscreenHook>();
IoCManager.Register<IClickMapManager, ClickMapManager>();
IoCManager.Register<IClientAdminManager, ClientAdminManager>();
IoCManager.Register<ISharedAdminManager, ClientAdminManager>();
IoCManager.Register<EuiManager, EuiManager>();
IoCManager.Register<IVoteManager, VoteManager>();
IoCManager.Register<ChangelogManager, ChangelogManager>();
IoCManager.Register<RulesManager, RulesManager>();
IoCManager.Register<ViewportManager, ViewportManager>();
IoCManager.Register<ISharedAdminLogManager, SharedAdminLogManager>();
IoCManager.Register<GhostKickManager>();
IoCManager.Register<ExtendedDisconnectInformationManager>();
IoCManager.Register<JobRequirementsManager>();
IoCManager.Register<DocumentParsingManager>();
IoCManager.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
var collection = IoCManager.Instance!;
collection.Register<IParallaxManager, ParallaxManager>();
collection.Register<IChatManager, ChatManager>();
collection.Register<IClientPreferencesManager, ClientPreferencesManager>();
collection.Register<IStylesheetManager, StylesheetManager>();
collection.Register<IScreenshotHook, ScreenshotHook>();
collection.Register<FullscreenHook, FullscreenHook>();
collection.Register<IClickMapManager, ClickMapManager>();
collection.Register<IClientAdminManager, ClientAdminManager>();
collection.Register<ISharedAdminManager, ClientAdminManager>();
collection.Register<EuiManager, EuiManager>();
collection.Register<IVoteManager, VoteManager>();
collection.Register<ChangelogManager, ChangelogManager>();
collection.Register<RulesManager, RulesManager>();
collection.Register<ViewportManager, ViewportManager>();
collection.Register<ISharedAdminLogManager, SharedAdminLogManager>();
collection.Register<GhostKickManager>();
collection.Register<ExtendedDisconnectInformationManager>();
collection.Register<JobRequirementsManager>();
collection.Register<DocumentParsingManager>();
collection.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
collection.Register<ISharedPlaytimeManager, JobRequirementsManager>();
}
}
}

View File

@@ -104,41 +104,12 @@ public sealed partial class LatheMenu : DefaultWindow
RecipeList.Children.Clear();
foreach (var prototype in sortedRecipesToShow)
{
StringBuilder sb = new();
var first = true;
foreach (var (id, amount) in prototype.RequiredMaterials)
{
if (!_prototypeManager.TryIndex<MaterialPrototype>(id, out var proto))
continue;
if (first)
first = false;
else
sb.Append('\n');
var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, component.MaterialUseMultiplier);
var sheetVolume = _materialStorage.GetSheetVolume(proto);
var unit = Loc.GetString(proto.Unit);
// rounded in locale not here
var sheets = adjustedAmount / (float) sheetVolume;
var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit));
var name = Loc.GetString(proto.Name);
sb.Append(Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText)));
}
if (!string.IsNullOrWhiteSpace(prototype.Description))
{
sb.Append('\n');
sb.Append(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
}
var icon = prototype.Icon == null
? _spriteSystem.GetPrototypeIcon(prototype.Result).Default
: _spriteSystem.Frame0(prototype.Icon);
var canProduce = _lathe.CanProduce(_owner, prototype, quantity);
var control = new RecipeControl(prototype, sb.ToString(), canProduce, icon);
var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, icon);
control.OnButtonPressed += s =>
{
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
@@ -149,6 +120,51 @@ public sealed partial class LatheMenu : DefaultWindow
}
}
private string GenerateTooltipText(LatheRecipePrototype prototype)
{
StringBuilder sb = new();
foreach (var (id, amount) in prototype.RequiredMaterials)
{
if (!_prototypeManager.TryIndex<MaterialPrototype>(id, out var proto))
continue;
var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, _entityManager.GetComponent<LatheComponent>(_owner).MaterialUseMultiplier);
var sheetVolume = _materialStorage.GetSheetVolume(proto);
var unit = Loc.GetString(proto.Unit);
var sheets = adjustedAmount / (float) sheetVolume;
var availableAmount = _materialStorage.GetMaterialAmount(_owner, id);
var missingAmount = Math.Max(0, adjustedAmount - availableAmount);
var missingSheets = missingAmount / (float) sheetVolume;
var name = Loc.GetString(proto.Name);
string tooltipText;
if (missingSheets > 0)
{
tooltipText = Loc.GetString("lathe-menu-material-amount-missing", ("amount", sheets), ("missingAmount", missingSheets), ("unit", unit), ("material", name));
}
else
{
var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit));
tooltipText = Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText));
}
sb.AppendLine(tooltipText);
}
if (!string.IsNullOrWhiteSpace(prototype.Description))
sb.AppendLine(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
// Remove last newline
if (sb.Length > 0)
sb.Remove(sb.Length - 1, 1);
return sb.ToString();
}
public void UpdateCategories()
{
var currentCategories = new List<ProtoId<LatheCategoryPrototype>>();

View File

@@ -11,17 +11,16 @@ namespace Content.Client.Lathe.UI;
public sealed partial class RecipeControl : Control
{
public Action<string>? OnButtonPressed;
public Func<string> TooltipTextSupplier;
public string TooltipText;
public RecipeControl(LatheRecipePrototype recipe, string tooltip, bool canProduce, Texture? texture = null)
public RecipeControl(LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, Texture? texture = null)
{
RobustXamlLoader.Load(this);
RecipeName.Text = recipe.Name;
RecipeTexture.Texture = texture;
Button.Disabled = !canProduce;
TooltipText = tooltip;
TooltipTextSupplier = tooltipTextSupplier;
Button.TooltipSupplier = SupplyTooltip;
Button.OnPressed += (_) =>
@@ -32,6 +31,6 @@ public sealed partial class RecipeControl : Control
private Control? SupplyTooltip(Control sender)
{
return new RecipeTooltip(TooltipText);
return new RecipeTooltip(TooltipTextSupplier());
}
}

View File

@@ -64,13 +64,19 @@ namespace Content.Client.Lobby
_characterSetup.CloseButton.OnPressed += _ =>
{
// Reset sliders etc.
_characterSetup?.UpdateControls();
var controller = _userInterfaceManager.GetUIController<LobbyUIController>();
controller.SetClothes(true);
controller.UpdateProfile();
_lobby.SwitchState(LobbyGui.LobbyGuiState.Default);
};
_characterSetup.SaveButton.OnPressed += _ =>
{
_characterSetup.Save();
_lobby.CharacterPreview.UpdateUI();
_userInterfaceManager.GetUIController<LobbyUIController>().ReloadProfile();
};
LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide);
@@ -84,10 +90,6 @@ namespace Content.Client.Lobby
_gameTicker.InfoBlobUpdated += UpdateLobbyUi;
_gameTicker.LobbyStatusUpdated += LobbyStatusUpdated;
_gameTicker.LobbyLateJoinStatusUpdated += LobbyLateJoinStatusUpdated;
_preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
_lobby.CharacterPreview.UpdateUI();
}
protected override void Shutdown()
@@ -109,13 +111,6 @@ namespace Content.Client.Lobby
_characterSetup?.Dispose();
_characterSetup = null;
_preferencesManager.OnServerDataLoaded -= PreferencesDataLoaded;
}
private void PreferencesDataLoaded()
{
_lobby?.CharacterPreview.UpdateUI();
}
private void OnSetupPressed(BaseButton.ButtonEventArgs args)

View File

@@ -0,0 +1,286 @@
using System.Linq;
using Content.Client.Humanoid;
using Content.Client.Inventory;
using Content.Client.Lobby.UI;
using Content.Client.Preferences;
using Content.Client.Preferences.UI;
using Content.Client.Station;
using Content.Shared.Clothing;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Roles;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Client.Lobby;
public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState>, IOnStateExited<LobbyState>
{
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
private LobbyCharacterPreviewPanel? _previewPanel;
private bool _showClothes = true;
/*
* Each character profile has its own dummy. There is also a dummy for the lobby screen + character editor
* that is shared too.
*/
/// <summary>
/// Preview dummy for role gear.
/// </summary>
private EntityUid? _previewDummy;
/// <summary>
/// If we currently have a job prototype selected.
/// </summary>
private JobPrototype? _dummyJob;
// TODO: Load the species directly and don't update entity ever.
public event Action<EntityUid>? PreviewDummyUpdated;
private HumanoidCharacterProfile? _profile;
public override void Initialize()
{
base.Initialize();
_preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
}
private void PreferencesDataLoaded()
{
UpdateProfile();
}
public void OnStateEntered(LobbyState state)
{
}
public void OnStateExited(LobbyState state)
{
EntityManager.DeleteEntity(_previewDummy);
_previewDummy = null;
}
public void SetPreviewPanel(LobbyCharacterPreviewPanel? panel)
{
_previewPanel = panel;
ReloadProfile();
}
public void SetClothes(bool value)
{
if (_showClothes == value)
return;
_showClothes = value;
ReloadCharacterUI();
}
public void SetDummyJob(JobPrototype? job)
{
_dummyJob = job;
ReloadCharacterUI();
}
/// <summary>
/// Updates the character only with the specified profile change.
/// </summary>
public void ReloadProfile()
{
// Test moment
if (_profile == null || _stateManager.CurrentState is not LobbyState)
return;
// Ignore job clothes and the likes so we don't spam entities out every frame of color changes.
var previewDummy = EnsurePreviewDummy(_profile);
_humanoid.LoadProfile(previewDummy, _profile);
}
/// <summary>
/// Updates the currently selected character's preview.
/// </summary>
public void ReloadCharacterUI()
{
// Test moment
if (_profile == null || _stateManager.CurrentState is not LobbyState)
return;
EntityManager.DeleteEntity(_previewDummy);
_previewDummy = null;
_previewDummy = EnsurePreviewDummy(_profile);
_previewPanel?.SetSprite(_previewDummy.Value);
_previewPanel?.SetSummaryText(_profile.Summary);
_humanoid.LoadProfile(_previewDummy.Value, _profile);
if (_showClothes)
GiveDummyJobClothesLoadout(_previewDummy.Value, _profile);
}
/// <summary>
/// Updates character profile to the default.
/// </summary>
public void UpdateProfile()
{
if (!_preferencesManager.ServerDataLoaded)
{
_profile = null;
return;
}
if (_preferencesManager.Preferences?.SelectedCharacter is HumanoidCharacterProfile selectedCharacter)
{
_profile = selectedCharacter;
_previewPanel?.SetLoaded(true);
}
else
{
_previewPanel?.SetSummaryText(string.Empty);
_previewPanel?.SetLoaded(false);
}
ReloadCharacterUI();
}
public void UpdateProfile(HumanoidCharacterProfile? profile)
{
if (_profile?.Equals(profile) == true)
return;
if (_stateManager.CurrentState is not LobbyState)
return;
_profile = profile;
}
private EntityUid EnsurePreviewDummy(HumanoidCharacterProfile profile)
{
if (_previewDummy != null)
return _previewDummy.Value;
_previewDummy = EntityManager.SpawnEntity(_prototypeManager.Index<SpeciesPrototype>(profile.Species).DollPrototype, MapCoordinates.Nullspace);
PreviewDummyUpdated?.Invoke(_previewDummy.Value);
return _previewDummy.Value;
}
/// <summary>
/// Applies the highest priority job's clothes to the dummy.
/// </summary>
public void GiveDummyJobClothesLoadout(EntityUid dummy, HumanoidCharacterProfile profile)
{
var job = _dummyJob ?? GetPreferredJob(profile);
GiveDummyJobClothes(dummy, profile, job);
if (_prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
{
var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), EntityManager, _prototypeManager);
GiveDummyLoadout(dummy, loadout);
}
}
/// <summary>
/// Gets the highest priority job for the profile.
/// </summary>
public JobPrototype GetPreferredJob(HumanoidCharacterProfile profile)
{
var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
return _prototypeManager.Index<JobPrototype>(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
}
public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout)
{
if (roleLoadout == null)
return;
foreach (var group in roleLoadout.SelectedLoadouts.Values)
{
foreach (var loadout in group)
{
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
continue;
_spawn.EquipStartingGear(uid, _prototypeManager.Index(loadoutProto.Equipment));
}
}
}
/// <summary>
/// Applies the specified job's clothes to the dummy.
/// </summary>
public void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile, JobPrototype job)
{
if (!_inventory.TryGetSlots(dummy, out var slots))
return;
// Apply loadout
if (profile.Loadouts.TryGetValue(job.ID, out var jobLoadout))
{
foreach (var loadouts in jobLoadout.SelectedLoadouts.Values)
{
foreach (var loadout in loadouts)
{
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
continue;
// TODO: Need some way to apply starting gear to an entity coz holy fucking shit dude.
var loadoutGear = _prototypeManager.Index(loadoutProto.Equipment);
foreach (var slot in slots)
{
var itemType = loadoutGear.GetGear(slot.Name);
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
{
EntityManager.DeleteEntity(unequippedItem.Value);
}
if (itemType != string.Empty)
{
var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
_inventory.TryEquip(dummy, item, slot.Name, true, true);
}
}
}
}
}
if (job.StartingGear == null)
return;
var gear = _prototypeManager.Index<StartingGearPrototype>(job.StartingGear);
foreach (var slot in slots)
{
var itemType = gear.GetGear(slot.Name);
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
{
EntityManager.DeleteEntity(unequippedItem.Value);
}
if (itemType != string.Empty)
{
var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
_inventory.TryEquip(dummy, item, slot.Name, true, true);
}
}
}
public EntityUid? GetPreviewDummy()
{
return _previewDummy;
}
}

View File

@@ -1,166 +0,0 @@
using System.Linq;
using System.Numerics;
using Content.Client.Alerts;
using Content.Client.Humanoid;
using Content.Client.Inventory;
using Content.Client.Preferences;
using Content.Client.UserInterface.Controls;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Lobby.UI
{
public sealed class LobbyCharacterPreviewPanel : Control
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private EntityUid? _previewDummy;
private readonly Label _summaryLabel;
private readonly BoxContainer _loaded;
private readonly BoxContainer _viewBox;
private readonly Label _unloaded;
public LobbyCharacterPreviewPanel()
{
IoCManager.InjectDependencies(this);
var header = new NanoHeading
{
Text = Loc.GetString("lobby-character-preview-panel-header")
};
CharacterSetupButton = new Button
{
Text = Loc.GetString("lobby-character-preview-panel-character-setup-button"),
HorizontalAlignment = HAlignment.Center,
Margin = new Thickness(0, 5, 0, 0),
};
_summaryLabel = new Label
{
HorizontalAlignment = HAlignment.Center,
Margin = new Thickness(3, 3),
};
var vBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
_unloaded = new Label { Text = Loc.GetString("lobby-character-preview-panel-unloaded-preferences-label") };
_loaded = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Visible = false
};
_viewBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalAlignment = HAlignment.Center,
};
var _vSpacer = new VSpacer();
_loaded.AddChild(_summaryLabel);
_loaded.AddChild(_viewBox);
_loaded.AddChild(_vSpacer);
_loaded.AddChild(CharacterSetupButton);
vBox.AddChild(header);
vBox.AddChild(_loaded);
vBox.AddChild(_unloaded);
AddChild(vBox);
UpdateUI();
}
public Button CharacterSetupButton { get; }
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing) return;
if (_previewDummy != null) _entityManager.DeleteEntity(_previewDummy.Value);
_previewDummy = default;
}
public void UpdateUI()
{
if (!_preferencesManager.ServerDataLoaded)
{
_loaded.Visible = false;
_unloaded.Visible = true;
}
else
{
_loaded.Visible = true;
_unloaded.Visible = false;
if (_preferencesManager.Preferences?.SelectedCharacter is not HumanoidCharacterProfile selectedCharacter)
{
_summaryLabel.Text = string.Empty;
}
else
{
_previewDummy = _entityManager.SpawnEntity(_prototypeManager.Index<SpeciesPrototype>(selectedCharacter.Species).DollPrototype, MapCoordinates.Nullspace);
_viewBox.DisposeAllChildren();
var spriteView = new SpriteView
{
OverrideDirection = Direction.South,
Scale = new Vector2(4f, 4f),
MaxSize = new Vector2(112, 112),
Stretch = SpriteView.StretchMode.Fill,
};
spriteView.SetEntity(_previewDummy.Value);
_viewBox.AddChild(spriteView);
_summaryLabel.Text = selectedCharacter.Summary;
_entityManager.System<HumanoidAppearanceSystem>().LoadProfile(_previewDummy.Value, selectedCharacter);
GiveDummyJobClothes(_previewDummy.Value, selectedCharacter);
}
}
}
public static void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile)
{
var protoMan = IoCManager.Resolve<IPrototypeManager>();
var entMan = IoCManager.Resolve<IEntityManager>();
var invSystem = EntitySystem.Get<ClientInventorySystem>();
var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
var job = protoMan.Index<JobPrototype>(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
if (job.StartingGear != null && invSystem.TryGetSlots(dummy, out var slots))
{
var gear = protoMan.Index<StartingGearPrototype>(job.StartingGear);
foreach (var slot in slots)
{
var itemType = gear.GetGear(slot.Name, profile);
if (invSystem.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
{
entMan.DeleteEntity(unequippedItem.Value);
}
if (itemType != string.Empty)
{
var item = entMan.SpawnEntity(itemType, MapCoordinates.Nullspace);
invSystem.TryEquip(dummy, item, slot.Name, true, true);
}
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
<Control
xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
<BoxContainer Name="VBox" Orientation="Vertical">
<controls:NanoHeading Name="Header" Text="{Loc 'lobby-character-preview-panel-header'}">
</controls:NanoHeading>
<BoxContainer Name="Loaded" Orientation="Vertical"
Visible="False">
<Label Name="Summary" HorizontalAlignment="Center" Margin="3 3"/>
<BoxContainer Name="ViewBox" Orientation="Horizontal" HorizontalAlignment="Center">
</BoxContainer>
<controls:VSpacer/>
<Button Name="CharacterSetup" Text="{Loc 'lobby-character-preview-panel-character-setup-button'}"
HorizontalAlignment="Center"
Margin="0 5 0 0"/>
</BoxContainer>
<Label Name="Unloaded" Text="{Loc 'lobby-character-preview-panel-unloaded-preferences-label'}"/>
</BoxContainer>
</Control>

View File

@@ -0,0 +1,45 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Lobby.UI;
[GenerateTypedNameReferences]
public sealed partial class LobbyCharacterPreviewPanel : Control
{
public Button CharacterSetupButton => CharacterSetup;
public LobbyCharacterPreviewPanel()
{
RobustXamlLoader.Load(this);
UserInterfaceManager.GetUIController<LobbyUIController>().SetPreviewPanel(this);
}
public void SetLoaded(bool value)
{
Loaded.Visible = value;
Unloaded.Visible = !value;
}
public void SetSummaryText(string value)
{
Summary.Text = string.Empty;
}
public void SetSprite(EntityUid uid)
{
ViewBox.DisposeAllChildren();
var spriteView = new SpriteView
{
OverrideDirection = Direction.South,
Scale = new Vector2(4f, 4f),
MaxSize = new Vector2(112, 112),
Stretch = SpriteView.StretchMode.Fill,
};
spriteView.SetEntity(uid);
ViewBox.AddChild(spriteView);
}
}

View File

@@ -1,23 +1,9 @@
using Content.Client.Chat.UI;
using Content.Client.Info;
using Content.Client.Message;
using Content.Client.Preferences;
using Content.Client.Preferences.UI;
using Content.Client.UserInterface.Screens;
using Content.Client.UserInterface.Systems.Chat.Widgets;
using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.Graphics;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Lobby.UI
{

View File

@@ -210,9 +210,9 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "dead");
}
else if (sensor.TotalDamage != null)
else if (sensor.DamagePercentage != null)
{
var index = MathF.Round(4f * (sensor.TotalDamage.Value / 100f));
var index = MathF.Round(4f * sensor.DamagePercentage.Value);
if (index >= 5)
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "critical");

View File

@@ -0,0 +1,173 @@
using System.Numerics;
using Content.Client.Buckle;
using Content.Client.Gravity;
using Content.Shared.ActionBlocker;
using Content.Shared.Buckle.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.StatusEffect;
using Content.Shared.Stunnable;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
using Robust.Shared.Timing;
namespace Content.Client.Movement.Systems;
public sealed class WaddleAnimationSystem : EntitySystem
{
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly GravitySystem _gravity = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly BuckleSystem _buckle = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
public override void Initialize()
{
SubscribeLocalEvent<WaddleAnimationComponent, MoveInputEvent>(OnMovementInput);
SubscribeLocalEvent<WaddleAnimationComponent, StartedWaddlingEvent>(OnStartedWalking);
SubscribeLocalEvent<WaddleAnimationComponent, StoppedWaddlingEvent>(OnStoppedWalking);
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted);
SubscribeLocalEvent<WaddleAnimationComponent, StunnedEvent>(OnStunned);
SubscribeLocalEvent<WaddleAnimationComponent, KnockedDownEvent>(OnKnockedDown);
SubscribeLocalEvent<WaddleAnimationComponent, BuckleChangeEvent>(OnBuckleChange);
}
private void OnMovementInput(EntityUid entity, WaddleAnimationComponent component, MoveInputEvent args)
{
// Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume
// they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
if (!_timing.IsFirstTimePredicted)
{
return;
}
if (!args.HasDirectionalMovement && component.IsCurrentlyWaddling)
{
var stopped = new StoppedWaddlingEvent(entity);
RaiseLocalEvent(entity, ref stopped);
return;
}
// Only start waddling if we're not currently AND we're actually moving.
if (component.IsCurrentlyWaddling || !args.HasDirectionalMovement)
return;
var started = new StartedWaddlingEvent(entity);
RaiseLocalEvent(entity, ref started);
}
private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args)
{
if (_animation.HasRunningAnimation(uid, component.KeyName))
return;
if (!TryComp<InputMoverComponent>(uid, out var mover))
return;
if (_gravity.IsWeightless(uid))
return;
if (!_actionBlocker.CanMove(uid, mover))
return;
// Do nothing if buckled in
if (_buckle.IsBuckled(uid))
return;
// Do nothing if crit or dead (for obvious reasons)
if (_mobState.IsIncapacitated(uid))
return;
var tumbleIntensity = component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
var len = mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
component.LastStep = !component.LastStep;
component.IsCurrentlyWaddling = true;
var anim = new Animation()
{
Length = TimeSpan.FromSeconds(len),
AnimationTracks =
{
new AnimationTrackComponentProperty()
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Rotation),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0),
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(tumbleIntensity), len/2),
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), len/2),
}
},
new AnimationTrackComponentProperty()
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Offset),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
new AnimationTrackProperty.KeyFrame(component.HopIntensity, len/2),
new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
}
}
}
};
_animation.Play(uid, anim, component.KeyName);
}
private void OnStoppedWalking(EntityUid uid, WaddleAnimationComponent component, StoppedWaddlingEvent args)
{
StopWaddling(uid, component);
}
private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args)
{
var started = new StartedWaddlingEvent(uid);
RaiseLocalEvent(uid, ref started);
}
private void OnStunned(EntityUid uid, WaddleAnimationComponent component, StunnedEvent args)
{
StopWaddling(uid, component);
}
private void OnKnockedDown(EntityUid uid, WaddleAnimationComponent component, KnockedDownEvent args)
{
StopWaddling(uid, component);
}
private void OnBuckleChange(EntityUid uid, WaddleAnimationComponent component, BuckleChangeEvent args)
{
StopWaddling(uid, component);
}
private void StopWaddling(EntityUid uid, WaddleAnimationComponent component)
{
if (!component.IsCurrentlyWaddling)
return;
_animation.Stop(uid, component.KeyName);
if (!TryComp<SpriteComponent>(uid, out var sprite))
{
return;
}
sprite.Offset = new Vector2();
sprite.Rotation = Angle.FromDegrees(0);
component.IsCurrentlyWaddling = false;
}
}

View File

@@ -49,6 +49,7 @@
<Label Text="{Loc 'ui-options-general-speech'}"
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
StyleClasses="LabelKeyText"/>
<CheckBox Name="ShowOocPatronColor" Text="{Loc 'ui-options-show-ooc-patron-color'}" />
<CheckBox Name="ShowLoocAboveHeadCheckBox" Text="{Loc 'ui-options-show-looc-on-head'}" />
<CheckBox Name="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" />
<CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" />

View File

@@ -3,11 +3,14 @@ using Content.Client.UserInterface.Screens;
using Content.Shared.CCVar;
using Content.Shared.HUD;
using Robust.Client.AutoGenerated;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Range = Robust.Client.UserInterface.Controls.Range;
@@ -16,6 +19,7 @@ namespace Content.Client.Options.UI.Tabs
[GenerateTypedNameReferences]
public sealed partial class MiscTab : Control
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -55,8 +59,13 @@ namespace Content.Client.Options.UI.Tabs
UpdateApplyButton();
};
// Channel can be null in replays so.
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
HudThemeOption.OnItemSelected += OnHudThemeChanged;
DiscordRich.OnToggled += OnCheckBoxToggled;
ShowOocPatronColor.OnToggled += OnCheckBoxToggled;
ShowLoocAboveHeadCheckBox.OnToggled += OnCheckBoxToggled;
ShowHeldItemCheckBox.OnToggled += OnCheckBoxToggled;
ShowCombatModeIndicatorsCheckBox.OnToggled += OnCheckBoxToggled;
@@ -73,6 +82,7 @@ namespace Content.Client.Options.UI.Tabs
HudThemeOption.SelectId(_hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0));
DiscordRich.Pressed = _cfg.GetCVar(CVars.DiscordEnabled);
ShowOocPatronColor.Pressed = _cfg.GetCVar(CCVars.ShowOocPatronColor);
ShowLoocAboveHeadCheckBox.Pressed = _cfg.GetCVar(CCVars.LoocAboveHeadShow);
ShowHeldItemCheckBox.Pressed = _cfg.GetCVar(CCVars.HudHeldItemShow);
ShowCombatModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
@@ -130,6 +140,7 @@ namespace Content.Client.Options.UI.Tabs
_cfg.SetCVar(CCVars.HudHeldItemShow, ShowHeldItemCheckBox.Pressed);
_cfg.SetCVar(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox.Pressed);
_cfg.SetCVar(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox.Pressed);
_cfg.SetCVar(CCVars.ShowOocPatronColor, ShowOocPatronColor.Pressed);
_cfg.SetCVar(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox.Pressed);
_cfg.SetCVar(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox.Pressed);
_cfg.SetCVar(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox.Pressed);
@@ -158,6 +169,7 @@ namespace Content.Client.Options.UI.Tabs
var isShowHeldItemSame = ShowHeldItemCheckBox.Pressed == _cfg.GetCVar(CCVars.HudHeldItemShow);
var isCombatModeIndicatorsSame = ShowCombatModeIndicatorsCheckBox.Pressed == _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
var isOpaqueStorageWindow = OpaqueStorageWindowCheckBox.Pressed == _cfg.GetCVar(CCVars.OpaqueStorageWindow);
var isOocPatronColorShowSame = ShowOocPatronColor.Pressed == _cfg.GetCVar(CCVars.ShowOocPatronColor);
var isLoocShowSame = ShowLoocAboveHeadCheckBox.Pressed == _cfg.GetCVar(CCVars.LoocAboveHeadShow);
var isFancyChatSame = FancySpeechBubblesCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
var isFancyBackgroundSame = FancyNameBackgroundsCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatFancyNameBackground);
@@ -175,6 +187,7 @@ namespace Content.Client.Options.UI.Tabs
isShowHeldItemSame &&
isCombatModeIndicatorsSame &&
isOpaqueStorageWindow &&
isOocPatronColorShowSame &&
isLoocShowSame &&
isFancyChatSame &&
isFancyBackgroundSame &&

View File

@@ -19,7 +19,6 @@ namespace Content.Client.Overlays;
/// </summary>
public sealed class EntityHealthBarOverlay : Overlay
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly IEntityManager _entManager;
private readonly SharedTransformSystem _transform;
private readonly MobStateSystem _mobStateSystem;
@@ -27,17 +26,14 @@ public sealed class EntityHealthBarOverlay : Overlay
private readonly ProgressColorSystem _progressColor;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
public HashSet<string> DamageContainers = new();
private readonly ShaderInstance _shader;
public EntityHealthBarOverlay(IEntityManager entManager)
{
IoCManager.InjectDependencies(this);
_entManager = entManager;
_transform = _entManager.System<SharedTransformSystem>();
_mobStateSystem = _entManager.System<MobStateSystem>();
_mobThresholdSystem = _entManager.System<MobThresholdSystem>();
_progressColor = _entManager.System<ProgressColorSystem>();
_shader = _prototype.Index<ShaderPrototype>("unshaded").Instance();
}
protected override void Draw(in OverlayDrawArgs args)
@@ -50,8 +46,6 @@ public sealed class EntityHealthBarOverlay : Overlay
var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
var rotationMatrix = Matrix3.CreateRotation(-rotation);
handle.UseShader(_shader);
var query = _entManager.AllEntityQueryEnumerator<MobThresholdsComponent, MobStateComponent, DamageableComponent, SpriteComponent>();
while (query.MoveNext(out var uid,
out var mobThresholdsComponent,
@@ -122,7 +116,6 @@ public sealed class EntityHealthBarOverlay : Overlay
handle.DrawRect(pixelDarken, Black.WithAlpha(128));
}
handle.UseShader(null);
handle.SetTransform(Matrix3.Identity);
}

View File

@@ -7,12 +7,13 @@ using Robust.Client;
using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Players.PlayTimeTracking;
public sealed class JobRequirementsManager
public sealed class JobRequirementsManager : ISharedPlaytimeManager
{
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IClientNetManager _net = default!;
@@ -133,5 +134,13 @@ public sealed class JobRequirementsManager
}
}
public IReadOnlyDictionary<string, TimeSpan> GetPlayTimes(ICommonSession session)
{
if (session != _playerManager.LocalSession)
{
return new Dictionary<string, TimeSpan>();
}
return _roles;
}
}

View File

@@ -184,6 +184,12 @@ namespace Content.Client.Popups
PopupEntity(message, uid, recipient.Value, type);
}
public override void PopupPredicted(string? recipientMessage, string? othersMessage, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small)
{
if (recipient != null && _timing.IsFirstTimePredicted)
PopupEntity(recipientMessage, uid, recipient.Value, type);
}
#endregion
#region Network Event Handlers

View File

@@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Preferences;
using Robust.Client;
using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -20,8 +18,7 @@ namespace Content.Client.Preferences
{
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IBaseClient _baseClient = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public event Action? OnServerDataLoaded;
@@ -64,7 +61,8 @@ namespace Content.Client.Preferences
public void UpdateCharacter(ICharacterProfile profile, int slot)
{
profile.EnsureValid(_cfg, _prototypes);
var collection = IoCManager.Instance!;
profile.EnsureValid(_playerManager.LocalSession!, collection);
var characters = new Dictionary<int, ICharacterProfile>(Preferences.Characters) {[slot] = profile};
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor);
var msg = new MsgUpdateCharacter

View File

@@ -0,0 +1,41 @@
using Content.Client.Players.PlayTimeTracking;
using Content.Shared.Roles;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Preferences.UI;
public sealed class AntagPreferenceSelector : RequirementsSelector<AntagPrototype>
{
// 0 is yes and 1 is no
public bool Preference
{
get => Options.SelectedValue == 0;
set => Options.Select((value && !Disabled) ? 0 : 1);
}
public event Action<bool>? PreferenceChanged;
public AntagPreferenceSelector(AntagPrototype proto, ButtonGroup btnGroup)
: base(proto, btnGroup)
{
Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference);
var items = new[]
{
("humanoid-profile-editor-antag-preference-yes-button", 0),
("humanoid-profile-editor-antag-preference-no-button", 1)
};
var title = Loc.GetString(proto.Name);
var description = Loc.GetString(proto.Objective);
// Not supported yet get fucked.
Setup(null, items, title, 250, description);
// immediately lock requirements if they arent met.
// another function checks Disabled after creating the selector so this has to be done now
var requirements = IoCManager.Resolve<JobRequirementsManager>();
if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason))
{
LockRequirements(reason);
}
}
}

View File

@@ -40,7 +40,7 @@
<gfx:StyleBoxFlat BackgroundColor="{x:Static style:StyleNano.NanoGold}" ContentMarginTopOverride="2" />
</PanelContainer.PanelOverride>
</PanelContainer>
<BoxContainer Name="CharEditor" />
<BoxContainer Name="CharEditor" HorizontalExpand="True" />
</BoxContainer>
</BoxContainer>
</Control>

View File

@@ -3,27 +3,23 @@ using System.Numerics;
using Content.Client.Humanoid;
using Content.Client.Info;
using Content.Client.Info.PlaytimeStats;
using Content.Client.Lobby.UI;
using Content.Client.Lobby;
using Content.Client.Resources;
using Content.Client.Stylesheets;
using Content.Shared.Clothing;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using Direction = Robust.Shared.Maths.Direction;
@@ -36,7 +32,6 @@ namespace Content.Client.Preferences.UI
private readonly IClientPreferencesManager _preferencesManager;
private readonly IEntityManager _entityManager;
private readonly IPrototypeManager _prototypeManager;
private readonly IConfigurationManager _configurationManager;
private readonly Button _createNewCharacterButton;
private readonly HumanoidProfileEditor _humanoidProfileEditor;
@@ -51,7 +46,6 @@ namespace Content.Client.Preferences.UI
_entityManager = entityManager;
_prototypeManager = prototypeManager;
_preferencesManager = preferencesManager;
_configurationManager = configurationManager;
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture
@@ -74,7 +68,7 @@ namespace Content.Client.Preferences.UI
args.Event.Handle();
};
_humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, entityManager, configurationManager);
_humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, configurationManager);
_humanoidProfileEditor.OnProfileChanged += ProfileChanged;
CharEditor.AddChild(_humanoidProfileEditor);
@@ -103,6 +97,12 @@ namespace Content.Client.Preferences.UI
UpdateUI();
}
public void UpdateControls()
{
// Reset sliders etc. upon going going back to GUI.
_humanoidProfileEditor.LoadServerData();
}
private void UpdateUI()
{
var numberOfFullSlots = 0;
@@ -120,11 +120,6 @@ namespace Content.Client.Preferences.UI
foreach (var (slot, character) in _preferencesManager.Preferences!.Characters)
{
if (character is null)
{
continue;
}
numberOfFullSlots++;
var characterPickerButton = new CharacterPickerButton(_entityManager,
_preferencesManager,
@@ -148,8 +143,12 @@ namespace Content.Client.Preferences.UI
_createNewCharacterButton.Disabled =
numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots;
Characters.AddChild(_createNewCharacterButton);
// TODO: Move this shit to the Lobby UI controller
}
/// <summary>
/// Shows individual characters on the side of the character GUI.
/// </summary>
private sealed class CharacterPickerButton : ContainerButton
{
private EntityUid _previewDummy;
@@ -180,7 +179,15 @@ namespace Content.Client.Preferences.UI
if (humanoid != null)
{
LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy, humanoid);
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
var job = controller.GetPreferredJob(humanoid);
controller.GiveDummyJobClothes(_previewDummy, humanoid, job);
if (prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
{
var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), entityManager, prototypeManager);
controller.GiveDummyLoadout(_previewDummy, loadout);
}
}
var isSelectedCharacter = profile == preferencesManager.Preferences?.SelectedCharacter;

View File

@@ -0,0 +1,11 @@
<PanelContainer
xmlns="https://spacestation14.io"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#2F2F35"
ContentMarginTopOverride="10"
ContentMarginBottomOverride="10"
ContentMarginLeftOverride="10"
ContentMarginRightOverride="10"/>
</PanelContainer.PanelOverride>
</PanelContainer>

View File

@@ -0,0 +1,14 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Preferences.UI;
[GenerateTypedNameReferences]
public sealed partial class HighlightedContainer : PanelContainer
{
public HighlightedContainer()
{
RobustXamlLoader.Load(this);
}
}

View File

@@ -5,8 +5,6 @@ namespace Content.Client.Preferences.UI
{
public sealed partial class HumanoidProfileEditor
{
private readonly IPrototypeManager _prototypeManager;
private void RandomizeEverything()
{
Profile = HumanoidCharacterProfile.Random();

View File

@@ -1,11 +1,11 @@
<Control xmlns="https://spacestation14.io"
<BoxContainer xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prefUi="clr-namespace:Content.Client.Preferences.UI"
xmlns:humanoid="clr-namespace:Content.Client.Humanoid"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
<BoxContainer Orientation="Horizontal">
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
HorizontalExpand="True">
<!-- Left side -->
<BoxContainer Orientation="Vertical" Margin="10 10 10 10">
<BoxContainer Orientation="Vertical" Margin="10 10 10 10" HorizontalExpand="True">
<!-- Middle container -->
<BoxContainer Orientation="Horizontal" SeparationOverride="10">
<!-- Name box-->
@@ -58,7 +58,9 @@
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-species-label'}" />
<Control HorizontalExpand="True"/>
<TextureButton Name="SpeciesInfoButton" Scale="0.3 0.3" VerticalAlignment="Center"></TextureButton>
<TextureButton Name="SpeciesInfoButton" Scale="0.3 0.3"
VerticalAlignment="Center"
ToolTip="{Loc 'humanoid-profile-editor-guidebook-button-tooltip'}"/>
<OptionButton Name="CSpeciesButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Age -->
@@ -85,18 +87,6 @@
<Control HorizontalExpand="True"/>
<Button Name="ShowClothes" Pressed="True" ToggleMode="True" Text="{Loc 'humanoid-profile-editor-clothing-show'}" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Clothing -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-clothing-label'}" />
<Control HorizontalExpand="True"/>
<OptionButton Name="CClothingButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Backpack -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-backpack-label'}" />
<Control HorizontalExpand="True"/>
<OptionButton Name="CBackpackButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Spawn Priority -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-spawn-priority-label'}" />
@@ -151,7 +141,7 @@
</TabContainer>
</BoxContainer>
<!-- Right side -->
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" VerticalAlignment="Center">
<BoxContainer Orientation="Vertical" VerticalExpand="True" VerticalAlignment="Center">
<SpriteView Name="CSpriteView" Scale="8 8" SizeFlagsStretchRatio="1" />
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 5">
<Button Name="CSpriteRotateLeft" Text="◀" StyleClasses="OpenRight" />
@@ -159,5 +149,4 @@
<Button Name="CSpriteRotateRight" Text="▶" StyleClasses="OpenLeft" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
</Control>
</BoxContainer>

View File

@@ -2,69 +2,48 @@ using System.Linq;
using System.Numerics;
using Content.Client.Guidebook;
using Content.Client.Humanoid;
using Content.Client.Lobby.UI;
using Content.Client.Lobby;
using Content.Client.Message;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.CCVar;
using Content.Shared.Clothing;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Roles;
using Content.Shared.StatusIcon;
using Content.Shared.Traits;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using Direction = Robust.Shared.Maths.Direction;
namespace Content.Client.Preferences.UI
{
public sealed class HighlightedContainer : PanelContainer
{
public HighlightedContainer()
{
PanelOverride = new StyleBoxFlat()
{
BackgroundColor = new Color(47, 47, 53),
ContentMarginTopOverride = 10,
ContentMarginBottomOverride = 10,
ContentMarginLeftOverride = 10,
ContentMarginRightOverride = 10
};
}
}
[GenerateTypedNameReferences]
public sealed partial class HumanoidProfileEditor : Control
public sealed partial class HumanoidProfileEditor : BoxContainer
{
private readonly IClientPreferencesManager _preferencesManager;
private readonly IEntityManager _entMan;
private readonly IConfigurationManager _configurationManager;
private readonly IPrototypeManager _prototypeManager;
private readonly MarkingManager _markingManager;
private readonly JobRequirementsManager _requirements;
private LineEdit _ageEdit => CAgeEdit;
private LineEdit _nameEdit => CNameEdit;
private TextEdit _flavorTextEdit = null!;
private TextEdit? _flavorTextEdit;
private Button _nameRandomButton => CNameRandomize;
private Button _randomizeEverythingButton => CRandomizeEverything;
private RichTextLabel _warningLabel => CWarningLabel;
@@ -72,8 +51,6 @@ namespace Content.Client.Preferences.UI
private OptionButton _sexButton => CSexButton;
private OptionButton _genderButton => CPronounsButton;
private Slider _skinColor => CSkin;
private OptionButton _clothingButton => CClothingButton;
private OptionButton _backpackButton => CBackpackButton;
private OptionButton _spawnPriorityButton => CSpawnPriorityButton;
private SingleMarkingPicker _hairPicker => CHairStylePicker;
private SingleMarkingPicker _facialHairPicker => CFacialHairPicker;
@@ -88,44 +65,39 @@ namespace Content.Client.Preferences.UI
private readonly Dictionary<string, BoxContainer> _jobCategories;
// Mildly hacky, as I don't trust prototype order to stay consistent and don't want the UI to break should a new one get added mid-edit. --moony
private readonly List<SpeciesPrototype> _speciesList;
private readonly List<AntagPreferenceSelector> _antagPreferences;
private readonly List<AntagPreferenceSelector> _antagPreferences = new();
private readonly List<TraitPreferenceSelector> _traitPreferences;
private SpriteView _previewSpriteView => CSpriteView;
private Button _previewRotateLeftButton => CSpriteRotateLeft;
private Button _previewRotateRightButton => CSpriteRotateRight;
private Direction _previewRotation = Direction.North;
private EntityUid? _previewDummy;
private BoxContainer _rgbSkinColorContainer => CRgbSkinColorContainer;
private ColorSelectorSliders _rgbSkinColorSelector;
private bool _isDirty;
private bool _needUpdatePreview;
public int CharacterSlot;
public HumanoidCharacterProfile? Profile;
private MarkingSet _markingSet = new(); // storing this here feels iffy but a few things need it this high up
public event Action<HumanoidCharacterProfile, int>? OnProfileChanged;
public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager,
IEntityManager entityManager, IConfigurationManager configurationManager)
[ValidatePrototypeId<GuideEntryPrototype>]
private const string DefaultSpeciesGuidebook = "Species";
public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager, IConfigurationManager configurationManager)
{
RobustXamlLoader.Load(this);
_prototypeManager = prototypeManager;
_entMan = entityManager;
_preferencesManager = preferencesManager;
_configurationManager = configurationManager;
_markingManager = IoCManager.Resolve<MarkingManager>();
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
controller.PreviewDummyUpdated += OnDummyUpdate;
SpeciesInfoButton.ToolTip = Loc.GetString("humanoid-profile-editor-guidebook-button-tooltip");
_previewSpriteView.SetEntity(controller.GetPreviewDummy());
#region Left
#region Randomize
#endregion Randomize
#region Name
_nameEdit.OnTextChanged += args => { SetName(args.Text); };
@@ -139,8 +111,6 @@ namespace Content.Client.Preferences.UI
_tabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab"));
ShowClothes.OnPressed += ToggleClothes;
#region Sex
_sexButton.OnItemSelected += args =>
@@ -220,7 +190,7 @@ namespace Content.Client.Preferences.UI
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairStyleName(newStyle.id));
IsDirty = true;
SetDirty();
};
_hairPicker.OnColorChanged += newColor =>
@@ -230,7 +200,7 @@ namespace Content.Client.Preferences.UI
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
UpdateCMarkingsHair();
IsDirty = true;
SetDirty();
};
_facialHairPicker.OnMarkingSelect += newStyle =>
@@ -239,7 +209,7 @@ namespace Content.Client.Preferences.UI
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairStyleName(newStyle.id));
IsDirty = true;
SetDirty();
};
_facialHairPicker.OnColorChanged += newColor =>
@@ -249,7 +219,7 @@ namespace Content.Client.Preferences.UI
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
UpdateCMarkingsFacialHair();
IsDirty = true;
SetDirty();
};
_hairPicker.OnSlotRemove += _ =>
@@ -261,7 +231,7 @@ namespace Content.Client.Preferences.UI
);
UpdateHairPickers();
UpdateCMarkingsHair();
IsDirty = true;
SetDirty();
};
_facialHairPicker.OnSlotRemove += _ =>
@@ -273,7 +243,7 @@ namespace Content.Client.Preferences.UI
);
UpdateHairPickers();
UpdateCMarkingsFacialHair();
IsDirty = true;
SetDirty();
};
_hairPicker.OnSlotAdd += delegate()
@@ -293,7 +263,7 @@ namespace Content.Client.Preferences.UI
UpdateHairPickers();
UpdateCMarkingsHair();
IsDirty = true;
SetDirty();
};
_facialHairPicker.OnSlotAdd += delegate()
@@ -313,38 +283,11 @@ namespace Content.Client.Preferences.UI
UpdateHairPickers();
UpdateCMarkingsFacialHair();
IsDirty = true;
SetDirty();
};
#endregion Hair
#region Clothing
_clothingButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-jumpsuit"), (int) ClothingPreference.Jumpsuit);
_clothingButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-jumpskirt"), (int) ClothingPreference.Jumpskirt);
_clothingButton.OnItemSelected += args =>
{
_clothingButton.SelectId(args.Id);
SetClothing((ClothingPreference) args.Id);
};
#endregion Clothing
#region Backpack
_backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-backpack"), (int) BackpackPreference.Backpack);
_backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-satchel"), (int) BackpackPreference.Satchel);
_backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-duffelbag"), (int) BackpackPreference.Duffelbag);
_backpackButton.OnItemSelected += args =>
{
_backpackButton.SelectId(args.Id);
SetBackpack((BackpackPreference) args.Id);
};
#endregion Backpack
#region SpawnPriority
foreach (var value in Enum.GetValues<SpawnPriorityPreference>())
@@ -369,7 +312,7 @@ namespace Content.Client.Preferences.UI
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithEyeColor(newColor));
CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
IsDirty = true;
SetDirty();
};
#endregion Eyes
@@ -393,46 +336,22 @@ namespace Content.Client.Preferences.UI
_preferenceUnavailableButton.SelectId(args.Id);
Profile = Profile?.WithPreferenceUnavailable((PreferenceUnavailableMode) args.Id);
IsDirty = true;
SetDirty();
};
_jobPriorities = new List<JobPrioritySelector>();
_jobCategories = new Dictionary<string, BoxContainer>();
_requirements = IoCManager.Resolve<JobRequirementsManager>();
// TODO: Move this to the LobbyUIController instead of being spaghetti everywhere.
_requirements.Updated += UpdateAntagRequirements;
_requirements.Updated += UpdateRoleRequirements;
UpdateAntagRequirements();
UpdateRoleRequirements();
#endregion Jobs
#region Antags
_tabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
_antagPreferences = new List<AntagPreferenceSelector>();
foreach (var antag in prototypeManager.EnumeratePrototypes<AntagPrototype>().OrderBy(a => Loc.GetString(a.Name)))
{
if (!antag.SetPreference)
continue;
var selector = new AntagPreferenceSelector(antag);
_antagList.AddChild(selector);
_antagPreferences.Add(selector);
if (selector.Disabled)
{
Profile = Profile?.WithAntagPreference(antag.ID, false);
IsDirty = true;
}
selector.PreferenceChanged += preference =>
{
Profile = Profile?.WithAntagPreference(antag.ID, preference);
IsDirty = true;
};
}
#endregion Antags
#region Traits
var traits = prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
@@ -450,7 +369,7 @@ namespace Content.Client.Preferences.UI
selector.PreferenceChanged += preference =>
{
Profile = Profile?.WithTraitPreference(trait.ID, preference);
IsDirty = true;
SetDirty();
};
}
}
@@ -483,7 +402,7 @@ namespace Content.Client.Preferences.UI
#region FlavorText
if (_configurationManager.GetCVar(CCVars.FlavorText))
if (configurationManager.GetCVar(CCVars.FlavorText))
{
var flavorText = new FlavorText.FlavorText();
_tabContainer.AddChild(flavorText);
@@ -500,22 +419,14 @@ namespace Content.Client.Preferences.UI
_previewRotateLeftButton.OnPressed += _ =>
{
_previewRotation = _previewRotation.TurnCw();
_needUpdatePreview = true;
SetPreviewRotation(_previewRotation);
};
_previewRotateRightButton.OnPressed += _ =>
{
_previewRotation = _previewRotation.TurnCcw();
_needUpdatePreview = true;
SetPreviewRotation(_previewRotation);
};
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype;
if (_previewDummy != null)
_entMan.DeleteEntity(_previewDummy!.Value);
_previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
_previewSpriteView.SetEntity(_previewDummy);
#endregion Dummy
#endregion Left
@@ -525,6 +436,13 @@ namespace Content.Client.Preferences.UI
LoadServerData();
}
ShowClothes.OnToggled += args =>
{
var lobby = UserInterfaceManager.GetUIController<LobbyUIController>();
lobby.SetClothes(args.Pressed);
SetDirty();
};
preferencesManager.OnServerDataLoaded += LoadServerData;
SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed;
@@ -532,28 +450,69 @@ namespace Content.Client.Preferences.UI
UpdateSpeciesGuidebookIcon();
IsDirty = false;
controller.UpdateProfile();
}
private void SetDirty()
{
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
controller.UpdateProfile(Profile);
controller.ReloadCharacterUI();
IsDirty = true;
}
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
{
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
var page = "Species";
var page = DefaultSpeciesGuidebook;
if (_prototypeManager.HasIndex<GuideEntryPrototype>(species))
page = species;
if (_prototypeManager.TryIndex<GuideEntryPrototype>("Species", out var guideRoot))
if (_prototypeManager.TryIndex<GuideEntryPrototype>(DefaultSpeciesGuidebook, out var guideRoot))
{
var dict = new Dictionary<string, GuideEntry>();
dict.Add("Species", guideRoot);
dict.Add(DefaultSpeciesGuidebook, guideRoot);
//TODO: Don't close the guidebook if its already open, just go to the correct page
guidebookController.ToggleGuidebook(dict, includeChildren:true, selected: page);
}
}
private void ToggleClothes(BaseButton.ButtonEventArgs obj)
private void OnDummyUpdate(EntityUid value)
{
RebuildSpriteView();
_previewSpriteView.SetEntity(value);
}
private void UpdateAntagRequirements()
{
_antagList.DisposeAllChildren();
_antagPreferences.Clear();
var btnGroup = new ButtonGroup();
foreach (var antag in _prototypeManager.EnumeratePrototypes<AntagPrototype>().OrderBy(a => Loc.GetString(a.Name)))
{
if (!antag.SetPreference)
continue;
var selector = new AntagPreferenceSelector(antag, btnGroup)
{
Margin = new Thickness(3f, 3f, 3f, 0f),
};
_antagList.AddChild(selector);
_antagPreferences.Add(selector);
if (selector.Disabled)
{
Profile = Profile?.WithAntagPreference(antag.ID, false);
SetDirty();
}
selector.PreferenceChanged += preference =>
{
Profile = Profile?.WithAntagPreference(antag.ID, preference);
SetDirty();
};
}
}
private void UpdateRoleRequirements()
@@ -614,10 +573,19 @@ namespace Content.Client.Preferences.UI
.Where(job => job.SetPreference)
.ToArray();
Array.Sort(jobs, JobUIComparer.Instance);
var jobLoadoutGroup = new ButtonGroup();
foreach (var job in jobs)
{
var selector = new JobPrioritySelector(job, _prototypeManager);
RoleLoadout? loadout = null;
// Clone so we don't modify the underlying loadout.
Profile?.Loadouts.TryGetValue(LoadoutSystem.GetJobPrototype(job.ID), out loadout);
loadout = loadout?.Clone();
var selector = new JobPrioritySelector(loadout, job, jobLoadoutGroup, _prototypeManager)
{
Margin = new Thickness(3f, 3f, 3f, 0f),
};
if (!_requirements.IsAllowed(job, out var reason))
{
@@ -627,10 +595,15 @@ namespace Content.Client.Preferences.UI
category.AddChild(selector);
_jobPriorities.Add(selector);
selector.LoadoutUpdated += args =>
{
Profile = Profile?.WithLoadout(args);
SetDirty();
};
selector.PriorityChanged += priority =>
{
Profile = Profile?.WithJobPriority(job.ID, priority);
IsDirty = true;
foreach (var jobSelector in _jobPriorities)
{
@@ -646,6 +619,8 @@ namespace Content.Client.Preferences.UI
Profile = Profile?.WithJobPriority(jobSelector.Proto.ID, JobPriority.Medium);
}
}
SetDirty();
};
}
@@ -663,7 +638,7 @@ namespace Content.Client.Preferences.UI
return;
Profile = Profile.WithFlavorText(content);
IsDirty = true;
SetDirty();
}
private void OnMarkingChange(MarkingSet markings)
@@ -672,20 +647,12 @@ namespace Content.Client.Preferences.UI
return;
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
_needUpdatePreview = true;
IsDirty = true;
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
controller.UpdateProfile(Profile);
controller.ReloadProfile();
}
private void OnMarkingColorChange(List<Marking> markings)
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings));
IsDirty = true;
}
private void OnSkinColorOnValueChanged()
{
if (Profile is null) return;
@@ -737,6 +704,9 @@ namespace Content.Client.Preferences.UI
}
IsDirty = true;
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
controller.UpdateProfile(Profile);
controller.ReloadProfile();
}
protected override void Dispose(bool disposing)
@@ -745,39 +715,28 @@ namespace Content.Client.Preferences.UI
if (!disposing)
return;
if (_previewDummy != null)
_entMan.DeleteEntity(_previewDummy.Value);
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
controller.PreviewDummyUpdated -= OnDummyUpdate;
_requirements.Updated -= UpdateAntagRequirements;
_requirements.Updated -= UpdateRoleRequirements;
_preferencesManager.OnServerDataLoaded -= LoadServerData;
}
private void RebuildSpriteView()
{
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype;
if (_previewDummy != null)
_entMan.DeleteEntity(_previewDummy!.Value);
_previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
_previewSpriteView.SetEntity(_previewDummy);
_needUpdatePreview = true;
}
private void LoadServerData()
public void LoadServerData()
{
Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
UpdateAntagRequirements();
UpdateRoleRequirements();
UpdateControls();
_needUpdatePreview = true;
ShowClothes.Pressed = true;
}
private void SetAge(int newAge)
{
Profile = Profile?.WithAge(newAge);
IsDirty = true;
SetDirty();
}
private void SetSex(Sex newSex)
@@ -798,13 +757,13 @@ namespace Content.Client.Preferences.UI
}
UpdateGenderControls();
CMarkings.SetSex(newSex);
IsDirty = true;
SetDirty();
}
private void SetGender(Gender newGender)
{
Profile = Profile?.WithGender(newGender);
IsDirty = true;
SetDirty();
}
private void SetSpecies(string newSpecies)
@@ -813,46 +772,34 @@ namespace Content.Client.Preferences.UI
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
CMarkings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
UpdateSexControls(); // update sex for new species
RebuildSpriteView(); // they might have different inv so we need a new dummy
UpdateSpeciesGuidebookIcon();
IsDirty = true;
_needUpdatePreview = true;
SetDirty();
UpdatePreview();
}
private void SetName(string newName)
{
Profile = Profile?.WithName(newName);
IsDirty = true;
}
private void SetClothing(ClothingPreference newClothing)
{
Profile = Profile?.WithClothingPreference(newClothing);
IsDirty = true;
}
private void SetBackpack(BackpackPreference newBackpack)
{
Profile = Profile?.WithBackpackPreference(newBackpack);
IsDirty = true;
SetDirty();
}
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
{
Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
IsDirty = true;
SetDirty();
}
public void Save()
{
IsDirty = false;
if (Profile != null)
{
_preferencesManager.UpdateCharacter(Profile, CharacterSlot);
OnProfileChanged?.Invoke(Profile, CharacterSlot);
_needUpdatePreview = true;
}
if (Profile == null)
return;
_preferencesManager.UpdateCharacter(Profile, CharacterSlot);
OnProfileChanged?.Invoke(Profile, CharacterSlot);
// Reset profile to default.
UserInterfaceManager.GetUIController<LobbyUIController>().UpdateProfile();
}
private bool IsDirty
@@ -861,7 +808,6 @@ namespace Content.Client.Preferences.UI
set
{
_isDirty = value;
_needUpdatePreview = true;
UpdateSaveButton();
}
}
@@ -981,7 +927,7 @@ namespace Content.Client.Preferences.UI
if (!_prototypeManager.HasIndex<GuideEntryPrototype>(species))
return;
var style = speciesProto.GuideBookIcon;
const string style = "SpeciesInfoDefault";
SpeciesInfoButton.StyleClasses.Add(style);
}
@@ -1017,26 +963,6 @@ namespace Content.Client.Preferences.UI
_genderButton.SelectId((int) Profile.Gender);
}
private void UpdateClothingControls()
{
if (Profile == null)
{
return;
}
_clothingButton.SelectId((int) Profile.Clothing);
}
private void UpdateBackpackControls()
{
if (Profile == null)
{
return;
}
_backpackButton.SelectId((int) Profile.Backpack);
}
private void UpdateSpawnPriorityControls()
{
if (Profile == null)
@@ -1166,13 +1092,13 @@ namespace Content.Client.Preferences.UI
if (Profile is null)
return;
var humanoid = _entMan.System<HumanoidAppearanceSystem>();
humanoid.LoadProfile(_previewDummy!.Value, Profile);
UserInterfaceManager.GetUIController<LobbyUIController>().ReloadProfile();
SetPreviewRotation(_previewRotation);
}
if (ShowClothes.Pressed)
LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile);
_previewSpriteView.OverrideDirection = (Direction) ((int) _previewRotation % 4 * 2);
private void SetPreviewRotation(Direction direction)
{
_previewSpriteView.OverrideDirection = (Direction) ((int) direction % 4 * 2);
}
public void UpdateControls()
@@ -1184,17 +1110,15 @@ namespace Content.Client.Preferences.UI
UpdateGenderControls();
UpdateSkinColor();
UpdateSpecies();
UpdateClothingControls();
UpdateBackpackControls();
UpdateSpawnPriorityControls();
UpdateAgeEdit();
UpdateEyePickers();
UpdateSaveButton();
UpdateLoadouts();
UpdateJobPriorities();
UpdateAntagPreferences();
UpdateTraitPreferences();
UpdateMarkings();
RebuildSpriteView();
UpdateHairPickers();
UpdateCMarkingsHair();
UpdateCMarkingsFacialHair();
@@ -1202,17 +1126,6 @@ namespace Content.Client.Preferences.UI
_preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (_needUpdatePreview)
{
UpdatePreview();
_needUpdatePreview = false;
}
}
private void UpdateJobPriorities()
{
foreach (var prioritySelector in _jobPriorities)
@@ -1225,143 +1138,11 @@ namespace Content.Client.Preferences.UI
}
}
private abstract class RequirementsSelector<T> : Control
private void UpdateLoadouts()
{
public T Proto { get; }
public bool Disabled => _lockStripe.Visible;
protected readonly RadioOptions<int> Options;
private StripeBack _lockStripe;
private Label _requirementsLabel;
protected RequirementsSelector(T proto)
foreach (var prioritySelector in _jobPriorities)
{
Proto = proto;
Options = new RadioOptions<int>(RadioOptionsLayout.Horizontal)
{
FirstButtonStyle = StyleBase.ButtonOpenRight,
ButtonStyle = StyleBase.ButtonOpenBoth,
LastButtonStyle = StyleBase.ButtonOpenLeft
};
//Override default radio option button width
Options.GenerateItem = GenerateButton;
Options.OnItemSelected += args => Options.Select(args.Id);
_requirementsLabel = new Label()
{
Text = Loc.GetString("role-timer-locked"),
Visible = true,
HorizontalAlignment = HAlignment.Center,
StyleClasses = {StyleBase.StyleClassLabelSubText},
};
_lockStripe = new StripeBack()
{
Visible = false,
HorizontalExpand = true,
MouseFilter = MouseFilterMode.Stop,
Children =
{
_requirementsLabel
}
};
// Setup must be called after
}
/// <summary>
/// Actually adds the controls, must be called in the inheriting class' constructor.
/// </summary>
protected void Setup((string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
{
foreach (var (text, value) in items)
{
Options.AddItem(Loc.GetString(text), value);
}
var titleLabel = new Label()
{
Margin = new Thickness(5f, 0, 5f, 0),
Text = title,
MinSize = new Vector2(titleSize, 0),
MouseFilter = MouseFilterMode.Stop,
ToolTip = description
};
var container = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
};
if (icon != null)
container.AddChild(icon);
container.AddChild(titleLabel);
container.AddChild(Options);
container.AddChild(_lockStripe);
AddChild(container);
}
public void LockRequirements(FormattedMessage requirements)
{
var tooltip = new Tooltip();
tooltip.SetMessage(requirements);
_lockStripe.TooltipSupplier = _ => tooltip;
_lockStripe.Visible = true;
Options.Visible = false;
}
// TODO: Subscribe to roletimers event. I am too lazy to do this RN But I doubt most people will notice fn
public void UnlockRequirements()
{
_lockStripe.Visible = false;
Options.Visible = true;
}
private Button GenerateButton(string text, int value)
{
return new Button
{
Text = text,
MinWidth = 90
};
}
}
private sealed class JobPrioritySelector : RequirementsSelector<JobPrototype>
{
public JobPriority Priority
{
get => (JobPriority) Options.SelectedValue;
set => Options.SelectByValue((int) value);
}
public event Action<JobPriority>? PriorityChanged;
public JobPrioritySelector(JobPrototype proto, IPrototypeManager protoMan)
: base(proto)
{
Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority);
var items = new[]
{
("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
};
var icon = new TextureRect
{
TextureScale = new Vector2(2, 2),
VerticalAlignment = VAlignment.Center
};
var jobIcon = protoMan.Index<StatusIconPrototype>(proto.Icon);
icon.Texture = jobIcon.Icon.Frame0();
Setup(items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
prioritySelector.CloseLoadout();
}
}
@@ -1386,41 +1167,6 @@ namespace Content.Client.Preferences.UI
}
}
private sealed class AntagPreferenceSelector : RequirementsSelector<AntagPrototype>
{
// 0 is yes and 1 is no
public bool Preference
{
get => Options.SelectedValue == 0;
set => Options.Select((value && !Disabled) ? 0 : 1);
}
public event Action<bool>? PreferenceChanged;
public AntagPreferenceSelector(AntagPrototype proto)
: base(proto)
{
Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference);
var items = new[]
{
("humanoid-profile-editor-antag-preference-yes-button", 0),
("humanoid-profile-editor-antag-preference-no-button", 1)
};
var title = Loc.GetString(proto.Name);
var description = Loc.GetString(proto.Objective);
Setup(items, title, 250, description);
// immediately lock requirements if they arent met.
// another function checks Disabled after creating the selector so this has to be done now
var requirements = IoCManager.Resolve<JobRequirementsManager>();
if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason))
{
LockRequirements(reason);
}
}
}
private sealed class TraitPreferenceSelector : Control
{
public TraitPrototype Trait { get; }

View File

@@ -0,0 +1,46 @@
using System.Numerics;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Roles;
using Content.Shared.StatusIcon;
using Robust.Client.UserInterface.Controls;
using Robust.Client.Utility;
using Robust.Shared.Prototypes;
namespace Content.Client.Preferences.UI;
public sealed class JobPrioritySelector : RequirementsSelector<JobPrototype>
{
public JobPriority Priority
{
get => (JobPriority) Options.SelectedValue;
set => Options.SelectByValue((int) value);
}
public event Action<JobPriority>? PriorityChanged;
public JobPrioritySelector(RoleLoadout? loadout, JobPrototype proto, ButtonGroup btnGroup, IPrototypeManager protoMan)
: base(proto, btnGroup)
{
Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority);
var items = new[]
{
("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
};
var icon = new TextureRect
{
TextureScale = new Vector2(2, 2),
VerticalAlignment = VAlignment.Center
};
var jobIcon = protoMan.Index<StatusIconPrototype>(proto.Icon);
icon.Texture = jobIcon.Icon.Frame0();
Setup(loadout, items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
}
}

View File

@@ -0,0 +1,15 @@
<BoxContainer Name="Container" xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Orientation="Horizontal"
HorizontalExpand="True"
MouseFilter="Ignore"
Margin="0 0 0 5">
<Button Name="SelectButton" ToggleMode="True" Margin="0 0 5 0" HorizontalExpand="True"/>
<PanelContainer SetSize="64 64" HorizontalAlignment="Right">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<SpriteView Name="Sprite" Scale="4 4" MouseFilter="Stop"/>
</PanelContainer>
</BoxContainer>

View File

@@ -0,0 +1,74 @@
using Content.Shared.Clothing;
using Content.Shared.Preferences.Loadouts;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Preferences.UI;
[GenerateTypedNameReferences]
public sealed partial class LoadoutContainer : BoxContainer
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
private readonly EntityUid? _entity;
public Button Select => SelectButton;
public LoadoutContainer(ProtoId<LoadoutPrototype> proto, bool disabled, FormattedMessage? reason)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
SelectButton.Disabled = disabled;
if (disabled && reason != null)
{
var tooltip = new Tooltip();
tooltip.SetMessage(reason);
SelectButton.TooltipSupplier = _ => tooltip;
}
if (_protoManager.TryIndex(proto, out var loadProto))
{
var ent = _entManager.System<LoadoutSystem>().GetFirstOrNull(loadProto);
if (ent != null)
{
_entity = _entManager.SpawnEntity(ent, MapCoordinates.Nullspace);
Sprite.SetEntity(_entity);
var spriteTooltip = new Tooltip();
spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent<MetaDataComponent>(_entity.Value).EntityDescription));
Sprite.TooltipSupplier = _ => spriteTooltip;
}
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_entManager.DeleteEntity(_entity);
}
public bool Pressed
{
get => SelectButton.Pressed;
set => SelectButton.Pressed = value;
}
public string? Text
{
get => SelectButton.Text;
set => SelectButton.Text = value;
}
}

View File

@@ -0,0 +1,10 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Orientation="Vertical">
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True">
<BoxContainer Name="LoadoutsContainer" Orientation="Vertical"/>
</PanelContainer>
<!-- Buffer space so we have 10 margin between controls but also 10 to the borders -->
<Label Text="{Loc 'loadout-restrictions'}" Margin="5 0 5 5"/>
<BoxContainer Name="RestrictionsContainer" Orientation="Vertical" HorizontalExpand="True" />
</BoxContainer>

View File

@@ -0,0 +1,93 @@
using System.Linq;
using Content.Shared.Clothing;
using Content.Shared.Preferences.Loadouts;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Preferences.UI;
[GenerateTypedNameReferences]
public sealed partial class LoadoutGroupContainer : BoxContainer
{
private readonly LoadoutGroupPrototype _groupProto;
public event Action<ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
public event Action<ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
public LoadoutGroupContainer(RoleLoadout loadout, LoadoutGroupPrototype groupProto, ICommonSession session, IDependencyCollection collection)
{
RobustXamlLoader.Load(this);
_groupProto = groupProto;
RefreshLoadouts(loadout, session, collection);
}
/// <summary>
/// Updates button availabilities and buttons.
/// </summary>
public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
{
var protoMan = collection.Resolve<IPrototypeManager>();
var loadoutSystem = collection.Resolve<IEntityManager>().System<LoadoutSystem>();
RestrictionsContainer.DisposeAllChildren();
if (_groupProto.MinLimit > 0)
{
RestrictionsContainer.AddChild(new Label()
{
Text = Loc.GetString("loadouts-min-limit", ("count", _groupProto.MinLimit)),
Margin = new Thickness(5, 0, 5, 5),
});
}
if (_groupProto.MaxLimit > 0)
{
RestrictionsContainer.AddChild(new Label()
{
Text = Loc.GetString("loadouts-max-limit", ("count", _groupProto.MaxLimit)),
Margin = new Thickness(5, 0, 5, 5),
});
}
if (protoMan.TryIndex(loadout.Role, out var roleProto) && roleProto.Points != null && loadout.Points != null)
{
RestrictionsContainer.AddChild(new Label()
{
Text = Loc.GetString("loadouts-points-limit", ("count", loadout.Points.Value), ("max", roleProto.Points.Value)),
Margin = new Thickness(5, 0, 5, 5),
});
}
LoadoutsContainer.DisposeAllChildren();
// Didn't use options because this is more robust in future.
var selected = loadout.SelectedLoadouts[_groupProto.ID];
foreach (var loadoutProto in _groupProto.Loadouts)
{
if (!protoMan.TryIndex(loadoutProto, out var loadProto))
continue;
var matchingLoadout = selected.FirstOrDefault(e => e.Prototype == loadoutProto);
var pressed = matchingLoadout != null;
var enabled = loadout.IsValid(session, loadoutProto, collection, out var reason);
var loadoutContainer = new LoadoutContainer(loadoutProto, !enabled, reason);
loadoutContainer.Select.Pressed = pressed;
loadoutContainer.Text = loadoutSystem.GetName(loadProto);
loadoutContainer.Select.OnPressed += args =>
{
if (args.Button.Pressed)
OnLoadoutPressed?.Invoke(loadoutProto);
else
OnLoadoutUnpressed?.Invoke(loadoutProto);
};
LoadoutsContainer.AddChild(loadoutContainer);
}
}
}

View File

@@ -0,0 +1,10 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
SetSize="800 800"
MinSize="800 64">
<VerticalTabContainer Name="LoadoutGroupsContainer"
VerticalExpand="True"
HorizontalExpand="True">
</VerticalTabContainer>
</controls:FancyWindow>

View File

@@ -0,0 +1,60 @@
using Content.Client.Lobby;
using Content.Client.UserInterface.Controls;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Preferences.UI;
[GenerateTypedNameReferences]
public sealed partial class LoadoutWindow : FancyWindow
{
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
private List<LoadoutGroupContainer> _groups = new();
public LoadoutWindow(RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection)
{
RobustXamlLoader.Load(this);
var protoManager = collection.Resolve<IPrototypeManager>();
foreach (var group in proto.Groups)
{
if (!protoManager.TryIndex(group, out var groupProto))
continue;
var container = new LoadoutGroupContainer(loadout, protoManager.Index(group), session, collection);
LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
_groups.Add(container);
container.OnLoadoutPressed += args =>
{
OnLoadoutPressed?.Invoke(group, args);
};
container.OnLoadoutUnpressed += args =>
{
OnLoadoutUnpressed?.Invoke(group, args);
};
}
}
public override void Close()
{
base.Close();
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
controller.SetDummyJob(null);
}
public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
{
foreach (var group in _groups)
{
group.RefreshLoadouts(loadout, session, collection);
}
}
}

View File

@@ -0,0 +1,222 @@
using System.Numerics;
using Content.Client.Lobby;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Clothing;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Roles;
using Robust.Client.Player;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Preferences.UI;
public abstract class RequirementsSelector<T> : BoxContainer where T : IPrototype
{
private ButtonGroup _loadoutGroup;
public T Proto { get; }
public bool Disabled => _lockStripe.Visible;
protected readonly RadioOptions<int> Options;
private readonly StripeBack _lockStripe;
private LoadoutWindow? _loadoutWindow;
private RoleLoadout? _loadout;
/// <summary>
/// Raised if a loadout has been updated.
/// </summary>
public event Action<RoleLoadout>? LoadoutUpdated;
protected RequirementsSelector(T proto, ButtonGroup loadoutGroup)
{
_loadoutGroup = loadoutGroup;
Proto = proto;
Options = new RadioOptions<int>(RadioOptionsLayout.Horizontal)
{
FirstButtonStyle = StyleBase.ButtonOpenRight,
ButtonStyle = StyleBase.ButtonOpenBoth,
LastButtonStyle = StyleBase.ButtonOpenLeft,
HorizontalExpand = true,
};
//Override default radio option button width
Options.GenerateItem = GenerateButton;
Options.OnItemSelected += args => Options.Select(args.Id);
var requirementsLabel = new Label()
{
Text = Loc.GetString("role-timer-locked"),
Visible = true,
HorizontalAlignment = HAlignment.Center,
StyleClasses = {StyleBase.StyleClassLabelSubText},
};
_lockStripe = new StripeBack()
{
Visible = false,
HorizontalExpand = true,
HasMargins = false,
MouseFilter = MouseFilterMode.Stop,
Children =
{
requirementsLabel
}
};
// Setup must be called after
}
/// <summary>
/// Actually adds the controls, must be called in the inheriting class' constructor.
/// </summary>
protected void Setup(RoleLoadout? loadout, (string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
{
_loadout = loadout;
foreach (var (text, value) in items)
{
Options.AddItem(Loc.GetString(text), value);
}
var titleLabel = new Label()
{
Margin = new Thickness(5f, 0, 5f, 0),
Text = title,
MinSize = new Vector2(titleSize, 0),
MouseFilter = MouseFilterMode.Stop,
ToolTip = description
};
if (icon != null)
AddChild(icon);
AddChild(titleLabel);
AddChild(Options);
AddChild(_lockStripe);
var loadoutWindowBtn = new Button()
{
Text = Loc.GetString("loadout-window"),
HorizontalAlignment = HAlignment.Right,
Group = _loadoutGroup,
Margin = new Thickness(3f, 0f, 0f, 0f),
};
var collection = IoCManager.Instance!;
var protoManager = collection.Resolve<IPrototypeManager>();
// If no loadout found then disabled button
if (!protoManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(Proto.ID)))
{
loadoutWindowBtn.Disabled = true;
}
// else
else
{
var session = collection.Resolve<IPlayerManager>().LocalSession!;
// TODO: Most of lobby state should be a uicontroller
// trying to handle all this shit is a big-ass mess.
// Every time I touch it I try to make it slightly better but it needs a howitzer dropped on it.
loadoutWindowBtn.OnPressed += args =>
{
if (args.Button.Pressed)
{
// We only create a loadout when necessary to avoid unnecessary DB entries.
_loadout ??= new RoleLoadout(LoadoutSystem.GetJobPrototype(Proto.ID));
_loadout.SetDefault(protoManager);
_loadoutWindow = new LoadoutWindow(_loadout, protoManager.Index(_loadout.Role), session, collection)
{
Title = Loc.GetString(Proto.ID + "-loadout"),
};
_loadoutWindow.RefreshLoadouts(_loadout, session, collection);
// If it's a job preview then refresh it.
if (Proto is JobPrototype jobProto)
{
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
controller.SetDummyJob(jobProto);
}
_loadoutWindow.OnLoadoutUnpressed += (selectedGroup, selectedLoadout) =>
{
if (!_loadout.RemoveLoadout(selectedGroup, selectedLoadout, protoManager))
return;
_loadout.EnsureValid(session, collection);
_loadoutWindow.RefreshLoadouts(_loadout, session, collection);
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
controller.ReloadProfile();
LoadoutUpdated?.Invoke(_loadout);
};
_loadoutWindow.OnLoadoutPressed += (selectedGroup, selectedLoadout) =>
{
if (!_loadout.AddLoadout(selectedGroup, selectedLoadout, protoManager))
return;
_loadout.EnsureValid(session, collection);
_loadoutWindow.RefreshLoadouts(_loadout, session, collection);
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
controller.ReloadProfile();
LoadoutUpdated?.Invoke(_loadout);
};
_loadoutWindow.OpenCenteredLeft();
_loadoutWindow.OnClose += () =>
{
loadoutWindowBtn.Pressed = false;
_loadoutWindow?.Dispose();
_loadoutWindow = null;
};
}
else
{
CloseLoadout();
}
};
}
AddChild(loadoutWindowBtn);
}
public void CloseLoadout()
{
_loadoutWindow?.Close();
_loadoutWindow?.Dispose();
_loadoutWindow = null;
}
public void LockRequirements(FormattedMessage requirements)
{
var tooltip = new Tooltip();
tooltip.SetMessage(requirements);
_lockStripe.TooltipSupplier = _ => tooltip;
_lockStripe.Visible = true;
Options.Visible = false;
}
// TODO: Subscribe to roletimers event. I am too lazy to do this RN But I doubt most people will notice fn
public void UnlockRequirements()
{
_lockStripe.Visible = false;
Options.Visible = true;
}
private Button GenerateButton(string text, int value)
{
return new Button
{
Text = text,
MinWidth = 90,
HorizontalExpand = true,
};
}
}

View File

@@ -1,8 +1,10 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Popups;
using Content.Shared.RCD;
using Content.Shared.RCD.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -16,17 +18,24 @@ public sealed partial class RCDMenu : RadialMenu
{
[Dependency] private readonly EntityManager _entManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private readonly SpriteSystem _spriteSystem;
private readonly SharedPopupSystem _popup;
public event Action<ProtoId<RCDPrototype>>? SendRCDSystemMessageAction;
private EntityUid _owner;
public RCDMenu(EntityUid owner, RCDMenuBoundUserInterface bui)
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
_spriteSystem = _entManager.System<SpriteSystem>();
_popup = _entManager.System<SharedPopupSystem>();
_owner = owner;
// Find the main radial container
var main = FindControl<RadialContainer>("Main");
@@ -51,14 +60,21 @@ public sealed partial class RCDMenu : RadialMenu
if (parent == null)
continue;
var name = Loc.GetString(proto.SetName);
name = char.ToUpper(name[0]) + name.Remove(0, 1);
var tooltip = Loc.GetString(proto.SetName);
if ((proto.Mode == RcdMode.ConstructTile || proto.Mode == RcdMode.ConstructObject) &&
proto.Prototype != null && _protoManager.TryIndex(proto.Prototype, out var entProto))
{
tooltip = Loc.GetString(entProto.Name);
}
tooltip = char.ToUpper(tooltip[0]) + tooltip.Remove(0, 1);
var button = new RCDMenuButton()
{
StyleClasses = { "RadialMenuButton" },
SetSize = new Vector2(64f, 64f),
ToolTip = name,
ToolTip = tooltip,
ProtoId = protoId,
};
@@ -120,6 +136,27 @@ public sealed partial class RCDMenu : RadialMenu
castChild.OnButtonUp += _ =>
{
SendRCDSystemMessageAction?.Invoke(castChild.ProtoId);
if (_playerManager.LocalSession?.AttachedEntity != null &&
_protoManager.TryIndex(castChild.ProtoId, out var proto))
{
var msg = Loc.GetString("rcd-component-change-mode", ("mode", Loc.GetString(proto.SetName)));
if (proto.Mode == RcdMode.ConstructTile || proto.Mode == RcdMode.ConstructObject)
{
var name = Loc.GetString(proto.SetName);
if (proto.Prototype != null &&
_protoManager.TryIndex(proto.Prototype, out var entProto))
name = entProto.Name;
msg = Loc.GetString("rcd-component-change-build-mode", ("name", name));
}
// Popup message
_popup.PopupClient(msg, _owner, _playerManager.LocalSession.AttachedEntity);
}
Close();
};
}

View File

@@ -3,9 +3,9 @@ using Content.Shared.StatusIcon.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using System.Numerics;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using System.Numerics;
namespace Content.Client.StatusIcon;
@@ -18,7 +18,7 @@ public sealed class StatusIconOverlay : Overlay
private readonly SpriteSystem _sprite;
private readonly TransformSystem _transform;
private readonly StatusIconSystem _statusIcon;
private readonly ShaderInstance _shader;
private readonly ShaderInstance _unshadedShader;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
@@ -29,7 +29,7 @@ public sealed class StatusIconOverlay : Overlay
_sprite = _entity.System<SpriteSystem>();
_transform = _entity.System<TransformSystem>();
_statusIcon = _entity.System<StatusIconSystem>();
_shader = _prototype.Index<ShaderPrototype>("unshaded").Instance();
_unshadedShader = _prototype.Index<ShaderPrototype>("unshaded").Instance();
}
protected override void Draw(in OverlayDrawArgs args)
@@ -42,8 +42,6 @@ public sealed class StatusIconOverlay : Overlay
var scaleMatrix = Matrix3.CreateScale(new Vector2(1, 1));
var rotationMatrix = Matrix3.CreateRotation(-eyeRot);
handle.UseShader(_shader);
var query = _entity.AllEntityQueryEnumerator<StatusIconComponent, SpriteComponent, TransformComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out var comp, out var sprite, out var xform, out var meta))
{
@@ -111,11 +109,16 @@ public sealed class StatusIconOverlay : Overlay
}
if (proto.IsShaded)
handle.UseShader(null);
else
handle.UseShader(_unshadedShader);
var position = new Vector2(xOffset, yOffset);
handle.DrawTexture(texture, position);
}
}
handle.UseShader(null);
handle.UseShader(null);
}
}
}

View File

@@ -17,7 +17,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
private string _windowName = Loc.GetString("store-ui-default-title");
[ViewVariables]
private string _search = "";
private string _search = string.Empty;
[ViewVariables]
private HashSet<ListingData> _listings = new();
@@ -41,7 +41,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
_menu.OnCategoryButtonPressed += (_, category) =>
{
_menu.CurrentCategory = category;
SendMessage(new StoreRequestUpdateInterfaceMessage());
_menu?.UpdateListing();
};
_menu.OnWithdrawAttempt += (_, type, amount) =>
@@ -49,11 +49,6 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
SendMessage(new StoreRequestWithdrawMessage(type, amount));
};
_menu.OnRefreshButtonPressed += (_) =>
{
SendMessage(new StoreRequestUpdateInterfaceMessage());
};
_menu.SearchTextUpdated += (_, search) =>
{
_search = search.Trim().ToLowerInvariant();

View File

@@ -15,6 +15,7 @@
Margin="0,0,4,0"
MinSize="48 48"
Stretch="KeepAspectCentered" />
<Control MinWidth="5"/>
<RichTextLabel Name="StoreItemDescription" />
</BoxContainer>
</BoxContainer>

View File

@@ -1,25 +1,91 @@
using Content.Client.GameTicking.Managers;
using Content.Shared.Store;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Graphics;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Store.Ui;
[GenerateTypedNameReferences]
public sealed partial class StoreListingControl : Control
{
public StoreListingControl(string itemName, string itemDescription,
string price, bool canBuy, Texture? texture = null)
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly ClientGameTicker _ticker;
private readonly ListingData _data;
private readonly bool _hasBalance;
private readonly string _price;
public StoreListingControl(ListingData data, string price, bool hasBalance, Texture? texture = null)
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
StoreItemName.Text = itemName;
StoreItemDescription.SetMessage(itemDescription);
_ticker = _entity.System<ClientGameTicker>();
StoreItemBuyButton.Text = price;
StoreItemBuyButton.Disabled = !canBuy;
_data = data;
_hasBalance = hasBalance;
_price = price;
StoreItemName.Text = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(_data, _prototype);
StoreItemDescription.SetMessage(ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(_data, _prototype));
UpdateBuyButtonText();
StoreItemBuyButton.Disabled = !CanBuy();
StoreItemTexture.Texture = texture;
}
private bool CanBuy()
{
if (!_hasBalance)
return false;
var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan);
if (_data.RestockTime > stationTime)
return false;
return true;
}
private void UpdateBuyButtonText()
{
var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan);
if (_data.RestockTime > stationTime)
{
var timeLeftToBuy = stationTime - _data.RestockTime;
StoreItemBuyButton.Text = timeLeftToBuy.Duration().ToString(@"mm\:ss");
}
else
{
StoreItemBuyButton.Text = _price;
}
}
private void UpdateName()
{
var name = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(_data, _prototype);
var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan);
if (_data.RestockTime > stationTime)
{
name += Loc.GetString("store-ui-button-out-of-stock");
}
StoreItemName.Text = name;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdateBuyButtonText();
UpdateName();
StoreItemBuyButton.Disabled = !CanBuy();
}
}

View File

@@ -12,11 +12,6 @@
HorizontalAlignment="Left"
Access="Public"
HorizontalExpand="True" />
<Button
Name="RefreshButton"
MinWidth="64"
HorizontalAlignment="Right"
Text="Refresh" />
<Button
Name="WithdrawButton"
MinWidth="64"

View File

@@ -1,6 +1,5 @@
using System.Linq;
using Content.Client.Actions;
using Content.Client.GameTicking.Managers;
using Content.Client.Message;
using Content.Shared.FixedPoint;
using Content.Shared.Store;
@@ -11,7 +10,6 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Store.Ui;
@@ -20,9 +18,6 @@ public sealed partial class StoreMenu : DefaultWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
private readonly ClientGameTicker _gameTicker;
private StoreWithdrawWindow? _withdrawWindow;
@@ -30,21 +25,19 @@ public sealed partial class StoreMenu : DefaultWindow
public event Action<BaseButton.ButtonEventArgs, ListingData>? OnListingButtonPressed;
public event Action<BaseButton.ButtonEventArgs, string>? OnCategoryButtonPressed;
public event Action<BaseButton.ButtonEventArgs, string, int>? OnWithdrawAttempt;
public event Action<BaseButton.ButtonEventArgs>? OnRefreshButtonPressed;
public event Action<BaseButton.ButtonEventArgs>? OnRefundAttempt;
public Dictionary<string, FixedPoint2> Balance = new();
public Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> Balance = new();
public string CurrentCategory = string.Empty;
private List<ListingData> _cachedListings = new();
public StoreMenu(string name)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_gameTicker = _entitySystem.GetEntitySystem<ClientGameTicker>();
WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
RefreshButton.OnButtonDown += OnRefreshButtonDown;
RefundButton.OnButtonDown += OnRefundButtonDown;
SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text);
@@ -52,12 +45,12 @@ public sealed partial class StoreMenu : DefaultWindow
Window.Title = name;
}
public void UpdateBalance(Dictionary<string, FixedPoint2> balance)
public void UpdateBalance(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> balance)
{
Balance = balance;
var currency = balance.ToDictionary(type =>
(type.Key, type.Value), type => _prototypeManager.Index<CurrencyPrototype>(type.Key));
(type.Key, type.Value), type => _prototypeManager.Index(type.Key));
var balanceStr = string.Empty;
foreach (var ((_, amount), proto) in currency)
@@ -80,7 +73,13 @@ public sealed partial class StoreMenu : DefaultWindow
public void UpdateListing(List<ListingData> listings)
{
var sorted = listings.OrderBy(l => l.Priority).ThenBy(l => l.Cost.Values.Sum());
_cachedListings = listings;
UpdateListing();
}
public void UpdateListing()
{
var sorted = _cachedListings.OrderBy(l => l.Priority).ThenBy(l => l.Cost.Values.Sum());
// should probably chunk these out instead. to-do if this clogs the internet tubes.
// maybe read clients prototypes instead?
@@ -96,12 +95,6 @@ public sealed partial class StoreMenu : DefaultWindow
TraitorFooter.Visible = visible;
}
private void OnRefreshButtonDown(BaseButton.ButtonEventArgs args)
{
OnRefreshButtonPressed?.Invoke(args);
}
private void OnWithdrawButtonDown(BaseButton.ButtonEventArgs args)
{
// check if window is already open
@@ -129,10 +122,8 @@ public sealed partial class StoreMenu : DefaultWindow
if (!listing.Categories.Contains(CurrentCategory))
return;
var listingName = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager);
var listingDesc = ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(listing, _prototypeManager);
var listingPrice = listing.Cost;
var canBuy = CanBuyListing(Balance, listingPrice);
var hasBalance = HasListingPrice(Balance, listingPrice);
var spriteSys = _entityManager.EntitySysManager.GetEntitySystem<SpriteSystem>();
@@ -154,39 +145,15 @@ public sealed partial class StoreMenu : DefaultWindow
texture = spriteSys.Frame0(action.Icon);
}
}
var listingInStock = ListingInStock(listing);
if (listingInStock != GetListingPriceString(listing))
{
listingName += " (Out of stock)";
canBuy = false;
}
var newListing = new StoreListingControl(listingName, listingDesc, listingInStock, canBuy, texture);
var newListing = new StoreListingControl(listing, GetListingPriceString(listing), hasBalance, texture);
newListing.StoreItemBuyButton.OnButtonDown += args
=> OnListingButtonPressed?.Invoke(args, listing);
StoreListingsContainer.AddChild(newListing);
}
/// <summary>
/// Return time until available or the cost.
/// </summary>
/// <param name="listing"></param>
/// <returns></returns>
public string ListingInStock(ListingData listing)
{
var stationTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan);
TimeSpan restockTimeSpan = TimeSpan.FromMinutes(listing.RestockTime);
if (restockTimeSpan > stationTime)
{
var timeLeftToBuy = stationTime - restockTimeSpan;
return timeLeftToBuy.Duration().ToString(@"mm\:ss");
}
return GetListingPriceString(listing);
}
public bool CanBuyListing(Dictionary<string, FixedPoint2> currency, Dictionary<string, FixedPoint2> price)
public bool HasListingPrice(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> currency, Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> price)
{
foreach (var type in price)
{
@@ -208,7 +175,7 @@ public sealed partial class StoreMenu : DefaultWindow
{
foreach (var (type, amount) in listing.Cost)
{
var currency = _prototypeManager.Index<CurrencyPrototype>(type);
var currency = _prototypeManager.Index(type);
text += Loc.GetString("store-ui-price-display", ("amount", amount),
("currency", Loc.GetString(currency.DisplayName, ("amount", amount))));
}
@@ -229,7 +196,7 @@ public sealed partial class StoreMenu : DefaultWindow
{
foreach (var cat in listing.Categories)
{
var proto = _prototypeManager.Index<StoreCategoryPrototype>(cat);
var proto = _prototypeManager.Index(cat);
if (!allCategories.Contains(proto))
allCategories.Add(proto);
}
@@ -248,12 +215,17 @@ public sealed partial class StoreMenu : DefaultWindow
if (allCategories.Count < 1)
return;
var group = new ButtonGroup();
foreach (var proto in allCategories)
{
var catButton = new StoreCategoryButton
{
Text = Loc.GetString(proto.Name),
Id = proto.ID
Id = proto.ID,
Pressed = proto.ID == CurrentCategory,
Group = group,
ToggleMode = true,
StyleClasses = { "OpenBoth" }
};
catButton.OnPressed += args => OnCategoryButtonPressed?.Invoke(args, catButton.Id);
@@ -269,7 +241,7 @@ public sealed partial class StoreMenu : DefaultWindow
public void UpdateRefund(bool allowRefund)
{
RefundButton.Disabled = !allowRefund;
RefundButton.Visible = allowRefund;
}
private sealed class StoreCategoryButton : Button

View File

@@ -28,12 +28,12 @@ public sealed partial class StoreWithdrawWindow : DefaultWindow
IoCManager.InjectDependencies(this);
}
public void CreateCurrencyButtons(Dictionary<string, FixedPoint2> balance)
public void CreateCurrencyButtons(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> balance)
{
_validCurrencies.Clear();
foreach (var currency in balance)
{
if (!_prototypeManager.TryIndex<CurrencyPrototype>(currency.Key, out var proto))
if (!_prototypeManager.TryIndex(currency.Key, out var proto))
continue;
_validCurrencies.Add(currency.Value, proto);

View File

@@ -93,12 +93,12 @@ namespace Content.Client.Stylesheets
public static readonly Color DangerousRedFore = Color.FromHex("#BB3232");
public static readonly Color DisabledFore = Color.FromHex("#5A5A5A");
public static readonly Color ButtonColorDefault = Color.FromHex("#464966");
public static readonly Color ButtonColorDefault = Color.FromHex("#664e46");
public static readonly Color ButtonColorDefaultRed = Color.FromHex("#D43B3B");
public static readonly Color ButtonColorHovered = Color.FromHex("#575b7f");
public static readonly Color ButtonColorHovered = Color.FromHex("#7f6357");
public static readonly Color ButtonColorHoveredRed = Color.FromHex("#DF6B6B");
public static readonly Color ButtonColorPressed = Color.FromHex("#3e6c45");
public static readonly Color ButtonColorDisabled = Color.FromHex("#30313c");
public static readonly Color ButtonColorPressed = Color.FromHex("#6c4a3e");
public static readonly Color ButtonColorDisabled = Color.FromHex("#3c3330");
public static readonly Color ButtonColorCautionDefault = Color.FromHex("#ab3232");
public static readonly Color ButtonColorCautionHovered = Color.FromHex("#cf2f2f");

View File

@@ -19,5 +19,6 @@ public enum WeaponArcAnimation : byte
Thrust,
Slash,
//CrystallPunk Melee upgrade
CPSlashLight
CPSlash,
CPThrust
}

View File

@@ -14,8 +14,6 @@ public sealed partial class MeleeWeaponSystem
private const string SlashAnimationKey = "melee-slash";
private const string ThrustAnimationKey = "melee-thrust";
private const string CPSlashLightAnimationKey = "cp-melee-slash-light"; //CrystallPunk Melee upgrade
/// <summary>
/// Does all of the melee effects for a player that are predicted, i.e. character lunge and weapon animation.
/// </summary>
@@ -45,7 +43,6 @@ public sealed partial class MeleeWeaponSystem
}
var length = 1f; //CrystallPunk Melee upgrade
var scale = 1f; //CrystallPunk Melee upgrade
var offset = -1f; //CrystallPunk Melee upgrade
var spriteRotation = Angle.Zero;
@@ -62,12 +59,10 @@ public sealed partial class MeleeWeaponSystem
angle *= -1;
length = meleeWeaponComponent.CPAnimationLength; //CrystallPunk Melee upgrade
scale = meleeWeaponComponent.CPAnimationScale; //CrystallPunk Melee upgrade
offset = meleeWeaponComponent.CPAnimationOffset; //CrystallPunk Melee upgrade
}
sprite.NoRotation = true;
sprite.Rotation = localPos.ToWorldAngle();
sprite.Scale = new Vector2(scale); //CrystallPunk Melee upgrade
var distance = Math.Clamp(localPos.Length() / 2f, 0.2f, 1f);
var xform = _xformQuery.GetComponent(animationUid);
@@ -96,12 +91,17 @@ public sealed partial class MeleeWeaponSystem
_animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey);
break;
//CrystallPunk MeleeUpgrade
case WeaponArcAnimation.CPSlashLight:
_animation.Play(animationUid, CPGetSlashLightAnimation(sprite, angle, spriteRotation, length, offset), CPSlashLightAnimationKey);
case WeaponArcAnimation.CPSlash:
_animation.Play(animationUid, CPGetSlashAnimation(sprite, angle, spriteRotation, length, offset), SlashAnimationKey);
TransformSystem.SetParent(animationUid, xform, user, userXform);
if (arcComponent.Fadeout)
_animation.Play(animationUid, GetFadeAnimation(sprite, length * 0.5f, length + 0.15f), FadeAnimationKey);
break;
case WeaponArcAnimation.CPThrust:
_animation.Play(animationUid, CPGetThrustAnimation(sprite, -offset, spriteRotation, length), ThrustAnimationKey);
TransformSystem.SetParent(animationUid, xform, user, userXform);
if (arcComponent.Fadeout)
_animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey);
break;
//CrystallPunk MeleeUpgrade end
}
@@ -219,8 +219,8 @@ public sealed partial class MeleeWeaponSystem
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0f), //CrystallPunk MeleeUpgrade
new AnimationTrackProperty.KeyFrame(direction.Normalized() * 0.15f, length/2), //CrystallPunk MeleeUpgrade
new AnimationTrackProperty.KeyFrame(Vector2.Zero, length)
new AnimationTrackProperty.KeyFrame(direction.Normalized() * 0.15f, length*0.4f), //CrystallPunk MeleeUpgrade
new AnimationTrackProperty.KeyFrame(Vector2.Zero, length*0.8f) //CrystallPunk MeleeUpgrade
}
}
}
@@ -228,7 +228,7 @@ public sealed partial class MeleeWeaponSystem
}
//CrystallPunk MeleeUpgrade start
private Animation CPGetSlashLightAnimation(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 endRotation = sprite.Rotation - (arc * 0.5f);
@@ -270,6 +270,34 @@ public sealed partial class MeleeWeaponSystem
}
};
}
private Animation CPGetThrustAnimation(SpriteComponent sprite, float distance, Angle spriteRotation, float length)
{
var startOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance / 5f));
var endOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance));
sprite.Rotation += spriteRotation;
return new Animation()
{
Length = TimeSpan.FromSeconds(length),
AnimationTracks =
{
new AnimationTrackComponentProperty()
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Offset),
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startOffset, endOffset, 0f), length * 0f),
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startOffset, endOffset, 1f), length * 0.5f),
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startOffset, endOffset, 0.9f), length * 0.8f),
}
},
}
};
}
//CrystallPunk MeleeUpgrade end
}

View File

@@ -56,7 +56,7 @@ public sealed class CraftingTests : InteractionTest
// Player's hands should be full of the remaining rods, except those dropped during the failed crafting attempt.
// Spear and left over stacks should be on the floor.
await AssertEntityLookup((Rod, 2), (Cable, 8), (ShardGlass, 2), (Spear, 1));
await AssertEntityLookup((Rod, 2), (Cable, 7), (ShardGlass, 2), (Spear, 1));
}
// The following is wrapped in an if DEBUG. This is because of cursed state handling bugs. Tests don't (de)serialize
@@ -100,7 +100,7 @@ public sealed class CraftingTests : InteractionTest
Assert.That(sys.IsEntityInContainer(rods), Is.False);
Assert.That(sys.IsEntityInContainer(wires), Is.False);
Assert.That(rodStack, Has.Count.EqualTo(8));
Assert.That(wireStack, Has.Count.EqualTo(8));
Assert.That(wireStack, Has.Count.EqualTo(7));
await FindEntity(Spear, shouldSucceed: false);
});

View File

@@ -19,7 +19,7 @@ public sealed partial class MindTests
await using var pair = await PoolManager.GetServerClient(settings);
// Client is connected with a valid entity & mind
Assert.That(pair.Client.EntMan.EntityExists(pair.Client.Player?.ControlledEntity));
Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity));
Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind));
// Delete **everything**
@@ -28,6 +28,12 @@ public sealed partial class MindTests
await pair.RunTicksSync(5);
Assert.That(pair.Server.EntMan.EntityCount, Is.EqualTo(0));
foreach (var ent in pair.Client.EntMan.GetEntities())
{
Console.WriteLine(pair.Client.EntMan.ToPrettyString(ent));
}
Assert.That(pair.Client.EntMan.EntityCount, Is.EqualTo(0));
// Create a new map.
@@ -36,7 +42,7 @@ public sealed partial class MindTests
await pair.RunTicksSync(5);
// Client is not attached to anything
Assert.That(pair.Client.Player?.ControlledEntity, Is.Null);
Assert.That(pair.Client.AttachedEntity, Is.Null);
Assert.That(pair.PlayerData?.Mind, Is.Null);
// Attempt to ghost
@@ -45,9 +51,9 @@ public sealed partial class MindTests
await pair.RunTicksSync(10);
// Client should be attached to a ghost placed on the new map.
Assert.That(pair.Client.EntMan.EntityExists(pair.Client.Player?.ControlledEntity));
Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity));
Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind));
var xform = pair.Client.Transform(pair.Client.Player!.ControlledEntity!.Value);
var xform = pair.Client.Transform(pair.Client.AttachedEntity!.Value);
Assert.That(xform.MapID, Is.EqualTo(new MapId(mapId)));
await pair.CleanReturnAsync();

View File

@@ -0,0 +1,44 @@
using Content.Server.Station.Systems;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles.Jobs;
using Robust.Shared.GameObjects;
namespace Content.IntegrationTests.Tests.Preferences;
[TestFixture]
[Ignore("HumanoidAppearance crashes upon loading default profiles.")]
public sealed class LoadoutTests
{
/// <summary>
/// Checks that an empty loadout still spawns with default gear and not naked.
/// </summary>
[Test]
public async Task TestEmptyLoadout()
{
var pair = await PoolManager.GetServerClient(new PoolSettings()
{
Dirty = true,
});
var server = pair.Server;
var entManager = server.ResolveDependency<IEntityManager>();
// Check that an empty role loadout spawns gear
var stationSystem = entManager.System<StationSpawningSystem>();
var testMap = await pair.CreateTestMap();
// That's right I can't even spawn a dummy profile without station spawning / humanoidappearance code crashing.
var profile = new HumanoidCharacterProfile();
profile.SetLoadout(new RoleLoadout("TestRoleLoadout"));
stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent()
{
// Sue me, there's so much involved in setting up jobs
Prototype = "CargoTechnician"
}, profile, station: null);
await pair.CleanReturnAsync();
}
}

View File

@@ -4,6 +4,8 @@ using Content.Server.Database;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Robust.Shared.Configuration;
@@ -53,8 +55,6 @@ namespace Content.IntegrationTests.Tests.Preferences
Color.Beige,
new ()
),
ClothingPreference.Jumpskirt,
BackpackPreference.Backpack,
SpawnPriorityPreference.None,
new Dictionary<string, JobPriority>
{
@@ -62,7 +62,8 @@ namespace Content.IntegrationTests.Tests.Preferences
},
PreferenceUnavailableMode.StayInLobby,
new List<string> (),
new List<string>()
new List<string>(),
new Dictionary<string, RoleLoadout>()
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class ClothingRemoval : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "backpack",
table: "profile");
migrationBuilder.DropColumn(
name: "clothing",
table: "profile");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "backpack",
table: "profile",
type: "text",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "clothing",
table: "profile",
type: "text",
nullable: false,
defaultValue: "");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,103 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class Loadouts : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "profile_role_loadout",
columns: table => new
{
profile_role_loadout_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
profile_id = table.Column<int>(type: "integer", nullable: false),
role_name = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_profile_role_loadout", x => x.profile_role_loadout_id);
table.ForeignKey(
name: "FK_profile_role_loadout_profile_profile_id",
column: x => x.profile_id,
principalTable: "profile",
principalColumn: "profile_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "profile_loadout_group",
columns: table => new
{
profile_loadout_group_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
profile_role_loadout_id = table.Column<int>(type: "integer", nullable: false),
group_name = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_profile_loadout_group", x => x.profile_loadout_group_id);
table.ForeignKey(
name: "FK_profile_loadout_group_profile_role_loadout_profile_role_loa~",
column: x => x.profile_role_loadout_id,
principalTable: "profile_role_loadout",
principalColumn: "profile_role_loadout_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "profile_loadout",
columns: table => new
{
profile_loadout_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
profile_loadout_group_id = table.Column<int>(type: "integer", nullable: false),
loadout_name = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_profile_loadout", x => x.profile_loadout_id);
table.ForeignKey(
name: "FK_profile_loadout_profile_loadout_group_profile_loadout_group~",
column: x => x.profile_loadout_group_id,
principalTable: "profile_loadout_group",
principalColumn: "profile_loadout_group_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_profile_loadout_profile_loadout_group_id",
table: "profile_loadout",
column: "profile_loadout_group_id");
migrationBuilder.CreateIndex(
name: "IX_profile_loadout_group_profile_role_loadout_id",
table: "profile_loadout_group",
column: "profile_role_loadout_id");
migrationBuilder.CreateIndex(
name: "IX_profile_role_loadout_profile_id",
table: "profile_role_loadout",
column: "profile_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "profile_loadout");
migrationBuilder.DropTable(
name: "profile_loadout_group");
migrationBuilder.DropTable(
name: "profile_role_loadout");
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class FixRoundStartDateNullability : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "start_date",
table: "round",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldDefaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.Sql("UPDATE round SET start_date = NULL WHERE start_date = '-Infinity';");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "start_date",
table: "round",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
}
}
}

View File

@@ -735,21 +735,11 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("integer")
.HasColumnName("age");
b.Property<string>("Backpack")
.IsRequired()
.HasColumnType("text")
.HasColumnName("backpack");
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("char_name");
b.Property<string>("Clothing")
.IsRequired()
.HasColumnType("text")
.HasColumnName("clothing");
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("text")
@@ -832,6 +822,84 @@ namespace Content.Server.Database.Migrations.Postgres
b.ToTable("profile", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("profile_loadout_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("LoadoutName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("loadout_name");
b.Property<int>("ProfileLoadoutGroupId")
.HasColumnType("integer")
.HasColumnName("profile_loadout_group_id");
b.HasKey("Id")
.HasName("PK_profile_loadout");
b.HasIndex("ProfileLoadoutGroupId");
b.ToTable("profile_loadout", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("profile_loadout_group_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("GroupName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("group_name");
b.Property<int>("ProfileRoleLoadoutId")
.HasColumnType("integer")
.HasColumnName("profile_role_loadout_id");
b.HasKey("Id")
.HasName("PK_profile_loadout_group");
b.HasIndex("ProfileRoleLoadoutId");
b.ToTable("profile_loadout_group", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("profile_role_loadout_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("ProfileId")
.HasColumnType("integer")
.HasColumnName("profile_id");
b.Property<string>("RoleName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("role_name");
b.HasKey("Id")
.HasName("PK_profile_role_loadout");
b.HasIndex("ProfileId");
b.ToTable("profile_role_loadout", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.Property<int>("Id")
@@ -845,10 +913,8 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("integer")
.HasColumnName("server_id");
b.Property<DateTime>("StartDate")
.ValueGeneratedOnAdd()
b.Property<DateTime?>("StartDate")
.HasColumnType("timestamp with time zone")
.HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
.HasColumnName("start_date");
b.HasKey("Id")
@@ -1521,6 +1587,42 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Preference");
});
modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
{
b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
.WithMany("Loadouts")
.HasForeignKey("ProfileLoadoutGroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~");
b.Navigation("ProfileLoadoutGroup");
});
modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
{
b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
.WithMany("Groups")
.HasForeignKey("ProfileRoleLoadoutId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~");
b.Navigation("ProfileRoleLoadout");
});
modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
.WithMany("Loadouts")
.HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_profile_role_loadout_profile_profile_id");
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.HasOne("Content.Server.Database.Server", "Server")
@@ -1733,9 +1835,21 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Jobs");
b.Navigation("Loadouts");
b.Navigation("Traits");
});
modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
{
b.Navigation("Loadouts");
});
modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
{
b.Navigation("Groups");
});
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.Navigation("AdminLogs");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class ClothingRemoval : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "backpack",
table: "profile");
migrationBuilder.DropColumn(
name: "clothing",
table: "profile");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "backpack",
table: "profile",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "clothing",
table: "profile",
type: "TEXT",
nullable: false,
defaultValue: "");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class Loadouts : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "profile_role_loadout",
columns: table => new
{
profile_role_loadout_id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
profile_id = table.Column<int>(type: "INTEGER", nullable: false),
role_name = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_profile_role_loadout", x => x.profile_role_loadout_id);
table.ForeignKey(
name: "FK_profile_role_loadout_profile_profile_id",
column: x => x.profile_id,
principalTable: "profile",
principalColumn: "profile_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "profile_loadout_group",
columns: table => new
{
profile_loadout_group_id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
profile_role_loadout_id = table.Column<int>(type: "INTEGER", nullable: false),
group_name = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_profile_loadout_group", x => x.profile_loadout_group_id);
table.ForeignKey(
name: "FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id",
column: x => x.profile_role_loadout_id,
principalTable: "profile_role_loadout",
principalColumn: "profile_role_loadout_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "profile_loadout",
columns: table => new
{
profile_loadout_id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
profile_loadout_group_id = table.Column<int>(type: "INTEGER", nullable: false),
loadout_name = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_profile_loadout", x => x.profile_loadout_id);
table.ForeignKey(
name: "FK_profile_loadout_profile_loadout_group_profile_loadout_group_id",
column: x => x.profile_loadout_group_id,
principalTable: "profile_loadout_group",
principalColumn: "profile_loadout_group_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_profile_loadout_profile_loadout_group_id",
table: "profile_loadout",
column: "profile_loadout_group_id");
migrationBuilder.CreateIndex(
name: "IX_profile_loadout_group_profile_role_loadout_id",
table: "profile_loadout_group",
column: "profile_role_loadout_id");
migrationBuilder.CreateIndex(
name: "IX_profile_role_loadout_profile_id",
table: "profile_role_loadout",
column: "profile_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "profile_loadout");
migrationBuilder.DropTable(
name: "profile_loadout_group");
migrationBuilder.DropTable(
name: "profile_role_loadout");
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class FixRoundStartDateNullability : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "start_date",
table: "round",
type: "TEXT",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "TEXT",
oldDefaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "start_date",
table: "round",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
oldClrType: typeof(DateTime),
oldType: "TEXT",
oldNullable: true);
}
}
}

View File

@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class FixRoundStartDateNullability2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// This needs to be its own separate migration,
// because EF Core re-arranges the order of the commands if it's a single migration...
// (only relevant for SQLite since it needs cursed shit to do ALTER COLUMN)
migrationBuilder.Sql("UPDATE round SET start_date = NULL WHERE start_date = '0001-01-01 00:00:00';");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@@ -688,21 +688,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("INTEGER")
.HasColumnName("age");
b.Property<string>("Backpack")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("backpack");
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("char_name");
b.Property<string>("Clothing")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("clothing");
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("TEXT")
@@ -785,6 +775,78 @@ namespace Content.Server.Database.Migrations.Sqlite
b.ToTable("profile", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("profile_loadout_id");
b.Property<string>("LoadoutName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("loadout_name");
b.Property<int>("ProfileLoadoutGroupId")
.HasColumnType("INTEGER")
.HasColumnName("profile_loadout_group_id");
b.HasKey("Id")
.HasName("PK_profile_loadout");
b.HasIndex("ProfileLoadoutGroupId");
b.ToTable("profile_loadout", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("profile_loadout_group_id");
b.Property<string>("GroupName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("group_name");
b.Property<int>("ProfileRoleLoadoutId")
.HasColumnType("INTEGER")
.HasColumnName("profile_role_loadout_id");
b.HasKey("Id")
.HasName("PK_profile_loadout_group");
b.HasIndex("ProfileRoleLoadoutId");
b.ToTable("profile_loadout_group", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("profile_role_loadout_id");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER")
.HasColumnName("profile_id");
b.Property<string>("RoleName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("role_name");
b.HasKey("Id")
.HasName("PK_profile_role_loadout");
b.HasIndex("ProfileId");
b.ToTable("profile_role_loadout", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.Property<int>("Id")
@@ -796,10 +858,8 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("INTEGER")
.HasColumnName("server_id");
b.Property<DateTime>("StartDate")
.ValueGeneratedOnAdd()
b.Property<DateTime?>("StartDate")
.HasColumnType("TEXT")
.HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
.HasColumnName("start_date");
b.HasKey("Id")
@@ -1452,6 +1512,42 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Preference");
});
modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
{
b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
.WithMany("Loadouts")
.HasForeignKey("ProfileLoadoutGroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id");
b.Navigation("ProfileLoadoutGroup");
});
modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
{
b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
.WithMany("Groups")
.HasForeignKey("ProfileRoleLoadoutId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id");
b.Navigation("ProfileRoleLoadout");
});
modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
.WithMany("Loadouts")
.HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_profile_role_loadout_profile_profile_id");
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.HasOne("Content.Server.Database.Server", "Server")
@@ -1664,9 +1760,21 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Jobs");
b.Navigation("Loadouts");
b.Navigation("Traits");
});
modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
{
b.Navigation("Loadouts");
});
modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
{
b.Navigation("Groups");
});
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.Navigation("AdminLogs");

View File

@@ -56,8 +56,26 @@ namespace Content.Server.Database
.IsUnique();
modelBuilder.Entity<Trait>()
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
.IsUnique();
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
.IsUnique();
modelBuilder.Entity<ProfileRoleLoadout>()
.HasOne(e => e.Profile)
.WithMany(e => e.Loadouts)
.HasForeignKey(e => e.ProfileId)
.IsRequired();
modelBuilder.Entity<ProfileLoadoutGroup>()
.HasOne(e => e.ProfileRoleLoadout)
.WithMany(e => e.Groups)
.HasForeignKey(e => e.ProfileRoleLoadoutId)
.IsRequired();
modelBuilder.Entity<ProfileLoadout>()
.HasOne(e => e.ProfileLoadoutGroup)
.WithMany(e => e.Loadouts)
.HasForeignKey(e => e.ProfileLoadoutGroupId)
.IsRequired();
modelBuilder.Entity<Job>()
.HasIndex(j => j.ProfileId);
@@ -118,10 +136,6 @@ namespace Content.Server.Database
modelBuilder.Entity<Round>()
.HasIndex(round => round.StartDate);
modelBuilder.Entity<Round>()
.Property(round => round.StartDate)
.HasDefaultValue(default(DateTime));
modelBuilder.Entity<AdminLogPlayer>()
.HasKey(logPlayer => new {logPlayer.RoundId, logPlayer.LogId, logPlayer.PlayerUserId});
@@ -341,13 +355,13 @@ namespace Content.Server.Database
public string FacialHairColor { get; set; } = null!;
public string EyeColor { get; set; } = null!;
public string SkinColor { get; set; } = null!;
public string Clothing { get; set; } = null!;
public string Backpack { get; set; } = null!;
public int SpawnPriority { get; set; } = 0;
public List<Job> Jobs { get; } = new();
public List<Antag> Antags { get; } = new();
public List<Trait> Traits { get; } = new();
public List<ProfileRoleLoadout> Loadouts { get; } = new();
[Column("pref_unavailable")] public DbPreferenceUnavailableMode PreferenceUnavailable { get; set; }
public int PreferenceId { get; set; }
@@ -391,6 +405,79 @@ namespace Content.Server.Database
public string TraitName { get; set; } = null!;
}
#region Loadouts
/// <summary>
/// Corresponds to a single role's loadout inside the DB.
/// </summary>
public class ProfileRoleLoadout
{
public int Id { get; set; }
public int ProfileId { get; set; }
public Profile Profile { get; set; } = null!;
/// <summary>
/// The corresponding role prototype on the profile.
/// </summary>
public string RoleName { get; set; } = string.Empty;
/// <summary>
/// Store the saved loadout groups. These may get validated and removed when loaded at runtime.
/// </summary>
public List<ProfileLoadoutGroup> Groups { get; set; } = new();
}
/// <summary>
/// Corresponds to a loadout group prototype with the specified loadouts attached.
/// </summary>
public class ProfileLoadoutGroup
{
public int Id { get; set; }
public int ProfileRoleLoadoutId { get; set; }
/// <summary>
/// The corresponding RoleLoadout that owns this.
/// </summary>
public ProfileRoleLoadout ProfileRoleLoadout { get; set; } = null!;
/// <summary>
/// The corresponding group prototype.
/// </summary>
public string GroupName { get; set; } = string.Empty;
/// <summary>
/// Selected loadout prototype. Null if none is set.
/// May get validated at runtime and updated to to the default.
/// </summary>
public List<ProfileLoadout> Loadouts { get; set; } = new();
}
/// <summary>
/// Corresponds to a selected loadout.
/// </summary>
public class ProfileLoadout
{
public int Id { get; set; }
public int ProfileLoadoutGroupId { get; set; }
public ProfileLoadoutGroup ProfileLoadoutGroup { get; set; } = null!;
/// <summary>
/// Corresponding loadout prototype.
/// </summary>
public string LoadoutName { get; set; } = string.Empty;
/*
* Insert extra data here like custom descriptions or colors or whatever.
*/
}
#endregion
public enum DbPreferenceUnavailableMode
{
// These enum values HAVE to match the ones in PreferenceUnavailableMode in Shared.
@@ -494,7 +581,7 @@ namespace Content.Server.Database
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime? StartDate { get; set; }
public List<Player> Players { get; set; } = default!;
@@ -879,8 +966,35 @@ namespace Content.Server.Database
public byte[] Data { get; set; } = default!;
}
// Note: this interface isn't used by the game, but it *is* used by SS14.Admin.
// Don't remove! Or face the consequences!
public interface IAdminRemarksCommon
{
public int Id { get; }
public int? RoundId { get; }
public Round? Round { get; }
public Guid? PlayerUserId { get; }
public Player? Player { get; }
public TimeSpan PlaytimeAtNote { get; }
public string Message { get; }
public Player? CreatedBy { get; }
public DateTime CreatedAt { get; }
public Player? LastEditedBy { get; }
public DateTime? LastEditedAt { get; }
public DateTime? ExpirationTime { get; }
public bool Deleted { get; }
}
[Index(nameof(PlayerUserId))]
public class AdminNote
public class AdminNote : IAdminRemarksCommon
{
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
@@ -914,7 +1028,7 @@ namespace Content.Server.Database
}
[Index(nameof(PlayerUserId))]
public class AdminWatchlist
public class AdminWatchlist : IAdminRemarksCommon
{
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
@@ -945,7 +1059,7 @@ namespace Content.Server.Database
}
[Index(nameof(PlayerUserId))]
public class AdminMessage
public class AdminMessage : IAdminRemarksCommon
{
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }

View File

@@ -97,7 +97,7 @@ namespace Content.Server.Administration.Commands
foreach (var slot in slots)
{
invSystem.TryUnequip(target, slot.Name, true, true, false, inventoryComponent);
var gearStr = startingGear.GetGear(slot.Name, profile);
var gearStr = startingGear.GetGear(slot.Name);
if (gearStr == string.Empty)
{
continue;

View File

@@ -0,0 +1,147 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Robust.Server.ServerStatus;
namespace Content.Server.Administration;
public sealed partial class ServerApi
{
private void RegisterHandler(HttpMethod method, string exactPath, Func<IStatusHandlerContext, Task> handler)
{
_statusHost.AddHandler(async context =>
{
if (context.RequestMethod != method || context.Url.AbsolutePath != exactPath)
return false;
if (!await CheckAccess(context))
return true;
await handler(context);
return true;
});
}
private void RegisterActorHandler(HttpMethod method, string exactPath, Func<IStatusHandlerContext, Actor, Task> handler)
{
RegisterHandler(method, exactPath, async context =>
{
if (await CheckActor(context) is not { } actor)
return;
await handler(context, actor);
});
}
/// <summary>
/// Async helper function which runs a task on the main thread and returns the result.
/// </summary>
private async Task<T> RunOnMainThread<T>(Func<T> func)
{
var taskCompletionSource = new TaskCompletionSource<T>();
_taskManager.RunOnMainThread(() =>
{
try
{
taskCompletionSource.TrySetResult(func());
}
catch (Exception e)
{
taskCompletionSource.TrySetException(e);
}
});
var result = await taskCompletionSource.Task;
return result;
}
/// <summary>
/// Runs an action on the main thread. This does not return any value and is meant to be used for void functions. Use <see cref="RunOnMainThread{T}"/> for functions that return a value.
/// </summary>
private async Task RunOnMainThread(Action action)
{
var taskCompletionSource = new TaskCompletionSource();
_taskManager.RunOnMainThread(() =>
{
try
{
action();
taskCompletionSource.TrySetResult();
}
catch (Exception e)
{
taskCompletionSource.TrySetException(e);
}
});
await taskCompletionSource.Task;
}
private async Task RunOnMainThread(Func<Task> action)
{
var taskCompletionSource = new TaskCompletionSource();
// ReSharper disable once AsyncVoidLambda
_taskManager.RunOnMainThread(async () =>
{
try
{
await action();
taskCompletionSource.TrySetResult();
}
catch (Exception e)
{
taskCompletionSource.TrySetException(e);
}
});
await taskCompletionSource.Task;
}
/// <summary>
/// Helper function to read JSON encoded data from the request body.
/// </summary>
private static async Task<T?> ReadJson<T>(IStatusHandlerContext context) where T : notnull
{
try
{
var json = await context.RequestBodyJsonAsync<T>();
if (json == null)
await RespondBadRequest(context, "Request body is null");
return json;
}
catch (Exception e)
{
await RespondBadRequest(context, "Unable to parse request body", ExceptionData.FromException(e));
return default;
}
}
private static async Task RespondError(
IStatusHandlerContext context,
ErrorCode errorCode,
HttpStatusCode statusCode,
string message,
ExceptionData? exception = null)
{
await context.RespondJsonAsync(new BaseResponse(message, errorCode, exception), statusCode)
.ConfigureAwait(false);
}
private static async Task RespondBadRequest(
IStatusHandlerContext context,
string message,
ExceptionData? exception = null)
{
await RespondError(context, ErrorCode.BadRequest, HttpStatusCode.BadRequest, message, exception)
.ConfigureAwait(false);
}
private static async Task RespondOk(IStatusHandlerContext context)
{
await context.RespondJsonAsync(new BaseResponse("OK"))
.ConfigureAwait(false);
}
private static string FormatLogActor(Actor actor) => $"{actor.Name} ({actor.Guid})";
}

View File

@@ -0,0 +1,711 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Content.Server.Administration.Systems;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Presets;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Maps;
using Content.Server.RoundEnd;
using Content.Shared.Administration.Managers;
using Content.Shared.CCVar;
using Content.Shared.Prototypes;
using Robust.Server.ServerStatus;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Administration;
/// <summary>
/// Exposes various admin-related APIs via the game server's <see cref="StatusHost"/>.
/// </summary>
public sealed partial class ServerApi : IPostInjectInit
{
private const string SS14TokenScheme = "SS14Token";
private static readonly HashSet<string> PanicBunkerCVars =
[
CCVars.PanicBunkerEnabled.Name,
CCVars.PanicBunkerDisableWithAdmins.Name,
CCVars.PanicBunkerEnableWithoutAdmins.Name,
CCVars.PanicBunkerCountDeadminnedAdmins.Name,
CCVars.PanicBunkerShowReason.Name,
CCVars.PanicBunkerMinAccountAge.Name,
CCVars.PanicBunkerMinOverallHours.Name,
CCVars.PanicBunkerCustomReason.Name,
];
[Dependency] private readonly IStatusHost _statusHost = default!;
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
[Dependency] private readonly ISharedAdminManager _adminManager = default!;
[Dependency] private readonly IGameMapManager _gameMapManager = default!;
[Dependency] private readonly IServerNetManager _netManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
private string _token = string.Empty;
private ISawmill _sawmill = default!;
void IPostInjectInit.PostInject()
{
_sawmill = _logManager.GetSawmill("serverApi");
// Get
RegisterActorHandler(HttpMethod.Get, "/admin/info", InfoHandler);
RegisterHandler(HttpMethod.Get, "/admin/game_rules", GetGameRules);
RegisterHandler(HttpMethod.Get, "/admin/presets", GetPresets);
// Post
RegisterActorHandler(HttpMethod.Post, "/admin/actions/round/start", ActionRoundStart);
RegisterActorHandler(HttpMethod.Post, "/admin/actions/round/end", ActionRoundEnd);
RegisterActorHandler(HttpMethod.Post, "/admin/actions/round/restartnow", ActionRoundRestartNow);
RegisterActorHandler(HttpMethod.Post, "/admin/actions/kick", ActionKick);
RegisterActorHandler(HttpMethod.Post, "/admin/actions/add_game_rule", ActionAddGameRule);
RegisterActorHandler(HttpMethod.Post, "/admin/actions/end_game_rule", ActionEndGameRule);
RegisterActorHandler(HttpMethod.Post, "/admin/actions/force_preset", ActionForcePreset);
RegisterActorHandler(HttpMethod.Post, "/admin/actions/set_motd", ActionForceMotd);
RegisterActorHandler(HttpMethod.Patch, "/admin/actions/panic_bunker", ActionPanicPunker);
}
public void Initialize()
{
_config.OnValueChanged(CCVars.AdminApiToken, UpdateToken, true);
}
public void Shutdown()
{
_config.UnsubValueChanged(CCVars.AdminApiToken, UpdateToken);
}
private void UpdateToken(string token)
{
_token = token;
}
#region Actions
/// <summary>
/// Changes the panic bunker settings.
/// </summary>
private async Task ActionPanicPunker(IStatusHandlerContext context, Actor actor)
{
var request = await ReadJson<JsonObject>(context);
if (request == null)
return;
var toSet = new Dictionary<string, object>();
foreach (var (cVar, value) in request)
{
if (!PanicBunkerCVars.Contains(cVar))
{
await RespondBadRequest(context, $"Invalid panic bunker CVar: '{cVar}'");
return;
}
if (value == null)
{
await RespondBadRequest(context, $"Value is null: '{cVar}'");
return;
}
if (value is not JsonValue jsonValue)
{
await RespondBadRequest(context, $"Value is not valid: '{cVar}'");
return;
}
object castValue;
var cVarType = _config.GetCVarType(cVar);
if (cVarType == typeof(bool))
{
if (!jsonValue.TryGetValue(out bool b))
{
await RespondBadRequest(context, $"CVar '{cVar}' must be of type bool.");
return;
}
castValue = b;
}
else if (cVarType == typeof(int))
{
if (!jsonValue.TryGetValue(out int i))
{
await RespondBadRequest(context, $"CVar '{cVar}' must be of type int.");
return;
}
castValue = i;
}
else if (cVarType == typeof(string))
{
if (!jsonValue.TryGetValue(out string? s))
{
await RespondBadRequest(context, $"CVar '{cVar}' must be of type string.");
return;
}
castValue = s;
}
else
{
throw new NotSupportedException("Unsupported CVar type");
}
toSet[cVar] = castValue;
}
await RunOnMainThread(() =>
{
foreach (var (cVar, value) in toSet)
{
_config.SetCVar(cVar, value);
_sawmill.Info(
$"Panic bunker property '{cVar}' changed to '{value}' by {FormatLogActor(actor)}.");
}
});
await RespondOk(context);
}
/// <summary>
/// Sets the current MOTD.
/// </summary>
private async Task ActionForceMotd(IStatusHandlerContext context, Actor actor)
{
var motd = await ReadJson<MotdActionBody>(context);
if (motd == null)
return;
_sawmill.Info($"MOTD changed to \"{motd.Motd}\" by {FormatLogActor(actor)}.");
await RunOnMainThread(() => _config.SetCVar(CCVars.MOTD, motd.Motd));
// A hook in the MOTD system sends the changes to each client
await RespondOk(context);
}
/// <summary>
/// Forces the next preset-
/// </summary>
private async Task ActionForcePreset(IStatusHandlerContext context, Actor actor)
{
var body = await ReadJson<PresetActionBody>(context);
if (body == null)
return;
await RunOnMainThread(async () =>
{
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
{
await RespondError(
context,
ErrorCode.InvalidRoundState,
HttpStatusCode.Conflict,
"Game must be in pre-round lobby");
return;
}
var preset = ticker.FindGamePreset(body.PresetId);
if (preset == null)
{
await RespondError(
context,
ErrorCode.GameRuleNotFound,
HttpStatusCode.UnprocessableContent,
$"Game rule '{body.PresetId}' doesn't exist");
return;
}
ticker.SetGamePreset(preset);
_sawmill.Info($"Forced the game to start with preset {body.PresetId} by {FormatLogActor(actor)}.");
await RespondOk(context);
});
}
/// <summary>
/// Ends an active game rule.
/// </summary>
private async Task ActionEndGameRule(IStatusHandlerContext context, Actor actor)
{
var body = await ReadJson<GameRuleActionBody>(context);
if (body == null)
return;
await RunOnMainThread(async () =>
{
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
var gameRule = ticker
.GetActiveGameRules()
.FirstOrNull(rule =>
_entityManager.MetaQuery.GetComponent(rule).EntityPrototype?.ID == body.GameRuleId);
if (gameRule == null)
{
await RespondError(context,
ErrorCode.GameRuleNotFound,
HttpStatusCode.UnprocessableContent,
$"Game rule '{body.GameRuleId}' not found or not active");
return;
}
_sawmill.Info($"Ended game rule {body.GameRuleId} by {FormatLogActor(actor)}.");
ticker.EndGameRule(gameRule.Value);
await RespondOk(context);
});
}
/// <summary>
/// Adds a game rule to the current round.
/// </summary>
private async Task ActionAddGameRule(IStatusHandlerContext context, Actor actor)
{
var body = await ReadJson<GameRuleActionBody>(context);
if (body == null)
return;
await RunOnMainThread(async () =>
{
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
if (!_prototypeManager.HasIndex<EntityPrototype>(body.GameRuleId))
{
await RespondError(context,
ErrorCode.GameRuleNotFound,
HttpStatusCode.UnprocessableContent,
$"Game rule '{body.GameRuleId}' not found or not active");
return;
}
var ruleEntity = ticker.AddGameRule(body.GameRuleId);
_sawmill.Info($"Added game rule {body.GameRuleId} by {FormatLogActor(actor)}.");
if (ticker.RunLevel == GameRunLevel.InRound)
{
ticker.StartGameRule(ruleEntity);
_sawmill.Info($"Started game rule {body.GameRuleId} by {FormatLogActor(actor)}.");
}
await RespondOk(context);
});
}
/// <summary>
/// Kicks a player.
/// </summary>
private async Task ActionKick(IStatusHandlerContext context, Actor actor)
{
var body = await ReadJson<KickActionBody>(context);
if (body == null)
return;
await RunOnMainThread(async () =>
{
if (!_playerManager.TryGetSessionById(new NetUserId(body.Guid), out var player))
{
await RespondError(
context,
ErrorCode.PlayerNotFound,
HttpStatusCode.UnprocessableContent,
"Player not found");
return;
}
var reason = body.Reason ?? "No reason supplied";
reason += " (kicked by admin)";
_netManager.DisconnectChannel(player.Channel, reason);
await RespondOk(context);
_sawmill.Info($"Kicked player {player.Name} ({player.UserId}) for {reason} by {FormatLogActor(actor)}");
});
}
private async Task ActionRoundStart(IStatusHandlerContext context, Actor actor)
{
await RunOnMainThread(async () =>
{
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
{
await RespondError(
context,
ErrorCode.InvalidRoundState,
HttpStatusCode.Conflict,
"Round already started");
return;
}
ticker.StartRound();
_sawmill.Info($"Forced round start by {FormatLogActor(actor)}");
await RespondOk(context);
});
}
private async Task ActionRoundEnd(IStatusHandlerContext context, Actor actor)
{
await RunOnMainThread(async () =>
{
var roundEndSystem = _entitySystemManager.GetEntitySystem<RoundEndSystem>();
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
if (ticker.RunLevel != GameRunLevel.InRound)
{
await RespondError(
context,
ErrorCode.InvalidRoundState,
HttpStatusCode.Conflict,
"Round is not active");
return;
}
roundEndSystem.EndRound();
_sawmill.Info($"Forced round end by {FormatLogActor(actor)}");
await RespondOk(context);
});
}
private async Task ActionRoundRestartNow(IStatusHandlerContext context, Actor actor)
{
await RunOnMainThread(async () =>
{
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
ticker.RestartRound();
_sawmill.Info($"Forced instant round restart by {FormatLogActor(actor)}");
await RespondOk(context);
});
}
#endregion
#region Fetching
/// <summary>
/// Returns an array containing all available presets.
/// </summary>
private async Task GetPresets(IStatusHandlerContext context)
{
var presets = await RunOnMainThread(() =>
{
var presets = new List<PresetResponse.Preset>();
foreach (var preset in _prototypeManager.EnumeratePrototypes<GamePresetPrototype>())
{
presets.Add(new PresetResponse.Preset
{
Id = preset.ID,
ModeTitle = _loc.GetString(preset.ModeTitle),
Description = _loc.GetString(preset.Description)
});
}
return presets;
});
await context.RespondJsonAsync(new PresetResponse
{
Presets = presets
});
}
/// <summary>
/// Returns an array containing all game rules.
/// </summary>
private async Task GetGameRules(IStatusHandlerContext context)
{
var gameRules = new List<string>();
foreach (var gameRule in _prototypeManager.EnumeratePrototypes<EntityPrototype>())
{
if (gameRule.Abstract)
continue;
if (gameRule.HasComponent<GameRuleComponent>(_componentFactory))
gameRules.Add(gameRule.ID);
}
await context.RespondJsonAsync(new GameruleResponse
{
GameRules = gameRules
});
}
/// <summary>
/// Handles fetching information.
/// </summary>
private async Task InfoHandler(IStatusHandlerContext context, Actor actor)
{
/*
Information to display
Round number
Connected players
Active admins
Active game rules
Active game preset
Active map
MOTD
Panic bunker status
*/
var info = await RunOnMainThread<InfoResponse>(() =>
{
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
var adminSystem = _entitySystemManager.GetEntitySystem<AdminSystem>();
var players = new List<InfoResponse.Player>();
foreach (var player in _playerManager.Sessions)
{
var adminData = _adminManager.GetAdminData(player, true);
players.Add(new InfoResponse.Player
{
UserId = player.UserId.UserId,
Name = player.Name,
IsAdmin = adminData != null,
IsDeadminned = !adminData?.Active ?? false
});
}
InfoResponse.MapInfo? mapInfo = null;
if (_gameMapManager.GetSelectedMap() is { } mapPrototype)
{
mapInfo = new InfoResponse.MapInfo
{
Id = mapPrototype.ID,
Name = mapPrototype.MapName
};
}
var gameRules = new List<string>();
foreach (var addedGameRule in ticker.GetActiveGameRules())
{
var meta = _entityManager.MetaQuery.GetComponent(addedGameRule);
gameRules.Add(meta.EntityPrototype?.ID ?? meta.EntityPrototype?.Name ?? "Unknown");
}
var panicBunkerCVars = PanicBunkerCVars.ToDictionary(c => c, c => _config.GetCVar(c));
return new InfoResponse
{
Players = players,
RoundId = ticker.RoundId,
Map = mapInfo,
PanicBunker = panicBunkerCVars,
GamePreset = ticker.CurrentPreset?.ID,
GameRules = gameRules,
MOTD = _config.GetCVar(CCVars.MOTD)
};
});
await context.RespondJsonAsync(info);
}
#endregion
private async Task<bool> CheckAccess(IStatusHandlerContext context)
{
var auth = context.RequestHeaders.TryGetValue("Authorization", out var authToken);
if (!auth)
{
await RespondError(
context,
ErrorCode.AuthenticationNeeded,
HttpStatusCode.Unauthorized,
"Authorization is required");
return false;
}
var authHeaderValue = authToken.ToString();
var spaceIndex = authHeaderValue.IndexOf(' ');
if (spaceIndex == -1)
{
await RespondBadRequest(context, "Invalid Authorization header value");
return false;
}
var authScheme = authHeaderValue[..spaceIndex];
var authValue = authHeaderValue[spaceIndex..].Trim();
if (authScheme != SS14TokenScheme)
{
await RespondBadRequest(context, "Invalid Authorization scheme");
return false;
}
if (_token == "")
{
_sawmill.Debug("No authorization token set for admin API");
}
else if (CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(authValue),
Encoding.UTF8.GetBytes(_token)))
{
return true;
}
await RespondError(
context,
ErrorCode.AuthenticationInvalid,
HttpStatusCode.Unauthorized,
"Authorization is invalid");
// Invalid auth header, no access
_sawmill.Info($"Unauthorized access attempt to admin API from {context.RemoteEndPoint}");
return false;
}
private async Task<Actor?> CheckActor(IStatusHandlerContext context)
{
// The actor is JSON encoded in the header
var actor = context.RequestHeaders.TryGetValue("Actor", out var actorHeader) ? actorHeader.ToString() : null;
if (actor == null)
{
await RespondBadRequest(context, "Actor must be supplied");
return null;
}
Actor? actorData;
try
{
actorData = JsonSerializer.Deserialize<Actor>(actor);
if (actorData == null)
{
await RespondBadRequest(context, "Actor is null");
return null;
}
}
catch (JsonException exception)
{
await RespondBadRequest(context, "Actor field JSON is invalid", ExceptionData.FromException(exception));
return null;
}
return actorData;
}
#region From Client
private sealed class Actor
{
public required Guid Guid { get; init; }
public required string Name { get; init; }
}
private sealed class KickActionBody
{
public required Guid Guid { get; init; }
public string? Reason { get; init; }
}
private sealed class GameRuleActionBody
{
public required string GameRuleId { get; init; }
}
private sealed class PresetActionBody
{
public required string PresetId { get; init; }
}
private sealed class MotdActionBody
{
public required string Motd { get; init; }
}
#endregion
#region Responses
private record BaseResponse(
string Message,
ErrorCode ErrorCode = ErrorCode.None,
ExceptionData? Exception = null);
private record ExceptionData(string Message, string? StackTrace = null)
{
public static ExceptionData FromException(Exception e)
{
return new ExceptionData(e.Message, e.StackTrace);
}
}
private enum ErrorCode
{
None = 0,
AuthenticationNeeded = 1,
AuthenticationInvalid = 2,
InvalidRoundState = 3,
PlayerNotFound = 4,
GameRuleNotFound = 5,
BadRequest = 6,
}
#endregion
#region Misc
/// <summary>
/// Record used to send the response for the info endpoint.
/// </summary>
private sealed class InfoResponse
{
public required int RoundId { get; init; }
public required List<Player> Players { get; init; }
public required List<string> GameRules { get; init; }
public required string? GamePreset { get; init; }
public required MapInfo? Map { get; init; }
public required string? MOTD { get; init; }
public required Dictionary<string, object> PanicBunker { get; init; }
public sealed class Player
{
public required Guid UserId { get; init; }
public required string Name { get; init; }
public required bool IsAdmin { get; init; }
public required bool IsDeadminned { get; init; }
}
public sealed class MapInfo
{
public required string Id { get; init; }
public required string Name { get; init; }
}
}
private sealed class PresetResponse
{
public required List<Preset> Presets { get; init; }
public sealed class Preset
{
public required string Id { get; init; }
public required string Description { get; init; }
public required string ModeTitle { get; init; }
}
}
private sealed class GameruleResponse
{
public required List<string> GameRules { get; init; }
}
#endregion
}

View File

@@ -61,7 +61,7 @@ namespace Content.Server.Administration.Systems
public IReadOnlySet<NetUserId> RoundActivePlayers => _roundActivePlayers;
private readonly HashSet<NetUserId> _roundActivePlayers = new();
private readonly PanicBunkerStatus _panicBunker = new();
public readonly PanicBunkerStatus PanicBunker = new();
public override void Initialize()
{
@@ -240,7 +240,7 @@ namespace Content.Server.Administration.Systems
private void OnPanicBunkerChanged(bool enabled)
{
_panicBunker.Enabled = enabled;
PanicBunker.Enabled = enabled;
_chat.SendAdminAlert(Loc.GetString(enabled
? "admin-ui-panic-bunker-enabled-admin-alert"
: "admin-ui-panic-bunker-disabled-admin-alert"
@@ -251,52 +251,52 @@ namespace Content.Server.Administration.Systems
private void OnPanicBunkerDisableWithAdminsChanged(bool enabled)
{
_panicBunker.DisableWithAdmins = enabled;
PanicBunker.DisableWithAdmins = enabled;
UpdatePanicBunker();
}
private void OnPanicBunkerEnableWithoutAdminsChanged(bool enabled)
{
_panicBunker.EnableWithoutAdmins = enabled;
PanicBunker.EnableWithoutAdmins = enabled;
UpdatePanicBunker();
}
private void OnPanicBunkerCountDeadminnedAdminsChanged(bool enabled)
{
_panicBunker.CountDeadminnedAdmins = enabled;
PanicBunker.CountDeadminnedAdmins = enabled;
UpdatePanicBunker();
}
private void OnShowReasonChanged(bool enabled)
{
_panicBunker.ShowReason = enabled;
PanicBunker.ShowReason = enabled;
SendPanicBunkerStatusAll();
}
private void OnPanicBunkerMinAccountAgeChanged(int minutes)
{
_panicBunker.MinAccountAgeHours = minutes / 60;
PanicBunker.MinAccountAgeHours = minutes / 60;
SendPanicBunkerStatusAll();
}
private void OnPanicBunkerMinOverallHoursChanged(int hours)
{
_panicBunker.MinOverallHours = hours;
PanicBunker.MinOverallHours = hours;
SendPanicBunkerStatusAll();
}
private void UpdatePanicBunker()
{
var admins = _panicBunker.CountDeadminnedAdmins
var admins = PanicBunker.CountDeadminnedAdmins
? _adminManager.AllAdmins
: _adminManager.ActiveAdmins;
var hasAdmins = admins.Any();
if (hasAdmins && _panicBunker.DisableWithAdmins)
if (hasAdmins && PanicBunker.DisableWithAdmins)
{
_config.SetCVar(CCVars.PanicBunkerEnabled, false);
}
else if (!hasAdmins && _panicBunker.EnableWithoutAdmins)
else if (!hasAdmins && PanicBunker.EnableWithoutAdmins)
{
_config.SetCVar(CCVars.PanicBunkerEnabled, true);
}
@@ -306,7 +306,7 @@ namespace Content.Server.Administration.Systems
private void SendPanicBunkerStatusAll()
{
var ev = new PanicBunkerChangedEvent(_panicBunker);
var ev = new PanicBunkerChangedEvent(PanicBunker);
foreach (var admin in _adminManager.AllAdmins)
{
RaiseNetworkEvent(ev, admin);

View File

@@ -24,6 +24,14 @@ public sealed partial class AdvertiseComponent : Component
[DataField]
public int MaximumWait { get; private set; } = 10 * 60;
/// <summary>
/// If true, the delay before the first advertisement (at MapInit) will ignore <see cref="MinimumWait"/>
/// and instead be rolled between 0 and <see cref="MaximumWait"/>. This only applies to the initial delay;
/// <see cref="MinimumWait"/> will be respected after that.
/// </summary>
[DataField]
public bool Prewarm = true;
/// <summary>
/// The identifier for the advertisements pack prototype.
/// </summary>

View File

@@ -37,13 +37,14 @@ public sealed class AdvertiseSystem : EntitySystem
private void OnMapInit(EntityUid uid, AdvertiseComponent advert, MapInitEvent args)
{
RandomizeNextAdvertTime(advert);
var prewarm = advert.Prewarm;
RandomizeNextAdvertTime(advert, prewarm);
_nextCheckTime = MathHelper.Min(advert.NextAdvertisementTime, _nextCheckTime);
}
private void RandomizeNextAdvertTime(AdvertiseComponent advert)
private void RandomizeNextAdvertTime(AdvertiseComponent advert, bool prewarm = false)
{
var minDuration = Math.Max(1, advert.MinimumWait);
var minDuration = prewarm ? 0 : Math.Max(1, advert.MinimumWait);
var maxDuration = Math.Max(minDuration, advert.MaximumWait);
var waitDuration = TimeSpan.FromSeconds(_random.Next(minDuration, maxDuration));

View File

@@ -2,6 +2,7 @@ using Content.Server.CrystallPunk.Temperature;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Audio;
using Content.Shared.Mobs;
namespace Content.Server.Audio;

View File

@@ -14,7 +14,7 @@ namespace Content.Server.Audio;
public sealed class ContentAudioSystem : SharedContentAudioSystem
{
[ValidatePrototypeId<SoundCollectionPrototype>]
private const string LobbyMusicCollection = "LobbyMusic";
private const string LobbyMusicCollection = "CPLobbyMusic";
[Dependency] private readonly AudioSystem _serverAudio = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;

View File

@@ -1,11 +1,16 @@
using System.Globalization;
using System.Linq;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
using Content.Server.Hands.Systems;
using Content.Server.Inventory;
using Content.Server.Popups;
using Content.Server.Chat.Systems;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Server.StationRecords;
using Content.Server.StationRecords.Systems;
using Content.Shared.StationRecords;
using Content.Shared.UserInterface;
using Content.Shared.Access.Systems;
using Content.Shared.Bed.Cryostorage;
@@ -32,6 +37,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly ClimbSystem _climb = default!;
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
@@ -40,6 +46,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly StationJobsSystem _stationJobs = default!;
[Dependency] private readonly StationRecordsSystem _stationRecords = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
@@ -163,26 +170,30 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
{
var comp = ent.Comp;
var cryostorageEnt = ent.Comp.Cryostorage;
var station = _station.GetOwningStation(ent);
var name = Name(ent.Owner);
if (!TryComp<CryostorageComponent>(cryostorageEnt, out var cryostorageComponent))
return;
// if we have a session, we use that to add back in all the job slots the player had.
if (userId != null)
{
foreach (var station in _station.GetStationsSet())
foreach (var uniqueStation in _station.GetStationsSet())
{
if (!TryComp<StationJobsComponent>(station, out var stationJobs))
if (!TryComp<StationJobsComponent>(uniqueStation, out var stationJobs))
continue;
if (!_stationJobs.TryGetPlayerJobs(station, userId.Value, out var jobs, stationJobs))
if (!_stationJobs.TryGetPlayerJobs(uniqueStation, userId.Value, out var jobs, stationJobs))
continue;
foreach (var job in jobs)
{
_stationJobs.TryAdjustJobSlot(station, job, 1, clamp: true);
_stationJobs.TryAdjustJobSlot(uniqueStation, job, 1, clamp: true);
}
_stationJobs.TryRemovePlayerJobs(station, userId.Value, stationJobs);
_stationJobs.TryRemovePlayerJobs(uniqueStation, userId.Value, stationJobs);
}
}
@@ -203,12 +214,36 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
_gameTicker.OnGhostAttempt(mind.Value, false);
}
}
comp.AllowReEnteringBody = false;
_transform.SetParent(ent, PausedMap.Value);
cryostorageComponent.StoredPlayers.Add(ent);
Dirty(ent, comp);
UpdateCryostorageUIState((cryostorageEnt.Value, cryostorageComponent));
AdminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(ent):player} was entered into cryostorage inside of {ToPrettyString(cryostorageEnt.Value)}");
if (!TryComp<StationRecordsComponent>(station, out var stationRecords))
return;
var jobName = Loc.GetString("earlyleave-cryo-job-unknown");
var recordId = _stationRecords.GetRecordByName(station.Value, name);
if (recordId != null)
{
var key = new StationRecordKey(recordId.Value, station.Value);
if (_stationRecords.TryGetRecord<GeneralStationRecord>(key, out var entry, stationRecords))
jobName = entry.JobTitle;
_stationRecords.RemoveRecord(key, stationRecords);
}
_chatSystem.DispatchStationAnnouncement(station.Value,
Loc.GetString(
"earlyleave-cryo-announcement",
("character", name),
("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName))
), Loc.GetString("earlyleave-cryo-sender"),
playDefaultSound: false
);
}
private void HandleCryostorageReconnection(Entity<CryostorageContainedComponent> entity)

View File

@@ -159,7 +159,6 @@ public sealed class PlantHolderSystem : EntitySystem
if (!_botany.TryGetSeed(seeds, out var seed))
return;
float? seedHealth = seeds.HealthOverride;
var name = Loc.GetString(seed.Name);
var noun = Loc.GetString(seed.Noun);
_popup.PopupCursor(Loc.GetString("plant-holder-component-plant-success-message",
@@ -169,9 +168,9 @@ public sealed class PlantHolderSystem : EntitySystem
component.Seed = seed;
component.Dead = false;
component.Age = 1;
if (seedHealth is float realSeedHealth)
if (seeds.HealthOverride != null)
{
component.Health = realSeedHealth;
component.Health = seeds.HealthOverride.Value;
}
else
{
@@ -288,8 +287,18 @@ public sealed class PlantHolderSystem : EntitySystem
}
component.Health -= (_random.Next(3, 5) * 10);
float? healthOverride;
if (component.Harvest)
{
healthOverride = null;
}
else
{
healthOverride = component.Health;
}
component.Seed.Unique = false;
var seed = _botany.SpawnSeedPacket(component.Seed, Transform(args.User).Coordinates, args.User, component.Health);
var seed = _botany.SpawnSeedPacket(component.Seed, Transform(args.User).Coordinates, args.User, healthOverride);
_randomHelper.RandomOffset(seed, 0.25f);
var displayName = Loc.GetString(component.Seed.DisplayName);
_popup.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message",

View File

@@ -1,4 +1,5 @@
using Content.Shared.Cargo;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Cargo.Components;
@@ -32,4 +33,16 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
/// </summary>
[DataField]
public HashSet<string> CheckedBounties = new();
/// <summary>
/// The time at which players will be able to skip the next bounty.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextSkipTime = TimeSpan.Zero;
/// <summary>
/// The time between skipping bounties.
/// </summary>
[DataField]
public TimeSpan SkipDelay = TimeSpan.FromMinutes(15);
}

View File

@@ -4,7 +4,7 @@ using Content.Server.Cargo.Components;
using Content.Server.Labels;
using Content.Server.NameIdentifier;
using Content.Server.Paper;
using Content.Server.Station.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Cargo;
using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Prototypes;
@@ -35,6 +35,7 @@ public sealed partial class CargoSystem
{
SubscribeLocalEvent<CargoBountyConsoleComponent, BoundUIOpenedEvent>(OnBountyConsoleOpened);
SubscribeLocalEvent<CargoBountyConsoleComponent, BountyPrintLabelMessage>(OnPrintLabelMessage);
SubscribeLocalEvent<CargoBountyConsoleComponent, BountySkipMessage>(OnSkipBountyMessage);
SubscribeLocalEvent<CargoBountyLabelComponent, PriceCalculationEvent>(OnGetBountyPrice);
SubscribeLocalEvent<EntitySoldEvent>(OnSold);
SubscribeLocalEvent<StationCargoBountyDatabaseComponent, MapInitEvent>(OnMapInit);
@@ -50,7 +51,8 @@ public sealed partial class CargoSystem
!TryComp<StationCargoBountyDatabaseComponent>(station, out var bountyDb))
return;
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties));
var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime;
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip));
}
private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
@@ -70,6 +72,37 @@ public sealed partial class CargoSystem
_audio.PlayPvs(component.PrintSound, uid);
}
private void OnSkipBountyMessage(EntityUid uid, CargoBountyConsoleComponent component, BountySkipMessage args)
{
if (_station.GetOwningStation(uid) is not { } station || !TryComp<StationCargoBountyDatabaseComponent>(station, out var db))
return;
if (_timing.CurTime < db.NextSkipTime)
return;
if (!TryGetBountyFromId(station, args.BountyId, out var bounty))
return;
if (args.Session.AttachedEntity is not { Valid: true } mob)
return;
if (TryComp<AccessReaderComponent>(uid, out var accessReaderComponent) &&
!_accessReaderSystem.IsAllowed(mob, uid, accessReaderComponent))
{
_audio.PlayPvs(component.DenySound, uid);
return;
}
if (!TryRemoveBounty(station, bounty.Value))
return;
FillBountyDatabase(station);
db.NextSkipTime = _timing.CurTime + db.SkipDelay;
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
_audio.PlayPvs(component.SkipSound, uid);
}
public void SetupBountyLabel(EntityUid uid, EntityUid stationId, CargoBountyData bounty, PaperComponent? paper = null, CargoBountyLabelComponent? label = null)
{
if (!Resolve(uid, ref paper, ref label) || !_protoMan.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var prototype))
@@ -431,7 +464,8 @@ public sealed partial class CargoSystem
!TryComp<StationCargoBountyDatabaseComponent>(station, out var db))
continue;
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties), ui: ui);
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip), ui: ui);
}
}

View File

@@ -244,8 +244,7 @@ namespace Content.Server.Chat.Managers
var prefs = _preferencesManager.GetPreferences(player.UserId);
colorOverride = prefs.AdminOOCColor;
}
if (player.Channel.UserData.PatronTier is { } patron &&
PatronOocColors.TryGetValue(patron, out var patronColor))
if ( _netConfigManager.GetClientCVar(player.Channel, CCVars.ShowOocPatronColor) && player.Channel.UserData.PatronTier is { } patron && PatronOocColors.TryGetValue(patron, out var patronColor))
{
wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", patronColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)));
}

View File

@@ -149,12 +149,12 @@ public sealed partial class SolutionContainerSystem : SharedSolutionContainerSys
var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name };
AddComp(uid, relation);
MetaData.SetEntityName(uid, $"solution - {name}");
ContainerSystem.Insert(uid, container, force: true);
return (uid, solution, relation);
}
#region Event Handlers
private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)

View File

@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using System.Runtime.InteropServices;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Content.Server.Database;
@@ -10,6 +11,7 @@ using Content.Shared.Players.PlayTimeTracking;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Timing;
namespace Content.Server.Connection
@@ -17,6 +19,18 @@ namespace Content.Server.Connection
public interface IConnectionManager
{
void Initialize();
/// <summary>
/// Temporarily allow a user to bypass regular connection requirements.
/// </summary>
/// <remarks>
/// The specified user will be allowed to bypass regular player cap,
/// whitelist and panic bunker restrictions for <paramref name="duration"/>.
/// Bans are not bypassed.
/// </remarks>
/// <param name="user">The user to give a temporary bypass.</param>
/// <param name="duration">How long the bypass should last for.</param>
void AddTemporaryConnectBypass(NetUserId user, TimeSpan duration);
}
/// <summary>
@@ -31,15 +45,31 @@ namespace Content.Server.Connection
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
[Dependency] private readonly ServerDbEntryManager _serverDbEntry = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private readonly Dictionary<NetUserId, TimeSpan> _temporaryBypasses = [];
private ISawmill _sawmill = default!;
public void Initialize()
{
_sawmill = _logManager.GetSawmill("connections");
_netMgr.Connecting += NetMgrOnConnecting;
_netMgr.AssignUserIdCallback = AssignUserIdCallback;
// Approval-based IP bans disabled because they don't play well with Happy Eyeballs.
// _netMgr.HandleApprovalCallback = HandleApproval;
}
public void AddTemporaryConnectBypass(NetUserId user, TimeSpan duration)
{
ref var time = ref CollectionsMarshal.GetValueRefOrAddDefault(_temporaryBypasses, user, out _);
var newTime = _gameTiming.RealTime + duration;
// Make sure we only update the time if we wouldn't shrink it.
if (newTime > time)
time = newTime;
}
/*
private async Task<NetApproval> HandleApproval(NetApprovalEventArgs eventArgs)
{
@@ -109,6 +139,20 @@ namespace Content.Server.Connection
hwId = null;
}
var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false);
if (bans.Count > 0)
{
var firstBan = bans[0];
var message = firstBan.FormatBanMessage(_cfg, _loc);
return (ConnectionDenyReason.Ban, message, bans);
}
if (HasTemporaryBypass(userId))
{
_sawmill.Verbose("User {UserId} has temporary bypass, skipping further connection checks", userId);
return null;
}
var adminData = await _dbManager.GetAdminDataForAsync(e.UserId);
if (_cfg.GetCVar(CCVars.PanicBunkerEnabled) && adminData == null)
@@ -167,14 +211,6 @@ namespace Content.Server.Connection
return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
}
var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false);
if (bans.Count > 0)
{
var firstBan = bans[0];
var message = firstBan.FormatBanMessage(_cfg, _loc);
return (ConnectionDenyReason.Ban, message, bans);
}
if (_cfg.GetCVar(CCVars.WhitelistEnabled))
{
var min = _cfg.GetCVar(CCVars.WhitelistMinPlayers);
@@ -195,6 +231,11 @@ namespace Content.Server.Connection
return null;
}
private bool HasTemporaryBypass(NetUserId user)
{
return _temporaryBypasses.TryGetValue(user, out var time) && time > _gameTiming.RealTime;
}
private async Task<NetUserId?> AssignUserIdCallback(string name)
{
if (!_cfg.GetCVar(CCVars.GamePersistGuests))

View File

@@ -0,0 +1,60 @@
using Content.Server.Administration;
using Content.Shared.Administration;
using Robust.Shared.Console;
namespace Content.Server.Connection;
[AdminCommand(AdminFlags.Admin)]
public sealed class GrantConnectBypassCommand : LocalizedCommands
{
private static readonly TimeSpan DefaultDuration = TimeSpan.FromHours(1);
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
[Dependency] private readonly IConnectionManager _connectionManager = default!;
public override string Command => "grant_connect_bypass";
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length is not (1 or 2))
{
shell.WriteError(Loc.GetString("cmd-grant_connect_bypass-invalid-args"));
return;
}
var argPlayer = args[0];
var info = await _playerLocator.LookupIdByNameOrIdAsync(argPlayer);
if (info == null)
{
shell.WriteError(Loc.GetString("cmd-grant_connect_bypass-unknown-user", ("user", argPlayer)));
return;
}
var duration = DefaultDuration;
if (args.Length > 1)
{
var argDuration = args[2];
if (!uint.TryParse(argDuration, out var minutes))
{
shell.WriteLine(Loc.GetString("cmd-grant_connect_bypass-invalid-duration", ("duration", argDuration)));
return;
}
duration = TimeSpan.FromMinutes(minutes);
}
_connectionManager.AddTemporaryConnectBypass(info.UserId, duration);
shell.WriteLine(Loc.GetString("cmd-grant_connect_bypass-success", ("user", argPlayer)));
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
return CompletionResult.FromHint(Loc.GetString("cmd-grant_connect_bypass-arg-user"));
if (args.Length == 2)
return CompletionResult.FromHint(Loc.GetString("cmd-grant_connect_bypass-arg-duration"));
return CompletionResult.Empty;
}
}

View File

@@ -3,7 +3,7 @@ using Content.Server.StationRecords.Systems;
using Content.Shared.CriminalRecords;
using Content.Shared.Security;
using Content.Shared.StationRecords;
using Robust.Shared.Timing;
using Content.Server.GameTicking;
namespace Content.Server.CriminalRecords.Systems;
@@ -17,7 +17,7 @@ namespace Content.Server.CriminalRecords.Systems;
/// </summary>
public sealed class CriminalRecordsSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly StationRecordsSystem _stationRecords = default!;
public override void Initialize()
@@ -71,7 +71,7 @@ public sealed class CriminalRecordsSystem : EntitySystem
/// </summary>
public bool TryAddHistory(StationRecordKey key, string line)
{
var entry = new CrimeHistory(_timing.CurTime, line);
var entry = new CrimeHistory(_ticker.RoundDuration(), line);
return TryAddHistory(key, entry);
}

View File

@@ -5,6 +5,11 @@ namespace Content.Server.Damage.Components;
[RegisterComponent, Access(typeof(DamagePopupSystem))]
public sealed partial class DamagePopupComponent : Component
{
/// <summary>
/// Bool that will be used to determine if the popup type can be changed with a left click.
/// </summary>
[DataField("allowTypeChange")] [ViewVariables(VVAccess.ReadWrite)]
public bool AllowTypeChange = false;
/// <summary>
/// Enum that will be used to determine the type of damage popup displayed.
/// </summary>

View File

@@ -1,3 +1,4 @@
using Content.Server._CP14.MeleeWeapon;
using Content.Server.Administration.Logs;
using Content.Server.Damage.Components;
using Content.Server.Weapons.Ranged.Systems;
@@ -32,7 +33,14 @@ namespace Content.Server.Damage.Systems
private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args)
{
var dmg = _damageable.TryChangeDamage(args.Target, component.Damage, component.IgnoreResistances, origin: args.Component.Thrower);
//CrystallPunk Melee upgrade
var damage = component.Damage;
if (TryComp<CPSharpenedComponent>(uid, out var sharp))
damage *= sharp.Sharpness;
var dmg = _damageable.TryChangeDamage(args.Target, damage, component.IgnoreResistances, origin: args.Component.Thrower);
//CrystallPunk Melee pgrade end
// Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying.
if (dmg != null && HasComp<MobStateComponent>(args.Target))
@@ -59,7 +67,12 @@ namespace Content.Server.Damage.Systems
private void OnDamageExamine(EntityUid uid, DamageOtherOnHitComponent component, ref DamageExamineEvent args)
{
_damageExamine.AddDamageExamine(args.Message, component.Damage, Loc.GetString("damage-throw"));
var damage = component.Damage;
if (TryComp<CPSharpenedComponent>(uid, out var sharp))
damage *= sharp.Sharpness;
_damageExamine.AddDamageExamine(args.Message, damage, Loc.GetString("damage-throw"));
}
}
}

View File

@@ -1,7 +1,8 @@
using System.Linq;
using Content.Server.Damage.Components;
using Content.Server.Popups;
using Content.Shared.Damage;
using Robust.Shared.Player;
using Content.Shared.Interaction;
namespace Content.Server.Damage.Systems;
@@ -13,6 +14,7 @@ public sealed class DamagePopupSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<DamagePopupComponent, DamageChangedEvent>(OnDamageChange);
SubscribeLocalEvent<DamagePopupComponent, InteractHandEvent>(OnInteractHand);
}
private void OnDamageChange(EntityUid uid, DamagePopupComponent component, DamageChangedEvent args)
@@ -33,4 +35,20 @@ public sealed class DamagePopupSystem : EntitySystem
_popupSystem.PopupEntity(msg, uid);
}
}
private void OnInteractHand(EntityUid uid, DamagePopupComponent component, InteractHandEvent args)
{
if (component.AllowTypeChange)
{
if (component.Type == Enum.GetValues(typeof(DamagePopupType)).Cast<DamagePopupType>().Last())
{
component.Type = Enum.GetValues(typeof(DamagePopupType)).Cast<DamagePopupType>().First();
}
else
{
component.Type = (DamagePopupType) (int) component.Type + 1;
}
_popupSystem.PopupEntity("Target set to type: " + component.Type.ToString(), uid);
}
}
}

View File

@@ -123,6 +123,6 @@ public sealed record PlayerRecord(
IPAddress LastSeenAddress,
ImmutableArray<byte>? HWId);
public sealed record RoundRecord(int Id, DateTimeOffset StartDate, ServerRecord Server);
public sealed record RoundRecord(int Id, DateTimeOffset? StartDate, ServerRecord Server);
public sealed record ServerRecord(int Id, string Name);

View File

@@ -13,6 +13,8 @@ using Content.Shared.Database;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Microsoft.EntityFrameworkCore;
using Robust.Shared.Enums;
using Robust.Shared.Network;
@@ -40,6 +42,10 @@ namespace Content.Server.Database
.Include(p => p.Profiles).ThenInclude(h => h.Jobs)
.Include(p => p.Profiles).ThenInclude(h => h.Antags)
.Include(p => p.Profiles).ThenInclude(h => h.Traits)
.Include(p => p.Profiles)
.ThenInclude(h => h.Loadouts)
.ThenInclude(l => l.Groups)
.ThenInclude(group => group.Loadouts)
.AsSingleQuery()
.SingleOrDefaultAsync(p => p.UserId == userId.UserId);
@@ -88,6 +94,9 @@ namespace Content.Server.Database
.Include(p => p.Jobs)
.Include(p => p.Antags)
.Include(p => p.Traits)
.Include(p => p.Loadouts)
.ThenInclude(l => l.Groups)
.ThenInclude(group => group.Loadouts)
.AsSplitQuery()
.SingleOrDefault(h => h.Slot == slot);
@@ -179,14 +188,6 @@ namespace Content.Server.Database
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
sex = sexVal;
var clothing = ClothingPreference.Jumpsuit;
if (Enum.TryParse<ClothingPreference>(profile.Clothing, true, out var clothingVal))
clothing = clothingVal;
var backpack = BackpackPreference.Backpack;
if (Enum.TryParse<BackpackPreference>(profile.Backpack, true, out var backpackVal))
backpack = backpackVal;
var spawnPriority = (SpawnPriorityPreference) profile.SpawnPriority;
var gender = sex == Sex.Male ? Gender.Male : Gender.Female;
@@ -209,6 +210,27 @@ namespace Content.Server.Database
}
}
var loadouts = new Dictionary<string, RoleLoadout>();
foreach (var role in profile.Loadouts)
{
var loadout = new RoleLoadout(role.RoleName);
foreach (var group in role.Groups)
{
var groupLoadouts = loadout.SelectedLoadouts.GetOrNew(group.GroupName);
foreach (var profLoadout in group.Loadouts)
{
groupLoadouts.Add(new Loadout()
{
Prototype = profLoadout.LoadoutName,
});
}
}
loadouts[role.RoleName] = loadout;
}
return new HumanoidCharacterProfile(
profile.CharacterName,
profile.FlavorText,
@@ -226,13 +248,12 @@ namespace Content.Server.Database
Color.FromHex(profile.SkinColor),
markings
),
clothing,
backpack,
spawnPriority,
jobs,
(PreferenceUnavailableMode) profile.PreferenceUnavailable,
antags.ToList(),
traits.ToList()
traits.ToList(),
loadouts
);
}
@@ -259,8 +280,6 @@ namespace Content.Server.Database
profile.FacialHairColor = appearance.FacialHairColor.ToHex();
profile.EyeColor = appearance.EyeColor.ToHex();
profile.SkinColor = appearance.SkinColor.ToHex();
profile.Clothing = humanoid.Clothing.ToString();
profile.Backpack = humanoid.Backpack.ToString();
profile.SpawnPriority = (int) humanoid.SpawnPriority;
profile.Markings = markings;
profile.Slot = slot;
@@ -285,6 +304,36 @@ namespace Content.Server.Database
.Select(t => new Trait {TraitName = t})
);
profile.Loadouts.Clear();
foreach (var (role, loadouts) in humanoid.Loadouts)
{
var dz = new ProfileRoleLoadout()
{
RoleName = role,
};
foreach (var (group, groupLoadouts) in loadouts.SelectedLoadouts)
{
var profileGroup = new ProfileLoadoutGroup()
{
GroupName = group,
};
foreach (var loadout in groupLoadouts)
{
profileGroup.Loadouts.Add(new ProfileLoadout()
{
LoadoutName = loadout.Prototype,
});
}
dz.Groups.Add(profileGroup);
}
profile.Loadouts.Add(dz);
}
return profile;
}
#endregion
@@ -696,7 +745,7 @@ namespace Content.Server.Database
await db.DbContext.SaveChangesAsync(cancel);
}
public virtual async Task<int> AddNewRound(Server server, params Guid[] playerIds)
public async Task<int> AddNewRound(Server server, params Guid[] playerIds)
{
await using var db = await GetDb();

View File

@@ -452,34 +452,6 @@ namespace Content.Server.Database
return (admins.Select(p => (p.a, p.LastSeenUserName)).ToArray(), adminRanks)!;
}
public override async Task<int> AddNewRound(Server server, params Guid[] playerIds)
{
await using var db = await GetDb();
var players = await db.DbContext.Player
.Where(player => playerIds.Contains(player.UserId))
.ToListAsync();
var nextId = 1;
if (await db.DbContext.Round.AnyAsync())
{
nextId = db.DbContext.Round.Max(round => round.Id) + 1;
}
var round = new Round
{
Id = nextId,
Players = players,
ServerId = server.Id
};
db.DbContext.Round.Add(round);
await db.DbContext.SaveChangesAsync();
return round.Id;
}
protected override IQueryable<AdminLog> StartAdminLogsQuery(ServerDbContext db, LogFilter? filter = null)
{
IQueryable<AdminLog> query = db.AdminLog;

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