Upstream sync (#846)
* Additional Ionstorm Law Updates (#34197) * Added a couple entires, removed the references to IRL countries, and fixed a grammar mistake on "Telecomunications Equipment" * Fixed another awkward grammar situation * Commented out a bunch of law elements I felt weren't good for discussion, added some new ones to help fill in the missing areas * Reverted pylons, and added more entries to help fill out the lists more * reverted all deletions * implemented feedback, also realized the IonStormActions section was rather short, so I added a bunch there * realized I didn't remove the extra resources line, also added more things, and fixed one grammatical error * Removed Portugal again, added more Musts since that list was the new shortest one. * Automatic changelog update * Add option to disable bwoink sound. (#33782) * Add option to disable bwoink sound. * Now it's working only with active admin status. * No bwoink, only "notification sound" * Moar changes * Another one * Automatic changelog update * Pride Scarves (#34448) * More scarfs (#216) * Sprites + Colored Scarf Implimentation Doesn't include into quickthreads or other clothing vendors * File Paths are important * Metas, items exist, winterdrobe I'm iffy on them being in the winterdrobe but the other option is quickthreads so idk * File Path Fix * Typo fix * Resolved merge conflict * Moved scarves around * Moved scarf textures out of the _CD directory * Removed final CD folder * Removed extra scarfs * Removed extra scarfs, again * Replaced ClothingNeckBase with ClothingScarfBase --------- Co-authored-by: PursuitInAshes <91865152+PursuitInAshes@users.noreply.github.com> Co-authored-by: TakoDragon <69509841+BackeTako@users.noreply.github.com> * Automatic changelog update * Replace the djstation intercoms with freelance intercoms (#34478) * Add Freelance intercom prototype, and replace the djstation ruin with them instead of three master keys * removed a new line * Update nix flake for .NET 9 (#34480) * Automatic changelog update * Holopad networking rework (#34112) * Initial commit * Finalizing main changes * Addressed reviews * Fixed a few issues * Switched to using global overrides * Removed unnecessary references * Make GasMixture enumerable I noticed that enumerating gases is frequently done in an annoying way with Enum.GetValues. So I made it better. Now GasMixture is IEnumerable<(Gas gas, float moles)> and it just works. * Improve canister admin logs. 1. Now clearly says "opened"/"closed" when changing the release valve. 2. Clearly says whether the valve was opened while a canister was inserted or not. 3. When a tank is ejected, logs if the valve is open and the ejection started spilling into the environment. Fixes #34488 * Optimize & clean up RadiationSystem (#34459) * Optimize & clean up RadiationSystem * comments * Update Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> --------- Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> * Update engine to v240.0.1 (#34497) * Various Locale Typo Fixes (and spaces) (#34483) Random spelling error and FTL linting (+PowersinkSystem because there was an misspelt locale for that) * Space lizard plushie can now be worn on your head (#33809) * space weh can now be work on top of head * space weh can now be worn on top of head * trim out unnecessary comps * Automatic changelog update * Adds bullet collision to wall mounted cameras (#34500) * Automatic changelog update * Change MaskComponent to accommodate sprites namings (#33451) Co-authored-by: Winkarst <74284083+Winkarst-cpu@users.noreply.github.co> * Automatic changelog update * Add a 10u vial of plasma to the chemical locker (#33871) Add 10u plasma to the chemistry locker so the chemists stop stealing tables. * Automatic changelog update * Rarer Highcaps (#34469) * Removed highcap from seclite * Changed cyborg starting battery to highcap, reduced seclite wattage to make it last as long as it used to. * Gave cyborgs back their medcap * Rounded seclite wattage down to 0.5 * Automatic changelog update * New dry fire sound (#34447) * new empty.ogg * source to tg commit * Automatic changelog update * CentComm Map Updates (#34475) * Bandage fix denied animations playing on devices without them * CentComm blast door prototype * CentComm button * CentComm window shutters * CC Updates * Save as grid * Remove an extra detective figurine I like them better in the interrogation room * Remove paramed locker, let pumps shut off * Fix wrong HOP locker prototype * Automatic changelog update * Cog power setup fix (#34188) * 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 * cog power setup was bad * made it artsy --------- Co-authored-by: Emisse <99158783+Emisse@users.noreply.github.com> * Add Airlocks with Bar and Kitchen access (#33821) * Add kitchen access to Bar-Cafeteria airlocks on Cog * Fix merge conflict * Remove mapping changes * Add Glass and Maints versions * Fixed minor spelling mistake in Noir Trenchcoat description. (#34519) Update coats.yml * Update Credits (#34507) Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> * Make storage implant drop items on gibbing (#33493) * Make storage implant drop items on gib/removal * Better way * Fix error * Forgotten trash * Cleanup * Unused var * Update Content.Server/Implants/ImplantedSystem.cs Co-authored-by: 0x6273 <0x40@keemail.me> --------- Co-authored-by: Winkarst <74284083+Winkarst-cpu@users.noreply.github.co> Co-authored-by: 0x6273 <0x40@keemail.me> * Automatic changelog update * Fix `emergency_elkridge` being saved as a map (#34496) Save `emergency_elkridge` as a grid * Update engine to v240.1.0 (#34524) * Fixes some mobs not being able to honk/weh (#33777) * fixes some mobs not being able to honk/weh * addresss review * Automatic changelog update * Welding gas mask toggleable with action (#32691) welding mask removable * Automatic changelog update * Update engine to v240.1.1 (#34527) * Automatic changelog update * Plasma Dirt Fix (#34534) did the dirt thing * Plasma station population tweak (#34549) * 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 * Update population caps Removed population cap of 60 players to make plasma into a highpop map - it's that easy! --------- Co-authored-by: jbox1 <40789662+jbox144@users.noreply.github.com> Co-authored-by: Emisse <99158783+Emisse@users.noreply.github.com> * Automatic changelog update * Job contraband rework (#33385) * contraband system rework to allow restriction by job, not just department * Fixing detective trenchcoat inheritance * removing unnecessary using declarations * trying to fix testing error by re-adding diagnostics using declaration * removing unecessary dependency, making allowedJobs nullable * Adding all of slarti's requested changes except for the hacky job icon method fix * removing accidental whitespace * choosing to use the non-localized version because we're comparing the string against the AllowedJobs field, and the contraband classes that fill that field are written in english * removing unneeded using dec, fixing nesting logic problem * didn't remove the old nesting, doing that now * using localized job title and localizing the allowed jobs string, removing usages of JobTitle field. Also networked the _jobTitle field instead. * rewrite some stuff * fixes * fix energy pen --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * Electrified doors/windoors now spark, new tips to deal with doors without access or when electrified (#34502) * new tips to open doors (throwing PDA/ID, dragging body) * electrified door sprite for players * tooltip to reset AI electrified doors * windoor electrified sprite * highsec electrified visual * increase tip dataset to 138 * corrected square bracket convention in this commit * removed door corpse tip from prior commit * Automatic changelog update * Blueprint double emergency tank (#34232) * blueprint * NitrogenTank * description * Automatic changelog update * Add system to kick people if they connect to multiple servers at once. (#34563) * Automatic changelog update * bagel update (#34572) * bagel update * Decal fault tolerance * Also fix writes * fix mv cable, decal issues, yar * fix a floor tile and some other shit --------- Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * remove tropico from devmap (#34585) * Update wizden config to disallow multiple connections to multiple wizden servers (#34584) * Give the chef access to cloth boxes (#34403) * Give the chef access to fiber bags * Update Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * lecter visual update (#34589) * Automatic changelog update * Box Station - Update (#34605) * Various changes * Implemented a bunch of changes Emisse wanted, finished up all my atmos changes I wanted to make as well * Added reporter to prototype, and fixed dirt * Resolved outstanding issues * Fixed a floating camera * pluralize the job name in the contra description (#34559) * pluralize the job name in the contra description * pluralization specific to contraband descriptions * Automatic changelog update * Feature/make radial menu great again (#32653) * it works! kinda * so it works now * minor cleanup * central button now is useful too * more cleanup * minor cleanup * more cleanup * refactor: migrated code from toolbox (as it was rejected as too specific) * feat: moved border drawing for radial menu into RadialMenuTextureButton. Radial menu position setting into was moved to OverrideArrange to not being called on every frame * refactor: major reworks! * renamed DrawBagleSector to DrawAnnulusSector * Remove strange indexing * Regularize math * refactor: re-orienting segment elements to be Y-mirrored * refactor: extracted radial menu radius multiplier property, changed color pallet for radial menu button * refactor: removed icon backgrounds on textures used in current radial menu buttons with sectors, RadialContainer Radius renamed and now actually changed control radius. * refactor: in RadialMenuTextureButtonWithSector all sector colors are converted to and from sRGB in property getter-setters * refactor: renamed srgb to include Srgb suffix so devs gonna see that its srgb clearly * fix: enabled any functional keys pressed when pushing radial menu buttons * fix: radial menu sector now scales with UIScale * fix: accept only one event when clicking on radial menu ContextualButton * fix: now radial menu buttons accepts only click/alt-click, now clicks outside menu closes menu always --------- Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru> Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> * Automatic changelog update * Return Drozd full-auto and semi-auto firing modes (#34604) return drozd full auto and firerate, arrange available modes in alphabetical order * Automatic changelog update * added missing allowed department to the restricted severity (#34558) * added missing allow job to the base restricted severity * no need to make a list * no more linq in ContrabandTest * less nesting in ContrabandTest * Automatic changelog update * C4 Helmet (#34076) * worn bomb * Update meta.json * Update meta.json * Automatic changelog update * Make radioactive material radioactive (#34436) * Make radioactive material radioactive * Increase the slopes of item uranium No free power for engineering * Glowing uranium * Revert "Increase the slopes of item uranium" This reverts commit 2acbda26 * Nerf Wall Rocks * Automatic changelog update * Small fixes for Meta station (#34613) * Fixed a few minor things, removed salvage's shuttle parts (sorry), added more equipment to armory. * Added back salvage shuttle equipment, now in a crate * Fix * replace all instances of "department-{id}" with department.name (#34607) replace all instances of "department-{id}" with department.name" * Renaming sexy mime and clown mask (#34258) * Renamed sexy clown and mime mask and removed lusty xenomorph poster. * removing odd file changes. not sure why these were edited at all. * gaaaahhh * removed some of the old templates * Renaming sexy mime and clown instances. * removed unwanted gitignore edit * Renaming sexy clown and sexy mime mask and Removing lusty exomorph poster. * Remove lusty xenomorph poster. * reverted changes to maps files, actually. Other contrib is learning, appologies for excessive commits * requested changes * lusty migration removal * dirty hanging whitespace * correct date format comment --------- Co-authored-by: NathanielJ14 <nathanielalbert1202@gmail.com> * Automatic changelog update * Moved Cyborg Recharging Circuit Board from Lathe to Circuit Imprinter (#34612) Moved Cyborg Recharging Cirbuit Board from Lathe to Circuit Imprinter * Automatic changelog update * Replace starter borg brain with Positronic (#34614) THE FLESH IS WEAK * Automatic changelog update * Elkridge Depot fixes and changes (#34539) * fixed grav gen room, gave atmos room to branch off, replaced AME controller crate with AME controller * added rust to maints, expanded atmos, expanded bar, expanded cap bedroom, fixed some tiles under doors, removed some gamer loot * removed interrogator lamp and some more gamer loot * fixed some more tiles under doors, replaced intercomall with intercomcommand in bridge and vault * added clothing drobes to arrivals, added bin to HoP, more stuff i forgot * i forgot to move the file from /bin to /resources... * delete radstorm locale (#34630) * display the current version in the changelog window (#34556) * just works first try * introduce more magic numbers into the codebase * idiot doesnt know the difference between AND/OR * Update Credits (#34644) Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> * New solar sprites, new solar panel upgrades, and some solar panel fixes. (#29224) * New solar sprites, new solar panel upgrades, and some solar panel fixes. This adds and changes a few things for solar panels! * New sprites for all solar panels and all related states. * Move from xform.WorldRotation to _xformSystem.SetWorldRotation within the solar code. * Few random fixes that Rider suggested as warnings. * Solar Tracker Electronics was using what looks like to be the airlock controller electronics, so that's now updated to something a bit more realistic. It also uses the engineering circuit sprite instead of the generic * New Solar Panels! Adds Plasma and Uranium Glass solar panels. These can be constructed by adding the respective glass to the panel. Plasma is a slight increase of power and health, and uranium is double the power and health of glass. Thus giving engineers something to update if they want to use solar panels and possibly giving small outposts a way to make a bit more power without a large and complex power setup. * Add in solar sprites that were not in the meta file. * Updated sprites based on feedback. * Fix the rotation of the solar panel sprites. * Automatic changelog update * Added Unused HoS's Flask to HoS locker (#34658) added hos flask to locker spawn * Automatic changelog update * Fland Fix (#34670) Update fland.yml * Reroute Meta station power, engineering cosmetic changes, minor fixes (#34669) * reroute meta power plus cosmetic fixes * fix a lot of other things * make changes from conflict * Amber Station Changes (#34656) * various changes * Increased player limit * slight modification * Followed proper yaml formatting, increased lawyers * Box Station Changes (#34655) * Various changes * Implemented a bunch of changes Emisse wanted, finished up all my atmos changes I wanted to make as well * Added reporter to prototype, and fixed dirt * Resolved outstanding issues * Fixed a floating camera * Added cameras to TEG, and some minor changes * Fix mv cable crate typo (#34673) fix mv cable crate typo * Plasma station patch 1 (#34602) * 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 * Update population caps Removed population cap of 60 players to make plasma into a highpop map - it's that easy! * Map fixes 11 * Map fixes 12 * Map fixes 13 * Map fixes 14 Now it's personal * Map fixes 15 --------- Co-authored-by: jbox1 <40789662+jbox144@users.noreply.github.com> Co-authored-by: Emisse <99158783+Emisse@users.noreply.github.com> * Automatic changelog update * add a chem dispenser to the nukie planet (#34674) * add a chem dispenser to the nukie planet * begone test fail * Adds better description to pneumatic valve and build menu description (#32655) * adds description to pneumatic valve and build menu description * Change one atmosphere to kPa * Automatic changelog update * fixrotations - Modified Targetted Entities (#34638) * Fix match box (#34632) FixMatchBox Now you can't put anything but matches in a box of matches. Co-authored-by: Helm4142 <Helm4142@users.noreply.github.com> * Automatic changelog update * Hi-viz vest now actually hi-viz (#34087) * 27-12-2024-light-hi-viz * 28-12-2024-meta-fix * Storage UI V2 (#33045) * Automatic changelog update * Added Pain Numbness Trait (#34538) * added pain-numbness component and system * added numb as a trait that pulls the pain numbness component * removed new event as mob threshold event as already being fired * checked for MobThresholdsComponent first before running VerifyThresholds * refacted force say to using LocalizedDatasetPrototype and added numb messages * added severity check alert * added comment for BeforeForceSayEvent * removed space formatting * changed Cancelled to CancelUpdate, fixed spacing and added two more damage-force-say-numb * changed prefix damage-force-say-numb to 5 (whoops) * Automatic changelog update * Update submodule to 241.0.0 (#34678) * Add puddle drawdepth (#32369) Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * Revert "Fix match box" (#34681) Revert "Fix match box (#34632)" This reverts commit88456a4a8c. * update .editorconfig (#34677) * Make crew monitor update blips at consistent rates (#32555) * Added the ability for pAIs and station maps to be stored in engineering belts (#33048) Co-authored-by: august-sun <45527070+august.sun@users.noreply.github.com> * Automatic changelog update * Engineering guidebook megaupdate v2 (#33062) Significantly updates the Engineering guidebook (more explicitly the Atmos section) to have a lot more relevant and useful information. Right now engineering has been getting update after update with no real change to the relevant guidebook entry. This has lead to a lot of out of date information and bad practices being prevalent in the guidebook, something that pains me to read. * Automatic changelog update * Omega: fix cryo pipe (#34663) fixed the freezer pipe in cryo room * Add more escape pods on Packed (#34628) * add escape pods north and south of station * move south escape pod a bit to the east * Storage sidebar fix (#34680) * Add conditional camera offset based on cursor - Hristov Rework, Part 1 (#31626) * Automatic changelog update * Amber Station Stuff (#34686) * Removed salv upper docking airlock, gave south west solars some uranium glass. * Routed HV wire through maints more, gave disposal blast doors a lever, added beacons and signs to escape pods * Fake mindshield componentry and Implanter (#34079) * Fake Mindshield (With some bad sprites) - Add FakeMindshield System and Component - Add FakeMindsheildImplantSystem and Component - modify ShowMindShieldIconsSystem to check for FakeMindshields - add all supporting yaml for the Implants, action and uplink - add loc file stuff - add unfinished sprites * Cleanup, add to thief toolbox, remove metagame - Move Implant sameness check to AFTER the implant DoAfter to prevent instant identification of Deception Implants - cleanup the systems and components - add the fake mindshield to the Thief toolbox * part 1 of fixing the folder problem * Make the fakemindshield sprite folder lowercase * CR - Move ImplantCheck into shared, cleanup - Moved ImplantCheck and eventsubscription into Shared - Remove Client/Server extensions of FakeMindshieldImplantSystem and FakeMindShieldSystem and make shared Sealed - make OnToggleMindshield Private, use the event! * CR - Cleanup extra lines, fix some Prototype - cleaned up extra liens in ImplanterSystem and SharedFakeMindshieldSystem from when i was developing - Uplink catalog no longer lists the implant in 2 spots, only implants now, also uses the On state action icon - added a comment about why it's reraising the action event rather than directly interacting with the FakeMindshield Component * Fake Mindshield CR: - Added a comment about IsEnabled - moved OnFakeMindShieldToggle to Entity<> from Uid, Comp - fixed some formatting in uplink_catalog * CR - Add a bit more comment * Automatic changelog update * reworking the chunked telecomms salv ruin (#34529) * aaa * bbb * ccc * ddd * switched to grid * pain --------- Co-authored-by: TytosB <duanlaintytos@yahoo.com> * loop update (#34688) loop loot gone * bagel update (#34690) * bagel update * fixe * move tesla gen * Fix airsensors not having a nitrogen threshold (#34689) * Buff frezon to acceptable values, pending a frezon rework (#34049) buff frezon * Automatic changelog update * Stun baton precise attack thrust animation (#34693) use weaponarcthrust instead of wide swing on single attack * Automatic changelog update * Add disposal units to marathon chapelroid (#34709) add disposal units to marathon chapelroid * Oasis: add some emergency O2/N2 lockers (#34715) Added O2, N2, and Fire lockers to Oasis station as per discussions in the issue thread Co-authored-by: Orange-Winds <sebastian.pelka@hotmail.com> * Removes radioactive suppermatter from Plasma Station (#34726) Removes radioactive suppermatter Literally 1984 can't have shit on SS14 e.t.c. e.t.c. Co-authored-by: jbox1 <40789662+jbox144@users.noreply.github.com> * New salvage ruin: the ruined prison ship (#34651) * aaa * little tweaks * changed to grid and added to protos * ccc * we dont talk about it * ddd --------- Co-authored-by: TytosB <dunalintytos@yahoo.com> * Buff savlage vault-medium-1's loot (#34732) * aaa * little tweaks * changed to grid and added to protos * ccc * we dont talk about it * ddd * aaa --------- Co-authored-by: TytosB <dunalintytos@yahoo.com> * drozd visual update (#34574) * drozd visual update * fix wielded sprotes * update sprotes * Automatic changelog update * Nuke Timer MinimumTime (#34734) * Implemented the thing * Made requested changes * Automatic changelog update * Fix thrust animation rotation (#34713) * Fix thrust animation rotation * directly update sprite instead * Automatic changelog update * Fix vulture spawning additional salvage debris (#34593) fix vulture spawning additional salvage debris * Nukie briefing fix (#34737) payloadpayloadpayloadpayloadpayloadpayload * Sentient medibot now can inject (#32110) medibot player injection Co-authored-by: YourUsername <you@example.com> * Criminal Records Computer Better UX + Filtering (#32352) * First pass at new Criminal Records Computer need buttons to highlight. * Filter status tabs/buttons now activate correctly via UpdateState * Removed unneeded Directives * Fix typo + undo VSCode changes * Implement Emo Feedback Loc NA and use inject deps Cannot use inject deps on sprite system. * try to undo vscode launch.json change * Added requests + Filter dropdown list + jobs Fixed maintainer fix requests, Added Job to announcement channel output Removed toggle buttons in-place of a dropdown list * Fixed missed merge conflict + fixed an bug with filterstatus not showing on re-open ui * Update criminal-records.ftl Fixed lint error. whoops. * Update Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs typo Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * impliment chromiumboy feedback hopefully this will do it.... --------- Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Added some more borg names (#32502) added a handful of new names * Automatic changelog update * Update ScalingViewport for Engine PR (#28786) * Update submodule to 242.0.0 (#34739) * fix stop bleeding popup (#34729) * fix stop bleeding popup * add identity * Fix RoleTimeRequirement localization (#34735) * fix job restriction localization * Update Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs * Update Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * reworking chem and adding psychology to loop (#34749) * aaa * little tweaks * changed to grid and added to protos * ccc * we dont talk about it * ddd * aaa * psychology real --------- Co-authored-by: TytosB <dunalintytos@yahoo.com> * Fix shuttle console angular velocity (#34748) * Syndicate and CentComm Radio Implanters (#33533) * Add Syndicate Radio Implant * Fix description grammar * remove unused var * Update - Small fixes * Un mess with imports * Remove unused tag * Correction * Clear lists instead of remove * Update 0 check * Add Centcomm implant * Correct centcom channel * Correct params * No more crying * Update Content.Shared/Implants/Components/RadioImplantComponent.cs * Update Content.Server/Implants/RadioImplantSystem.cs * Update Content.Server/Implants/RadioImplantSystem.cs --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * New Highpop map: Convex recreational complex (#33346) * start ig * command work +AI * finished bridge, added signs to departments * engi partly done * finish engi + atmos except for cams * added med yippie * carGODO * nerd department finished * se(x)c finish * courtroom + service power * man im tweaking out * service propa done * hallways * added cams everywhere and named all the doors :pain: * arrivals maints + exterior catwalk * more maints * voxob * draft done * holopadpilled + gaspipesensor based * added convex name everywhere it needed to be * nvm i guess we dont do oxford comma * redid ALL THE HGOLOPADS FAAAAAAAAAAAAAAAAAAAAA * Fix conflicts? * ok let me try something * this works right? * atmos updated + jani lights * uniform hallway tiles +roundstart TEG * fixed some hidden atmos stuff * Update map_attributions.txt * :finnadie: no map review ... * the fog is coming * maints theater redone * light dirt and the murder of most trims * 💔 * decal based and tileoverlay pilled * updated arrivals/library * bungus * yvgh * 6 hours later... * finished for real? * Automatic changelog update * Update cigarette.yml (#34756) simple change to one of Dan's Smokes that fixes a typo. * Seperate EMAG into EMAG and Authentication Disruptor (#34337) * Automatic changelog update * Astro Asteroid Sand (#34463) * Added astro asteroid sand * fixed the stack name * adjusted tile sprites for asteroid and ice * Adjusted meta.json * Realized the other tile sprites all have solid lines on their perimeters * Update Resources/Prototypes/Stacks/floor_tile_stacks.yml that works Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> * Update tiles.yml * Update Resources/Locale/en-US/tiles/tiles.ftl Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> * adjusted the snow sprite * Update meta.json --------- Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> * Automatic changelog update * The Goliath Hardsuit (#34721) * Entities for hardsuit and helmet, graph and crafting recipe; modifies goliath hide drop rate * Added hardsuit sprites, corrected some crafting YAML * fixed crafting recipe, adjusted resource requirements * Added durathread material req --------- Co-authored-by: august-sun <45527070+august.sun@users.noreply.github.com> * Automatic changelog update * make RefreshOverlay default to the player session (#32354) * Loop station minor tweaks (#34758) * small fixes * eee * Juice that makes you go boom (#34730) * Juice that makes you go boom * moved explosive juice to fun yml - fixed ExplosionReactionEffect.cs not having TileBreakScale parameter - made Drazil plushie major contraband (they are evil!!!!!) * removed JASON!!!! JASOOON!!! JASON!!! * don't do commits at 1am * Update fun.yml fix ident * no more bullying the server (only 1 explosion) * Automatic changelog update * Adjust inventory size of ginormous scrap (#33454) Adjusts the size of ginormous scrap to fit in bags of holding * Automatic changelog update * After getting banned, you now have to re-read the rules! (#33270) * first commit * opps * Reset cooldown instead * Added ccvar * Not replicated! * More robust Particle Accelerator menu (retry) (#34037) Particle Acceleratir fixed Co-authored-by: VideoKompany <135313844+VlaDOS1408@users.noreply.github.com> * Serializable emag flags (#34766) * serializable idk * guh * Fix Double Muzzle Flash (#33981) * Pass user to effects to properly fix double muzzle flash. * Use gun when user is null. * Update MuzzleFlashEvent.cs * Update MuzzleFlashEvent.cs * Update GunSystem.cs * Update SharedGunSystem.cs * Update MuzzleFlashEvent.cs * Update SharedGunSystem.cs * Update SharedGunSystem.cs * Update Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> --------- Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> * Automatic changelog update * fix BankClientComponent never updating (#33123) * fix * oh right * Add history tab to bounty console (#33932) * Add struct for holding historical data on cargo bounties * Add localisation strings for bounty history * Add new XAML entry for display bounty history * Expand cargo bounty menu to include tabs * Ensure station databases hold historical bounty data * Add to the bounty history when removing one from active * Feed bounty history into cargo's bounty system * Move tab title setting to constructor * Remove redundant access specifications * Remove un-needed override * Fixup BountyHistoryEntry backing code * Fix formatting in CargoBountyMenu * Reformat BountyHistoryData * Rework TryRemoveBounty to use new Entity type * Add Enum for showing bounty results * Rework look and feel of History tab * Add visible text when no bounties have been completed yet * Remove control * Swap default to null * Reverse ordering of bounties so last entry comes first * Remove redundant Visible * Move enum docs into the enum * Automatic changelog update * Minor shotgun changes and comments for future changes (#33512) * kammerer ammo, firerate and comment * bulldog description * enforcer description and comment * DB and sawn-off firerate parity, sawn-off comment * description changes * reduce kammerer firerate again * Automatic changelog update * Fixed: Ore now correctly drops the right amount of ore (#34557) Fixed bug * Automatic changelog update * Implement mrp rule changes pursuant to community votes (#34772) * #34771 Fix door system assuming all door layer states are in single RSIs (#34775) * #34771 Fix door system assuming all door layer states are in single RSIs * Delta confirmed monarch of sloggery * Do a dirty, rotten web edit Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * HOTFIX update staging submodule to 242.0.1 (#34833) * HOTFIX revert lecter visual update #34589 (#34826) * Allowed windows to be properly clicked on (#34751) * Fix hitting through directional windows (and more!) (#34793) * Revert "Add taped logo back for 10th anniversary" (#34831) * Hristov & .60 changes - Hristov Rework, Part 2 (#31662) * Initial commit * Updated values to reflect new resistances * Review fixes * Review fixes * LINQ BEGONETH * revert engine * Revert "Storage sidebar fix (#34680)" This reverts commit3e091c4dfa. * Revert "Storage UI V2 (#33045)" This reverts commitfd25dac720. * Revert "Update ScalingViewport for Engine PR (#28786)" This reverts commit3ad83378bb. * Update physical.yml * Update CP14SharpeningSystem.cs --------- Co-authored-by: Southbridge <7013162+southbridge-fur@users.noreply.github.com> Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> Co-authored-by: c4llv07e <igor@c4llv07e.xyz> Co-authored-by: PursuitInAshes <91865152+PursuitInAshes@users.noreply.github.com> Co-authored-by: TakoDragon <69509841+BackeTako@users.noreply.github.com> Co-authored-by: Minemoder5000 <minemoder50000@gmail.com> Co-authored-by: Tobias Berger <toby@tobot.dev> Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com> Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> Co-authored-by: Partmedia <kevinz5000@gmail.com> Co-authored-by: Mono <182929384+Monotheonist@users.noreply.github.com> Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com> Co-authored-by: Spessmann <156740760+Spessmann@users.noreply.github.com> Co-authored-by: Myra <vasilis@pikachu.systems> Co-authored-by: Winkarst <74284083+Winkarst-cpu@users.noreply.github.com> Co-authored-by: Winkarst <74284083+Winkarst-cpu@users.noreply.github.co> Co-authored-by: Nox <nebulousnox38@gmail.com> Co-authored-by: K-Dynamic <20566341+K-Dynamic@users.noreply.github.com> Co-authored-by: War Pigeon <54217755+minus1over12@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: Alfred Baumann <93665570+CheesePlated@users.noreply.github.com> Co-authored-by: Tezzaide <ewankayne@hotmail.co.uk> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: 0x6273 <0x40@keemail.me> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Co-authored-by: compilatron <40789662+Compilatron144@users.noreply.github.com> Co-authored-by: jbox1 <40789662+jbox144@users.noreply.github.com> Co-authored-by: John <35928781+sporkyz@users.noreply.github.com> Co-authored-by: Nim <128169402+Nimfar11@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com> Co-authored-by: pathetic meowmeow <uhhadd@gmail.com> Co-authored-by: Ignaz "Ian" Kraft <ignaz.k@live.de> Co-authored-by: Fildrance <fildrance@gmail.com> Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru> Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> Co-authored-by: JustinWinningham <justinmwinningham@gmail.com> Co-authored-by: NathanielJ14 <nathanielalbert1202@gmail.com> Co-authored-by: Velken <8467292+Velken@users.noreply.github.com> Co-authored-by: Deerstop <edainturner@gmail.com> Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com> Co-authored-by: Milon <milonpl.git@proton.me> Co-authored-by: CaasGit <87243814+CaasGit@users.noreply.github.com> Co-authored-by: Coolsurf6 <coolsurf24@yahoo.com.au> Co-authored-by: SlimSlam <73899110+SlimmSlamm@users.noreply.github.com> Co-authored-by: Dinner <180707738+DinnerCalzone@users.noreply.github.com> Co-authored-by: Helm4142 <158806576+Helm4142@users.noreply.github.com> Co-authored-by: Helm4142 <Helm4142@users.noreply.github.com> Co-authored-by: kosticia <kosticia46@gmail.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: August Sun <45527070+august-sun@users.noreply.github.com> Co-authored-by: august-sun <45527070+august.sun@users.noreply.github.com> Co-authored-by: NakataRin <45946146+NakataRin@users.noreply.github.com> Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Co-authored-by: Zachary Higgs <compgeek223@gmail.com> Co-authored-by: TytosB <duanlaintytos@yahoo.com> Co-authored-by: Orange-Winds <orange.wind@outlook.com> Co-authored-by: Orange-Winds <sebastian.pelka@hotmail.com> Co-authored-by: TytosB <dunalintytos@yahoo.com> Co-authored-by: themias <89101928+themias@users.noreply.github.com> Co-authored-by: godisdeadLOL <169250097+godisdeadLOL@users.noreply.github.com> Co-authored-by: YourUsername <you@example.com> Co-authored-by: James Simonson <jamessimo89@gmail.com> Co-authored-by: darkdan <145926356+Starbuckss14@users.noreply.github.com> Co-authored-by: Preston Smith <92108534+thetolbean@users.noreply.github.com> Co-authored-by: DR-DOCTOR-EVIL-EVIL <hudsonirwin11@icloud.com> Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Co-authored-by: SpaceRox1244 <138547931+SpaceRox1244@users.noreply.github.com> Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Co-authored-by: VideoKompany <135313844+VlaDOS1408@users.noreply.github.com> Co-authored-by: sleepyyapril <123355664+sleepyyapril@users.noreply.github.com> Co-authored-by: BarryNorfolk <barrynorfolkman@protonmail.com> Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Co-authored-by: Hannah Giovanna Dawson <karakkaraz@gmail.com> Co-authored-by: pubbi <63283968+impubbi@users.noreply.github.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server.Access;
|
||||
@@ -31,11 +30,9 @@ public sealed partial class AccessWireAction : ComponentWireAction<AccessReaderC
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
if (!EntityManager.HasComponent<EmaggedComponent>(wire.Owner))
|
||||
{
|
||||
comp.Enabled = true;
|
||||
EntityManager.Dirty(wire.Owner, comp);
|
||||
}
|
||||
comp.Enabled = true;
|
||||
EntityManager.Dirty(wire.Owner, comp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -58,7 +55,7 @@ public sealed partial class AccessWireAction : ComponentWireAction<AccessReaderC
|
||||
{
|
||||
if (!wire.IsCut)
|
||||
{
|
||||
if (EntityManager.TryGetComponent<AccessReaderComponent>(wire.Owner, out var access) && !EntityManager.HasComponent<EmaggedComponent>(wire.Owner))
|
||||
if (EntityManager.TryGetComponent<AccessReaderComponent>(wire.Owner, out var access))
|
||||
{
|
||||
access.Enabled = true;
|
||||
EntityManager.Dirty(wire.Owner, access);
|
||||
|
||||
@@ -245,6 +245,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
||||
$"{ToPrettyString(player):player} has modified {ToPrettyString(accessReaderEnt.Value):entity} with the following allowed access level holders: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
|
||||
|
||||
accessReaderEnt.Value.Comp.AccessLists = ConvertAccessListToHashSet(newAccessList);
|
||||
|
||||
var ev = new OnAccessOverriderAccessUpdatedEvent(player);
|
||||
RaiseLocalEvent(component.TargetAccessReaderId, ref ev);
|
||||
|
||||
Dirty(accessReaderEnt.Value);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Server.Database;
|
||||
|
||||
namespace Content.Server.Administration.Managers;
|
||||
|
||||
@@ -30,36 +28,15 @@ public sealed partial class BanManager
|
||||
private TimeSpan _banNotificationRateLimitStart;
|
||||
private int _banNotificationRateLimitCount;
|
||||
|
||||
private void OnDatabaseNotification(DatabaseNotification notification)
|
||||
private bool OnDatabaseNotificationEarlyFilter()
|
||||
{
|
||||
if (notification.Channel != BanNotificationChannel)
|
||||
return;
|
||||
|
||||
if (notification.Payload == null)
|
||||
{
|
||||
_sawmill.Error("Got ban notification with null payload!");
|
||||
return;
|
||||
}
|
||||
|
||||
BanNotificationData data;
|
||||
try
|
||||
{
|
||||
data = JsonSerializer.Deserialize<BanNotificationData>(notification.Payload)
|
||||
?? throw new JsonException("Content is null");
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
_sawmill.Error($"Got invalid JSON in ban notification: {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckBanRateLimit())
|
||||
{
|
||||
_sawmill.Verbose("Not processing ban notification due to rate limit");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_taskManager.RunOnMainThread(() => ProcessBanNotification(data));
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void ProcessBanNotification(BanNotificationData data)
|
||||
|
||||
@@ -53,7 +53,12 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
{
|
||||
_netManager.RegisterNetMessage<MsgRoleBans>();
|
||||
|
||||
_db.SubscribeToNotifications(OnDatabaseNotification);
|
||||
_db.SubscribeToJsonNotification<BanNotificationData>(
|
||||
_taskManager,
|
||||
_sawmill,
|
||||
BanNotificationChannel,
|
||||
ProcessBanNotification,
|
||||
OnDatabaseNotificationEarlyFilter);
|
||||
|
||||
_userDbData.AddOnLoadPlayer(CachePlayerData);
|
||||
_userDbData.AddOnPlayerDisconnect(ClearPlayerData);
|
||||
@@ -160,6 +165,8 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
null);
|
||||
|
||||
await _db.AddServerBanAsync(banDef);
|
||||
if (_cfg.GetCVar(CCVars.ServerBanResetLastReadRules) && target != null)
|
||||
await _db.SetLastReadRules(target.Value, null); // Reset their last read rules. They probably need a refresher!
|
||||
var adminName = banningAdmin == null
|
||||
? Loc.GetString("system-user")
|
||||
: (await _db.GetPlayerRecordByUserId(banningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user");
|
||||
|
||||
114
Content.Server/Administration/Managers/MultiServerKickManager.cs
Normal file
114
Content.Server/Administration/Managers/MultiServerKickManager.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Administration.Managers;
|
||||
|
||||
/// <summary>
|
||||
/// Handles kicking people that connect to multiple servers on the same DB at once.
|
||||
/// </summary>
|
||||
/// <seealso cref="CCVars.AdminAllowMultiServerPlay"/>
|
||||
public sealed class MultiServerKickManager
|
||||
{
|
||||
public const string NotificationChannel = "multi_server_kick";
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = null!;
|
||||
[Dependency] private readonly IServerDbManager _dbManager = null!;
|
||||
[Dependency] private readonly ILogManager _logManager = null!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = null!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = null!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = null!;
|
||||
[Dependency] private readonly IServerNetManager _netManager = null!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = null!;
|
||||
[Dependency] private readonly ServerDbEntryManager _serverDbEntry = null!;
|
||||
|
||||
private ISawmill _sawmill = null!;
|
||||
private bool _allowed;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("multi_server_kick");
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
_cfg.OnValueChanged(CCVars.AdminAllowMultiServerPlay, b => _allowed = b, true);
|
||||
|
||||
_dbManager.SubscribeToJsonNotification<NotificationData>(
|
||||
_taskManager,
|
||||
_sawmill,
|
||||
NotificationChannel,
|
||||
OnNotification,
|
||||
OnNotificationEarlyFilter
|
||||
);
|
||||
}
|
||||
|
||||
// ReSharper disable once AsyncVoidMethod
|
||||
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (_allowed)
|
||||
return;
|
||||
|
||||
if (e.NewStatus != SessionStatus.InGame)
|
||||
return;
|
||||
|
||||
// Send notification to other servers so they can kick this player that just connected.
|
||||
try
|
||||
{
|
||||
await _dbManager.SendNotification(new DatabaseNotification
|
||||
{
|
||||
Channel = NotificationChannel,
|
||||
Payload = JsonSerializer.Serialize(new NotificationData
|
||||
{
|
||||
PlayerId = e.Session.UserId,
|
||||
ServerId = (await _serverDbEntry.ServerEntity).Id,
|
||||
}),
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sawmill.Error($"Failed to send notification for multi server kick: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnNotificationEarlyFilter()
|
||||
{
|
||||
if (_allowed)
|
||||
{
|
||||
_sawmill.Verbose("Received notification for player join, but multi server play is allowed on this server. Ignoring");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ReSharper disable once AsyncVoidMethod
|
||||
private async void OnNotification(NotificationData notification)
|
||||
{
|
||||
if (!_playerManager.TryGetSessionById(new NetUserId(notification.PlayerId), out var player))
|
||||
return;
|
||||
|
||||
if (notification.ServerId == (await _serverDbEntry.ServerEntity).Id)
|
||||
return;
|
||||
|
||||
if (_adminManager.IsAdmin(player, includeDeAdmin: true))
|
||||
return;
|
||||
|
||||
_sawmill.Info($"Kicking {player} for connecting to another server. Multi-server play is not allowed.");
|
||||
_netManager.DisconnectChannel(player.Channel, _loc.GetString("multi-server-kick-reason"));
|
||||
}
|
||||
|
||||
private sealed class NotificationData
|
||||
{
|
||||
[JsonPropertyName("player_id")]
|
||||
public Guid PlayerId { get; set; }
|
||||
|
||||
[JsonPropertyName("server_id")]
|
||||
public int ServerId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -116,8 +116,11 @@ public sealed class TechAnomalySystem : EntitySystem
|
||||
|
||||
if (_random.Prob(tech.Comp.EmagSupercritProbability))
|
||||
{
|
||||
_emag.DoEmagEffect(tech, source);
|
||||
_emag.DoEmagEffect(tech, sink);
|
||||
var sourceEv = new GotEmaggedEvent(tech, EmagType.Access | EmagType.Interaction);
|
||||
RaiseLocalEvent(source, ref sourceEv);
|
||||
|
||||
var sinkEv = new GotEmaggedEvent(tech, EmagType.Access | EmagType.Interaction);
|
||||
RaiseLocalEvent(sink, ref sinkEv);
|
||||
}
|
||||
|
||||
CreateNewLink(tech, source, sink);
|
||||
|
||||
@@ -21,6 +21,7 @@ public sealed class FireAlarmSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
||||
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _access = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
@@ -77,11 +78,18 @@ public sealed class FireAlarmSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, FireAlarmComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (TryComp<AtmosAlarmableComponent>(uid, out var alarmable))
|
||||
{
|
||||
// Remove the atmos alarmable component permanently from this device.
|
||||
_atmosAlarmable.ForceAlert(uid, AtmosAlarmType.Emagged, alarmable);
|
||||
RemCompDeferred<AtmosAlarmableComponent>(uid);
|
||||
}
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (!TryComp<AtmosAlarmableComponent>(uid, out var alarmable))
|
||||
return;
|
||||
|
||||
// Remove the atmos alarmable component permanently from this device.
|
||||
_atmosAlarmable.ForceAlert(uid, AtmosAlarmType.Emagged, alarmable);
|
||||
RemCompDeferred<AtmosAlarmableComponent>(uid);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Binary.Components
|
||||
{
|
||||
@@ -38,6 +39,7 @@ namespace Content.Server.Atmos.Piping.Binary.Components
|
||||
public float LowerThreshold { get; set; } = 0.01f;
|
||||
|
||||
[DataField("higherThreshold")]
|
||||
[GuidebookData]
|
||||
public float HigherThreshold { get; set; } = DefaultHigherThreshold;
|
||||
public static readonly float DefaultHigherThreshold = 2 * Atmospherics.MaxOutputPressure;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Trinary.Components
|
||||
{
|
||||
@@ -27,6 +28,7 @@ namespace Content.Server.Atmos.Piping.Trinary.Components
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("threshold")]
|
||||
[GuidebookData]
|
||||
public float Threshold { get; set; } = Atmospherics.OneAtmosphere;
|
||||
|
||||
[DataField("maxTransferRate")]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
@@ -61,5 +62,12 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
|
||||
[DataField("accessDeniedSound")]
|
||||
public SoundSpecifier AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
|
||||
|
||||
#region GuidebookData
|
||||
|
||||
[GuidebookData]
|
||||
public float Volume => Air.Volume;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Atmos.Piping.Binary.Components;
|
||||
using Content.Server.Atmos.Piping.Unary.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
{
|
||||
@@ -29,6 +30,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
public float MaxTransferRate = Atmospherics.MaxTransferRate;
|
||||
|
||||
[DataField("maxPressure")]
|
||||
[GuidebookData]
|
||||
public float MaxPressure { get; set; } = GasVolumePumpComponent.DefaultHigherThreshold;
|
||||
|
||||
[DataField("inlet")]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
{
|
||||
@@ -13,6 +14,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// thermomachine to heat or cool air.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float HeatCapacity = 5000;
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -21,6 +23,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// <summary>
|
||||
/// Tolerance for temperature setpoint hysteresis.
|
||||
/// </summary>
|
||||
[GuidebookData]
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public float TemperatureTolerance = 2f;
|
||||
|
||||
@@ -44,6 +47,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// Ignored if heater.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float MinTemperature = 73.15f;
|
||||
|
||||
/// <summary>
|
||||
@@ -51,6 +55,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// Ignored if freezer.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float MaxTemperature = 593.15f;
|
||||
|
||||
/// <summary>
|
||||
@@ -63,6 +68,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// An percentage of the energy change that is leaked into the surrounding environment rather than the inlet pipe.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float EnergyLeakPercentage;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
@@ -35,6 +36,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// In releasing mode, do not pump when environment pressure is below this limit.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[GuidebookData]
|
||||
public float UnderPressureLockoutThreshold = 80; // this must be tuned in conjunction with atmos.mmos_spacing_speed
|
||||
|
||||
/// <summary>
|
||||
@@ -58,7 +60,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
[DataField]
|
||||
public bool IsPressureLockoutManuallyDisabled = false;
|
||||
/// <summary>
|
||||
/// The time when the manual pressure lockout will be reenabled.
|
||||
/// The time when the manual pressure lockout will be reenabled.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[AutoPausedField]
|
||||
@@ -101,6 +103,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// Max pressure of the target gas (NOT relative to source).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[GuidebookData]
|
||||
public float MaxPressure = Atmospherics.MaxOutputPressure;
|
||||
|
||||
/// <summary>
|
||||
@@ -172,5 +175,12 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
InternalPressureBound = data.InternalPressureBound;
|
||||
PressureLockoutOverride = data.PressureLockoutOverride;
|
||||
}
|
||||
|
||||
#region GuidebookData
|
||||
|
||||
[GuidebookData]
|
||||
public float DefaultExternalBound => Atmospherics.OneAtmosphere;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,15 @@ public sealed class GasCanisterSystem : EntitySystem
|
||||
|
||||
var item = canister.GasTankSlot.Item;
|
||||
_slots.TryEjectToHands(uid, canister.GasTankSlot, args.Actor);
|
||||
_adminLogger.Add(LogType.CanisterTankEjected, LogImpact.Medium, $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister}");
|
||||
|
||||
if (canister.ReleaseValve)
|
||||
{
|
||||
_adminLogger.Add(LogType.CanisterTankEjected, LogImpact.High, $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister} while the valve was open, releasing [{GetContainedGasesString((uid, canister))}] to atmosphere");
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.CanisterTankEjected, LogImpact.Medium, $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCanisterChangeReleasePressure(EntityUid uid, GasCanisterComponent canister, GasCanisterChangeReleasePressureMessage args)
|
||||
@@ -124,24 +132,24 @@ public sealed class GasCanisterSystem : EntitySystem
|
||||
|
||||
private void OnCanisterChangeReleaseValve(EntityUid uid, GasCanisterComponent canister, GasCanisterChangeReleaseValveMessage args)
|
||||
{
|
||||
var impact = LogImpact.High;
|
||||
// filling a jetpack with plasma is less important than filling a room with it
|
||||
impact = canister.GasTankSlot.HasItem ? LogImpact.Medium : LogImpact.High;
|
||||
var hasItem = canister.GasTankSlot.HasItem;
|
||||
var impact = hasItem ? LogImpact.Medium : LogImpact.High;
|
||||
|
||||
var containedGasDict = new Dictionary<Gas, float>();
|
||||
var containedGasArray = Enum.GetValues(typeof(Gas));
|
||||
|
||||
for (int i = 0; i < containedGasArray.Length; i++)
|
||||
{
|
||||
containedGasDict.Add((Gas)i, canister.Air[i]);
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.CanisterValve, impact, $"{ToPrettyString(args.Actor):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState} while it contained [{string.Join(", ", containedGasDict)}]");
|
||||
_adminLogger.Add(
|
||||
LogType.CanisterValve,
|
||||
impact,
|
||||
$"{ToPrettyString(args.Actor):player} {(args.Valve ? "opened" : "closed")} the valve on {ToPrettyString(uid):canister} to {(hasItem ? "inserted tank" : "environment")} while it contained [{GetContainedGasesString((uid, canister))}]");
|
||||
|
||||
canister.ReleaseValve = args.Valve;
|
||||
DirtyUI(uid, canister);
|
||||
}
|
||||
|
||||
private static string GetContainedGasesString(Entity<GasCanisterComponent> canister)
|
||||
{
|
||||
return string.Join(", ", canister.Comp.Air);
|
||||
}
|
||||
|
||||
private void OnCanisterUpdated(EntityUid uid, GasCanisterComponent canister, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
_atmos.React(canister.Air, canister);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Portable
|
||||
{
|
||||
@@ -45,5 +46,12 @@ namespace Content.Server.Atmos.Portable
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TransferRate = 800;
|
||||
|
||||
#region GuidebookData
|
||||
|
||||
[GuidebookData]
|
||||
public float Volume => Air.Volume;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Piping.Portable.Components;
|
||||
using Content.Shared.Atmos.Visuals;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Portable;
|
||||
|
||||
@@ -23,12 +24,14 @@ public sealed partial class SpaceHeaterComponent : Component
|
||||
/// Maximum target temperature the device can be set to
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float MaxTemperature = Atmospherics.T20C + 20;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal target temperature the device can be set to
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float MinTemperature = Atmospherics.T0C - 10;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Content.Server.Bed
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SleepingSystem _sleepingSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
@@ -114,7 +115,12 @@ namespace Content.Server.Bed
|
||||
|
||||
private void OnEmagged(EntityUid uid, StasisBedComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
args.Repeatable = true;
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
// Reset any metabolisms first so they receive the multiplier correctly
|
||||
UpdateMetabolisms(uid, component, false);
|
||||
component.Multiplier = 1 / component.Multiplier;
|
||||
|
||||
@@ -12,15 +12,22 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
|
||||
/// <summary>
|
||||
/// Maximum amount of bounties a station can have.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public int MaxBounties = 6;
|
||||
|
||||
/// <summary>
|
||||
/// A list of all the bounties currently active for a station.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public List<CargoBountyData> Bounties = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list of all the bounties that have been completed or
|
||||
/// skipped for a station.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<CargoBountyHistoryData> History = new();
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine unique order IDs
|
||||
/// </summary>
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Content.Shared.Paper;
|
||||
using Content.Shared.Stacks;
|
||||
@@ -16,6 +17,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
@@ -25,6 +27,7 @@ public sealed partial class CargoSystem
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
[ValidatePrototypeId<NameIdentifierGroupPrototype>]
|
||||
private const string BountyNameIdentifierGroup = "Bounty";
|
||||
@@ -54,7 +57,7 @@ public sealed partial class CargoSystem
|
||||
return;
|
||||
|
||||
var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime;
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip));
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, bountyDb.History, untilNextSkip));
|
||||
}
|
||||
|
||||
private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
|
||||
@@ -95,13 +98,13 @@ public sealed partial class CargoSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryRemoveBounty(station, bounty.Value))
|
||||
if (!TryRemoveBounty(station, bounty.Value, true, args.Actor))
|
||||
return;
|
||||
|
||||
FillBountyDatabase(station);
|
||||
db.NextSkipTime = _timing.CurTime + db.SkipDelay;
|
||||
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
|
||||
_audio.PlayPvs(component.SkipSound, uid);
|
||||
}
|
||||
|
||||
@@ -179,7 +182,7 @@ public sealed partial class CargoSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
TryRemoveBounty(station, bounty.Value);
|
||||
TryRemoveBounty(station, bounty.Value, false);
|
||||
FillBountyDatabase(station);
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"Bounty \"{bounty.Value.Bounty}\" (id:{bounty.Value.Id}) was fulfilled");
|
||||
}
|
||||
@@ -434,24 +437,44 @@ public sealed partial class CargoSystem
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryRemoveBounty(EntityUid uid, string dataId, StationCargoBountyDatabaseComponent? component = null)
|
||||
public bool TryRemoveBounty(Entity<StationCargoBountyDatabaseComponent?> ent,
|
||||
string dataId,
|
||||
bool skipped,
|
||||
EntityUid? actor = null)
|
||||
{
|
||||
if (!TryGetBountyFromId(uid, dataId, out var data, component))
|
||||
if (!TryGetBountyFromId(ent.Owner, dataId, out var data, ent.Comp))
|
||||
return false;
|
||||
|
||||
return TryRemoveBounty(uid, data.Value, component);
|
||||
return TryRemoveBounty(ent, data.Value, skipped, actor);
|
||||
}
|
||||
|
||||
public bool TryRemoveBounty(EntityUid uid, CargoBountyData data, StationCargoBountyDatabaseComponent? component = null)
|
||||
public bool TryRemoveBounty(Entity<StationCargoBountyDatabaseComponent?> ent,
|
||||
CargoBountyData data,
|
||||
bool skipped,
|
||||
EntityUid? actor = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < component.Bounties.Count; i++)
|
||||
for (var i = 0; i < ent.Comp.Bounties.Count; i++)
|
||||
{
|
||||
if (component.Bounties[i].Id == data.Id)
|
||||
if (ent.Comp.Bounties[i].Id == data.Id)
|
||||
{
|
||||
component.Bounties.RemoveAt(i);
|
||||
string? actorName = null;
|
||||
if (actor != null)
|
||||
{
|
||||
var getIdentityEvent = new TryGetIdentityShortInfoEvent(ent.Owner, actor.Value);
|
||||
RaiseLocalEvent(getIdentityEvent);
|
||||
actorName = getIdentityEvent.Title;
|
||||
}
|
||||
|
||||
ent.Comp.History.Add(new CargoBountyHistoryData(data,
|
||||
skipped
|
||||
? CargoBountyHistoryData.BountyResult.Skipped
|
||||
: CargoBountyHistoryData.BountyResult.Completed,
|
||||
_gameTiming.CurTime,
|
||||
actorName));
|
||||
ent.Comp.Bounties.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -492,7 +515,7 @@ public sealed partial class CargoSystem
|
||||
}
|
||||
|
||||
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
||||
_uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
|
||||
_uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Events;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Paper;
|
||||
@@ -21,6 +21,7 @@ namespace Content.Server.Cargo.Systems
|
||||
public sealed partial class CargoSystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How much time to wait (in seconds) before increasing bank accounts balance.
|
||||
@@ -41,6 +42,7 @@ namespace Content.Server.Cargo.Systems
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, BankBalanceUpdatedEvent>(OnOrderBalanceUpdated);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, GotEmaggedEvent>(OnEmagged);
|
||||
Reset();
|
||||
}
|
||||
|
||||
@@ -75,6 +77,17 @@ namespace Content.Server.Cargo.Systems
|
||||
_timer = 0;
|
||||
}
|
||||
|
||||
private void OnEmagged(Entity<CargoOrderConsoleComponent> ent, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(ent, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void UpdateConsole(float frameTime)
|
||||
{
|
||||
_timer += frameTime;
|
||||
@@ -85,9 +98,11 @@ namespace Content.Server.Cargo.Systems
|
||||
{
|
||||
_timer -= Delay;
|
||||
|
||||
foreach (var account in EntityQuery<StationBankAccountComponent>())
|
||||
var stationQuery = EntityQueryEnumerator<StationBankAccountComponent>();
|
||||
while (stationQuery.MoveNext(out var uid, out var bank))
|
||||
{
|
||||
account.Balance += account.IncreasePerSecond * Delay;
|
||||
var balanceToAdd = bank.IncreasePerSecond * Delay;
|
||||
UpdateBankAccount(uid, bank, balanceToAdd);
|
||||
}
|
||||
|
||||
var query = EntityQueryEnumerator<CargoOrderConsoleComponent>();
|
||||
@@ -192,7 +207,7 @@ namespace Content.Server.Cargo.Systems
|
||||
order.Approved = true;
|
||||
_audio.PlayPvs(component.ConfirmSound, uid);
|
||||
|
||||
if (!HasComp<EmaggedComponent>(uid))
|
||||
if (!_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(uid, player);
|
||||
RaiseLocalEvent(tryGetIdentityShortInfoEvent);
|
||||
@@ -213,7 +228,7 @@ namespace Content.Server.Cargo.Systems
|
||||
$"{ToPrettyString(player):user} approved order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}] with balance at {bank.Balance}");
|
||||
|
||||
orderDatabase.Orders.Remove(order);
|
||||
DeductFunds(bank, cost);
|
||||
UpdateBankAccount(station.Value, bank, -cost);
|
||||
UpdateOrders(station.Value);
|
||||
}
|
||||
|
||||
@@ -536,11 +551,6 @@ namespace Content.Server.Cargo.Systems
|
||||
|
||||
}
|
||||
|
||||
private void DeductFunds(StationBankAccountComponent component, int amount)
|
||||
{
|
||||
component.Balance = Math.Max(0, component.Balance - amount);
|
||||
}
|
||||
|
||||
#region Station
|
||||
|
||||
private bool TryGetOrderDatabase([NotNullWhen(true)] EntityUid? stationUid, [MaybeNullWhen(false)] out StationCargoOrderDatabaseComponent dbComp)
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace Content.Server.Cloning
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
[Dependency] private readonly SharedJobSystem _jobs = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public readonly Dictionary<MindComponent, EntityUid> ClonesWaitingForMind = new();
|
||||
public const float EasyModeCloningCost = 0.7f;
|
||||
@@ -276,10 +277,15 @@ namespace Content.Server.Cloning
|
||||
/// </summary>
|
||||
private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(clonePod.SparkSound, uid);
|
||||
_popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid);
|
||||
args.Handled = true;
|
||||
}
|
||||
@@ -309,7 +315,7 @@ namespace Content.Server.Cloning
|
||||
var indices = _transformSystem.GetGridTilePositionOrDefault((uid, transform));
|
||||
var tileMix = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true);
|
||||
|
||||
if (HasComp<EmaggedComponent>(uid))
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
_audio.PlayPvs(clonePod.ScreamSound, uid);
|
||||
Spawn(clonePod.MobSpawnId, transform.Coordinates);
|
||||
@@ -327,7 +333,7 @@ namespace Content.Server.Cloning
|
||||
}
|
||||
_puddleSystem.TrySpillAt(uid, bloodSolution, out _);
|
||||
|
||||
if (!HasComp<EmaggedComponent>(uid))
|
||||
if (!_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
_material.SpawnMultipleFromMaterial(_robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ using Content.Shared.Chat;
|
||||
using Content.Shared.Communications;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -177,7 +176,7 @@ namespace Content.Server.Communications
|
||||
|
||||
private bool CanUse(EntityUid user, EntityUid console)
|
||||
{
|
||||
if (TryComp<AccessReaderComponent>(console, out var accessReaderComponent) && !HasComp<EmaggedComponent>(console))
|
||||
if (TryComp<AccessReaderComponent>(console, out var accessReaderComponent))
|
||||
{
|
||||
return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent);
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ public sealed class FixRotationsCommand : IConsoleCommand
|
||||
valid |= tagSystem.HasTag(child, "ForceFixRotations");
|
||||
// override
|
||||
valid &= !tagSystem.HasTag(child, "ForceNoFixRotations");
|
||||
// remove diagonal entities as well
|
||||
valid &= !tagSystem.HasTag(child, "Diagonal");
|
||||
|
||||
if (!valid)
|
||||
continue;
|
||||
|
||||
@@ -13,6 +13,8 @@ using Robust.Server.GameObjects;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Security.Components;
|
||||
using System.Linq;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
|
||||
namespace Content.Server.CriminalRecords.Systems;
|
||||
|
||||
@@ -42,6 +44,7 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
subs.Event<CriminalRecordChangeStatus>(OnChangeStatus);
|
||||
subs.Event<CriminalRecordAddHistory>(OnAddHistory);
|
||||
subs.Event<CriminalRecordDeleteHistory>(OnDeleteHistory);
|
||||
subs.Event<CriminalRecordSetStatusFilter>(OnStatusFilterPressed);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,6 +60,11 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
ent.Comp.ActiveKey = msg.SelectedKey;
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
private void OnStatusFilterPressed(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordSetStatusFilter msg)
|
||||
{
|
||||
ent.Comp.FilterStatus = msg.FilterStatus;
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
|
||||
private void OnFiltersChanged(Entity<CriminalRecordsConsoleComponent> ent, ref SetStationRecordFilter msg)
|
||||
{
|
||||
@@ -112,13 +120,26 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
}
|
||||
|
||||
// will probably never fail given the checks above
|
||||
name = _records.RecordName(key.Value);
|
||||
officer = Loc.GetString("criminal-records-console-unknown-officer");
|
||||
var jobName = "Unknown";
|
||||
|
||||
_records.TryGetRecord<GeneralStationRecord>(key.Value, out var entry);
|
||||
if (entry != null)
|
||||
jobName = entry.JobTitle;
|
||||
|
||||
var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(null, mob.Value);
|
||||
RaiseLocalEvent(tryGetIdentityShortInfoEvent);
|
||||
if (tryGetIdentityShortInfoEvent.Title != null)
|
||||
officer = tryGetIdentityShortInfoEvent.Title;
|
||||
|
||||
_criminalRecords.TryChangeStatus(key.Value, msg.Status, msg.Reason, officer);
|
||||
|
||||
(string, object)[] args;
|
||||
if (reason != null)
|
||||
args = new (string, object)[] { ("name", name), ("officer", officer), ("reason", reason) };
|
||||
args = new (string, object)[] { ("name", name), ("officer", officer), ("reason", reason), ("job", jobName) };
|
||||
else
|
||||
args = new (string, object)[] { ("name", name), ("officer", officer) };
|
||||
args = new (string, object)[] { ("name", name), ("officer", officer), ("job", jobName) };
|
||||
|
||||
// figure out which radio message to send depending on transition
|
||||
var statusString = (oldStatus, msg.Status) switch
|
||||
@@ -193,8 +214,18 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
return;
|
||||
}
|
||||
|
||||
// get the listing of records to display
|
||||
var listing = _records.BuildListing((owningStation.Value, stationRecords), console.Filter);
|
||||
|
||||
// filter the listing by the selected criminal record status
|
||||
//if NONE, dont filter by status, just show all crew
|
||||
if (console.FilterStatus != SecurityStatus.None)
|
||||
{
|
||||
listing = listing
|
||||
.Where(x => _records.TryGetRecord<CriminalRecord>(new StationRecordKey(x.Key, owningStation.Value), out var record) && record.Status == console.FilterStatus)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
}
|
||||
|
||||
var state = new CriminalRecordsConsoleState(listing, console.Filter);
|
||||
if (console.ActiveKey is { } id)
|
||||
{
|
||||
@@ -205,6 +236,9 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
state.SelectedKey = id;
|
||||
}
|
||||
|
||||
// Set the Current Tab aka the filter status type for the records list
|
||||
state.FilterStatus = console.FilterStatus;
|
||||
|
||||
_ui.SetUiState(uid, CriminalRecordsConsoleKey.Key, state);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Events;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Mobs;
|
||||
@@ -47,7 +48,7 @@ public sealed class DamageForceSaySystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void TryForceSay(EntityUid uid, DamageForceSayComponent component, bool useSuffix=true, string? suffixOverride = null)
|
||||
private void TryForceSay(EntityUid uid, DamageForceSayComponent component, bool useSuffix=true)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(uid, out var actor))
|
||||
return;
|
||||
@@ -57,7 +58,13 @@ public sealed class DamageForceSaySystem : EntitySystem
|
||||
_timing.CurTime < component.NextAllowedTime)
|
||||
return;
|
||||
|
||||
var suffix = Loc.GetString(suffixOverride ?? component.ForceSayStringPrefix + _random.Next(1, component.ForceSayStringCount));
|
||||
var ev = new BeforeForceSayEvent(component.ForceSayStringDataset);
|
||||
RaiseLocalEvent(uid, ev);
|
||||
|
||||
if (!_prototype.TryIndex(ev.Prefix, out var prefixList))
|
||||
return;
|
||||
|
||||
var suffix = Loc.GetString(_random.Pick(prefixList.Values));
|
||||
|
||||
// set cooldown & raise event
|
||||
component.NextAllowedTime = _timing.CurTime + component.Cooldown;
|
||||
@@ -80,7 +87,7 @@ public sealed class DamageForceSaySystem : EntitySystem
|
||||
if (!args.FellAsleep)
|
||||
return;
|
||||
|
||||
TryForceSay(uid, component, true, "damage-force-say-sleep");
|
||||
TryForceSay(uid, component);
|
||||
AllowNextSpeech(uid);
|
||||
}
|
||||
|
||||
|
||||
@@ -1112,7 +1112,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
.SingleOrDefaultAsync());
|
||||
}
|
||||
|
||||
public async Task SetLastReadRules(NetUserId player, DateTimeOffset date)
|
||||
public async Task SetLastReadRules(NetUserId player, DateTimeOffset? date)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
|
||||
@@ -1122,7 +1122,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
return;
|
||||
}
|
||||
|
||||
dbPlayer.LastReadRules = date.UtcDateTime;
|
||||
dbPlayer.LastReadRules = date?.UtcDateTime;
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
@@ -1801,6 +1801,8 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
|
||||
#endregion
|
||||
|
||||
public abstract Task SendNotification(DatabaseNotification notification);
|
||||
|
||||
// SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc.
|
||||
// Normalize DateTimes here so they're always Utc. Thanks.
|
||||
protected abstract DateTime NormalizeDatabaseTime(DateTime time);
|
||||
|
||||
@@ -282,7 +282,7 @@ namespace Content.Server.Database
|
||||
#region Rules
|
||||
|
||||
Task<DateTimeOffset?> GetLastReadRules(NetUserId player);
|
||||
Task SetLastReadRules(NetUserId player, DateTimeOffset time);
|
||||
Task SetLastReadRules(NetUserId player, DateTimeOffset? time);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -350,6 +350,15 @@ namespace Content.Server.Database
|
||||
/// <param name="notification">The notification to trigger</param>
|
||||
void InjectTestNotification(DatabaseNotification notification);
|
||||
|
||||
/// <summary>
|
||||
/// Send a notification to all other servers connected to the same database.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The local server will receive the sent notification itself again.
|
||||
/// </remarks>
|
||||
/// <param name="notification">The notification to send.</param>
|
||||
Task SendNotification(DatabaseNotification notification);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -821,7 +830,7 @@ namespace Content.Server.Database
|
||||
return RunDbCommand(() => _db.GetLastReadRules(player));
|
||||
}
|
||||
|
||||
public Task SetLastReadRules(NetUserId player, DateTimeOffset time)
|
||||
public Task SetLastReadRules(NetUserId player, DateTimeOffset? time)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.SetLastReadRules(player, time));
|
||||
@@ -1045,6 +1054,12 @@ namespace Content.Server.Database
|
||||
HandleDatabaseNotification(notification);
|
||||
}
|
||||
|
||||
public Task SendNotification(DatabaseNotification notification)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.SendNotification(notification));
|
||||
}
|
||||
|
||||
private async void HandleDatabaseNotification(DatabaseNotification notification)
|
||||
{
|
||||
lock (_notificationHandlers)
|
||||
|
||||
76
Content.Server/Database/ServerDbManagerExt.cs
Normal file
76
Content.Server/Database/ServerDbManagerExt.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System.Text.Json;
|
||||
using Robust.Shared.Asynchronous;
|
||||
|
||||
namespace Content.Server.Database;
|
||||
|
||||
public static class ServerDbManagerExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Subscribe to a database notification on a specific channel, formatted as JSON.
|
||||
/// </summary>
|
||||
/// <param name="dbManager">The database manager to subscribe on.</param>
|
||||
/// <param name="taskManager">The task manager used to run the main callback on the main thread.</param>
|
||||
/// <param name="sawmill">Sawmill to log any errors to.</param>
|
||||
/// <param name="channel">
|
||||
/// The notification channel to listen on. Only notifications on this channel will be handled.
|
||||
/// </param>
|
||||
/// <param name="action">
|
||||
/// The action to run on the notification data.
|
||||
/// This runs on the main thread.
|
||||
/// </param>
|
||||
/// <param name="earlyFilter">
|
||||
/// An early filter callback that runs before the JSON message is deserialized.
|
||||
/// Return false to not handle the notification.
|
||||
/// This does not run on the main thread.
|
||||
/// </param>
|
||||
/// <param name="filter">
|
||||
/// A filter callback that runs after the JSON message is deserialized.
|
||||
/// Return false to not handle the notification.
|
||||
/// This does not run on the main thread.
|
||||
/// </param>
|
||||
/// <typeparam name="TData">The type of JSON data to deserialize.</typeparam>
|
||||
public static void SubscribeToJsonNotification<TData>(
|
||||
this IServerDbManager dbManager,
|
||||
ITaskManager taskManager,
|
||||
ISawmill sawmill,
|
||||
string channel,
|
||||
Action<TData> action,
|
||||
Func<bool>? earlyFilter = null,
|
||||
Func<TData, bool>? filter = null)
|
||||
{
|
||||
dbManager.SubscribeToNotifications(notification =>
|
||||
{
|
||||
if (notification.Channel != channel)
|
||||
return;
|
||||
|
||||
if (notification.Payload == null)
|
||||
{
|
||||
sawmill.Error($"Got {channel} notification with null payload!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (earlyFilter != null && !earlyFilter())
|
||||
return;
|
||||
|
||||
TData data;
|
||||
try
|
||||
{
|
||||
data = JsonSerializer.Deserialize<TData>(notification.Payload)
|
||||
?? throw new JsonException("Content is null");
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
sawmill.Error($"Got invalid JSON in {channel} notification: {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (filter != null && !filter(data))
|
||||
return;
|
||||
|
||||
taskManager.RunOnMainThread(() =>
|
||||
{
|
||||
action(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Npgsql;
|
||||
|
||||
namespace Content.Server.Database;
|
||||
@@ -17,6 +18,7 @@ public sealed partial class ServerDbPostgres
|
||||
private static readonly string[] NotificationChannels =
|
||||
[
|
||||
BanManager.BanNotificationChannel,
|
||||
MultiServerKickManager.NotificationChannel,
|
||||
];
|
||||
|
||||
private static readonly TimeSpan ReconnectWaitIncrease = TimeSpan.FromSeconds(10);
|
||||
@@ -111,6 +113,14 @@ public sealed partial class ServerDbPostgres
|
||||
});
|
||||
}
|
||||
|
||||
public override async Task SendNotification(DatabaseNotification notification)
|
||||
{
|
||||
await using var db = await GetDbImpl();
|
||||
|
||||
await db.PgDbContext.Database.ExecuteSqlAsync(
|
||||
$"SELECT pg_notify({notification.Channel}, {notification.Payload})");
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
_notificationTokenSource.Cancel();
|
||||
|
||||
@@ -537,6 +537,12 @@ namespace Content.Server.Database
|
||||
return await base.AddAdminMessage(message);
|
||||
}
|
||||
|
||||
public override Task SendNotification(DatabaseNotification notification)
|
||||
{
|
||||
// Notifications not implemented on SQLite.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override DateTime NormalizeDatabaseTime(DateTime time)
|
||||
{
|
||||
DebugTools.Assert(time.Kind == DateTimeKind.Unspecified);
|
||||
|
||||
@@ -15,7 +15,6 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
|
||||
/// The type of explosion. Determines damage types and tile break chance scaling.
|
||||
/// </summary>
|
||||
[DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ExplosionPrototype>))]
|
||||
[JsonIgnore]
|
||||
public string ExplosionType = default!;
|
||||
|
||||
/// <summary>
|
||||
@@ -23,14 +22,12 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
|
||||
/// chance.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[JsonIgnore]
|
||||
public float MaxIntensity = 5;
|
||||
|
||||
/// <summary>
|
||||
/// How quickly intensity drops off as you move away from the epicenter
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[JsonIgnore]
|
||||
public float IntensitySlope = 1;
|
||||
|
||||
/// <summary>
|
||||
@@ -41,15 +38,20 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
|
||||
/// A slope of 1 and MaxTotalIntensity of 100 corresponds to a radius of around 4.5 tiles.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
[JsonIgnore]
|
||||
public float MaxTotalIntensity = 100;
|
||||
|
||||
/// <summary>
|
||||
/// The intensity of the explosion per unit reaction.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[JsonIgnore]
|
||||
public float IntensityPerUnit = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Factor used to scale the explosion intensity when calculating tile break chances. Allows for stronger
|
||||
/// explosives that don't space tiles, without having to create a new explosion-type prototype.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float TileBreakScale = 1f;
|
||||
|
||||
public override bool ShouldLog => true;
|
||||
|
||||
@@ -72,6 +74,7 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
|
||||
ExplosionType,
|
||||
intensity,
|
||||
IntensitySlope,
|
||||
MaxIntensity);
|
||||
MaxIntensity,
|
||||
TileBreakScale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,7 @@ namespace Content.Server.Entry
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
|
||||
IoCManager.Resolve<IBanManager>().Initialize();
|
||||
IoCManager.Resolve<IConnectionManager>().PostInit();
|
||||
IoCManager.Resolve<MultiServerKickManager>().Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly FaxecuteSystem _faxecute = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
private const string PaperSlotId = "Paper";
|
||||
|
||||
@@ -227,7 +228,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.KnownFaxes.ContainsValue(newName) && !HasComp<EmaggedComponent>(uid)) // Allow existing names if emagged for fun
|
||||
if (component.KnownFaxes.ContainsValue(newName) && !_emag.CheckFlag(uid, EmagType.Interaction)) // Allow existing names if emagged for fun
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-exist"), uid);
|
||||
return;
|
||||
@@ -246,7 +247,12 @@ public sealed class FaxSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, FaxMachineComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
_audioSystem.PlayPvs(component.EmagSound, uid);
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -260,7 +266,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
switch (command)
|
||||
{
|
||||
case FaxConstants.FaxPingCommand:
|
||||
var isForSyndie = HasComp<EmaggedComponent>(uid) &&
|
||||
var isForSyndie = _emag.CheckFlag(uid, EmagType.Interaction) &&
|
||||
args.Data.ContainsKey(FaxConstants.FaxSyndicateData);
|
||||
if (!isForSyndie && !component.ResponsePings)
|
||||
return;
|
||||
@@ -405,7 +411,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
{ DeviceNetworkConstants.Command, FaxConstants.FaxPingCommand }
|
||||
};
|
||||
|
||||
if (HasComp<EmaggedComponent>(uid))
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
payload.Add(FaxConstants.FaxSyndicateData, true);
|
||||
|
||||
_deviceNetworkSystem.QueuePacket(uid, null, payload);
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.Telephone;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -35,22 +36,15 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly PvsOverrideSystem _pvs = default!;
|
||||
|
||||
private float _updateTimer = 1.0f;
|
||||
|
||||
private const float UpdateTime = 1.0f;
|
||||
private const float MinTimeBetweenSyncRequests = 0.5f;
|
||||
private TimeSpan _minTimeSpanBetweenSyncRequests;
|
||||
|
||||
private HashSet<EntityUid> _pendingRequestsForSpriteState = new();
|
||||
private HashSet<EntityUid> _recentlyUpdatedHolograms = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_minTimeSpanBetweenSyncRequests = TimeSpan.FromSeconds(MinTimeBetweenSyncRequests);
|
||||
|
||||
// Holopad UI and bound user interface messages
|
||||
SubscribeLocalEvent<HolopadComponent, BeforeActivatableUIOpenEvent>(OnUIOpen);
|
||||
SubscribeLocalEvent<HolopadComponent, HolopadStartNewCallMessage>(OnHolopadStartNewCall);
|
||||
@@ -68,7 +62,6 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
|
||||
// Networked events
|
||||
SubscribeNetworkEvent<HolopadUserTypingChangedEvent>(OnTypingChanged);
|
||||
SubscribeNetworkEvent<PlayerSpriteStateMessage>(OnPlayerSpriteStateMessage);
|
||||
|
||||
// Component start/shutdown events
|
||||
SubscribeLocalEvent<HolopadComponent, ComponentInit>(OnHolopadInit);
|
||||
@@ -268,16 +261,11 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
if (source.Comp.Hologram == null)
|
||||
GenerateHologram(source);
|
||||
|
||||
// Receiver holopad holograms have to be generated now instead of waiting for their own event
|
||||
// to fire because holographic avatars get synced immediately
|
||||
if (TryComp<HolopadComponent>(args.Receiver, out var receivingHolopad) && receivingHolopad.Hologram == null)
|
||||
GenerateHologram((args.Receiver, receivingHolopad));
|
||||
|
||||
if (source.Comp.User != null)
|
||||
{
|
||||
// Re-link the user to refresh the sprite data
|
||||
LinkHolopadToUser(source, source.Comp.User.Value);
|
||||
}
|
||||
// Re-link the user to refresh the sprite data
|
||||
LinkHolopadToUser(source, source.Comp.User);
|
||||
}
|
||||
|
||||
private void OnHoloCallEnded(Entity<HolopadComponent> entity, ref TelephoneCallEndedEvent args)
|
||||
@@ -323,22 +311,6 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerSpriteStateMessage(PlayerSpriteStateMessage ev, EntitySessionEventArgs args)
|
||||
{
|
||||
var uid = args.SenderSession.AttachedEntity;
|
||||
|
||||
if (!Exists(uid))
|
||||
return;
|
||||
|
||||
if (!_pendingRequestsForSpriteState.Remove(uid.Value))
|
||||
return;
|
||||
|
||||
if (!TryComp<HolopadUserComponent>(uid, out var holopadUser))
|
||||
return;
|
||||
|
||||
SyncHolopadUserWithLinkedHolograms((uid.Value, holopadUser), ev.SpriteLayerData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region: Component start/shutdown events
|
||||
@@ -485,8 +457,6 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_recentlyUpdatedHolograms.Clear();
|
||||
}
|
||||
|
||||
public void UpdateUIState(Entity<HolopadComponent> entity, TelephoneComponent? telephone = null)
|
||||
@@ -555,10 +525,16 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
QueueDel(hologram);
|
||||
}
|
||||
|
||||
private void LinkHolopadToUser(Entity<HolopadComponent> entity, EntityUid user)
|
||||
private void LinkHolopadToUser(Entity<HolopadComponent> entity, EntityUid? user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
UnlinkHolopadFromUser(entity, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<HolopadUserComponent>(user, out var holopadUser))
|
||||
holopadUser = AddComp<HolopadUserComponent>(user);
|
||||
holopadUser = AddComp<HolopadUserComponent>(user.Value);
|
||||
|
||||
if (user != entity.Comp.User?.Owner)
|
||||
{
|
||||
@@ -567,51 +543,44 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
|
||||
// Assigns the new user in their place
|
||||
holopadUser.LinkedHolopads.Add(entity);
|
||||
entity.Comp.User = (user, holopadUser);
|
||||
entity.Comp.User = (user.Value, holopadUser);
|
||||
}
|
||||
|
||||
if (TryComp<HolographicAvatarComponent>(user, out var avatar))
|
||||
{
|
||||
SyncHolopadUserWithLinkedHolograms((user, holopadUser), avatar.LayerData);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have no apriori sprite data for the hologram, request
|
||||
// the current appearance of the user from the client
|
||||
RequestHolopadUserSpriteUpdate((user, holopadUser));
|
||||
// Add the new user to PVS and sync their appearance with any
|
||||
// holopads connected to the one they are using
|
||||
_pvs.AddGlobalOverride(user.Value);
|
||||
SyncHolopadHologramAppearanceWithTarget(entity, entity.Comp.User);
|
||||
}
|
||||
|
||||
private void UnlinkHolopadFromUser(Entity<HolopadComponent> entity, Entity<HolopadUserComponent>? user)
|
||||
{
|
||||
if (user == null)
|
||||
return;
|
||||
|
||||
entity.Comp.User = null;
|
||||
SyncHolopadHologramAppearanceWithTarget(entity, null);
|
||||
|
||||
foreach (var linkedHolopad in GetLinkedHolopads(entity))
|
||||
{
|
||||
if (linkedHolopad.Comp.Hologram != null)
|
||||
{
|
||||
_appearanceSystem.SetData(linkedHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.IsTyping, false);
|
||||
|
||||
// Send message with no sprite data to the client
|
||||
// This will set the holgram sprite to a generic icon
|
||||
var ev = new PlayerSpriteStateMessage(GetNetEntity(linkedHolopad.Comp.Hologram.Value));
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
if (!HasComp<HolopadUserComponent>(user))
|
||||
if (user == null)
|
||||
return;
|
||||
|
||||
user.Value.Comp.LinkedHolopads.Remove(entity);
|
||||
|
||||
if (!user.Value.Comp.LinkedHolopads.Any())
|
||||
if (!user.Value.Comp.LinkedHolopads.Any() &&
|
||||
user.Value.Comp.LifeStage < ComponentLifeStage.Stopping)
|
||||
{
|
||||
_pendingRequestsForSpriteState.Remove(user.Value);
|
||||
_pvs.RemoveGlobalOverride(user.Value);
|
||||
RemComp<HolopadUserComponent>(user.Value);
|
||||
}
|
||||
}
|
||||
private void SyncHolopadHologramAppearanceWithTarget(Entity<HolopadComponent> entity, Entity<HolopadUserComponent>? user)
|
||||
{
|
||||
foreach (var linkedHolopad in GetLinkedHolopads(entity))
|
||||
{
|
||||
if (linkedHolopad.Comp.Hologram == null)
|
||||
continue;
|
||||
|
||||
if (user.Value.Comp.LifeStage < ComponentLifeStage.Stopping)
|
||||
RemComp<HolopadUserComponent>(user.Value);
|
||||
if (user == null)
|
||||
_appearanceSystem.SetData(linkedHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.IsTyping, false);
|
||||
|
||||
linkedHolopad.Comp.Hologram.Value.Comp.LinkedEntity = user;
|
||||
Dirty(linkedHolopad.Comp.Hologram.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -646,31 +615,6 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
Dirty(entity);
|
||||
}
|
||||
|
||||
private void RequestHolopadUserSpriteUpdate(Entity<HolopadUserComponent> user)
|
||||
{
|
||||
if (!_pendingRequestsForSpriteState.Add(user))
|
||||
return;
|
||||
|
||||
var ev = new PlayerSpriteStateRequest(GetNetEntity(user));
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
|
||||
private void SyncHolopadUserWithLinkedHolograms(Entity<HolopadUserComponent> entity, PrototypeLayerData[]? spriteLayerData)
|
||||
{
|
||||
foreach (var linkedHolopad in entity.Comp.LinkedHolopads)
|
||||
{
|
||||
foreach (var receivingHolopad in GetLinkedHolopads(linkedHolopad))
|
||||
{
|
||||
if (receivingHolopad.Comp.Hologram == null || !_recentlyUpdatedHolograms.Add(receivingHolopad.Comp.Hologram.Value))
|
||||
continue;
|
||||
|
||||
var netHologram = GetNetEntity(receivingHolopad.Comp.Hologram.Value);
|
||||
var ev = new PlayerSpriteStateMessage(netHologram, spriteLayerData);
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivateProjector(Entity<HolopadComponent> entity, EntityUid user)
|
||||
{
|
||||
if (!TryComp<TelephoneComponent>(entity, out var receiverTelephone))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Implants;
|
||||
@@ -9,6 +10,7 @@ public sealed partial class ImplanterSystem
|
||||
{
|
||||
SubscribeLocalEvent<ImplantedComponent, ComponentInit>(OnImplantedInit);
|
||||
SubscribeLocalEvent<ImplantedComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<ImplantedComponent, BeingGibbedEvent>(OnGibbed);
|
||||
}
|
||||
|
||||
private void OnImplantedInit(EntityUid uid, ImplantedComponent component, ComponentInit args)
|
||||
@@ -22,4 +24,10 @@ public sealed partial class ImplanterSystem
|
||||
//If the entity is deleted, get rid of the implants
|
||||
_container.CleanContainer(component.ImplantContainer);
|
||||
}
|
||||
|
||||
private void OnGibbed(Entity<ImplantedComponent> ent, ref BeingGibbedEvent args)
|
||||
{
|
||||
//If the entity is gibbed, get rid of the implants
|
||||
_container.CleanContainer(ent.Comp.ImplantContainer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,17 +58,6 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we are trying to implant a implant which is already implanted
|
||||
if (implant.HasValue && !component.AllowMultipleImplants && CheckSameImplant(target, implant.Value))
|
||||
{
|
||||
var name = Identity.Name(target, EntityManager, args.User);
|
||||
var msg = Loc.GetString("implanter-component-implant-already", ("implant", implant), ("target", name));
|
||||
_popup.PopupEntity(msg, target, args.User);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//Implant self instantly, otherwise try to inject the target.
|
||||
if (args.User == target)
|
||||
Implant(target, target, uid, component);
|
||||
@@ -79,14 +68,7 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public bool CheckSameImplant(EntityUid target, EntityUid implant)
|
||||
{
|
||||
if (!TryComp<ImplantedComponent>(target, out var implanted))
|
||||
return false;
|
||||
|
||||
var implantPrototype = Prototype(implant);
|
||||
return implanted.ImplantContainer.ContainedEntities.Any(entity => Prototype(entity) == implantPrototype);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to implant someone else.
|
||||
|
||||
76
Content.Server/Implants/RadioImplantSystem.cs
Normal file
76
Content.Server/Implants/RadioImplantSystem.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using Content.Server.Radio.Components;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Implants;
|
||||
|
||||
public sealed class RadioImplantSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RadioImplantComponent, ImplantImplantedEvent>(OnImplantImplanted);
|
||||
SubscribeLocalEvent<RadioImplantComponent, EntGotRemovedFromContainerMessage>(OnRemove);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If implanted with a radio implant, installs the necessary intrinsic radio components
|
||||
/// </summary>
|
||||
private void OnImplantImplanted(Entity<RadioImplantComponent> ent, ref ImplantImplantedEvent args)
|
||||
{
|
||||
if (args.Implanted == null)
|
||||
return;
|
||||
|
||||
var activeRadio = EnsureComp<ActiveRadioComponent>(args.Implanted.Value);
|
||||
foreach (var channel in ent.Comp.RadioChannels)
|
||||
{
|
||||
if (activeRadio.Channels.Add(channel))
|
||||
ent.Comp.ActiveAddedChannels.Add(channel);
|
||||
}
|
||||
|
||||
EnsureComp<IntrinsicRadioReceiverComponent>(args.Implanted.Value);
|
||||
|
||||
var intrinsicRadioTransmitter = EnsureComp<IntrinsicRadioTransmitterComponent>(args.Implanted.Value);
|
||||
foreach (var channel in ent.Comp.RadioChannels)
|
||||
{
|
||||
if (intrinsicRadioTransmitter.Channels.Add(channel))
|
||||
ent.Comp.TransmitterAddedChannels.Add(channel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes intrinsic radio components once the Radio Implant is removed
|
||||
/// </summary>
|
||||
private void OnRemove(Entity<RadioImplantComponent> ent, ref EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
if (TryComp<ActiveRadioComponent>(args.Container.Owner, out var activeRadioComponent))
|
||||
{
|
||||
foreach (var channel in ent.Comp.ActiveAddedChannels)
|
||||
{
|
||||
activeRadioComponent.Channels.Remove(channel);
|
||||
}
|
||||
ent.Comp.ActiveAddedChannels.Clear();
|
||||
|
||||
if (activeRadioComponent.Channels.Count == 0)
|
||||
{
|
||||
RemCompDeferred<ActiveRadioComponent>(args.Container.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
if (!TryComp<IntrinsicRadioTransmitterComponent>(args.Container.Owner, out var radioTransmitterComponent))
|
||||
return;
|
||||
|
||||
foreach (var channel in ent.Comp.TransmitterAddedChannels)
|
||||
{
|
||||
radioTransmitterComponent.Channels.Remove(channel);
|
||||
}
|
||||
ent.Comp.TransmitterAddedChannels.Clear();
|
||||
|
||||
if (radioTransmitterComponent.Channels.Count == 0 || activeRadioComponent?.Channels.Count == 0)
|
||||
{
|
||||
RemCompDeferred<IntrinsicRadioTransmitterComponent>(args.Container.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public sealed class RulesManager
|
||||
{
|
||||
PopupTime = _cfg.GetCVar(CCVars.RulesWaitTime),
|
||||
CoreRules = _cfg.GetCVar(CCVars.RulesFile),
|
||||
ShouldShowRules = !isLocalhost && !hasCooldown
|
||||
ShouldShowRules = !isLocalhost && !hasCooldown,
|
||||
};
|
||||
_netManager.ServerSendMessage(showRulesMessage, e.Channel);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ namespace Content.Server.IoC
|
||||
IoCManager.Register<MappingManager>();
|
||||
IoCManager.Register<IWatchlistWebhookManager, WatchlistWebhookManager>();
|
||||
IoCManager.Register<ConnectionManager>();
|
||||
IoCManager.Register<MultiServerKickManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Lathe;
|
||||
using Content.Shared.Materials;
|
||||
@@ -42,6 +43,7 @@ namespace Content.Server.Lathe
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSys = default!;
|
||||
[Dependency] private readonly MaterialStorageSystem _materialStorage = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
@@ -292,7 +294,7 @@ namespace Content.Server.Lathe
|
||||
{
|
||||
if (uid != args.Lathe || !TryComp<TechnologyDatabaseComponent>(uid, out var technologyDatabase))
|
||||
return;
|
||||
if (!args.getUnavailable && !HasComp<EmaggedComponent>(uid))
|
||||
if (!args.getUnavailable && !_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
foreach (var recipe in component.EmagDynamicRecipes)
|
||||
{
|
||||
|
||||
@@ -72,7 +72,10 @@ public sealed class HealingSystem : EntitySystem
|
||||
_bloodstreamSystem.TryModifyBleedAmount(entity.Owner, healing.BloodlossModifier);
|
||||
if (isBleeding != bloodstream.BleedAmount > 0)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("medical-item-stop-bleeding"), entity, args.User);
|
||||
var popup = (args.User == entity.Owner)
|
||||
? Loc.GetString("medical-item-stop-bleeding-self")
|
||||
: Loc.GetString("medical-item-stop-bleeding", ("target", Identity.Entity(entity.Owner, EntityManager)));
|
||||
_popupSystem.PopupEntity(popup, entity, args.User);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -42,6 +43,7 @@ public sealed class SuitSensorSystem : EntitySystem
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -369,7 +371,7 @@ public sealed class SuitSensorSystem : EntitySystem
|
||||
userJobIcon = card.Comp.JobIcon;
|
||||
|
||||
foreach (var department in card.Comp.JobDepartments)
|
||||
userJobDepartments.Add(Loc.GetString($"department-{department}"));
|
||||
userJobDepartments.Add(Loc.GetString(_proto.Index(department).Name));
|
||||
}
|
||||
|
||||
// get health mob state
|
||||
|
||||
@@ -35,7 +35,7 @@ public sealed class MiningSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var coords = Transform(uid).Coordinates;
|
||||
var toSpawn = _random.Next(proto.MinOreYield, proto.MaxOreYield);
|
||||
var toSpawn = _random.Next(proto.MinOreYield, proto.MaxOreYield+1);
|
||||
for (var i = 0; i < toSpawn; i++)
|
||||
{
|
||||
Spawn(proto.OreEntity, coords.Offset(_random.NextVector2(0.2f)));
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Server.Movement.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class EyeCursorOffsetComponent : SharedEyeCursorOffsetComponent
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace Content.Server.NPC.Components
|
||||
{
|
||||
/// Added when a medibot injects someone
|
||||
/// So they don't get injected again for at least a minute.
|
||||
[RegisterComponent]
|
||||
public sealed partial class NPCRecentlyInjectedComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("removeTime")]
|
||||
public TimeSpan RemoveTime = TimeSpan.FromMinutes(1);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Shared.NPC.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Emag.Components;
|
||||
@@ -55,34 +55,11 @@ public sealed partial class MedibotInjectOperator : HTNOperator
|
||||
if (!_entMan.TryGetComponent<MedibotComponent>(owner, out var botComp))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
|
||||
if (!_entMan.TryGetComponent<DamageableComponent>(target, out var damage))
|
||||
if (!_medibot.CheckInjectable((owner, botComp), target) || !_medibot.TryInject((owner, botComp), target))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
if (!_solutionContainer.TryGetInjectableSolution(target, out var injectable, out _))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(owner, target))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
var total = damage.TotalDamage;
|
||||
|
||||
// always inject healthy patients when emagged
|
||||
if (total == 0 && !_entMan.HasComponent<EmaggedComponent>(owner))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
if (!_entMan.TryGetComponent<MobStateComponent>(target, out var mobState))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
var state = mobState.CurrentState;
|
||||
if (!_medibot.TryGetTreatment(botComp, mobState.CurrentState, out var treatment) || !treatment.IsValid(total))
|
||||
return HTNOperatorStatus.Failed;
|
||||
|
||||
_entMan.EnsureComponent<NPCRecentlyInjectedComponent>(target);
|
||||
_solutionContainer.TryAddReagent(injectable.Value, treatment.Reagent, treatment.Quantity, out _);
|
||||
_popup.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, target);
|
||||
_audio.PlayPvs(botComp.InjectSound, target);
|
||||
_chat.TrySendInGameICMessage(owner, Loc.GetString("medibot-finish-inject"), InGameICChatType.Speak, hideChat: true, hideLog: true);
|
||||
|
||||
return HTNOperatorStatus.Finished;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Shared.NPC.Components;
|
||||
using Content.Server.NPC.Pathfinding;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Damage;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Shared.NPC.Components;
|
||||
|
||||
namespace Content.Server.NPC.Systems;
|
||||
|
||||
|
||||
@@ -25,6 +25,13 @@ namespace Content.Server.Nuke
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Timer = 300;
|
||||
|
||||
/// <summary>
|
||||
/// If the nuke is disarmed, this sets the minimum amount of time the timer can have.
|
||||
/// The remaining time will reset to this value if it is below it.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int MinimumTime = 180;
|
||||
|
||||
/// <summary>
|
||||
/// How long until the bomb can arm again after deactivation.
|
||||
/// Used to prevent announcements spam.
|
||||
|
||||
@@ -522,6 +522,9 @@ public sealed class NukeSystem : EntitySystem
|
||||
_sound.PlayGlobalOnStation(uid, _audio.GetSound(component.DisarmSound));
|
||||
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
|
||||
|
||||
// reset nuke remaining time to either itself or the minimum time, whichever is higher
|
||||
component.RemainingTime = Math.Max(component.RemainingTime, component.MinimumTime);
|
||||
|
||||
// disable sound and reset it
|
||||
component.PlayedAlertSound = false;
|
||||
component.AlertAudioStream = _audio.Stop(component.AlertAudioStream);
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Content.Server.Nutrition.EntitySystems;
|
||||
public sealed class FatExtractorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly HungerSystem _hunger = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
@@ -36,8 +37,13 @@ public sealed class FatExtractorSystem : EntitySystem
|
||||
|
||||
private void OnGotEmagged(EntityUid uid, FatExtractorComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
args.Repeatable = false;
|
||||
}
|
||||
|
||||
private void OnClosed(EntityUid uid, FatExtractorComponent component, ref StorageAfterCloseEvent args)
|
||||
@@ -103,7 +109,7 @@ public sealed class FatExtractorSystem : EntitySystem
|
||||
if (_hunger.GetHunger(hunger) < component.NutritionPerSecond)
|
||||
return false;
|
||||
|
||||
if (hunger.CurrentThreshold < component.MinHungerThreshold && !HasComp<EmaggedComponent>(uid))
|
||||
if (hunger.CurrentThreshold < component.MinHungerThreshold && !_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
{
|
||||
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly FoodSystem _foodSystem = default!;
|
||||
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
@@ -63,7 +64,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
forced = false;
|
||||
}
|
||||
|
||||
if (entity.Comp.ExplodeOnUse || HasComp<EmaggedComponent>(entity.Owner))
|
||||
if (entity.Comp.ExplodeOnUse || _emag.CheckFlag(entity, EmagType.Interaction))
|
||||
{
|
||||
_explosionSystem.QueueExplosion(entity.Owner, "Default", entity.Comp.ExplosionIntensity, 0.5f, 3, canCreateVacuum: false);
|
||||
EntityManager.DeleteEntity(entity);
|
||||
@@ -161,8 +162,15 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
args.Args.Target.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEmagged(Entity<VapeComponent> entity, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(entity, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
@@ -16,6 +17,7 @@ namespace Content.Server.Power.Components
|
||||
/// Maximum charge of the battery in joules (ie. watt seconds)
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[GuidebookData]
|
||||
public float MaxCharge;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Power.Pow3r;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
@@ -24,6 +25,7 @@ namespace Content.Server.Power.Components
|
||||
|
||||
[DataField("maxSupply")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float MaxSupply
|
||||
{
|
||||
get => NetworkBattery.MaxSupply;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Power.NodeGroups;
|
||||
using Content.Server.Power.Pow3r;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
@@ -8,6 +9,7 @@ namespace Content.Server.Power.Components
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("supplyRate")]
|
||||
[GuidebookData]
|
||||
public float MaxSupply { get => NetworkSupply.MaxSupply; set => NetworkSupply.MaxSupply = value; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Server.Power.Components;
|
||||
using Content.Server.Power.Pow3r;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.APC;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Rounding;
|
||||
@@ -19,6 +18,7 @@ public sealed class ApcSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
@@ -111,7 +111,12 @@ public sealed class ApcSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, ApcComponent comp, ref GotEmaggedEvent args)
|
||||
{
|
||||
// no fancy conditions
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -170,7 +175,7 @@ public sealed class ApcSystem : EntitySystem
|
||||
|
||||
private ApcChargeState CalcChargeState(EntityUid uid, PowerState.Battery battery)
|
||||
{
|
||||
if (HasComp<EmaggedComponent>(uid))
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return ApcChargeState.Emag;
|
||||
|
||||
if (battery.CurrentStorage / battery.Capacity > ApcComponent.HighPowerThreshold)
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Content.Server.PowerSink
|
||||
|
||||
_chat.DispatchStationAnnouncement(
|
||||
station.Value,
|
||||
Loc.GetString("powersink-immiment-explosion-announcement"),
|
||||
Loc.GetString("powersink-imminent-explosion-announcement"),
|
||||
playDefaultSound: true,
|
||||
colorOverride: Color.Yellow
|
||||
);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Destructible;
|
||||
using Content.Server.Effects;
|
||||
using Content.Server.Weapons.Ranged.Systems;
|
||||
using Content.Shared.Camera;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Projectiles;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Player;
|
||||
@@ -15,6 +17,7 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly ColorFlashEffectSystem _color = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly DestructibleSystem _destructibleSystem = default!;
|
||||
[Dependency] private readonly GunSystem _guns = default!;
|
||||
[Dependency] private readonly SharedCameraRecoilSystem _sharedCameraRecoil = default!;
|
||||
|
||||
@@ -28,7 +31,7 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
||||
{
|
||||
// This is so entities that shouldn't get a collision are ignored.
|
||||
if (args.OurFixtureId != ProjectileFixture || !args.OtherFixture.Hard
|
||||
|| component.DamagedEntity || component is { Weapon: null, OnlyCollideWhenShot: true })
|
||||
|| component.ProjectileSpent || component is { Weapon: null, OnlyCollideWhenShot: true })
|
||||
return;
|
||||
|
||||
var target = args.OtherEntity;
|
||||
@@ -45,7 +48,13 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
var otherName = ToPrettyString(target);
|
||||
var modifiedDamage = _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, origin: component.Shooter);
|
||||
var damageRequired = _destructibleSystem.DestroyedAt(target);
|
||||
if (TryComp<DamageableComponent>(target, out var damageableComponent))
|
||||
{
|
||||
damageRequired -= damageableComponent.TotalDamage;
|
||||
damageRequired = FixedPoint2.Max(damageRequired, FixedPoint2.Zero);
|
||||
}
|
||||
var modifiedDamage = _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, damageable: damageableComponent, origin: component.Shooter);
|
||||
var deleted = Deleted(target);
|
||||
|
||||
if (modifiedDamage is not null && EntityManager.EntityExists(component.Shooter))
|
||||
@@ -60,6 +69,46 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
||||
$"Projectile {ToPrettyString(uid):projectile} shot by {ToPrettyString(component.Shooter!.Value):user} hit {otherName:target} and dealt {modifiedDamage.GetTotal():damage} damage");
|
||||
}
|
||||
|
||||
// If penetration is to be considered, we need to do some checks to see if the projectile should stop.
|
||||
if (modifiedDamage is not null && component.PenetrationThreshold != 0)
|
||||
{
|
||||
// If a damage type is required, stop the bullet if the hit entity doesn't have that type.
|
||||
if (component.PenetrationDamageTypeRequirement != null)
|
||||
{
|
||||
var stopPenetration = false;
|
||||
foreach (var requiredDamageType in component.PenetrationDamageTypeRequirement)
|
||||
{
|
||||
if (!modifiedDamage.DamageDict.Keys.Contains(requiredDamageType))
|
||||
{
|
||||
stopPenetration = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stopPenetration)
|
||||
component.ProjectileSpent = true;
|
||||
}
|
||||
|
||||
// If the object won't be destroyed, it "tanks" the penetration hit.
|
||||
if (modifiedDamage.GetTotal() < damageRequired)
|
||||
{
|
||||
component.ProjectileSpent = true;
|
||||
}
|
||||
|
||||
if (!component.ProjectileSpent)
|
||||
{
|
||||
component.PenetrationAmount += damageRequired;
|
||||
// The projectile has dealt enough damage to be spent.
|
||||
if (component.PenetrationAmount >= component.PenetrationThreshold)
|
||||
{
|
||||
component.ProjectileSpent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
component.ProjectileSpent = true;
|
||||
}
|
||||
|
||||
if (!deleted)
|
||||
{
|
||||
_guns.PlayImpactSound(target, modifiedDamage, component.SoundHit, component.ForceSound);
|
||||
@@ -68,9 +117,7 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
||||
_sharedCameraRecoil.KickCamera(target, args.OurBody.LinearVelocity.Normalized());
|
||||
}
|
||||
|
||||
component.DamagedEntity = true;
|
||||
|
||||
if (component.DeleteOnCollide)
|
||||
if (component.DeleteOnCollide && component.ProjectileSpent)
|
||||
QueueDel(uid);
|
||||
|
||||
if (component.ImpactEffect != null && TryComp(uid, out TransformComponent? xform))
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.Radiation.Events;
|
||||
using Content.Shared.Radiation.Systems;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Debugging;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
@@ -42,12 +43,12 @@ public partial class RadiationSystem
|
||||
/// </summary>
|
||||
private void UpdateDebugOverlay(EntityEventArgs ev)
|
||||
{
|
||||
var sessions = _debugSessions.ToArray();
|
||||
foreach (var session in sessions)
|
||||
foreach (var session in _debugSessions)
|
||||
{
|
||||
if (session.Status != SessionStatus.InGame)
|
||||
_debugSessions.Remove(session);
|
||||
RaiseNetworkEvent(ev, session.Channel);
|
||||
else
|
||||
RaiseNetworkEvent(ev, session);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,13 +71,16 @@ public partial class RadiationSystem
|
||||
UpdateDebugOverlay(ev);
|
||||
}
|
||||
|
||||
private void UpdateGridcastDebugOverlay(double elapsedTime, int totalSources,
|
||||
int totalReceivers, List<RadiationRay> rays)
|
||||
private void UpdateGridcastDebugOverlay(
|
||||
double elapsedTime,
|
||||
int totalSources,
|
||||
int totalReceivers,
|
||||
List<DebugRadiationRay>? rays)
|
||||
{
|
||||
if (_debugSessions.Count == 0)
|
||||
return;
|
||||
|
||||
var ev = new OnRadiationOverlayUpdateEvent(elapsedTime, totalSources, totalReceivers, rays);
|
||||
var ev = new OnRadiationOverlayUpdateEvent(elapsedTime, totalSources, totalReceivers, rays ?? new());
|
||||
UpdateDebugOverlay(ev);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Radiation.Components;
|
||||
using Content.Server.Radiation.Events;
|
||||
using Content.Shared.Radiation.Components;
|
||||
using Content.Shared.Radiation.Systems;
|
||||
using Content.Shared.Stacks;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -16,68 +13,86 @@ namespace Content.Server.Radiation.Systems;
|
||||
// main algorithm that fire radiation rays to target
|
||||
public partial class RadiationSystem
|
||||
{
|
||||
[Dependency] private readonly SharedStackSystem _stack = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
private EntityQuery<RadiationBlockingContainerComponent> _radiationBlockingContainers;
|
||||
private readonly record struct SourceData(
|
||||
float Intensity,
|
||||
Entity<RadiationSourceComponent, TransformComponent> Entity,
|
||||
Vector2 WorldPosition)
|
||||
{
|
||||
public EntityUid? GridUid => Entity.Comp2.GridUid;
|
||||
public float Slope => Entity.Comp1.Slope;
|
||||
public TransformComponent Transform => Entity.Comp2;
|
||||
}
|
||||
|
||||
private void UpdateGridcast()
|
||||
{
|
||||
// should we save debug information into rays?
|
||||
// if there is no debug sessions connected - just ignore it
|
||||
var saveVisitedTiles = _debugSessions.Count > 0;
|
||||
var debug = _debugSessions.Count > 0;
|
||||
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
_sources.Clear();
|
||||
_sources.EnsureCapacity(EntityManager.Count<RadiationSourceComponent>());
|
||||
|
||||
var sources = EntityQueryEnumerator<RadiationSourceComponent, TransformComponent>();
|
||||
var destinations = EntityQueryEnumerator<RadiationReceiverComponent, TransformComponent>();
|
||||
var resistanceQuery = GetEntityQuery<RadiationGridResistanceComponent>();
|
||||
var transformQuery = GetEntityQuery<TransformComponent>();
|
||||
var gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
var stackQuery = GetEntityQuery<StackComponent>();
|
||||
|
||||
_radiationBlockingContainers = GetEntityQuery<RadiationBlockingContainerComponent>();
|
||||
|
||||
// precalculate world positions for each source
|
||||
// so we won't need to calc this in cycle over and over again
|
||||
var sourcesData = new ValueList<(EntityUid, RadiationSourceComponent, TransformComponent, Vector2)>();
|
||||
while (sources.MoveNext(out var uid, out var source, out var sourceTrs))
|
||||
while (sources.MoveNext(out var uid, out var source, out var xform))
|
||||
{
|
||||
if (!source.Enabled)
|
||||
continue;
|
||||
|
||||
var worldPos = _transform.GetWorldPosition(sourceTrs, transformQuery);
|
||||
var data = (uid, source, sourceTrs, worldPos);
|
||||
sourcesData.Add(data);
|
||||
var worldPos = _transform.GetWorldPosition(xform);
|
||||
|
||||
// Intensity is scaled by stack size.
|
||||
var intensity = source.Intensity * _stack.GetCount(uid);
|
||||
|
||||
// Apply rad modifier if the source is enclosed within a radiation blocking container
|
||||
// Note that this also applies to receivers, and it doesn't bother to check if the container sits between them.
|
||||
// I.e., a source & receiver in the same blocking container will get double-blocked, when no blocking should be applied.
|
||||
intensity = GetAdjustedRadiationIntensity(uid, intensity);
|
||||
|
||||
_sources.Add(new(intensity, (uid, source, xform), worldPos));
|
||||
}
|
||||
|
||||
// trace all rays from rad source to rad receivers
|
||||
var rays = new List<RadiationRay>();
|
||||
var debugRays = debug ? new List<DebugRadiationRay>() : null;
|
||||
var receiversTotalRads = new ValueList<(Entity<RadiationReceiverComponent>, float)>();
|
||||
|
||||
// TODO RADIATION Parallelize
|
||||
// Would need to give receiversTotalRads a fixed size.
|
||||
// Also the _grids list needs to be local to a job. (or better yet cached in SourceData)
|
||||
// And I guess disable parallelization when debugging to make populating the debug List<RadiationRay> easier.
|
||||
// Or just make it threadsafe?
|
||||
while (destinations.MoveNext(out var destUid, out var dest, out var destTrs))
|
||||
{
|
||||
var destWorld = _transform.GetWorldPosition(destTrs, transformQuery);
|
||||
var destWorld = _transform.GetWorldPosition(destTrs);
|
||||
|
||||
var rads = 0f;
|
||||
foreach (var (uid, source, sourceTrs, sourceWorld) in sourcesData)
|
||||
foreach (var source in _sources)
|
||||
{
|
||||
stackQuery.TryGetComponent(uid, out var stack);
|
||||
var intensity = source.Intensity * _stack.GetCount(uid, stack);
|
||||
|
||||
// send ray towards destination entity
|
||||
var ray = Irradiate(uid, sourceTrs, sourceWorld,
|
||||
destUid, destTrs, destWorld,
|
||||
intensity, source.Slope, saveVisitedTiles, resistanceQuery, transformQuery, gridQuery);
|
||||
if (ray == null)
|
||||
if (Irradiate(source, destUid, destTrs, destWorld, debug) is not {} ray)
|
||||
continue;
|
||||
|
||||
// save ray for debug
|
||||
rays.Add(ray);
|
||||
|
||||
// add rads to total rad exposure
|
||||
if (ray.ReachedDestination)
|
||||
rads += ray.Rads;
|
||||
|
||||
if (!debug)
|
||||
continue;
|
||||
|
||||
debugRays!.Add(new DebugRadiationRay(
|
||||
ray.MapId,
|
||||
GetNetEntity(ray.SourceUid),
|
||||
ray.Source,
|
||||
GetNetEntity(ray.DestinationUid),
|
||||
ray.Destination,
|
||||
ray.Rads,
|
||||
ray.Blockers ?? new())
|
||||
);
|
||||
}
|
||||
|
||||
// Apply modifier if the destination entity is hidden within a radiation blocking container
|
||||
@@ -88,9 +103,9 @@ public partial class RadiationSystem
|
||||
|
||||
// update information for debug overlay
|
||||
var elapsedTime = stopwatch.Elapsed.TotalMilliseconds;
|
||||
var totalSources = sourcesData.Count;
|
||||
var totalSources = _sources.Count;
|
||||
var totalReceivers = receiversTotalRads.Count;
|
||||
UpdateGridcastDebugOverlay(elapsedTime, totalSources, totalReceivers, rays);
|
||||
UpdateGridcastDebugOverlay(elapsedTime, totalSources, totalReceivers, debugRays);
|
||||
|
||||
// send rads to each entity
|
||||
foreach (var (receiver, rads) in receiversTotalRads)
|
||||
@@ -108,19 +123,20 @@ public partial class RadiationSystem
|
||||
RaiseLocalEvent(new RadiationSystemUpdatedEvent());
|
||||
}
|
||||
|
||||
private RadiationRay? Irradiate(EntityUid sourceUid, TransformComponent sourceTrs, Vector2 sourceWorld,
|
||||
EntityUid destUid, TransformComponent destTrs, Vector2 destWorld,
|
||||
float incomingRads, float slope, bool saveVisitedTiles,
|
||||
EntityQuery<RadiationGridResistanceComponent> resistanceQuery,
|
||||
EntityQuery<TransformComponent> transformQuery, EntityQuery<MapGridComponent> gridQuery)
|
||||
private RadiationRay? Irradiate(SourceData source,
|
||||
EntityUid destUid,
|
||||
TransformComponent destTrs,
|
||||
Vector2 destWorld,
|
||||
bool saveVisitedTiles)
|
||||
{
|
||||
// lets first check that source and destination on the same map
|
||||
if (sourceTrs.MapID != destTrs.MapID)
|
||||
if (source.Transform.MapID != destTrs.MapID)
|
||||
return null;
|
||||
var mapId = sourceTrs.MapID;
|
||||
|
||||
var mapId = destTrs.MapID;
|
||||
|
||||
// get direction from rad source to destination and its distance
|
||||
var dir = destWorld - sourceWorld;
|
||||
var dir = destWorld - source.WorldPosition;
|
||||
var dist = dir.Length();
|
||||
|
||||
// check if receiver is too far away
|
||||
@@ -128,41 +144,42 @@ public partial class RadiationSystem
|
||||
return null;
|
||||
|
||||
// will it even reach destination considering distance penalty
|
||||
var rads = incomingRads - slope * dist;
|
||||
|
||||
// Apply rad modifier if the source is enclosed within a radiation blocking container
|
||||
rads = GetAdjustedRadiationIntensity(sourceUid, rads);
|
||||
|
||||
if (rads <= MinIntensity)
|
||||
var rads = source.Intensity - source.Slope * dist;
|
||||
if (rads < MinIntensity)
|
||||
return null;
|
||||
|
||||
// create a new radiation ray from source to destination
|
||||
// at first we assume that it doesn't hit any radiation blockers
|
||||
// and has only distance penalty
|
||||
var ray = new RadiationRay(mapId, GetNetEntity(sourceUid), sourceWorld, GetNetEntity(destUid), destWorld, rads);
|
||||
var ray = new RadiationRay(mapId, source.Entity, source.WorldPosition, destUid, destWorld, rads);
|
||||
|
||||
// if source and destination on the same grid it's possible that
|
||||
// between them can be another grid (ie. shuttle in center of donut station)
|
||||
// however we can do simplification and ignore that case
|
||||
if (GridcastSimplifiedSameGrid && sourceTrs.GridUid != null && sourceTrs.GridUid == destTrs.GridUid)
|
||||
if (GridcastSimplifiedSameGrid && destTrs.GridUid is {} gridUid && source.GridUid == gridUid)
|
||||
{
|
||||
if (!gridQuery.TryGetComponent(sourceTrs.GridUid.Value, out var gridComponent))
|
||||
if (!_gridQuery.TryGetComponent(gridUid, out var gridComponent))
|
||||
return ray;
|
||||
return Gridcast((sourceTrs.GridUid.Value, gridComponent), ray, saveVisitedTiles, resistanceQuery, sourceTrs, destTrs, transformQuery.GetComponent(sourceTrs.GridUid.Value));
|
||||
return Gridcast((gridUid, gridComponent, Transform(gridUid)), ref ray, saveVisitedTiles, source.Transform, destTrs);
|
||||
}
|
||||
|
||||
// lets check how many grids are between source and destination
|
||||
// do a box intersection test between target and destination
|
||||
// it's not very precise, but really cheap
|
||||
var box = Box2.FromTwoPoints(sourceWorld, destWorld);
|
||||
var grids = new List<Entity<MapGridComponent>>();
|
||||
_mapManager.FindGridsIntersecting(mapId, box, ref grids, true);
|
||||
|
||||
// TODO RADIATION
|
||||
// Consider caching this in SourceData?
|
||||
// I.e., make the lookup for grids as large as the sources's max distance and store the result in SourceData.
|
||||
// Avoids having to do a lookup per source*receiver.
|
||||
var box = Box2.FromTwoPoints(source.WorldPosition, destWorld);
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(mapId, box, ref _grids, true);
|
||||
|
||||
// gridcast through each grid and try to hit some radiation blockers
|
||||
// the ray will be updated with each grid that has some blockers
|
||||
foreach (var grid in grids)
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
ray = Gridcast(grid, ray, saveVisitedTiles, resistanceQuery, sourceTrs, destTrs, transformQuery.GetComponent(grid));
|
||||
ray = Gridcast((grid.Owner, grid.Comp, Transform(grid)), ref ray, saveVisitedTiles, source.Transform, destTrs);
|
||||
|
||||
// looks like last grid blocked all radiation
|
||||
// we can return right now
|
||||
@@ -170,20 +187,23 @@ public partial class RadiationSystem
|
||||
return ray;
|
||||
}
|
||||
|
||||
_grids.Clear();
|
||||
|
||||
return ray;
|
||||
}
|
||||
|
||||
private RadiationRay Gridcast(Entity<MapGridComponent> grid, RadiationRay ray, bool saveVisitedTiles,
|
||||
EntityQuery<RadiationGridResistanceComponent> resistanceQuery,
|
||||
private RadiationRay Gridcast(
|
||||
Entity<MapGridComponent, TransformComponent> grid,
|
||||
ref RadiationRay ray,
|
||||
bool saveVisitedTiles,
|
||||
TransformComponent sourceTrs,
|
||||
TransformComponent destTrs,
|
||||
TransformComponent gridTrs)
|
||||
TransformComponent destTrs)
|
||||
{
|
||||
var blockers = new List<(Vector2i, float)>();
|
||||
var blockers = saveVisitedTiles ? new List<(Vector2i, float)>() : null;
|
||||
|
||||
// if grid doesn't have resistance map just apply distance penalty
|
||||
var gridUid = grid.Owner;
|
||||
if (!resistanceQuery.TryGetComponent(gridUid, out var resistance))
|
||||
if (!_resistanceQuery.TryGetComponent(gridUid, out var resistance))
|
||||
return ray;
|
||||
var resistanceMap = resistance.ResistancePerTile;
|
||||
|
||||
@@ -195,19 +215,19 @@ public partial class RadiationSystem
|
||||
|
||||
Vector2 srcLocal = sourceTrs.ParentUid == grid.Owner
|
||||
? sourceTrs.LocalPosition
|
||||
: Vector2.Transform(ray.Source, gridTrs.InvLocalMatrix);
|
||||
: Vector2.Transform(ray.Source, grid.Comp2.InvLocalMatrix);
|
||||
|
||||
Vector2 dstLocal = destTrs.ParentUid == grid.Owner
|
||||
? destTrs.LocalPosition
|
||||
: Vector2.Transform(ray.Destination, gridTrs.InvLocalMatrix);
|
||||
: Vector2.Transform(ray.Destination, grid.Comp2.InvLocalMatrix);
|
||||
|
||||
Vector2i sourceGrid = new(
|
||||
(int) Math.Floor(srcLocal.X / grid.Comp.TileSize),
|
||||
(int) Math.Floor(srcLocal.Y / grid.Comp.TileSize));
|
||||
(int) Math.Floor(srcLocal.X / grid.Comp1.TileSize),
|
||||
(int) Math.Floor(srcLocal.Y / grid.Comp1.TileSize));
|
||||
|
||||
Vector2i destGrid = new(
|
||||
(int) Math.Floor(dstLocal.X / grid.Comp.TileSize),
|
||||
(int) Math.Floor(dstLocal.Y / grid.Comp.TileSize));
|
||||
(int) Math.Floor(dstLocal.X / grid.Comp1.TileSize),
|
||||
(int) Math.Floor(dstLocal.Y / grid.Comp1.TileSize));
|
||||
|
||||
// iterate tiles in grid line from source to destination
|
||||
var line = new GridLineEnumerator(sourceGrid, destGrid);
|
||||
@@ -220,7 +240,7 @@ public partial class RadiationSystem
|
||||
|
||||
// save data for debug
|
||||
if (saveVisitedTiles)
|
||||
blockers.Add((point, ray.Rads));
|
||||
blockers!.Add((point, ray.Rads));
|
||||
|
||||
// no intensity left after blocker
|
||||
if (ray.Rads <= MinIntensity)
|
||||
@@ -230,21 +250,45 @@ public partial class RadiationSystem
|
||||
}
|
||||
}
|
||||
|
||||
if (!saveVisitedTiles || blockers!.Count <= 0)
|
||||
return ray;
|
||||
|
||||
// save data for debug if needed
|
||||
if (saveVisitedTiles && blockers.Count > 0)
|
||||
ray.Blockers.Add(GetNetEntity(gridUid), blockers);
|
||||
ray.Blockers ??= new();
|
||||
ray.Blockers.Add(GetNetEntity(gridUid), blockers);
|
||||
|
||||
return ray;
|
||||
}
|
||||
|
||||
private float GetAdjustedRadiationIntensity(EntityUid uid, float rads)
|
||||
{
|
||||
var radblockingComps = new List<RadiationBlockingContainerComponent>();
|
||||
if (_container.TryFindComponentsOnEntityContainerOrParent(uid, _radiationBlockingContainers, radblockingComps))
|
||||
var child = uid;
|
||||
var xform = Transform(uid);
|
||||
var parent = xform.ParentUid;
|
||||
|
||||
while (parent.IsValid())
|
||||
{
|
||||
rads -= radblockingComps.Sum(x => x.RadResistance);
|
||||
var parentXform = Transform(parent);
|
||||
var childMeta = MetaData(child);
|
||||
|
||||
if ((childMeta.Flags & MetaDataFlags.InContainer) != MetaDataFlags.InContainer)
|
||||
{
|
||||
child = parent;
|
||||
parent = parentXform.ParentUid;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_blockerQuery.TryComp(xform.ParentUid, out var blocker))
|
||||
{
|
||||
rads -= blocker.RadResistance;
|
||||
if (rads < 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
child = parent;
|
||||
parent = parentXform.ParentUid;
|
||||
}
|
||||
|
||||
return float.Max(rads, 0);
|
||||
return rads;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Content.Server.Radiation.Components;
|
||||
using Content.Shared.Radiation.Components;
|
||||
using Content.Shared.Radiation.Events;
|
||||
using Content.Shared.Stacks;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Radiation.Systems;
|
||||
|
||||
@@ -11,14 +13,26 @@ public sealed partial class RadiationSystem : EntitySystem
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedStackSystem _stack = default!;
|
||||
|
||||
private EntityQuery<RadiationBlockingContainerComponent> _blockerQuery;
|
||||
private EntityQuery<RadiationGridResistanceComponent> _resistanceQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<StackComponent> _stackQuery;
|
||||
|
||||
private float _accumulator;
|
||||
private List<SourceData> _sources = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeCvars();
|
||||
InitRadBlocking();
|
||||
|
||||
_blockerQuery = GetEntityQuery<RadiationBlockingContainerComponent>();
|
||||
_resistanceQuery = GetEntityQuery<RadiationGridResistanceComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_stackQuery = GetEntityQuery<StackComponent>();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Server.Doors.Systems;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Remotes.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Remotes.Components;
|
||||
using Content.Shared.Remotes.EntitySystems;
|
||||
|
||||
namespace Content.Shared.Remotes
|
||||
{
|
||||
@@ -17,6 +17,7 @@ namespace Content.Shared.Remotes
|
||||
[Dependency] private readonly AirlockSystem _airlock = default!;
|
||||
[Dependency] private readonly DoorSystem _doorSystem = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examine = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -31,9 +32,12 @@ namespace Content.Shared.Remotes
|
||||
if (args.Handled
|
||||
|| args.Target == null
|
||||
|| !TryComp<DoorComponent>(args.Target, out var doorComp) // If it isn't a door we don't use it
|
||||
// Only able to control doors if they are within your vision and within your max range.
|
||||
// Not affected by mobs or machines anymore.
|
||||
|| !_examine.InRangeUnOccluded(args.User, args.Target.Value, SharedInteractionSystem.MaxRaycastRange, null))
|
||||
// Only able to control doors if they are within your vision and within your max range.
|
||||
// Not affected by mobs or machines anymore.
|
||||
|| !_examine.InRangeUnOccluded(args.User,
|
||||
args.Target.Value,
|
||||
SharedInteractionSystem.MaxRaycastRange,
|
||||
null))
|
||||
|
||||
{
|
||||
return;
|
||||
@@ -50,7 +54,8 @@ namespace Content.Shared.Remotes
|
||||
if (TryComp<AccessReaderComponent>(args.Target, out var accessComponent)
|
||||
&& !_doorSystem.HasAccess(args.Target.Value, args.Used, doorComp, accessComponent))
|
||||
{
|
||||
_doorSystem.Deny(args.Target.Value, doorComp, args.User);
|
||||
if (isAirlock)
|
||||
_doorSystem.Deny(args.Target.Value, doorComp, args.User);
|
||||
Popup.PopupEntity(Loc.GetString("door-remote-denied"), args.User, args.User);
|
||||
return;
|
||||
}
|
||||
@@ -59,7 +64,9 @@ namespace Content.Shared.Remotes
|
||||
{
|
||||
case OperatingMode.OpenClose:
|
||||
if (_doorSystem.TryToggleDoor(args.Target.Value, doorComp, args.Used))
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)}: {doorComp.State}");
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)}: {doorComp.State}");
|
||||
break;
|
||||
case OperatingMode.ToggleBolts:
|
||||
if (TryComp<DoorBoltComponent>(args.Target, out var boltsComp))
|
||||
@@ -67,17 +74,22 @@ namespace Content.Shared.Remotes
|
||||
if (!boltsComp.BoltWireCut)
|
||||
{
|
||||
_doorSystem.SetBoltsDown((args.Target.Value, boltsComp), !boltsComp.BoltsDown, args.Used);
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)} to {(boltsComp.BoltsDown ? "" : "un")}bolt it");
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)} to {(boltsComp.BoltsDown ? "" : "un")}bolt it");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case OperatingMode.ToggleEmergencyAccess:
|
||||
if (airlockComp != null)
|
||||
{
|
||||
_airlock.SetEmergencyAccess((args.Target.Value, airlockComp), !airlockComp.EmergencyAccess);
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)} to set emergency access {(airlockComp.EmergencyAccess ? "on" : "off")}");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server.Research.Components;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Research.Components;
|
||||
using Content.Shared.Research.Prototypes;
|
||||
@@ -11,6 +12,8 @@ namespace Content.Server.Research.Systems;
|
||||
|
||||
public sealed partial class ResearchSystem
|
||||
{
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
private void InitializeConsole()
|
||||
{
|
||||
SubscribeLocalEvent<ResearchConsoleComponent, ConsoleUnlockTechnologyMessage>(OnConsoleUnlock);
|
||||
@@ -18,6 +21,7 @@ public sealed partial class ResearchSystem
|
||||
SubscribeLocalEvent<ResearchConsoleComponent, ResearchServerPointsChangedEvent>(OnPointsChanged);
|
||||
SubscribeLocalEvent<ResearchConsoleComponent, ResearchRegistrationChangedEvent>(OnConsoleRegistrationChanged);
|
||||
SubscribeLocalEvent<ResearchConsoleComponent, TechnologyDatabaseModifiedEvent>(OnConsoleDatabaseModified);
|
||||
SubscribeLocalEvent<ResearchConsoleComponent, GotEmaggedEvent>(OnEmagged);
|
||||
}
|
||||
|
||||
private void OnConsoleUnlock(EntityUid uid, ResearchConsoleComponent component, ConsoleUnlockTechnologyMessage args)
|
||||
@@ -39,7 +43,7 @@ public sealed partial class ResearchSystem
|
||||
if (!UnlockTechnology(uid, args.Id, act))
|
||||
return;
|
||||
|
||||
if (!HasComp<EmaggedComponent>(uid))
|
||||
if (!_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
var getIdentityEvent = new TryGetIdentityShortInfoEvent(uid, act);
|
||||
RaiseLocalEvent(getIdentityEvent);
|
||||
@@ -52,7 +56,7 @@ public sealed partial class ResearchSystem
|
||||
);
|
||||
_radio.SendRadioMessage(uid, message, component.AnnouncementChannel, uid, escapeMarkup: false);
|
||||
}
|
||||
|
||||
|
||||
SyncClientWithServer(uid);
|
||||
UpdateConsoleInterface(uid, component);
|
||||
}
|
||||
@@ -100,4 +104,15 @@ public sealed partial class ResearchSystem
|
||||
UpdateConsoleInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OnEmagged(Entity<ResearchConsoleComponent> ent, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(ent, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ public sealed partial class RevenantSystem
|
||||
{
|
||||
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||
[Dependency] private readonly EntityStorageSystem _entityStorage = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
|
||||
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||
@@ -343,7 +342,8 @@ public sealed partial class RevenantSystem
|
||||
_whitelistSystem.IsBlacklistPass(component.MalfunctionBlacklist, ent))
|
||||
continue;
|
||||
|
||||
_emag.DoEmagEffect(uid, ent); //it does not emag itself. adorable.
|
||||
var ev = new GotEmaggedEvent(uid, EmagType.Interaction | EmagType.Access);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Explosion.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
@@ -15,6 +16,8 @@ namespace Content.Server.Silicons.Borgs;
|
||||
/// <inheritdoc/>
|
||||
public sealed partial class BorgSystem
|
||||
{
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
private void InitializeTransponder()
|
||||
{
|
||||
SubscribeLocalEvent<BorgTransponderComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||
@@ -127,7 +130,7 @@ public sealed partial class BorgSystem
|
||||
|
||||
private bool CheckEmagged(EntityUid uid, string name)
|
||||
{
|
||||
if (HasComp<EmaggedComponent>(uid))
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString($"borg-transponder-emagged-{name}-popup"), uid, uid, PopupType.LargeCaution);
|
||||
return true;
|
||||
|
||||
@@ -6,7 +6,6 @@ using Content.Server.Roles;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
@@ -14,7 +13,6 @@ using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -22,7 +20,6 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Silicons.Laws;
|
||||
|
||||
@@ -34,8 +31,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
@@ -52,7 +49,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, IonStormLawsEvent>(OnIonStormLaws);
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, MindAddedMessage>(OnLawProviderMindAdded);
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, MindRemovedMessage>(OnLawProviderMindRemoved);
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, GotEmaggedEvent>(OnEmagLawsAdded);
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, SiliconEmaggedEvent>(OnEmagLawsAdded);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args)
|
||||
@@ -135,7 +132,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
private void OnIonStormLaws(EntityUid uid, SiliconLawProviderComponent component, ref IonStormLawsEvent args)
|
||||
{
|
||||
// Emagged borgs are immune to ion storm
|
||||
if (!HasComp<EmaggedComponent>(uid))
|
||||
if (!_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
component.Lawset = args.Lawset;
|
||||
|
||||
@@ -152,9 +149,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEmagLawsAdded(EntityUid uid, SiliconLawProviderComponent component, ref GotEmaggedEvent args)
|
||||
private void OnEmagLawsAdded(EntityUid uid, SiliconLawProviderComponent component, ref SiliconEmaggedEvent args)
|
||||
{
|
||||
|
||||
if (component.Lawset == null)
|
||||
component.Lawset = GetLawset(component.Laws);
|
||||
|
||||
@@ -164,7 +160,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
// Add the first emag law before the others
|
||||
component.Lawset?.Laws.Insert(0, new SiliconLaw
|
||||
{
|
||||
LawString = Loc.GetString("law-emag-custom", ("name", Name(args.UserUid)), ("title", Loc.GetString(component.Lawset.ObeysTo))),
|
||||
LawString = Loc.GetString("law-emag-custom", ("name", Name(args.user)), ("title", Loc.GetString(component.Lawset.ObeysTo))),
|
||||
Order = 0
|
||||
});
|
||||
|
||||
@@ -176,20 +172,6 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnGotEmagged(EntityUid uid, EmagSiliconLawComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (component.RequireOpenPanel && TryComp<WiresPanelComponent>(uid, out var panel) && !panel.Open)
|
||||
return;
|
||||
|
||||
base.OnGotEmagged(uid, component, ref args);
|
||||
NotifyLawsChanged(uid, component.EmaggedSound);
|
||||
if(_mind.TryGetMind(uid, out var mindId, out _))
|
||||
EnsureSubvertedSiliconRole(mindId);
|
||||
|
||||
_stunSystem.TryParalyze(uid, component.StunTime, true);
|
||||
|
||||
}
|
||||
|
||||
private void EnsureSubvertedSiliconRole(EntityUid mindId)
|
||||
{
|
||||
if (!_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Content.Server.VendingMachines
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _light = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
private const float WallVendEjectDistanceFromWall = 1f;
|
||||
|
||||
@@ -48,7 +49,6 @@ namespace Content.Server.VendingMachines
|
||||
|
||||
SubscribeLocalEvent<VendingMachineComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, BreakageEventArgs>(OnBreak);
|
||||
SubscribeLocalEvent<VendingMachineComponent, GotEmaggedEvent>(OnEmagged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, PriceCalculationEvent>(OnVendingPrice);
|
||||
SubscribeLocalEvent<VendingMachineComponent, EmpPulseEvent>(OnEmpPulse);
|
||||
@@ -123,12 +123,6 @@ namespace Content.Server.VendingMachines
|
||||
TryUpdateVisualState(uid, vendComponent);
|
||||
}
|
||||
|
||||
private void OnEmagged(EntityUid uid, VendingMachineComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
// only emag if there are emag-only items
|
||||
args.Handled = component.EmaggedInventory.Count > 0;
|
||||
}
|
||||
|
||||
private void OnDamageChanged(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (!args.DamageIncreased && component.Broken)
|
||||
@@ -232,7 +226,7 @@ namespace Content.Server.VendingMachines
|
||||
if (!TryComp<AccessReaderComponent>(uid, out var accessReader))
|
||||
return true;
|
||||
|
||||
if (_accessReader.IsAllowed(sender, uid, accessReader) || HasComp<EmaggedComponent>(uid))
|
||||
if (_accessReader.IsAllowed(sender, uid, accessReader))
|
||||
return true;
|
||||
|
||||
Popup.PopupEntity(Loc.GetString("vending-machine-component-try-eject-access-denied"), uid);
|
||||
@@ -422,7 +416,7 @@ namespace Content.Server.VendingMachines
|
||||
if (!Resolve(uid, ref component))
|
||||
return null;
|
||||
|
||||
if (type == InventoryType.Emagged && HasComp<EmaggedComponent>(uid))
|
||||
if (type == InventoryType.Emagged && _emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return component.EmaggedInventory.GetValueOrDefault(entryId);
|
||||
|
||||
if (type == InventoryType.Contraband && component.Contraband)
|
||||
|
||||
@@ -78,7 +78,7 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
// Could also check the arc though future effort + if they're aimbotting it's not really going to make a difference.
|
||||
|
||||
// (This runs lagcomp internally and is what clickattacks use)
|
||||
if (!Interaction.InRangeUnobstructed(ignore, targetUid, range + 0.1f))
|
||||
if (!Interaction.InRangeUnobstructed(ignore, targetUid, range + 0.1f, overlapCheck: false))
|
||||
return false;
|
||||
|
||||
// TODO: Check arc though due to the aforementioned aimbot + damage split comments it's less important.
|
||||
@@ -193,7 +193,7 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
if (session is { } pSession)
|
||||
{
|
||||
(targetCoordinates, targetLocalAngle) = _lag.GetCoordinatesAngle(target, pSession);
|
||||
return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range);
|
||||
return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range, overlapCheck: false);
|
||||
}
|
||||
|
||||
return Interaction.InRangeUnobstructed(user, target, range);
|
||||
|
||||
45
Content.Server/Wieldable/WieldableSystem.cs
Normal file
45
Content.Server/Wieldable/WieldableSystem.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Content.Server.Movement.Components;
|
||||
using Content.Server.Movement.Systems;
|
||||
using Content.Shared.Camera;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Wieldable;
|
||||
using Content.Shared.Wieldable.Components;
|
||||
|
||||
namespace Content.Server.Wieldable;
|
||||
|
||||
public sealed class WieldableSystem : SharedWieldableSystem
|
||||
{
|
||||
[Dependency] private readonly ContentEyeSystem _eye = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CursorOffsetRequiresWieldComponent, ItemUnwieldedEvent>(OnEyeOffsetUnwielded);
|
||||
SubscribeLocalEvent<CursorOffsetRequiresWieldComponent, ItemWieldedEvent>(OnEyeOffsetWielded);
|
||||
SubscribeLocalEvent<CursorOffsetRequiresWieldComponent, HeldRelayedEvent<GetEyePvsScaleRelayedEvent>>(OnGetEyePvsScale);
|
||||
}
|
||||
|
||||
private void OnEyeOffsetUnwielded(Entity<CursorOffsetRequiresWieldComponent> entity, ref ItemUnwieldedEvent args)
|
||||
{
|
||||
_eye.UpdatePvsScale(args.User);
|
||||
}
|
||||
|
||||
private void OnEyeOffsetWielded(Entity<CursorOffsetRequiresWieldComponent> entity, ref ItemWieldedEvent args)
|
||||
{
|
||||
_eye.UpdatePvsScale(args.User);
|
||||
}
|
||||
|
||||
private void OnGetEyePvsScale(Entity<CursorOffsetRequiresWieldComponent> entity,
|
||||
ref HeldRelayedEvent<GetEyePvsScaleRelayedEvent> args)
|
||||
{
|
||||
if (!TryComp(entity, out EyeCursorOffsetComponent? eyeCursorOffset) || !TryComp(entity.Owner, out WieldableComponent? wieldableComp))
|
||||
return;
|
||||
|
||||
if (!wieldableComp.Wielded)
|
||||
return;
|
||||
|
||||
args.Args.Scale += eyeCursorOffset.PvsIncrease;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user