Upstream sync (#786)
* Box Station - Dechristmassified (#34135)
* dechrismassified
* removed camera from shower
* Marathon Station - Dechristmassified (#34136)
* dechristmassified
* further dechristmassified
* Loop Station Decal and maints additions (#34103)
* many changes
* contentingregrationtests
* serialized invalid removed
* blank
* "Changes and fixes as suggested"
* blank
* blank
* added desk bells
* engi rework rework rework
* added gate to content integration
* tweaks
* aaa
* bbb
* added holopads
* ccc
* Update default.yml
* hotfix
* aaa
* bbb
* many many tweaks and fixes
* aaa
* decals and maints
* aaa
* bbb
* ccc
---------
Co-authored-by: Emisse <99158783+Emisse@users.noreply.github.com>
* Rename cryobed yml file (#34134)
renamed cryopod.yml to cryogenic_sleep_unit.yml
* Cog update (not very merry) (#34144)
removed christmas merry
* bagel update (#34145)
* Add hair pulato (#34117)
* add sprite pulato
* update
* add pulato hair
* add pulato hair
* add pulato hair
* update meta "pulato"
* Automatic changelog update
* Holopad UI tweak for incoming calls (#34137)
* Initial commit
* Update
* Comment correction
* Minor margin increase
* Holopads no longer log broadcasted speech and emotes in the chat (#34114)
Initial commit
* Automatic changelog update
* Fixes borgs not being able to check their laws in crit (#34133)
* fix
* fix2
* Add contraband parent to laser gun safe (#34132)
* Automatic changelog update
* Add Holopad Circuit Board to A/V Communication Technology (#34150)
Added the holopad circuit board to the AV Communication technology and circuit imprinter lathe.
* Automatic changelog update
* Fix disposal signal routers sprites (#34139)
* Fix disposal signal routers sprites
* Remove old shitcode
* Automatic changelog update
* Meta station overhaul (#33506)
* added mail, moved some things around, and fixed a lot of APCs
* fixed my mistakes
* Fixed a few mistakes and AI camera names
* Redid south medbay and more wiring
* Finished sci overhaul, and fixed all issues that I could find.
* rebuilt botany, removed vox box, fixed all known issues.
* Overhauled security
* Minor commit as I prepare to update my copy
* Rebalanced role counts
* Final changes, ready for review!
* Emisse and other people fixed issues with the station
* Finalized changes (for real this time)!
* Standardize shotgun ammo in storagefills (#34156)
shotgun ammo changes
* Automatic changelog update
* meta update (#34158)
* Amber Station Adjustments (#34126)
* Made a couple fixes to various decals, cleaned up some entities, gave the clown their bag and the bartender a handlabeler
* Several changes, more cameras, lighting fixes, adjusted hydro a bit, gave sec a bunch of shutters
* Added new random spawners for science and added them to Amber
* fixed the science spawners and modified amber slightly
* Fixed the random instrument entry
* Fix friendly vent spiders (#34153)
Swapped order of parents for MobGiantSpiderAngry
* Removed UseDelay component from RCD (#34149)
* Automatic changelog update
* Decrease hp for rusted walls (#34043)
* Automatic changelog update
* FIX: Thief beacon doubled steal targets (#33750)
* Automatic changelog update
* remove nukemass song (#34066)
* Automatic changelog update
* Corrected all ghost role names to title case. (#34155)
* Corrected all ghost role names to title case.
* Removes full stop from Hamlet's title.
* Updated ghost role names not in the main ghost roles .ftl
* Two capitals corrections
* Packed Update (Remove Christmas & New Evac) (#34168)
* Packed update (remove christmas, new shuttle)
* Fix invalid
* the voices
* Omega Update (Remove Christmas) (#34174)
omega soap
* Renamed "Irish Car Bomb" drink to "Irish Slammer" (#34107)
* Renamed "Irish Car Bomb" drink to "Irish Slammer", due to concerns over insensitivity.
* Fixing some missed references
* Added prototype id changes to migration.yml. Removed any reference to the troubles (and corrected ale to stout for flavour text).
* Corrected description back to "Irish Cream"
* Removed non-entities from migration.yml
* Automatic changelog update
* Bugfix for the AI player's eye getting stuck when their broadcast is interrupted (#34093)
Initial commit
* Speech is relayed by holopad holograms (#33978)
* Initial commit
* Corrected a field attribute
* Make JPEG a PNG (#34176)
Make 3.png a PNG
* Removed Undesirable Ion Storm Verbs (#34175)
* Remove Undesirable Laws
* empty
* added basic admin logs for PDA notekeeper notes (#34118)
* added basic admin logs for PDA notekeeper notes
* formatting
* added new LogType 'PdaInteract' and changed PDA notekeeper logs to it
---------
Co-authored-by: dylanhunter <dylan2.whittingham@live.uwe.ac.uk>
* Automatic changelog update
* Sprites defined for all non-generic computer boards. Added new syndicate computer board sprite. (#34104)
* Defined sprites for non-generic computer boards. Added new syndicate computer board sprite.
* Added new sprite to meta.json and updated attribution.
* Reformatted module.rsi meta.json to match other meta file styles.
* Syndicate board sprite made less yellow/gold, changed outer chips to black. Using grey/silver for CPU centre, akin to syndie agent PDA theme, and to keep distinctive from security board.
* Corrected indentation spacing for currently edited entities.
* Update Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml
* add pr link to attribution
---------
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com>
* Added pricegun sound (#34119)
added pricegun sound
Co-authored-by: dylanhunter <dylan2.whittingham@live.uwe.ac.uk>
* Automatic changelog update
* Separate Tables n' Counters (#32673)
* Update tables.yml
* Remove Extra base: state_
* Update tables.yml
* Automatic changelog update
* Add Chameleon PDA (#30514)
* V1 commit
* Remove PDA name and unnecessary pda state
* Adds PDA to Chameleon backpack & thief toolbox
* Change to use AppearanceDataInit
* Add basic PDA state to ensure there's always a sprite before AppearanceData can be applied
* Revert PDA name (this will be changed to another way later)
* Update PDA name updating to new system
* Fix yaml, and fix Agent ID chameleon
* Updated based on review
* Automatic changelog update
* Add some ion storm actions to replace removed ones (#34180)
* Add some ion storm actions to replace removed ones
* Remove other country references, replace
* Some more tuning of the storm values, removing real-world countries
* boldy basics
* Automatic changelog update
* Amber Station and Science Spawner Tweaks (#34187)
* Modified science spawners a bit since I realized including maints loot was undesireable
* Linked Medical doors to buttons, redesigned the floor of the dining area a bit, placed more science spawners
* Somehow I overlooked that I was importing the maints loot table instead of the sci loot table
* Gave sci an EOD closet
* named the evac shuttle
* Core update (#34201)
add
* Elkridge Depot (The station formerly known as Cell) (#34085)
* named apcs, doors, air alarms, cameras, fire alarms, substations, SMESs
* updated PostMapInitTest.cs to include Cell
* added psychologist spawn
* fixed scanner console link, fixed disposals conveyors, and more
* added janitor service lights, maints firelocks, and more
* added more fun maint rooms
* improved head offices, kitchen, psych. added maints between science and arrivals
* fixed spawners placed over solid objects
* added unique evac shuttle, the Cilium
* evac shuttle is now orientated correctly
* added unique cargo shuttle
* updated kitchen area
* renamed Cell Station to Elkridge Depot, removed most main hall airlocks for smoother travel
* general last-minute touch-ups around the bridge and sec
* changed station name in PostMapInitTest.cs
* Add Elkridge Depot into Map Rotation (#34206)
* named apcs, doors, air alarms, cameras, fire alarms, substations, SMESs
* updated PostMapInitTest.cs to include Cell
* added psychologist spawn
* fixed scanner console link, fixed disposals conveyors, and more
* added janitor service lights, maints firelocks, and more
* added more fun maint rooms
* improved head offices, kitchen, psych. added maints between science and arrivals
* fixed spawners placed over solid objects
* added unique evac shuttle, the Cilium
* evac shuttle is now orientated correctly
* added unique cargo shuttle
* updated kitchen area
* renamed Cell Station to Elkridge Depot, removed most main hall airlocks for smoother travel
* general last-minute touch-ups around the bridge and sec
* changed station name in PostMapInitTest.cs
* added Elkridge to default map pool
* added myself to map_attribution.yml credits
* Automatic changelog update
* Packed Update (#34208)
Packed Update (decals mostly)
* Apply forensics when loading with an ammo box (#32280)
* Automatic changelog update
* Update Credits (#34220)
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
* Fix rainbow lizard plushie inhands (#34128)
* fix rainbow plushie inhands
* address requested changes
* attribute sprites
* wielding refactor/fixes (#32188)
* refactor wieldable events
* fix inconsitency with wielding and use updated events
* wieldable cosmetic refactoring
* Update Content.Shared/Wieldable/Events.cs
Co-authored-by: Centronias <charlie.t.santos@gmail.com>
* real
Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
---------
Co-authored-by: deltanedas <@deltanedas:kde.org>
Co-authored-by: Centronias <charlie.t.santos@gmail.com>
Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
* Automatic changelog update
* Lobby chat width and custom lobby titles (#33783)
* lobby name cvar
* panel width
* skrek
* server name localization fix
* comment format fix
Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com>
* remove redundant newline
Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com>
* string.empty
Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com>
* use SetWidth
* Update Resources/Locale/en-US/lobby/lobby-gui.ftl
---------
Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Automatic changelog update
* Adds bullet collision to station lights (#34070)
Adds collision with bullets to lights
* Automatic changelog update
* Oasis Update (#34245)
santa is keel.
* Amber Station - Minor Fixes (#34246)
* Moved the stand clear decal in front of the janitor's shutters up two pixels
* added tech maints under most maints doors, fixed power issues in cargo, and fixed a couple minor issues
* Make station anchor hitbox less insufferable (#34217)
* Automatic changelog update
* Remove kessler and zombeteors gamemodes from the secret pool (#34051)
* Remove kessler, zombeteors gameodes
* Probably should keep the protos in case an admin wants to torture players secretly
* address slart review
* Automatic changelog update
* Added distinct ad and bye chatter to Dr. Gibb vending (#34182)
* Added distinct ad and bye chatter to Dr. Gibb vending
* Correcting revert mistake
* Changed ad pack names to better match naming convention
* Implement approved rule changes (#34233)
* Special reagents now appear in the guidebook (#34265)
* Special reagents now appear in the guidebook
* Improved guidebook wording for reagent category
* Automatic changelog update
* Implement approved rule changes (#34233)
* Fix compilation errors in tests from update (#34272)
Required for https://github.com/space-wizards/RobustToolbox/pull/5590 to not cause compile fails, but can be merged on its own
* Fix portable scrubber appearing powered on spawn (#34274)
* [HOTFIX] Fix chameleon PDAs renaming IDs (#34249)
Fix chameleon PDA
* [HOTFIX] Fix Meta station power (#34256)
* hotfix meta power
* fixed AME
* add missing cargo shuttle pilot console to cargo
* Update vessel_warning.ogg (#34263)
* Update vessel_warning.ogg
Remove DC offset and apply short fade out.
* Update attributions.yml
* Update attributions.yml
* Add bleating accent to goats (#34273)
* Automatic changelog update
* Happy New Year (#34288)
happy new year
* Amber Station - Balance Improvements (#34294)
changed the center area in med bay to a garden, weakened meteor shielding in some areas, also general touch ups around the station
* Fixed Loop Station's southern solar array unlinked airlocks (#34296)
Fixed Southern solar external airlock door bolts
* Fix empty lines in adminwho with stealthmins. (#34122)
Don't print newline if admin is hidden.
* Automatic changelog update
* Added missing cameras to Loop Station (#34308)
* Added missing cameras
* Added missing cameras
* Amber Station - Fixes and Warm Lights (#34324)
* Added warm lights, placed them around the map, also fixed an issue with the MV wire in the cafeteria
* Fixed lv wiring in caf, and adjusted a couple things
* Empty commit to force checks to rerun
* Automatic changelog update
* change locking to use ComplexInteraction (#34326)
Co-authored-by: deltanedas <@deltanedas:kde.org>
* Automatic changelog update
* Drink titles and soda vendor consistency (#34178)
* Made capitalisation of proper names consistent.
* Roy Rogers is presumably a proper name.
* Second pass at distinguishing proper names only.
* Two nitpicking/minor changes
* Fixed some overlooked can brand names. Matched case with descriptions.
* Switched generic sodas with brands for SodaInventory
* Removed commonly available branded cans
* Matched case consistency used elsewhere. Minor SPAG corrections.
* Added "nothing" and some missing alcohol bottles to RandomSpawner
* Added distinct ad and bye chatter to Dr. Gibb machines.
* Revert "Added distinct ad and bye chatter to Dr. Gibb machines."
This reverts commit f90b8a470556de05aca81255db8b6b03596ae944.
* Revert "Removed commonly available branded cans"
This reverts commit 43b82168dac1f73b187b7677f34ecdd33b6bb81a.
* Revert "Switched generic sodas with brands for SodaInventory"
This reverts commit f1790f0ce61ef135c79068de6a741e8bb50d85d3.
* Lowercased DrinkGlass suffix. Moved alcoholic drinks from drinks to alcohol.
* Renamed energy drink to Red Bool. Corrected and added some jug descriptions.
* Added reagent names for all bottles except poison-wine
* Revision of title case for cocktails
* SPAG and fixed the only brand reagen with unbranded name.
* Possibly controversial, shortened some bran names to better fit the UI.
* Fixed some inconsistencies in naming
* Matched brand localisation change
* Two name style edits
* Fixed Smite bottle name
* Minor, punctuation
* Blank line to end of file
* Upgraded descriptive names to title case
* Banana Mama
* reverts change, moved to another PR to avoid conflict.
* Removed caffeine reference.
* Minor, corrected some more inconsistencies
* Removed Bottle of Nothing from random spawner.
* Automatic changelog update
* Fix access configurator debug assert (#34330)
* fix
* greytide fix
* fix admin log
* Dirty
* Renamed water melon juice to watermelon juice (#34341)
* Fix battery charging stopping just short of being full (#34028)
* Add copy threshold button to air alarms (#34346)
* Automatic changelog update
* Oasis updoot the dimmining (#34347)
updooty
* Fland Station - Dirt Fix (#34352)
Fland
* Omega Station - Dirt Fix (#34353)
omega
* Marathon Station - Dirt Fix (#34354)
* Marathon
* Rerunning tests
* Cog Station - Dirt Fix (#34355)
Cog
* Box Station - Dirt Fix (#34356)
Box
* Bagel Station - Dirt Fix (#34357)
Bagel
* Packed Station - Dirt Fix (#34351)
* packed
* Rerunning tests
* Replace some sound PlayEntity with PlayPvs (#34317)
* Fixed Forensic Gloves to be Security Contraband (#34193)
* added BaseRestrictedContraband to forensic gloves
* moved from id to parent
* Automatic changelog update
* add large instruments to the cargo request computer (#34240)
* added the church organ to the cargo console (will add more in this PR, assuming i did this right (HOW DO YOU BUY CARGO ORDERS IN DEV ENVIROMENT???? *sobs))
* added other structure instruments to cargo Catalog
* fixed an epic copy/paste fail
* changed prices
* fixed epic copy/paste fail #2
---------
Co-authored-by: TeenSarlacc <baddiepro123@gmail.com>
* Automatic changelog update
* Fix crayon losing durability on stamped paper (#34202)
* Automatic changelog update
* Adds a border to Oppenhopper poster (#34219)
* border
* Update meta.json
* Update Resources/Textures/Structures/Wallmounts/posters.rsi/meta.json
---------
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Trim trailing newlines from examine messages (#33381)
* Trim trailing newlines from examine messages
* TrimTrailingNewlines -> TrimEnd
* Add a popup message when ghost Boo action does nothing (#34369)
* fix ghost_component.ftl locale grammar (#34372)
fix ghost component locale grammar
* Let ghosts sometimes make certain devices say creepy things (#34368)
* Add SpookySpeaker component/system
* Shuffle Boo action targets before trying to activate them
* Add SpookySpeaker to vending machines
* Fix chatcode eating messages starting with "..."
* Add SpookySpeaker to recycler
* Oops
* Decrease speak probability for vending machines
* Add spooky speaker to arcade machines
* Automatic changelog update
* Add directional escape pod sign (#34367)
* Make indestructible tiles not breakable by explosions (#34339)
* No more Ai Spacing
* Move guard into earlier guard statement
* Automatic changelog update
* Arachnid stomach organ yaml fix (#34298)
Arachnid stomach yaml fix
Arachnids had their stomach `updateInterval` set to 1.5, 50% slower than
normal. But this doesn't actually slow down the speed that the stomach
digests things, only the rate at which it updates to check if enough
time has passed. (See 23f0b304f2/Content.Server/Body/Systems/StomachSystem.cs (L57) )
This PR changes arachnid stomachs to have a `digestionDelay` of 30 (20
is default) to achive the desired effect.
Stasis beds are also bugged in a similar manner. They are intended to
slow down the digestion speed, but similarly all they do is change the
update rate. But fixing that requires actual code changes and is out of
scope for this commit.
* Automatic changelog update
* Bended radiator (#34251)
* Automatic changelog update
* Remove Entity<T> data-fields (#34083)
* Update submodule, .NET 9 (#34320)
* Role Types (#33420)
* mindcomponent namespace
* wip MindRole stuff
* admin player tab
* mindroletype comment
* mindRolePrototype redesign
* broken param
* wip RoleType implementation
* basic role type switching for antags
* traitor fix
* fix AdminPanel update
* the renameningTM
* cleanup
* feature uncreeping
* roletypes on mind roles
* update MindComponent.RoleType when MindRoles change
* ghostrole configuration
* ghostrole config improvements
* live update of roleType on the character window
* logging stuff and notes
* remove thing no one asked for
* weh
* Mind Role Entities wip
* headrev count fix
* silicon stuff, cleanup
* exclusive antag config, cleanup
* jobroleadd overwerite
* logging stuff
* MindHasRole cleanup, admin log stuff
* last second cleanup
* ocd
* move roletypeprototype to its own file, minor note stuff
* remove Roletype.Created
* log stuff
* roletype setup for ghostroles and autotraitor reinforcements
* ghostrole type configs
* adjustable admin overlay
* cleanup
* fix this in its own PR
* silicon antagonist
* borg stuff
* mmi roletype handling
* spawnable borg roletype handling
* weh
* ghost role cleanup
* weh
* RoleEvent update
* polish
* log stuff
* admin overlay config
* ghostrolecomponent cleanup
* weh
* admin overlay code cleanup
* minor cleanup
* Obsolete MindRoleAddedEvent
* comment
* minor code cleanup
* MindOnDoGreeting fix
* Role update message
* fix duplicate job greeting for cyborgs
* fix emag job message dupe
* nicer-looking role type update
* crew aligned
* syndicate assault borg role fix
* fix test fail
* fix a merge mistake
* fix LoneOp role type
* Update Content.Client/Administration/AdminNameOverlay.cs
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Update Content.Shared/Roles/SharedRoleSystem.cs
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* comment formatting
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* change logging category
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* fix a space
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* use MindAddRoles
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* get MindComponent from TryGetMind
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* move var declaration outside loop
* remove TryComp
* take RoleEnum behind the barn
* don't use ensurecomp unnecessarily
* cvar comments
* toggleableghostrolecomponent documentation
* skrek
* use EntProtoId
* mindrole config
* merge baserolecomponent into basemindrolecomponent
* ai and borg silicon role tweaks
* formatting
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* I will end you (the color)
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* use LocId type for a locale id
* update RoleEvent documentation
* update RoleEvent documentation
* remove obsolete MindRoleAddedEvent
* refine MindRolesUpdate()
* use dependency
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* inject dependency
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* roleType.Name no longer required
* reformatted draw code logic
* GhostRoleMarkerRoleComponent comment
* minor SharedRoleSystem cleanup
* StartingMindRoleComponent, unhardcode roundstart silicon
* Update Content.Shared/Roles/SharedRoleSystem.cs
* remove a whitespace
---------
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Automatic changelog update
* Update Credits (#34389)
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
* Elkridge Depot Improvements (#34377)
* updates decals
* more decal work, more dinginess in certain areas
* added decals under doors
* Fix force-feeding Loc strings not using target's gender (#34276)
* HOTFIX Tweaked air alarm default settings for nitrogen breathing crew (#34198)
air alarm default settings modified for anaerobic crew
* #33571 Bomb defusal lockers always should have tools (#34394)
* Automatic changelog update
* [HOTFIX] fix holopads with multiple ai cores dying (#34289)
change return to continue
Co-authored-by: deltanedas <@deltanedas:kde.org>
* Reduce Panic Bunker Minimum Playtime to 2 hours (#34401)
* Add IPIntel API support. (#33339)
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
* Automatic changelog update
* Fland Reporters Room (#34408)
changed the command checkpoint to a reporters room.
* Automatic changelog update
* Add a high-capacity water tank to the janitor's closet of Oasis (#34366)
added high capacity water tank
* Darkened Service job interface icons for better contrast (#34270)
* Darkened Service job interface icons for better contrast
* Fixed Botanist job interface icon dark handle hole
* Change to new, darker, service color in all resource yml files
* Revert Map file service color changes
* Use new darker service color on id cards
* Revert Service color change in mapping_actions.yml
* Revert salvage difficulties service color
* Redo service ID and job colors to match advanced palette
* Revert all service color yml file changes
* Switch icons to use existing service pallete colors from advanced pallete
* Update meta.json for darkened service icons
---------
Co-authored-by: Erskin Cherry <frobnic8@gmail.com>
* Amber Station - Moved Vents Around (#34410)
* Moved all vents around, made some small changes
* Finished work
* Removed insuls spawner since they're not merged yet
* Insuls Spawner (#34407)
* Added insuls spawner, time to test
* adjusted whitespace since that was causing issues
* Update Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
---------
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Manual Valves Resprite (#34378)
* resprited manual valves to be colourblind friendly
* Update Resources/Textures/Structures/Piping/Atmospherics/pump.rsi/meta.json
Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com>
---------
Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com>
* Automatic changelog update
* loop station door access fixes and air sink (#34414)
small fixes
* Raise syndicate kobold reinforcement HP crit threshold from 75 to 100 to match monkey. (#34409)
kobold ops have 100 health
* Anomaly dragging exploit fix and QOL changes (#34280)
* Wood wall is now built from barricade congraph and on top of a barricade instead of using rods
* APE dragging exploit fix
* Fixed doors being blocked with mousetraps, and other Collidable items (#34045)
* Changed SharedDoorSystem.GetColliding() to allow non-LowImpassible mask entities to stay in the door while it closes
* Update Content.Shared/Doors/Systems/SharedDoorSystem.cs
Clarifies comment of how the mask is used
Co-authored-by: Centronias <charlie.t.santos@gmail.com>
---------
Co-authored-by: Centronias <charlie.t.santos@gmail.com>
* Fixed Jazz Instrument for Electric Guitars (#33363)
* fixed jazz midi program byte
* swapped around jazz and clean in instrumentList
* Automatic changelog update
* Porting Pride-O-Mat to Upstream (#34412)
* Pride-O-Mat (#1322)
* Added Pride-O-Mat
* Yep
* Updated license to the correct one
* Added more lines, reconfigured settings a bit, also added cloaks to inventory, set coder socks to emag inventory
* Removed bunny ears, fixed typo
* Made requested changes
Webedit lmao
---------
Co-authored-by: Dorragon <101672978+Dorragon@users.noreply.github.com>
* Automatic changelog update
* Oasis Power Rebalance + Misc fixes (#34425)
* balance oasis power as a stopgap
* change waste color to proper waste color
* Fix IPIntel causing frequent errors with the cleanup job. (#34428)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
* craftable pet carrier (#34431)
* craftable pet carrier
* epic integration test fail
* Update Resources/Prototypes/Recipes/Crafting/improvised.yml
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Update Resources/Prototypes/Recipes/Crafting/Graphs/storage/pet_carrier.yml
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Update Resources/Prototypes/Recipes/Crafting/Graphs/storage/pet_carrier.yml
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Update Resources/Prototypes/Entities/Objects/Misc/pet_carrier.yml
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* extra tab begone
* epic linter fail
* how did linter not see this???
---------
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Automatic changelog update
* Adds omnisexual pin (#34439)
* Make important change (#7)
This is to help julian test his bot
* Omnibus
* Remove random test file from testing a gh bot
* Add pin to vendor, spawners and loadout
---------
Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
* Fix bad Rider analysis error in AccessOverriderWindow.xaml.cs (#34213)
* Disable meta-atlas for big rare RSIs (#33643)
* Persist deadmin to database, add admin suspension system (#34048)
* Automatic changelog update
* STAThread client content start (#34212)
* Minor client packaging changes (#33787)
* Fix muzzle accent (#34419)
* Automatic changelog update
* Add Discord webhook on watchlist connection (#33483)
* Automatic changelog update
* Fixed Thief starting gear failing on specific bag inventories. (#34430)
Fixed it yayyy
* Added missing details from worn capes to head of department beadsheets (#34396)
* Added key and missing details from worn cape to HOP bedsheet
* Corrected canvas size for sprite
* Subtle tweak to shading to reduce color blurring at pillow edge
* Matched Hue and tone to cape
* Tweak chekered pattern marks for gold trim
* Removed accidental palette inclusion
* Clearer wording and corrected attribution name.
* Tweaked shading on key image to fit in better with bed aesthetics
* Added CE cape icon to bedsheet
* Added cape image to HOP bedsheet. Made gold trim better match cape visuals
* RD cape icon added. Colour tweaked to better match cape.
* Updates json
* Tweaks to gold trim shading to match bed aesthetics
* Added better shading for HOP sheet side. Halved file size.
* Optimised HOS RD and CE sheet sprites
* Corrected sprite title in attribution
* Replace ERT Medic's Advanced Medkits with 2 Combat Medkits (#34380)
Replaced Adv kits with 2 combat kits
* Fix nonsensical RegEx for name restriction (#34375)
* Fixed nonsense RegEx
"-" character is a range, caused an error.
No need for "," to repeat so much, it's not a separator.
"\\" - just why?
* Further optimized RegEx structure
Added:
"@" delimiter for consistency
"/" to escape "-" for good and to avoid further problems
* Remove the ability to print the station anchor circuit board (#34358)
remove the ability to print the station anchor circuit board
* Automatic changelog update
* Meta hotfix (#34306)
* Fixed major issues with power, cargo shuttle docking, etc.
* remove serialized invalids
* Finished fixing the critical issues, ready for merge?
* Empty commit
* Added new break room to sci (they deserve it)
* Fixed up other minor issues
* if this map isnt PERFECT Emisse has permission to gib me and turn me into a cyborg
* added Roomba's changes
---------
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
* Make Mime PDA interactions silent (#34426)
* make insert and eject datafields in ItemSlotsComponent.cs nullable, make mime PDA silent
* make it so that you can't fit wirecutters into the slots, among other various things
* Automatic changelog update
* Smite vending machine (#34420)
* Added smite machine to YAML
* Added smite ads and inventory
* Added smite vendor sprites
* Changed the description of the machine to not repeat and ad line.
* Added newline to end of inventory .yml
* Corrected erroneous edit.
* Tweaked all sprites
* Added tesla toy to contraband. Reduced number of drinks available
* Reduced soda varieties but increased can numbers.
* Removed tesla toy from contraband inventory
* Removed speech component from vending machines that already inherit it
* Moved Sprite component to top of list
* Added Smite vendors to random spanwers
* Alphabetised spawn prototypes, commented where name is unclear
* Automatic changelog update
* Printable bedsheets (#34034)
* Bedsheets
* that one fixes yellow bedsheet and delete american bedsheet
* Automatic changelog update
* Update RT to v239.0.1 (#34454)
* Remove christmas anomaly spawn (#34053)
Update anomaly.yml
* Automatic changelog update
* Remove baby jail (#34443)
* Remove baby jail
Closes #33893
* Test fail fix.
* Add a CCVar to allow from hiding admins in the reported player count. (#34406)
Good for:
- Keeping admins hidden
- Not confuse players seeing 84/80 players
Nicely pairs up with the ``admin.admins_count_for_max_players`` ccvar
* Automatic changelog update
* Fix Mixed puddles not updating slips when evap (#34303)
* Fix Mixed puddles not updating slips when evap
* Remove Comment that isn't needed
Co-authored-by: Centronias <charlie.t.santos@gmail.com>
* CR - use SolutionContainerSystem.UpdateChemicals
* CR - cleanup unused imports
---------
Co-authored-by: Centronias <charlie.t.santos@gmail.com>
* Automatic changelog update
* WizDen config update for IPIntel (#34457)
* Fix DNA scrambler updating station record (#34091)
* Fix DNA scrambler updating station record
* Update Content.Server/Implants/SubdermalImplantSystem.cs
---------
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Automatic changelog update
* New and Modified Map Spawners (#34424)
* Added spanwers and modified others
* adjusted values to be more in line with what I want
* this comment may have caused that test fail
* oh my god another typo
* Modified door crate to be engineering flavored
* reduced the pride vendor odds
Webedit lmao
* Elkridge Depot Fixes Again (#34461)
fixes evac shuttle, fix north solars, fix vents and scrubbers
* Space Ruins Variant (#34445)
* Space Ruins Variant
* Updated File
* Added Goliaths/Removed some mobs
* Plasma Station (#33991)
* Plasma Station initial commit
* Map fixes 1
Expanded science's SMES array
Added advanced SMES
Redone stamped documents with custom stamps
Expanded atmospherics with more storage tanks
Added status displays
Add missing beacons to solars
Replaced the passive gates in science with valves
Removed protolathe in engineering
Added guitar to CE office
Replaced throngler plushie with weh cloak
Add a lattice tile outside the atmos burn chamber and storange tanks
Added atmos network monitor in bridge
* Add cargo and emergency shuttle
* Updated maps
* Add plasma to map testing list
* Map fixes 2
Reworked pipenets to not go under walls
Redid salvage and disposals
Reworked the bar to include a new bar extension facing the pool
Replaced arrivals cryo with an arcade
Replaced the toilets in the service plaza with cryo
Removed the cryo in dorms
Added more details to hallways
Redid tools room to include a front desk for the janitor closet
Reconnected sci to power roundstart
Removed some unideal spawns
Expanded the TEG airlock to be 2x3 instead of 1x3
Reduced the size of the SMES bank from 10 to 6
Disabled the plasma miners (downstreams or admins can re-enable them)
Replaced illegal maint items
* Fixes a 6 pack destroying the universe
Ok maybe cracking a cold one with the boys wasn't a great idea.
* Map fixes 3
* Quick research assistant fix
* Map fixes 4
* Map fixes 5
* webedit go brrrt
* Map fixes 6
* Map fixes 7
* Map fixes 8
* Fixes non-existent object
It's amazing this game runs at all
* Map fixes 9
* update pools
* Map fixes 10
* forgot to clear my multitool
I love mapping I love mapping I love mapping I love mapping I love mapping
---------
Co-authored-by: Emisse <99158783+Emisse@users.noreply.github.com>
* Automatic changelog update
* Plasma station population tweak (#34462)
* Plasma Station initial commit
* Map fixes 1
Expanded science's SMES array
Added advanced SMES
Redone stamped documents with custom stamps
Expanded atmospherics with more storage tanks
Added status displays
Add missing beacons to solars
Replaced the passive gates in science with valves
Removed protolathe in engineering
Added guitar to CE office
Replaced throngler plushie with weh cloak
Add a lattice tile outside the atmos burn chamber and storange tanks
Added atmos network monitor in bridge
* Add cargo and emergency shuttle
* Updated maps
* Add plasma to map testing list
* Map fixes 2
Reworked pipenets to not go under walls
Redid salvage and disposals
Reworked the bar to include a new bar extension facing the pool
Replaced arrivals cryo with an arcade
Replaced the toilets in the service plaza with cryo
Removed the cryo in dorms
Added more details to hallways
Redid tools room to include a front desk for the janitor closet
Reconnected sci to power roundstart
Removed some unideal spawns
Expanded the TEG airlock to be 2x3 instead of 1x3
Reduced the size of the SMES bank from 10 to 6
Disabled the plasma miners (downstreams or admins can re-enable them)
Replaced illegal maint items
* Fixes a 6 pack destroying the universe
Ok maybe cracking a cold one with the boys wasn't a great idea.
* Map fixes 3
* Quick research assistant fix
* Map fixes 4
* Map fixes 5
* webedit go brrrt
* Map fixes 6
* Map fixes 7
* Map fixes 8
* Fixes non-existent object
It's amazing this game runs at all
* Map fixes 9
* update pools
* Map fixes 10
* forgot to clear my multitool
I love mapping I love mapping I love mapping I love mapping I love mapping
* Tweaked player counts
---------
Co-authored-by: Emisse <99158783+Emisse@users.noreply.github.com>
* Automatic changelog update
* Fix inconsistent borg flashlight state (#33027)
* Fix borg light being stuck on if no cell is inserted
* Fix HandheldLightComponent.Activted becoming out of sync with SharedPointLightComponent.Enabled
* Fix for entities which don't have a handheld light component
* FIX: Uranium, Cak, and BreadDog are not garbage! (#34192)
* FIX: Uranium, Cak, and BreadDog are not garbage!
* Fixed bread typo for spacegarbage change.
* Style: moved ediblebase
* Update Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml
* Update Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml
---------
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Automatic changelog update
* Fix the HoS mantle metashield break (#33831)
Changes 'nukies' to 'syndicate agents' in the HoS mantle's description.
* fix for climbable pianos (#33690)
fix for climable pianos
Co-authored-by: aa5g21 <aa5g21@soton.ac.uk>
* Automatic changelog update
* BorgChassis transfer their mind to a dropped BorgBrain fix (#34464)
Fix
* Staging: Add taped logo back for 10th anniversary (#34486)
* Update engine to v240.0.1 (#34497)
* mind roles
* partial ritual serialization fix
* Update CP14RoundEndSystem.cs
* delete worldEdge system
* Delete StencilOverlay.WorldEdge.cs
* Update CP14MagicEffectComponent.cs
* delete rituals system
* fix demiplane serialization
* mapdamage fix serialization
* common objectives fix
* remove failed personal goals endscreen
* fix special selling, fix serialization
* more fixes
* more fixes x2
* final bruh
* fix
---------
Co-authored-by: Southbridge <7013162+southbridge-fur@users.noreply.github.com>
Co-authored-by: TytosB <54259736+TytosB@users.noreply.github.com>
Co-authored-by: Emisse <99158783+Emisse@users.noreply.github.com>
Co-authored-by: Booblesnoot42 <108703193+Booblesnoot42@users.noreply.github.com>
Co-authored-by: Spessmann <156740760+Spessmann@users.noreply.github.com>
Co-authored-by: ~DreamlyJack~ <148849095+DreamlyJack@users.noreply.github.com>
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com>
Co-authored-by: PopGamer46 <yt1popgamer@gmail.com>
Co-authored-by: crazybrain23 <44417085+crazybrain23@users.noreply.github.com>
Co-authored-by: amatwiedle <amatwiedle@gmail.com>
Co-authored-by: justdie12 <125140938+justdie12@users.noreply.github.com>
Co-authored-by: Nox <nebulousnox38@gmail.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com>
Co-authored-by: ReeZer2 <63300653+ReeZer2@users.noreply.github.com>
Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com>
Co-authored-by: Alpaccalypse <21291379+Alpaccalypse@users.noreply.github.com>
Co-authored-by: Spanky <scott@wearejacob.com>
Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com>
Co-authored-by: Dylan Hunter Whittingham <45404433+DylanWhittingham@users.noreply.github.com>
Co-authored-by: dylanhunter <dylan2.whittingham@live.uwe.ac.uk>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Co-authored-by: Ps3Moira <113228053+ps3moira@users.noreply.github.com>
Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Co-authored-by: Hannah Giovanna Dawson <karakkaraz@gmail.com>
Co-authored-by: Ubaser <134914314+UbaserB@users.noreply.github.com>
Co-authored-by: Deerstop <edainturner@gmail.com>
Co-authored-by: themias <89101928+themias@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: Centronias <charlie.t.santos@gmail.com>
Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Co-authored-by: SpaceRox1244 <138547931+SpaceRox1244@users.noreply.github.com>
Co-authored-by: IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com>
Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
Co-authored-by: Pancake <Pangogie@users.noreply.github.com>
Co-authored-by: Piras314 <p1r4s@proton.me>
Co-authored-by: flymo5678 <86871317+flymo5678@users.noreply.github.com>
Co-authored-by: c4llv07e <igor@c4llv07e.xyz>
Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
Co-authored-by: Coolsurf6 <coolsurf24@yahoo.com.au>
Co-authored-by: TeenSarlacc <46608342+TeenSarlacc@users.noreply.github.com>
Co-authored-by: TeenSarlacc <baddiepro123@gmail.com>
Co-authored-by: SpaceManiac <tad@platymuus.com>
Co-authored-by: War Pigeon <54217755+minus1over12@users.noreply.github.com>
Co-authored-by: Zachary Higgs <compgeek223@gmail.com>
Co-authored-by: 0x6273 <0x40@keemail.me>
Co-authored-by: Floxington <florian.decker@mailbox.org>
Co-authored-by: Myra <vasilis@pikachu.systems>
Co-authored-by: SlimSlam <73899110+Stewie523@users.noreply.github.com>
Co-authored-by: frobnic8 <erskin@eldritch.org>
Co-authored-by: Erskin Cherry <frobnic8@gmail.com>
Co-authored-by: hyperDelegate <zachary1064@gmail.com>
Co-authored-by: JustinWinningham <justinmwinningham@gmail.com>
Co-authored-by: zHonys <69396539+zHonys@users.noreply.github.com>
Co-authored-by: Dorragon <101672978+Dorragon@users.noreply.github.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
Co-authored-by: Killerqu00 <47712032+Killerqu00@users.noreply.github.com>
Co-authored-by: Julian Giebel <juliangiebel@live.de>
Co-authored-by: Palladinium <patrick.chieppe@hotmail.com>
Co-authored-by: Alpha-Two <92269094+Alpha-Two@users.noreply.github.com>
Co-authored-by: Hyper B <137433177+HyperB1@users.noreply.github.com>
Co-authored-by: kosticia <kosticia46@gmail.com>
Co-authored-by: compilatron <40789662+jbox144@users.noreply.github.com>
Co-authored-by: eoineoineoin <github@eoinrul.es>
Co-authored-by: Patrik Caes-Sayrs <heartofgoldfish@gmail.com>
Co-authored-by: ApolloVector <149586366+ApolloVector@users.noreply.github.com>
Co-authored-by: Gansu <68031780+GansuLalan@users.noreply.github.com>
Co-authored-by: aa5g21 <aa5g21@soton.ac.uk>
This commit is contained in:
2
.github/workflows/build-docfx.yml
vendored
2
.github/workflows/build-docfx.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3.2.0
|
uses: actions/setup-dotnet@v3.2.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 8.0.x
|
dotnet-version: 9.0.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
|
|||||||
2
.github/workflows/build-map-renderer.yml
vendored
2
.github/workflows/build-map-renderer.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3.2.0
|
uses: actions/setup-dotnet@v3.2.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 8.0.x
|
dotnet-version: 9.0.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
|
|||||||
2
.github/workflows/build-test-debug.yml
vendored
2
.github/workflows/build-test-debug.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3.2.0
|
uses: actions/setup-dotnet@v3.2.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 8.0.x
|
dotnet-version: 9.0.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
|
|||||||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3.2.0
|
uses: actions/setup-dotnet@v3.2.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 8.0.x
|
dotnet-version: 9.0.x
|
||||||
|
|
||||||
- name: Get Engine Tag
|
- name: Get Engine Tag
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/test-packaging.yml
vendored
2
.github/workflows/test-packaging.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
|||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3.2.0
|
uses: actions/setup-dotnet@v3.2.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 8.0.x
|
dotnet-version: 9.0.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
|
|||||||
2
.github/workflows/yaml-linter.yml
vendored
2
.github/workflows/yaml-linter.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3.2.0
|
uses: actions/setup-dotnet@v3.2.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 8.0.x
|
dotnet-version: 9.0.x
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|||||||
@@ -88,8 +88,9 @@ namespace Content.Client.Access.UI
|
|||||||
button.Disabled = !interfaceEnabled;
|
button.Disabled = !interfaceEnabled;
|
||||||
if (interfaceEnabled)
|
if (interfaceEnabled)
|
||||||
{
|
{
|
||||||
button.Pressed = state.TargetAccessReaderIdAccessList?.Contains(accessName) ?? false;
|
// Explicit cast because Rider gives a false error otherwise.
|
||||||
button.Disabled = (!state.AllowedModifyAccessList?.Contains(accessName)) ?? true;
|
button.Pressed = state.TargetAccessReaderIdAccessList?.Contains((ProtoId<AccessLevelPrototype>) accessName) ?? false;
|
||||||
|
button.Disabled = (!state.AllowedModifyAccessList?.Contains((ProtoId<AccessLevelPrototype>) accessName)) ?? true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Client.Administration.Systems;
|
using Content.Client.Administration.Systems;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Mind;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.ResourceManagement;
|
using Robust.Client.ResourceManagement;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Shared;
|
|
||||||
using Robust.Shared.Enums;
|
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Client.Administration;
|
namespace Content.Client.Administration;
|
||||||
|
|
||||||
internal sealed class AdminNameOverlay : Overlay
|
internal sealed class AdminNameOverlay : Overlay
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||||
|
|
||||||
private readonly AdminSystem _system;
|
private readonly AdminSystem _system;
|
||||||
private readonly IEntityManager _entityManager;
|
private readonly IEntityManager _entityManager;
|
||||||
private readonly IEyeManager _eyeManager;
|
private readonly IEyeManager _eyeManager;
|
||||||
@@ -18,8 +23,16 @@ internal sealed class AdminNameOverlay : Overlay
|
|||||||
private readonly IUserInterfaceManager _userInterfaceManager;
|
private readonly IUserInterfaceManager _userInterfaceManager;
|
||||||
private readonly Font _font;
|
private readonly Font _font;
|
||||||
|
|
||||||
|
//TODO make this adjustable via GUI
|
||||||
|
private readonly ProtoId<RoleTypePrototype>[] _filter =
|
||||||
|
["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"];
|
||||||
|
private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
|
||||||
|
private readonly Color _antagColorClassic = Color.OrangeRed;
|
||||||
|
|
||||||
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager)
|
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager)
|
||||||
{
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
_system = system;
|
_system = system;
|
||||||
_entityManager = entityManager;
|
_entityManager = entityManager;
|
||||||
_eyeManager = eyeManager;
|
_eyeManager = eyeManager;
|
||||||
@@ -35,6 +48,9 @@ internal sealed class AdminNameOverlay : Overlay
|
|||||||
{
|
{
|
||||||
var viewport = args.WorldAABB;
|
var viewport = args.WorldAABB;
|
||||||
|
|
||||||
|
//TODO make this adjustable via GUI
|
||||||
|
var classic = _config.GetCVar(CCVars.AdminOverlayClassic);
|
||||||
|
|
||||||
foreach (var playerInfo in _system.PlayerList)
|
foreach (var playerInfo in _system.PlayerList)
|
||||||
{
|
{
|
||||||
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
|
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
|
||||||
@@ -64,12 +80,20 @@ internal sealed class AdminNameOverlay : Overlay
|
|||||||
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
|
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
|
||||||
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
|
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
|
||||||
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
|
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
|
||||||
if (playerInfo.Antag)
|
|
||||||
|
if (classic && playerInfo.Antag)
|
||||||
{
|
{
|
||||||
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", uiScale, Color.OrangeRed);
|
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), _antagLabelClassic, uiScale, _antagColorClassic);
|
||||||
;
|
|
||||||
}
|
}
|
||||||
args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
|
else if (!classic && _filter.Contains(playerInfo.RoleProto.ID))
|
||||||
|
{
|
||||||
|
var label = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
|
||||||
|
var color = playerInfo.RoleProto.Color;
|
||||||
|
|
||||||
|
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), label, uiScale, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.ScreenHandle.DrawString(_font, screenCoordinates + lineoffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||||
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
xmlns:tabs="clr-namespace:Content.Client.Administration.UI.Tabs"
|
xmlns:tabs="clr-namespace:Content.Client.Administration.UI.Tabs"
|
||||||
xmlns:playerTab="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
|
xmlns:playerTab="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
|
||||||
xmlns:objectsTab="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
|
xmlns:objectsTab="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
|
||||||
xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab"
|
xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab">
|
||||||
xmlns:baby="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab">
|
|
||||||
<TabContainer Name="MasterTabContainer">
|
<TabContainer Name="MasterTabContainer">
|
||||||
<adminTab:AdminTab />
|
<adminTab:AdminTab />
|
||||||
<adminbusTab:AdminbusTab />
|
<adminbusTab:AdminbusTab />
|
||||||
@@ -15,7 +14,6 @@
|
|||||||
<tabs:RoundTab />
|
<tabs:RoundTab />
|
||||||
<tabs:ServerTab />
|
<tabs:ServerTab />
|
||||||
<panic:PanicBunkerTab Name="PanicBunkerControl" Access="Public" />
|
<panic:PanicBunkerTab Name="PanicBunkerControl" Access="Public" />
|
||||||
<baby:BabyJailTab Name="BabyJailControl" Access="Public" />
|
|
||||||
<playerTab:PlayerTab Name="PlayerTabControl" Access="Public" />
|
<playerTab:PlayerTab Name="PlayerTabControl" Access="Public" />
|
||||||
<objectsTab:ObjectsTab Name="ObjectsTabControl" Access="Public" />
|
<objectsTab:ObjectsTab Name="ObjectsTabControl" Access="Public" />
|
||||||
</TabContainer>
|
</TabContainer>
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ public sealed partial class AdminMenuWindow : DefaultWindow
|
|||||||
MasterTabContainer.SetTabTitle((int) TabIndex.Round, Loc.GetString("admin-menu-round-tab"));
|
MasterTabContainer.SetTabTitle((int) TabIndex.Round, Loc.GetString("admin-menu-round-tab"));
|
||||||
MasterTabContainer.SetTabTitle((int) TabIndex.Server, Loc.GetString("admin-menu-server-tab"));
|
MasterTabContainer.SetTabTitle((int) TabIndex.Server, Loc.GetString("admin-menu-server-tab"));
|
||||||
MasterTabContainer.SetTabTitle((int) TabIndex.PanicBunker, Loc.GetString("admin-menu-panic-bunker-tab"));
|
MasterTabContainer.SetTabTitle((int) TabIndex.PanicBunker, Loc.GetString("admin-menu-panic-bunker-tab"));
|
||||||
/*
|
|
||||||
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
|
||||||
*/
|
|
||||||
MasterTabContainer.SetTabTitle((int) TabIndex.BabyJail, Loc.GetString("admin-menu-baby-jail-tab"));
|
|
||||||
MasterTabContainer.SetTabTitle((int) TabIndex.Players, Loc.GetString("admin-menu-players-tab"));
|
MasterTabContainer.SetTabTitle((int) TabIndex.Players, Loc.GetString("admin-menu-players-tab"));
|
||||||
MasterTabContainer.SetTabTitle((int) TabIndex.Objects, Loc.GetString("admin-menu-objects-tab"));
|
MasterTabContainer.SetTabTitle((int) TabIndex.Objects, Loc.GetString("admin-menu-objects-tab"));
|
||||||
MasterTabContainer.OnTabChanged += OnTabChanged;
|
MasterTabContainer.OnTabChanged += OnTabChanged;
|
||||||
@@ -52,7 +48,6 @@ public sealed partial class AdminMenuWindow : DefaultWindow
|
|||||||
Round,
|
Round,
|
||||||
Server,
|
Server,
|
||||||
PanicBunker,
|
PanicBunker,
|
||||||
BabyJail,
|
|
||||||
Players,
|
Players,
|
||||||
Objects,
|
Objects,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ namespace Content.Client.Administration.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
var title = string.IsNullOrWhiteSpace(popup.TitleEdit.Text) ? null : popup.TitleEdit.Text;
|
var title = string.IsNullOrWhiteSpace(popup.TitleEdit.Text) ? null : popup.TitleEdit.Text;
|
||||||
|
var suspended = popup.SuspendedCheckbox.Pressed;
|
||||||
|
|
||||||
if (popup.SourceData is { } src)
|
if (popup.SourceData is { } src)
|
||||||
{
|
{
|
||||||
@@ -139,7 +140,8 @@ namespace Content.Client.Administration.UI
|
|||||||
Title = title,
|
Title = title,
|
||||||
PosFlags = pos,
|
PosFlags = pos,
|
||||||
NegFlags = neg,
|
NegFlags = neg,
|
||||||
RankId = rank
|
RankId = rank,
|
||||||
|
Suspended = suspended,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -152,7 +154,8 @@ namespace Content.Client.Administration.UI
|
|||||||
Title = title,
|
Title = title,
|
||||||
PosFlags = pos,
|
PosFlags = pos,
|
||||||
NegFlags = neg,
|
NegFlags = neg,
|
||||||
RankId = rank
|
RankId = rank,
|
||||||
|
Suspended = suspended,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +174,7 @@ namespace Content.Client.Administration.UI
|
|||||||
{
|
{
|
||||||
Id = src,
|
Id = src,
|
||||||
Flags = flags,
|
Flags = flags,
|
||||||
Name = name
|
Name = name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -351,6 +354,7 @@ namespace Content.Client.Administration.UI
|
|||||||
public readonly OptionButton RankButton;
|
public readonly OptionButton RankButton;
|
||||||
public readonly Button SaveButton;
|
public readonly Button SaveButton;
|
||||||
public readonly Button? RemoveButton;
|
public readonly Button? RemoveButton;
|
||||||
|
public readonly CheckBox SuspendedCheckbox;
|
||||||
|
|
||||||
public readonly Dictionary<AdminFlags, (Button inherit, Button sub, Button plus)> FlagButtons
|
public readonly Dictionary<AdminFlags, (Button inherit, Button sub, Button plus)> FlagButtons
|
||||||
= new();
|
= new();
|
||||||
@@ -381,6 +385,12 @@ namespace Content.Client.Administration.UI
|
|||||||
RankButton = new OptionButton();
|
RankButton = new OptionButton();
|
||||||
SaveButton = new Button { Text = Loc.GetString("permissions-eui-edit-admin-window-save-button"), HorizontalAlignment = HAlignment.Right };
|
SaveButton = new Button { Text = Loc.GetString("permissions-eui-edit-admin-window-save-button"), HorizontalAlignment = HAlignment.Right };
|
||||||
|
|
||||||
|
SuspendedCheckbox = new CheckBox
|
||||||
|
{
|
||||||
|
Text = Loc.GetString("permissions-eui-edit-admin-window-suspended"),
|
||||||
|
Pressed = data?.Suspended ?? false,
|
||||||
|
};
|
||||||
|
|
||||||
RankButton.AddItem(Loc.GetString("permissions-eui-edit-admin-window-no-rank-button"), NoRank);
|
RankButton.AddItem(Loc.GetString("permissions-eui-edit-admin-window-no-rank-button"), NoRank);
|
||||||
foreach (var (rId, rank) in ui._ranks)
|
foreach (var (rId, rank) in ui._ranks)
|
||||||
{
|
{
|
||||||
@@ -488,7 +498,8 @@ namespace Content.Client.Administration.UI
|
|||||||
{
|
{
|
||||||
nameControl,
|
nameControl,
|
||||||
TitleEdit,
|
TitleEdit,
|
||||||
RankButton
|
RankButton,
|
||||||
|
SuspendedCheckbox,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
permGrid
|
permGrid
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<controls:BabyJailStatusWindow
|
|
||||||
xmlns="https://spacestation14.io"
|
|
||||||
xmlns:controls="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab"
|
|
||||||
Title="{Loc admin-ui-baby-jail-window-title}">
|
|
||||||
<RichTextLabel Name="MessageLabel" Access="Public" />
|
|
||||||
</controls:BabyJailStatusWindow>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
using Content.Client.Message;
|
|
||||||
using Content.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.AutoGenerated;
|
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
|
||||||
|
|
||||||
namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
|
||||||
public sealed partial class BabyJailStatusWindow : FancyWindow
|
|
||||||
{
|
|
||||||
public BabyJailStatusWindow()
|
|
||||||
{
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
MessageLabel.SetMarkup(Loc.GetString("admin-ui-baby-jail-is-enabled"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<controls:BabyJailTab
|
|
||||||
xmlns="https://spacestation14.io"
|
|
||||||
xmlns:controls="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab"
|
|
||||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
|
||||||
Margin="4">
|
|
||||||
<BoxContainer Orientation="Vertical">
|
|
||||||
<cc:CommandButton Name="EnabledButton" Command="babyjail" ToggleMode="True"
|
|
||||||
Text="{Loc admin-ui-baby-jail-disabled}"
|
|
||||||
ToolTip="{Loc admin-ui-baby-jail-tooltip}" />
|
|
||||||
<cc:CommandButton Name="ShowReasonButton" Command="babyjail_show_reason"
|
|
||||||
ToggleMode="True" Text="{Loc admin-ui-baby-jail-show-reason}"
|
|
||||||
ToolTip="{Loc admin-ui-baby-jail-show-reason-tooltip}" />
|
|
||||||
<BoxContainer Orientation="Vertical" Margin="0 10 0 0">
|
|
||||||
<BoxContainer Orientation="Horizontal" Margin="2">
|
|
||||||
<Label Text="{Loc admin-ui-baby-jail-max-account-age}" MinWidth="175" />
|
|
||||||
<LineEdit Name="MaxAccountAge" MinWidth="50" Margin="0 0 5 0" />
|
|
||||||
<Label Text="{Loc generic-minutes}" />
|
|
||||||
</BoxContainer>
|
|
||||||
<BoxContainer Orientation="Horizontal" Margin="2">
|
|
||||||
<Label Text="{Loc admin-ui-baby-jail-max-overall-minutes}" MinWidth="175" />
|
|
||||||
<LineEdit Name="MaxOverallMinutes" MinWidth="50" Margin="0 0 5 0" />
|
|
||||||
<Label Text="{Loc generic-minutes}" />
|
|
||||||
</BoxContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
</controls:BabyJailTab>
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
using Content.Shared.Administration.Events;
|
|
||||||
using Robust.Client.AutoGenerated;
|
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
|
||||||
using Robust.Shared.Console;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
|
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
|
||||||
public sealed partial class BabyJailTab : Control
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IConsoleHost _console = default!;
|
|
||||||
|
|
||||||
private string _maxAccountAge;
|
|
||||||
private string _maxOverallMinutes;
|
|
||||||
|
|
||||||
public BabyJailTab()
|
|
||||||
{
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
|
|
||||||
MaxAccountAge.OnTextEntered += args => SendMaxAccountAge(args.Text);
|
|
||||||
MaxAccountAge.OnFocusExit += args => SendMaxAccountAge(args.Text);
|
|
||||||
_maxAccountAge = MaxAccountAge.Text;
|
|
||||||
|
|
||||||
MaxOverallMinutes.OnTextEntered += args => SendMaxOverallMinutes(args.Text);
|
|
||||||
MaxOverallMinutes.OnFocusExit += args => SendMaxOverallMinutes(args.Text);
|
|
||||||
_maxOverallMinutes = MaxOverallMinutes.Text;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SendMaxAccountAge(string text)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(text) ||
|
|
||||||
text == _maxAccountAge ||
|
|
||||||
!int.TryParse(text, out var minutes))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_console.ExecuteCommand($"babyjail_max_account_age {minutes}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SendMaxOverallMinutes(string text)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(text) ||
|
|
||||||
text == _maxOverallMinutes ||
|
|
||||||
!int.TryParse(text, out var minutes))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_console.ExecuteCommand($"babyjail_max_overall_minutes {minutes}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateStatus(BabyJailStatus status)
|
|
||||||
{
|
|
||||||
EnabledButton.Pressed = status.Enabled;
|
|
||||||
EnabledButton.Text = Loc.GetString(status.Enabled
|
|
||||||
? "admin-ui-baby-jail-enabled"
|
|
||||||
: "admin-ui-baby-jail-disabled"
|
|
||||||
);
|
|
||||||
EnabledButton.ModulateSelfOverride = status.Enabled ? Color.Red : null;
|
|
||||||
ShowReasonButton.Pressed = status.ShowReason;
|
|
||||||
|
|
||||||
MaxAccountAge.Text = status.MaxAccountAgeMinutes.ToString();
|
|
||||||
_maxAccountAge = MaxAccountAge.Text;
|
|
||||||
|
|
||||||
MaxOverallMinutes.Text = status.MaxOverallMinutes.ToString();
|
|
||||||
_maxOverallMinutes = MaxOverallMinutes.Text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -197,6 +197,7 @@ public sealed partial class PlayerTab : Control
|
|||||||
Header.Character => Compare(x.CharacterName, y.CharacterName),
|
Header.Character => Compare(x.CharacterName, y.CharacterName),
|
||||||
Header.Job => Compare(x.StartingJob, y.StartingJob),
|
Header.Job => Compare(x.StartingJob, y.StartingJob),
|
||||||
Header.Antagonist => x.Antag.CompareTo(y.Antag),
|
Header.Antagonist => x.Antag.CompareTo(y.Antag),
|
||||||
|
Header.RoleType => Compare(x.RoleProto.Name , y.RoleProto.Name),
|
||||||
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
|
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
|
||||||
_ => 1
|
_ => 1
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,6 +24,11 @@
|
|||||||
HorizontalExpand="True"
|
HorizontalExpand="True"
|
||||||
ClipText="True"/>
|
ClipText="True"/>
|
||||||
<customControls:VSeparator/>
|
<customControls:VSeparator/>
|
||||||
|
<Label Name="RoleTypeLabel"
|
||||||
|
SizeFlagsStretchRatio="2"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
ClipText="True"/>
|
||||||
|
<customControls:VSeparator/>
|
||||||
<Label Name="OverallPlaytimeLabel"
|
<Label Name="OverallPlaytimeLabel"
|
||||||
SizeFlagsStretchRatio="1"
|
SizeFlagsStretchRatio="1"
|
||||||
HorizontalExpand="True"
|
HorizontalExpand="True"
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ public sealed partial class PlayerTabEntry : PanelContainer
|
|||||||
if (player.IdentityName != player.CharacterName)
|
if (player.IdentityName != player.CharacterName)
|
||||||
CharacterLabel.Text += $" [{player.IdentityName}]";
|
CharacterLabel.Text += $" [{player.IdentityName}]";
|
||||||
AntagonistLabel.Text = Loc.GetString(player.Antag ? "player-tab-is-antag-yes" : "player-tab-is-antag-no");
|
AntagonistLabel.Text = Loc.GetString(player.Antag ? "player-tab-is-antag-yes" : "player-tab-is-antag-no");
|
||||||
|
RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
|
||||||
|
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
|
||||||
BackgroundColorPanel.PanelOverride = styleBoxFlat;
|
BackgroundColorPanel.PanelOverride = styleBoxFlat;
|
||||||
OverallPlaytimeLabel.Text = player.PlaytimeString;
|
OverallPlaytimeLabel.Text = player.PlaytimeString;
|
||||||
PlayerEntity = player.NetEntity;
|
PlayerEntity = player.NetEntity;
|
||||||
|
|||||||
@@ -32,6 +32,13 @@
|
|||||||
Text="{Loc player-tab-antagonist}"
|
Text="{Loc player-tab-antagonist}"
|
||||||
MouseFilter="Pass"/>
|
MouseFilter="Pass"/>
|
||||||
<cc:VSeparator/>
|
<cc:VSeparator/>
|
||||||
|
<Label Name="RoleTypeLabel"
|
||||||
|
SizeFlagsStretchRatio="2"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
ClipText="True"
|
||||||
|
Text="{Loc player-tab-roletype}"
|
||||||
|
MouseFilter="Pass"/>
|
||||||
|
<cc:VSeparator/>
|
||||||
<Label Name="PlaytimeLabel"
|
<Label Name="PlaytimeLabel"
|
||||||
SizeFlagsStretchRatio="1"
|
SizeFlagsStretchRatio="1"
|
||||||
HorizontalExpand="True"
|
HorizontalExpand="True"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public sealed partial class PlayerTabHeader : Control
|
|||||||
CharacterLabel.OnKeyBindDown += CharacterClicked;
|
CharacterLabel.OnKeyBindDown += CharacterClicked;
|
||||||
JobLabel.OnKeyBindDown += JobClicked;
|
JobLabel.OnKeyBindDown += JobClicked;
|
||||||
AntagonistLabel.OnKeyBindDown += AntagonistClicked;
|
AntagonistLabel.OnKeyBindDown += AntagonistClicked;
|
||||||
|
RoleTypeLabel.OnKeyBindDown += RoleTypeClicked;
|
||||||
PlaytimeLabel.OnKeyBindDown += PlaytimeClicked;
|
PlaytimeLabel.OnKeyBindDown += PlaytimeClicked;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ public sealed partial class PlayerTabHeader : Control
|
|||||||
Header.Character => CharacterLabel,
|
Header.Character => CharacterLabel,
|
||||||
Header.Job => JobLabel,
|
Header.Job => JobLabel,
|
||||||
Header.Antagonist => AntagonistLabel,
|
Header.Antagonist => AntagonistLabel,
|
||||||
|
Header.RoleType => RoleTypeLabel,
|
||||||
Header.Playtime => PlaytimeLabel,
|
Header.Playtime => PlaytimeLabel,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
|
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
|
||||||
};
|
};
|
||||||
@@ -41,6 +43,7 @@ public sealed partial class PlayerTabHeader : Control
|
|||||||
CharacterLabel.Text = Loc.GetString("player-tab-character");
|
CharacterLabel.Text = Loc.GetString("player-tab-character");
|
||||||
JobLabel.Text = Loc.GetString("player-tab-job");
|
JobLabel.Text = Loc.GetString("player-tab-job");
|
||||||
AntagonistLabel.Text = Loc.GetString("player-tab-antagonist");
|
AntagonistLabel.Text = Loc.GetString("player-tab-antagonist");
|
||||||
|
RoleTypeLabel.Text = Loc.GetString("player-tab-roletype");
|
||||||
PlaytimeLabel.Text = Loc.GetString("player-tab-playtime");
|
PlaytimeLabel.Text = Loc.GetString("player-tab-playtime");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +78,11 @@ public sealed partial class PlayerTabHeader : Control
|
|||||||
HeaderClicked(args, Header.Antagonist);
|
HeaderClicked(args, Header.Antagonist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RoleTypeClicked(GUIBoundKeyEventArgs args)
|
||||||
|
{
|
||||||
|
HeaderClicked(args, Header.RoleType);
|
||||||
|
}
|
||||||
|
|
||||||
private void PlaytimeClicked(GUIBoundKeyEventArgs args)
|
private void PlaytimeClicked(GUIBoundKeyEventArgs args)
|
||||||
{
|
{
|
||||||
HeaderClicked(args, Header.Playtime);
|
HeaderClicked(args, Header.Playtime);
|
||||||
@@ -90,6 +98,7 @@ public sealed partial class PlayerTabHeader : Control
|
|||||||
CharacterLabel.OnKeyBindDown -= CharacterClicked;
|
CharacterLabel.OnKeyBindDown -= CharacterClicked;
|
||||||
JobLabel.OnKeyBindDown -= JobClicked;
|
JobLabel.OnKeyBindDown -= JobClicked;
|
||||||
AntagonistLabel.OnKeyBindDown -= AntagonistClicked;
|
AntagonistLabel.OnKeyBindDown -= AntagonistClicked;
|
||||||
|
RoleTypeLabel.OnKeyBindDown -= RoleTypeClicked;
|
||||||
PlaytimeLabel.OnKeyBindDown -= PlaytimeClicked;
|
PlaytimeLabel.OnKeyBindDown -= PlaytimeClicked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,6 +109,7 @@ public sealed partial class PlayerTabHeader : Control
|
|||||||
Character,
|
Character,
|
||||||
Job,
|
Job,
|
||||||
Antagonist,
|
Antagonist,
|
||||||
|
RoleType,
|
||||||
Playtime
|
Playtime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,8 +130,8 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
|||||||
if (!_pumps.TryGetValue(addr, out var pumpControl))
|
if (!_pumps.TryGetValue(addr, out var pumpControl))
|
||||||
{
|
{
|
||||||
var control= new PumpControl(pump, addr);
|
var control= new PumpControl(pump, addr);
|
||||||
control.PumpDataChanged += AtmosDeviceDataChanged!.Invoke;
|
control.PumpDataChanged += AtmosDeviceDataChanged;
|
||||||
control.PumpDataCopied += AtmosDeviceDataCopied!.Invoke;
|
control.PumpDataCopied += AtmosDeviceDataCopied;
|
||||||
_pumps.Add(addr, control);
|
_pumps.Add(addr, control);
|
||||||
CVentContainer.AddChild(control);
|
CVentContainer.AddChild(control);
|
||||||
}
|
}
|
||||||
@@ -145,8 +145,8 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
|||||||
if (!_scrubbers.TryGetValue(addr, out var scrubberControl))
|
if (!_scrubbers.TryGetValue(addr, out var scrubberControl))
|
||||||
{
|
{
|
||||||
var control = new ScrubberControl(scrubber, addr);
|
var control = new ScrubberControl(scrubber, addr);
|
||||||
control.ScrubberDataChanged += AtmosDeviceDataChanged!.Invoke;
|
control.ScrubberDataChanged += AtmosDeviceDataChanged;
|
||||||
control.ScrubberDataCopied += AtmosDeviceDataCopied!.Invoke;
|
control.ScrubberDataCopied += AtmosDeviceDataCopied;
|
||||||
_scrubbers.Add(addr, control);
|
_scrubbers.Add(addr, control);
|
||||||
CScrubberContainer.AddChild(control);
|
CScrubberContainer.AddChild(control);
|
||||||
}
|
}
|
||||||
@@ -161,6 +161,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
|||||||
{
|
{
|
||||||
var control = new SensorInfo(sensor, addr);
|
var control = new SensorInfo(sensor, addr);
|
||||||
control.OnThresholdUpdate += AtmosAlarmThresholdChanged;
|
control.OnThresholdUpdate += AtmosAlarmThresholdChanged;
|
||||||
|
control.SensorDataCopied += AtmosDeviceDataCopied;
|
||||||
_sensors.Add(addr, control);
|
_sensors.Add(addr, control);
|
||||||
CSensorContainer.AddChild(control);
|
CSensorContainer.AddChild(control);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,10 +83,10 @@ public sealed partial class PumpControl : BoxContainer
|
|||||||
PumpDataChanged?.Invoke(_address, _data);
|
PumpDataChanged?.Invoke(_address, _data);
|
||||||
};
|
};
|
||||||
|
|
||||||
_copySettings.OnPressed += _ =>
|
_copySettings.OnPressed += _ =>
|
||||||
{
|
{
|
||||||
PumpDataCopied?.Invoke(_data);
|
PumpDataCopied?.Invoke(_data);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeData(GasVentPumpData data)
|
public void ChangeData(GasVentPumpData data)
|
||||||
|
|||||||
@@ -72,10 +72,10 @@ public sealed partial class ScrubberControl : BoxContainer
|
|||||||
ScrubberDataChanged?.Invoke(_address, _data);
|
ScrubberDataChanged?.Invoke(_address, _data);
|
||||||
};
|
};
|
||||||
|
|
||||||
_copySettings.OnPressed += _ =>
|
_copySettings.OnPressed += _ =>
|
||||||
{
|
{
|
||||||
ScrubberDataCopied?.Invoke(_data);
|
ScrubberDataCopied?.Invoke(_data);
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var value in Enum.GetValues<Gas>())
|
foreach (var value in Enum.GetValues<Gas>())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
<CollapsibleHeading Name="SensorAddress" />
|
<CollapsibleHeading Name="SensorAddress" />
|
||||||
<CollapsibleBody Margin="20 2 2 2">
|
<CollapsibleBody Margin="20 2 2 2">
|
||||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||||
|
<BoxContainer Orientation="Horizontal" Margin ="0 0 0 2">
|
||||||
|
<Button Name="CCopySettings" Text="{Loc 'air-alarm-ui-thresholds-copy'}" ToolTip="{Loc 'air-alarm-ui-thresholds-copy-tooltip'}" />
|
||||||
|
</BoxContainer>
|
||||||
<BoxContainer Orientation="Vertical" Margin="0 0 2 0" HorizontalExpand="True">
|
<BoxContainer Orientation="Vertical" Margin="0 0 2 0" HorizontalExpand="True">
|
||||||
<RichTextLabel Name="AlarmStateLabel" />
|
<RichTextLabel Name="AlarmStateLabel" />
|
||||||
<RichTextLabel Name="PressureLabel" />
|
<RichTextLabel Name="PressureLabel" />
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets;
|
|||||||
public sealed partial class SensorInfo : BoxContainer
|
public sealed partial class SensorInfo : BoxContainer
|
||||||
{
|
{
|
||||||
public Action<string, AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? OnThresholdUpdate;
|
public Action<string, AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? OnThresholdUpdate;
|
||||||
|
public event Action<AtmosSensorData>? SensorDataCopied;
|
||||||
private string _address;
|
private string _address;
|
||||||
|
|
||||||
private ThresholdControl _pressureThreshold;
|
private ThresholdControl _pressureThreshold;
|
||||||
private ThresholdControl _temperatureThreshold;
|
private ThresholdControl _temperatureThreshold;
|
||||||
private Dictionary<Gas, ThresholdControl> _gasThresholds = new();
|
private Dictionary<Gas, ThresholdControl> _gasThresholds = new();
|
||||||
private Dictionary<Gas, RichTextLabel> _gasLabels = new();
|
private Dictionary<Gas, RichTextLabel> _gasLabels = new();
|
||||||
|
private Button _copySettings => CCopySettings;
|
||||||
|
|
||||||
public SensorInfo(AtmosSensorData data, string address)
|
public SensorInfo(AtmosSensorData data, string address)
|
||||||
{
|
{
|
||||||
@@ -56,7 +58,7 @@ public sealed partial class SensorInfo : BoxContainer
|
|||||||
gasThresholdControl.Margin = new Thickness(20, 2, 2, 2);
|
gasThresholdControl.Margin = new Thickness(20, 2, 2, 2);
|
||||||
gasThresholdControl.ThresholdDataChanged += (type, alarmThreshold, arg3) =>
|
gasThresholdControl.ThresholdDataChanged += (type, alarmThreshold, arg3) =>
|
||||||
{
|
{
|
||||||
OnThresholdUpdate!(_address, type, alarmThreshold, arg3);
|
OnThresholdUpdate?.Invoke(_address, type, alarmThreshold, arg3);
|
||||||
};
|
};
|
||||||
|
|
||||||
_gasThresholds.Add(gas, gasThresholdControl);
|
_gasThresholds.Add(gas, gasThresholdControl);
|
||||||
@@ -72,12 +74,17 @@ public sealed partial class SensorInfo : BoxContainer
|
|||||||
|
|
||||||
_pressureThreshold.ThresholdDataChanged += (type, threshold, arg3) =>
|
_pressureThreshold.ThresholdDataChanged += (type, threshold, arg3) =>
|
||||||
{
|
{
|
||||||
OnThresholdUpdate!(_address, type, threshold, arg3);
|
OnThresholdUpdate?.Invoke(_address, type, threshold, arg3);
|
||||||
};
|
};
|
||||||
|
|
||||||
_temperatureThreshold.ThresholdDataChanged += (type, threshold, arg3) =>
|
_temperatureThreshold.ThresholdDataChanged += (type, threshold, arg3) =>
|
||||||
{
|
{
|
||||||
OnThresholdUpdate!(_address, type, threshold, arg3);
|
OnThresholdUpdate?.Invoke(_address, type, threshold, arg3);
|
||||||
|
};
|
||||||
|
|
||||||
|
_copySettings.OnPressed += _ =>
|
||||||
|
{
|
||||||
|
SensorDataCopied?.Invoke(data);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Content.Shared.Cargo.Components;
|
||||||
using Content.Shared.Timing;
|
using Content.Shared.Timing;
|
||||||
using Content.Shared.Cargo.Systems;
|
using Content.Shared.Cargo.Systems;
|
||||||
|
|
||||||
@@ -10,9 +11,9 @@ public sealed class ClientPriceGunSystem : SharedPriceGunSystem
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||||
|
|
||||||
protected override bool GetPriceOrBounty(EntityUid priceGunUid, EntityUid target, EntityUid user)
|
protected override bool GetPriceOrBounty(Entity<PriceGunComponent> entity, EntityUid target, EntityUid user)
|
||||||
{
|
{
|
||||||
if (!TryComp(priceGunUid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((priceGunUid, useDelay)))
|
if (!TryComp(entity, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((entity, useDelay)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// It feels worse if the cooldown is predicted but the popup isn't! So only do the cooldown reset on the server.
|
// It feels worse if the cooldown is predicted but the popup isn't! So only do the cooldown reset on the server.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Client.PDA;
|
||||||
using Content.Shared.Clothing.Components;
|
using Content.Shared.Clothing.Components;
|
||||||
using Content.Shared.Clothing.EntitySystems;
|
using Content.Shared.Clothing.EntitySystems;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
@@ -51,6 +52,15 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
|||||||
{
|
{
|
||||||
sprite.CopyFrom(otherSprite);
|
sprite.CopyFrom(otherSprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Edgecase for PDAs to include visuals when UI is open
|
||||||
|
if (TryComp(uid, out PdaBorderColorComponent? borderColor)
|
||||||
|
&& proto.TryGetComponent(out PdaBorderColorComponent? otherBorderColor, _factory))
|
||||||
|
{
|
||||||
|
borderColor.BorderColor = otherBorderColor.BorderColor;
|
||||||
|
borderColor.AccentHColor = otherBorderColor.AccentHColor;
|
||||||
|
borderColor.AccentVColor = otherBorderColor.AccentVColor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
using Content.Client.Clothing.Systems;
|
using Content.Client.Clothing.Systems;
|
||||||
using Content.Shared.Clothing.Components;
|
using Content.Shared.Clothing.Components;
|
||||||
|
using Content.Shared.Tag;
|
||||||
|
using Content.Shared.Prototypes;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Client.Clothing.UI;
|
namespace Content.Client.Clothing.UI;
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class ChameleonBoundUserInterface : BoundUserInterface
|
public sealed class ChameleonBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
private readonly ChameleonClothingSystem _chameleon;
|
private readonly ChameleonClothingSystem _chameleon;
|
||||||
|
private readonly TagSystem _tag;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private ChameleonMenu? _menu;
|
private ChameleonMenu? _menu;
|
||||||
@@ -17,6 +23,7 @@ public sealed class ChameleonBoundUserInterface : BoundUserInterface
|
|||||||
public ChameleonBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
public ChameleonBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
_chameleon = EntMan.System<ChameleonClothingSystem>();
|
_chameleon = EntMan.System<ChameleonClothingSystem>();
|
||||||
|
_tag = EntMan.System<TagSystem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
@@ -34,7 +41,24 @@ public sealed class ChameleonBoundUserInterface : BoundUserInterface
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var targets = _chameleon.GetValidTargets(st.Slot);
|
var targets = _chameleon.GetValidTargets(st.Slot);
|
||||||
_menu?.UpdateState(targets, st.SelectedId);
|
if (st.RequiredTag != null)
|
||||||
|
{
|
||||||
|
var newTargets = new List<string>();
|
||||||
|
foreach (var target in targets)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(target) || !_proto.TryIndex(target, out EntityPrototype? proto))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!proto.TryGetComponent(out TagComponent? tag, _factory) || !_tag.HasTag(tag, st.RequiredTag))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
newTargets.Add(target);
|
||||||
|
}
|
||||||
|
_menu?.UpdateState(newTargets, st.SelectedId);
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
_menu?.UpdateState(targets, st.SelectedId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnIdSelected(string selectedId)
|
private void OnIdSelected(string selectedId)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<Label Name="CallStatusText" Margin="10 5 10 0" ReservesSpace="False"/>
|
<Label Name="CallStatusText" Margin="10 5 10 0" ReservesSpace="False"/>
|
||||||
<BoxContainer Name="CallerIdContainer" Orientation="Vertical" ReservesSpace="False">
|
<BoxContainer Name="CallerIdContainer" Orientation="Vertical" ReservesSpace="False">
|
||||||
<RichTextLabel Name="CallerIdText" HorizontalAlignment="Center" Margin="0 0 0 0"/>
|
<RichTextLabel Name="CallerIdText" HorizontalAlignment="Center" Margin="0 0 0 0"/>
|
||||||
<Label Text="{Loc 'holopad-window-relay-label'}" Margin="10 5 10 0" ReservesSpace="False"/>
|
<Label Text="{Loc 'holopad-window-relay-label'}" Margin="10 10 10 0" ReservesSpace="False"/>
|
||||||
<RichTextLabel Name="HolopadIdText" HorizontalAlignment="Center" Margin="0 0 0 10"/>
|
<RichTextLabel Name="HolopadIdText" HorizontalAlignment="Center" Margin="0 0 0 10"/>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
|||||||
@@ -5,19 +5,21 @@ using Content.Client.Lobby.UI;
|
|||||||
using Content.Client.Message;
|
using Content.Client.Message;
|
||||||
using Content.Client.UserInterface.Systems.Chat;
|
using Content.Client.UserInterface.Systems.Chat;
|
||||||
using Content.Client.Voting;
|
using Content.Client.Voting;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
using Robust.Client;
|
using Robust.Client;
|
||||||
using Robust.Client.Console;
|
using Robust.Client.Console;
|
||||||
using Robust.Client.ResourceManagement;
|
using Robust.Client.ResourceManagement;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
|
||||||
namespace Content.Client.Lobby
|
namespace Content.Client.Lobby
|
||||||
{
|
{
|
||||||
public sealed class LobbyState : Robust.Client.State.State
|
public sealed class LobbyState : Robust.Client.State.State
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IBaseClient _baseClient = default!;
|
[Dependency] private readonly IBaseClient _baseClient = default!;
|
||||||
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||||
@@ -49,7 +51,17 @@ namespace Content.Client.Lobby
|
|||||||
|
|
||||||
_voteManager.SetPopupContainer(Lobby.VoteContainer);
|
_voteManager.SetPopupContainer(Lobby.VoteContainer);
|
||||||
LayoutContainer.SetAnchorPreset(Lobby, LayoutContainer.LayoutPreset.Wide);
|
LayoutContainer.SetAnchorPreset(Lobby, LayoutContainer.LayoutPreset.Wide);
|
||||||
Lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you...
|
|
||||||
|
var lobbyNameCvar = _cfg.GetCVar(CCVars.ServerLobbyName);
|
||||||
|
var serverName = _baseClient.GameInfo?.ServerName ?? string.Empty;
|
||||||
|
|
||||||
|
Lobby.ServerName.Text = string.IsNullOrEmpty(lobbyNameCvar)
|
||||||
|
? Loc.GetString("ui-lobby-title", ("serverName", serverName))
|
||||||
|
: lobbyNameCvar;
|
||||||
|
|
||||||
|
var width = _cfg.GetCVar(CCVars.ServerLobbyRightPanelWidth);
|
||||||
|
Lobby.RightSide.SetWidth = width;
|
||||||
|
|
||||||
UpdateLobbyUi();
|
UpdateLobbyUi();
|
||||||
|
|
||||||
Lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed;
|
Lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed;
|
||||||
|
|||||||
@@ -62,14 +62,12 @@
|
|||||||
<Control Access="Public" Visible="False" Name="CharacterSetupState" VerticalExpand="True" />
|
<Control Access="Public" Visible="False" Name="CharacterSetupState" VerticalExpand="True" />
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<!-- Right Panel -->
|
<!-- Right Panel -->
|
||||||
<PanelContainer Name="RightSide" StyleClasses="AngleRect" HorizontalAlignment="Right" VerticalExpand="True"
|
<PanelContainer Name="RightSide" Access="Public" StyleClasses="AngleRect" HorizontalAlignment="Right" VerticalExpand="True"
|
||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch">
|
||||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||||
<!-- Top row -->
|
<!-- Top row -->
|
||||||
<BoxContainer Orientation="Horizontal" MinSize="0 40" Name="HeaderContainer" Access="Public"
|
<BoxContainer Orientation="Horizontal" MinSize="0 40" Name="HeaderContainer" Access="Public"
|
||||||
SeparationOverride="4">
|
SeparationOverride="4">
|
||||||
<Label Margin="8 0 0 0" StyleClasses="LabelHeadingBigger" VAlign="Center"
|
|
||||||
Text="{Loc 'ui-lobby-title'}" />
|
|
||||||
<Label Name="ServerName" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center"
|
<Label Name="ServerName" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center"
|
||||||
HorizontalExpand="True" HorizontalAlignment="Center" />
|
HorizontalExpand="True" HorizontalAlignment="Center" />
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System.Numerics;
|
|||||||
using Content.Client.Parallax;
|
using Content.Client.Parallax;
|
||||||
using Content.Client.Weather;
|
using Content.Client.Weather;
|
||||||
using Content.Shared._CP14.DayCycle.Components;
|
using Content.Shared._CP14.DayCycle.Components;
|
||||||
using Content.Shared._CP14.WorldEdge;
|
|
||||||
using Content.Shared.Salvage;
|
using Content.Shared.Salvage;
|
||||||
using Content.Shared.Weather;
|
using Content.Shared.Weather;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
@@ -84,13 +83,6 @@ public sealed partial class StencilOverlay : Overlay
|
|||||||
}
|
}
|
||||||
//CP14 Overlays end
|
//CP14 Overlays end
|
||||||
|
|
||||||
//CP14 Overlays
|
|
||||||
if (_entManager.TryGetComponent<CP14WorldEdgeComponent>(mapUid, out var worldEdge))
|
|
||||||
{
|
|
||||||
DrawWorldEdge(args, worldEdge, invMatrix);
|
|
||||||
}
|
|
||||||
//CP14 Overlays end
|
|
||||||
|
|
||||||
args.WorldHandle.UseShader(null);
|
args.WorldHandle.UseShader(null);
|
||||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,6 +141,11 @@ namespace Content.Client.PDA
|
|||||||
_pdaOwner = state.PdaOwnerInfo.ActualOwnerName;
|
_pdaOwner = state.PdaOwnerInfo.ActualOwnerName;
|
||||||
PdaOwnerLabel.SetMarkup(Loc.GetString("comp-pda-ui-owner",
|
PdaOwnerLabel.SetMarkup(Loc.GetString("comp-pda-ui-owner",
|
||||||
("actualOwnerName", _pdaOwner)));
|
("actualOwnerName", _pdaOwner)));
|
||||||
|
PdaOwnerLabel.Visible = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PdaOwnerLabel.Visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +1,8 @@
|
|||||||
using Content.Shared.PDA;
|
using Content.Shared.PDA;
|
||||||
using Content.Shared.Light;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.PDA;
|
namespace Content.Client.PDA;
|
||||||
|
|
||||||
public sealed class PdaSystem : SharedPdaSystem
|
public sealed class PdaSystem : SharedPdaSystem
|
||||||
{
|
{
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<PdaComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAppearanceChange(EntityUid uid, PdaComponent component, ref AppearanceChangeEvent args)
|
|
||||||
{
|
|
||||||
if (args.Sprite == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Appearance.TryGetData<bool>(uid, UnpoweredFlashlightVisuals.LightOn, out var isFlashlightOn, args.Component))
|
|
||||||
args.Sprite.LayerSetVisible(PdaVisualLayers.Flashlight, isFlashlightOn);
|
|
||||||
|
|
||||||
if (Appearance.TryGetData<bool>(uid, PdaVisuals.IdCardInserted, out var isCardInserted, args.Component))
|
|
||||||
args.Sprite.LayerSetVisible(PdaVisualLayers.IdLight, isCardInserted);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnComponentInit(EntityUid uid, PdaComponent component, ComponentInit args)
|
|
||||||
{
|
|
||||||
base.OnComponentInit(uid, component, args);
|
|
||||||
|
|
||||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (component.State != null)
|
|
||||||
sprite.LayerSetState(PdaVisualLayers.Base, component.State);
|
|
||||||
|
|
||||||
sprite.LayerSetVisible(PdaVisualLayers.Flashlight, component.FlashlightOn);
|
|
||||||
sprite.LayerSetVisible(PdaVisualLayers.IdLight, component.IdSlot.StartingItem != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PdaVisualLayers : byte
|
|
||||||
{
|
|
||||||
Base,
|
|
||||||
Flashlight,
|
|
||||||
IdLight
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
30
Content.Client/PDA/PdaVisualizerSystem.cs
Normal file
30
Content.Client/PDA/PdaVisualizerSystem.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Content.Shared.Light;
|
||||||
|
using Content.Shared.PDA;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.PDA;
|
||||||
|
|
||||||
|
public sealed class PdaVisualizerSystem : VisualizerSystem<PdaVisualsComponent>
|
||||||
|
{
|
||||||
|
protected override void OnAppearanceChange(EntityUid uid, PdaVisualsComponent comp, ref AppearanceChangeEvent args)
|
||||||
|
{
|
||||||
|
if (args.Sprite == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (AppearanceSystem.TryGetData<string>(uid, PdaVisuals.PdaType, out var pdaType, args.Component))
|
||||||
|
args.Sprite.LayerSetState(PdaVisualLayers.Base, pdaType);
|
||||||
|
|
||||||
|
if (AppearanceSystem.TryGetData<bool>(uid, UnpoweredFlashlightVisuals.LightOn, out var isFlashlightOn, args.Component))
|
||||||
|
args.Sprite.LayerSetVisible(PdaVisualLayers.Flashlight, isFlashlightOn);
|
||||||
|
|
||||||
|
if (AppearanceSystem.TryGetData<bool>(uid, PdaVisuals.IdCardInserted, out var isCardInserted, args.Component))
|
||||||
|
args.Sprite.LayerSetVisible(PdaVisualLayers.IdLight, isCardInserted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PdaVisualLayers : byte
|
||||||
|
{
|
||||||
|
Base,
|
||||||
|
Flashlight,
|
||||||
|
IdLight
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Content.Client/PDA/PdaVisualsComponent.cs
Normal file
14
Content.Client/PDA/PdaVisualsComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Content.Client.PDA;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for visualizing PDA visuals.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class PdaVisualsComponent : Component
|
||||||
|
{
|
||||||
|
public string? BorderColor;
|
||||||
|
|
||||||
|
public string? AccentHColor;
|
||||||
|
|
||||||
|
public string? AccentVColor;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ namespace Content.Client
|
|||||||
{
|
{
|
||||||
internal static class Program
|
internal static class Program
|
||||||
{
|
{
|
||||||
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
ContentStart.Start(args);
|
ContentStart.Start(args);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using Content.Client.Administration.Systems;
|
|||||||
using Content.Client.Administration.UI;
|
using Content.Client.Administration.UI;
|
||||||
using Content.Client.Administration.UI.Tabs.ObjectsTab;
|
using Content.Client.Administration.UI.Tabs.ObjectsTab;
|
||||||
using Content.Client.Administration.UI.Tabs.PanicBunkerTab;
|
using Content.Client.Administration.UI.Tabs.PanicBunkerTab;
|
||||||
using Content.Client.Administration.UI.Tabs.BabyJailTab;
|
|
||||||
using Content.Client.Administration.UI.Tabs.PlayerTab;
|
using Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||||
using Content.Client.Gameplay;
|
using Content.Client.Gameplay;
|
||||||
using Content.Client.Lobby;
|
using Content.Client.Lobby;
|
||||||
@@ -38,13 +37,11 @@ public sealed class AdminUIController : UIController,
|
|||||||
private AdminMenuWindow? _window;
|
private AdminMenuWindow? _window;
|
||||||
private MenuButton? AdminButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.AdminButton;
|
private MenuButton? AdminButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.AdminButton;
|
||||||
private PanicBunkerStatus? _panicBunker;
|
private PanicBunkerStatus? _panicBunker;
|
||||||
private BabyJailStatus? _babyJail;
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeNetworkEvent<PanicBunkerChangedEvent>(OnPanicBunkerUpdated);
|
SubscribeNetworkEvent<PanicBunkerChangedEvent>(OnPanicBunkerUpdated);
|
||||||
SubscribeNetworkEvent<BabyJailChangedEvent>(OnBabyJailUpdated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPanicBunkerUpdated(PanicBunkerChangedEvent msg, EntitySessionEventArgs args)
|
private void OnPanicBunkerUpdated(PanicBunkerChangedEvent msg, EntitySessionEventArgs args)
|
||||||
@@ -59,18 +56,6 @@ public sealed class AdminUIController : UIController,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBabyJailUpdated(BabyJailChangedEvent msg, EntitySessionEventArgs args)
|
|
||||||
{
|
|
||||||
var showDialog = _babyJail == null && msg.Status.Enabled;
|
|
||||||
_babyJail = msg.Status;
|
|
||||||
_window?.BabyJailControl.UpdateStatus(msg.Status);
|
|
||||||
|
|
||||||
if (showDialog)
|
|
||||||
{
|
|
||||||
UIManager.CreateWindow<BabyJailStatusWindow>().OpenCentered();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnStateEntered(GameplayState state)
|
public void OnStateEntered(GameplayState state)
|
||||||
{
|
{
|
||||||
EnsureWindow();
|
EnsureWindow();
|
||||||
@@ -116,13 +101,6 @@ public sealed class AdminUIController : UIController,
|
|||||||
if (_panicBunker != null)
|
if (_panicBunker != null)
|
||||||
_window.PanicBunkerControl.UpdateStatus(_panicBunker);
|
_window.PanicBunkerControl.UpdateStatus(_panicBunker);
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (_babyJail != null)
|
|
||||||
_window.BabyJailControl.UpdateStatus(_babyJail);
|
|
||||||
|
|
||||||
_window.PlayerTabControl.OnEntryKeyBindDown += PlayerTabEntryKeyBindDown;
|
_window.PlayerTabControl.OnEntryKeyBindDown += PlayerTabEntryKeyBindDown;
|
||||||
_window.ObjectsTabControl.OnEntryKeyBindDown += ObjectsTabEntryKeyBindDown;
|
_window.ObjectsTabControl.OnEntryKeyBindDown += ObjectsTabEntryKeyBindDown;
|
||||||
_window.OnOpen += OnWindowOpen;
|
_window.OnOpen += OnWindowOpen;
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ using Content.Client.UserInterface.Systems.Character.Controls;
|
|||||||
using Content.Client.UserInterface.Systems.Character.Windows;
|
using Content.Client.UserInterface.Systems.Character.Windows;
|
||||||
using Content.Client.UserInterface.Systems.Objectives.Controls;
|
using Content.Client.UserInterface.Systems.Objectives.Controls;
|
||||||
using Content.Shared.Input;
|
using Content.Shared.Input;
|
||||||
using Content.Shared.Objectives.Systems;
|
using Content.Shared.Mind;
|
||||||
|
using Content.Shared.Mind.Components;
|
||||||
|
using Content.Shared.Roles;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
@@ -15,6 +17,7 @@ using Robust.Client.UserInterface;
|
|||||||
using Robust.Client.UserInterface.Controllers;
|
using Robust.Client.UserInterface.Controllers;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Shared.Input.Binding;
|
using Robust.Shared.Input.Binding;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using static Content.Client.CharacterInfo.CharacterInfoSystem;
|
using static Content.Client.CharacterInfo.CharacterInfoSystem;
|
||||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||||
@@ -24,10 +27,25 @@ namespace Content.Client.UserInterface.Systems.Character;
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class CharacterUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CharacterInfoSystem>
|
public sealed class CharacterUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CharacterInfoSystem>
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _ent = default!;
|
||||||
|
[Dependency] private readonly ILogManager _logMan = default!;
|
||||||
[Dependency] private readonly IPlayerManager _player = default!;
|
[Dependency] private readonly IPlayerManager _player = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
[UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
|
[UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
|
||||||
[UISystemDependency] private readonly SpriteSystem _sprite = default!;
|
[UISystemDependency] private readonly SpriteSystem _sprite = default!;
|
||||||
|
|
||||||
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_sawmill = _logMan.GetSawmill("character");
|
||||||
|
|
||||||
|
SubscribeNetworkEvent<MindRoleTypeChangedEvent>(OnRoleTypeChanged);
|
||||||
|
}
|
||||||
|
|
||||||
private CharacterWindow? _window;
|
private CharacterWindow? _window;
|
||||||
private MenuButton? CharacterButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.CharacterButton;
|
private MenuButton? CharacterButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.CharacterButton;
|
||||||
|
|
||||||
@@ -110,6 +128,9 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
|||||||
var (entity, job, objectives, briefing, entityName) = data;
|
var (entity, job, objectives, briefing, entityName) = data;
|
||||||
|
|
||||||
_window.SpriteView.SetEntity(entity);
|
_window.SpriteView.SetEntity(entity);
|
||||||
|
|
||||||
|
UpdateRoleType();
|
||||||
|
|
||||||
_window.NameLabel.Text = entityName;
|
_window.NameLabel.Text = entityName;
|
||||||
_window.SubText.Text = job;
|
_window.SubText.Text = job;
|
||||||
_window.Objectives.RemoveAllChildren();
|
_window.Objectives.RemoveAllChildren();
|
||||||
@@ -173,6 +194,37 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
|||||||
_window.RolePlaceholder.Visible = briefing == null && !controls.Any() && !objectives.Any();
|
_window.RolePlaceholder.Visible = briefing == null && !controls.Any() && !objectives.Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnRoleTypeChanged(MindRoleTypeChangedEvent ev, EntitySessionEventArgs _)
|
||||||
|
{
|
||||||
|
UpdateRoleType();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateRoleType()
|
||||||
|
{
|
||||||
|
if (_window == null || !_window.IsOpen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_ent.TryGetComponent<MindContainerComponent>(_player.LocalEntity, out var container)
|
||||||
|
|| container.Mind is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_ent.TryGetComponent<MindComponent>(container.Mind.Value, out var mind))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var roleText = Loc.GetString("role-type-crew-aligned-name");
|
||||||
|
var color = Color.White;
|
||||||
|
if (_prototypeManager.TryIndex(mind.RoleType, out var proto))
|
||||||
|
{
|
||||||
|
roleText = Loc.GetString(proto.Name);
|
||||||
|
color = proto.Color;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_sawmill.Error($"{_player.LocalEntity} has invalid Role Type '{mind.RoleType}'. Displaying '{roleText}' instead");
|
||||||
|
|
||||||
|
_window.RoleType.Text = roleText;
|
||||||
|
_window.RoleType.FontColorOverride = color;
|
||||||
|
}
|
||||||
|
|
||||||
private void CharacterDetached(EntityUid uid)
|
private void CharacterDetached(EntityUid uid)
|
||||||
{
|
{
|
||||||
CloseWindow();
|
CloseWindow();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
MinHeight="545">
|
MinHeight="545">
|
||||||
<ScrollContainer>
|
<ScrollContainer>
|
||||||
<BoxContainer Orientation="Vertical">
|
<BoxContainer Orientation="Vertical">
|
||||||
|
<Label Name="RoleType" VerticalAlignment="Top" Margin="0 6 0 10" HorizontalAlignment="Center" StyleClasses="LabelHeading" Access="Public"/>
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer Orientation="Horizontal">
|
||||||
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64"/>
|
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64"/>
|
||||||
<BoxContainer Orientation="Vertical" VerticalAlignment="Top">
|
<BoxContainer Orientation="Vertical" VerticalAlignment="Top">
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Content.Shared._CP14.WorldEdge;
|
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
|
|
||||||
namespace Content.Client.Overlays;
|
|
||||||
|
|
||||||
public sealed partial class StencilOverlay
|
|
||||||
{
|
|
||||||
private void DrawWorldEdge(in OverlayDrawArgs args, CP14WorldEdgeComponent rangeComp, Matrix3x2 invMatrix)
|
|
||||||
{
|
|
||||||
var worldHandle = args.WorldHandle;
|
|
||||||
var renderScale = args.Viewport.RenderScale.X;
|
|
||||||
// TODO: This won't handle non-standard zooms so uhh yeah, not sure how to structure it on the shader side.
|
|
||||||
var zoom = args.Viewport.Eye?.Zoom ?? Vector2.One;
|
|
||||||
var length = zoom.X;
|
|
||||||
var bufferRange = MathF.Min(10f, rangeComp.Range);
|
|
||||||
|
|
||||||
var pixelCenter = Vector2.Transform(rangeComp.Origin, invMatrix);
|
|
||||||
// Something something offset?
|
|
||||||
var vertical = args.Viewport.Size.Y;
|
|
||||||
|
|
||||||
var pixelMaxRange = rangeComp.Range * renderScale / length * EyeManager.PixelsPerMeter;
|
|
||||||
var pixelBufferRange = bufferRange * renderScale / length * EyeManager.PixelsPerMeter;
|
|
||||||
var pixelMinRange = pixelMaxRange - pixelBufferRange;
|
|
||||||
|
|
||||||
_shader.SetParameter("position", new Vector2(pixelCenter.X, vertical - pixelCenter.Y));
|
|
||||||
_shader.SetParameter("maxRange", pixelMaxRange);
|
|
||||||
_shader.SetParameter("minRange", pixelMinRange);
|
|
||||||
_shader.SetParameter("bufferRange", pixelBufferRange);
|
|
||||||
_shader.SetParameter("gradient", 0.80f);
|
|
||||||
|
|
||||||
var worldAABB = args.WorldAABB;
|
|
||||||
var worldBounds = args.WorldBounds;
|
|
||||||
var position = args.Viewport.Eye?.Position.Position ?? Vector2.Zero;
|
|
||||||
var localAABB = invMatrix.TransformBox(worldAABB);
|
|
||||||
|
|
||||||
// Cut out the irrelevant bits via stencil
|
|
||||||
// This is why we don't just use parallax; we might want specific tiles to get drawn over
|
|
||||||
// particularly for planet maps or stations.
|
|
||||||
worldHandle.RenderInRenderTarget(_blep!, () =>
|
|
||||||
{
|
|
||||||
worldHandle.UseShader(_shader);
|
|
||||||
worldHandle.DrawRect(localAABB, Color.White);
|
|
||||||
}, Color.Transparent);
|
|
||||||
|
|
||||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
|
||||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilMask").Instance());
|
|
||||||
worldHandle.DrawTextureRect(_blep!.Texture, worldBounds);
|
|
||||||
var curTime = _timing.RealTime;
|
|
||||||
var sprite = _sprite.GetFrame(new SpriteSpecifier.Texture(new ResPath("/Textures/Parallaxes/noise.png")), curTime);
|
|
||||||
|
|
||||||
// Draw the rain
|
|
||||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilDraw").Instance());
|
|
||||||
_parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, new Vector2(0.2f, 0.1f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -92,7 +92,7 @@ public sealed class FluidSpill
|
|||||||
|
|
||||||
#pragma warning disable NUnit2045 // Interdependent tests
|
#pragma warning disable NUnit2045 // Interdependent tests
|
||||||
Assert.That(puddle, Is.Not.Null);
|
Assert.That(puddle, Is.Not.Null);
|
||||||
//Assert.That(puddleSystem.CurrentVolume(puddle!.Value.Owner, puddle), Is.EqualTo(FixedPoint2.New(100)));
|
//Assert.That(puddleSystem.CurrentVolume(puddle!.Value.Owner, puddle), Is.EqualTo(FixedPoint2.New(100))); //TOOD: CP14 fix failing test because force undersky evaporation :(
|
||||||
#pragma warning restore NUnit2045
|
#pragma warning restore NUnit2045
|
||||||
|
|
||||||
for (var x = 0; x < 3; x++)
|
for (var x = 0; x < 3; x++)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
|
|||||||
{
|
{
|
||||||
playerUid = serverPlayerManager.Sessions.Single().AttachedEntity.GetValueOrDefault();
|
playerUid = serverPlayerManager.Sessions.Single().AttachedEntity.GetValueOrDefault();
|
||||||
#pragma warning disable NUnit2045 // Interdependent assertions.
|
#pragma warning disable NUnit2045 // Interdependent assertions.
|
||||||
Assert.That(playerUid, Is.Not.EqualTo(default));
|
Assert.That(playerUid, Is.Not.EqualTo(default(EntityUid)));
|
||||||
// Making sure it exists
|
// Making sure it exists
|
||||||
Assert.That(entManager.HasComponent<AlertsComponent>(playerUid));
|
Assert.That(entManager.HasComponent<AlertsComponent>(playerUid));
|
||||||
#pragma warning restore NUnit2045
|
#pragma warning restore NUnit2045
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ using Robust.Shared.Map;
|
|||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Content.Shared.Station.Components;
|
using Content.Shared.Station.Components;
|
||||||
using FastAccessors;
|
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using YamlDotNet.RepresentationModel;
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,13 @@ public static class ClientPackaging
|
|||||||
var graph = new RobustClientAssetGraph();
|
var graph = new RobustClientAssetGraph();
|
||||||
pass.Dependencies.Add(new AssetPassDependency(graph.Output.Name));
|
pass.Dependencies.Add(new AssetPassDependency(graph.Output.Name));
|
||||||
|
|
||||||
AssetGraph.CalculateGraph(graph.AllPasses.Append(pass).ToArray(), logger);
|
var dropSvgPass = new AssetPassFilterDrop(f => f.Path.EndsWith(".svg"))
|
||||||
|
{
|
||||||
|
Name = "DropSvgPass",
|
||||||
|
};
|
||||||
|
dropSvgPass.AddDependency(graph.Input).AddBefore(graph.PresetPasses);
|
||||||
|
|
||||||
|
AssetGraph.CalculateGraph([pass, dropSvgPass, ..graph.AllPasses], logger);
|
||||||
|
|
||||||
var inputPass = graph.Input;
|
var inputPass = graph.Input;
|
||||||
|
|
||||||
@@ -72,7 +78,7 @@ public static class ClientPackaging
|
|||||||
new[] { "Content.Client", "Content.Shared", "Content.Shared.Database" },
|
new[] { "Content.Client", "Content.Shared", "Content.Shared.Database" },
|
||||||
cancel: cancel);
|
cancel: cancel);
|
||||||
|
|
||||||
await RobustClientPackaging.WriteClientResources(contentDir, pass, cancel);
|
await RobustClientPackaging.WriteClientResources(contentDir, inputPass, cancel);
|
||||||
|
|
||||||
inputPass.InjectFinished();
|
inputPass.InjectFinished();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
|
|||||||
2104
Content.Server.Database/Migrations/Postgres/20241122174243_IPIntel.Designer.cs
generated
Normal file
2104
Content.Server.Database/Migrations/Postgres/20241122174243_IPIntel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Postgres
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class IPIntel : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ipintel_cache",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ipintel_cache_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
address = table.Column<IPAddress>(type: "inet", nullable: false),
|
||||||
|
time = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
score = table.Column<float>(type: "real", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ipintel_cache", x => x.ipintel_cache_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.Sql("CREATE UNIQUE INDEX idx_ipintel_cache_address ON ipintel_cache(address)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ipintel_cache");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2084
Content.Server.Database/Migrations/Postgres/20241223235939_AdminStatus.Designer.cs
generated
Normal file
2084
Content.Server.Database/Migrations/Postgres/20241223235939_AdminStatus.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Postgres
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AdminStatus : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "deadminned",
|
||||||
|
table: "admin",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "suspended",
|
||||||
|
table: "admin",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "deadminned",
|
||||||
|
table: "admin");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "suspended",
|
||||||
|
table: "admin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,14 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
.HasColumnType("integer")
|
.HasColumnType("integer")
|
||||||
.HasColumnName("admin_rank_id");
|
.HasColumnName("admin_rank_id");
|
||||||
|
|
||||||
|
b.Property<bool>("Deadminned")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("deadminned");
|
||||||
|
|
||||||
|
b.Property<bool>("Suspended")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("suspended");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.HasColumnType("text")
|
.HasColumnType("text")
|
||||||
.HasColumnName("title");
|
.HasColumnName("title");
|
||||||
@@ -627,6 +635,34 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.IPIntelCache", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ipintel_cache_id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<IPAddress>("Address")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("inet")
|
||||||
|
.HasColumnName("address");
|
||||||
|
|
||||||
|
b.Property<float>("Score")
|
||||||
|
.HasColumnType("real")
|
||||||
|
.HasColumnName("score");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Time")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("time");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ipintel_cache");
|
||||||
|
|
||||||
|
b.ToTable("ipintel_cache", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Job", b =>
|
modelBuilder.Entity("Content.Server.Database.Job", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|||||||
2028
Content.Server.Database/Migrations/Sqlite/20241122174236_IPIntel.Designer.cs
generated
Normal file
2028
Content.Server.Database/Migrations/Sqlite/20241122174236_IPIntel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Sqlite
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class IPIntel : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ipintel_cache",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ipintel_cache_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
address = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
time = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
score = table.Column<float>(type: "REAL", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ipintel_cache", x => x.ipintel_cache_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ipintel_cache_address",
|
||||||
|
table: "ipintel_cache",
|
||||||
|
column: "address",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ipintel_cache");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2007
Content.Server.Database/Migrations/Sqlite/20241223235932_AdminStatus.Designer.cs
generated
Normal file
2007
Content.Server.Database/Migrations/Sqlite/20241223235932_AdminStatus.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Sqlite
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AdminStatus : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "deadminned",
|
||||||
|
table: "admin",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "suspended",
|
||||||
|
table: "admin",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "deadminned",
|
||||||
|
table: "admin");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "suspended",
|
||||||
|
table: "admin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,14 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
.HasColumnName("admin_rank_id");
|
.HasColumnName("admin_rank_id");
|
||||||
|
|
||||||
|
b.Property<bool>("Deadminned")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("deadminned");
|
||||||
|
|
||||||
|
b.Property<bool>("Suspended")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("suspended");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("title");
|
.HasColumnName("title");
|
||||||
@@ -591,6 +599,35 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.ToTable("connection_log", (string)null);
|
b.ToTable("connection_log", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.IPIntelCache", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ipintel_cache_id");
|
||||||
|
|
||||||
|
b.Property<string>("Address")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("address");
|
||||||
|
|
||||||
|
b.Property<float>("Score")
|
||||||
|
.HasColumnType("REAL")
|
||||||
|
.HasColumnName("score");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Time")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("time");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ipintel_cache");
|
||||||
|
|
||||||
|
b.HasIndex("Address")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ipintel_cache", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Job", b =>
|
modelBuilder.Entity("Content.Server.Database.Job", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ namespace Content.Server.Database
|
|||||||
public DbSet<AdminMessage> AdminMessages { get; set; } = null!;
|
public DbSet<AdminMessage> AdminMessages { get; set; } = null!;
|
||||||
public DbSet<RoleWhitelist> RoleWhitelists { get; set; } = null!;
|
public DbSet<RoleWhitelist> RoleWhitelists { get; set; } = null!;
|
||||||
public DbSet<BanTemplate> BanTemplate { get; set; } = null!;
|
public DbSet<BanTemplate> BanTemplate { get; set; } = null!;
|
||||||
|
public DbSet<IPIntelCache> IPIntelCache { get; set; } = null!;
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@@ -609,6 +610,16 @@ namespace Content.Server.Database
|
|||||||
[Key] public Guid UserId { get; set; }
|
[Key] public Guid UserId { get; set; }
|
||||||
public string? Title { get; set; }
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, the admin is voluntarily deadminned. They can re-admin at any time.
|
||||||
|
/// </summary>
|
||||||
|
public bool Deadminned { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, the admin is suspended by an admin with <c>PERMISSIONS</c>. They will not have in-game permissions.
|
||||||
|
/// </summary>
|
||||||
|
public bool Suspended { get; set; }
|
||||||
|
|
||||||
public int? AdminRankId { get; set; }
|
public int? AdminRankId { get; set; }
|
||||||
public AdminRank? AdminRank { get; set; }
|
public AdminRank? AdminRank { get; set; }
|
||||||
public List<AdminFlag> Flags { get; set; } = default!;
|
public List<AdminFlag> Flags { get; set; } = default!;
|
||||||
@@ -962,12 +973,14 @@ namespace Content.Server.Database
|
|||||||
Full = 2,
|
Full = 2,
|
||||||
Panic = 3,
|
Panic = 3,
|
||||||
/*
|
/*
|
||||||
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
|
||||||
*
|
|
||||||
* If baby jail is removed, please reserve this value for as long as can reasonably be done to prevent causing ambiguity in connection denial reasons.
|
* If baby jail is removed, please reserve this value for as long as can reasonably be done to prevent causing ambiguity in connection denial reasons.
|
||||||
* Reservation by commenting out the value is likely sufficient for this purpose, but may impact projects which depend on SS14 like SS14.Admin.
|
* Reservation by commenting out the value is likely sufficient for this purpose, but may impact projects which depend on SS14 like SS14.Admin.
|
||||||
|
*
|
||||||
|
* Edit: It has
|
||||||
*/
|
*/
|
||||||
BabyJail = 4,
|
BabyJail = 4,
|
||||||
|
/// Results from rejected connections with external API checking tools
|
||||||
|
IPChecks = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ServerBanHit
|
public class ServerBanHit
|
||||||
@@ -1284,4 +1297,28 @@ namespace Content.Server.Database
|
|||||||
return new ImmutableTypedHwid(hwid.Hwid.ToImmutableArray(), hwid.Type);
|
return new ImmutableTypedHwid(hwid.Hwid.ToImmutableArray(), hwid.Type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cache for the IPIntel system
|
||||||
|
/// </summary>
|
||||||
|
public class IPIntelCache
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The IP address (duh). This is made unique manually for psql cause of ef core bug.
|
||||||
|
/// </summary>
|
||||||
|
public IPAddress Address { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Date this record was added. Used to check if our cache is out of date.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Time { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The score IPIntel returned
|
||||||
|
/// </summary>
|
||||||
|
public float Score { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ namespace Content.Server.Database
|
|||||||
modelBuilder.Entity<Profile>()
|
modelBuilder.Entity<Profile>()
|
||||||
.Property(log => log.Markings)
|
.Property(log => log.Markings)
|
||||||
.HasConversion(jsonByteArrayConverter);
|
.HasConversion(jsonByteArrayConverter);
|
||||||
|
|
||||||
|
// EF core can make this automatically unique on sqlite but not psql.
|
||||||
|
modelBuilder.Entity<IPIntelCache>()
|
||||||
|
.HasIndex(p => p.Address)
|
||||||
|
.IsUnique();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int CountAdminLogs()
|
public override int CountAdminLogs()
|
||||||
|
|||||||
@@ -116,10 +116,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
|||||||
targetLabel = Loc.GetString("access-overrider-window-target-label") + " " + EntityManager.GetComponent<MetaDataComponent>(component.TargetAccessReaderId).EntityName;
|
targetLabel = Loc.GetString("access-overrider-window-target-label") + " " + EntityManager.GetComponent<MetaDataComponent>(component.TargetAccessReaderId).EntityName;
|
||||||
targetLabelColor = Color.White;
|
targetLabelColor = Color.White;
|
||||||
|
|
||||||
if (!_accessReader.GetMainAccessReader(accessReader, out var accessReaderComponent))
|
if (!_accessReader.GetMainAccessReader(accessReader, out var accessReaderEnt))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var currentAccessHashsets = accessReaderComponent.AccessLists;
|
var currentAccessHashsets = accessReaderEnt.Value.Comp.AccessLists;
|
||||||
currentAccess = ConvertAccessHashSetsToList(currentAccessHashsets).ToArray();
|
currentAccess = ConvertAccessHashSetsToList(currentAccessHashsets).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,10 +210,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_accessReader.GetMainAccessReader(component.TargetAccessReaderId, out var accessReader))
|
if (!_accessReader.GetMainAccessReader(component.TargetAccessReaderId, out var accessReaderEnt))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var oldTags = ConvertAccessHashSetsToList(accessReader.AccessLists);
|
var oldTags = ConvertAccessHashSetsToList(accessReaderEnt.Value.Comp.AccessLists);
|
||||||
var privilegedId = component.PrivilegedIdSlot.Item;
|
var privilegedId = component.PrivilegedIdSlot.Item;
|
||||||
|
|
||||||
if (oldTags.SequenceEqual(newAccessList))
|
if (oldTags.SequenceEqual(newAccessList))
|
||||||
@@ -242,10 +242,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
|||||||
var removedTags = oldTags.Except(newAccessList).Select(tag => "-" + tag).ToList();
|
var removedTags = oldTags.Except(newAccessList).Select(tag => "-" + tag).ToList();
|
||||||
|
|
||||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||||
$"{ToPrettyString(player):player} has modified {ToPrettyString(component.TargetAccessReaderId):entity} with the following allowed access level holders: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
|
$"{ToPrettyString(player):player} has modified {ToPrettyString(accessReaderEnt.Value):entity} with the following allowed access level holders: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
|
||||||
|
|
||||||
accessReader.AccessLists = ConvertAccessListToHashSet(newAccessList);
|
accessReaderEnt.Value.Comp.AccessLists = ConvertAccessListToHashSet(newAccessList);
|
||||||
Dirty(component.TargetAccessReaderId, accessReader);
|
Dirty(accessReaderEnt.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -33,16 +33,16 @@ public sealed class AdminWhoCommand : IConsoleCommand
|
|||||||
var first = true;
|
var first = true;
|
||||||
foreach (var admin in adminMgr.ActiveAdmins)
|
foreach (var admin in adminMgr.ActiveAdmins)
|
||||||
{
|
{
|
||||||
if (!first)
|
|
||||||
sb.Append('\n');
|
|
||||||
first = false;
|
|
||||||
|
|
||||||
var adminData = adminMgr.GetAdminData(admin)!;
|
var adminData = adminMgr.GetAdminData(admin)!;
|
||||||
DebugTools.AssertNotNull(adminData);
|
DebugTools.AssertNotNull(adminData);
|
||||||
|
|
||||||
if (adminData.Stealth && !seeStealth)
|
if (adminData.Stealth && !seeStealth)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (!first)
|
||||||
|
sb.Append('\n');
|
||||||
|
first = false;
|
||||||
|
|
||||||
sb.Append(admin.Name);
|
sb.Append(admin.Name);
|
||||||
if (adminData.Title is { } title)
|
if (adminData.Title is { } title)
|
||||||
sb.Append($": [{title}]");
|
sb.Append($": [{title}]");
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
using Content.Shared.Administration;
|
|
||||||
using Content.Shared.CCVar;
|
|
||||||
using Robust.Shared.Configuration;
|
|
||||||
using Robust.Shared.Console;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Content.Server.Administration.Commands;
|
|
||||||
|
|
||||||
[AdminCommand(AdminFlags.Server)]
|
|
||||||
public sealed class BabyJailCommand : LocalizedCommands
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
||||||
|
|
||||||
public override string Command => "babyjail";
|
|
||||||
|
|
||||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
||||||
{
|
|
||||||
var toggle = Toggle(CCVars.BabyJailEnabled, shell, args, _cfg);
|
|
||||||
if (toggle == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
shell.WriteLine(Loc.GetString(toggle.Value ? "babyjail-command-enabled" : "babyjail-command-disabled"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool? Toggle(CVarDef<bool> cvar, IConsoleShell shell, string[] args, IConfigurationManager config)
|
|
||||||
{
|
|
||||||
if (args.Length > 1)
|
|
||||||
{
|
|
||||||
shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var enabled = config.GetCVar(cvar);
|
|
||||||
|
|
||||||
switch (args.Length)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
enabled = !enabled;
|
|
||||||
break;
|
|
||||||
case 1 when !bool.TryParse(args[0], out enabled):
|
|
||||||
shell.WriteError(Loc.GetString("shell-argument-must-be-boolean"));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.SetCVar(cvar, enabled);
|
|
||||||
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[AdminCommand(AdminFlags.Server)]
|
|
||||||
public sealed class BabyJailShowReasonCommand : LocalizedCommands
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
||||||
|
|
||||||
public override string Command => "babyjail_show_reason";
|
|
||||||
|
|
||||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
||||||
{
|
|
||||||
var toggle = BabyJailCommand.Toggle(CCVars.BabyJailShowReason, shell, args, _cfg);
|
|
||||||
if (toggle == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
shell.WriteLine(Loc.GetString(toggle.Value
|
|
||||||
? "babyjail-command-show-reason-enabled"
|
|
||||||
: "babyjail-command-show-reason-disabled"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[AdminCommand(AdminFlags.Server)]
|
|
||||||
public sealed class BabyJailMinAccountAgeCommand : LocalizedCommands
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
||||||
|
|
||||||
public override string Command => "babyjail_max_account_age";
|
|
||||||
|
|
||||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
||||||
{
|
|
||||||
switch (args.Length)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
{
|
|
||||||
var current = _cfg.GetCVar(CCVars.BabyJailMaxAccountAge);
|
|
||||||
shell.WriteLine(Loc.GetString("babyjail-command-max-account-age-is", ("minutes", current)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case > 1:
|
|
||||||
shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!int.TryParse(args[0], out var minutes))
|
|
||||||
{
|
|
||||||
shell.WriteError(Loc.GetString("shell-argument-must-be-number"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_cfg.SetCVar(CCVars.BabyJailMaxAccountAge, minutes);
|
|
||||||
shell.WriteLine(Loc.GetString("babyjail-command-max-account-age-set", ("minutes", minutes)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[AdminCommand(AdminFlags.Server)]
|
|
||||||
public sealed class BabyJailMinOverallHoursCommand : LocalizedCommands
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
||||||
|
|
||||||
public override string Command => "babyjail_max_overall_minutes";
|
|
||||||
|
|
||||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
||||||
{
|
|
||||||
switch (args.Length)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
{
|
|
||||||
var current = _cfg.GetCVar(CCVars.BabyJailMaxOverallMinutes);
|
|
||||||
shell.WriteLine(Loc.GetString("babyjail-command-max-overall-minutes-is", ("minutes", current)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case > 1:
|
|
||||||
shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!int.TryParse(args[0], out var hours))
|
|
||||||
{
|
|
||||||
shell.WriteError(Loc.GetString("shell-argument-must-be-number"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_cfg.SetCVar(CCVars.BabyJailMaxOverallMinutes, hours);
|
|
||||||
shell.WriteLine(Loc.GetString("babyjail-command-overall-minutes-set", ("hours", hours)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -91,14 +91,29 @@ namespace Content.Server.Administration.Managers
|
|||||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-de-admin-message", ("exAdminName", session.Name)));
|
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-de-admin-message", ("exAdminName", session.Name)));
|
||||||
_chat.DispatchServerMessage(session, Loc.GetString("admin-manager-became-normal-player-message"));
|
_chat.DispatchServerMessage(session, Loc.GetString("admin-manager-became-normal-player-message"));
|
||||||
|
|
||||||
var plyData = session.ContentData()!;
|
UpdateDatabaseDeadminnedState(session, true);
|
||||||
plyData.ExplicitlyDeadminned = true;
|
|
||||||
reg.Data.Active = false;
|
reg.Data.Active = false;
|
||||||
|
|
||||||
SendPermsChangedEvent(session);
|
SendPermsChangedEvent(session);
|
||||||
UpdateAdminStatus(session);
|
UpdateAdminStatus(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void UpdateDatabaseDeadminnedState(ICommonSession player, bool newState)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// NOTE: This function gets called if you deadmin/readmin from a transient admin status.
|
||||||
|
// (e.g. loginlocal)
|
||||||
|
// In which case there may not be a database record.
|
||||||
|
// The DB function handles this scenario fine, but it's worth noting.
|
||||||
|
await _dbManager.UpdateAdminDeadminnedAsync(player.UserId, newState);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_sawmill.Error("Failed to save deadmin state to database for {Admin}", player.UserId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Stealth(ICommonSession session)
|
public void Stealth(ICommonSession session)
|
||||||
{
|
{
|
||||||
if (!_admins.TryGetValue(session, out var reg))
|
if (!_admins.TryGetValue(session, out var reg))
|
||||||
@@ -151,8 +166,7 @@ namespace Content.Server.Administration.Managers
|
|||||||
|
|
||||||
_chat.DispatchServerMessage(session, Loc.GetString("admin-manager-became-admin-message"));
|
_chat.DispatchServerMessage(session, Loc.GetString("admin-manager-became-admin-message"));
|
||||||
|
|
||||||
var plyData = session.ContentData()!;
|
UpdateDatabaseDeadminnedState(session, false);
|
||||||
plyData.ExplicitlyDeadminned = false;
|
|
||||||
reg.Data.Active = true;
|
reg.Data.Active = true;
|
||||||
|
|
||||||
if (!reg.Data.Stealth)
|
if (!reg.Data.Stealth)
|
||||||
@@ -208,13 +222,13 @@ namespace Content.Server.Administration.Managers
|
|||||||
curAdmin.IsSpecialLogin = special;
|
curAdmin.IsSpecialLogin = special;
|
||||||
curAdmin.RankId = rankId;
|
curAdmin.RankId = rankId;
|
||||||
curAdmin.Data = aData;
|
curAdmin.Data = aData;
|
||||||
}
|
|
||||||
|
|
||||||
if (!player.ContentData()!.ExplicitlyDeadminned)
|
if (curAdmin.Data.Active)
|
||||||
{
|
{
|
||||||
aData.Active = true;
|
aData.Active = true;
|
||||||
|
|
||||||
_chat.DispatchServerMessage(player, Loc.GetString("admin-manager-admin-permissions-updated-message"));
|
_chat.DispatchServerMessage(player, Loc.GetString("admin-manager-admin-permissions-updated-message"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.ContentData()!.Stealthed)
|
if (player.ContentData()!.Stealthed)
|
||||||
@@ -381,10 +395,8 @@ namespace Content.Server.Administration.Managers
|
|||||||
if (session.ContentData()!.Stealthed)
|
if (session.ContentData()!.Stealthed)
|
||||||
reg.Data.Stealth = true;
|
reg.Data.Stealth = true;
|
||||||
|
|
||||||
if (!session.ContentData()!.ExplicitlyDeadminned)
|
if (reg.Data.Active)
|
||||||
{
|
{
|
||||||
reg.Data.Active = true;
|
|
||||||
|
|
||||||
if (_cfg.GetCVar(CCVars.AdminAnnounceLogin))
|
if (_cfg.GetCVar(CCVars.AdminAnnounceLogin))
|
||||||
{
|
{
|
||||||
if (reg.Data.Stealth)
|
if (reg.Data.Stealth)
|
||||||
@@ -430,6 +442,7 @@ namespace Content.Server.Administration.Managers
|
|||||||
{
|
{
|
||||||
Title = Loc.GetString("admin-manager-admin-data-host-title"),
|
Title = Loc.GetString("admin-manager-admin-data-host-title"),
|
||||||
Flags = AdminFlagsHelper.Everything,
|
Flags = AdminFlagsHelper.Everything,
|
||||||
|
Active = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (data, null, true);
|
return (data, null, true);
|
||||||
@@ -444,6 +457,12 @@ namespace Content.Server.Administration.Managers
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dbData.Suspended)
|
||||||
|
{
|
||||||
|
// Suspended admins don't count.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var flags = AdminFlags.None;
|
var flags = AdminFlags.None;
|
||||||
|
|
||||||
if (dbData.AdminRank != null)
|
if (dbData.AdminRank != null)
|
||||||
@@ -466,7 +485,8 @@ namespace Content.Server.Administration.Managers
|
|||||||
|
|
||||||
var data = new AdminData
|
var data = new AdminData
|
||||||
{
|
{
|
||||||
Flags = flags
|
Flags = flags,
|
||||||
|
Active = !dbData.Deadminned,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (dbData.Title != null && _cfg.GetCVar(CCVars.AdminUseCustomNamesAdminRank))
|
if (dbData.Title != null && _cfg.GetCVar(CCVars.AdminUseCustomNamesAdminRank))
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using Content.Server.Administration.Notes;
|
||||||
|
using Content.Server.Database;
|
||||||
|
using Content.Server.Discord;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Robust.Server;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Content.Server.Administration.Managers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This manager sends a webhook notification whenever a player with an active
|
||||||
|
/// watchlist joins the server.
|
||||||
|
/// </summary>
|
||||||
|
public interface IWatchlistWebhookManager
|
||||||
|
{
|
||||||
|
void Initialize();
|
||||||
|
void Update();
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
using Content.Server.Administration.Notes;
|
||||||
|
using Content.Server.Database;
|
||||||
|
using Content.Server.Discord;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Robust.Server;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Content.Server.Administration.Managers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This manager sends a Discord webhook notification whenever a player with an active
|
||||||
|
/// watchlist joins the server.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class WatchlistWebhookManager : IWatchlistWebhookManager
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IAdminNotesManager _adminNotes = default!;
|
||||||
|
[Dependency] private readonly IBaseServer _baseServer = default!;
|
||||||
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
|
[Dependency] private readonly DiscordWebhook _discord = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
|
||||||
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
|
private string _webhookUrl = default!;
|
||||||
|
private TimeSpan _bufferTime;
|
||||||
|
|
||||||
|
private List<WatchlistConnection> watchlistConnections = new();
|
||||||
|
private TimeSpan? _bufferStartTime;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_sawmill = Logger.GetSawmill("discord");
|
||||||
|
_cfg.OnValueChanged(CCVars.DiscordWatchlistConnectionBufferTime, SetBufferTime, true);
|
||||||
|
_cfg.OnValueChanged(CCVars.DiscordWatchlistConnectionWebhook, SetWebhookUrl, true);
|
||||||
|
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetBufferTime(float bufferTimeSeconds)
|
||||||
|
{
|
||||||
|
_bufferTime = TimeSpan.FromSeconds(bufferTimeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetWebhookUrl(string webhookUrl)
|
||||||
|
{
|
||||||
|
_webhookUrl = webhookUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.NewStatus != SessionStatus.Connected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var watchlists = await _adminNotes.GetActiveWatchlists(e.Session.UserId);
|
||||||
|
|
||||||
|
if (watchlists.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
watchlistConnections.Add(new WatchlistConnection(e.Session.Name, watchlists));
|
||||||
|
|
||||||
|
if (_bufferTime > TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
if (_bufferStartTime == null)
|
||||||
|
_bufferStartTime = _gameTiming.RealTime;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendDiscordMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (_bufferStartTime != null && _gameTiming.RealTime > (_bufferStartTime + _bufferTime))
|
||||||
|
{
|
||||||
|
SendDiscordMessage();
|
||||||
|
_bufferStartTime = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void SendDiscordMessage()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_webhookUrl))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var webhookData = await _discord.GetWebhook(_webhookUrl);
|
||||||
|
if (webhookData == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var webhookIdentifier = webhookData.Value.ToIdentifier();
|
||||||
|
|
||||||
|
var messageBuilder = new StringBuilder(Loc.GetString("discord-watchlist-connection-header",
|
||||||
|
("players", watchlistConnections.Count),
|
||||||
|
("serverName", _baseServer.ServerName)));
|
||||||
|
|
||||||
|
foreach (var connection in watchlistConnections)
|
||||||
|
{
|
||||||
|
messageBuilder.Append('\n');
|
||||||
|
|
||||||
|
var watchlist = connection.Watchlists.First();
|
||||||
|
var expiry = watchlist.ExpirationTime?.ToUnixTimeSeconds();
|
||||||
|
messageBuilder.Append(Loc.GetString("discord-watchlist-connection-entry",
|
||||||
|
("playerName", connection.PlayerName),
|
||||||
|
("message", watchlist.Message),
|
||||||
|
("expiry", expiry ?? 0),
|
||||||
|
("otherWatchlists", connection.Watchlists.Count - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new WebhookPayload { Content = messageBuilder.ToString() };
|
||||||
|
|
||||||
|
await _discord.CreateMessage(webhookIdentifier, payload);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_sawmill.Error($"Error while sending discord watchlist connection message:\n{e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the buffered list regardless of whether the message is sent successfully
|
||||||
|
// This prevents infinitely buffering connections if we fail to send a message
|
||||||
|
watchlistConnections.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class WatchlistConnection
|
||||||
|
{
|
||||||
|
public string PlayerName;
|
||||||
|
public List<AdminWatchlistRecord> Watchlists;
|
||||||
|
|
||||||
|
public WatchlistConnection(string playerName, List<AdminWatchlistRecord> watchlists)
|
||||||
|
{
|
||||||
|
PlayerName = playerName;
|
||||||
|
Watchlists = watchlists;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,6 @@ using Content.Shared.IdentityManagement;
|
|||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
using Content.Shared.PDA;
|
using Content.Shared.PDA;
|
||||||
using Content.Shared.Players;
|
|
||||||
using Content.Shared.Players.PlayTimeTracking;
|
using Content.Shared.Players.PlayTimeTracking;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
@@ -32,6 +31,7 @@ using Robust.Shared.Configuration;
|
|||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.Administration.Systems;
|
namespace Content.Server.Administration.Systems;
|
||||||
|
|
||||||
@@ -48,6 +48,7 @@ public sealed class AdminSystem : EntitySystem
|
|||||||
[Dependency] private readonly PopupSystem _popup = default!;
|
[Dependency] private readonly PopupSystem _popup = default!;
|
||||||
[Dependency] private readonly PhysicsSystem _physics = default!;
|
[Dependency] private readonly PhysicsSystem _physics = default!;
|
||||||
[Dependency] private readonly PlayTimeTrackingManager _playTime = default!;
|
[Dependency] private readonly PlayTimeTrackingManager _playTime = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
@@ -63,7 +64,6 @@ public sealed class AdminSystem : EntitySystem
|
|||||||
|
|
||||||
private readonly HashSet<NetUserId> _roundActivePlayers = new();
|
private readonly HashSet<NetUserId> _roundActivePlayers = new();
|
||||||
public readonly PanicBunkerStatus PanicBunker = new();
|
public readonly PanicBunkerStatus PanicBunker = new();
|
||||||
public readonly BabyJailStatus BabyJail = new();
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -82,16 +82,6 @@ public sealed class AdminSystem : EntitySystem
|
|||||||
Subs.CVar(_config, CCVars.PanicBunkerMinAccountAge, OnPanicBunkerMinAccountAgeChanged, true);
|
Subs.CVar(_config, CCVars.PanicBunkerMinAccountAge, OnPanicBunkerMinAccountAgeChanged, true);
|
||||||
Subs.CVar(_config, CCVars.PanicBunkerMinOverallMinutes, OnPanicBunkerMinOverallMinutesChanged, true);
|
Subs.CVar(_config, CCVars.PanicBunkerMinOverallMinutes, OnPanicBunkerMinOverallMinutesChanged, true);
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Baby Jail Settings
|
|
||||||
Subs.CVar(_config, CCVars.BabyJailEnabled, OnBabyJailChanged, true);
|
|
||||||
Subs.CVar(_config, CCVars.BabyJailShowReason, OnBabyJailShowReasonChanged, true);
|
|
||||||
Subs.CVar(_config, CCVars.BabyJailMaxAccountAge, OnBabyJailMaxAccountAgeChanged, true);
|
|
||||||
Subs.CVar(_config, CCVars.BabyJailMaxOverallMinutes, OnBabyJailMaxOverallMinutesChanged, true);
|
|
||||||
|
|
||||||
SubscribeLocalEvent<IdentityChangedEvent>(OnIdentityChanged);
|
SubscribeLocalEvent<IdentityChangedEvent>(OnIdentityChanged);
|
||||||
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
|
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
|
||||||
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
|
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
|
||||||
@@ -165,7 +155,8 @@ public sealed class AdminSystem : EntitySystem
|
|||||||
private void OnRoleEvent(RoleEvent ev)
|
private void OnRoleEvent(RoleEvent ev)
|
||||||
{
|
{
|
||||||
var session = _minds.GetSession(ev.Mind);
|
var session = _minds.GetSession(ev.Mind);
|
||||||
if (!ev.Antagonist || session == null)
|
|
||||||
|
if (!ev.RoleTypeUpdate || session == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
UpdatePlayerList(session);
|
UpdatePlayerList(session);
|
||||||
@@ -239,9 +230,16 @@ public sealed class AdminSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
var antag = false;
|
var antag = false;
|
||||||
|
|
||||||
|
RoleTypePrototype roleType = new();
|
||||||
var startingRole = string.Empty;
|
var startingRole = string.Empty;
|
||||||
if (_minds.TryGetMind(session, out var mindId, out _))
|
if (_minds.TryGetMind(session, out var mindId, out var mindComp))
|
||||||
{
|
{
|
||||||
|
if (_proto.TryIndex(mindComp.RoleType, out var role))
|
||||||
|
roleType = role;
|
||||||
|
else
|
||||||
|
Log.Error($"{ToPrettyString(mindId)} has invalid Role Type '{mindComp.RoleType}'. Displaying '{Loc.GetString(roleType.Name)}' instead");
|
||||||
|
|
||||||
antag = _role.MindIsAntagonist(mindId);
|
antag = _role.MindIsAntagonist(mindId);
|
||||||
startingRole = _jobs.MindTryGetJobName(mindId);
|
startingRole = _jobs.MindTryGetJobName(mindId);
|
||||||
}
|
}
|
||||||
@@ -255,7 +253,7 @@ public sealed class AdminSystem : EntitySystem
|
|||||||
overallPlaytime = playTime;
|
overallPlaytime = playTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PlayerInfo(name, entityName, identityName, startingRole, antag, GetNetEntity(session?.AttachedEntity), data.UserId,
|
return new PlayerInfo(name, entityName, identityName, startingRole, antag, roleType, GetNetEntity(session?.AttachedEntity), data.UserId,
|
||||||
connected, _roundActivePlayers.Contains(data.UserId), overallPlaytime);
|
connected, _roundActivePlayers.Contains(data.UserId), overallPlaytime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,17 +268,6 @@ public sealed class AdminSystem : EntitySystem
|
|||||||
SendPanicBunkerStatusAll();
|
SendPanicBunkerStatusAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBabyJailChanged(bool enabled)
|
|
||||||
{
|
|
||||||
BabyJail.Enabled = enabled;
|
|
||||||
_chat.SendAdminAlert(Loc.GetString(enabled
|
|
||||||
? "admin-ui-baby-jail-enabled-admin-alert"
|
|
||||||
: "admin-ui-baby-jail-disabled-admin-alert"
|
|
||||||
));
|
|
||||||
|
|
||||||
SendBabyJailStatusAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPanicBunkerDisableWithAdminsChanged(bool enabled)
|
private void OnPanicBunkerDisableWithAdminsChanged(bool enabled)
|
||||||
{
|
{
|
||||||
PanicBunker.DisableWithAdmins = enabled;
|
PanicBunker.DisableWithAdmins = enabled;
|
||||||
@@ -305,36 +292,18 @@ public sealed class AdminSystem : EntitySystem
|
|||||||
SendPanicBunkerStatusAll();
|
SendPanicBunkerStatusAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBabyJailShowReasonChanged(bool enabled)
|
|
||||||
{
|
|
||||||
BabyJail.ShowReason = enabled;
|
|
||||||
SendBabyJailStatusAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPanicBunkerMinAccountAgeChanged(int minutes)
|
private void OnPanicBunkerMinAccountAgeChanged(int minutes)
|
||||||
{
|
{
|
||||||
PanicBunker.MinAccountAgeMinutes = minutes;
|
PanicBunker.MinAccountAgeMinutes = minutes;
|
||||||
SendPanicBunkerStatusAll();
|
SendPanicBunkerStatusAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBabyJailMaxAccountAgeChanged(int minutes)
|
|
||||||
{
|
|
||||||
BabyJail.MaxAccountAgeMinutes = minutes;
|
|
||||||
SendBabyJailStatusAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPanicBunkerMinOverallMinutesChanged(int minutes)
|
private void OnPanicBunkerMinOverallMinutesChanged(int minutes)
|
||||||
{
|
{
|
||||||
PanicBunker.MinOverallMinutes = minutes;
|
PanicBunker.MinOverallMinutes = minutes;
|
||||||
SendPanicBunkerStatusAll();
|
SendPanicBunkerStatusAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBabyJailMaxOverallMinutesChanged(int minutes)
|
|
||||||
{
|
|
||||||
BabyJail.MaxOverallMinutes = minutes;
|
|
||||||
SendBabyJailStatusAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdatePanicBunker()
|
private void UpdatePanicBunker()
|
||||||
{
|
{
|
||||||
var hasAdmins = false;
|
var hasAdmins = false;
|
||||||
@@ -381,15 +350,6 @@ public sealed class AdminSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendBabyJailStatusAll()
|
|
||||||
{
|
|
||||||
var ev = new BabyJailChangedEvent(BabyJail);
|
|
||||||
foreach (var admin in _adminManager.AllAdmins)
|
|
||||||
{
|
|
||||||
RaiseNetworkEvent(ev, admin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Erases a player from the round.
|
/// Erases a player from the round.
|
||||||
/// This removes them and any trace of them from the round, deleting their
|
/// This removes them and any trace of them from the round, deleting their
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ namespace Content.Server.Administration.UI
|
|||||||
Title = p.a.Title,
|
Title = p.a.Title,
|
||||||
RankId = p.a.AdminRankId,
|
RankId = p.a.AdminRankId,
|
||||||
UserId = new NetUserId(p.a.UserId),
|
UserId = new NetUserId(p.a.UserId),
|
||||||
UserName = p.lastUserName
|
UserName = p.lastUserName,
|
||||||
|
Suspended = p.a.Suspended,
|
||||||
}).ToArray(),
|
}).ToArray(),
|
||||||
|
|
||||||
AdminRanks = _adminRanks.ToDictionary(a => a.Id, a => new PermissionsEuiState.AdminRankData
|
AdminRanks = _adminRanks.ToDictionary(a => a.Id, a => new PermissionsEuiState.AdminRankData
|
||||||
@@ -255,6 +256,7 @@ namespace Content.Server.Administration.UI
|
|||||||
admin.Title = ua.Title;
|
admin.Title = ua.Title;
|
||||||
admin.AdminRankId = ua.RankId;
|
admin.AdminRankId = ua.RankId;
|
||||||
admin.Flags = GenAdminFlagList(ua.PosFlags, ua.NegFlags);
|
admin.Flags = GenAdminFlagList(ua.PosFlags, ua.NegFlags);
|
||||||
|
admin.Suspended = ua.Suspended;
|
||||||
|
|
||||||
await _db.UpdateAdminAsync(admin);
|
await _db.UpdateAdminAsync(admin);
|
||||||
|
|
||||||
@@ -335,7 +337,8 @@ namespace Content.Server.Administration.UI
|
|||||||
Flags = GenAdminFlagList(ca.PosFlags, ca.NegFlags),
|
Flags = GenAdminFlagList(ca.PosFlags, ca.NegFlags),
|
||||||
AdminRankId = ca.RankId,
|
AdminRankId = ca.RankId,
|
||||||
UserId = userId.UserId,
|
UserId = userId.UserId,
|
||||||
Title = ca.Title
|
Title = ca.Title,
|
||||||
|
Suspended = ca.Suspended,
|
||||||
};
|
};
|
||||||
|
|
||||||
await _db.AddAdminAsync(admin);
|
await _db.AddAdminAsync(admin);
|
||||||
|
|||||||
@@ -97,6 +97,6 @@ public sealed partial class ReagentProducerAnomalyComponent : Component
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Solution where the substance is generated
|
/// Solution where the substance is generated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("solutionRef")]
|
[ViewVariables]
|
||||||
public Entity<SolutionComponent>? Solution = null;
|
public Entity<SolutionComponent>? Solution = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ public partial struct AntagSelectionDefinition()
|
|||||||
/// List of Mind Role Prototypes to be added to the player's mind.
|
/// List of Mind Role Prototypes to be added to the player's mind.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public List<ProtoId<EntityPrototype>>? MindRoles;
|
public List<EntProtoId>? MindRoles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A set of starting gear that's equipped to the player.
|
/// A set of starting gear that's equipped to the player.
|
||||||
|
|||||||
@@ -131,6 +131,19 @@ public sealed class AirAlarmSystem : EntitySystem
|
|||||||
SyncDevice(uid, address);
|
SyncDevice(uid, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetAllThresholds(EntityUid uid, string address, AtmosSensorData data)
|
||||||
|
{
|
||||||
|
var payload = new NetworkPayload
|
||||||
|
{
|
||||||
|
[DeviceNetworkConstants.Command] = AtmosMonitorSystem.AtmosMonitorSetAllThresholdsCmd,
|
||||||
|
[AtmosMonitorSystem.AtmosMonitorAllThresholdData] = data
|
||||||
|
};
|
||||||
|
|
||||||
|
_deviceNet.QueuePacket(uid, address, payload);
|
||||||
|
|
||||||
|
SyncDevice(uid, address);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sync this air alarm's mode with the rest of the network.
|
/// Sync this air alarm's mode with the rest of the network.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -341,6 +354,13 @@ public sealed class AirAlarmSystem : EntitySystem
|
|||||||
SetData(uid, addr, args.Data);
|
SetData(uid, addr, args.Data);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case AtmosSensorData sensorData:
|
||||||
|
foreach (string addr in component.SensorData.Keys)
|
||||||
|
{
|
||||||
|
SetAllThresholds(uid, addr, sensorData);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,10 +33,11 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
|||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
|
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
|
||||||
|
public const string AtmosMonitorSetAllThresholdsCmd = "atmos_monitor_set_all_thresholds";
|
||||||
|
|
||||||
// Packet data
|
// Packet data
|
||||||
public const string AtmosMonitorThresholdData = "atmos_monitor_threshold_data";
|
public const string AtmosMonitorThresholdData = "atmos_monitor_threshold_data";
|
||||||
|
public const string AtmosMonitorAllThresholdData = "atmos_monitor_all_threshold_data";
|
||||||
public const string AtmosMonitorThresholdDataType = "atmos_monitor_threshold_type";
|
public const string AtmosMonitorThresholdDataType = "atmos_monitor_threshold_type";
|
||||||
|
|
||||||
public const string AtmosMonitorThresholdGasType = "atmos_monitor_threshold_gas";
|
public const string AtmosMonitorThresholdGasType = "atmos_monitor_threshold_gas";
|
||||||
@@ -138,7 +139,12 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
|||||||
args.Data.TryGetValue(AtmosMonitorThresholdGasType, out Gas? gas);
|
args.Data.TryGetValue(AtmosMonitorThresholdGasType, out Gas? gas);
|
||||||
SetThreshold(uid, thresholdType.Value, thresholdData, gas);
|
SetThreshold(uid, thresholdType.Value, thresholdData, gas);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case AtmosMonitorSetAllThresholdsCmd:
|
||||||
|
if (args.Data.TryGetValue(AtmosMonitorAllThresholdData, out AtmosSensorData? allThresholdData))
|
||||||
|
{
|
||||||
|
SetAllThresholds(uid, allThresholdData);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case AtmosDeviceNetworkSystem.SyncData:
|
case AtmosDeviceNetworkSystem.SyncData:
|
||||||
var payload = new NetworkPayload();
|
var payload = new NetworkPayload();
|
||||||
@@ -403,4 +409,20 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets all of a monitor's thresholds at once according to the incoming
|
||||||
|
/// AtmosSensorData object's thresholds.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid">The entity's uid</param>
|
||||||
|
/// <param name="allThresholdData">An AtmosSensorData object from which the thresholds will be loaded.</param>
|
||||||
|
public void SetAllThresholds(EntityUid uid, AtmosSensorData allThresholdData)
|
||||||
|
{
|
||||||
|
SetThreshold(uid, AtmosMonitorThresholdType.Temperature, allThresholdData.TemperatureThreshold);
|
||||||
|
SetThreshold(uid, AtmosMonitorThresholdType.Pressure, allThresholdData.PressureThreshold);
|
||||||
|
foreach (var gas in Enum.GetValues<Gas>())
|
||||||
|
{
|
||||||
|
SetThreshold(uid, AtmosMonitorThresholdType.Gas, allThresholdData.GasThresholds[gas], gas);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public sealed partial class GasCondenserComponent : Component
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The solution that gases are condensed into.
|
/// The solution that gases are condensed into.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[ViewVariables]
|
||||||
public Entity<SolutionComponent>? Solution = null;
|
public Entity<SolutionComponent>? Solution = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -157,22 +157,22 @@ namespace Content.Server.Body.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal solution for blood storage
|
/// Internal solution for blood storage
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[ViewVariables]
|
||||||
public Entity<SolutionComponent>? BloodSolution = null;
|
public Entity<SolutionComponent>? BloodSolution;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal solution for reagent storage
|
/// Internal solution for reagent storage
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[ViewVariables]
|
||||||
public Entity<SolutionComponent>? ChemicalSolution = null;
|
public Entity<SolutionComponent>? ChemicalSolution;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Temporary blood solution.
|
/// Temporary blood solution.
|
||||||
/// When blood is lost, it goes to this solution, and when this
|
/// When blood is lost, it goes to this solution, and when this
|
||||||
/// solution hits a certain cap, the blood is actually spilled as a puddle.
|
/// solution hits a certain cap, the blood is actually spilled as a puddle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[ViewVariables]
|
||||||
public Entity<SolutionComponent>? TemporarySolution = null;
|
public Entity<SolutionComponent>? TemporarySolution;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Variable that stores the amount of status time added by having a low blood level.
|
/// Variable that stores the amount of status time added by having a low blood level.
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public sealed partial class LungComponent : Component
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The solution on this entity that these lungs act on.
|
/// The solution on this entity that these lungs act on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[ViewVariables]
|
||||||
public Entity<SolutionComponent>? Solution = null;
|
public Entity<SolutionComponent>? Solution = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ namespace Content.Server.Body.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The solution inside of this stomach this transfers reagents to the body.
|
/// The solution inside of this stomach this transfers reagents to the body.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[ViewVariables]
|
||||||
public Entity<SolutionComponent>? Solution = null;
|
public Entity<SolutionComponent>? Solution;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// What solution should this stomach push reagents into, on the body?
|
/// What solution should this stomach push reagents into, on the body?
|
||||||
|
|||||||
@@ -96,6 +96,6 @@ public sealed partial class PlantHolderComponent : Component
|
|||||||
[DataField]
|
[DataField]
|
||||||
public string SoilSolutionName = "soil";
|
public string SoilSolutionName = "soil";
|
||||||
|
|
||||||
[DataField]
|
[ViewVariables]
|
||||||
public Entity<SolutionComponent>? SoilSolution = null;
|
public Entity<SolutionComponent>? SoilSolution = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
|
using Content.Shared.Cargo.Components;
|
||||||
using Content.Shared.IdentityManagement;
|
using Content.Shared.IdentityManagement;
|
||||||
using Content.Shared.Timing;
|
using Content.Shared.Timing;
|
||||||
using Content.Shared.Cargo.Systems;
|
using Content.Shared.Cargo.Systems;
|
||||||
|
using Robust.Shared.Audio.Systems;
|
||||||
|
|
||||||
namespace Content.Server.Cargo.Systems;
|
namespace Content.Server.Cargo.Systems;
|
||||||
|
|
||||||
@@ -11,12 +13,12 @@ public sealed class PriceGunSystem : SharedPriceGunSystem
|
|||||||
[Dependency] private readonly PricingSystem _pricingSystem = default!;
|
[Dependency] private readonly PricingSystem _pricingSystem = default!;
|
||||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||||
[Dependency] private readonly CargoSystem _bountySystem = default!;
|
[Dependency] private readonly CargoSystem _bountySystem = default!;
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
|
||||||
protected override bool GetPriceOrBounty(EntityUid priceGunUid, EntityUid target, EntityUid user)
|
protected override bool GetPriceOrBounty(Entity<PriceGunComponent> entity, EntityUid target, EntityUid user)
|
||||||
{
|
{
|
||||||
if (!TryComp(priceGunUid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((priceGunUid, useDelay)))
|
if (!TryComp(entity.Owner, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((entity.Owner, useDelay)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Check if we're scanning a bounty crate
|
// Check if we're scanning a bounty crate
|
||||||
if (_bountySystem.IsBountyComplete(target, out _))
|
if (_bountySystem.IsBountyComplete(target, out _))
|
||||||
{
|
{
|
||||||
@@ -25,10 +27,15 @@ public sealed class PriceGunSystem : SharedPriceGunSystem
|
|||||||
else // Otherwise appraise the price
|
else // Otherwise appraise the price
|
||||||
{
|
{
|
||||||
var price = _pricingSystem.GetPrice(target);
|
var price = _pricingSystem.GetPrice(target);
|
||||||
_popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result", ("object", Identity.Entity(target, EntityManager)), ("price", $"{price:F2}")), user, user);
|
_popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result",
|
||||||
|
("object", Identity.Entity(target, EntityManager)),
|
||||||
|
("price", $"{price:F2}")),
|
||||||
|
user,
|
||||||
|
user);
|
||||||
}
|
}
|
||||||
|
|
||||||
_useDelay.TryResetDelay((priceGunUid, useDelay));
|
_audio.PlayPvs(entity.Comp.AppraisalSound, entity.Owner);
|
||||||
|
_useDelay.TryResetDelay((entity.Owner, useDelay));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Shared.CartridgeLoader;
|
using Content.Shared.CartridgeLoader;
|
||||||
using Content.Shared.CartridgeLoader.Cartridges;
|
using Content.Shared.CartridgeLoader.Cartridges;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
|
||||||
namespace Content.Server.CartridgeLoader.Cartridges;
|
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
public sealed class NotekeeperCartridgeSystem : EntitySystem
|
public sealed class NotekeeperCartridgeSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly CartridgeLoaderSystem? _cartridgeLoaderSystem = default!;
|
[Dependency] private readonly CartridgeLoaderSystem? _cartridgeLoaderSystem = default!;
|
||||||
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -36,16 +39,19 @@ public sealed class NotekeeperCartridgeSystem : EntitySystem
|
|||||||
if (message.Action == NotekeeperUiAction.Add)
|
if (message.Action == NotekeeperUiAction.Add)
|
||||||
{
|
{
|
||||||
component.Notes.Add(message.Note);
|
component.Notes.Add(message.Note);
|
||||||
|
_adminLogger.Add(LogType.PdaInteract, LogImpact.Low,
|
||||||
|
$"{ToPrettyString(args.Actor)} added a note to PDA: '{message.Note}' contained on: {ToPrettyString(uid)}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
component.Notes.Remove(message.Note);
|
component.Notes.Remove(message.Note);
|
||||||
|
_adminLogger.Add(LogType.PdaInteract, LogImpact.Low,
|
||||||
|
$"{ToPrettyString(args.Actor)} removed a note from PDA: '{message.Note}' was contained on: {ToPrettyString(uid)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateUiState(uid, GetEntity(args.LoaderUid), component);
|
UpdateUiState(uid, GetEntity(args.LoaderUid), component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void UpdateUiState(EntityUid uid, EntityUid loaderUid, NotekeeperCartridgeComponent? component)
|
private void UpdateUiState(EntityUid uid, EntityUid loaderUid, NotekeeperCartridgeComponent? component)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component))
|
if (!Resolve(uid, ref component))
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public sealed partial class SolutionRegenerationComponent : Component
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The solution to add reagents to.
|
/// The solution to add reagents to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[ViewVariables]
|
||||||
public Entity<SolutionComponent>? SolutionRef = null;
|
public Entity<SolutionComponent>? SolutionRef = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.IdentityManagement;
|
using Content.Server.IdentityManagement;
|
||||||
using Content.Shared.Clothing.Components;
|
using Content.Shared.Clothing.Components;
|
||||||
using Content.Shared.Clothing.EntitySystems;
|
using Content.Shared.Clothing.EntitySystems;
|
||||||
using Content.Shared.IdentityManagement.Components;
|
using Content.Shared.IdentityManagement.Components;
|
||||||
@@ -63,7 +63,7 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
|||||||
if (!Resolve(uid, ref component))
|
if (!Resolve(uid, ref component))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var state = new ChameleonBoundUserInterfaceState(component.Slot, component.Default);
|
var state = new ChameleonBoundUserInterfaceState(component.Slot, component.Default, component.RequireTag);
|
||||||
_uiSystem.SetUiState(uid, ChameleonUiKey.Key, state);
|
_uiSystem.SetUiState(uid, ChameleonUiKey.Key, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
|||||||
// make sure that it is valid change
|
// make sure that it is valid change
|
||||||
if (string.IsNullOrEmpty(protoId) || !_proto.TryIndex(protoId, out EntityPrototype? proto))
|
if (string.IsNullOrEmpty(protoId) || !_proto.TryIndex(protoId, out EntityPrototype? proto))
|
||||||
return;
|
return;
|
||||||
if (!IsValidTarget(proto, component.Slot))
|
if (!IsValidTarget(proto, component.Slot, component.RequireTag))
|
||||||
return;
|
return;
|
||||||
component.Default = protoId;
|
component.Default = protoId;
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public sealed partial class ConnectionManager
|
|||||||
{
|
{
|
||||||
private PlayerConnectionWhitelistPrototype[]? _whitelists;
|
private PlayerConnectionWhitelistPrototype[]? _whitelists;
|
||||||
|
|
||||||
public void PostInit()
|
private void InitializeWhitelist()
|
||||||
{
|
{
|
||||||
_cfg.OnValueChanged(CCVars.WhitelistPrototypeList, UpdateWhitelists, true);
|
_cfg.OnValueChanged(CCVars.WhitelistPrototypeList, UpdateWhitelists, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Content.Server.Administration.Managers;
|
using Content.Server.Administration.Managers;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
|
using Content.Server.Connection.IPIntel;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.Preferences.Managers;
|
using Content.Server.Preferences.Managers;
|
||||||
@@ -40,6 +41,8 @@ namespace Content.Server.Connection
|
|||||||
/// <param name="user">The user to give a temporary bypass.</param>
|
/// <param name="user">The user to give a temporary bypass.</param>
|
||||||
/// <param name="duration">How long the bypass should last for.</param>
|
/// <param name="duration">How long the bypass should last for.</param>
|
||||||
void AddTemporaryConnectBypass(NetUserId user, TimeSpan duration);
|
void AddTemporaryConnectBypass(NetUserId user, TimeSpan duration);
|
||||||
|
|
||||||
|
void Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -57,16 +60,24 @@ namespace Content.Server.Connection
|
|||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly ILogManager _logManager = default!;
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||||
|
[Dependency] private readonly IHttpClientHolder _http = default!;
|
||||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
private readonly Dictionary<NetUserId, TimeSpan> _temporaryBypasses = [];
|
private readonly Dictionary<NetUserId, TimeSpan> _temporaryBypasses = [];
|
||||||
|
private IPIntel.IPIntel _ipintel = default!;
|
||||||
|
|
||||||
|
public void PostInit()
|
||||||
|
{
|
||||||
|
InitializeWhitelist();
|
||||||
|
}
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
_sawmill = _logManager.GetSawmill("connections");
|
_sawmill = _logManager.GetSawmill("connections");
|
||||||
|
|
||||||
|
_ipintel = new IPIntel.IPIntel(new IPIntelApi(_http, _cfg), _db, _cfg, _logManager, _chatManager, _gameTiming);
|
||||||
|
|
||||||
_netMgr.Connecting += NetMgrOnConnecting;
|
_netMgr.Connecting += NetMgrOnConnecting;
|
||||||
_netMgr.AssignUserIdCallback = AssignUserIdCallback;
|
_netMgr.AssignUserIdCallback = AssignUserIdCallback;
|
||||||
_plyMgr.PlayerStatusChanged += PlayerStatusChanged;
|
_plyMgr.PlayerStatusChanged += PlayerStatusChanged;
|
||||||
@@ -83,6 +94,18 @@ namespace Content.Server.Connection
|
|||||||
time = newTime;
|
time = newTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async void Update()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _ipintel.Update();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_sawmill.Error("IPIntel update failed:" + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
private async Task<NetApproval> HandleApproval(NetApprovalEventArgs eventArgs)
|
private async Task<NetApproval> HandleApproval(NetApprovalEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
@@ -260,14 +283,6 @@ namespace Content.Server.Connection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_cfg.GetCVar(CCVars.BabyJailEnabled) && adminData == null)
|
|
||||||
{
|
|
||||||
var result = await IsInvalidConnectionDueToBabyJail(userId, e);
|
|
||||||
|
|
||||||
if (result.IsInvalid)
|
|
||||||
return (ConnectionDenyReason.BabyJail, result.Reason, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
|
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
|
||||||
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
|
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
|
||||||
status == PlayerGameStatus.JoinedGame;
|
status == PlayerGameStatus.JoinedGame;
|
||||||
@@ -291,7 +306,7 @@ namespace Content.Server.Connection
|
|||||||
{
|
{
|
||||||
_sawmill.Error("Whitelist enabled but no whitelists loaded.");
|
_sawmill.Error("Whitelist enabled but no whitelists loaded.");
|
||||||
// Misconfigured, deny everyone.
|
// Misconfigured, deny everyone.
|
||||||
return (ConnectionDenyReason.Whitelist, Loc.GetString("whitelist-misconfigured"), null);
|
return (ConnectionDenyReason.Whitelist, Loc.GetString("generic-misconfigured"), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var whitelist in _whitelists)
|
foreach (var whitelist in _whitelists)
|
||||||
@@ -314,75 +329,18 @@ namespace Content.Server.Connection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ALWAYS keep this at the end, to preserve the API limit.
|
||||||
|
if (_cfg.GetCVar(CCVars.GameIPIntelEnabled) && adminData == null)
|
||||||
|
{
|
||||||
|
var result = await _ipintel.IsVpnOrProxy(e);
|
||||||
|
|
||||||
|
if (result.IsBad)
|
||||||
|
return (ConnectionDenyReason.IPChecks, result.Reason, null);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(bool IsInvalid, string Reason)> IsInvalidConnectionDueToBabyJail(NetUserId userId, NetConnectingArgs e)
|
|
||||||
{
|
|
||||||
// If you're whitelisted then bypass this whole thing
|
|
||||||
if (await _db.GetWhitelistStatusAsync(userId))
|
|
||||||
return (false, "");
|
|
||||||
|
|
||||||
// Initial cvar retrieval
|
|
||||||
var showReason = _cfg.GetCVar(CCVars.BabyJailShowReason);
|
|
||||||
var reason = _cfg.GetCVar(CCVars.BabyJailCustomReason);
|
|
||||||
var maxAccountAgeMinutes = _cfg.GetCVar(CCVars.BabyJailMaxAccountAge);
|
|
||||||
var maxPlaytimeMinutes = _cfg.GetCVar(CCVars.BabyJailMaxOverallMinutes);
|
|
||||||
|
|
||||||
// Wait some time to lookup data
|
|
||||||
var record = await _db.GetPlayerRecordByUserId(userId);
|
|
||||||
|
|
||||||
// No player record = new account or the DB is having a skill issue
|
|
||||||
if (record == null)
|
|
||||||
return (false, "");
|
|
||||||
|
|
||||||
var isAccountAgeInvalid = record.FirstSeenTime.CompareTo(DateTimeOffset.UtcNow - TimeSpan.FromMinutes(maxAccountAgeMinutes)) <= 0;
|
|
||||||
|
|
||||||
if (isAccountAgeInvalid)
|
|
||||||
{
|
|
||||||
_sawmill.Debug($"Baby jail will deny {userId} for account age {record.FirstSeenTime}"); // Remove on or after 2024-09
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAccountAgeInvalid && showReason)
|
|
||||||
{
|
|
||||||
var locAccountReason = reason != string.Empty
|
|
||||||
? reason
|
|
||||||
: Loc.GetString("baby-jail-account-denied-reason",
|
|
||||||
("reason",
|
|
||||||
Loc.GetString(
|
|
||||||
"baby-jail-account-reason-account",
|
|
||||||
("minutes", maxAccountAgeMinutes))));
|
|
||||||
|
|
||||||
return (true, locAccountReason);
|
|
||||||
}
|
|
||||||
|
|
||||||
var overallTime = ( await _db.GetPlayTimes(e.UserId)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
|
|
||||||
var isTotalPlaytimeInvalid = overallTime != null && overallTime.TimeSpent.TotalMinutes >= maxPlaytimeMinutes;
|
|
||||||
|
|
||||||
if (isTotalPlaytimeInvalid)
|
|
||||||
{
|
|
||||||
_sawmill.Debug($"Baby jail will deny {userId} for playtime {overallTime!.TimeSpent}"); // Remove on or after 2024-09
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTotalPlaytimeInvalid && showReason)
|
|
||||||
{
|
|
||||||
var locPlaytimeReason = reason != string.Empty
|
|
||||||
? reason
|
|
||||||
: Loc.GetString("baby-jail-account-denied-reason",
|
|
||||||
("reason",
|
|
||||||
Loc.GetString(
|
|
||||||
"baby-jail-account-reason-overall",
|
|
||||||
("minutes", maxPlaytimeMinutes))));
|
|
||||||
|
|
||||||
return (true, locPlaytimeReason);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!showReason && isTotalPlaytimeInvalid || isAccountAgeInvalid)
|
|
||||||
return (true, Loc.GetString("baby-jail-account-denied"));
|
|
||||||
|
|
||||||
return (false, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HasTemporaryBypass(NetUserId user)
|
private bool HasTemporaryBypass(NetUserId user)
|
||||||
{
|
{
|
||||||
return _temporaryBypasses.TryGetValue(user, out var time) && time > _gameTiming.RealTime;
|
return _temporaryBypasses.TryGetValue(user, out var time) && time > _gameTiming.RealTime;
|
||||||
|
|||||||
387
Content.Server/Connection/IPIntel/IPIntel.cs
Normal file
387
Content.Server/Connection/IPIntel/IPIntel.cs
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Chat.Managers;
|
||||||
|
using Content.Server.Database;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Players.PlayTimeTracking;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection.IPIntel;
|
||||||
|
|
||||||
|
// Handles checking/warning if the connecting IP address is sus.
|
||||||
|
public sealed class IPIntel
|
||||||
|
{
|
||||||
|
private readonly IIPIntelApi _api;
|
||||||
|
private readonly IServerDbManager _db;
|
||||||
|
private readonly IChatManager _chatManager;
|
||||||
|
private readonly IGameTiming _gameTiming;
|
||||||
|
|
||||||
|
private readonly ISawmill _sawmill;
|
||||||
|
|
||||||
|
public IPIntel(IIPIntelApi api,
|
||||||
|
IServerDbManager db,
|
||||||
|
IConfigurationManager cfg,
|
||||||
|
ILogManager logManager,
|
||||||
|
IChatManager chatManager,
|
||||||
|
IGameTiming gameTiming)
|
||||||
|
{
|
||||||
|
_api = api;
|
||||||
|
_db = db;
|
||||||
|
_chatManager = chatManager;
|
||||||
|
_gameTiming = gameTiming;
|
||||||
|
|
||||||
|
_sawmill = logManager.GetSawmill("ipintel");
|
||||||
|
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelEmail, b => _contactEmail = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelEnabled, b => _enabled = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelRejectUnknown, b => _rejectUnknown = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelRejectBad, b => _rejectBad = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelRejectRateLimited, b => _rejectLimited = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelMaxMinute, b => _minute.Limit = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelMaxDay, b => _day.Limit = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelBackOffSeconds, b => _backoffSeconds = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelCleanupMins, b => _cleanupMins = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelBadRating, b => _rating = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelCacheLength, b => _cacheDays = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelExemptPlaytime, b => _exemptPlaytime = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelAlertAdminReject, b => _alertAdminReject = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelAlertAdminWarnRating, b => _alertAdminWarn = b, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct Ratelimits
|
||||||
|
{
|
||||||
|
public bool RateLimited;
|
||||||
|
public bool LimitHasBeenHandled;
|
||||||
|
public int CurrentRequests;
|
||||||
|
public int Limit;
|
||||||
|
public TimeSpan LastRatelimited;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Self-managed preemptive rate limits.
|
||||||
|
private Ratelimits _day;
|
||||||
|
private Ratelimits _minute;
|
||||||
|
|
||||||
|
// Next time we need to clean the database of stale cached IPIntel results.
|
||||||
|
private TimeSpan _nextClean;
|
||||||
|
|
||||||
|
// Responsive backoff if we hit a Too Many Requests API error.
|
||||||
|
private int _failedRequests;
|
||||||
|
private TimeSpan _releasePeriod;
|
||||||
|
|
||||||
|
// CCVars
|
||||||
|
private string? _contactEmail;
|
||||||
|
private bool _enabled;
|
||||||
|
private bool _rejectUnknown;
|
||||||
|
private bool _rejectBad;
|
||||||
|
private bool _rejectLimited;
|
||||||
|
private bool _alertAdminReject;
|
||||||
|
private int _backoffSeconds;
|
||||||
|
private int _cleanupMins;
|
||||||
|
private TimeSpan _cacheDays;
|
||||||
|
private TimeSpan _exemptPlaytime;
|
||||||
|
private float _rating;
|
||||||
|
private float _alertAdminWarn;
|
||||||
|
|
||||||
|
public async Task<(bool IsBad, string Reason)> IsVpnOrProxy(NetConnectingArgs e)
|
||||||
|
{
|
||||||
|
// Check Exemption flags, let them skip if they have them.
|
||||||
|
var flags = await _db.GetBanExemption(e.UserId);
|
||||||
|
if ((flags & (ServerBanExemptFlags.Datacenter | ServerBanExemptFlags.BlacklistedRange)) != 0)
|
||||||
|
{
|
||||||
|
return (false, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check playtime, if 0 we skip this check. If player has more playtime then _exemptPlaytime is configured for then they get to skip this check.
|
||||||
|
// Helps with saving your limited request limit.
|
||||||
|
if (_exemptPlaytime != TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
var overallTime = ( await _db.GetPlayTimes(e.UserId)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
|
||||||
|
if (overallTime != null && overallTime.TimeSpent >= _exemptPlaytime)
|
||||||
|
{
|
||||||
|
return (false, string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip = e.IP.Address;
|
||||||
|
var username = e.UserName;
|
||||||
|
|
||||||
|
// Is this a local ip address?
|
||||||
|
if (IsAddressReservedIpv4(ip) || IsAddressReservedIpv6(ip))
|
||||||
|
{
|
||||||
|
_sawmill.Warning($"{e.UserName} joined using a local address. Do you need IPIntel? Or is something terribly misconfigured on your server? Trusting this connection.");
|
||||||
|
return (false, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check our cache
|
||||||
|
var query = await _db.GetIPIntelCache(ip);
|
||||||
|
|
||||||
|
// Does it exist?
|
||||||
|
if (query != null)
|
||||||
|
{
|
||||||
|
// Skip to score check if result is older than _cacheDays
|
||||||
|
if (DateTime.UtcNow - query.Time <= _cacheDays)
|
||||||
|
{
|
||||||
|
var score = query.Score;
|
||||||
|
return ScoreCheck(score, username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure our contact email is good to use.
|
||||||
|
if (string.IsNullOrEmpty(_contactEmail) || !_contactEmail.Contains('@') || !_contactEmail.Contains('.'))
|
||||||
|
{
|
||||||
|
_sawmill.Error("IPIntel is enabled, but contact email is empty or not a valid email, treating this connection like an unknown IPIntel response.");
|
||||||
|
return _rejectUnknown ? (true, Loc.GetString("generic-misconfigured")) : (false, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiResult = await QueryIPIntelRateLimited(ip);
|
||||||
|
switch (apiResult.Code)
|
||||||
|
{
|
||||||
|
case IPIntelResultCode.Success:
|
||||||
|
await Task.Run(() => _db.UpsertIPIntelCache(DateTime.UtcNow, ip, apiResult.Score));
|
||||||
|
return ScoreCheck(apiResult.Score, username);
|
||||||
|
|
||||||
|
case IPIntelResultCode.RateLimited:
|
||||||
|
return _rejectLimited ? (true, Loc.GetString("ipintel-server-ratelimited")) : (false, string.Empty);
|
||||||
|
|
||||||
|
case IPIntelResultCode.Errored:
|
||||||
|
return _rejectUnknown ? (true, Loc.GetString("ipintel-unknown")) : (false, string.Empty);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IPIntelResult> QueryIPIntelRateLimited(IPAddress ip)
|
||||||
|
{
|
||||||
|
IncrementAndTestRateLimit(ref _day, TimeSpan.FromDays(1), "daily");
|
||||||
|
IncrementAndTestRateLimit(ref _minute, TimeSpan.FromMinutes(1), "minute");
|
||||||
|
|
||||||
|
if (_minute.RateLimited || _day.RateLimited || CheckSuddenRateLimit())
|
||||||
|
return new IPIntelResult(0, IPIntelResultCode.RateLimited);
|
||||||
|
|
||||||
|
// Info about flag B: https://getipintel.net/free-proxy-vpn-tor-detection-api/#flagsb
|
||||||
|
// TLDR: We don't care about knowing if a connection is compromised.
|
||||||
|
// We just want to know if it's a vpn. This also speeds up the request by quite a bit. (A full scan can take 200ms to 5 seconds. This will take at most 120ms)
|
||||||
|
using var request = await _api.GetIPScore(ip);
|
||||||
|
|
||||||
|
if (request.StatusCode == HttpStatusCode.TooManyRequests)
|
||||||
|
{
|
||||||
|
_sawmill.Warning($"We hit the IPIntel request limit at some point. (Current limit count: Minute: {_minute.CurrentRequests} Day: {_day.CurrentRequests})");
|
||||||
|
CalculateSuddenRatelimit();
|
||||||
|
return new IPIntelResult(0, IPIntelResultCode.RateLimited);
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await request.Content.ReadAsStringAsync();
|
||||||
|
var score = Parse.Float(response);
|
||||||
|
|
||||||
|
if (request.StatusCode == HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
_failedRequests = 0;
|
||||||
|
return new IPIntelResult(score, IPIntelResultCode.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ErrorMessages.TryGetValue(response, out var errorMessage))
|
||||||
|
{
|
||||||
|
_sawmill.Error($"IPIntel returned error {response}: {errorMessage}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Oh boy, we don't know this error.
|
||||||
|
_sawmill.Error($"IPIntel returned {response} (Status code: {request.StatusCode})... we don't know what this error code is. Please make an issue in upstream!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new IPIntelResult(0, IPIntelResultCode.Errored);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckSuddenRateLimit()
|
||||||
|
{
|
||||||
|
return _failedRequests >= 1 && _releasePeriod > _gameTiming.RealTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CalculateSuddenRatelimit()
|
||||||
|
{
|
||||||
|
_failedRequests++;
|
||||||
|
_releasePeriod = _gameTiming.RealTime + TimeSpan.FromSeconds(_failedRequests * _backoffSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, string> ErrorMessages = new()
|
||||||
|
{
|
||||||
|
["-1"] = "Invalid/No input.",
|
||||||
|
["-2"] = "Invalid IP address.",
|
||||||
|
["-3"] = "Unroutable address / private address given to the api. Make an issue in upstream as it should have been handled.",
|
||||||
|
["-4"] = "Unable to reach IPIntel database. Perhaps it's down?",
|
||||||
|
["-5"] = "Server's IP/Contact may have been banned, go to getipintel.net and make contact to be unbanned.",
|
||||||
|
["-6"] = "You did not provide any contact information with your query or the contact information is invalid.",
|
||||||
|
};
|
||||||
|
|
||||||
|
private void IncrementAndTestRateLimit(ref Ratelimits ratelimits, TimeSpan expireInterval, string name)
|
||||||
|
{
|
||||||
|
if (ratelimits.CurrentRequests < ratelimits.Limit)
|
||||||
|
{
|
||||||
|
ratelimits.CurrentRequests += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShouldLiftRateLimit(in ratelimits, expireInterval))
|
||||||
|
{
|
||||||
|
_sawmill.Info($"IPIntel {name} rate limit lifted. We are back to normal.");
|
||||||
|
ratelimits.RateLimited = false;
|
||||||
|
ratelimits.CurrentRequests = 0;
|
||||||
|
ratelimits.LimitHasBeenHandled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ratelimits.LimitHasBeenHandled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_sawmill.Warning($"We just hit our last {name} IPIntel limit ({ratelimits.Limit})");
|
||||||
|
ratelimits.RateLimited = true;
|
||||||
|
ratelimits.LimitHasBeenHandled = true;
|
||||||
|
ratelimits.LastRatelimited = _gameTiming.RealTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldLiftRateLimit(in Ratelimits ratelimits, TimeSpan liftingTime)
|
||||||
|
{
|
||||||
|
// Should we raise this limit now?
|
||||||
|
return ratelimits.RateLimited && _gameTiming.RealTime >= ratelimits.LastRatelimited + liftingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private (bool, string Empty) ScoreCheck(float score, string username)
|
||||||
|
{
|
||||||
|
var decisionIsReject = score > _rating;
|
||||||
|
|
||||||
|
if (_alertAdminWarn != 0f && _alertAdminWarn < score && !decisionIsReject)
|
||||||
|
{
|
||||||
|
_chatManager.SendAdminAlert(Loc.GetString("admin-alert-ipintel-warning",
|
||||||
|
("player", username),
|
||||||
|
("percent", Math.Round(score))));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decisionIsReject)
|
||||||
|
return (false, string.Empty);
|
||||||
|
|
||||||
|
if (_alertAdminReject)
|
||||||
|
{
|
||||||
|
_chatManager.SendAdminAlert(Loc.GetString("admin-alert-ipintel-blocked",
|
||||||
|
("player", username),
|
||||||
|
("percent", Math.Round(score))));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _rejectBad ? (true, Loc.GetString("ipintel-suspicious")) : (false, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Update()
|
||||||
|
{
|
||||||
|
if (_enabled && _gameTiming.RealTime >= _nextClean)
|
||||||
|
{
|
||||||
|
_nextClean = _gameTiming.RealTime + TimeSpan.FromMinutes(_cleanupMins);
|
||||||
|
await _db.CleanIPIntelCache(_cacheDays);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stolen from Lidgren.Network (Space Wizards Edition) (NetReservedAddress.cs)
|
||||||
|
// Modified with IPV6 on top
|
||||||
|
private static int Ipv4(byte a, byte b, byte c, byte d)
|
||||||
|
{
|
||||||
|
return (a << 24) | (b << 16) | (c << 8) | d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From miniupnpc
|
||||||
|
private static readonly (int ip, int mask)[] ReservedRangesIpv4 =
|
||||||
|
[
|
||||||
|
// @formatter:off
|
||||||
|
(Ipv4(0, 0, 0, 0), 8 ), // RFC1122 "This host on this network"
|
||||||
|
(Ipv4(10, 0, 0, 0), 8 ), // RFC1918 Private-Use
|
||||||
|
(Ipv4(100, 64, 0, 0), 10), // RFC6598 Shared Address Space
|
||||||
|
(Ipv4(127, 0, 0, 0), 8 ), // RFC1122 Loopback
|
||||||
|
(Ipv4(169, 254, 0, 0), 16), // RFC3927 Link-Local
|
||||||
|
(Ipv4(172, 16, 0, 0), 12), // RFC1918 Private-Use
|
||||||
|
(Ipv4(192, 0, 0, 0), 24), // RFC6890 IETF Protocol Assignments
|
||||||
|
(Ipv4(192, 0, 2, 0), 24), // RFC5737 Documentation (TEST-NET-1)
|
||||||
|
(Ipv4(192, 31, 196, 0), 24), // RFC7535 AS112-v4
|
||||||
|
(Ipv4(192, 52, 193, 0), 24), // RFC7450 AMT
|
||||||
|
(Ipv4(192, 88, 99, 0), 24), // RFC7526 6to4 Relay Anycast
|
||||||
|
(Ipv4(192, 168, 0, 0), 16), // RFC1918 Private-Use
|
||||||
|
(Ipv4(192, 175, 48, 0), 24), // RFC7534 Direct Delegation AS112 Service
|
||||||
|
(Ipv4(198, 18, 0, 0), 15), // RFC2544 Benchmarking
|
||||||
|
(Ipv4(198, 51, 100, 0), 24), // RFC5737 Documentation (TEST-NET-2)
|
||||||
|
(Ipv4(203, 0, 113, 0), 24), // RFC5737 Documentation (TEST-NET-3)
|
||||||
|
(Ipv4(224, 0, 0, 0), 4 ), // RFC1112 Multicast
|
||||||
|
(Ipv4(240, 0, 0, 0), 4 ), // RFC1112 Reserved for Future Use + RFC919 Limited Broadcast
|
||||||
|
// @formatter:on
|
||||||
|
];
|
||||||
|
|
||||||
|
private static UInt128 ToAddressBytes(string ip)
|
||||||
|
{
|
||||||
|
return BinaryPrimitives.ReadUInt128BigEndian(IPAddress.Parse(ip).GetAddressBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly (UInt128 ip, int mask)[] ReservedRangesIpv6 =
|
||||||
|
[
|
||||||
|
(ToAddressBytes("::1"), 128), // "This host on this network"
|
||||||
|
(ToAddressBytes("::ffff:0:0"), 96), // IPv4-mapped addresses
|
||||||
|
(ToAddressBytes("::ffff:0:0:0"), 96), // IPv4-translated addresses
|
||||||
|
(ToAddressBytes("64:ff9b:1::"), 48), // IPv4/IPv6 translation
|
||||||
|
(ToAddressBytes("100::"), 64), // Discard prefix
|
||||||
|
(ToAddressBytes("2001:20::"), 28), // ORCHIDv2
|
||||||
|
(ToAddressBytes("2001:db8::"), 32), // Addresses used in documentation and example source code
|
||||||
|
(ToAddressBytes("3fff::"), 20), // Addresses used in documentation and example source code
|
||||||
|
(ToAddressBytes("5f00::"), 16), // IPv6 Segment Routing (SRv6)
|
||||||
|
(ToAddressBytes("fc00::"), 7), // Unique local address
|
||||||
|
];
|
||||||
|
|
||||||
|
internal static bool IsAddressReservedIpv4(IPAddress address)
|
||||||
|
{
|
||||||
|
if (address.AddressFamily != AddressFamily.InterNetwork)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Span<byte> ipBitsByte = stackalloc byte[4];
|
||||||
|
address.TryWriteBytes(ipBitsByte, out _);
|
||||||
|
var ipBits = BinaryPrimitives.ReadInt32BigEndian(ipBitsByte);
|
||||||
|
|
||||||
|
foreach (var (reservedIp, maskBits) in ReservedRangesIpv4)
|
||||||
|
{
|
||||||
|
var mask = uint.MaxValue << (32 - maskBits);
|
||||||
|
if ((ipBits & mask) == (reservedIp & mask))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsAddressReservedIpv6(IPAddress address)
|
||||||
|
{
|
||||||
|
if (address.AddressFamily != AddressFamily.InterNetworkV6)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (address.IsIPv4MappedToIPv6)
|
||||||
|
return IsAddressReservedIpv4(address.MapToIPv4());
|
||||||
|
|
||||||
|
Span<byte> ipBitsByte = stackalloc byte[16];
|
||||||
|
address.TryWriteBytes(ipBitsByte, out _);
|
||||||
|
var ipBits = BinaryPrimitives.ReadInt128BigEndian(ipBitsByte);
|
||||||
|
|
||||||
|
foreach (var (reservedIp, maskBits) in ReservedRangesIpv6)
|
||||||
|
{
|
||||||
|
var mask = UInt128.MaxValue << (128 - maskBits);
|
||||||
|
if (((UInt128) ipBits & mask ) == (reservedIp & mask))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct IPIntelResult(float Score, IPIntelResultCode Code);
|
||||||
|
|
||||||
|
public enum IPIntelResultCode : byte
|
||||||
|
{
|
||||||
|
Success = 0,
|
||||||
|
RateLimited,
|
||||||
|
Errored,
|
||||||
|
}
|
||||||
|
}
|
||||||
40
Content.Server/Connection/IPIntel/IPIntelAPI.cs
Normal file
40
Content.Server/Connection/IPIntel/IPIntelAPI.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection.IPIntel;
|
||||||
|
|
||||||
|
public interface IIPIntelApi
|
||||||
|
{
|
||||||
|
Task<HttpResponseMessage> GetIPScore(IPAddress ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class IPIntelApi : IIPIntelApi
|
||||||
|
{
|
||||||
|
// Holds-The-HttpClient
|
||||||
|
private readonly IHttpClientHolder _http;
|
||||||
|
|
||||||
|
// CCvars
|
||||||
|
private string? _contactEmail;
|
||||||
|
private string? _baseUrl;
|
||||||
|
private string? _flags;
|
||||||
|
|
||||||
|
public IPIntelApi(
|
||||||
|
IHttpClientHolder http,
|
||||||
|
IConfigurationManager cfg)
|
||||||
|
{
|
||||||
|
_http = http;
|
||||||
|
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelEmail, b => _contactEmail = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelBase, b => _baseUrl = b, true);
|
||||||
|
cfg.OnValueChanged(CCVars.GameIPIntelFlags, b => _flags = b, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<HttpResponseMessage> GetIPScore(IPAddress ip)
|
||||||
|
{
|
||||||
|
return _http.Client.GetAsync($"{_baseUrl}/check.php?ip={ip}&contact={_contactEmail}&flags={_flags}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -751,6 +751,20 @@ namespace Content.Server.Database
|
|||||||
existing.Flags = admin.Flags;
|
existing.Flags = admin.Flags;
|
||||||
existing.Title = admin.Title;
|
existing.Title = admin.Title;
|
||||||
existing.AdminRankId = admin.AdminRankId;
|
existing.AdminRankId = admin.AdminRankId;
|
||||||
|
existing.Deadminned = admin.Deadminned;
|
||||||
|
existing.Suspended = admin.Suspended;
|
||||||
|
|
||||||
|
await db.DbContext.SaveChangesAsync(cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAdminDeadminnedAsync(NetUserId userId, bool deadminned, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb(cancel);
|
||||||
|
|
||||||
|
var adminRecord = db.DbContext.Admin.Where(a => a.UserId == userId);
|
||||||
|
await adminRecord.ExecuteUpdateAsync(
|
||||||
|
set => set.SetProperty(p => p.Deadminned, deadminned),
|
||||||
|
cancellationToken: cancel);
|
||||||
|
|
||||||
await db.DbContext.SaveChangesAsync(cancel);
|
await db.DbContext.SaveChangesAsync(cancel);
|
||||||
}
|
}
|
||||||
@@ -1720,6 +1734,73 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
# region IPIntel
|
||||||
|
|
||||||
|
public async Task<bool> UpsertIPIntelCache(DateTime time, IPAddress ip, float score)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var db = await GetDb();
|
||||||
|
|
||||||
|
var existing = await db.DbContext.IPIntelCache
|
||||||
|
.Where(w => ip.Equals(w.Address))
|
||||||
|
.SingleOrDefaultAsync();
|
||||||
|
|
||||||
|
if (existing == null)
|
||||||
|
{
|
||||||
|
var newCache = new IPIntelCache
|
||||||
|
{
|
||||||
|
Time = time,
|
||||||
|
Address = ip,
|
||||||
|
Score = score,
|
||||||
|
};
|
||||||
|
db.DbContext.IPIntelCache.Add(newCache);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existing.Time = time;
|
||||||
|
existing.Score = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(5000);
|
||||||
|
|
||||||
|
await db.DbContext.SaveChangesAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (DbUpdateException)
|
||||||
|
{
|
||||||
|
_opsLog.Warning("IPIntel UPSERT failed with a db exception... retrying.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IPIntelCache?> GetIPIntelCache(IPAddress ip)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb();
|
||||||
|
|
||||||
|
return await db.DbContext.IPIntelCache
|
||||||
|
.SingleOrDefaultAsync(w => ip.Equals(w.Address));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CleanIPIntelCache(TimeSpan range)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb();
|
||||||
|
|
||||||
|
// Calculating this here cause otherwise sqlite whines.
|
||||||
|
var cutoffTime = DateTime.UtcNow.Subtract(range);
|
||||||
|
|
||||||
|
await db.DbContext.IPIntelCache
|
||||||
|
.Where(w => w.Time <= cutoffTime)
|
||||||
|
.ExecuteDeleteAsync();
|
||||||
|
|
||||||
|
await db.DbContext.SaveChangesAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
// SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc.
|
// SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc.
|
||||||
// Normalize DateTimes here so they're always Utc. Thanks.
|
// Normalize DateTimes here so they're always Utc. Thanks.
|
||||||
protected abstract DateTime NormalizeDatabaseTime(DateTime time);
|
protected abstract DateTime NormalizeDatabaseTime(DateTime time);
|
||||||
|
|||||||
@@ -217,6 +217,16 @@ namespace Content.Server.Database
|
|||||||
Task AddAdminAsync(Admin admin, CancellationToken cancel = default);
|
Task AddAdminAsync(Admin admin, CancellationToken cancel = default);
|
||||||
Task UpdateAdminAsync(Admin admin, CancellationToken cancel = default);
|
Task UpdateAdminAsync(Admin admin, CancellationToken cancel = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update whether an admin has voluntarily deadminned.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This does nothing if the player is not an admin.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="userId">The user ID of the admin.</param>
|
||||||
|
/// <param name="deadminned">Whether the admin is deadminned or not.</param>
|
||||||
|
Task UpdateAdminDeadminnedAsync(NetUserId userId, bool deadminned, CancellationToken cancel = default);
|
||||||
|
|
||||||
Task RemoveAdminRankAsync(int rankId, CancellationToken cancel = default);
|
Task RemoveAdminRankAsync(int rankId, CancellationToken cancel = default);
|
||||||
Task AddAdminRankAsync(AdminRank rank, CancellationToken cancel = default);
|
Task AddAdminRankAsync(AdminRank rank, CancellationToken cancel = default);
|
||||||
Task UpdateAdminRankAsync(AdminRank rank, CancellationToken cancel = default);
|
Task UpdateAdminRankAsync(AdminRank rank, CancellationToken cancel = default);
|
||||||
@@ -322,6 +332,14 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region IPintel
|
||||||
|
|
||||||
|
Task<bool> UpsertIPIntelCache(DateTime time, IPAddress ip, float score);
|
||||||
|
Task<IPIntelCache?> GetIPIntelCache(IPAddress ip);
|
||||||
|
Task<bool> CleanIPIntelCache(TimeSpan range);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region DB Notifications
|
#region DB Notifications
|
||||||
|
|
||||||
void SubscribeToNotifications(Action<DatabaseNotification> handler);
|
void SubscribeToNotifications(Action<DatabaseNotification> handler);
|
||||||
@@ -666,6 +684,12 @@ namespace Content.Server.Database
|
|||||||
return RunDbCommand(() => _db.UpdateAdminAsync(admin, cancel));
|
return RunDbCommand(() => _db.UpdateAdminAsync(admin, cancel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task UpdateAdminDeadminnedAsync(NetUserId userId, bool deadminned, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
DbWriteOpsMetric.Inc();
|
||||||
|
return RunDbCommand(() => _db.UpdateAdminDeadminnedAsync(userId, deadminned, cancel));
|
||||||
|
}
|
||||||
|
|
||||||
public Task RemoveAdminRankAsync(int rankId, CancellationToken cancel = default)
|
public Task RemoveAdminRankAsync(int rankId, CancellationToken cancel = default)
|
||||||
{
|
{
|
||||||
DbWriteOpsMetric.Inc();
|
DbWriteOpsMetric.Inc();
|
||||||
@@ -991,6 +1015,23 @@ namespace Content.Server.Database
|
|||||||
return RunDbCommand(() => _db.RemoveJobWhitelist(player, job));
|
return RunDbCommand(() => _db.RemoveJobWhitelist(player, job));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<bool> UpsertIPIntelCache(DateTime time, IPAddress ip, float score)
|
||||||
|
{
|
||||||
|
DbWriteOpsMetric.Inc();
|
||||||
|
return RunDbCommand(() => _db.UpsertIPIntelCache(time, ip, score));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IPIntelCache?> GetIPIntelCache(IPAddress ip)
|
||||||
|
{
|
||||||
|
return RunDbCommand(() => _db.GetIPIntelCache(ip));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> CleanIPIntelCache(TimeSpan range)
|
||||||
|
{
|
||||||
|
DbWriteOpsMetric.Inc();
|
||||||
|
return RunDbCommand(() => _db.CleanIPIntelCache(range));
|
||||||
|
}
|
||||||
|
|
||||||
public void SubscribeToNotifications(Action<DatabaseNotification> handler)
|
public void SubscribeToNotifications(Action<DatabaseNotification> handler)
|
||||||
{
|
{
|
||||||
lock (_notificationHandlers)
|
lock (_notificationHandlers)
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ namespace Content.Server.Entry
|
|||||||
private PlayTimeTrackingManager? _playTimeTracking;
|
private PlayTimeTrackingManager? _playTimeTracking;
|
||||||
private IEntitySystemManager? _sysMan;
|
private IEntitySystemManager? _sysMan;
|
||||||
private IServerDbManager? _dbManager;
|
private IServerDbManager? _dbManager;
|
||||||
|
private IWatchlistWebhookManager _watchlistWebhookManager = default!;
|
||||||
|
private IConnectionManager? _connectionManager;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Init()
|
public override void Init()
|
||||||
@@ -91,8 +93,10 @@ namespace Content.Server.Entry
|
|||||||
_voteManager = IoCManager.Resolve<IVoteManager>();
|
_voteManager = IoCManager.Resolve<IVoteManager>();
|
||||||
_updateManager = IoCManager.Resolve<ServerUpdateManager>();
|
_updateManager = IoCManager.Resolve<ServerUpdateManager>();
|
||||||
_playTimeTracking = IoCManager.Resolve<PlayTimeTrackingManager>();
|
_playTimeTracking = IoCManager.Resolve<PlayTimeTrackingManager>();
|
||||||
|
_connectionManager = IoCManager.Resolve<IConnectionManager>();
|
||||||
_sysMan = IoCManager.Resolve<IEntitySystemManager>();
|
_sysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||||
_dbManager = IoCManager.Resolve<IServerDbManager>();
|
_dbManager = IoCManager.Resolve<IServerDbManager>();
|
||||||
|
_watchlistWebhookManager = IoCManager.Resolve<IWatchlistWebhookManager>();
|
||||||
|
|
||||||
logManager.GetSawmill("Storage").Level = LogLevel.Info;
|
logManager.GetSawmill("Storage").Level = LogLevel.Info;
|
||||||
logManager.GetSawmill("db.ef").Level = LogLevel.Info;
|
logManager.GetSawmill("db.ef").Level = LogLevel.Info;
|
||||||
@@ -110,6 +114,7 @@ namespace Content.Server.Entry
|
|||||||
_voteManager.Initialize();
|
_voteManager.Initialize();
|
||||||
_updateManager.Initialize();
|
_updateManager.Initialize();
|
||||||
_playTimeTracking.Initialize();
|
_playTimeTracking.Initialize();
|
||||||
|
_watchlistWebhookManager.Initialize();
|
||||||
IoCManager.Resolve<JobWhitelistManager>().Initialize();
|
IoCManager.Resolve<JobWhitelistManager>().Initialize();
|
||||||
IoCManager.Resolve<PlayerRateLimitManager>().Initialize();
|
IoCManager.Resolve<PlayerRateLimitManager>().Initialize();
|
||||||
}
|
}
|
||||||
@@ -166,6 +171,8 @@ namespace Content.Server.Entry
|
|||||||
case ModUpdateLevel.FramePostEngine:
|
case ModUpdateLevel.FramePostEngine:
|
||||||
_updateManager.Update();
|
_updateManager.Update();
|
||||||
_playTimeTracking?.Update();
|
_playTimeTracking?.Update();
|
||||||
|
_watchlistWebhookManager.Update();
|
||||||
|
_connectionManager?.Update();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -512,7 +512,8 @@ public sealed partial class ExplosionSystem
|
|||||||
List<(Vector2i GridIndices, Tile Tile)> damagedTiles,
|
List<(Vector2i GridIndices, Tile Tile)> damagedTiles,
|
||||||
ExplosionPrototype type)
|
ExplosionPrototype type)
|
||||||
{
|
{
|
||||||
if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef)
|
if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef
|
||||||
|
|| tileDef.Indestructible)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!CanCreateVacuum)
|
if (!CanCreateVacuum)
|
||||||
|
|||||||
@@ -356,6 +356,11 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
|||||||
// + if the bomb is big enough, people outside of it too
|
// + if the bomb is big enough, people outside of it too
|
||||||
// this is capped to 30 because otherwise really huge bombs
|
// this is capped to 30 because otherwise really huge bombs
|
||||||
// will attempt to play regular audio for people who can't hear it anyway because the epicenter is so far away
|
// will attempt to play regular audio for people who can't hear it anyway because the epicenter is so far away
|
||||||
|
//
|
||||||
|
// TODO EXPLOSION redo this.
|
||||||
|
// Use the Filter.Pvs range-multiplier option instead of AddInRange.
|
||||||
|
// Also the default PVS range is 25*2 = 50. So capping it at 30 makes no sense here.
|
||||||
|
// So actually maybe don't use Filter.Pvs at all and only use AddInRange?
|
||||||
var audioRange = Math.Min(iterationIntensity.Count * 2, MaxExplosionAudioRange);
|
var audioRange = Math.Min(iterationIntensity.Count * 2, MaxExplosionAudioRange);
|
||||||
var filter = Filter.Pvs(pos).AddInRange(pos, audioRange);
|
var filter = Filter.Pvs(pos).AddInRange(pos, audioRange);
|
||||||
var sound = iterationIntensity.Count < queued.Proto.SmallSoundIterationThreshold
|
var sound = iterationIntensity.Count < queued.Proto.SmallSoundIterationThreshold
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ public sealed partial class PuddleSystem
|
|||||||
Spawn("PuddleSparkle", xformQuery.GetComponent(uid).Coordinates);
|
Spawn("PuddleSparkle", xformQuery.GetComponent(uid).Coordinates);
|
||||||
QueueDel(uid);
|
QueueDel(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_solutionContainerSystem.UpdateChemicals(puddle.Solution.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,8 +222,6 @@ namespace Content.Server.GameTicking
|
|||||||
_mind.SetUserId(newMind, data.UserId);
|
_mind.SetUserId(newMind, data.UserId);
|
||||||
|
|
||||||
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
|
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
|
||||||
_roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId);
|
|
||||||
var jobName = _jobs.MindTryGetJobName(newMind);
|
|
||||||
|
|
||||||
_playTimeTrackings.PlayerRolesChanged(player);
|
_playTimeTrackings.PlayerRolesChanged(player);
|
||||||
|
|
||||||
@@ -233,6 +231,9 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
_mind.TransferTo(newMind, mob);
|
_mind.TransferTo(newMind, mob);
|
||||||
|
|
||||||
|
_roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId);
|
||||||
|
var jobName = _jobs.MindTryGetJobName(newMind);
|
||||||
|
|
||||||
if (lateJoin && !silent)
|
if (lateJoin && !silent)
|
||||||
{
|
{
|
||||||
if (jobPrototype.JoinNotifyCrew)
|
if (jobPrototype.JoinNotifyCrew)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Linq;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
@@ -43,15 +44,11 @@ namespace Content.Server.GameTicking
|
|||||||
jObject["name"] = _baseServer.ServerName;
|
jObject["name"] = _baseServer.ServerName;
|
||||||
jObject["map"] = _gameMapManager.GetSelectedMap()?.MapName;
|
jObject["map"] = _gameMapManager.GetSelectedMap()?.MapName;
|
||||||
jObject["round_id"] = _gameTicker.RoundId;
|
jObject["round_id"] = _gameTicker.RoundId;
|
||||||
jObject["players"] = _playerManager.PlayerCount;
|
jObject["players"] = _cfg.GetCVar(CCVars.AdminsCountInReportedPlayerCount)
|
||||||
|
? _playerManager.PlayerCount
|
||||||
|
: _playerManager.PlayerCount - _adminManager.ActiveAdmins.Count();
|
||||||
jObject["soft_max_players"] = _cfg.GetCVar(CCVars.SoftMaxPlayers);
|
jObject["soft_max_players"] = _cfg.GetCVar(CCVars.SoftMaxPlayers);
|
||||||
jObject["panic_bunker"] = _cfg.GetCVar(CCVars.PanicBunkerEnabled);
|
jObject["panic_bunker"] = _cfg.GetCVar(CCVars.PanicBunkerEnabled);
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
|
||||||
*/
|
|
||||||
|
|
||||||
jObject["baby_jail"] = _cfg.GetCVar(CCVars.BabyJailEnabled);
|
|
||||||
jObject["run_level"] = (int) _runLevel;
|
jObject["run_level"] = (int) _runLevel;
|
||||||
if (preset != null)
|
if (preset != null)
|
||||||
jObject["preset"] = Loc.GetString(preset.ModeTitle);
|
jObject["preset"] = Loc.GetString(preset.ModeTitle);
|
||||||
|
|||||||
37
Content.Server/Ghost/Components/SpookySpeakerComponent.cs
Normal file
37
Content.Server/Ghost/Components/SpookySpeakerComponent.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using Content.Shared.Dataset;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Ghost.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Causes this entity to react to ghost player using the "Boo!" action by speaking
|
||||||
|
/// a randomly chosen message from a specified set.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, AutoGenerateComponentPause]
|
||||||
|
public sealed partial class SpookySpeakerComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ProtoId of the LocalizedDataset to use for messages.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public ProtoId<LocalizedDatasetPrototype> MessageSet;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Probability that this entity will speak if activated by a Boo action.
|
||||||
|
/// This is so whole banks of entities don't trigger at the same time.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float SpeakChance = 0.5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum time that must pass after speaking before this entity can speak again.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan Cooldown = TimeSpan.FromSeconds(30);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Time when the cooldown will have elapsed and the entity can speak again.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoPausedField]
|
||||||
|
public TimeSpan NextSpeakTime;
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ using Content.Shared.Mobs.Components;
|
|||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Movement.Events;
|
using Content.Shared.Movement.Events;
|
||||||
using Content.Shared.Movement.Systems;
|
using Content.Shared.Movement.Systems;
|
||||||
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Storage.Components;
|
using Content.Shared.Storage.Components;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
@@ -33,6 +34,7 @@ using Robust.Shared.Physics.Components;
|
|||||||
using Robust.Shared.Physics.Systems;
|
using Robust.Shared.Physics.Systems;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.Ghost
|
namespace Content.Server.Ghost
|
||||||
@@ -61,6 +63,8 @@ namespace Content.Server.Ghost
|
|||||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
private EntityQuery<GhostComponent> _ghostQuery;
|
private EntityQuery<GhostComponent> _ghostQuery;
|
||||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||||
@@ -125,7 +129,9 @@ namespace Content.Server.Ghost
|
|||||||
if (args.Handled)
|
if (args.Handled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var entities = _lookup.GetEntitiesInRange(args.Performer, component.BooRadius);
|
var entities = _lookup.GetEntitiesInRange(args.Performer, component.BooRadius).ToList();
|
||||||
|
// Shuffle the possible targets so we don't favor any particular entities
|
||||||
|
_random.Shuffle(entities);
|
||||||
|
|
||||||
var booCounter = 0;
|
var booCounter = 0;
|
||||||
foreach (var ent in entities)
|
foreach (var ent in entities)
|
||||||
@@ -139,6 +145,9 @@ namespace Content.Server.Ghost
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (booCounter == 0)
|
||||||
|
_popup.PopupEntity(Loc.GetString("ghost-component-boo-action-failed"), uid, uid);
|
||||||
|
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
namespace Content.Server.Ghost;
|
using Content.Shared.Roles;
|
||||||
|
|
||||||
|
namespace Content.Server.Ghost;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is used to mark Observers properly, as they get Minds
|
/// This is used to mark Observers properly, as they get Minds
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class ObserverRoleComponent : Component
|
public sealed partial class ObserverRoleComponent : BaseMindRoleComponent
|
||||||
{
|
{
|
||||||
public string Name => Loc.GetString("observer-role-name");
|
public string Name => Loc.GetString("observer-role-name");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,12 +72,16 @@ public sealed partial class GhostRoleComponent : Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataField("allowSpeech")]
|
/// <summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
/// The mind roles that will be added to the mob's mind entity
|
||||||
|
/// </summary>
|
||||||
|
[DataField, Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // Don't make eye contact
|
||||||
|
public List<EntProtoId> MindRoles = new() { "MindRoleGhostRoleNeutral" };
|
||||||
|
|
||||||
|
[DataField]
|
||||||
public bool AllowSpeech { get; set; } = true;
|
public bool AllowSpeech { get; set; } = true;
|
||||||
|
|
||||||
[DataField("allowMovement")]
|
[DataField]
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public bool AllowMovement { get; set; }
|
public bool AllowMovement { get; set; }
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadOnly)]
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
@@ -107,3 +111,4 @@ public sealed partial class GhostRoleComponent : Component
|
|||||||
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // also FIXME Friends
|
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // also FIXME Friends
|
||||||
public ProtoId<JobPrototype>? JobProto = null;
|
public ProtoId<JobPrototype>? JobProto = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,39 +9,81 @@ namespace Content.Server.Ghost.Roles.Components;
|
|||||||
[RegisterComponent, Access(typeof(ToggleableGhostRoleSystem))]
|
[RegisterComponent, Access(typeof(ToggleableGhostRoleSystem))]
|
||||||
public sealed partial class ToggleableGhostRoleComponent : Component
|
public sealed partial class ToggleableGhostRoleComponent : Component
|
||||||
{
|
{
|
||||||
[DataField("examineTextMindPresent")]
|
/// <summary>
|
||||||
|
/// The text shown on the entity's Examine when it is controlled by a player
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
public string ExamineTextMindPresent = string.Empty;
|
public string ExamineTextMindPresent = string.Empty;
|
||||||
|
|
||||||
[DataField("examineTextMindSearching")]
|
/// <summary>
|
||||||
|
/// The text shown on the entity's Examine when it is waiting for a controlling player
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
public string ExamineTextMindSearching = string.Empty;
|
public string ExamineTextMindSearching = string.Empty;
|
||||||
|
|
||||||
[DataField("examineTextNoMind")]
|
/// <summary>
|
||||||
|
/// The text shown on the entity's Examine when it has no controlling player
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
public string ExamineTextNoMind = string.Empty;
|
public string ExamineTextNoMind = string.Empty;
|
||||||
|
|
||||||
[DataField("beginSearchingText")]
|
/// <summary>
|
||||||
|
/// The popup text when the entity (PAI/positronic brain) it is activated to seek a controlling player
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
public string BeginSearchingText = string.Empty;
|
public string BeginSearchingText = string.Empty;
|
||||||
|
|
||||||
[DataField("roleName")]
|
/// <summary>
|
||||||
|
/// The name shown on the Ghost Role list
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
public string RoleName = string.Empty;
|
public string RoleName = string.Empty;
|
||||||
|
|
||||||
[DataField("roleDescription")]
|
/// <summary>
|
||||||
|
/// The description shown on the Ghost Role list
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
public string RoleDescription = string.Empty;
|
public string RoleDescription = string.Empty;
|
||||||
|
|
||||||
[DataField("roleRules")]
|
/// <summary>
|
||||||
|
/// The introductory message shown when trying to take the ghost role/join the raffle
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
public string RoleRules = string.Empty;
|
public string RoleRules = string.Empty;
|
||||||
|
|
||||||
[DataField("wipeVerbText")]
|
/// <summary>
|
||||||
|
/// A list of mind roles that will be added to the entity's mind
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public List<EntProtoId> MindRoles;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The displayed name of the verb to wipe the controlling player
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
public string WipeVerbText = string.Empty;
|
public string WipeVerbText = string.Empty;
|
||||||
|
|
||||||
[DataField("wipeVerbPopup")]
|
/// /// <summary>
|
||||||
|
/// The popup message when wiping the controlling player
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
public string WipeVerbPopup = string.Empty;
|
public string WipeVerbPopup = string.Empty;
|
||||||
|
|
||||||
[DataField("stopSearchVerbText")]
|
/// <summary>
|
||||||
|
/// The displayed name of the verb to stop searching for a controlling player
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
public string StopSearchVerbText = string.Empty;
|
public string StopSearchVerbText = string.Empty;
|
||||||
|
|
||||||
[DataField("stopSearchVerbPopup")]
|
/// /// <summary>
|
||||||
|
/// The popup message when stopping to search for a controlling player
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
public string StopSearchVerbPopup = string.Empty;
|
public string StopSearchVerbPopup = string.Empty;
|
||||||
|
|
||||||
|
/// /// <summary>
|
||||||
|
/// The prototype ID of the job that will be given to the controlling mind
|
||||||
|
/// </summary>
|
||||||
[DataField("job")]
|
[DataField("job")]
|
||||||
public ProtoId<JobPrototype>? JobProto = null;
|
public ProtoId<JobPrototype>? JobProto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
namespace Content.Server.Ghost.Roles;
|
namespace Content.Server.Ghost.Roles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is used for round end display of ghost roles.
|
/// Added to mind role entities to tag that they are a ghostrole.
|
||||||
/// It may also be used to ensure some ghost roles count as antagonists in future.
|
/// It also holds the name for the round end display
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent
|
public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent
|
||||||
{
|
{
|
||||||
[DataField("name")] public string? Name;
|
//TODO does anything still use this? It gets populated by GhostRolesystem but I don't see anything ever reading it
|
||||||
|
[DataField] public string? Name;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ using Content.Server.Popups;
|
|||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Robust.Shared.Collections;
|
using Robust.Shared.Collections;
|
||||||
using Content.Shared.Ghost.Roles.Components;
|
using Content.Shared.Ghost.Roles.Components;
|
||||||
using Content.Shared.Roles.Jobs;
|
|
||||||
|
|
||||||
namespace Content.Server.Ghost.Roles;
|
namespace Content.Server.Ghost.Roles;
|
||||||
|
|
||||||
@@ -514,13 +513,13 @@ public sealed class GhostRoleSystem : EntitySystem
|
|||||||
var newMind = _mindSystem.CreateMind(player.UserId,
|
var newMind = _mindSystem.CreateMind(player.UserId,
|
||||||
EntityManager.GetComponent<MetaDataComponent>(mob).EntityName);
|
EntityManager.GetComponent<MetaDataComponent>(mob).EntityName);
|
||||||
|
|
||||||
_roleSystem.MindAddRole(newMind, "MindRoleGhostMarker");
|
|
||||||
|
|
||||||
if(_roleSystem.MindHasRole<GhostRoleMarkerRoleComponent>(newMind!, out var markerRole))
|
|
||||||
markerRole.Value.Comp2.Name = role.RoleName;
|
|
||||||
|
|
||||||
_mindSystem.SetUserId(newMind, player.UserId);
|
_mindSystem.SetUserId(newMind, player.UserId);
|
||||||
_mindSystem.TransferTo(newMind, mob);
|
_mindSystem.TransferTo(newMind, mob);
|
||||||
|
|
||||||
|
_roleSystem.MindAddRoles(newMind.Owner, role.MindRoles, newMind.Comp);
|
||||||
|
|
||||||
|
if (_roleSystem.MindHasRole<GhostRoleMarkerRoleComponent>(newMind!, out var markerRole))
|
||||||
|
markerRole.Value.Comp2.Name = role.RoleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -51,10 +51,13 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem
|
|||||||
|
|
||||||
var ghostRole = EnsureComp<GhostRoleComponent>(uid);
|
var ghostRole = EnsureComp<GhostRoleComponent>(uid);
|
||||||
EnsureComp<GhostTakeoverAvailableComponent>(uid);
|
EnsureComp<GhostTakeoverAvailableComponent>(uid);
|
||||||
|
|
||||||
|
//GhostRoleComponent inherits custom settings from the ToggleableGhostRoleComponent
|
||||||
ghostRole.RoleName = Loc.GetString(component.RoleName);
|
ghostRole.RoleName = Loc.GetString(component.RoleName);
|
||||||
ghostRole.RoleDescription = Loc.GetString(component.RoleDescription);
|
ghostRole.RoleDescription = Loc.GetString(component.RoleDescription);
|
||||||
ghostRole.RoleRules = Loc.GetString(component.RoleRules);
|
ghostRole.RoleRules = Loc.GetString(component.RoleRules);
|
||||||
ghostRole.JobProto = component.JobProto;
|
ghostRole.JobProto = component.JobProto;
|
||||||
|
ghostRole.MindRoles = component.MindRoles;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnExamined(EntityUid uid, ToggleableGhostRoleComponent component, ExaminedEvent args)
|
private void OnExamined(EntityUid uid, ToggleableGhostRoleComponent component, ExaminedEvent args)
|
||||||
|
|||||||
51
Content.Server/Ghost/SpookySpeakerSystem.cs
Normal file
51
Content.Server/Ghost/SpookySpeakerSystem.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using Content.Server.Chat.Systems;
|
||||||
|
using Content.Server.Ghost.Components;
|
||||||
|
using Content.Shared.Random.Helpers;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Server.Ghost;
|
||||||
|
|
||||||
|
public sealed class SpookySpeakerSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
[Dependency] private readonly ChatSystem _chat = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<SpookySpeakerComponent, GhostBooEvent>(OnGhostBoo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGhostBoo(Entity<SpookySpeakerComponent> entity, ref GhostBooEvent args)
|
||||||
|
{
|
||||||
|
// Only activate sometimes, so groups don't all trigger together
|
||||||
|
if (!_random.Prob(entity.Comp.SpeakChance))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var curTime = _timing.CurTime;
|
||||||
|
// Enforce a delay between messages to prevent spam
|
||||||
|
if (curTime < entity.Comp.NextSpeakTime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_proto.TryIndex(entity.Comp.MessageSet, out var messages))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Grab a random localized message from the set
|
||||||
|
var message = _random.Pick(messages);
|
||||||
|
// Chatcode moment: messages starting with '.' are considered radio messages unless prefixed with '>'
|
||||||
|
// So this is a stupid trick to make the "...Oooo"-style messages work.
|
||||||
|
message = '>' + message;
|
||||||
|
// Say the message
|
||||||
|
_chat.TrySendInGameICMessage(entity, message, InGameICChatType.Speak, hideChat: true);
|
||||||
|
|
||||||
|
// Set the delay for the next message
|
||||||
|
entity.Comp.NextSpeakTime = curTime + entity.Comp.Cooldown;
|
||||||
|
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user