From 4eadbbc39d2df69aace020583f1f5c57d0826c85 Mon Sep 17 00:00:00 2001 From: Killerqu00 <47712032+Killerqu00@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:40:02 +0200 Subject: [PATCH 001/243] fix typos in bonfire descriptions (#28515) * fix typo * a --- Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml b/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml index cc69a6304d..29efdaea5d 100644 --- a/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml +++ b/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml @@ -2,7 +2,7 @@ id: Bonfire parent: BaseStructure name: bonfire - description: What can be better then late evening under the sky with guitar and friends. + description: What can be better than a late evening under the sky with guitar and friends? components: - type: Sprite noRot: true From 81821e87af5e14c6399482c0cc0f34aa73b86a02 Mon Sep 17 00:00:00 2001 From: stellar-novas Date: Sun, 2 Jun 2024 15:49:31 -0400 Subject: [PATCH 002/243] Update to nixpkgs 24.05 (#28529) --- flake.lock | 14 +++++++------- flake.nix | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/flake.lock b/flake.lock index 6ab38fa41b..7baaa468ea 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -20,16 +20,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708210246, - "narHash": "sha256-Q8L9XwrBK53fbuuIFMbjKvoV7ixfLFKLw4yV+SD28Y8=", + "lastModified": 1717352157, + "narHash": "sha256-hbBzucWOhwxt3QzeAyUojtD6/aHH81JssDfhFfmqOy0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "69405156cffbdf2be50153f13cbdf9a0bea38e49", + "rev": "44f538ab12e2726af450877a5529f4fd88ddb0fb", "type": "github" }, "original": { "owner": "NixOS", - "ref": "release-23.11", + "ref": "release-24.05", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index e2e119eb99..095e6b017c 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Development environment for Space Station 14"; - inputs.nixpkgs.url = "github:NixOS/nixpkgs/release-23.11"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs/release-24.05"; inputs.flake-utils.url = "github:numtide/flake-utils"; outputs = { self, nixpkgs, flake-utils }: From 504208c6656bb7e56681c23e3e46092c3205c940 Mon Sep 17 00:00:00 2001 From: Voomra Date: Mon, 3 Jun 2024 02:51:19 +0300 Subject: [PATCH 003/243] fix: localize PraySystem UI (#28535) tweak: localize PraySystem UI --- Content.Server/Prayer/PrayerSystem.cs | 2 +- Resources/Locale/en-US/prayers/prayers.ftl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Content.Server/Prayer/PrayerSystem.cs b/Content.Server/Prayer/PrayerSystem.cs index c8ef368dad..3b1ec3fa08 100644 --- a/Content.Server/Prayer/PrayerSystem.cs +++ b/Content.Server/Prayer/PrayerSystem.cs @@ -54,7 +54,7 @@ public sealed class PrayerSystem : EntitySystem return; } - _quickDialog.OpenDialog(actor.PlayerSession, Loc.GetString(comp.Verb), "Message", (string message) => + _quickDialog.OpenDialog(actor.PlayerSession, Loc.GetString(comp.Verb), Loc.GetString("prayer-popup-notify-pray-ui-message"), (string message) => { Pray(actor.PlayerSession, comp, message); }); diff --git a/Resources/Locale/en-US/prayers/prayers.ftl b/Resources/Locale/en-US/prayers/prayers.ftl index 07713bc821..532ba4954f 100644 --- a/Resources/Locale/en-US/prayers/prayers.ftl +++ b/Resources/Locale/en-US/prayers/prayers.ftl @@ -13,3 +13,4 @@ prayer-popup-notify-centcom-sent = You left a voicemail message for Central Comm prayer-popup-notify-syndicate-sent = You left a voicemail message for Syndicate High Command... prayer-popup-notify-pray-sent = Your message has been sent to the gods... prayer-popup-notify-pray-locked = You don't feel worthy enough... +prayer-popup-notify-pray-ui-message = Message From 947ed7bb22655e2499f27e521766e094663b13dc Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 2 Jun 2024 23:52:25 +0000 Subject: [PATCH 004/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index ec5569327f..53f9f65ab3 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Velcroboy - changes: - - message: Tweaked gas canisters to pass through cargo flaps. - type: Tweak - id: 6167 - time: '2024-03-17T00:55:32.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26193 - author: metalgearsloth changes: - message: Fix ID cards sometimes not loading properly. @@ -3853,3 +3846,10 @@ id: 6666 time: '2024-06-02T17:30:27.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28518 +- author: Voomra + changes: + - message: localize Pray UI + type: Fix + id: 6667 + time: '2024-06-02T23:51:19.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28535 From 307a0428c7fa324a69ec93fc0faf2a3679cd83de Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 3 Jun 2024 03:05:18 +0200 Subject: [PATCH 005/243] Shut up a child throwing a tantrum (#28538) --- Resources/Credits/GitHub.txt | 2 +- Tools/contribs_shared.ps1 | 2 ++ Tools/dump_github_contributors.ps1 | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index b3627580b1..f240c95247 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, Afrokada, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlexUm418, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, Baptr0b0t, BasedUser, beck-thompson, BellwetherLogic, BGare, bhenrich, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, blueDev2, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CakeQ, Callmore, CaptainSqrBeard, Carbonhell, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, Ciac32, clement-or, Clyybber, Cojoke-dot, ColdAutumnRain, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, deepdarkdepths, deepy, Delete69, deltanedas, DerbyX, DexlerXD, Doctor-Cpu, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DoutorWhite, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, DuskyJay, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, GNF54, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, gusxyz, Gyrandola, h3half, Hanzdegloker, Hardly3D, harikattar, Hebiman, Henry12116, HerCoyote23, hitomishirichan, Hmeister-real, HoofedEar, hord-brayden, hubismal, Hugal31, Huxellberger, Hyenh, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, ItsMeThom, j-giebel, Jackal298, Jackrost, jamessimo, janekvap, Jark255, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, joelhed, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTether, JustinTrotter, K-Dynamic, KaiShibaa, kalane15, kalanosh, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, luckyshotpictures, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Mangohydra, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MisterMecky, Mith-randalf, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, pigeonpeas, pissdemon, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, PursuitInAshes, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, RumiTiger, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, Shadowtheprotogen546, shampunj, SignalWalker, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, snebl, Snowni, snowsignal, SonicHDC, SoulFN, SoulSloth, SpaceManiac, SpeltIncorrectyl, SphiraI, spoogemonster, ssdaniel24, Stealthbomber16, StrawberryMoses, superjj18, SweptWasTaken, Szunti, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, Terraspark4941, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, Tornado-Technology, tosatur, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UBlueberry, UKNOWH, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, volundr-, Voomra, Vordenburg, vulppine, wafehling, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem +0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, Afrokada, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlexUm418, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, Baptr0b0t, BasedUser, beck-thompson, BellwetherLogic, BGare, bhenrich, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, blueDev2, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CakeQ, Callmore, CaptainSqrBeard, Carbonhell, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, Ciac32, clement-or, Clyybber, Cojoke-dot, ColdAutumnRain, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, deepdarkdepths, deepy, Delete69, deltanedas, DerbyX, DexlerXD, Doctor-Cpu, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DoutorWhite, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, DuskyJay, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, GNF54, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, gusxyz, Gyrandola, h3half, Hanzdegloker, Hardly3D, harikattar, Hebiman, Henry12116, HerCoyote23, hitomishirichan, Hmeister-real, HoofedEar, hord-brayden, hubismal, Hugal31, Huxellberger, Hyenh, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, ItsMeThom, j-giebel, Jackal298, Jackrost, jamessimo, janekvap, Jark255, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, joelhed, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTether, JustinTrotter, K-Dynamic, KaiShibaa, kalane15, kalanosh, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, luckyshotpictures, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Mangohydra, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MisterMecky, Mith-randalf, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, pigeonpeas, pissdemon, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, PursuitInAshes, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, RamZ, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, RumiTiger, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, Shadowtheprotogen546, shampunj, SignalWalker, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, snebl, Snowni, snowsignal, SonicHDC, SoulFN, SoulSloth, SpaceManiac, SpeltIncorrectyl, SphiraI, spoogemonster, ssdaniel24, Stealthbomber16, StrawberryMoses, superjj18, SweptWasTaken, Szunti, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, Terraspark4941, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, Tornado-Technology, tosatur, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UBlueberry, UKNOWH, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, volundr-, Voomra, Vordenburg, vulppine, wafehling, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem diff --git a/Tools/contribs_shared.ps1 b/Tools/contribs_shared.ps1 index ba97c50a9a..a3638b41b8 100644 --- a/Tools/contribs_shared.ps1 +++ b/Tools/contribs_shared.ps1 @@ -12,3 +12,5 @@ $ignore = @{ "ZDDM" = $true "TYoung86" = $true } + +$add = @("RamZ") diff --git a/Tools/dump_github_contributors.ps1 b/Tools/dump_github_contributors.ps1 index 51d8612b28..10e74bcf0b 100755 --- a/Tools/dump_github_contributors.ps1 +++ b/Tools/dump_github_contributors.ps1 @@ -29,7 +29,7 @@ function load_contribs([string] $repo) $engineJson = load_contribs("space-wizards/RobustToolbox") $contentJson = load_contribs("space-wizards/space-station-14") -($engineJson).login + ($contentJson).login ` +($engineJson).login + ($contentJson).login + ($add) ` | select -unique ` | Where-Object { -not $ignore[$_] }` | ForEach-Object { if($replacements[$_] -eq $null){ $_ } else { $replacements[$_] }} ` From 9dbdacda9533304004382ebf5f8c63a32e50916e Mon Sep 17 00:00:00 2001 From: Whisper <121047731+QuietlyWhisper@users.noreply.github.com> Date: Sun, 2 Jun 2024 22:18:49 -0400 Subject: [PATCH 006/243] Rename admin cloak to weh cloak (#28540) --- Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml b/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml index 35a793791c..a329c4034b 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml @@ -8,7 +8,7 @@ sprite: Clothing/Neck/Cloaks/centcomcloakformal.rsi - type: StealTarget stealGroup: HeadCloak # leaving this here because I suppose it might be interesting? - + - type: entity parent: ClothingNeckBase id: ClothingNeckCloakCap @@ -118,7 +118,7 @@ - type: entity parent: ClothingNeckBase id: ClothingNeckCloakAdmin - name: admin cloak + name: weh cloak description: Weh! components: - type: Sprite From 7ecff3e6996c4141e70611f4fb7585d1fcb983e5 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 02:19:55 +0000 Subject: [PATCH 007/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 53f9f65ab3..b602964081 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: metalgearsloth - changes: - - message: Fix ID cards sometimes not loading properly. - type: Fix - id: 6168 - time: '2024-03-17T01:10:59.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26195 - author: 21Melkuu changes: - message: Add new explosion-proof backpack in aplink. @@ -3853,3 +3846,10 @@ id: 6667 time: '2024-06-02T23:51:19.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28535 +- author: Whisper + changes: + - message: Renamed the player-obtainable "admin cloak" to "weh cloak". + type: Tweak + id: 6668 + time: '2024-06-03T02:18:49.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28540 From 509e3aedf7b91c1e1d11e5a885608bc92909f552 Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:28:38 -0400 Subject: [PATCH 008/243] RespawnRuleSystem tweaks. (#28528) --- .../Components/RespawnDeadRuleComponent.cs | 5 + .../Components/RespawnTrackerComponent.cs | 12 +- .../GameTicking/Rules/DeathMatchRuleSystem.cs | 4 +- .../GameTicking/Rules/RespawnRuleSystem.cs | 115 +++++++++--------- Resources/Prototypes/GameRules/roundstart.yml | 11 ++ 5 files changed, 84 insertions(+), 63 deletions(-) diff --git a/Content.Server/GameTicking/Rules/Components/RespawnDeadRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/RespawnDeadRuleComponent.cs index fafe811dd9..f6e4a3b129 100644 --- a/Content.Server/GameTicking/Rules/Components/RespawnDeadRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/RespawnDeadRuleComponent.cs @@ -6,4 +6,9 @@ [RegisterComponent, Access(typeof(RespawnRuleSystem))] public sealed partial class RespawnDeadRuleComponent : Component { + /// + /// Whether or not we want to add everyone who dies to the respawn tracker + /// + [DataField] + public bool AlwaysRespawnDead; } diff --git a/Content.Server/GameTicking/Rules/Components/RespawnTrackerComponent.cs b/Content.Server/GameTicking/Rules/Components/RespawnTrackerComponent.cs index 3d338c2d13..b9c8fe1096 100644 --- a/Content.Server/GameTicking/Rules/Components/RespawnTrackerComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/RespawnTrackerComponent.cs @@ -13,18 +13,24 @@ public sealed partial class RespawnTrackerComponent : Component /// A list of the people that should be respawned. /// Used to make sure that we don't respawn aghosts or observers. /// - [DataField("players")] + [DataField] public HashSet Players = new(); /// /// The delay between dying and respawning. /// - [DataField("respawnDelay")] + [DataField] public TimeSpan RespawnDelay = TimeSpan.Zero; /// /// A dictionary of player netuserids and when they will respawn. /// - [DataField("respawnQueue")] + [DataField] public Dictionary RespawnQueue = new(); + + /// + /// Whether or not to delete the original body when respawning + /// + [DataField] + public bool DeleteBody = true; } diff --git a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs index ad7c63ff58..9e3203d170 100644 --- a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs @@ -56,7 +56,7 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem(mob); - _respawn.AddToTracker(ev.Player.UserId, uid, tracker); + _respawn.AddToTracker(ev.Player.UserId, (uid, tracker)); _point.EnsurePlayer(ev.Player.UserId, uid, point); @@ -73,7 +73,7 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem SubscribeLocalEvent(OnMobStateChanged); } - private void OnSuicide(SuicideEvent ev) - { - if (!TryComp(ev.Victim, out var actor)) - return; - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out _, out var respawn)) - { - if (respawn.Players.Remove(actor.PlayerSession.UserId)) - QueueDel(ev.Victim); - } - } - - private void OnMobStateChanged(MobStateChangedEvent args) - { - if (args.NewMobState == MobState.Alive) - return; - - if (!TryComp(args.Target, out var actor)) - return; - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out _, out var rule)) - { - if (!GameTicker.IsGameRuleActive(uid, rule)) - continue; - - if (RespawnPlayer(args.Target, uid, actor: actor)) - break; - } - } - public override void Update(float frameTime) { base.Update(frameTime); @@ -75,8 +44,7 @@ public sealed class RespawnRuleSystem : GameRuleSystem foreach (var tracker in EntityQuery()) { - var queue = new Dictionary(tracker.RespawnQueue); - foreach (var (player, time) in queue) + foreach (var (player, time) in tracker.RespawnQueue) { if (_timing.CurTime < time) continue; @@ -92,53 +60,84 @@ public sealed class RespawnRuleSystem : GameRuleSystem } } - /// - /// Adds a given player to the respawn tracker, ensuring that they are respawned if they die. - /// - public void AddToTracker(EntityUid player, EntityUid tracker, RespawnTrackerComponent? component = null, ActorComponent? actor = null) + private void OnSuicide(SuicideEvent ev) { - if (!Resolve(tracker, ref component) || !Resolve(player, ref actor, false)) - return; + if (!TryComp(ev.Victim, out var actor)) + return; - AddToTracker(actor.PlayerSession.UserId, tracker, component); + var query = EntityQueryEnumerator(); + while (query.MoveNext(out _, out var respawn)) + { + if (respawn.Players.Remove(actor.PlayerSession.UserId)) + QueueDel(ev.Victim); + } } - /// - /// Adds a given player to the respawn tracker, ensuring that they are respawned if they die. - /// - public void AddToTracker(NetUserId id, EntityUid tracker, RespawnTrackerComponent? component = null) + private void OnMobStateChanged(MobStateChangedEvent args) { - if (!Resolve(tracker, ref component)) + if (args.NewMobState != MobState.Dead) return; - component.Players.Add(id); + if (!TryComp(args.Target, out var actor)) + return; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var respawnRule, out var tracker, out var rule)) + { + if (!GameTicker.IsGameRuleActive(uid, rule)) + continue; + + if (respawnRule.AlwaysRespawnDead) + AddToTracker(actor.PlayerSession.UserId, (uid, tracker)); + if (RespawnPlayer((args.Target, actor), (uid, tracker))) + break; + } } /// /// Attempts to directly respawn a player, skipping the lobby screen. /// - public bool RespawnPlayer(EntityUid player, EntityUid respawnTracker, RespawnTrackerComponent? component = null, ActorComponent? actor = null) + public bool RespawnPlayer(Entity player, Entity respawnTracker) { - if (!Resolve(respawnTracker, ref component) || !Resolve(player, ref actor, false)) + if (!respawnTracker.Comp.Players.Contains(player.Comp.PlayerSession.UserId) || respawnTracker.Comp.RespawnQueue.ContainsKey(player.Comp.PlayerSession.UserId)) return false; - if (!component.Players.Contains(actor.PlayerSession.UserId) || component.RespawnQueue.ContainsKey(actor.PlayerSession.UserId)) - return false; - - if (component.RespawnDelay == TimeSpan.Zero) + if (respawnTracker.Comp.RespawnDelay == TimeSpan.Zero) { if (_station.GetStations().FirstOrNull() is not { } station) return false; - QueueDel(player); - GameTicker.MakeJoinGame(actor.PlayerSession, station, silent: true); + if (respawnTracker.Comp.DeleteBody) + QueueDel(player); + GameTicker.MakeJoinGame(player.Comp.PlayerSession, station, silent: true); return false; } - var msg = Loc.GetString("rule-respawn-in-seconds", ("second", component.RespawnDelay.TotalSeconds)); + var msg = Loc.GetString("rule-respawn-in-seconds", ("second", respawnTracker.Comp.RespawnDelay.TotalSeconds)); var wrappedMsg = Loc.GetString("chat-manager-server-wrap-message", ("message", msg)); - _chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMsg, respawnTracker, false, actor.PlayerSession.Channel, Color.LimeGreen); - component.RespawnQueue[actor.PlayerSession.UserId] = _timing.CurTime + component.RespawnDelay; + _chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMsg, respawnTracker, false, player.Comp.PlayerSession.Channel, Color.LimeGreen); + + respawnTracker.Comp.RespawnQueue[player.Comp.PlayerSession.UserId] = _timing.CurTime + respawnTracker.Comp.RespawnDelay; + return true; } + + /// + /// Adds a given player to the respawn tracker, ensuring that they are respawned if they die. + /// + public void AddToTracker(Entity player, Entity respawnTracker) + { + if (!Resolve(respawnTracker, ref respawnTracker.Comp) || !Resolve(player, ref player.Comp, false)) + return; + + AddToTracker(player.Comp.PlayerSession.UserId, (respawnTracker, respawnTracker.Comp)); + } + + /// + /// Adds a given player to the respawn tracker, ensuring that they are respawned if they die. + /// + public void AddToTracker(NetUserId id, Entity tracker) + { + tracker.Comp.Players.Add(id); + } } diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index 93350163f6..cf2c5b2f47 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -5,6 +5,17 @@ components: - type: GameRule +- type: entity + noSpawn: true + parent: BaseGameRule + id: RespawnDeadRule + components: + - type: RespawnDeadRule + alwaysRespawnDead: true + - type: RespawnTracker + respawnDelay: 10 + deleteBody: false + - type: entity noSpawn: true parent: BaseGameRule From 31eb3ed62ce607205aaac59416c53f7f2c40da97 Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Sun, 2 Jun 2024 22:28:53 -0500 Subject: [PATCH 009/243] Welding tweaks (#27959) --- .../Configurable/ConfigurationSystem.cs | 6 +- .../DamageOnToolInteractComponent.cs | 27 +++---- .../Systems/DamageOnToolInteractSystem.cs | 5 +- .../Mech/Systems/MechAssemblySystem.cs | 4 +- Content.Server/Mech/Systems/MechSystem.cs | 4 +- .../PneumaticCannon/PneumaticCannonSystem.cs | 8 +- .../Melee/EnergySword/EnergySwordSystem.cs | 6 +- .../ArtifactElectricityTriggerSystem.cs | 4 +- .../EntitySystems/AnchorableSystem.cs | 2 +- .../EntitySystems/EncryptionKeySystem.cs | 4 +- .../Tools/Components/ToolComponent.cs | 75 ++++++++----------- .../Components/ToolRefinableComponent.cs | 11 +-- .../Tools/Systems/SharedToolSystem.Welder.cs | 22 +++--- .../Tools/Systems/SharedToolSystem.cs | 3 +- .../Tools/Systems/ToolRefinableSystem.cs | 20 +++-- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 2 +- .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 2 +- .../Consumable/Drinks/drinks_bottles.yml | 2 +- .../Objects/Consumable/Drinks/drinks_cans.yml | 2 +- .../Entities/Objects/Materials/shards.yml | 10 +-- .../Entities/Objects/Misc/broken_bottle.yml | 2 +- .../Objects/Misc/fire_extinguisher.yml | 2 +- .../Entities/Objects/Power/lights.yml | 14 ++-- .../Objects/Specific/Medical/surgery.yml | 8 +- .../Entities/Objects/Tools/cowtools.yml | 10 +-- .../Entities/Objects/Tools/gas_tanks.yml | 2 +- .../Entities/Objects/Tools/jaws_of_life.yml | 4 +- .../Entities/Objects/Tools/tools.yml | 4 +- .../Entities/Objects/Tools/welders.yml | 4 +- .../Objects/Weapons/Melee/baseball_bat.yml | 2 +- .../Structures/Storage/Tanks/tanks.yml | 4 +- .../Structures/Wallmounts/walldispenser.yml | 3 +- .../XenoArch/Effects/utility_effects.yml | 2 +- 33 files changed, 139 insertions(+), 141 deletions(-) rename Content.Server/Construction/Components/WelderRefinableComponent.cs => Content.Shared/Tools/Components/ToolRefinableComponent.cs (74%) rename Content.Server/Construction/RefiningSystem.cs => Content.Shared/Tools/Systems/ToolRefinableSystem.cs (59%) diff --git a/Content.Server/Configurable/ConfigurationSystem.cs b/Content.Server/Configurable/ConfigurationSystem.cs index 5f5f1ef7d1..bf89c3f7ed 100644 --- a/Content.Server/Configurable/ConfigurationSystem.cs +++ b/Content.Server/Configurable/ConfigurationSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Configurable; using Content.Shared.Interaction; using Content.Shared.Tools.Components; +using Content.Shared.Tools.Systems; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.Player; @@ -11,6 +12,7 @@ namespace Content.Server.Configurable; public sealed class ConfigurationSystem : EntitySystem { [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { @@ -28,7 +30,7 @@ public sealed class ConfigurationSystem : EntitySystem if (args.Handled) return; - if (!TryComp(args.Used, out ToolComponent? tool) || !tool.Qualities.Contains(component.QualityNeeded)) + if (!_toolSystem.HasQuality(args.Used, component.QualityNeeded)) return; args.Handled = _uiSystem.TryOpenUi(uid, ConfigurationUiKey.Key, args.User); @@ -68,7 +70,7 @@ public sealed class ConfigurationSystem : EntitySystem private void OnInsert(EntityUid uid, ConfigurationComponent component, ContainerIsInsertingAttemptEvent args) { - if (!TryComp(args.EntityUid, out ToolComponent? tool) || !tool.Qualities.Contains(component.QualityNeeded)) + if (!_toolSystem.HasQuality(args.EntityUid, component.QualityNeeded)) return; args.Cancel(); diff --git a/Content.Server/Damage/Components/DamageOnToolInteractComponent.cs b/Content.Server/Damage/Components/DamageOnToolInteractComponent.cs index e54090cdbb..547c29a202 100644 --- a/Content.Server/Damage/Components/DamageOnToolInteractComponent.cs +++ b/Content.Server/Damage/Components/DamageOnToolInteractComponent.cs @@ -1,22 +1,19 @@ using Content.Shared.Damage; using Content.Shared.Tools; -using Robust.Shared.Utility; +using Robust.Shared.Prototypes; -namespace Content.Server.Damage.Components +namespace Content.Server.Damage.Components; + +[RegisterComponent] +public sealed partial class DamageOnToolInteractComponent : Component { - [RegisterComponent] - public sealed partial class DamageOnToolInteractComponent : Component - { - [DataField("tools")] - public PrototypeFlags Tools { get; private set; } = new (); + [DataField] + public ProtoId Tools { get; private set; } - // TODO: Remove this snowflake stuff, make damage per-tool quality perhaps? - [DataField("weldingDamage")] - [ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier? WeldingDamage { get; private set; } + // TODO: Remove this snowflake stuff, make damage per-tool quality perhaps? + [DataField] + public DamageSpecifier? WeldingDamage { get; private set; } - [DataField("defaultDamage")] - [ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier? DefaultDamage { get; private set; } - } + [DataField] + public DamageSpecifier? DefaultDamage { get; private set; } } diff --git a/Content.Server/Damage/Systems/DamageOnToolInteractSystem.cs b/Content.Server/Damage/Systems/DamageOnToolInteractSystem.cs index 42676c1891..5980455e49 100644 --- a/Content.Server/Damage/Systems/DamageOnToolInteractSystem.cs +++ b/Content.Server/Damage/Systems/DamageOnToolInteractSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Interaction; using Content.Shared.Tools.Components; +using Content.Shared.Tools.Systems; using ItemToggleComponent = Content.Shared.Item.ItemToggle.Components.ItemToggleComponent; namespace Content.Server.Damage.Systems @@ -12,6 +13,7 @@ namespace Content.Server.Damage.Systems { [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { @@ -42,8 +44,7 @@ namespace Content.Server.Damage.Systems args.Handled = true; } else if (component.DefaultDamage is {} damage - && EntityManager.TryGetComponent(args.Used, out ToolComponent? tool) - && tool.Qualities.ContainsAny(component.Tools)) + && _toolSystem.HasQuality(args.Used, component.Tools)) { var dmg = _damageableSystem.TryChangeDamage(args.Target, damage, origin: args.User); diff --git a/Content.Server/Mech/Systems/MechAssemblySystem.cs b/Content.Server/Mech/Systems/MechAssemblySystem.cs index c1fff819b4..4b408343b7 100644 --- a/Content.Server/Mech/Systems/MechAssemblySystem.cs +++ b/Content.Server/Mech/Systems/MechAssemblySystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Interaction; using Content.Shared.Tag; using Content.Shared.Tools.Components; +using Content.Shared.Tools.Systems; using Robust.Server.Containers; using Robust.Shared.Containers; @@ -15,6 +16,7 @@ public sealed class MechAssemblySystem : EntitySystem { [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly TagSystem _tag = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; /// public override void Initialize() @@ -30,7 +32,7 @@ public sealed class MechAssemblySystem : EntitySystem private void OnInteractUsing(EntityUid uid, MechAssemblyComponent component, InteractUsingEvent args) { - if (TryComp(args.Used, out var toolComp) && toolComp.Qualities.Contains(component.QualityNeeded)) + if (_toolSystem.HasQuality(args.Used, component.QualityNeeded)) { foreach (var tag in component.RequiredParts.Keys) { diff --git a/Content.Server/Mech/Systems/MechSystem.cs b/Content.Server/Mech/Systems/MechSystem.cs index 53c6c62cdb..68b973f588 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -17,6 +17,7 @@ using Content.Shared.Tools.Components; using Content.Shared.Verbs; using Content.Shared.Wires; using Content.Server.Body.Systems; +using Content.Shared.Tools.Systems; using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Containers; @@ -35,6 +36,7 @@ public sealed partial class MechSystem : SharedMechSystem [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; /// public override void Initialize() @@ -87,7 +89,7 @@ public sealed partial class MechSystem : SharedMechSystem return; } - if (TryComp(args.Used, out var tool) && tool.Qualities.Contains("Prying") && component.BatterySlot.ContainedEntity != null) + if (_toolSystem.HasQuality(args.Used, "Prying") && component.BatterySlot.ContainedEntity != null) { var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.BatteryRemovalDelay, new RemoveBatteryEvent(), uid, target: uid, used: args.Target) diff --git a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs index 6e0e0c503a..7882522d30 100644 --- a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs +++ b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs @@ -7,7 +7,7 @@ using Content.Shared.Containers.ItemSlots; using Content.Shared.Interaction; using Content.Shared.PneumaticCannon; using Content.Shared.StatusEffect; -using Content.Shared.Tools.Components; +using Content.Shared.Tools.Systems; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Systems; @@ -22,6 +22,7 @@ public sealed class PneumaticCannonSystem : SharedPneumaticCannonSystem [Dependency] private readonly GunSystem _gun = default!; [Dependency] private readonly StunSystem _stun = default!; [Dependency] private readonly ItemSlotsSystem _slots = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { @@ -38,10 +39,7 @@ public sealed class PneumaticCannonSystem : SharedPneumaticCannonSystem if (args.Handled) return; - if (!TryComp(args.Used, out var tool)) - return; - - if (!tool.Qualities.Contains(component.ToolModifyPower)) + if (!_toolSystem.HasQuality(args.Used, component.ToolModifyPower)) return; var val = (int) component.Power; diff --git a/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs b/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs index e8897781f5..5970e16319 100644 --- a/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs +++ b/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs @@ -2,8 +2,7 @@ using Content.Shared.Interaction; using Content.Shared.Light; using Content.Shared.Light.Components; using Content.Shared.Toggleable; -using Content.Shared.Tools.Components; -using Content.Shared.Item; +using Content.Shared.Tools.Systems; using Robust.Shared.Random; namespace Content.Server.Weapons.Melee.EnergySword; @@ -13,6 +12,7 @@ public sealed class EnergySwordSystem : EntitySystem [Dependency] private readonly SharedRgbLightControllerSystem _rgbSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { @@ -38,7 +38,7 @@ public sealed class EnergySwordSystem : EntitySystem if (args.Handled) return; - if (!TryComp(args.Used, out ToolComponent? tool) || !tool.Qualities.ContainsAny("Pulsing")) + if (!_toolSystem.HasQuality(args.Used, "Pulsing")) return; args.Handled = true; diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs index aa2a16aa1b..019e09bbbb 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs @@ -3,12 +3,14 @@ using Content.Server.Power.Events; using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; using Content.Shared.Interaction; using Content.Shared.Tools.Components; +using Content.Shared.Tools.Systems; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; public sealed class ArtifactElectricityTriggerSystem : EntitySystem { [Dependency] private readonly ArtifactSystem _artifactSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { @@ -42,7 +44,7 @@ public sealed class ArtifactElectricityTriggerSystem : EntitySystem if (args.Handled) return; - if (!TryComp(args.Used, out ToolComponent? tool) || !tool.Qualities.ContainsAny("Pulsing")) + if (!_toolSystem.HasQuality(args.Used, "Pulsing")) return; args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User); diff --git a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs index e9ef053f62..efb5dfd024 100644 --- a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs +++ b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs @@ -79,7 +79,7 @@ public sealed partial class AnchorableSystem : EntitySystem return; // If the used entity doesn't have a tool, return early. - if (!TryComp(args.Used, out ToolComponent? usedTool) || !usedTool.Qualities.Contains(anchorable.Tool)) + if (!TryComp(args.Used, out ToolComponent? usedTool) || !_tool.HasQuality(args.Used, anchorable.Tool, usedTool)) return; args.Handled = true; diff --git a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs index 746147eb5b..ea07b5f8a5 100644 --- a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs +++ b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs @@ -6,10 +6,8 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Radio.Components; -using Content.Shared.Tools; using Content.Shared.Tools.Components; using Content.Shared.Wires; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Network; @@ -106,7 +104,7 @@ public sealed partial class EncryptionKeySystem : EntitySystem TryInsertKey(uid, component, args); } else if (TryComp(args.Used, out var tool) - && tool.Qualities.Contains(component.KeysExtractionMethod) + && _tool.HasQuality(args.Used, component.KeysExtractionMethod, tool) && component.KeyContainer.ContainedEntities.Count > 0) // dont block deconstruction { args.Handled = true; diff --git a/Content.Shared/Tools/Components/ToolComponent.cs b/Content.Shared/Tools/Components/ToolComponent.cs index 92857ab905..a7210c6fa0 100644 --- a/Content.Shared/Tools/Components/ToolComponent.cs +++ b/Content.Shared/Tools/Components/ToolComponent.cs @@ -1,52 +1,43 @@ +using Content.Shared.Tools.Systems; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Utility; -namespace Content.Shared.Tools.Components +namespace Content.Shared.Tools.Components; + +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedToolSystem))] +public sealed partial class ToolComponent : Component { - [RegisterComponent, NetworkedComponent] // TODO move tool system to shared, and make it a friend. - public sealed partial class ToolComponent : Component - { - [DataField("qualities")] - public PrototypeFlags Qualities { get; set; } = new(); - - /// - /// For tool interactions that have a delay before action this will modify the rate, time to wait is divided by this value - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("speed")] - public float SpeedModifier { get; set; } = 1; - - [DataField("useSound")] - public SoundSpecifier? UseSound { get; set; } - } + [DataField] + public PrototypeFlags Qualities = []; /// - /// Attempt event called *before* any do afters to see if the tool usage should succeed or not. - /// Raised on both the tool and then target. + /// For tool interactions that have a delay before action this will modify the rate, time to wait is divided by this value /// - public sealed class ToolUseAttemptEvent : CancellableEntityEventArgs - { - public EntityUid User { get; } + [DataField] + public float SpeedModifier = 1; - public ToolUseAttemptEvent(EntityUid user) - { - User = user; - } - } - - /// - /// Event raised on the user of a tool to see if they can actually use it. - /// - [ByRefEvent] - public struct ToolUserAttemptUseEvent - { - public EntityUid? Target; - public bool Cancelled = false; - - public ToolUserAttemptUseEvent(EntityUid? target) - { - Target = target; - } - } + [DataField] + public SoundSpecifier? UseSound; +} + +/// +/// Attempt event called *before* any do afters to see if the tool usage should succeed or not. +/// Raised on both the tool and then target. +/// +public sealed class ToolUseAttemptEvent(EntityUid user, float fuel) : CancellableEntityEventArgs +{ + public EntityUid User { get; } = user; + public float Fuel = fuel; +} + +/// +/// Event raised on the user of a tool to see if they can actually use it. +/// +[ByRefEvent] +public struct ToolUserAttemptUseEvent(EntityUid? target) +{ + public EntityUid? Target = target; + public bool Cancelled = false; } diff --git a/Content.Server/Construction/Components/WelderRefinableComponent.cs b/Content.Shared/Tools/Components/ToolRefinableComponent.cs similarity index 74% rename from Content.Server/Construction/Components/WelderRefinableComponent.cs rename to Content.Shared/Tools/Components/ToolRefinableComponent.cs index 2fe88f2670..5a311cdda8 100644 --- a/Content.Server/Construction/Components/WelderRefinableComponent.cs +++ b/Content.Shared/Tools/Components/ToolRefinableComponent.cs @@ -1,15 +1,16 @@ using Content.Shared.Storage; -using Content.Shared.Tools; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; +using Content.Shared.Tools.Systems; -namespace Content.Server.Construction.Components; +namespace Content.Shared.Tools.Components; /// /// Used for something that can be refined by welder. /// For example, glass shard can be refined to glass sheet. /// -[RegisterComponent, Access(typeof(RefiningSystem))] -public sealed partial class WelderRefinableComponent : Component +[RegisterComponent, NetworkedComponent, Access(typeof(ToolRefinablSystem))] +public sealed partial class ToolRefinableComponent : Component { /// /// The items created when the item is refined. @@ -27,7 +28,7 @@ public sealed partial class WelderRefinableComponent : Component /// The amount of fuel it takes to refine a given item. /// [DataField] - public float RefineFuel; + public float RefineFuel = 3f; /// /// The tool type needed in order to refine this item. diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs b/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs index e790b59cd1..60eafce474 100644 --- a/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs +++ b/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs @@ -16,8 +16,15 @@ public abstract partial class SharedToolSystem { SubscribeLocalEvent(OnWelderExamine); SubscribeLocalEvent(OnWelderAfterInteract); - SubscribeLocalEvent>(OnWelderToolUseAttempt); + + SubscribeLocalEvent((uid, comp, ev) => { + CanCancelWelderUse((uid, comp), ev.User, ev.Fuel, ev); + }); + SubscribeLocalEvent>((uid, comp, ev) => { + CanCancelWelderUse((uid, comp), ev.Event.User, ev.Event.Fuel, ev); + }); SubscribeLocalEvent(OnWelderDoAfter); + SubscribeLocalEvent(OnToggle); SubscribeLocalEvent(OnActivateAttempt); } @@ -120,23 +127,20 @@ public abstract partial class SharedToolSystem } } - private void OnWelderToolUseAttempt(Entity entity, ref DoAfterAttemptEvent args) + private void CanCancelWelderUse(Entity entity, EntityUid user, float requiredFuel, CancellableEntityEventArgs ev) { - var user = args.DoAfter.Args.User; - if (!ItemToggle.IsActivated(entity.Owner)) { _popup.PopupClient(Loc.GetString("welder-component-welder-not-lit-message"), entity, user); - args.Cancel(); - return; + ev.Cancel(); } - var (fuel, _) = GetWelderFuelAndCapacity(entity); + var (currentFuel, _) = GetWelderFuelAndCapacity(entity); - if (args.Event.Fuel > fuel) + if (requiredFuel > currentFuel) { _popup.PopupClient(Loc.GetString("welder-component-cannot-weld-message"), entity, user); - args.Cancel(); + ev.Cancel(); } } diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.cs b/Content.Shared/Tools/Systems/SharedToolSystem.cs index 9edae9b78f..72e984fd82 100644 --- a/Content.Shared/Tools/Systems/SharedToolSystem.cs +++ b/Content.Shared/Tools/Systems/SharedToolSystem.cs @@ -217,7 +217,7 @@ public abstract partial class SharedToolSystem : EntitySystem return false; // check if the tool allows being used - var beforeAttempt = new ToolUseAttemptEvent(user); + var beforeAttempt = new ToolUseAttemptEvent(user, fuel); RaiseLocalEvent(tool, beforeAttempt); if (beforeAttempt.Cancelled) return false; @@ -296,4 +296,3 @@ public abstract partial class SharedToolSystem : EntitySystem public sealed partial class CableCuttingFinishedEvent : SimpleDoAfterEvent; #endregion - diff --git a/Content.Server/Construction/RefiningSystem.cs b/Content.Shared/Tools/Systems/ToolRefinableSystem.cs similarity index 59% rename from Content.Server/Construction/RefiningSystem.cs rename to Content.Shared/Tools/Systems/ToolRefinableSystem.cs index ce7eb49ef1..e8ac4d492d 100644 --- a/Content.Server/Construction/RefiningSystem.cs +++ b/Content.Shared/Tools/Systems/ToolRefinableSystem.cs @@ -1,25 +1,26 @@ -using Content.Server.Construction.Components; using Content.Shared.Construction; using Content.Shared.Interaction; using Content.Shared.Storage; -using Content.Shared.Tools.Systems; +using Content.Shared.Tools.Components; +using Robust.Shared.Network; using Robust.Shared.Random; -namespace Content.Server.Construction; +namespace Content.Shared.Tools.Systems; -public sealed class RefiningSystem : EntitySystem +public sealed class ToolRefinablSystem : EntitySystem { + [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnDoAfter); } - private void OnInteractUsing(EntityUid uid, WelderRefinableComponent component, InteractUsingEvent args) + private void OnInteractUsing(EntityUid uid, ToolRefinableComponent component, InteractUsingEvent args) { if (args.Handled) return; @@ -34,11 +35,14 @@ public sealed class RefiningSystem : EntitySystem fuel: component.RefineFuel); } - private void OnDoAfter(EntityUid uid, WelderRefinableComponent component, WelderRefineDoAfterEvent args) + private void OnDoAfter(EntityUid uid, ToolRefinableComponent component, WelderRefineDoAfterEvent args) { if (args.Cancelled) return; + if (_net.IsClient) + return; + var xform = Transform(uid); var spawns = EntitySpawnCollection.GetSpawns(component.RefineResult, _random); foreach (var spawn in spawns) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index b5f69e6f56..51647f6985 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -2255,7 +2255,7 @@ - DoorBumpOpener - FootstepSound - type: Tool # Open door from xeno.yml. - speed: 1.5 + speedModifier: 1.5 qualities: - Prying useSound: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index a3d4eafacb..8c262d23da 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -22,7 +22,7 @@ NavSmash: !type:Bool true - type: Tool - speed: 1.5 + speedModifier: 1.5 qualities: - Prying - type: Prying diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml index 35ad43586b..7287119019 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml @@ -80,7 +80,7 @@ - type: Tool qualities: - Rolling - speed: 0.75 # not as good as a rolling pin but does the job + speedModifier: 0.75 # not as good as a rolling pin but does the job - type: PhysicalComposition materialComposition: Glass: 100 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml index 5d092673ed..f5cb260e03 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml @@ -56,7 +56,7 @@ - type: Tool qualities: - Rolling - speed: 0.25 # its small so takes longer to roll the entire dough flat + speedModifier: 0.25 # its small so takes longer to roll the entire dough flat - type: SpaceGarbage - type: TrashOnSolutionEmpty solution: drink diff --git a/Resources/Prototypes/Entities/Objects/Materials/shards.yml b/Resources/Prototypes/Entities/Objects/Materials/shards.yml index 561140a46a..fa6937dac3 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/shards.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/shards.yml @@ -85,7 +85,7 @@ components: - type: Sprite color: "#bbeeff" - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - type: DamageUserOnTrigger @@ -120,7 +120,7 @@ damage: types: Slash: 4.5 - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: PartRodMetal1 @@ -156,7 +156,7 @@ damage: types: Slash: 5.5 - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: SheetPlasma1 @@ -195,7 +195,7 @@ types: Slash: 4.5 Radiation: 2 - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: SheetUranium1 @@ -230,7 +230,7 @@ components: - type: Sprite color: "#e0aa36" - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: SheetBrass1 diff --git a/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml b/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml index 32222d0036..9d3ef6c424 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml @@ -26,6 +26,6 @@ materialComposition: Glass: 50 - type: SpaceGarbage - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 diff --git a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml index 112ce99710..71629919ae 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml @@ -46,7 +46,7 @@ - type: Tool qualities: - Rolling - speed: 0.5 # its very big, akward to use + speedModifier: 0.5 # its very big, akward to use - type: Appearance - type: GenericVisualizer visuals: diff --git a/Resources/Prototypes/Entities/Objects/Power/lights.yml b/Resources/Prototypes/Entities/Objects/Power/lights.yml index bccf10bee5..3f7da1efab 100644 --- a/Resources/Prototypes/Entities/Objects/Power/lights.yml +++ b/Resources/Prototypes/Entities/Objects/Power/lights.yml @@ -66,7 +66,7 @@ materialComposition: Glass: 25 - type: SpaceGarbage - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 @@ -273,7 +273,7 @@ - type: Construction graph: CyanLight node: icon - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: ShardCrystalCyan @@ -293,7 +293,7 @@ - type: Construction graph: BlueLight node: icon - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: ShardCrystalBlue @@ -313,7 +313,7 @@ - type: Construction graph: PinkLight node: icon - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: ShardCrystalPink @@ -333,7 +333,7 @@ - type: Construction graph: OrangeLight node: icon - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: ShardCrystalOrange @@ -353,7 +353,7 @@ - type: Construction graph: RedLight node: icon - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: ShardCrystalRed @@ -373,7 +373,7 @@ - type: Construction graph: GreenLight node: icon - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: ShardCrystalGreen diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml index aa0cf46187..c4f7798154 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml @@ -178,7 +178,7 @@ - type: Tool qualities: - Sawing - speed: 1.0 + speedModifier: 1.0 # No melee for regular saw because have you ever seen someone use a band saw as a weapon? It's dumb. - type: entity @@ -200,7 +200,7 @@ - type: Tool qualities: - Sawing - speed: 0.5 + speedModifier: 0.5 - type: entity name: circular saw @@ -222,7 +222,7 @@ - type: Tool qualities: - Sawing - speed: 1.5 + speedModifier: 1.5 - type: entity name: advanced circular saw @@ -245,4 +245,4 @@ - type: Tool qualities: - Sawing - speed: 2.0 + speedModifier: 2.0 diff --git a/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml b/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml index 87959ebef3..c9b37b8b1a 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml @@ -21,7 +21,7 @@ - Cutting useSound: path: /Audio/Items/wirecutter.ogg - speed: 0.05 + speedModifier: 0.05 - type: Item sprite: Objects/Tools/Cowtools/haycutters.rsi @@ -46,7 +46,7 @@ - Screwing useSound: collection: Screwdriver - speed: 0.05 + speedModifier: 0.05 - type: entity name: wronch @@ -69,7 +69,7 @@ - Anchoring useSound: path: /Audio/Items/ratchet.ogg - speed: 0.05 + speedModifier: 0.05 - type: entity name: cowbar @@ -93,7 +93,7 @@ - Prying useSound: path: /Audio/Items/crowbar.ogg - speed: 0.05 + speedModifier: 0.05 - type: ToolTileCompatible - type: Prying @@ -127,7 +127,7 @@ size: Small sprite: Objects/Tools/Cowtools/cowelder.rsi - type: Tool - speed: 0.05 + speedModifier: 0.05 - type: entity name: milkalyzer diff --git a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml index 7bdd32f457..53423e84a4 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml @@ -51,7 +51,7 @@ - type: Tool qualities: - Rolling - speed: 0.6 # fairly unwieldly but nice round surface + speedModifier: 0.6 # fairly unwieldly but nice round surface - type: entity parent: GasTankRoundBase diff --git a/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml b/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml index 8e2b759797..c80e53870e 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml @@ -21,7 +21,7 @@ - type: Tool qualities: - Prying - speed: 1.5 + speedModifier: 1.5 useSound: /Audio/Items/jaws_pry.ogg - type: Prying pryPowered: true @@ -69,7 +69,7 @@ - type: Tool qualities: - Prying - speed: 3.0 + speedModifier: 3.0 - type: MultipleTool entries: - behavior: Prying diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml index 452a905329..7930482960 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml @@ -304,7 +304,7 @@ - type: Tool qualities: - Screwing - speed: 1.5 + speedModifier: 1.5 useSound: /Audio/Items/drill_use.ogg - type: MultipleTool statusShowBehavior: true @@ -479,7 +479,7 @@ - type: Tool qualities: - Screwing - speed: 1.2 # Kept for future adjustments. Currently 1.2x for balance + speedModifier: 1.2 # Kept for future adjustments. Currently 1.2x for balance useSound: /Audio/Items/drill_use.ogg - type: ToolTileCompatible - type: MultipleTool diff --git a/Resources/Prototypes/Entities/Objects/Tools/welders.yml b/Resources/Prototypes/Entities/Objects/Tools/welders.yml index 9bf3f2e2cb..9db30edb52 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/welders.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/welders.yml @@ -139,7 +139,7 @@ Quantity: 250 maxVol: 250 - type: Tool - speed: 1.3 + speedModifier: 1.3 - type: entity name: experimental welding tool @@ -190,7 +190,7 @@ Quantity: 50 maxVol: 50 - type: Tool - speed: 0.7 + speedModifier: 0.7 - type: PointLight enabled: false radius: 1.0 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml index 5347096bf1..818c4bd676 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml @@ -26,7 +26,7 @@ - type: Tool qualities: - Rolling - speed: 0.75 # a bit unwieldly but does the job + speedModifier: 0.75 # a bit unwieldly but does the job - type: Clothing quickEquip: false slots: diff --git a/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml b/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml index 934298b620..df19550cdb 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml @@ -25,8 +25,7 @@ - type: ReagentTank tankType: Fuel - type: DamageOnToolInteract - tools: - - Welding + tools: Welding weldingDamage: types: Heat: 10 @@ -217,4 +216,3 @@ fillBaseName: watertank-2- - type: ExaminableSolution solution: tank - diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/walldispenser.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/walldispenser.yml index 72ea308af0..3570264a57 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/walldispenser.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/walldispenser.yml @@ -82,8 +82,7 @@ - type: ReagentTank tankType: Fuel - type: DamageOnToolInteract - tools: - - Welding + tools: Welding weldingDamage: types: Heat: 20 diff --git a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml index 84df09af33..a896708057 100644 --- a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml +++ b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml @@ -220,7 +220,7 @@ - type: Tool qualities: - Screwing - speed: 2 # Very powerful multitool to balance out the desire to sell or scrap for points + speedModifier: 2 # Very powerful multitool to balance out the desire to sell or scrap for points useSound: /Audio/Items/drill_use.ogg - type: Tag tags: From 91875e91e13e77af5c9ca3b0c8a65a0fa232d5cd Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:30:00 -0400 Subject: [PATCH 010/243] Strip Items From Things Before Biomassing Them (#28544) --- .../Medical/BiomassReclaimer/BiomassReclaimerSystem.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index f8d5139a46..a6285294c9 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -15,6 +15,7 @@ using Content.Shared.DoAfter; using Content.Shared.Humanoid; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; +using Content.Shared.Inventory; using Content.Shared.Jittering; using Content.Shared.Medical; using Content.Shared.Mind; @@ -36,6 +37,7 @@ namespace Content.Server.Medical.BiomassReclaimer public sealed class BiomassReclaimerSystem : EntitySystem { [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedJitteringSystem _jitteringSystem = default!; [Dependency] private readonly SharedAudioSystem _sharedAudioSystem = default!; @@ -49,6 +51,7 @@ namespace Content.Server.Medical.BiomassReclaimer [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly MaterialStorageSystem _material = default!; [Dependency] private readonly SharedMindSystem _minds = default!; + [Dependency] private readonly InventorySystem _inventory = default!; [ValidatePrototypeId] public const string BiomassPrototype = "Biomass"; @@ -221,6 +224,12 @@ namespace Content.Server.Medical.BiomassReclaimer component.ProcessingTimer = physics.FixturesMass * component.ProcessingTimePerUnitMass; + var inventory = _inventory.GetHandOrInventoryEntities(toProcess); + foreach (var item in inventory) + { + _transform.DropNextTo(item, ent.Owner); + } + QueueDel(toProcess); } From 0a8167f055929533129a8c2b2aa518937db1d968 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 03:29:59 +0000 Subject: [PATCH 011/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index b602964081..3086e6a71c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: 21Melkuu - changes: - - message: Add new explosion-proof backpack in aplink. - type: Add - id: 6169 - time: '2024-03-17T02:21:13.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26187 - author: Ilya246 changes: - message: Syndicate decoy bombs may now be purchased from the uplink. @@ -3853,3 +3846,10 @@ id: 6668 time: '2024-06-03T02:18:49.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28540 +- author: Vermidia + changes: + - message: Glass shards are no longer weldable with an unlit welder. + type: Fix + id: 6669 + time: '2024-06-03T03:28:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/27959 From 42217079b76084ef2d45d3b32b96027ae36c5355 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 03:31:08 +0000 Subject: [PATCH 012/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 3086e6a71c..941b933136 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,14 +1,4 @@ Entries: -- author: Ilya246 - changes: - - message: Syndicate decoy bombs may now be purchased from the uplink. - type: Add - - message: Syndicate bomb description no longer lies about their minimum detonation - time. - type: Fix - id: 6170 - time: '2024-03-17T02:31:41.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26034 - author: wafehling changes: - message: Added 18 new bounties to cargo bounty system. @@ -3853,3 +3843,11 @@ id: 6669 time: '2024-06-03T03:28:53.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/27959 +- author: AJCM-git + changes: + - message: Biomass reclaimers no longer act as a void to any unfortunate belongings + a corpse may be wearing + type: Tweak + id: 6670 + time: '2024-06-03T03:30:00.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28544 From be35a5d36991237525a490116af5363e06ab5216 Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:33:30 -0400 Subject: [PATCH 013/243] Header of Gas tank UI screen now fetched from Entity name and local (#28545) --- .../GasTank/GasTankBoundUserInterface.cs | 3 +- .../Systems/Atmos/GasTank/GasTankWindow.cs | 339 +++++++++--------- .../Locale/en-US/atmos/gas-tank-component.ftl | 1 - 3 files changed, 167 insertions(+), 176 deletions(-) diff --git a/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankBoundUserInterface.cs b/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankBoundUserInterface.cs index ee8cb28d2c..4702f8f365 100644 --- a/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankBoundUserInterface.cs +++ b/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankBoundUserInterface.cs @@ -1,6 +1,5 @@ using Content.Shared.Atmos.Components; using JetBrains.Annotations; -using Robust.Client.GameObjects; namespace Content.Client.UserInterface.Systems.Atmos.GasTank { @@ -30,7 +29,7 @@ namespace Content.Client.UserInterface.Systems.Atmos.GasTank protected override void Open() { base.Open(); - _window = new GasTankWindow(this); + _window = new GasTankWindow(this, EntMan.GetComponent(Owner).EntityName); _window.OnClose += Close; _window.OpenCentered(); } diff --git a/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankWindow.cs b/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankWindow.cs index 7797a096de..c23850a650 100644 --- a/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankWindow.cs +++ b/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankWindow.cs @@ -10,201 +10,194 @@ using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using static Robust.Client.UserInterface.Controls.BoxContainer; -namespace Content.Client.UserInterface.Systems.Atmos.GasTank +namespace Content.Client.UserInterface.Systems.Atmos.GasTank; + +public sealed class GasTankWindow + : BaseWindow { - public sealed class GasTankWindow - : BaseWindow + private readonly RichTextLabel _lblPressure; + private readonly FloatSpinBox _spbPressure; + private readonly RichTextLabel _lblInternals; + private readonly Button _btnInternals; + + public GasTankWindow(GasTankBoundUserInterface owner, string uidName) { - private GasTankBoundUserInterface _owner; - private readonly Label _lblName; - private readonly BoxContainer _topContainer; - private readonly Control _contentContainer; + Control contentContainer; + BoxContainer topContainer; + TextureButton btnClose; + var resourceCache = IoCManager.Resolve(); + var rootContainer = new LayoutContainer { Name = "GasTankRoot" }; + AddChild(rootContainer); + MouseFilter = MouseFilterMode.Stop; - private readonly IResourceCache _resourceCache = default!; - private readonly RichTextLabel _lblPressure; - private readonly FloatSpinBox _spbPressure; - private readonly RichTextLabel _lblInternals; - private readonly Button _btnInternals; - - public GasTankWindow(GasTankBoundUserInterface owner) + var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png"); + var back = new StyleBoxTexture { - TextureButton btnClose; - _resourceCache = IoCManager.Resolve(); - _owner = owner; - var rootContainer = new LayoutContainer {Name = "GasTankRoot"}; - AddChild(rootContainer); + Texture = panelTex, + Modulate = Color.FromHex("#25252A"), + }; - MouseFilter = MouseFilterMode.Stop; + back.SetPatchMargin(StyleBox.Margin.All, 10); - var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png"); - var back = new StyleBoxTexture + var topPanel = new PanelContainer + { + PanelOverride = back, + MouseFilter = MouseFilterMode.Pass + }; + + var bottomWrap = new LayoutContainer + { + Name = "BottomWrap" + }; + + rootContainer.AddChild(topPanel); + rootContainer.AddChild(bottomWrap); + + LayoutContainer.SetAnchorPreset(topPanel, LayoutContainer.LayoutPreset.Wide); + LayoutContainer.SetMarginBottom(topPanel, -85); + + LayoutContainer.SetAnchorPreset(bottomWrap, LayoutContainer.LayoutPreset.VerticalCenterWide); + LayoutContainer.SetGrowHorizontal(bottomWrap, LayoutContainer.GrowDirection.Both); + + + var topContainerWrap = new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Children = { - Texture = panelTex, - Modulate = Color.FromHex("#25252A"), - }; - - back.SetPatchMargin(StyleBox.Margin.All, 10); - - var topPanel = new PanelContainer - { - PanelOverride = back, - MouseFilter = MouseFilterMode.Pass - }; - - var bottomWrap = new LayoutContainer - { - Name = "BottomWrap" - }; - - rootContainer.AddChild(topPanel); - rootContainer.AddChild(bottomWrap); - - LayoutContainer.SetAnchorPreset(topPanel, LayoutContainer.LayoutPreset.Wide); - LayoutContainer.SetMarginBottom(topPanel, -85); - - LayoutContainer.SetAnchorPreset(bottomWrap, LayoutContainer.LayoutPreset.VerticalCenterWide); - LayoutContainer.SetGrowHorizontal(bottomWrap, LayoutContainer.GrowDirection.Both); - - - var topContainerWrap = new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - Children = + (topContainer = new BoxContainer { - (_topContainer = new BoxContainer - { - Orientation = LayoutOrientation.Vertical - }), - new Control {MinSize = new Vector2(0, 110)} - } - }; + Orientation = LayoutOrientation.Vertical + }), + new Control {MinSize = new Vector2(0, 110)} + } + }; - rootContainer.AddChild(topContainerWrap); + rootContainer.AddChild(topContainerWrap); - LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide); + LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide); - var font = _resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13); + var font = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13); - var topRow = new BoxContainer + var topRow = new BoxContainer + { + Orientation = LayoutOrientation.Horizontal, + Margin = new Thickness(4, 2, 12, 2), + Children = + { + (new Label + { + Text = uidName, + FontOverride = font, + FontColorOverride = StyleNano.NanoGold, + VerticalAlignment = VAlignment.Center, + HorizontalExpand = true, + HorizontalAlignment = HAlignment.Left, + Margin = new Thickness(0, 0, 20, 0), + }), + (btnClose = new TextureButton + { + StyleClasses = {DefaultWindow.StyleClassWindowCloseButton}, + VerticalAlignment = VAlignment.Center + }) + } + }; + + var middle = new PanelContainer + { + PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#202025") }, + Children = + { + (contentContainer = new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Margin = new Thickness(8, 4), + }) + } + }; + + topContainer.AddChild(topRow); + topContainer.AddChild(new PanelContainer + { + MinSize = new Vector2(0, 2), + PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#525252ff") } + }); + topContainer.AddChild(middle); + topContainer.AddChild(new PanelContainer + { + MinSize = new Vector2(0, 2), + PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#525252ff") } + }); + + + _lblPressure = new RichTextLabel(); + contentContainer.AddChild(_lblPressure); + + //internals + _lblInternals = new RichTextLabel + { MinSize = new Vector2(200, 0), VerticalAlignment = VAlignment.Center }; + _btnInternals = new Button { Text = Loc.GetString("gas-tank-window-internals-toggle-button") }; + + contentContainer.AddChild( + new BoxContainer { Orientation = LayoutOrientation.Horizontal, - Margin = new Thickness(4, 2, 12, 2), - Children = - { - (_lblName = new Label - { - Text = Loc.GetString("gas-tank-window-label"), - FontOverride = font, - FontColorOverride = StyleNano.NanoGold, - VerticalAlignment = VAlignment.Center, - HorizontalExpand = true, - HorizontalAlignment = HAlignment.Left, - Margin = new Thickness(0, 0, 20, 0), - }), - (btnClose = new TextureButton - { - StyleClasses = {DefaultWindow.StyleClassWindowCloseButton}, - VerticalAlignment = VAlignment.Center - }) - } - }; - - var middle = new PanelContainer - { - PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#202025")}, - Children = - { - (_contentContainer = new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - Margin = new Thickness(8, 4), - }) - } - }; - - _topContainer.AddChild(topRow); - _topContainer.AddChild(new PanelContainer - { - MinSize = new Vector2(0, 2), - PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")} - }); - _topContainer.AddChild(middle); - _topContainer.AddChild(new PanelContainer - { - MinSize = new Vector2(0, 2), - PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")} + Margin = new Thickness(0, 7, 0, 0), + Children = { _lblInternals, _btnInternals } }); - - _lblPressure = new RichTextLabel(); - _contentContainer.AddChild(_lblPressure); - - //internals - _lblInternals = new RichTextLabel - {MinSize = new Vector2(200, 0), VerticalAlignment = VAlignment.Center}; - _btnInternals = new Button {Text = Loc.GetString("gas-tank-window-internals-toggle-button") }; - - _contentContainer.AddChild( - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Margin = new Thickness(0, 7, 0, 0), - Children = {_lblInternals, _btnInternals} - }); - - // Separator - _contentContainer.AddChild(new Control - { - MinSize = new Vector2(0, 10) - }); - - _contentContainer.AddChild(new Label - { - Text = Loc.GetString("gas-tank-window-output-pressure-label"), - Align = Label.AlignMode.Center - }); - _spbPressure = new FloatSpinBox - { - IsValid = f => f >= 0 || f <= 3000, - Margin = new Thickness(25, 0, 25, 7) - }; - _contentContainer.AddChild(_spbPressure); - - // Handlers - _spbPressure.OnValueChanged += args => - { - _owner.SetOutputPressure(args.Value); - }; - - _btnInternals.OnPressed += args => - { - _owner.ToggleInternals(); - }; - - btnClose.OnPressed += _ => Close(); - } - - public void UpdateState(GasTankBoundUserInterfaceState state) + // Separator + contentContainer.AddChild(new Control { - _lblPressure.SetMarkup(Loc.GetString("gas-tank-window-tank-pressure-text", ("tankPressure", $"{state.TankPressure:0.##}"))); - _btnInternals.Disabled = !state.CanConnectInternals; - _lblInternals.SetMarkup(Loc.GetString("gas-tank-window-internal-text", - ("status", Loc.GetString(state.InternalsConnected ? "gas-tank-window-internal-connected" : "gas-tank-window-internal-disconnected")))); - if (state.OutputPressure.HasValue) - { - _spbPressure.Value = state.OutputPressure.Value; - } - } + MinSize = new Vector2(0, 10) + }); - protected override DragMode GetDragModeFor(Vector2 relativeMousePos) + contentContainer.AddChild(new Label { - return DragMode.Move; - } + Text = Loc.GetString("gas-tank-window-output-pressure-label"), + Align = Label.AlignMode.Center + }); + _spbPressure = new FloatSpinBox + { + IsValid = f => f >= 0 || f <= 3000, + Margin = new Thickness(25, 0, 25, 7) + }; + contentContainer.AddChild(_spbPressure); - protected override bool HasPoint(Vector2 point) + // Handlers + _spbPressure.OnValueChanged += args => { - return false; + owner.SetOutputPressure(args.Value); + }; + + _btnInternals.OnPressed += args => + { + owner.ToggleInternals(); + }; + + btnClose.OnPressed += _ => Close(); + } + + public void UpdateState(GasTankBoundUserInterfaceState state) + { + _lblPressure.SetMarkup(Loc.GetString("gas-tank-window-tank-pressure-text", ("tankPressure", $"{state.TankPressure:0.##}"))); + _btnInternals.Disabled = !state.CanConnectInternals; + _lblInternals.SetMarkup(Loc.GetString("gas-tank-window-internal-text", + ("status", Loc.GetString(state.InternalsConnected ? "gas-tank-window-internal-connected" : "gas-tank-window-internal-disconnected")))); + if (state.OutputPressure.HasValue) + { + _spbPressure.Value = state.OutputPressure.Value; } } + + protected override DragMode GetDragModeFor(Vector2 relativeMousePos) + { + return DragMode.Move; + } + + protected override bool HasPoint(Vector2 point) + { + return false; + } } diff --git a/Resources/Locale/en-US/atmos/gas-tank-component.ftl b/Resources/Locale/en-US/atmos/gas-tank-component.ftl index ae10d5630c..1f2d8f1e9f 100644 --- a/Resources/Locale/en-US/atmos/gas-tank-component.ftl +++ b/Resources/Locale/en-US/atmos/gas-tank-component.ftl @@ -14,7 +14,6 @@ comp-gas-tank-examine-closed-valve = Gas release valve is [color=green]closed[/c control-verb-open-control-panel-text = Open Control Panel ## UI -gas-tank-window-label = Gas Tank gas-tank-window-internals-toggle-button = Toggle gas-tank-window-output-pressure-label = Output Pressure gas-tank-window-tank-pressure-text = Pressure: {$tankPressure} kPA From 39481fb2b018921bca058f49433422db848cd7ef Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 03:34:36 +0000 Subject: [PATCH 014/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 941b933136..f30481fee7 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: wafehling - changes: - - message: Added 18 new bounties to cargo bounty system. - type: Add - id: 6171 - time: '2024-03-17T17:06:17.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26160 - author: Ko4erga changes: - message: Added craftable high and small wooden fences, bench. New stairs. @@ -3851,3 +3844,10 @@ id: 6670 time: '2024-06-03T03:30:00.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28544 +- author: Laneron + changes: + - message: Jetpack UI window now has correctly displayed header + type: Fix + id: 6671 + time: '2024-06-03T03:33:31.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28545 From 0d864d324cd0ca06e06484b38eadd8ea5c06ef5b Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 3 Jun 2024 01:12:13 -0400 Subject: [PATCH 015/243] Add support for HeldPrefix to SolutionContainerVisualsSystem (#28532) --- .../Visualizers/SolutionContainerVisualsSystem.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs index f1e8e8d7aa..17b88fb5a8 100644 --- a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs +++ b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Hands; +using Content.Shared.Item; using Content.Shared.Rounding; using Robust.Client.GameObjects; using Robust.Shared.Prototypes; @@ -150,6 +151,9 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem(uid, out var item)) + return; + if (!AppearanceSystem.TryGetData(uid, SolutionContainerVisuals.FillFraction, out var fraction, appearance)) return; @@ -159,7 +163,8 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem Date: Mon, 3 Jun 2024 17:21:46 +1200 Subject: [PATCH 016/243] Add PrototypeUploadTest (#28522) --- .../PrototypeTests/PrototypeUploadTest.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 Content.IntegrationTests/Tests/PrototypeTests/PrototypeUploadTest.cs diff --git a/Content.IntegrationTests/Tests/PrototypeTests/PrototypeUploadTest.cs b/Content.IntegrationTests/Tests/PrototypeTests/PrototypeUploadTest.cs new file mode 100644 index 0000000000..c641cd9bd1 --- /dev/null +++ b/Content.IntegrationTests/Tests/PrototypeTests/PrototypeUploadTest.cs @@ -0,0 +1,85 @@ +using Content.Shared.Tag; +using Robust.Client.Upload.Commands; +using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; +using Robust.Shared.Upload; + +namespace Content.IntegrationTests.Tests.PrototypeTests; + +public sealed class PrototypeUploadTest +{ + public const string IdA = "UploadTestPrototype"; + public const string IdB = $"{IdA}NoParent"; + public const string IdC = $"{IdA}Abstract"; + public const string IdD = $"{IdA}UploadedParent"; + + private const string File = $@" +- type: entity + parent: BaseStructure # BaseItem can cause AllItemsHaveSpritesTest to fail + id: {IdA} + +- type: entity + id: {IdB} + +- type: entity + id: {IdC} + abstract: true + components: + - type: Tag + +- type: entity + id: {IdD} + parent: {IdC} +"; + + [Test] + [TestOf(typeof(LoadPrototypeCommand))] + public async Task TestFileUpload() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings {Connected = true}); + var sCompFact = pair.Server.ResolveDependency(); + var cCompFact = pair.Client.ResolveDependency(); + + Assert.That(!pair.Server.ProtoMan.TryIndex(IdA, out _)); + Assert.That(!pair.Server.ProtoMan.TryIndex(IdB, out _)); + Assert.That(!pair.Server.ProtoMan.TryIndex(IdC, out _)); + Assert.That(!pair.Server.ProtoMan.TryIndex(IdD, out _)); + + Assert.That(!pair.Client.ProtoMan.TryIndex(IdA, out _)); + Assert.That(!pair.Client.ProtoMan.TryIndex(IdB, out _)); + Assert.That(!pair.Client.ProtoMan.TryIndex(IdC, out _)); + Assert.That(!pair.Client.ProtoMan.TryIndex(IdD, out _)); + + var protoLoad = pair.Client.ResolveDependency(); + await pair.Client.WaitPost(() => protoLoad.SendGamePrototype(File)); + await pair.RunTicksSync(10); + + Assert.That(pair.Server.ProtoMan.TryIndex(IdA, out var sProtoA)); + Assert.That(pair.Server.ProtoMan.TryIndex(IdB, out var sProtoB)); + Assert.That(!pair.Server.ProtoMan.TryIndex(IdC, out _)); + Assert.That(pair.Server.ProtoMan.TryIndex(IdD, out var sProtoD)); + + Assert.That(pair.Client.ProtoMan.TryIndex(IdA, out var cProtoA)); + Assert.That(pair.Client.ProtoMan.TryIndex(IdB, out var cProtoB)); + Assert.That(!pair.Client.ProtoMan.TryIndex(IdC, out _)); + Assert.That(pair.Client.ProtoMan.TryIndex(IdD, out var cProtoD)); + + // Arbitrarily choosing TagComponent to check that inheritance works for uploaded prototypes. + + await pair.Server.WaitPost(() => + { + Assert.That(sProtoA!.TryGetComponent(out _, sCompFact), Is.True); + Assert.That(sProtoB!.TryGetComponent(out _, sCompFact), Is.False); + Assert.That(sProtoD!.TryGetComponent(out _, sCompFact), Is.True); + }); + + await pair.Client.WaitPost(() => + { + Assert.That(cProtoA!.TryGetComponent(out _, cCompFact), Is.True); + Assert.That(cProtoB!.TryGetComponent(out _, cCompFact), Is.False); + Assert.That(cProtoD!.TryGetComponent(out _, cCompFact), Is.True); + }); + + await pair.CleanReturnAsync(); + } +} From 36396e3f864b85783f831dde21f9f94b121b2da5 Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Mon, 3 Jun 2024 06:54:32 -0500 Subject: [PATCH 017/243] Changes SyndiCat spawner to a radio (#28492) --- Resources/Locale/en-US/store/uplink-catalog.ftl | 4 ++-- Resources/Prototypes/Catalog/uplink_catalog.yml | 2 +- .../reinforcement_teleporter.yml | 17 +++++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl index 3ae2d65c16..1b9fa8aff9 100644 --- a/Resources/Locale/en-US/store/uplink-catalog.ftl +++ b/Resources/Locale/en-US/store/uplink-catalog.ftl @@ -370,8 +370,8 @@ uplink-syndicate-sponge-box-desc = A box containing 6 syndicate sponges disguise uplink-slipocalypse-clustersoap-name = Slipocalypse Clustersoap uplink-slipocalypse-clustersoap-desc = Scatters arounds small pieces of syndicate-brand soap after being thrown, these pieces of soap evaporate after 60 seconds. -uplink-mobcat-microbomb-name = SyndiCat -uplink-mobcat-microbomb-desc = A hand cat equipped with a microbomb implant. Explodes when seriously injured. Can bite painfully +uplink-mobcat-microbomb-name = SyndiCat Teleporter +uplink-mobcat-microbomb-desc = Call in a handy cat equipped with a microbomb implant. Explodes when seriously injured. Can bite painfully. uplink-chameleon-projector-name = Chameleon Projector uplink-chameleon-projector-desc = Disappear in plain sight by creating a hologram of an item around you. Do not use this to play the game "Object Search". diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index c8d6eb4f61..efec2417d0 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -999,7 +999,7 @@ name: uplink-mobcat-microbomb-name description: uplink-mobcat-microbomb-desc icon: { sprite: /Textures/Mobs/Pets/cat.rsi, state: syndicat } - productEntity: MobCatSyndy + productEntity: ReinforcementRadioSyndicateSyndiCat cost: Telecrystal: 6 categories: diff --git a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml index c62783fcee..2085422c9b 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml @@ -54,6 +54,23 @@ prototype: MobMonkeySyndicateAgentNukeops selectablePrototypes: ["SyndicateMonkeyNukeops", "SyndicateKoboldNukeops"] +- type: entity + parent: ReinforcementRadioSyndicate + id: ReinforcementRadioSyndicateSyndiCat + name: syndicat reinforcement radio + description: Calls in a faithfully trained cat with a microbomb to assist you. + components: + - type: GhostRole + name: ghost-role-information-SyndiCat-name + description: ghost-role-information-SyndiCat-description + rules: ghost-role-information-SyndiCat-rules + raffle: + settings: default + - type: GhostRoleMobSpawner + prototype: MobCatSyndy + - type: EmitSoundOnUse + sound: /Audio/Animals/cat_meow.ogg + - type: entity parent: ReinforcementRadioSyndicate id: ReinforcementRadioSyndicateCyborgAssault # Reinforcement radio exclusive to nukeops uplink From ba5af33ee42b82d7c653e7724ddf67b0a8c3cf1a Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 11:55:38 +0000 Subject: [PATCH 018/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f30481fee7..7235a60a0d 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Ko4erga - changes: - - message: Added craftable high and small wooden fences, bench. New stairs. - type: Add - id: 6172 - time: '2024-03-17T21:18:59.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26182 - author: potato1234_x changes: - message: Changed the puddle sprites so the smoothing is less jagged @@ -3851,3 +3844,10 @@ id: 6671 time: '2024-06-03T03:33:31.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28545 +- author: Vermidia + changes: + - message: SyndiCats now spawn from a radio like other reinforcements. + type: Tweak + id: 6672 + time: '2024-06-03T11:54:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28492 From eb026c9b5b582420ba8e97f1bdee02dc5193c736 Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Mon, 3 Jun 2024 07:05:14 -0500 Subject: [PATCH 019/243] Gives Medibots Random Lines(not a good title) (#28543) --- Resources/Locale/en-US/advertisements/other/medibot.ftl | 9 +++++++++ .../Catalog/VendingMachines/advertisements.yml | 6 ++++++ Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml | 2 ++ 3 files changed, 17 insertions(+) create mode 100644 Resources/Locale/en-US/advertisements/other/medibot.ftl diff --git a/Resources/Locale/en-US/advertisements/other/medibot.ftl b/Resources/Locale/en-US/advertisements/other/medibot.ftl new file mode 100644 index 0000000000..9e919ce49f --- /dev/null +++ b/Resources/Locale/en-US/advertisements/other/medibot.ftl @@ -0,0 +1,9 @@ +advertisement-medibot-1 = What kind of medbay is this? Everyone's dropping like dead flies. +advertisement-medibot-2 = I knew it, I should've been a plastic surgeon. +advertisement-medibot-3 = There's always a catch, and I'm the best there is. +advertisement-medibot-4 = An apple a day keeps me away. +advertisement-medibot-5 = I'm different! +advertisement-medibot-6 = Fuck you. +advertisement-medibot-7 = Why are we still here? Just to suffer? +advertisement-medibot-8 = I...I've never lost a patient before. Not today, I mean. +advertisement-medibot-9 = Lexorin in. diff --git a/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml b/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml index 6dbb60af6a..c0052dba07 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml @@ -255,3 +255,9 @@ values: prefix: advertisement-virodrobe- count: 3 + +- type: localizedDataset + id: MedibotAds + values: + prefix: advertisement-medibot- + count: 9 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index b2fefb67ae..12788e457f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -287,6 +287,8 @@ interactFailureString: petting-failure-medibot interactSuccessSound: path: /Audio/Ambience/Objects/periodic_beep.ogg + - type: Advertise + pack: MedibotAds - type: entity parent: MobSiliconBase From 64ff99231ad36ccdad2dfd1f1a07370643c7c3b9 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 12:06:20 +0000 Subject: [PATCH 020/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 7235a60a0d..784f6f0de0 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: potato1234_x - changes: - - message: Changed the puddle sprites so the smoothing is less jagged - type: Tweak - id: 6173 - time: '2024-03-17T23:15:45.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26171 - author: FungiFellow changes: - message: Changed Monkey Reinf Price to 6TC @@ -3851,3 +3844,10 @@ id: 6672 time: '2024-06-03T11:54:32.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28492 +- author: Cojoke-dot + changes: + - message: Medibots will now occasionally talk + type: Tweak + id: 6673 + time: '2024-06-03T12:05:14.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28543 From a0b49116be679f2bd13c009a70f6aa4ef58b8839 Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Mon, 3 Jun 2024 07:10:24 -0500 Subject: [PATCH 021/243] Adds Chat Triggers to the Gasp Emote (#28466) --- Resources/Prototypes/Voice/speech_emotes.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/Voice/speech_emotes.yml b/Resources/Prototypes/Voice/speech_emotes.yml index e571836976..e47d6382a1 100644 --- a/Resources/Prototypes/Voice/speech_emotes.yml +++ b/Resources/Prototypes/Voice/speech_emotes.yml @@ -1,4 +1,4 @@ -# vocal emotes +# vocal emotes - type: emote id: Scream name: chat-emote-name-scream @@ -345,6 +345,16 @@ components: - Respirator chatMessages: ["chat-emote-msg-gasp"] + chatTriggers: + - gasp + - gasp. + - gasp! + - gasps + - gasps. + - gasps! + - gasped + - gasped. + - gasped! - type: emote id: DefaultDeathgasp From f8f55dea1ee00e4e32d3852116d4c45e38694703 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 12:11:31 +0000 Subject: [PATCH 022/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 784f6f0de0..91acfe3296 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: FungiFellow - changes: - - message: Changed Monkey Reinf Price to 6TC - type: Tweak - id: 6174 - time: '2024-03-18T01:25:58.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26214 - author: Golinth changes: - message: Criminal record icons now show up below job icons @@ -3851,3 +3844,10 @@ id: 6673 time: '2024-06-03T12:05:14.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28543 +- author: Cojoke-dot + changes: + - message: The player can now use the gasping emote + type: Tweak + id: 6674 + time: '2024-06-03T12:10:25.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28466 From 50f8de08a4ce5f7647ea4cd15ddca4ac2790b55e Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:34:50 +0000 Subject: [PATCH 023/243] prevent nukie kidnapping (#28387) --- Content.Server/Antag/AntagSelectionSystem.cs | 21 +++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 47da1b6475..710bb8f3d7 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -182,20 +182,20 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem GameTicker.PlayerGameStatuses[x.UserId] == PlayerGameStatus.JoinedGame) .ToList(); - ChooseAntags((uid, component), players); + ChooseAntags((uid, component), players, midround: true); } /// /// Chooses antagonists from the given selection of players /// - public void ChooseAntags(Entity ent, IList pool) + public void ChooseAntags(Entity ent, IList pool, bool midround = false) { if (ent.Comp.SelectionsComplete) return; foreach (var def in ent.Comp.Definitions) { - ChooseAntags(ent, pool, def); + ChooseAntags(ent, pool, def, midround: midround); } ent.Comp.SelectionsComplete = true; @@ -204,17 +204,28 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem /// Chooses antagonists from the given selection of players for the given antag definition. /// - public void ChooseAntags(Entity ent, IList pool, AntagSelectionDefinition def) + /// Disable picking players for pre-spawn antags in the middle of a round + public void ChooseAntags(Entity ent, IList pool, AntagSelectionDefinition def, bool midround = false) { var playerPool = GetPlayerPool(ent, pool, def); var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def); // if there is both a spawner and players getting picked, let it fall back to a spawner. var noSpawner = def.SpawnerPrototype == null; + var picking = def.PickPlayer; + if (midround && ent.Comp.SelectionTime == AntagSelectionTime.PrePlayerSpawn) + { + // prevent antag selection from happening if the round is on-going, requiring a spawner if used midround. + // this is so rules like nukies, if added by an admin midround, dont make random living people nukies + Log.Info($"Antags for rule {ent:?} get picked pre-spawn so only spawners will be made."); + DebugTools.Assert(def.SpawnerPrototype != null, $"Rule {ent:?} had no spawner for pre-spawn rule added mid-round!"); + picking = false; + } + for (var i = 0; i < count; i++) { var session = (ICommonSession?) null; - if (def.PickPlayer) + if (picking) { if (!playerPool.TryPickAndTake(RobustRandom, out session) && noSpawner) { From 977bd5ad6dd1a6fe7a32721f296129cd3bb6bbce Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:39:25 +0000 Subject: [PATCH 024/243] make GameRule use entity category (#28294) --- .../Components/GameRuleComponent.cs | 3 +- Resources/Locale/en-US/entity-categories.ftl | 1 + Resources/Prototypes/Entities/categories.yml | 5 +++ .../Prototypes/GameRules/cargo_gifts.yml | 10 ------ Resources/Prototypes/GameRules/events.yml | 33 ------------------- Resources/Prototypes/GameRules/midround.yml | 1 - Resources/Prototypes/GameRules/roundstart.yml | 15 --------- Resources/Prototypes/GameRules/variation.yml | 8 ----- 8 files changed, 8 insertions(+), 68 deletions(-) diff --git a/Content.Server/GameTicking/Components/GameRuleComponent.cs b/Content.Server/GameTicking/Components/GameRuleComponent.cs index 1e6c3f0ab1..635452b3f3 100644 --- a/Content.Server/GameTicking/Components/GameRuleComponent.cs +++ b/Content.Server/GameTicking/Components/GameRuleComponent.cs @@ -1,4 +1,5 @@ using Content.Server.Destructible.Thresholds; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Server.GameTicking.Components; @@ -7,7 +8,7 @@ namespace Content.Server.GameTicking.Components; /// Component attached to all gamerule entities. /// Used to both track the entity as well as store basic data /// -[RegisterComponent] +[RegisterComponent, EntityCategory("GameRules")] public sealed partial class GameRuleComponent : Component { /// diff --git a/Resources/Locale/en-US/entity-categories.ftl b/Resources/Locale/en-US/entity-categories.ftl index 190fe5713a..457397845d 100644 --- a/Resources/Locale/en-US/entity-categories.ftl +++ b/Resources/Locale/en-US/entity-categories.ftl @@ -1 +1,2 @@ entity-category-name-actions = Actions +entity-category-name-game-rules = Game Rules diff --git a/Resources/Prototypes/Entities/categories.yml b/Resources/Prototypes/Entities/categories.yml index 2fb56818f9..0535a8a4c2 100644 --- a/Resources/Prototypes/Entities/categories.yml +++ b/Resources/Prototypes/Entities/categories.yml @@ -2,3 +2,8 @@ id: Actions name: entity-category-name-actions hideSpawnMenu: true + +- type: entityCategory + id: GameRules + name: entity-category-name-game-rules + hideSpawnMenu: true diff --git a/Resources/Prototypes/GameRules/cargo_gifts.yml b/Resources/Prototypes/GameRules/cargo_gifts.yml index c5a5251297..ffc2ed404b 100644 --- a/Resources/Prototypes/GameRules/cargo_gifts.yml +++ b/Resources/Prototypes/GameRules/cargo_gifts.yml @@ -17,7 +17,6 @@ - type: entity id: GiftsPizzaPartySmall parent: CargoGiftsBase - noSpawn: true components: - type: StationEvent weight: 5 @@ -35,7 +34,6 @@ - type: entity id: GiftsPizzaPartyLarge parent: CargoGiftsBase - noSpawn: true components: - type: StationEvent weight: 2 @@ -53,7 +51,6 @@ - type: entity id: GiftsEngineering parent: CargoGiftsBase - noSpawn: true components: - type: StationEvent weight: 5 @@ -74,7 +71,6 @@ - type: entity id: GiftsVendingRestock parent: CargoGiftsBase - noSpawn: true components: - type: StationEvent weight: 4 @@ -95,7 +91,6 @@ - type: entity id: GiftsJanitor parent: CargoGiftsBase - noSpawn: true components: - type: StationEvent weight: 6 @@ -113,7 +108,6 @@ - type: entity id: GiftsMedical parent: CargoGiftsBase - noSpawn: true components: - type: StationEvent weight: 8 @@ -133,7 +127,6 @@ - type: entity id: GiftsSpacingSupplies parent: CargoGiftsBase - noSpawn: true components: - type: StationEvent weight: 4 @@ -153,7 +146,6 @@ - type: entity id: GiftsFireProtection parent: CargoGiftsBase - noSpawn: true components: - type: StationEvent weight: 4 @@ -171,7 +163,6 @@ - type: entity id: GiftsSecurityGuns parent: CargoGiftsBase - noSpawn: true components: - type: StationEvent weight: 3 @@ -190,7 +181,6 @@ - type: entity id: GiftsSecurityRiot parent: CargoGiftsBase - noSpawn: true components: - type: StationEvent weight: 4 diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 9f6e6a8125..f421cf6818 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -2,7 +2,6 @@ id: BaseStationEvent parent: BaseGameRule abstract: true - noSpawn: true components: - type: GameRule delay: @@ -13,7 +12,6 @@ id: BaseStationEventShortDelay parent: BaseGameRule abstract: true - noSpawn: true components: - type: GameRule delay: @@ -24,7 +22,6 @@ id: BaseStationEventLongDelay parent: BaseGameRule abstract: true - noSpawn: true components: - type: GameRule delay: @@ -34,7 +31,6 @@ - type: entity id: AnomalySpawn parent: BaseStationEventShortDelay - noSpawn: true components: - type: StationEvent startAnnouncementColor: "#18abf5" @@ -47,7 +43,6 @@ - type: entity id: BluespaceArtifact parent: BaseStationEventShortDelay - noSpawn: true components: - type: GameRule delay: @@ -64,7 +59,6 @@ - type: entity id: BluespaceLocker parent: BaseGameRule - noSpawn: true components: - type: StationEvent weight: 2 @@ -76,7 +70,6 @@ - type: entity id: BreakerFlip parent: BaseGameRule - noSpawn: true components: - type: StationEvent weight: 7 @@ -87,7 +80,6 @@ - type: entity id: BureaucraticError parent: BaseGameRule - noSpawn: true components: - type: StationEvent startAnnouncement: station-event-bureaucratic-error-announcement @@ -99,7 +91,6 @@ - type: entity id: ClericalError parent: BaseGameRule - noSpawn: true components: - type: StationEvent startAnnouncement: station-event-clerical-error-announcement @@ -111,7 +102,6 @@ - type: entity parent: BaseGameRule id: ClosetSkeleton - noSpawn: true components: - type: StationEvent weight: 5 @@ -123,7 +113,6 @@ - type: entity parent: BaseGameRule id: DragonSpawn - noSpawn: true components: - type: StationEvent weight: 6.5 @@ -153,7 +142,6 @@ - type: entity parent: BaseGameRule id: NinjaSpawn - noSpawn: true components: - type: StationEvent weight: 6 @@ -166,7 +154,6 @@ - type: entity parent: BaseGameRule id: RevenantSpawn - noSpawn: true components: - type: StationEvent weight: 7.5 @@ -180,7 +167,6 @@ #- type: entity # id: FalseAlarm # parent: BaseGameRule -# noSpawn: true # components: # - type: StationEvent # weight: 15 @@ -190,7 +176,6 @@ - type: entity id: GasLeak parent: BaseStationEventShortDelay - noSpawn: true components: - type: StationEvent startAnnouncement: station-event-gas-leak-start-announcement @@ -203,7 +188,6 @@ - type: entity id: KudzuGrowth parent: BaseStationEventLongDelay - noSpawn: true components: - type: StationEvent earliestStart: 15 @@ -215,7 +199,6 @@ - type: entity id: MeteorSwarm parent: BaseStationEventLongDelay - noSpawn: true components: - type: StationEvent earliestStart: 30 @@ -233,7 +216,6 @@ - type: entity id: MouseMigration parent: BaseStationEventShortDelay - noSpawn: true components: - type: StationEvent startAnnouncement: station-event-vent-creatures-start-announcement @@ -259,7 +241,6 @@ - type: entity id: CockroachMigration parent: BaseStationEventShortDelay - noSpawn: true components: - type: StationEvent startAnnouncement: station-event-vent-creatures-start-announcement @@ -277,7 +258,6 @@ - type: entity id: PowerGridCheck parent: BaseStationEventShortDelay - noSpawn: true components: - type: StationEvent weight: 5 @@ -294,7 +274,6 @@ - type: entity id: RandomSentience parent: BaseGameRule - noSpawn: true components: - type: StationEvent weight: 6 @@ -307,7 +286,6 @@ - type: entity parent: BaseGameRule id: SolarFlare - noSpawn: true components: - type: StationEvent weight: 8 @@ -336,7 +314,6 @@ - type: entity id: VentClog parent: BaseStationEventLongDelay - noSpawn: true components: - type: StationEvent startAnnouncement: station-event-vent-clog-start-announcement @@ -351,7 +328,6 @@ - type: entity id: SlimesSpawn parent: BaseStationEventShortDelay - noSpawn: true components: - type: StationEvent startAnnouncement: station-event-vent-creatures-start-announcement @@ -373,7 +349,6 @@ - type: entity id: SpiderSpawn parent: BaseStationEventShortDelay - noSpawn: true components: - type: StationEvent startAnnouncement: station-event-vent-creatures-start-announcement @@ -391,7 +366,6 @@ - type: entity id: SpiderClownSpawn parent: BaseStationEventShortDelay - noSpawn: true components: - type: StationEvent startAnnouncement: station-event-vent-creatures-start-announcement @@ -409,7 +383,6 @@ - type: entity id: ZombieOutbreak parent: BaseGameRule - noSpawn: true components: - type: StationEvent earliestStart: 50 @@ -441,7 +414,6 @@ prototype: InitialInfected - type: entity - noSpawn: true parent: BaseNukeopsRule id: LoneOpsSpawn components: @@ -475,7 +447,6 @@ prototype: Nukeops - type: entity - noSpawn: true parent: BaseTraitorRule id: SleeperAgentsRule components: @@ -501,7 +472,6 @@ - type: entity id: MassHallucinations parent: BaseGameRule - noSpawn: true components: - type: StationEvent weight: 7 @@ -518,7 +488,6 @@ - type: entity id: ImmovableRodSpawn parent: BaseGameRule - noSpawn: true components: - type: StationEvent startAnnouncement: station-event-immovable-rod-start-announcement @@ -556,7 +525,6 @@ orGroup: rodProto - type: entity - noSpawn: true parent: BaseGameRule id: IonStorm components: @@ -569,7 +537,6 @@ - type: entity id: MimicVendorRule parent: BaseGameRule - noSpawn: true components: - type: StationEvent earliestStart: 0 diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index fe5af117e6..c190b0333a 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -18,7 +18,6 @@ threats: NinjaThreats - type: entity - noSpawn: true parent: BaseGameRule id: Thief components: diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index cf2c5b2f47..3493749457 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -1,12 +1,10 @@ - type: entity id: BaseGameRule abstract: true - noSpawn: true components: - type: GameRule - type: entity - noSpawn: true parent: BaseGameRule id: RespawnDeadRule components: @@ -17,7 +15,6 @@ deleteBody: false - type: entity - noSpawn: true parent: BaseGameRule id: SubGamemodesRule components: @@ -29,7 +26,6 @@ - type: entity id: DeathMatch31 parent: BaseGameRule - noSpawn: true components: - type: DeathMatchRule rewardSpawns: @@ -59,7 +55,6 @@ - type: entity id: InactivityTimeRestart parent: BaseGameRule - noSpawn: true components: - type: InactivityRule inactivityMaxTime: 600 @@ -68,7 +63,6 @@ - type: entity id: MaxTimeRestart parent: BaseGameRule - noSpawn: true components: - type: MaxTimeRestartRule roundMaxTime: 300 @@ -88,7 +82,6 @@ - type: AntagLoadProfileRule - type: entity - noSpawn: true parent: BaseNukeopsRule id: Nukeops components: @@ -166,7 +159,6 @@ agentName: traitor-round-end-agent-name - type: entity - noSpawn: true parent: BaseTraitorRule id: Traitor components: @@ -188,7 +180,6 @@ - type: entity id: Revolutionary parent: BaseGameRule - noSpawn: true components: - type: GameRule minPlayers: 15 @@ -213,21 +204,18 @@ - type: entity id: Sandbox parent: BaseGameRule - noSpawn: true components: - type: SandboxRule - type: entity id: Secret parent: BaseGameRule - noSpawn: true components: - type: SecretRule - type: entity id: Zombie parent: BaseGameRule - noSpawn: true components: - type: GameRule minPlayers: 20 @@ -260,14 +248,12 @@ - type: entity id: BasicStationEventScheduler parent: BaseGameRule - noSpawn: true components: - type: BasicStationEventScheduler - type: entity id: RampingStationEventScheduler parent: BaseGameRule - noSpawn: true components: - type: RampingStationEventScheduler @@ -275,7 +261,6 @@ - type: entity id: BasicRoundstartVariation parent: BaseGameRule - noSpawn: true components: - type: RoundstartStationVariationRule rules: diff --git a/Resources/Prototypes/GameRules/variation.yml b/Resources/Prototypes/GameRules/variation.yml index 2884d5f9d6..093a832d72 100644 --- a/Resources/Prototypes/GameRules/variation.yml +++ b/Resources/Prototypes/GameRules/variation.yml @@ -4,7 +4,6 @@ id: BaseVariationPass parent: BaseGameRule abstract: true - noSpawn: true components: - type: StationVariationPassRule @@ -13,14 +12,12 @@ - type: entity id: BasicPoweredLightVariationPass parent: BaseVariationPass - noSpawn: true components: - type: PoweredLightVariationPass - type: entity id: SolidWallRustingVariationPass parent: BaseVariationPass - noSpawn: true components: - type: WallReplaceVariationPass - type: EntityReplaceVariationPass @@ -32,7 +29,6 @@ - type: entity id: ReinforcedWallRustingVariationPass parent: BaseVariationPass - noSpawn: true components: - type: ReinforcedWallReplaceVariationPass - type: EntityReplaceVariationPass @@ -44,7 +40,6 @@ - type: entity id: BasicTrashVariationPass parent: BaseVariationPass - noSpawn: true components: - type: EntitySpawnVariationPass tilesPerEntityAverage: 35 @@ -105,7 +100,6 @@ - type: entity id: BasicPuddleMessVariationPass parent: BaseVariationPass - noSpawn: true components: - type: PuddleMessVariationPass randomPuddleSolutionFill: RandomFillTrashPuddle @@ -113,7 +107,6 @@ - type: entity id: BloodbathPuddleMessVariationPass parent: BaseVariationPass - noSpawn: true components: - type: PuddleMessVariationPass tilesPerSpillAverage: 150 @@ -123,7 +116,6 @@ - type: entity id: CutWireVariationPass parent: BaseVariationPass - noSpawn: true components: - type: CutWireVariationPass wireCutChance: 0.01 From e784f2cf3c638d9df1834751924916548389a1ab Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Mon, 3 Jun 2024 08:04:07 -0500 Subject: [PATCH 025/243] Make projectiles not hit crates unless clicked on (#28072) --- .../RequireProjectileTargetComponent.cs | 14 +++++ .../Systems/RequireProjectileTargetSystem.cs | 51 +++++++++++++++++++ .../Systems/MobStateSystem.Subscribers.cs | 17 ------- Resources/Prototypes/Entities/Mobs/base.yml | 4 +- .../Storage/Crates/base_structurecrates.yml | 1 + 5 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 Content.Shared/Damage/Components/RequireProjectileTargetComponent.cs create mode 100644 Content.Shared/Damage/Systems/RequireProjectileTargetSystem.cs diff --git a/Content.Shared/Damage/Components/RequireProjectileTargetComponent.cs b/Content.Shared/Damage/Components/RequireProjectileTargetComponent.cs new file mode 100644 index 0000000000..5bd8292daa --- /dev/null +++ b/Content.Shared/Damage/Components/RequireProjectileTargetComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Damage.Components; + +/// +/// Prevent the object from getting hit by projetiles unless you target the object. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(RequireProjectileTargetSystem))] +public sealed partial class RequireProjectileTargetComponent : Component +{ + [DataField, AutoNetworkedField] + public bool Active = true; +} diff --git a/Content.Shared/Damage/Systems/RequireProjectileTargetSystem.cs b/Content.Shared/Damage/Systems/RequireProjectileTargetSystem.cs new file mode 100644 index 0000000000..79b374a60f --- /dev/null +++ b/Content.Shared/Damage/Systems/RequireProjectileTargetSystem.cs @@ -0,0 +1,51 @@ +using Content.Shared.Projectiles; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Standing; +using Robust.Shared.Physics.Events; + +namespace Content.Shared.Damage.Components; + +public sealed class RequireProjectileTargetSystem : EntitySystem +{ + public override void Initialize() + { + SubscribeLocalEvent(PreventCollide); + SubscribeLocalEvent(StandingBulletHit); + SubscribeLocalEvent(LayingBulletPass); + } + + private void PreventCollide(Entity ent, ref PreventCollideEvent args) + { + if (args.Cancelled) + return; + + if (!ent.Comp.Active) + return; + + var other = args.OtherEntity; + if (HasComp(other) && + CompOrNull(other)?.Target != ent) + { + args.Cancelled = true; + } + } + + private void SetActive(Entity ent, bool value) + { + if (ent.Comp.Active == value) + return; + + ent.Comp.Active = value; + Dirty(ent); + } + + private void StandingBulletHit(Entity ent, ref StoodEvent args) + { + SetActive(ent, false); + } + + private void LayingBulletPass(Entity ent, ref DownedEvent args) + { + SetActive(ent, true); + } +} diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs index ee747554e1..08b351e61e 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs @@ -46,7 +46,6 @@ public partial class MobStateSystem SubscribeLocalEvent(OnSleepAttempt); SubscribeLocalEvent(OnCombatModeShouldHandInteract); SubscribeLocalEvent(OnAttemptPacifiedAttack); - SubscribeLocalEvent(OnPreventCollide); } private void OnStateExitSubscribers(EntityUid target, MobStateComponent component, MobState state) @@ -179,21 +178,5 @@ public partial class MobStateSystem args.Cancelled = true; } - private void OnPreventCollide(Entity ent, ref PreventCollideEvent args) - { - if (args.Cancelled) - return; - - if (IsAlive(ent, ent)) - return; - - var other = args.OtherEntity; - if (HasComp(other) && - CompOrNull(other)?.Target != ent.Owner) - { - args.Cancelled = true; - } - } - #endregion } diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 0a2b68d0a1..fae4711310 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -1,4 +1,4 @@ -# The progenitor. This should only container the most basic components possible. +# The progenitor. This should only container the most basic components possible. # Only put things on here if every mob *must* have it. This includes ghosts. - type: entity save: false @@ -43,6 +43,8 @@ - type: MovementSpeedModifier - type: Polymorphable - type: StatusIcon + - type: RequireProjectileTarget + active: False # Used for mobs that have health and can take damage. - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Storage/Crates/base_structurecrates.yml b/Resources/Prototypes/Entities/Structures/Storage/Crates/base_structurecrates.yml index 2d84541231..01c226cb0f 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Crates/base_structurecrates.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Crates/base_structurecrates.yml @@ -89,6 +89,7 @@ node: crategenericsteel containers: - entity_storage + - type: RequireProjectileTarget - type: entity parent: CrateGeneric From 33d963a149a0b0ad0a7dc29a0c3ef808c98f8bd2 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 13:05:14 +0000 Subject: [PATCH 026/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 91acfe3296..0587709ee3 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Golinth - changes: - - message: Criminal record icons now show up below job icons - type: Tweak - id: 6175 - time: '2024-03-18T01:37:00.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26203 - author: PJB3005 changes: - message: Fixed pressure damage calculations to what they were always supposed @@ -3851,3 +3844,13 @@ id: 6674 time: '2024-06-03T12:10:25.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28466 +- author: Cojoke-dot + changes: + - message: Bullets no longer hit crates unless directly clicked on while shooting. + type: Tweak + - message: Bullets now pass over mobs that are lying down unless directly clicked + on while shooting. + type: Tweak + id: 6675 + time: '2024-06-03T13:04:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28072 From 8d78b7442af48ff87607efbcdfb4692a00678daa Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Mon, 3 Jun 2024 13:23:52 +0000 Subject: [PATCH 027/243] make objectives use entityCategory (#28269) --- .../Components/ObjectiveComponent.cs | 10 +++-- Resources/Locale/en-US/entity-categories.ftl | 1 + Resources/Prototypes/Entities/categories.yml | 5 +++ .../Prototypes/Objectives/base_objectives.yml | 6 +-- Resources/Prototypes/Objectives/dragon.yml | 2 - Resources/Prototypes/Objectives/ninja.yml | 6 --- Resources/Prototypes/Objectives/thief.yml | 41 ------------------- Resources/Prototypes/Objectives/traitor.yml | 19 --------- 8 files changed, 15 insertions(+), 75 deletions(-) diff --git a/Content.Shared/Objectives/Components/ObjectiveComponent.cs b/Content.Shared/Objectives/Components/ObjectiveComponent.cs index 95fbc68561..36d3fa0bde 100644 --- a/Content.Shared/Objectives/Components/ObjectiveComponent.cs +++ b/Content.Shared/Objectives/Components/ObjectiveComponent.cs @@ -2,6 +2,7 @@ using Content.Shared.Mind; using Content.Shared.Objectives; using Content.Shared.Objectives.Systems; using Robust.Shared.Utility; +using Robust.Shared.Prototypes; namespace Content.Shared.Objectives.Components; @@ -9,32 +10,33 @@ namespace Content.Shared.Objectives.Components; /// Required component for an objective entity prototype. /// [RegisterComponent, Access(typeof(SharedObjectivesSystem))] +[EntityCategory("Objectives")] public sealed partial class ObjectiveComponent : Component { /// /// Difficulty rating used to avoid assigning too many difficult objectives. /// - [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] public float Difficulty; /// /// Organisation that issued this objective, used for grouping and as a header above common objectives. /// - [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] public string Issuer = string.Empty; /// /// Unique objectives can only have 1 per prototype id. /// Set this to false if you want multiple objectives of the same prototype. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public bool Unique = true; /// /// Icon of this objective to display in the character menu. /// Can be specified by an handler but is usually done in the prototype. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public SpriteSpecifier? Icon; } diff --git a/Resources/Locale/en-US/entity-categories.ftl b/Resources/Locale/en-US/entity-categories.ftl index 457397845d..6067830b7a 100644 --- a/Resources/Locale/en-US/entity-categories.ftl +++ b/Resources/Locale/en-US/entity-categories.ftl @@ -1,2 +1,3 @@ entity-category-name-actions = Actions entity-category-name-game-rules = Game Rules +entity-category-name-objectives = Objectives \ No newline at end of file diff --git a/Resources/Prototypes/Entities/categories.yml b/Resources/Prototypes/Entities/categories.yml index 0535a8a4c2..bc4dd104de 100644 --- a/Resources/Prototypes/Entities/categories.yml +++ b/Resources/Prototypes/Entities/categories.yml @@ -7,3 +7,8 @@ id: GameRules name: entity-category-name-game-rules hideSpawnMenu: true + +- type: entityCategory + id: Objectives + name: entity-category-name-objectives + hideSpawnMenu: true \ No newline at end of file diff --git a/Resources/Prototypes/Objectives/base_objectives.yml b/Resources/Prototypes/Objectives/base_objectives.yml index 2ab5149213..1fbd23dfce 100644 --- a/Resources/Prototypes/Objectives/base_objectives.yml +++ b/Resources/Prototypes/Objectives/base_objectives.yml @@ -1,6 +1,6 @@ # OBJECTIVE STYLE -# in comments anything that says final prototype means the objective that isnt abstract -# the final prototype must be noSpawn to avoid showing in f5 +# in comments anything that says final prototype means the objective that isnt abstract. +# you dont need noSpawn because Objectives category is automatically added, which has hideSpawnmenu # components are listed in this order: # 1. Objective # 2. requirement components @@ -8,7 +8,7 @@ # 4. the condition component # all objectives should inherit this at some point -# then have its difficulty etc fields set in the final objective prototypes +# then have its icon etc fields set in the final objective prototypes - type: entity abstract: true id: BaseObjective diff --git a/Resources/Prototypes/Objectives/dragon.yml b/Resources/Prototypes/Objectives/dragon.yml index 2cf7eb292f..10ca942cb3 100644 --- a/Resources/Prototypes/Objectives/dragon.yml +++ b/Resources/Prototypes/Objectives/dragon.yml @@ -13,7 +13,6 @@ - DragonRole - type: entity - noSpawn: true parent: BaseDragonObjective id: CarpRiftsObjective components: @@ -30,7 +29,6 @@ - type: CarpRiftsCondition - type: entity - noSpawn: true parent: [BaseDragonObjective, BaseSurviveObjective] id: DragonSurviveObjective name: Survive diff --git a/Resources/Prototypes/Objectives/ninja.yml b/Resources/Prototypes/Objectives/ninja.yml index bc17992e90..3c2fbcaca8 100644 --- a/Resources/Prototypes/Objectives/ninja.yml +++ b/Resources/Prototypes/Objectives/ninja.yml @@ -13,7 +13,6 @@ - NinjaRole - type: entity - noSpawn: true parent: BaseNinjaObjective id: DoorjackObjective components: @@ -29,7 +28,6 @@ - type: DoorjackCondition - type: entity - noSpawn: true parent: BaseNinjaObjective id: StealResearchObjective description: Your gloves can be used to hack a research server and steal its precious data. If science has been slacking you'll have to get to work. @@ -45,7 +43,6 @@ - type: StealResearchCondition - type: entity - noSpawn: true parent: [BaseNinjaObjective, BaseCodeObjective] id: SpiderChargeObjective description: This bomb can be detonated in a specific location. Note that the bomb will not work anywhere else! @@ -56,7 +53,6 @@ state: icon - type: entity - noSpawn: true parent: [BaseNinjaObjective, BaseSurviveObjective] id: NinjaSurviveObjective name: Survive @@ -68,7 +64,6 @@ state: icon - type: entity - noSpawn: true parent: [BaseNinjaObjective, BaseCodeObjective] id: TerrorObjective name: Call in a threat @@ -80,7 +75,6 @@ state: red_phone - type: entity - noSpawn: true parent: [BaseNinjaObjective, BaseCodeObjective] id: MassArrestObjective name: Set everyone to wanted diff --git a/Resources/Prototypes/Objectives/thief.yml b/Resources/Prototypes/Objectives/thief.yml index 1815485097..8b5307e9a0 100644 --- a/Resources/Prototypes/Objectives/thief.yml +++ b/Resources/Prototypes/Objectives/thief.yml @@ -55,7 +55,6 @@ # Collections - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: FigurineStealCollectionObjective components: @@ -67,7 +66,6 @@ difficulty: 0.25 - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: HeadCloakStealCollectionObjective components: @@ -79,7 +77,6 @@ difficulty: 1.5 - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: HeadBedsheetStealCollectionObjective components: @@ -91,7 +88,6 @@ difficulty: 1.0 - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: StampStealCollectionObjective components: @@ -103,7 +99,6 @@ difficulty: 1.0 - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: DoorRemoteStealCollectionObjective components: @@ -115,7 +110,6 @@ difficulty: 1.5 - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: TechnologyDiskStealCollectionObjective components: @@ -130,7 +124,6 @@ difficulty: 0.8 - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: IDCardsStealCollectionObjective components: @@ -145,7 +138,6 @@ - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: LAMPStealCollectionObjective components: @@ -163,7 +155,6 @@ # steal item - type: entity #Security subgroup - noSpawn: true parent: BaseThiefStealObjective id: ForensicScannerStealObjective components: @@ -175,7 +166,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: FlippoEngravedLighterStealObjective components: @@ -187,7 +177,6 @@ difficulty: 0.8 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: ClothingHeadHatWardenStealObjective components: @@ -197,7 +186,6 @@ difficulty: 1.2 - type: entity #Medical subgroup - noSpawn: true parent: BaseThiefStealObjective id: ClothingOuterHardsuitVoidParamedStealObjective components: @@ -209,7 +197,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: MedicalTechFabCircuitboardStealObjective components: @@ -221,7 +208,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: ClothingHeadsetAltMedicalStealObjective components: @@ -233,7 +219,6 @@ difficulty: 1 - type: entity #Engineering subgroup - noSpawn: true parent: BaseThiefStealObjective id: FireAxeStealObjective components: @@ -245,7 +230,6 @@ difficulty: 0.8 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: AmePartFlatpackStealObjective components: @@ -257,7 +241,6 @@ difficulty: 1 - type: entity #Cargo subgroup - noSpawn: true parent: BaseThiefStealObjective id: ExpeditionsCircuitboardStealObjective components: @@ -269,7 +252,6 @@ difficulty: 0.7 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: CargoShuttleCircuitboardStealObjective components: @@ -281,7 +263,6 @@ difficulty: 0.7 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: SalvageShuttleCircuitboardStealObjective components: @@ -293,7 +274,6 @@ difficulty: 0.7 - type: entity #Service subgroup - noSpawn: true parent: BaseThiefStealObjective id: ClothingEyesHudBeerStealObjective components: @@ -305,7 +285,6 @@ difficulty: 0.3 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: BibleStealObjective components: @@ -317,7 +296,6 @@ difficulty: 0.4 - type: entity #Other subgroup - noSpawn: true parent: BaseThiefStealObjective id: ClothingNeckGoldmedalStealObjective components: @@ -329,7 +307,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: ClothingNeckClownmedalStealObjective components: @@ -343,7 +320,6 @@ # Structures - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: NuclearBombStealObjective components: @@ -355,7 +331,6 @@ difficulty: 2.5 #Good luck - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: FaxMachineCaptainStealObjective components: @@ -367,7 +342,6 @@ difficulty: 2 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: ChemDispenserStealObjective components: @@ -379,7 +353,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: XenoArtifactStealObjective components: @@ -391,7 +364,6 @@ difficulty: 0.5 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: FreezerHeaterStealObjective components: @@ -403,7 +375,6 @@ difficulty: 0.5 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: TegStealObjective components: @@ -415,7 +386,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: BoozeDispenserStealObjective components: @@ -427,7 +397,6 @@ difficulty: 0.5 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: AltarNanotrasenStealObjective components: @@ -439,7 +408,6 @@ difficulty: 0.5 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: PlantRDStealObjective components: @@ -453,7 +421,6 @@ # Animal - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: IanStealObjective components: @@ -465,7 +432,6 @@ difficulty: 2.5 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: BingusStealObjective components: @@ -475,7 +441,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: McGriffStealObjective components: @@ -487,7 +452,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: WalterStealObjective components: @@ -499,7 +463,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: MortyStealObjective components: @@ -509,7 +472,6 @@ difficulty: 0.5 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: RenaultStealObjective components: @@ -521,7 +483,6 @@ difficulty: 2 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: ShivaStealObjective components: @@ -533,7 +494,6 @@ difficulty: 2 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: TropicoStealObjective components: @@ -547,7 +507,6 @@ # Escape - type: entity - noSpawn: true parent: [BaseThiefObjective, BaseLivingObjective] id: EscapeThiefShuttleObjective name: Escape to centcom alive and unrestrained. diff --git a/Resources/Prototypes/Objectives/traitor.yml b/Resources/Prototypes/Objectives/traitor.yml index 14397535d4..ad5f56a443 100644 --- a/Resources/Prototypes/Objectives/traitor.yml +++ b/Resources/Prototypes/Objectives/traitor.yml @@ -36,7 +36,6 @@ # state - type: entity - noSpawn: true parent: [BaseTraitorObjective, BaseLivingObjective] id: EscapeShuttleObjective name: Escape to centcom alive and unrestrained. @@ -50,7 +49,6 @@ - type: EscapeShuttleCondition - type: entity - noSpawn: true parent: BaseTraitorObjective id: DieObjective name: Die a glorious death @@ -69,7 +67,6 @@ - type: DieCondition #- type: entity -# noSpawn: true # parent: [BaseTraitorObjective, BaseLivingObjective] # id: HijackShuttleObjective # name: Hijack emergency shuttle @@ -85,7 +82,6 @@ # kill - type: entity - noSpawn: true parent: [BaseTraitorObjective, BaseKillObjective] id: KillRandomPersonObjective description: Do it however you like, just make sure they don't make it to centcom. @@ -98,7 +94,6 @@ - type: PickRandomPerson - type: entity - noSpawn: true parent: [BaseTraitorObjective, BaseKillObjective] id: KillRandomHeadObjective description: We need this head gone and you probably know why. Good luck, agent. @@ -119,7 +114,6 @@ # social - type: entity - noSpawn: true parent: [BaseTraitorSocialObjective, BaseKeepAliveObjective] id: RandomTraitorAliveObjective description: Identify yourself at your own risk. We just need them alive. @@ -131,7 +125,6 @@ - type: RandomTraitorAlive - type: entity - noSpawn: true parent: [BaseTraitorSocialObjective, BaseHelpProgressObjective] id: RandomTraitorProgressObjective description: Identify yourself at your own risk. We just need them to succeed. @@ -157,7 +150,6 @@ owner: job-name-cmo - type: entity - noSpawn: true parent: BaseCMOStealObjective id: CMOHyposprayStealObjective components: @@ -165,7 +157,6 @@ stealGroup: Hypospray - type: entity - noSpawn: true parent: BaseCMOStealObjective id: CMOCrewMonitorStealObjective components: @@ -185,7 +176,6 @@ owner: job-name-rd - type: entity - noSpawn: true parent: BaseRDStealObjective id: RDHardsuitStealObjective components: @@ -196,7 +186,6 @@ difficulty: 3 - type: entity - noSpawn: true parent: BaseRDStealObjective id: HandTeleporterStealObjective components: @@ -206,7 +195,6 @@ ## hos - type: entity - noSpawn: true parent: BaseTraitorStealObjective id: SecretDocumentsStealObjective components: @@ -222,7 +210,6 @@ ## ce - type: entity - noSpawn: true parent: BaseTraitorStealObjective id: MagbootsStealObjective components: @@ -235,7 +222,6 @@ ## qm - type: entity - noSpawn: true parent: BaseTraitorStealObjective id: ClipboardStealObjective components: @@ -248,7 +234,6 @@ ## hop - type: entity - noSpawn: true parent: BaseTraitorStealObjective id: CorgiMeatStealObjective components: @@ -274,7 +259,6 @@ job: Captain - type: entity - noSpawn: true parent: BaseCaptainObjective id: CaptainIDStealObjective components: @@ -282,7 +266,6 @@ stealGroup: CaptainIDCard - type: entity - noSpawn: true parent: BaseCaptainObjective id: CaptainJetpackStealObjective components: @@ -290,7 +273,6 @@ stealGroup: JetpackCaptainFilled - type: entity - noSpawn: true parent: BaseCaptainObjective id: CaptainGunStealObjective components: @@ -299,7 +281,6 @@ owner: job-name-captain - type: entity - noSpawn: true parent: BaseCaptainObjective id: NukeDiskStealObjective components: From def04cdaf52ee4e39fe269d3a92d94d02e1e3dc0 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Mon, 3 Jun 2024 06:30:47 -0700 Subject: [PATCH 028/243] Add more info to GettingAttackedAttemptEvent (#28548) --- Content.Shared/ActionBlocker/ActionBlockerSystem.cs | 6 +----- .../Interaction/Events/GettingAttackedAttemptEvent.cs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs index d2883b5ef5..f1c77fda43 100644 --- a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs +++ b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs @@ -1,13 +1,9 @@ -using Content.Shared.Bed.Sleep; using Content.Shared.Body.Events; -using Content.Shared.DragDrop; using Content.Shared.Emoting; using Content.Shared.Hands; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Item; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; using Content.Shared.Speech; @@ -194,7 +190,7 @@ namespace Content.Shared.ActionBlocker if (target == null) return true; - var tev = new GettingAttackedAttemptEvent(); + var tev = new GettingAttackedAttemptEvent(uid, weapon, disarm); RaiseLocalEvent(target.Value, ref tev); return !tev.Cancelled; } diff --git a/Content.Shared/Interaction/Events/GettingAttackedAttemptEvent.cs b/Content.Shared/Interaction/Events/GettingAttackedAttemptEvent.cs index ed7379fd72..d37c810e3f 100644 --- a/Content.Shared/Interaction/Events/GettingAttackedAttemptEvent.cs +++ b/Content.Shared/Interaction/Events/GettingAttackedAttemptEvent.cs @@ -4,4 +4,4 @@ namespace Content.Shared.Interaction.Events; /// Raised directed on the target entity when being attacked. /// [ByRefEvent] -public record struct GettingAttackedAttemptEvent(bool Cancelled); +public record struct GettingAttackedAttemptEvent(EntityUid Attacker, EntityUid? Weapon, bool Disarm, bool Cancelled = false); From 0b4388d5d423669f7a3d431ea4f983f3ef50a06d Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Tue, 4 Jun 2024 01:36:07 +1200 Subject: [PATCH 029/243] Try fix KeyNotFoundException in KillTrackingSystem (#28553) --- .../KillTracking/KillTrackingSystem.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Content.Server/KillTracking/KillTrackingSystem.cs b/Content.Server/KillTracking/KillTrackingSystem.cs index 63627fd1b9..ba27ea5d9e 100644 --- a/Content.Server/KillTracking/KillTrackingSystem.cs +++ b/Content.Server/KillTracking/KillTrackingSystem.cs @@ -2,6 +2,7 @@ using Content.Server.NPC.HTN; using Content.Shared.Damage; using Content.Shared.FixedPoint; using Content.Shared.Mobs; +using Content.Shared.Mobs.Systems; using Robust.Shared.Player; namespace Content.Server.KillTracking; @@ -14,7 +15,8 @@ public sealed class KillTrackingSystem : EntitySystem /// public override void Initialize() { - SubscribeLocalEvent(OnDamageChanged); + // Add damage to LifetimeDamage before MobStateChangedEvent gets raised + SubscribeLocalEvent(OnDamageChanged, before: [ typeof(MobThresholdSystem) ]); SubscribeLocalEvent(OnMobStateChanged); } @@ -50,7 +52,7 @@ public sealed class KillTrackingSystem : EntitySystem var largestSource = GetLargestSource(component.LifetimeDamage); largestSource ??= killImpulse; - KillSource? killSource; + KillSource killSource; KillSource? assistSource = null; if (killImpulse is KillEnvironmentSource) @@ -69,13 +71,13 @@ public sealed class KillTrackingSystem : EntitySystem killSource = killImpulse; // no assist is given to environmental kills - if (largestSource is not KillEnvironmentSource) + if (largestSource is not KillEnvironmentSource + && component.LifetimeDamage.TryGetValue(largestSource, out var largestDamage)) { - // you have to do at least 50% of largest source's damage to get the assist. - if (component.LifetimeDamage[largestSource] >= component.LifetimeDamage[killSource] / 2) - { + var killDamage = component.LifetimeDamage.GetValueOrDefault(killSource); + // you have to do at least twice as much damage as the killing source to get the assist. + if (largestDamage >= killDamage / 2) assistSource = largestSource; - } } } From 87ffbab4614ff616447d7a89d161a699d5750013 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:37:30 +0200 Subject: [PATCH 030/243] Remove locale related changelog (#28547) --- Resources/Changelog/Changelog.yml | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 0587709ee3..cab645fa8d 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -3787,25 +3787,18 @@ id: 6666 time: '2024-06-02T17:30:27.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28518 -- author: Voomra - changes: - - message: localize Pray UI - type: Fix - id: 6667 - time: '2024-06-02T23:51:19.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/28535 - author: Whisper changes: - message: Renamed the player-obtainable "admin cloak" to "weh cloak". type: Tweak - id: 6668 + id: 6667 time: '2024-06-03T02:18:49.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28540 - author: Vermidia changes: - message: Glass shards are no longer weldable with an unlit welder. type: Fix - id: 6669 + id: 6668 time: '2024-06-03T03:28:53.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/27959 - author: AJCM-git @@ -3813,35 +3806,35 @@ - message: Biomass reclaimers no longer act as a void to any unfortunate belongings a corpse may be wearing type: Tweak - id: 6670 + id: 6669 time: '2024-06-03T03:30:00.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28544 - author: Laneron changes: - message: Jetpack UI window now has correctly displayed header type: Fix - id: 6671 + id: 6670 time: '2024-06-03T03:33:31.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28545 - author: Vermidia changes: - message: SyndiCats now spawn from a radio like other reinforcements. type: Tweak - id: 6672 + id: 6671 time: '2024-06-03T11:54:32.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28492 - author: Cojoke-dot changes: - message: Medibots will now occasionally talk type: Tweak - id: 6673 + id: 6672 time: '2024-06-03T12:05:14.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28543 - author: Cojoke-dot changes: - message: The player can now use the gasping emote type: Tweak - id: 6674 + id: 6673 time: '2024-06-03T12:10:25.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28466 - author: Cojoke-dot @@ -3851,6 +3844,6 @@ - message: Bullets now pass over mobs that are lying down unless directly clicked on while shooting. type: Tweak - id: 6675 + id: 6674 time: '2024-06-03T13:04:07.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28072 From 8c105810109a076c30518f0b122b58fcac354bd7 Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:12:21 -0400 Subject: [PATCH 031/243] Cleans up StatusIconSystem and fixing some bugs (#28270) --- .../UI/AgentIDCardBoundUserInterface.cs | 4 +- .../Access/UI/AgentIDCardWindow.xaml.cs | 8 +-- Content.Client/Antag/AntagStatusIconSystem.cs | 55 ------------------- .../Commands/ShowHealthBarsCommand.cs | 1 + Content.Client/LateJoin/LateJoinGui.cs | 2 +- .../Lobby/UI/HumanoidProfileEditor.xaml.cs | 2 +- .../Overlays/EntityHealthBarOverlay.cs | 26 +++++---- .../Overlays/ShowCriminalRecordIconsSystem.cs | 4 +- .../Overlays/ShowHealthBarsSystem.cs | 14 ++++- .../Overlays/ShowHealthIconsSystem.cs | 3 +- .../Overlays/ShowHungerIconsSystem.cs | 2 +- Content.Client/Overlays/ShowJobIconsSystem.cs | 2 +- .../Overlays/ShowMindShieldIconsSystem.cs | 4 +- .../Overlays/ShowSyndicateIconsSystem.cs | 3 +- .../Overlays/ShowThirstIconsSystem.cs | 2 +- .../Revolutionary/RevolutionarySystem.cs | 37 +++++-------- .../SSDIndicator/SSDIndicatorSystem.cs | 3 +- .../StatusIcon/StatusIconOverlay.cs | 4 +- Content.Client/StatusIcon/StatusIconSystem.cs | 36 +++++++++++- Content.Client/Stealth/StealthSystem.cs | 3 +- Content.Client/Zombies/ZombieSystem.cs | 50 ++++++++--------- .../Access/Components/AgentIDCardComponent.cs | 6 +- .../Access/Systems/AgentIDCardSystem.cs | 8 +-- .../Access/Systems/IdCardConsoleSystem.cs | 2 +- .../Access/Systems/PresetIdCardSystem.cs | 4 +- .../Revolutionary/RevolutionarySystem.cs | 5 ++ .../Station/Systems/StationSpawningSystem.cs | 4 +- .../Access/SharedAgentIDCardSystem.cs | 10 ++-- .../Antag/IAntagStatusIconComponent.cs | 12 ---- .../Antag/ShowAntagIconsComponent.cs | 9 +++ .../Overlays/ShowHealthBarsComponent.cs | 5 ++ .../Components/HeadRevolutionaryComponent.cs | 5 +- .../Components/RevolutionaryComponent.cs | 5 +- .../Components/ShowRevIconsComponent.cs | 11 ---- .../SharedRevolutionarySystem.cs | 22 +++----- Content.Shared/Roles/JobPrototype.cs | 4 +- .../SSDIndicator/SSDIndicatorComponent.cs | 5 +- .../Components/StatusIconComponent.cs | 14 +---- .../StatusIcon/StatusIconPrototype.cs | 42 +++++++++++--- .../Zombies/InitialInfectedComponent.cs | 8 +-- .../Zombies/ShowZombieIconsComponent.cs | 12 ---- Content.Shared/Zombies/ZombieComponent.cs | 5 +- .../Entities/Mobs/Player/admin_ghost.yml | 3 +- .../{ => StatusIcon}/StatusEffects/health.yml | 0 .../{ => StatusIcon}/StatusEffects/hunger.yml | 0 .../{ => StatusIcon}/StatusEffects/ssd.yml | 0 Resources/Prototypes/StatusIcon/antag.yml | 17 ++++++ .../{StatusEffects => StatusIcon}/job.yml | 0 .../security.yml | 0 49 files changed, 224 insertions(+), 259 deletions(-) delete mode 100644 Content.Client/Antag/AntagStatusIconSystem.cs create mode 100644 Content.Server/Revolutionary/RevolutionarySystem.cs delete mode 100644 Content.Shared/Antag/IAntagStatusIconComponent.cs create mode 100644 Content.Shared/Antag/ShowAntagIconsComponent.cs delete mode 100644 Content.Shared/Revolutionary/Components/ShowRevIconsComponent.cs delete mode 100644 Content.Shared/Zombies/ShowZombieIconsComponent.cs rename Resources/Prototypes/{ => StatusIcon}/StatusEffects/health.yml (100%) rename Resources/Prototypes/{ => StatusIcon}/StatusEffects/hunger.yml (100%) rename Resources/Prototypes/{ => StatusIcon}/StatusEffects/ssd.yml (100%) rename Resources/Prototypes/{StatusEffects => StatusIcon}/job.yml (100%) rename Resources/Prototypes/{StatusEffects => StatusIcon}/security.yml (100%) diff --git a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs index c3fac8cb92..761f52988a 100644 --- a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs +++ b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs @@ -1,5 +1,7 @@ using Content.Shared.Access.Systems; +using Content.Shared.StatusIcon; using Robust.Client.GameObjects; +using Robust.Shared.Prototypes; namespace Content.Client.Access.UI { @@ -40,7 +42,7 @@ namespace Content.Client.Access.UI SendMessage(new AgentIDCardJobChangedMessage(newJob)); } - public void OnJobIconChanged(string newJobIconId) + public void OnJobIconChanged(ProtoId newJobIconId) { SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId)); } diff --git a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs index 9a38c0c485..6d0b2a184f 100644 --- a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs +++ b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs @@ -38,7 +38,7 @@ namespace Content.Client.Access.UI JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text); } - public void SetAllowedIcons(HashSet icons, string currentJobIconId) + public void SetAllowedIcons(HashSet> icons, string currentJobIconId) { IconGrid.DisposeAllChildren(); @@ -46,10 +46,8 @@ namespace Content.Client.Access.UI var i = 0; foreach (var jobIconId in icons) { - if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon)) - { + if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon)) continue; - } String styleBase = StyleBase.ButtonOpenBoth; var modulo = i % JobIconColumnCount; @@ -77,7 +75,7 @@ namespace Content.Client.Access.UI }; jobIconButton.AddChild(jobIconTexture); - jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIcon.ID); + jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIconId); IconGrid.AddChild(jobIconButton); if (jobIconId.Equals(currentJobIconId)) diff --git a/Content.Client/Antag/AntagStatusIconSystem.cs b/Content.Client/Antag/AntagStatusIconSystem.cs deleted file mode 100644 index 804ae21ad4..0000000000 --- a/Content.Client/Antag/AntagStatusIconSystem.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Content.Shared.Antag; -using Content.Shared.Revolutionary.Components; -using Content.Shared.StatusIcon; -using Content.Shared.StatusIcon.Components; -using Content.Shared.Zombies; -using Robust.Client.Player; -using Robust.Shared.Prototypes; - -namespace Content.Client.Antag; - -/// -/// Used for assigning specified icons for antags. -/// -public sealed class AntagStatusIconSystem : SharedStatusIconSystem -{ - [Dependency] private readonly IPrototypeManager _prototype = default!; - [Dependency] private readonly IPlayerManager _player = default!; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(GetRevIcon); - SubscribeLocalEvent(GetIcon); - SubscribeLocalEvent(GetIcon); - SubscribeLocalEvent(GetIcon); - } - - /// - /// Adds a Status Icon on an entity if the player is supposed to see it. - /// - private void GetIcon(EntityUid uid, T comp, ref GetStatusIconsEvent ev) where T: IAntagStatusIconComponent - { - var ent = _player.LocalSession?.AttachedEntity; - - var canEv = new CanDisplayStatusIconsEvent(ent); - RaiseLocalEvent(uid, ref canEv); - - if (!canEv.Cancelled) - ev.StatusIcons.Add(_prototype.Index(comp.StatusIcon)); - } - - - /// - /// Adds the Rev Icon on an entity if the player is supposed to see it. This additional function is needed to deal - /// with a special case where if someone is a head rev we only want to display the headrev icon. - /// - private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatusIconsEvent ev) - { - if (HasComp(uid)) - return; - - GetIcon(uid, comp, ref ev); - - } -} diff --git a/Content.Client/Commands/ShowHealthBarsCommand.cs b/Content.Client/Commands/ShowHealthBarsCommand.cs index bd3e21718f..ef918313a0 100644 --- a/Content.Client/Commands/ShowHealthBarsCommand.cs +++ b/Content.Client/Commands/ShowHealthBarsCommand.cs @@ -35,6 +35,7 @@ public sealed class ShowHealthBarsCommand : LocalizedCommands var showHealthBarsComponent = new ShowHealthBarsComponent { DamageContainers = args.ToList(), + HealthStatusIcon = "", NetSyncEnabled = false }; diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs index ba9351d674..252aa9aafa 100644 --- a/Content.Client/LateJoin/LateJoinGui.cs +++ b/Content.Client/LateJoin/LateJoinGui.cs @@ -244,7 +244,7 @@ namespace Content.Client.LateJoin VerticalAlignment = VAlignment.Center }; - var jobIcon = _prototypeManager.Index(prototype.Icon); + var jobIcon = _prototypeManager.Index(prototype.Icon); icon.Texture = _sprites.Frame0(jobIcon.Icon); jobSelector.AddChild(icon); diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index eb182c83ee..53c332c185 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -806,7 +806,7 @@ namespace Content.Client.Lobby.UI TextureScale = new Vector2(2, 2), VerticalAlignment = VAlignment.Center }; - var jobIcon = _prototypeManager.Index(job.Icon); + var jobIcon = _prototypeManager.Index(job.Icon); icon.Texture = jobIcon.Icon.Frame0(); selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon); diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs index 758bb562f9..55978d98f7 100644 --- a/Content.Client/Overlays/EntityHealthBarOverlay.cs +++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs @@ -1,14 +1,17 @@ using System.Numerics; +using Content.Client.StatusIcon; using Content.Client.UserInterface.Systems; using Content.Shared.Damage; using Content.Shared.FixedPoint; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.StatusIcon; using Content.Shared.StatusIcon.Components; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.Enums; +using Robust.Shared.Prototypes; using static Robust.Shared.Maths.Color; namespace Content.Client.Overlays; @@ -19,19 +22,27 @@ namespace Content.Client.Overlays; public sealed class EntityHealthBarOverlay : Overlay { private readonly IEntityManager _entManager; + private readonly IPrototypeManager _prototype; + private readonly SharedTransformSystem _transform; private readonly MobStateSystem _mobStateSystem; private readonly MobThresholdSystem _mobThresholdSystem; + private readonly StatusIconSystem _statusIconSystem; private readonly ProgressColorSystem _progressColor; + + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; public HashSet DamageContainers = new(); + public ProtoId StatusIcon; - public EntityHealthBarOverlay(IEntityManager entManager) + public EntityHealthBarOverlay(IEntityManager entManager, IPrototypeManager prototype) { _entManager = entManager; + _prototype = prototype; _transform = _entManager.System(); _mobStateSystem = _entManager.System(); _mobThresholdSystem = _entManager.System(); + _statusIconSystem = _entManager.System(); _progressColor = _entManager.System(); } @@ -44,6 +55,7 @@ public sealed class EntityHealthBarOverlay : Overlay const float scale = 1f; var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale)); var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation); + _prototype.TryIndex(StatusIcon, out var statusIcon); var query = _entManager.AllEntityQueryEnumerator(); while (query.MoveNext(out var uid, @@ -52,31 +64,23 @@ public sealed class EntityHealthBarOverlay : Overlay out var damageableComponent, out var spriteComponent)) { - if (_entManager.TryGetComponent(uid, out var metaDataComponent) && - metaDataComponent.Flags.HasFlag(MetaDataFlags.InContainer)) - { + if (statusIcon != null && !_statusIconSystem.IsVisible((uid, _entManager.GetComponent(uid)), statusIcon)) continue; - } + // We want the stealth user to still be able to see his health bar himself if (!xformQuery.TryGetComponent(uid, out var xform) || xform.MapID != args.MapId) - { continue; - } if (damageableComponent.DamageContainerID == null || !DamageContainers.Contains(damageableComponent.DamageContainerID)) - { continue; - } // we use the status icon component bounds if specified otherwise use sprite var bounds = _entManager.GetComponentOrNull(uid)?.Bounds ?? spriteComponent.Bounds; var worldPos = _transform.GetWorldPosition(xform, xformQuery); if (!bounds.Translated(worldPos).Intersects(args.WorldAABB)) - { continue; - } // we are all progressing towards death every day if (CalcProgress(uid, mobStateComponent, damageableComponent, mobThresholdsComponent) is not { } deathProgress) diff --git a/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs b/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs index 8f23cd510c..c353b17272 100644 --- a/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs +++ b/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs @@ -19,10 +19,10 @@ public sealed class ShowCriminalRecordIconsSystem : EquipmentHudSystem(component.StatusIcon.Id, out var iconPrototype)) + if (_prototype.TryIndex(component.StatusIcon, out var iconPrototype)) ev.StatusIcons.Add(iconPrototype); } } diff --git a/Content.Client/Overlays/ShowHealthBarsSystem.cs b/Content.Client/Overlays/ShowHealthBarsSystem.cs index 170f552cf3..1eb712a898 100644 --- a/Content.Client/Overlays/ShowHealthBarsSystem.cs +++ b/Content.Client/Overlays/ShowHealthBarsSystem.cs @@ -2,6 +2,8 @@ using Content.Shared.Inventory.Events; using Content.Shared.Overlays; using Robust.Client.Graphics; using System.Linq; +using Robust.Client.Player; +using Robust.Shared.Prototypes; namespace Content.Client.Overlays; @@ -11,6 +13,7 @@ namespace Content.Client.Overlays; public sealed class ShowHealthBarsSystem : EquipmentHudSystem { [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; private EntityHealthBarOverlay _overlay = default!; @@ -18,16 +21,21 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem component) { base.UpdateInternal(component); - foreach (var damageContainerId in component.Components.SelectMany(x => x.DamageContainers)) + foreach (var comp in component.Components) { - _overlay.DamageContainers.Add(damageContainerId); + foreach (var damageContainerId in comp.DamageContainers) + { + _overlay.DamageContainers.Add(damageContainerId); + } + + _overlay.StatusIcon = comp.HealthStatusIcon; } if (!_overlayMan.HasOverlay()) diff --git a/Content.Client/Overlays/ShowHealthIconsSystem.cs b/Content.Client/Overlays/ShowHealthIconsSystem.cs index a546cf4d82..d8af91482b 100644 --- a/Content.Client/Overlays/ShowHealthIconsSystem.cs +++ b/Content.Client/Overlays/ShowHealthIconsSystem.cs @@ -24,7 +24,6 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem(OnGetStatusIconsEvent); - } protected override void UpdateInternal(RefreshEquipmentHudEvent component) @@ -46,7 +45,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem entity, ref GetStatusIconsEvent args) { - if (!IsActive || args.InContainer) + if (!IsActive) return; var healthIcons = DecideHealthIcons(entity); diff --git a/Content.Client/Overlays/ShowHungerIconsSystem.cs b/Content.Client/Overlays/ShowHungerIconsSystem.cs index b1c0f3a1a0..6b0d575a81 100644 --- a/Content.Client/Overlays/ShowHungerIconsSystem.cs +++ b/Content.Client/Overlays/ShowHungerIconsSystem.cs @@ -18,7 +18,7 @@ public sealed class ShowHungerIconsSystem : EquipmentHudSystem(component.MindShieldStatusIcon.Id, out var iconPrototype)) + if (_prototype.TryIndex(component.MindShieldStatusIcon, out var iconPrototype)) ev.StatusIcons.Add(iconPrototype); } } diff --git a/Content.Client/Overlays/ShowSyndicateIconsSystem.cs b/Content.Client/Overlays/ShowSyndicateIconsSystem.cs index 660ef198e1..782178a29d 100644 --- a/Content.Client/Overlays/ShowSyndicateIconsSystem.cs +++ b/Content.Client/Overlays/ShowSyndicateIconsSystem.cs @@ -19,11 +19,10 @@ public sealed class ShowSyndicateIconsSystem : EquipmentHudSystem(component.SyndStatusIcon, out var iconPrototype)) ev.StatusIcons.Add(iconPrototype); } } - diff --git a/Content.Client/Overlays/ShowThirstIconsSystem.cs b/Content.Client/Overlays/ShowThirstIconsSystem.cs index b08aa4340b..44be1f7a67 100644 --- a/Content.Client/Overlays/ShowThirstIconsSystem.cs +++ b/Content.Client/Overlays/ShowThirstIconsSystem.cs @@ -18,7 +18,7 @@ public sealed class ShowThirstIconsSystem : EquipmentHudSystem /// Used for the client to get status icons from other revs. /// -public sealed class RevolutionarySystem : EntitySystem +public sealed class RevolutionarySystem : SharedRevolutionarySystem { + [Dependency] private readonly IPrototypeManager _prototype = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnCanShowRevIcon); - SubscribeLocalEvent(OnCanShowRevIcon); + SubscribeLocalEvent(GetRevIcon); + SubscribeLocalEvent(GetHeadRevIcon); } - /// - /// Determine whether a client should display the rev icon. - /// - private void OnCanShowRevIcon(EntityUid uid, T comp, ref CanDisplayStatusIconsEvent args) where T : IAntagStatusIconComponent + private void GetRevIcon(Entity ent, ref GetStatusIconsEvent args) { - args.Cancelled = !CanDisplayIcon(args.User, comp.IconVisibleToGhost); + if (HasComp(ent)) + return; + + if (_prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype)) + args.StatusIcons.Add(iconPrototype); } - /// - /// The criteria that determine whether a client should see Rev/Head rev icons. - /// - private bool CanDisplayIcon(EntityUid? uid, bool visibleToGhost) + private void GetHeadRevIcon(Entity ent, ref GetStatusIconsEvent args) { - if (HasComp(uid) || HasComp(uid)) - return true; - - if (visibleToGhost && HasComp(uid)) - return true; - - return HasComp(uid); + if (_prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype)) + args.StatusIcons.Add(iconPrototype); } - } diff --git a/Content.Client/SSDIndicator/SSDIndicatorSystem.cs b/Content.Client/SSDIndicator/SSDIndicatorSystem.cs index 587450a2f6..e731195317 100644 --- a/Content.Client/SSDIndicator/SSDIndicatorSystem.cs +++ b/Content.Client/SSDIndicator/SSDIndicatorSystem.cs @@ -30,13 +30,12 @@ public sealed class SSDIndicatorSystem : EntitySystem { if (component.IsSSD && _cfg.GetCVar(CCVars.ICShowSSDIndicator) && - !args.InContainer && !_mobState.IsDead(uid) && !HasComp(uid) && TryComp(uid, out var mindContainer) && mindContainer.ShowExamineInfo) { - args.StatusIcons.Add(_prototype.Index(component.Icon)); + args.StatusIcons.Add(_prototype.Index(component.Icon)); } } } diff --git a/Content.Client/StatusIcon/StatusIconOverlay.cs b/Content.Client/StatusIcon/StatusIconOverlay.cs index f1473bda7a..4b3daae22f 100644 --- a/Content.Client/StatusIcon/StatusIconOverlay.cs +++ b/Content.Client/StatusIcon/StatusIconOverlay.cs @@ -45,7 +45,7 @@ public sealed class StatusIconOverlay : Overlay var query = _entity.AllEntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp, out var sprite, out var xform, out var meta)) { - if (xform.MapID != args.MapId) + if (xform.MapID != args.MapId || !sprite.Visible) continue; var bounds = comp.Bounds ?? sprite.Bounds; @@ -72,6 +72,8 @@ public sealed class StatusIconOverlay : Overlay foreach (var proto in icons) { + if (!_statusIcon.IsVisible((uid, meta), proto)) + continue; var curTime = _timing.RealTime; var texture = _sprite.GetFrame(proto.Icon, curTime); diff --git a/Content.Client/StatusIcon/StatusIconSystem.cs b/Content.Client/StatusIcon/StatusIconSystem.cs index 980fd9f2a9..63f5776769 100644 --- a/Content.Client/StatusIcon/StatusIconSystem.cs +++ b/Content.Client/StatusIcon/StatusIconSystem.cs @@ -1,7 +1,11 @@ using Content.Shared.CCVar; +using Content.Shared.Ghost; using Content.Shared.StatusIcon; using Content.Shared.StatusIcon.Components; +using Content.Shared.Stealth.Components; +using Content.Shared.Whitelist; using Robust.Client.Graphics; +using Robust.Client.Player; using Robust.Shared.Configuration; namespace Content.Client.StatusIcon; @@ -13,6 +17,8 @@ public sealed class StatusIconSystem : SharedStatusIconSystem { [Dependency] private readonly IConfigurationManager _configuration = default!; [Dependency] private readonly IOverlayManager _overlay = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!; private bool _globalEnabled; private bool _localEnabled; @@ -54,10 +60,34 @@ public sealed class StatusIconSystem : SharedStatusIconSystem if (meta.EntityLifeStage >= EntityLifeStage.Terminating) return list; - var inContainer = (meta.Flags & MetaDataFlags.InContainer) != 0; - var ev = new GetStatusIconsEvent(list, inContainer); + var ev = new GetStatusIconsEvent(list); RaiseLocalEvent(uid, ref ev); return ev.StatusIcons; } -} + /// + /// For overlay to check if an entity can be seen. + /// + public bool IsVisible(Entity ent, StatusIconData data) + { + var viewer = _playerManager.LocalSession?.AttachedEntity; + + // Always show our icons to our entity + if (viewer == ent.Owner) + return true; + + if (data.VisibleToGhosts && HasComp(viewer)) + return true; + + if (data.HideInContainer && (ent.Comp.Flags & MetaDataFlags.InContainer) != 0) + return false; + + if (data.HideOnStealth && TryComp(ent, out var stealth) && stealth.Enabled) + return false; + + if (data.ShowTo != null && !_entityWhitelist.IsValid(data.ShowTo, viewer)) + return false; + + return true; + } +} diff --git a/Content.Client/Stealth/StealthSystem.cs b/Content.Client/Stealth/StealthSystem.cs index b60ffc2a40..0b94e41f6b 100644 --- a/Content.Client/Stealth/StealthSystem.cs +++ b/Content.Client/Stealth/StealthSystem.cs @@ -1,4 +1,5 @@ using Content.Client.Interactable.Components; +using Content.Client.StatusIcon; using Content.Shared.Stealth; using Content.Shared.Stealth.Components; using Robust.Client.GameObjects; @@ -18,6 +19,7 @@ public sealed class StealthSystem : SharedStealthSystem base.Initialize(); _shader = _protoMan.Index("Stealth").InstanceUnique(); + SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnShaderRender); @@ -93,4 +95,3 @@ public sealed class StealthSystem : SharedStealthSystem args.Sprite.Color = new Color(visibility, visibility, 1, 1); } } - diff --git a/Content.Client/Zombies/ZombieSystem.cs b/Content.Client/Zombies/ZombieSystem.cs index 49b5d6aec1..d250e41850 100644 --- a/Content.Client/Zombies/ZombieSystem.cs +++ b/Content.Client/Zombies/ZombieSystem.cs @@ -1,21 +1,40 @@ using System.Linq; using Content.Shared.Ghost; using Content.Shared.Humanoid; +using Content.Shared.StatusIcon; using Content.Shared.StatusIcon.Components; using Content.Shared.Zombies; using Robust.Client.GameObjects; +using Robust.Shared.Prototypes; namespace Content.Client.Zombies; -public sealed class ZombieSystem : EntitySystem +public sealed class ZombieSystem : SharedZombieSystem { + [Dependency] private readonly IPrototypeManager _prototype = default!; + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnCanDisplayStatusIcons); - SubscribeLocalEvent(OnCanDisplayStatusIcons); + SubscribeLocalEvent(GetZombieIcon); + SubscribeLocalEvent(GetInitialInfectedIcon); + } + + private void GetZombieIcon(Entity ent, ref GetStatusIconsEvent args) + { + var iconPrototype = _prototype.Index(ent.Comp.StatusIcon); + args.StatusIcons.Add(iconPrototype); + } + + private void GetInitialInfectedIcon(Entity ent, ref GetStatusIconsEvent args) + { + if (HasComp(ent)) + return; + + var iconPrototype = _prototype.Index(ent.Comp.StatusIcon); + args.StatusIcons.Add(iconPrototype); } private void OnStartup(EntityUid uid, ZombieComponent component, ComponentStartup args) @@ -31,29 +50,4 @@ public sealed class ZombieSystem : EntitySystem sprite.LayerSetColor(i, component.SkinColor); } } - - /// - /// Determines whether a player should be able to see the StatusIcon for zombies. - /// - private void OnCanDisplayStatusIcons(EntityUid uid, ZombieComponent component, ref CanDisplayStatusIconsEvent args) - { - if (HasComp(args.User) || HasComp(args.User) || HasComp(args.User)) - return; - - if (component.IconVisibleToGhost && HasComp(args.User)) - return; - - args.Cancelled = true; - } - - private void OnCanDisplayStatusIcons(EntityUid uid, InitialInfectedComponent component, ref CanDisplayStatusIconsEvent args) - { - if (HasComp(args.User) && !HasComp(args.User)) - return; - - if (component.IconVisibleToGhost && HasComp(args.User)) - return; - - args.Cancelled = true; - } } diff --git a/Content.Server/Access/Components/AgentIDCardComponent.cs b/Content.Server/Access/Components/AgentIDCardComponent.cs index 4b92b43ea9..7a97c5f565 100644 --- a/Content.Server/Access/Components/AgentIDCardComponent.cs +++ b/Content.Server/Access/Components/AgentIDCardComponent.cs @@ -1,5 +1,5 @@ using Content.Shared.StatusIcon; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; +using Robust.Shared.Prototypes; namespace Content.Server.Access.Components { @@ -9,7 +9,7 @@ namespace Content.Server.Access.Components /// /// Set of job icons that the agent ID card can show. /// - [DataField("icons", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet Icons = new(); + [DataField] + public HashSet> Icons; } } diff --git a/Content.Server/Access/Systems/AgentIDCardSystem.cs b/Content.Server/Access/Systems/AgentIDCardSystem.cs index d5e9dc357d..f0ce3acbdb 100644 --- a/Content.Server/Access/Systems/AgentIDCardSystem.cs +++ b/Content.Server/Access/Systems/AgentIDCardSystem.cs @@ -90,14 +90,10 @@ namespace Content.Server.Access.Systems private void OnJobIconChanged(EntityUid uid, AgentIDCardComponent comp, AgentIDCardJobIconChangedMessage args) { if (!TryComp(uid, out var idCard)) - { return; - } - if (!_prototypeManager.TryIndex(args.JobIconId, out var jobIcon)) - { + if (!_prototypeManager.TryIndex(args.JobIconId, out var jobIcon)) return; - } _cardSystem.TryChangeJobIcon(uid, jobIcon, idCard); @@ -109,7 +105,7 @@ namespace Content.Server.Access.Systems { foreach (var jobPrototype in _prototypeManager.EnumeratePrototypes()) { - if(jobPrototype.Icon == jobIcon.ID) + if (jobPrototype.Icon == jobIcon.ID) { job = jobPrototype; return true; diff --git a/Content.Server/Access/Systems/IdCardConsoleSystem.cs b/Content.Server/Access/Systems/IdCardConsoleSystem.cs index 4e63c93a83..e02664f2bb 100644 --- a/Content.Server/Access/Systems/IdCardConsoleSystem.cs +++ b/Content.Server/Access/Systems/IdCardConsoleSystem.cs @@ -129,7 +129,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem _idCard.TryChangeJobTitle(targetId, newJobTitle, player: player); if (_prototype.TryIndex(newJobProto, out var job) - && _prototype.TryIndex(job.Icon, out var jobIcon)) + && _prototype.TryIndex(job.Icon, out var jobIcon)) { _idCard.TryChangeJobIcon(targetId, jobIcon, player: player); _idCard.TryChangeJobDepartment(targetId, job); diff --git a/Content.Server/Access/Systems/PresetIdCardSystem.cs b/Content.Server/Access/Systems/PresetIdCardSystem.cs index 3e775b9c35..426e523243 100644 --- a/Content.Server/Access/Systems/PresetIdCardSystem.cs +++ b/Content.Server/Access/Systems/PresetIdCardSystem.cs @@ -82,9 +82,7 @@ public sealed class PresetIdCardSystem : EntitySystem _cardSystem.TryChangeJobTitle(uid, job.LocalizedName); _cardSystem.TryChangeJobDepartment(uid, job); - if (_prototypeManager.TryIndex(job.Icon, out var jobIcon)) - { + if (_prototypeManager.TryIndex(job.Icon, out var jobIcon)) _cardSystem.TryChangeJobIcon(uid, jobIcon); - } } } diff --git a/Content.Server/Revolutionary/RevolutionarySystem.cs b/Content.Server/Revolutionary/RevolutionarySystem.cs new file mode 100644 index 0000000000..597c4896b9 --- /dev/null +++ b/Content.Server/Revolutionary/RevolutionarySystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Revolutionary; + +namespace Content.Server.Revolutionary; + +public sealed class RevolutionarySystem : SharedRevolutionarySystem; diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index f175565a5a..557a1f9566 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -257,10 +257,8 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem _cardSystem.TryChangeFullName(cardId, characterName, card); _cardSystem.TryChangeJobTitle(cardId, jobPrototype.LocalizedName, card); - if (_prototypeManager.TryIndex(jobPrototype.Icon, out var jobIcon)) - { + if (_prototypeManager.TryIndex(jobPrototype.Icon, out var jobIcon)) _cardSystem.TryChangeJobIcon(cardId, jobIcon, card); - } var extendedAccess = false; if (station != null) diff --git a/Content.Shared/Access/SharedAgentIDCardSystem.cs b/Content.Shared/Access/SharedAgentIDCardSystem.cs index d027a3937f..91aa626fe3 100644 --- a/Content.Shared/Access/SharedAgentIDCardSystem.cs +++ b/Content.Shared/Access/SharedAgentIDCardSystem.cs @@ -1,3 +1,5 @@ +using Content.Shared.StatusIcon; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Access.Systems @@ -23,12 +25,12 @@ namespace Content.Shared.Access.Systems [Serializable, NetSerializable] public sealed class AgentIDCardBoundUserInterfaceState : BoundUserInterfaceState { - public readonly HashSet Icons; + public readonly HashSet> Icons; public string CurrentName { get; } public string CurrentJob { get; } public string CurrentJobIconId { get; } - public AgentIDCardBoundUserInterfaceState(string currentName, string currentJob, string currentJobIconId, HashSet icons) + public AgentIDCardBoundUserInterfaceState(string currentName, string currentJob, string currentJobIconId, HashSet> icons) { Icons = icons; CurrentName = currentName; @@ -62,9 +64,9 @@ namespace Content.Shared.Access.Systems [Serializable, NetSerializable] public sealed class AgentIDCardJobIconChangedMessage : BoundUserInterfaceMessage { - public string JobIconId { get; } + public ProtoId JobIconId { get; } - public AgentIDCardJobIconChangedMessage(string jobIconId) + public AgentIDCardJobIconChangedMessage(ProtoId jobIconId) { JobIconId = jobIconId; } diff --git a/Content.Shared/Antag/IAntagStatusIconComponent.cs b/Content.Shared/Antag/IAntagStatusIconComponent.cs deleted file mode 100644 index 981937c916..0000000000 --- a/Content.Shared/Antag/IAntagStatusIconComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Content.Shared.StatusIcon; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Antag; - -public interface IAntagStatusIconComponent -{ - public ProtoId StatusIcon { get; set; } - - public bool IconVisibleToGhost { get; set; } -} - diff --git a/Content.Shared/Antag/ShowAntagIconsComponent.cs b/Content.Shared/Antag/ShowAntagIconsComponent.cs new file mode 100644 index 0000000000..c451b69c3e --- /dev/null +++ b/Content.Shared/Antag/ShowAntagIconsComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Antag; + +/// +/// Determines whether Someone can see antags icons +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ShowAntagIconsComponent: Component; diff --git a/Content.Shared/Overlays/ShowHealthBarsComponent.cs b/Content.Shared/Overlays/ShowHealthBarsComponent.cs index 48e3162269..1297d56838 100644 --- a/Content.Shared/Overlays/ShowHealthBarsComponent.cs +++ b/Content.Shared/Overlays/ShowHealthBarsComponent.cs @@ -1,5 +1,7 @@ using Content.Shared.Damage.Prototypes; +using Content.Shared.StatusIcon; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Overlays; @@ -15,4 +17,7 @@ public sealed partial class ShowHealthBarsComponent : Component /// [DataField("damageContainers", customTypeSerializer: typeof(PrototypeIdListSerializer))] public List DamageContainers = new(); + + [DataField] + public ProtoId HealthStatusIcon = "HealthIconFine"; } diff --git a/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs b/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs index d2c8374fef..ef2bad65e0 100644 --- a/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs +++ b/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs @@ -9,7 +9,7 @@ namespace Content.Shared.Revolutionary.Components; /// Component used for marking a Head Rev for conversion and winning/losing. /// [RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))] -public sealed partial class HeadRevolutionaryComponent : Component, IAntagStatusIconComponent +public sealed partial class HeadRevolutionaryComponent : Component { /// /// The status icon corresponding to the head revolutionary. @@ -24,7 +24,4 @@ public sealed partial class HeadRevolutionaryComponent : Component, IAntagStatus public TimeSpan StunTime = TimeSpan.FromSeconds(3); public override bool SessionSpecific => true; - - [DataField] - public bool IconVisibleToGhost { get; set; } = true; } diff --git a/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs b/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs index 73f533cf69..1f6b45ddea 100644 --- a/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs +++ b/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs @@ -10,7 +10,7 @@ namespace Content.Shared.Revolutionary.Components; /// Used for marking regular revs as well as storing icon prototypes so you can see fellow revs. /// [RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))] -public sealed partial class RevolutionaryComponent : Component, IAntagStatusIconComponent +public sealed partial class RevolutionaryComponent : Component { /// /// The status icon prototype displayed for revolutionaries @@ -25,7 +25,4 @@ public sealed partial class RevolutionaryComponent : Component, IAntagStatusIcon public SoundSpecifier RevStartSound = new SoundPathSpecifier("/Audio/Ambience/Antag/headrev_start.ogg"); public override bool SessionSpecific => true; - - [DataField] - public bool IconVisibleToGhost { get; set; } = true; } diff --git a/Content.Shared/Revolutionary/Components/ShowRevIconsComponent.cs b/Content.Shared/Revolutionary/Components/ShowRevIconsComponent.cs deleted file mode 100644 index 11e20db3f8..0000000000 --- a/Content.Shared/Revolutionary/Components/ShowRevIconsComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Revolutionary.Components; - -/// -/// Determines whether Someone can see rev/headrev icons on revs. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class ShowRevIconsComponent: Component -{ -} diff --git a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs b/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs index ddaf73fcc9..bbf91193cc 100644 --- a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs +++ b/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs @@ -1,4 +1,3 @@ -using Content.Shared.Ghost; using Content.Shared.IdentityManagement; using Content.Shared.Mindshield.Components; using Content.Shared.Popups; @@ -6,10 +5,11 @@ using Content.Shared.Revolutionary.Components; using Content.Shared.Stunnable; using Robust.Shared.GameStates; using Robust.Shared.Player; +using Content.Shared.Antag; namespace Content.Shared.Revolutionary; -public sealed class SharedRevolutionarySystem : EntitySystem +public abstract class SharedRevolutionarySystem : EntitySystem { [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedStunSystem _sharedStun = default!; @@ -23,7 +23,7 @@ public sealed class SharedRevolutionarySystem : EntitySystem SubscribeLocalEvent(OnRevCompGetStateAttempt); SubscribeLocalEvent(DirtyRevComps); SubscribeLocalEvent(DirtyRevComps); - SubscribeLocalEvent(DirtyRevComps); + SubscribeLocalEvent(DirtyRevComps); } /// @@ -52,7 +52,7 @@ public sealed class SharedRevolutionarySystem : EntitySystem /// private void OnRevCompGetStateAttempt(EntityUid uid, HeadRevolutionaryComponent comp, ref ComponentGetStateAttemptEvent args) { - args.Cancelled = !CanGetState(args.Player, comp.IconVisibleToGhost); + args.Cancelled = !CanGetState(args.Player); } /// @@ -60,30 +60,24 @@ public sealed class SharedRevolutionarySystem : EntitySystem /// private void OnRevCompGetStateAttempt(EntityUid uid, RevolutionaryComponent comp, ref ComponentGetStateAttemptEvent args) { - args.Cancelled = !CanGetState(args.Player, comp.IconVisibleToGhost); + args.Cancelled = !CanGetState(args.Player); } /// /// The criteria that determine whether a Rev/HeadRev component should be sent to a client. /// /// The Player the component will be sent to. - /// Whether the component permits the icon to be visible to observers. /// - private bool CanGetState(ICommonSession? player, bool visibleToGhosts) + private bool CanGetState(ICommonSession? player) { //Apparently this can be null in replays so I am just returning true. - if (player is null) + if (player?.AttachedEntity is not {} uid) return true; - var uid = player.AttachedEntity; - if (HasComp(uid) || HasComp(uid)) return true; - if (visibleToGhosts && HasComp(uid)) - return true; - - return HasComp(uid); + return HasComp(uid); } /// /// Dirties all the Rev components so they are sent to clients. diff --git a/Content.Shared/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index 2959b56497..0b3bfb4438 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -97,8 +97,8 @@ namespace Content.Shared.Roles [DataField("jobEntity", customTypeSerializer: typeof(PrototypeIdSerializer))] public string? JobEntity = null; - [DataField("icon", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Icon { get; private set; } = "JobIconUnknown"; + [DataField] + public ProtoId Icon { get; private set; } = "JobIconUnknown"; [DataField("special", serverOnly: true)] public JobSpecial[] Special { get; private set; } = Array.Empty(); diff --git a/Content.Shared/SSDIndicator/SSDIndicatorComponent.cs b/Content.Shared/SSDIndicator/SSDIndicatorComponent.cs index 66310505a1..ee67e3296f 100644 --- a/Content.Shared/SSDIndicator/SSDIndicatorComponent.cs +++ b/Content.Shared/SSDIndicator/SSDIndicatorComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.StatusIcon; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.SSDIndicator; @@ -16,6 +17,6 @@ public sealed partial class SSDIndicatorComponent : Component public bool IsSSD = true; [ViewVariables(VVAccess.ReadWrite)] - [DataField("icon", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Icon = "SSDIcon"; + [DataField] + public ProtoId Icon = "SSDIcon"; } diff --git a/Content.Shared/StatusIcon/Components/StatusIconComponent.cs b/Content.Shared/StatusIcon/Components/StatusIconComponent.cs index 385f9760c6..c56be7c96a 100644 --- a/Content.Shared/StatusIcon/Components/StatusIconComponent.cs +++ b/Content.Shared/StatusIcon/Components/StatusIconComponent.cs @@ -24,16 +24,4 @@ public sealed partial class StatusIconComponent : Component /// /// [ByRefEvent] -public record struct GetStatusIconsEvent(List StatusIcons, bool InContainer); - -/// -/// Event raised on the Client-side to determine whether to display a status icon on an entity. -/// -/// The player that will see the icons -[ByRefEvent] -public record struct CanDisplayStatusIconsEvent(EntityUid? User = null) -{ - public EntityUid? User = User; - - public bool Cancelled = false; -} +public record struct GetStatusIconsEvent(List StatusIcons); diff --git a/Content.Shared/StatusIcon/StatusIconPrototype.cs b/Content.Shared/StatusIcon/StatusIconPrototype.cs index 2bd13b9361..29a1b60114 100644 --- a/Content.Shared/StatusIcon/StatusIconPrototype.cs +++ b/Content.Shared/StatusIcon/StatusIconPrototype.cs @@ -1,3 +1,5 @@ +using Content.Shared.Stealth.Components; +using Content.Shared.Whitelist; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; @@ -15,26 +17,45 @@ public partial class StatusIconData : IComparable /// /// The icon that's displayed on the entity. /// - [DataField("icon", required: true)] + [DataField(required: true)] public SpriteSpecifier Icon = default!; /// /// A priority for the order in which the icons will be displayed. /// - [DataField("priority")] + [DataField] public int Priority = 10; + /// + /// Whether or not to hide the icon to ghosts + /// + [DataField] + public bool VisibleToGhosts = true; + + /// + /// Whether or not to hide the icon when we are inside a container like a locker or a crate. + /// + [DataField] + public bool HideInContainer = true; + + /// + /// Whether or not to hide the icon when the entity has an active + /// + [DataField] + public bool HideOnStealth = true; + + /// + /// Specifies what entities and components/tags this icon can be shown to. + /// + [DataField] + public EntityWhitelist? ShowTo; + /// /// A preference for where the icon will be displayed. None | Left | Right /// - [DataField("locationPreference")] + [DataField] public StatusIconLocationPreference LocationPreference = StatusIconLocationPreference.None; - public int CompareTo(StatusIconData? other) - { - return Priority.CompareTo(other?.Priority ?? int.MaxValue); - } - /// /// The layer the icon is displayed on. Mod is drawn above Base. Base | Mod /// @@ -52,6 +73,11 @@ public partial class StatusIconData : IComparable /// [DataField] public bool IsShaded = false; + + public int CompareTo(StatusIconData? other) + { + return Priority.CompareTo(other?.Priority ?? int.MaxValue); + } } /// diff --git a/Content.Shared/Zombies/InitialInfectedComponent.cs b/Content.Shared/Zombies/InitialInfectedComponent.cs index 3200dd7f5e..3149de63bd 100644 --- a/Content.Shared/Zombies/InitialInfectedComponent.cs +++ b/Content.Shared/Zombies/InitialInfectedComponent.cs @@ -1,4 +1,3 @@ -using Content.Shared.Antag; using Content.Shared.StatusIcon; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -6,11 +5,8 @@ using Robust.Shared.Prototypes; namespace Content.Shared.Zombies; [RegisterComponent, NetworkedComponent] -public sealed partial class InitialInfectedComponent : Component, IAntagStatusIconComponent +public sealed partial class InitialInfectedComponent : Component { - [DataField("initialInfectedStatusIcon")] - public ProtoId StatusIcon { get; set; } = "InitialInfectedFaction"; - [DataField] - public bool IconVisibleToGhost { get; set; } = true; + public ProtoId StatusIcon = "InitialInfectedFaction"; } diff --git a/Content.Shared/Zombies/ShowZombieIconsComponent.cs b/Content.Shared/Zombies/ShowZombieIconsComponent.cs deleted file mode 100644 index a2bc85c074..0000000000 --- a/Content.Shared/Zombies/ShowZombieIconsComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Zombies; - -/// -/// Makes it so an entity can view ZombieAntagIcons. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class ShowZombieIconsComponent: Component -{ - -} diff --git a/Content.Shared/Zombies/ZombieComponent.cs b/Content.Shared/Zombies/ZombieComponent.cs index 3673a2c51d..2cd0cdb96d 100644 --- a/Content.Shared/Zombies/ZombieComponent.cs +++ b/Content.Shared/Zombies/ZombieComponent.cs @@ -14,7 +14,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy namespace Content.Shared.Zombies; [RegisterComponent, NetworkedComponent] -public sealed partial class ZombieComponent : Component, IAntagStatusIconComponent +public sealed partial class ZombieComponent : Component { /// /// The baseline infection chance you have if you are completely nude @@ -97,9 +97,6 @@ public sealed partial class ZombieComponent : Component, IAntagStatusIconCompone [DataField("zombieStatusIcon")] public ProtoId StatusIcon { get; set; } = "ZombieFaction"; - [DataField] - public bool IconVisibleToGhost { get; set; } = true; - /// /// Healing each second /// diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 4a83593bc3..8b0f60ca58 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -84,8 +84,7 @@ - type: Stripping - type: SolutionScanner - type: IgnoreUIRange - - type: ShowRevIcons - - type: ShowZombieIcons + - type: ShowAntagIcons - type: Inventory templateId: aghost - type: InventorySlots diff --git a/Resources/Prototypes/StatusEffects/health.yml b/Resources/Prototypes/StatusIcon/StatusEffects/health.yml similarity index 100% rename from Resources/Prototypes/StatusEffects/health.yml rename to Resources/Prototypes/StatusIcon/StatusEffects/health.yml diff --git a/Resources/Prototypes/StatusEffects/hunger.yml b/Resources/Prototypes/StatusIcon/StatusEffects/hunger.yml similarity index 100% rename from Resources/Prototypes/StatusEffects/hunger.yml rename to Resources/Prototypes/StatusIcon/StatusEffects/hunger.yml diff --git a/Resources/Prototypes/StatusEffects/ssd.yml b/Resources/Prototypes/StatusIcon/StatusEffects/ssd.yml similarity index 100% rename from Resources/Prototypes/StatusEffects/ssd.yml rename to Resources/Prototypes/StatusIcon/StatusEffects/ssd.yml diff --git a/Resources/Prototypes/StatusIcon/antag.yml b/Resources/Prototypes/StatusIcon/antag.yml index 0dbdfce4f9..0da545b8a2 100644 --- a/Resources/Prototypes/StatusIcon/antag.yml +++ b/Resources/Prototypes/StatusIcon/antag.yml @@ -1,6 +1,11 @@ - type: statusIcon id: ZombieFaction priority: 11 + showTo: + components: + - ShowAntagIcons + - Zombie + - InitialInfected icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: Zombie @@ -8,6 +13,10 @@ - type: statusIcon id: InitialInfectedFaction priority: 11 + showTo: + components: + - ShowAntagIcons + - InitialInfected icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: InitialInfected @@ -15,6 +24,10 @@ - type: statusIcon id: RevolutionaryFaction priority: 11 + showTo: + components: + - ShowAntagIcons + - Revolutionary icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: Revolutionary @@ -22,6 +35,10 @@ - type: statusIcon id: HeadRevolutionaryFaction priority: 11 + showTo: + components: + - ShowAntagIcons + - Revolutionary icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: HeadRevolutionary diff --git a/Resources/Prototypes/StatusEffects/job.yml b/Resources/Prototypes/StatusIcon/job.yml similarity index 100% rename from Resources/Prototypes/StatusEffects/job.yml rename to Resources/Prototypes/StatusIcon/job.yml diff --git a/Resources/Prototypes/StatusEffects/security.yml b/Resources/Prototypes/StatusIcon/security.yml similarity index 100% rename from Resources/Prototypes/StatusEffects/security.yml rename to Resources/Prototypes/StatusIcon/security.yml From 1e04b3c9f492e82bf28da9e9e97bcad65bb01c67 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 16:13:28 +0000 Subject: [PATCH 032/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index cab645fa8d..f2d3131827 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -3847,3 +3847,11 @@ id: 6674 time: '2024-06-03T13:04:07.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28072 +- author: AJCM-git + changes: + - message: Ninja stealth mode, stealth boxes and everything with stealth won't show + up in equipment HUDs like the medical or security HUD anymore. + type: Fix + id: 6675 + time: '2024-06-03T16:12:21.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28270 From 619d82ed426c50a72d71ca195d901c11b09adea9 Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:22:26 -0400 Subject: [PATCH 033/243] Small code cleanup to health bar (#28554) --- Content.Client/Commands/ShowHealthBarsCommand.cs | 2 +- Content.Client/Overlays/EntityHealthBarOverlay.cs | 2 +- Content.Shared/Overlays/ShowHealthBarsComponent.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Content.Client/Commands/ShowHealthBarsCommand.cs b/Content.Client/Commands/ShowHealthBarsCommand.cs index ef918313a0..0811f96663 100644 --- a/Content.Client/Commands/ShowHealthBarsCommand.cs +++ b/Content.Client/Commands/ShowHealthBarsCommand.cs @@ -35,7 +35,7 @@ public sealed class ShowHealthBarsCommand : LocalizedCommands var showHealthBarsComponent = new ShowHealthBarsComponent { DamageContainers = args.ToList(), - HealthStatusIcon = "", + HealthStatusIcon = null, NetSyncEnabled = false }; diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs index 55978d98f7..4f92843739 100644 --- a/Content.Client/Overlays/EntityHealthBarOverlay.cs +++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs @@ -33,7 +33,7 @@ public sealed class EntityHealthBarOverlay : Overlay public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; public HashSet DamageContainers = new(); - public ProtoId StatusIcon; + public ProtoId? StatusIcon; public EntityHealthBarOverlay(IEntityManager entManager, IPrototypeManager prototype) { diff --git a/Content.Shared/Overlays/ShowHealthBarsComponent.cs b/Content.Shared/Overlays/ShowHealthBarsComponent.cs index 1297d56838..4229e27af6 100644 --- a/Content.Shared/Overlays/ShowHealthBarsComponent.cs +++ b/Content.Shared/Overlays/ShowHealthBarsComponent.cs @@ -19,5 +19,5 @@ public sealed partial class ShowHealthBarsComponent : Component public List DamageContainers = new(); [DataField] - public ProtoId HealthStatusIcon = "HealthIconFine"; + public ProtoId? HealthStatusIcon = "HealthIconFine"; } From b5e8a696225a3c790939b67a832067caa1354c67 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 3 Jun 2024 12:24:32 -0400 Subject: [PATCH 034/243] Add a test for sliceable cargo bounty exploits (#28357) --- Content.IntegrationTests/Tests/CargoTest.cs | 79 +++++++++++++++++++ .../Cargo/Systems/CargoSystem.Bounty.cs | 17 +++- .../Prototypes/Catalog/Bounties/bounties.yml | 6 ++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/Content.IntegrationTests/Tests/CargoTest.cs b/Content.IntegrationTests/Tests/CargoTest.cs index 09f179cf4f..8e1d536054 100644 --- a/Content.IntegrationTests/Tests/CargoTest.cs +++ b/Content.IntegrationTests/Tests/CargoTest.cs @@ -3,8 +3,13 @@ using System.Linq; using System.Numerics; using Content.Server.Cargo.Components; using Content.Server.Cargo.Systems; +using Content.Server.Nutrition.Components; +using Content.Server.Nutrition.EntitySystems; using Content.Shared.Cargo.Prototypes; +using Content.Shared.IdentityManagement; using Content.Shared.Stacks; +using Content.Shared.Tag; +using Content.Shared.Whitelist; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -149,6 +154,80 @@ public sealed class CargoTest await pair.CleanReturnAsync(); } + /// + /// Tests to see if any items that are valid for cargo bounties can be sliced into items that + /// are also valid for the same bounty entry. + /// + [Test] + public async Task NoSliceableBountyArbitrageTest() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var testMap = await pair.CreateTestMap(); + + var entManager = server.ResolveDependency(); + var mapManager = server.ResolveDependency(); + var protoManager = server.ResolveDependency(); + var componentFactory = server.ResolveDependency(); + var whitelist = entManager.System(); + var cargo = entManager.System(); + var sliceableSys = entManager.System(); + + var bounties = protoManager.EnumeratePrototypes().ToList(); + + await server.WaitAssertion(() => + { + var mapId = testMap.MapId; + var grid = mapManager.CreateGridEntity(mapId); + var coord = new EntityCoordinates(grid.Owner, 0, 0); + + var sliceableEntityProtos = protoManager.EnumeratePrototypes() + .Where(p => !p.Abstract) + .Where(p => !pair.IsTestPrototype(p)) + .Where(p => p.TryGetComponent(out _, componentFactory)) + .Select(p => p.ID) + .ToList(); + + foreach (var proto in sliceableEntityProtos) + { + var ent = entManager.SpawnEntity(proto, coord); + var sliceable = entManager.GetComponent(ent); + + // Check each bounty + foreach (var bounty in bounties) + { + // Check each entry in the bounty + foreach (var entry in bounty.Entries) + { + // See if the entity counts as part of this bounty entry + if (!cargo.IsValidBountyEntry(ent, entry)) + continue; + + // Spawn a slice + var slice = entManager.SpawnEntity(sliceable.Slice, coord); + + // See if the slice also counts for this bounty entry + if (!cargo.IsValidBountyEntry(slice, entry)) + { + entManager.DeleteEntity(slice); + continue; + } + + entManager.DeleteEntity(slice); + + // If for some reason it can only make one slice, that's okay, I guess + Assert.That(sliceable.TotalCount, Is.EqualTo(1), $"{proto} counts as part of cargo bounty {bounty.ID} and slices into {sliceable.TotalCount} slices which count for the same bounty!"); + } + } + + entManager.DeleteEntity(ent); + } + mapManager.DeleteMap(mapId); + }); + + await pair.CleanReturnAsync(); + } [TestPrototypes] private const string StackProto = @" diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs index 0fcfd160bb..554b349b9b 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs @@ -300,6 +300,21 @@ public sealed partial class CargoSystem return IsBountyComplete(GetBountyEntities(container), entries, out bountyEntities); } + /// + /// Determines whether the meets the criteria for the bounty . + /// + /// true if is a valid item for the bounty entry, otherwise false + public bool IsValidBountyEntry(EntityUid entity, CargoBountyItemEntry entry) + { + if (!_whitelistSys.IsValid(entry.Whitelist, entity)) + return false; + + if (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity)) + return false; + + return true; + } + public bool IsBountyComplete(HashSet entities, IEnumerable entries, out HashSet bountyEntities) { bountyEntities = new(); @@ -313,7 +328,7 @@ public sealed partial class CargoSystem var temp = new HashSet(); foreach (var entity in entities) { - if (!_whitelistSys.IsValid(entry.Whitelist, entity) || (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity))) + if (!IsValidBountyEntry(entity, entry)) continue; count += _stackQuery.CompOrNull(entity)?.Count ?? 1; diff --git a/Resources/Prototypes/Catalog/Bounties/bounties.yml b/Resources/Prototypes/Catalog/Bounties/bounties.yml index bedfe44287..08bb2a1422 100644 --- a/Resources/Prototypes/Catalog/Bounties/bounties.yml +++ b/Resources/Prototypes/Catalog/Bounties/bounties.yml @@ -42,6 +42,9 @@ whitelist: tags: - Bread + blacklist: + tags: + - Slice - type: cargoBounty id: BountyCarrot @@ -533,6 +536,9 @@ whitelist: tags: - Meat + blacklist: + components: + - SliceableFood - type: cargoBounty id: BountyFruit From ee8224bce22c5d5ca76faaa43b2fc8441f356610 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 3 Jun 2024 12:59:59 -0400 Subject: [PATCH 035/243] Tip 51 does not exist (#28555) --- Resources/Locale/en-US/tips.ftl | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Locale/en-US/tips.ftl b/Resources/Locale/en-US/tips.ftl index 52912d1ba4..90a0616452 100644 --- a/Resources/Locale/en-US/tips.ftl +++ b/Resources/Locale/en-US/tips.ftl @@ -48,6 +48,7 @@ tips-dataset-47 = As a Salvage Specialist, you can use your proto-kinetic accele tips-dataset-48 = As a Salvage Specialist, never forget to mine ore! Ore can be sold to cargo for a pretty penny, be used for construction, and also be used by Scientists for fancy technology. tips-dataset-49 = As a Salvage Specialist, try asking science for a tethergun. It can be used to grab items off of salvage wrecks extremely efficiently! tips-dataset-50 = As a Salvage Specialist, try asking science for a grappling hook. It can be used to propel yourself onto wrecks, or if stuck in space you don't have to rely on the proto-kinetic accelerator. +tips-dataset-51 = Tip #51 does not exist and has never existed. Ignore any rumors to the contrary. tips-dataset-52 = As a Salvage Specialist, consider cooperating with the Cargo Technicians. They can order you a wide variety of useful items, including ones that may be hard to get otherwise, such laser guns and shuttle building materials. tips-dataset-53 = As a Cargo Technician, consider asking science for a Ripley APLU. When paired with a hydraulic clamp, you can grab valuable maintenance objects like fuel tanks much more easily, and make deliveries in a swift manner. tips-dataset-54 = As a Cargo Technician, try to maintain a surplus of materials. They are extremely useful for Scientists and Station Engineers to have immediate access to. From a4d16017589e82822c9880b146b8d3dab9621093 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Mon, 3 Jun 2024 21:47:06 +0300 Subject: [PATCH 036/243] Accent trait limit (#28046) --- .../Lobby/UI/HumanoidProfileEditor.xaml.cs | 113 +++++++++++++----- .../UI/Roles/TraitPreferenceSelector.xaml | 2 +- .../UI/Roles/TraitPreferenceSelector.xaml.cs | 9 +- Content.Server/Traits/TraitSystem.cs | 30 ++--- .../Preferences/HumanoidCharacterProfile.cs | 32 ++++- .../Traits/TraitCategoryPrototype.cs | 26 ++++ Content.Shared/Traits/TraitPrototype.cs | 90 +++++++------- .../ui/humanoid-profile-editor.ftl | 11 +- Resources/Locale/en-US/traits/traits.ftl | 18 ++- Resources/Prototypes/Traits/categories.yml | 8 ++ Resources/Prototypes/Traits/disabilities.yml | 58 +++++---- .../Prototypes/Traits/inconveniences.yml | 18 --- Resources/Prototypes/Traits/neutral.yml | 33 ----- Resources/Prototypes/Traits/speech.yml | 88 ++++++++++++++ 14 files changed, 365 insertions(+), 171 deletions(-) create mode 100644 Content.Shared/Traits/TraitCategoryPrototype.cs create mode 100644 Resources/Prototypes/Traits/categories.yml delete mode 100644 Resources/Prototypes/Traits/inconveniences.yml delete mode 100644 Resources/Prototypes/Traits/neutral.yml create mode 100644 Resources/Prototypes/Traits/speech.yml diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index 53c332c185..ac43fa11a8 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -7,6 +7,7 @@ using Content.Client.Lobby.UI.Loadouts; using Content.Client.Lobby.UI.Roles; using Content.Client.Message; using Content.Client.Players.PlayTimeTracking; +using Content.Client.Stylesheets; using Content.Client.UserInterface.Systems.Guidebook; using Content.Shared.CCVar; using Content.Shared.Clothing; @@ -466,38 +467,96 @@ namespace Content.Client.Lobby.UI var traits = _prototypeManager.EnumeratePrototypes().OrderBy(t => Loc.GetString(t.Name)).ToList(); TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab")); - if (traits.Count > 0) - { - foreach (var trait in traits) - { - var selector = new TraitPreferenceSelector(trait); - - if (Profile?.TraitPreferences.Contains(trait.ID) == true) - { - selector.Preference = true; - } - else - { - selector.Preference = false; - } - - selector.PreferenceChanged += preference => - { - Profile = Profile?.WithTraitPreference(trait.ID, preference); - SetDirty(); - }; - - TraitsList.AddChild(selector); - } - } - else + if (traits.Count < 1) { TraitsList.AddChild(new Label { - // TODO: Localise - Text = "No traits available :(", + Text = Loc.GetString("humanoid-profile-editor-no-traits"), FontColorOverride = Color.Gray, }); + return; + } + + //Setup model + Dictionary> model = new(); + List defaultTraits = new(); + model.Add("default", defaultTraits); + + foreach (var trait in traits) + { + if (trait.Category == null) + { + defaultTraits.Add(trait.ID); + continue; + } + + if (!model.ContainsKey(trait.Category)) + { + model.Add(trait.Category, new()); + } + model[trait.Category].Add(trait.ID); + } + + //Create UI view from model + foreach (var (categoryId, traitId) in model) + { + TraitCategoryPrototype? category = null; + if (categoryId != "default") + { + category = _prototypeManager.Index(categoryId); + // Label + TraitsList.AddChild(new Label + { + Text = Loc.GetString(category.Name), + Margin = new Thickness(0, 10, 0, 0), + StyleClasses = { StyleBase.StyleClassLabelHeading }, + }); + } + + List selectors = new(); + var selectionCount = 0; + + foreach (var traitProto in traitId) + { + var trait = _prototypeManager.Index(traitProto); + var selector = new TraitPreferenceSelector(trait); + + selector.Preference = Profile?.TraitPreferences.Contains(trait.ID) == true; + if (selector.Preference) + selectionCount += trait.Cost; + + selector.PreferenceChanged += preference => + { + Profile = Profile?.WithTraitPreference(trait.ID, categoryId, preference); + SetDirty(); + RefreshTraits(); // If too many traits are selected, they will be reset to the real value. + }; + selectors.Add(selector); + } + + // Selection counter + if (category is { MaxTraitPoints: >= 0 }) + { + TraitsList.AddChild(new Label + { + Text = Loc.GetString("humanoid-profile-editor-trait-count-hint", ("current", selectionCount) ,("max", category.MaxTraitPoints)), + FontColorOverride = Color.Gray + }); + } + + foreach (var selector in selectors) + { + if (selector == null) + continue; + + if (category is { MaxTraitPoints: >= 0 } && + selector.Cost + selectionCount > category.MaxTraitPoints) + { + selector.Checkbox.Label.FontColorOverride = Color.Red; + } + + TraitsList.AddChild(selector); + } } } diff --git a/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml index 18dabe3090..266b4b8eee 100644 --- a/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml +++ b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml @@ -2,6 +2,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - + diff --git a/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs index 498a5ca4e5..a52a3fa2db 100644 --- a/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs +++ b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs @@ -9,6 +9,8 @@ namespace Content.Client.Lobby.UI.Roles; [GenerateTypedNameReferences] public sealed partial class TraitPreferenceSelector : Control { + public int Cost; + public bool Preference { get => Checkbox.Pressed; @@ -20,7 +22,12 @@ public sealed partial class TraitPreferenceSelector : Control public TraitPreferenceSelector(TraitPrototype trait) { RobustXamlLoader.Load(this); - Checkbox.Text = Loc.GetString(trait.Name); + + var text = trait.Cost != 0 ? $"[{trait.Cost}] " : ""; + text += Loc.GetString(trait.Name); + + Cost = trait.Cost; + Checkbox.Text = text; Checkbox.OnToggled += OnCheckBoxToggled; if (trait.Description is { } desc) diff --git a/Content.Server/Traits/TraitSystem.cs b/Content.Server/Traits/TraitSystem.cs index 22ee0e4861..f7531f7e2c 100644 --- a/Content.Server/Traits/TraitSystem.cs +++ b/Content.Server/Traits/TraitSystem.cs @@ -38,27 +38,21 @@ public sealed class TraitSystem : EntitySystem continue; // Add all components required by the prototype - foreach (var entry in traitPrototype.Components.Values) - { - if (HasComp(args.Mob, entry.Component.GetType())) - continue; - - var comp = (Component) _serializationManager.CreateCopy(entry.Component, notNullableOverride: true); - comp.Owner = args.Mob; - EntityManager.AddComponent(args.Mob, comp); - } + EntityManager.AddComponents(args.Mob, traitPrototype.Components, false); // Add item required by the trait - if (traitPrototype.TraitGear != null) - { - if (!TryComp(args.Mob, out HandsComponent? handsComponent)) - continue; + if (traitPrototype.TraitGear == null) + continue; - var coords = Transform(args.Mob).Coordinates; - var inhandEntity = EntityManager.SpawnEntity(traitPrototype.TraitGear, coords); - _sharedHandsSystem.TryPickup(args.Mob, inhandEntity, checkActionBlocker: false, - handsComp: handsComponent); - } + if (!TryComp(args.Mob, out HandsComponent? handsComponent)) + continue; + + var coords = Transform(args.Mob).Coordinates; + var inhandEntity = EntityManager.SpawnEntity(traitPrototype.TraitGear, coords); + _sharedHandsSystem.TryPickup(args.Mob, + inhandEntity, + checkActionBlocker: false, + handsComp: handsComponent); } } } diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index f47a3ac3db..bd55bbb40a 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -346,13 +346,43 @@ namespace Content.Shared.Preferences }; } - public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref) + public HumanoidCharacterProfile WithTraitPreference(string traitId, string? categoryId, bool pref) { + var prototypeManager = IoCManager.Resolve(); + var traitProto = prototypeManager.Index(traitId); + + TraitCategoryPrototype? categoryProto = null; + if (categoryId != null && categoryId != "default") + categoryProto = prototypeManager.Index(categoryId); + var list = new HashSet(_traitPreferences); if (pref) { list.Add(traitId); + + if (categoryProto == null || categoryProto.MaxTraitPoints < 0) + { + return new(this) + { + _traitPreferences = list, + }; + } + + var count = 0; + foreach (var trait in list) + { + var traitProtoTemp = prototypeManager.Index(trait); + count += traitProtoTemp.Cost; + } + + if (count > categoryProto.MaxTraitPoints && traitProto.Cost != 0) + { + return new(this) + { + _traitPreferences = _traitPreferences, + }; + } } else { diff --git a/Content.Shared/Traits/TraitCategoryPrototype.cs b/Content.Shared/Traits/TraitCategoryPrototype.cs new file mode 100644 index 0000000000..1da624173a --- /dev/null +++ b/Content.Shared/Traits/TraitCategoryPrototype.cs @@ -0,0 +1,26 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Traits; + +/// +/// Traits category with general settings. Allows you to limit the number of taken traits in one category +/// +[Prototype] +public sealed partial class TraitCategoryPrototype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// Name of the trait category displayed in the UI + /// + [DataField] + public LocId Name { get; private set; } = string.Empty; + + /// + /// The maximum number of traits that can be taken in this category. If -1, you can take as many traits as you like. + /// + [DataField] + public int MaxTraitPoints = -1; +} diff --git a/Content.Shared/Traits/TraitPrototype.cs b/Content.Shared/Traits/TraitPrototype.cs index 34feb8da22..c79d3cbf30 100644 --- a/Content.Shared/Traits/TraitPrototype.cs +++ b/Content.Shared/Traits/TraitPrototype.cs @@ -1,55 +1,63 @@ using Content.Shared.Whitelist; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -// don't worry about it +namespace Content.Shared.Traits; -namespace Content.Shared.Traits +/// +/// Describes a trait. +/// +[Prototype] +public sealed partial class TraitPrototype : IPrototype { + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + /// - /// Describes a trait. + /// The name of this trait. /// - [Prototype("trait")] - public sealed partial class TraitPrototype : IPrototype - { - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; + [DataField] + public LocId Name { get; private set; } = string.Empty; - /// - /// The name of this trait. - /// - [DataField("name")] - public string Name { get; private set; } = ""; + /// + /// The description of this trait. + /// + [DataField] + public LocId? Description { get; private set; } - /// - /// The description of this trait. - /// - [DataField("description")] - public string? Description { get; private set; } + /// + /// Don't apply this trait to entities this whitelist IS NOT valid for. + /// + [DataField] + public EntityWhitelist? Whitelist; - /// - /// Don't apply this trait to entities this whitelist IS NOT valid for. - /// - [DataField("whitelist")] - public EntityWhitelist? Whitelist; + /// + /// Don't apply this trait to entities this whitelist IS valid for. (hence, a blacklist) + /// + [DataField] + public EntityWhitelist? Blacklist; - /// - /// Don't apply this trait to entities this whitelist IS valid for. (hence, a blacklist) - /// - [DataField("blacklist")] - public EntityWhitelist? Blacklist; + /// + /// The components that get added to the player, when they pick this trait. + /// + [DataField] + public ComponentRegistry Components { get; private set; } = default!; - /// - /// The components that get added to the player, when they pick this trait. - /// - [DataField("components")] - public ComponentRegistry Components { get; private set; } = default!; + /// + /// Gear that is given to the player, when they pick this trait. + /// + [DataField] + public EntProtoId? TraitGear; - /// - /// Gear that is given to the player, when they pick this trait. - /// - [DataField("traitGear", required: false, customTypeSerializer:typeof(PrototypeIdSerializer))] - public string? TraitGear; - } + /// + /// Trait Price. If negative number, points will be added. + /// + [DataField] + public int Cost = 0; + + /// + /// Adds a trait to a category, allowing you to limit the selection of some traits to the settings of that category. + /// + [DataField] + public ProtoId? Category; } diff --git a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl index c7a24d5405..bfdbeb2f14 100644 --- a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl +++ b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl @@ -42,7 +42,7 @@ humanoid-profile-editor-department-jobs-label = {$departmentName} jobs humanoid-profile-editor-antags-tab = Antags humanoid-profile-editor-antag-preference-yes-button = Yes humanoid-profile-editor-antag-preference-no-button = No -humanoid-profile-editor-traits-tab = Traits + humanoid-profile-editor-job-priority-high-button = High humanoid-profile-editor-job-priority-medium-button = Medium humanoid-profile-editor-job-priority-low-button = Low @@ -50,3 +50,12 @@ humanoid-profile-editor-job-priority-never-button = Never humanoid-profile-editor-naming-rules-warning = Warning: Offensive or LRP IC names and descriptions will lead to admin intervention on this server. Read our \[Rules\] for more. humanoid-profile-editor-markings-tab = Markings humanoid-profile-editor-flavortext-tab = Description + +# Traits +humanoid-profile-editor-traits-tab = Traits +humanoid-profile-editor-no-traits = No traits available + +humanoid-profile-editor-trait-count-hint = Points available: [{$current}/{$max}] + +trait-category-disabilities = Disabilities +trait-category-speech = Speech traits \ No newline at end of file diff --git a/Resources/Locale/en-US/traits/traits.ftl b/Resources/Locale/en-US/traits/traits.ftl index cbf65308f3..e3aed1c8b2 100644 --- a/Resources/Locale/en-US/traits/traits.ftl +++ b/Resources/Locale/en-US/traits/traits.ftl @@ -12,7 +12,7 @@ trait-pacifist-desc = You cannot attack or hurt any living beings. permanent-blindness-trait-examined = [color=lightblue]{CAPITALIZE(POSS-ADJ($target))} eyes are glassy and unfocused. It doesn't seem like {SUBJECT($target)} can see you well, if at all.[/color] -trait-lightweight-name = Lightweight Drunk +trait-lightweight-name = Lightweight drunk trait-lightweight-desc = Alcohol has a stronger effect on you trait-muted-name = Muted @@ -24,23 +24,29 @@ trait-paracusia-desc = You hear sounds that aren't really there trait-unrevivable-name = Unrevivable trait-unrevivable-desc = You are unable to be revived by defibrillators. -trait-pirate-accent-name = Pirate Accent +trait-pirate-accent-name = Pirate accent trait-pirate-accent-desc = You can't stop speaking like a pirate! trait-accentless-name = Accentless trait-accentless-desc = You don't have the accent that your species would usually have -trait-frontal-lisp-name = Frontal Lisp +trait-frontal-lisp-name = Frontal lisp trait-frontal-lisp-desc = You thpeak with a lithp -trait-socialanxiety-name = Social Anxiety +trait-socialanxiety-name = Social anxiety trait-socialanxiety-desc = You are anxious when you speak and stutter. -trait-southern-name = Southern Drawl +trait-southern-name = Southern drawl trait-southern-desc = You have a different way of speakin'. trait-snoring-name = Snoring trait-snoring-desc = You will snore while sleeping. trait-liar-name = Pathological liar -trait-liar-desc = You can hardly bring yourself to tell the truth. Sometimes you lie anyway. \ No newline at end of file +trait-liar-desc = You can hardly bring yourself to tell the truth. Sometimes you lie anyway. + +trait-cowboy-name = Cowboy accent +trait-cowboy-desc = You speak with a distinct cowboy accent! + +trait-italian-name = Italian accent +trait-italian-desc = Mamma mia! You seem to have lived in space italy! \ No newline at end of file diff --git a/Resources/Prototypes/Traits/categories.yml b/Resources/Prototypes/Traits/categories.yml new file mode 100644 index 0000000000..a3621648a4 --- /dev/null +++ b/Resources/Prototypes/Traits/categories.yml @@ -0,0 +1,8 @@ +- type: traitCategory + id: Disabilities + name: trait-category-disabilities + +- type: traitCategory + id: SpeechTraits + name: trait-category-speech + maxTraitPoints: 2 diff --git a/Resources/Prototypes/Traits/disabilities.yml b/Resources/Prototypes/Traits/disabilities.yml index be1e981549..6e0026e44e 100644 --- a/Resources/Prototypes/Traits/disabilities.yml +++ b/Resources/Prototypes/Traits/disabilities.yml @@ -3,6 +3,7 @@ name: trait-blindness-name description: trait-blindness-desc traitGear: WhiteCane + category: Disabilities whitelist: components: - Blindable @@ -14,6 +15,7 @@ name: trait-poor-vision-name description: trait-poor-vision-desc traitGear: ClothingEyesGlasses + category: Disabilities whitelist: components: - Blindable @@ -25,6 +27,7 @@ id: Narcolepsy name: trait-narcolepsy-name description: trait-narcolepsy-desc + category: Disabilities components: - type: Narcolepsy timeBetweenIncidents: 300, 600 @@ -34,13 +37,43 @@ id: Pacifist name: trait-pacifist-name description: trait-pacifist-desc + category: Disabilities components: - type: Pacified +- type: trait + id: Unrevivable + name: trait-unrevivable-name + description: trait-unrevivable-desc + category: Disabilities + components: + - type: Unrevivable + +- type: trait + id: Muted + name: trait-muted-name + description: trait-muted-desc + category: Disabilities + blacklist: + components: + - BorgChassis + components: + - type: Muted + +- type: trait + id: LightweightDrunk + name: trait-lightweight-name + description: trait-lightweight-desc + category: Disabilities + components: + - type: LightweightDrunk + boozeStrengthMultiplier: 2 + - type: trait id: Paracusia name: trait-paracusia-name description: trait-paracusia-desc + category: Disabilities components: - type: Paracusia minTimeBetweenIncidents: 0.1 @@ -49,33 +82,10 @@ sounds: collection: Paracusia -- type: trait - id: Unrevivable - name: trait-unrevivable-name - description: trait-unrevivable-desc - components: - - type: Unrevivable - -- type: trait - id: Muted - name: trait-muted-name - description: trait-muted-desc - blacklist: - components: - - BorgChassis - components: - - type: Muted - -- type: trait - id: FrontalLisp - name: trait-frontal-lisp-name - description: trait-frontal-lisp-desc - components: - - type: FrontalLisp - - type: trait id: Snoring name: trait-snoring-name description: trait-snoring-desc + category: Disabilities components: - type: Snoring diff --git a/Resources/Prototypes/Traits/inconveniences.yml b/Resources/Prototypes/Traits/inconveniences.yml deleted file mode 100644 index 657781d1b5..0000000000 --- a/Resources/Prototypes/Traits/inconveniences.yml +++ /dev/null @@ -1,18 +0,0 @@ -- type: trait - id: LightweightDrunk - name: trait-lightweight-name - description: trait-lightweight-desc - components: - - type: LightweightDrunk - boozeStrengthMultiplier: 2 - -- type: trait - id: SocialAnxiety - name: trait-socialanxiety-name - description: trait-socialanxiety-desc - components: - - type: StutteringAccent - matchRandomProb: 0.1 - fourRandomProb: 0 - threeRandomProb: 0 - cutRandomProb: 0 diff --git a/Resources/Prototypes/Traits/neutral.yml b/Resources/Prototypes/Traits/neutral.yml deleted file mode 100644 index 78d2bba049..0000000000 --- a/Resources/Prototypes/Traits/neutral.yml +++ /dev/null @@ -1,33 +0,0 @@ -- type: trait - id: PirateAccent - name: trait-pirate-accent-name - description: trait-pirate-accent-desc - components: - - type: PirateAccent - -- type: trait - id: Accentless - name: trait-accentless-name - description: trait-accentless-desc - components: - - type: Accentless - removes: - - type: LizardAccent - - type: MothAccent - - type: ReplacementAccent - accent: dwarf - -- type: trait - id: Southern - name: trait-southern-name - description: trait-southern-desc - components: - - type: SouthernAccent - -- type: trait - id: Liar - name: trait-liar-name - description: trait-liar-desc - components: - - type: ReplacementAccent - accent: liar diff --git a/Resources/Prototypes/Traits/speech.yml b/Resources/Prototypes/Traits/speech.yml new file mode 100644 index 0000000000..9448e160b5 --- /dev/null +++ b/Resources/Prototypes/Traits/speech.yml @@ -0,0 +1,88 @@ +# Free + +- type: trait + id: Accentless + name: trait-accentless-name + description: trait-accentless-desc + category: SpeechTraits + components: + - type: Accentless + removes: + - type: LizardAccent + - type: MothAccent + - type: ReplacementAccent + accent: dwarf + +# 1 Cost + +- type: trait + id: SouthernAccent + name: trait-southern-name + description: trait-southern-desc + category: SpeechTraits + cost: 1 + components: + - type: SouthernAccent + +- type: trait + id: PirateAccent + name: trait-pirate-accent-name + description: trait-pirate-accent-desc + category: SpeechTraits + cost: 1 + components: + - type: PirateAccent + +- type: trait + id: CowboyAccent + name: trait-cowboy-name + description: trait-cowboy-desc + category: SpeechTraits + cost: 1 + components: + - type: ReplacementAccent + accent: cowboy + +- type: trait + id: ItalianAccent + name: trait-italian-name + description: trait-italian-desc + category: SpeechTraits + cost: 1 + components: + - type: ReplacementAccent + accent: italian + +- type: trait + id: Liar + name: trait-liar-name + description: trait-liar-desc + category: SpeechTraits + cost: 1 + components: + - type: ReplacementAccent + accent: liar + +# 2 Cost + +- type: trait + id: SocialAnxiety + name: trait-socialanxiety-name + description: trait-socialanxiety-desc + category: SpeechTraits + cost: 2 + components: + - type: StutteringAccent + matchRandomProb: 0.1 + fourRandomProb: 0 + threeRandomProb: 0 + cutRandomProb: 0 + +- type: trait + id: FrontalLisp + name: trait-frontal-lisp-name + description: trait-frontal-lisp-desc + category: SpeechTraits + cost: 2 + components: + - type: FrontalLisp From 3e2eb6cc7a6f82b5aedc853ad74ceeb1e8ce8838 Mon Sep 17 00:00:00 2001 From: RenQ <164364533+ImRenQ@users.noreply.github.com> Date: Mon, 3 Jun 2024 21:48:01 +0300 Subject: [PATCH 037/243] "Death gasp" for a moth (#28409) --- Resources/Audio/Effects/Gasp/attributions.yml | 6 ++++++ Resources/Audio/Effects/Gasp/moth_DeathGasp.ogg | Bin 0 -> 18078 bytes .../Prototypes/SoundCollections/deathgasp.yml | 5 +++++ .../Prototypes/Voice/speech_emote_sounds.yml | 2 +- 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 Resources/Audio/Effects/Gasp/moth_DeathGasp.ogg diff --git a/Resources/Audio/Effects/Gasp/attributions.yml b/Resources/Audio/Effects/Gasp/attributions.yml index fe8f817c5a..8d84b770f3 100644 --- a/Resources/Audio/Effects/Gasp/attributions.yml +++ b/Resources/Audio/Effects/Gasp/attributions.yml @@ -24,3 +24,9 @@ license: "CC-BY-SA-3.0" copyright: "Taken from tgstation at https://github.com/tgstation/tgstation/commit/f7a49c4068f1277e6857baf0892d355f1c055974" source: "https://github.com/tgstation/tgstation/tree/f7a49c4068f1277e6857baf0892d355f1c055974/sound/voice/human" + +- files: + - moth_DeathGasp.ogg + license: "CC-BY-SA-3.0" + copyright: "Taken from tgstation at https://github.com/tgstation/tgstation/commit/948ad3dd5b22803a01cd74c27f37e509dc61395b" + source: "https://github.com/tgstation/tgstation/blob/948ad3dd5b22803a01cd74c27f37e509dc61395b/sound/voice/moth/moth_death.ogg" diff --git a/Resources/Audio/Effects/Gasp/moth_DeathGasp.ogg b/Resources/Audio/Effects/Gasp/moth_DeathGasp.ogg new file mode 100644 index 0000000000000000000000000000000000000000..df23cfa472ac1043b1fc1eacde71a493402f9133 GIT binary patch literal 18078 zcmagG1z1(V*Dt&e-5^~DX*j^4Bt=qDQo2F9yH#33>F$=2?oR1OS_J7vKtQ^_jsD;J zzTf@6=idEn=9xWfX04gEerxSDbG9m&o2vi_z<(}gw11VSG1~DEDu|oClaZzKLlfjh z$$z@IL;hA8APNs7|JU^}@_~ZcTWWy!;{N}24Iutw!~@bbEgdY_6`aheZ7hw{|FWl+ zrsn44=H%q(){MeiVN-`H4fwI{jY+W$Aky~kN{t1^oX2I zX{%8|b5f=Vrz}Cs*8&C9s3A(rg6N&6jK+5PHTl+dqc8?EY%OE}RFIeyO(3E2Lt*rZ zCWsU)K2kdM(-fpRVX%D3`hdaRPwg+qy^|aJ`svP>oQS8p%r)O6FyS>5GICtg>c(ZQ zD0-gGqk>y$|0&S_u7et!iv$Xp37!Pn1N%>~{3Iuk)xTv?1HRxgfmhLl(v^fmm6Rjn z^a{spN(8*)Pb5`T<<-E;RZqjsbk@yv*3C;RDNv`uORFJJ=X;>ud7vR_(0}d=uch;c z`|r}JAwU+LV8R;gGcEb&Rl(1Q0YN1QfXRbOsL^9+VKF(<`DRvSmZ^0X#dWqrRcu35 zSbw{K#zlIV4UlD-lK=m?&9o9s|L?b$ML!E52I{iUk+RQ`UQ(I9&xsB7uZH^p(57O_ zY&}jqubg@MoS%XBE0-4oadIPn;M2#zooVNI3v%Lm58l#H-;fJnJk!3##Hl zBmd{i?=N1!h0vs%C0O~QJ&7{>s}^5yVWin;&$0eV0y})lUZ+m^mU1#8h?BCBRY;gR z`88NIm8r2XDfO=fZAQt~KwH8FlY6p~zoj^jN@Jzu{j0shPem;)oB-R%w$gBV$qd1M zmJ=zcg>#zVSgmgOX$}fkok)w9&7EE8vLWAF0-j=z3u7(j@I* z1pT4ye}|74Xg9;}q`m1MsJ$Xkj0kD+X=15dB5`7~xcXtaRN)aXWeWWjmC2a$Tt56= zIW+(T;QdAMe-(eB{13&sF(E92%+DG5UV)P7<4{@aawRnog>j zPP>`T)tTzldui4E$6@}t&C;yv|L~j#7oio5Sd)rD`ftz4Vvbl9h@g{>q1TFGe3xJz zoKl#au~YaNlM&WT#p)Qgn|I+{f&=G|t`!|j#DZ{6gd8d`(YO2EjXO97? z(>(H%JfLFZ0e}nu=s+U};DtCvN!ST%qH%4~BWDYuWOtJW6yQC<62zu;i4tcLZio@= z!a7-2W+r+e=O50%A3=@XnXfCoA%F&kJtRO00DQUnxw=W6Mx_U`UW{^~r^odRGUdfi zNWq)%WB`S`|bX*mV4U(O zY%@xEkZXfdSw^rRO=VON4H_`Wg_7=%2g{>W8HM#x1`LaDr1^gnX9fW34hZ-s!OUYK z3E(jS%MSEou{^Sb46+20V}z!&1oE@-JR>~3N~&r@1ZvZ&YV*YMYIO|q1oHBKRMiM1 z)riz46XexuZO3>_=Ty~x601$una&e?Yb3ZXJ(1S}A7?uiFT{*0|&(tlXV6aHnz zr#4&1tK_1lq-Lt6s;1>;s&%fWG2BwyjDmbIL}VIf>gs9Q^R`KC1%SMORLXcic*-1iVBPBY>UcDbv|zweJ&cTGA}AC z8LTQVF0b?{;l@3-_q?Q%$)g5AXvF$ZE6qS_~SN(dR zHkI!6I_@=bdgjB1@{4zy3Y(hj_L>}r>uf1=igw#rhB_+-s}A>?IBHyJl+;{*#_6qT zfa|c;chAr{53I5kYA?MW1Q|?R2#(oPHiE;d?I^&u-V;_)LxG4q^AvEVMWv}l#d}qD ztX(C05=v@bdhw=9{CZj>c1!#bIhT1?uTnoMSl+B!!dh>iBA-$>;)62O3 z9DU4szSr^k>DrlVPB|8OUnGbP$OASA2F0>zSP^^$AOH%@7NH6rOAoaRp7Jhrt|}J_ zjq_`n9?CdH87rE&DVAPp)d=bSv{-x@3s~HQGHQ07qKtKR+>EkyW&V`WK6Ng!j3qp7 zLffjacS<|#kue_kR<6mYGU04gU z%F88|mEo!*)Uo55p#(?d;_FDmdI?o*;k_wzz0@G9fd}D#Wo#tHfvl4KV`OAX<71SE z(_$Z3`A5h|!|GyW>84hIRw zwyo1C9*6(6aw98v?~GCDgK(D0_!v<5<`3;K!MF({kX4Lw7&t=5rZGRk3M8Z`ht_WX zaJJz+o6;_MkRc1cA)F%{Pu0PwA=JN~*YihOVn1OrDWVQ~dfyQp%rJPMGLVJh?kHCpNlLLskbSlg#trupMYpb>%70N+)^ zf?$Cs0TRG`FWVn1^5P~Q2+A@$)LCVv;V(?Kv8(Ckf1P z@<|_35AY@Xkbi`M1n{vJBnWO%J``!r5&xZ-{C5rg|7Q_}V4n3z9ei8&l2V}k%gSHk z|9v#J`A?Fb^zZS1aPS~(S-8Sv34j5kOb?9%hysDvwbLK*Z=QsWZPfwZl`sawNg zwKGOxwV?jN2r*+6$~&Wt2`t+=0KQTcjzRIc)t%$Ze{D?zCfgE!wf(R$PzG(0^?(+d z7cwBK9xy@^0Gi=nTR_zRYQuZF^$%JgptkN3oW#4unh(ULB{;I#sJpP|0QsL z!5DxKrgCv0=YMT+_@Iox>0O-i10q4yJP`gu^=}QG=0RP5JJb9d`;ZHQ5Jh~5!gSm8 zIfJw|JS&}uiWdDcfG-vRIDWUNvT2i9{oOm=({KO!Hr2U|v03;ZYRnhT{ zOBV@g9iJNm3!^WGDYh7L@jk!6CbsdAami!xH#z=tqJXOyC3w&@Gw;e5(-_YfvijRSE1?B3J0TQ zmpgmbw{zu@xNjCH%4Q9VoH9O|4#=jbsOZFsOH4c4U(Ok4DhCSo1hrwFFUab*Z#<6Y z;cDfOKmBfDy;gM}Px2#WPTAI>zV2Gza5v=VyMtTy+z96!_wI%$WiRETLuvE;sEPZu zmqmQky+R7&`N2|&da9*rh~D$STh`3;+HzDMsWds8ClG`4wp}&~PN79!)rvm|gFa#d zhZAkLHyr~WR<({P4Y$X%XTB(#0t4NLJgfE4%Fru-;QUNmJ!wDfx_!@Nni11 zpzs<4t3v`;Z?g(Ik+r;2lO?kcQ=Yr$PDJySV`Zy@V`W1FpNIskGB9XJ(c$`wV32Y? zuf7fF_JOm)l=uO4M)OA`1VF?m6jtnn>l17dH`U5IME-jyUcq$8TaT`H_oQsjl?+KT zqa#2!N|p)dCqjx>DhIf5kD%{2GG1;6^9Q<>b~ia^Vcc&m$UJTj%)&EP^URz{=nu!Q zbn1(33m z+XDD`u=RFp_v4^h-QZZa!~s;(F`|TzwR`WAw-D{M(nlUDiuJ9~VOca+-%zcz={oP5 zt*X?s;XTVgH0tWlvfiqP@pJrU5co5-`4kXQAEsVGD7uHN*69atVQt;wIFHVeTRgdV zwWPWT#3>L#qCbyvjLAcBSas8HLW)feLD}I zpA#znm|)Cg+`21yaY4cnplZtcB2=*Fv*lc<3?G9LOkwOp>L?bK^w`;A1YJ1LADZIP z*K!@3S!0>8X*r{UqL^xg#;6e75+F92Jjq(zQdr!sEFHt{O0SW)B_U>xa6@B- zf*!g>cF4ixiCS{%MHBFiyaJL;+9I&l7fQnXbNgU{%I%j0@J4+&rs)m84?>LI8=a$( zd0%`s+X0`4qlfRE(bcRTr@Dgf%x_<1Nrl*Q-YIU z25nv*m7@Hk8bj}+6)u0dEmr1~ut`@c8;!fD&Ijn_#X0=Bx)$E(EiYUDz=Y*yo9fhV z(7YrPc>TWi${U3!Zxxo&xRs$p6>mw;GKh-cZSB$XCK}>jy|s2oS|V+Y^BTeU^!aw{ zp70agaITC>Rxg*d*X`HN@lPS8qUMI9u;5Q%AQ=wc-k-$)qWZrQ+{Ag*t&Y?|fW?&U>jf%5dbpq%!v5rGXlzg8pq=BZ5l$C@!pFi-eZL{<0YO~%HX;lcpY~pL5DfgR zt)=OFkFev;E*s!k-~3gF%5ji28W+GW>H4@!Qq6U{%qNc6vD?9dMYoxy_X3y6e@05! zr4*ogg~e5kxP}^2;L30-aH|)Tx-?H|!x=OQYeaBJ3PS6gplM6;Y4aN9eaF~XkC>>O zAJKmK?k?3VNhtG-GC2}%nMk`o3$cA0c_^5dp>uHl=#go(pJVzvcY2z&X00|SW{5plBy@W&okWQ?7 zC>xt>Fr~?_ezOTXMpQdxR6+)*m_VZ;Td|`Z)s^;7{`i?Qrtj*exMK1rUxr&p0*jW? zxFTrs&tk?M>%ggBs{7%+uuDq1x3qh{;9HTc_d?zU&N@Fm&IoOAOgr~g^kOxW*EBVt zzngw%@zv+Rj4l-qsm~&qzns}ShZdum$~0GdO489B`Y*i#&guNWWWHE{(p@h5u_t!? zT-W1*$|b30V+`d9SfB9QIpU-n?5xf$XsPT4%g2gk`)Udoij^KbM<1E6CQ;l8S)TyJ zQr(`_6Y!z^2B>SFj86#ZTDp9j``~n#qTP?zp>-mu2M4J5haonojE~V)tZVV97)2AA z3~Q&W6}|<<5{W6;;9)-s$U_vuIoa&kaTV4@eDWE-SsNyP*2hNjr?jh4$FiZ%3`D|BH;-g!sNN>Si#x)aYuYPXQtd1R3>ujW!}7r2y5K z&mz%!kB}&wvx@%kp_WRH!o}X`@e|gh9rxSzva}@#G$+UF*5Mfm#qH`xRPeg8jGl9k zj!l!n_ggJQNsqkRE&Txs)XNk1EJRyUPiB`TRo`IV%g+_O(twHRZ9eXvI+hXab4&Nr zuHETldxS+ZO5g7|F^_UDjP-(X%S9e%o7DDgXk8*2H@plc>X69~5pyEW7Gd(xxSDQY zRCNjwA?s6_Bqv#YH$R!CIi-%D&l{c0FVqZYwEPlL_s)tUnWu6NndM7i_DD+V8VU|I zNkPBc^lQ8Uy3Dm#*T&AoVja*Xb~*~?0X^|oc)u>bz|+Pi%zvzudb<^D#n##6E~iu8 zr0=3hZR&XJqL%ynZ=gr8>L~Q?Wz(Pngo+Wzr#I7*jF0H>&uHS=zBP&adNSo(&Eo>x z&##7zHj4TMq(2@;HXqFqZ*#Ara|pji0q#Cxan>TPSXu1s4akU?&%Y;;UMjy4{*oa| z^P14`8LAiq#N<))$mOBUvHXaL&!)d+q-}G?)8^7%4yWqpW_j=H zo~Wg%G)`ARj3j^d1_c3*h0umeDQ;NR-NKVXHQVB9Yn88W{S?n67cXk>xy#iDX)tK` ziw^gNEma-#HmD+Y)GX4qd3!&0iZA$epq13`C~efEXYt(WqPPHWWBdv9nH>0UF=PB8phtb za9yvCGM%?bij?2>S0V3E##R+JoGp^3kP?8eNgVkPov%zJvEKS=dF^}8OWOP_i5S~3 zLcmj@N8~{o_^8IsE%t4--t&-#H7=`Iz07i`o8C(Tq7A{AnN-i9e8wy^`&o!YgY^LD zh^6&}dR~n8^owrt31yYs7x6aavK+vd0B`qU$L}~VJgI}qr$d%^$MDM(T6q*#ZeElF zGThjXnony;3s4y+m8eweUbi2&w)3BuoMg8Q^~xvBw#bAL@b-qQSd9pZm0JH{FkHS3 zJxZ}Tt8Z4fB*&b*wku$pwgN7?gL8eH##3Fprn6^9{mlE7%yFgmq^qO$Kv zJEPvj&ue1~7oP+_+l{zEW8@jl%s8ceJ33S&H{0CN14p*gL4K0Kiznv-GykqJuQE5a zbQyW9!o@^{uKi}mS%Z5ZU!4{PT-o&a9+H;~G?{23#=gwB%gYQ%;h%_2C!HPz7R?{R z4IUlyifudo(&F;1$W2-)Nml3Gls=@ygq#))B}yc~-}ykxGMVy|4*M|q)-N4lZ`1qF z_a%SGn@k%p3X5-Re|DSME6WcVlkf)-=xO@G9+&% zaqj%4cQ>>C(X-vBZrLVtdp_ecHmZGWJ-0Krlt( zrHy?s9d5ISd=bpUn!R%7If2z&Pvcr(;e|(`B>_A?dF45Re|Tr;_C3_<5(DvK((lSPQmn^2>wJ|Xo#l$49N%ORWW>v-^?C{Cs(5Qrt3dfxJt11Qfr{!N;xr;!C`!LOT`3?l$ z29N*qwr;bUFpo{|Dbtq#Mk)%<@Tu zpQokiYL5F5%I~7TaLQ+Wt9zgQevb*8L3lxnA}Ra*AG>t}ob1|n!`mp-)3Y8YwC`!W zL!coc)qE^Z=U0o^EM|h&OL(aL_Q|nbQ3_Q}cEZ68VY)+YS(&d%%g zZu?aM)vU$#f%G6f(Dtf^%MB9$7;Q(I;o0c7pIyE4=UN6>{3@#$Ss&0O*peN7k?nB! z$fKd=qeh<_+R1sxH{K+js$`HSgyfLv$=gR3f6ehK;%XQ8`ed2`Rd$H5Cx_S)Ro+SK z-9gY=y?}HXp9LGLu&iy7`48!?c%?etiIqt6>6HNVH{;PEs<$~AAq~yj&MNldgXV5F zYHEC*PBRp~M^bQ4mvwLVI+`hF<+pAMCdh!78~E?~@!fIRIXYIZhpS zfno;vc1~OFLGUE3{_FSC_XYMt>}wz7FZF-CKmjtCYGAGaFG0X@t!ih^%hLm9r-3mX z##VtV3V`6HGm>}!-JA4kQ*)I&_^cC2TVy?h{GH+1O6t=|%Yp76i3|rrjLwQeHO3p2 z-QDoo+Hm&o6G2oO(5#oAJI4o`GZPv=PWh3VELa%!P3j>naCZZi{@rFhCMT&1BqMg3Gv2dk49}B zAR7J|Yv+OzPZ5S-GeCT{>~CJncbOtZXrL1^=zlhg+`*hGy|R@`m7LbvW1EQp{O4Ew zg#SLPKm@CUG~*iFQ$L1=E4vD+8cT~ybITan80guUm^t9g^lY4P1|~KRI1`MUi=GY6 z1!rVoXJg=ibBnyfimpINQ4kM}m3gb;e@>xf6#Ub_ErUOX6(EU^V37Stg$hmoL0YFL zpf4<77j+(WyRz=_xF$D4qbf+Q2Y$GlaVj$sDPzN2R=L3N*z zVK+r3uChNJkx(cKaf&7}lj=ybisRKv2`j(RmFvaj5n%vydM%I2FKR5ikASw9SdcmdB2jc$ODXG7cF=*)`b3_47WS+v)pPMFd?}?gEvka+ zwvPf^ms8ejfhr+X;e|fVg{FEg88+W~n#U&#V2Gt-@mNm^arVhF>sN1+Ch?yBuc7M=3 z$uq7!Ptk#eg-nxxD=!z?(D(Cn-RZUi5t5;f*J;?I_IEtF?^rE&DCSZ=tfdTtT>PL-N$;B288=rzxxz-P+8y990=^d5-h_{Gb)NK zb8)TqE2?T^U&@~-7`wLqI5ZgP?*9m^mVdWJ326E*gU$@psVm`(avE$6UZ;gW@m-X0 zJ(ZgUe4Cm&k$^4jNpstm%Wxkoz}rnVsRl9jK0Qd)+|pLGWwOIvsGqbgHLKH1CPF4Z zKqbbgiucfYST>2I-t)b~e6?$X^RFL$N3nKRj-rjj6qN}2ucqCS;E@h1vg*7W3Wxe| zlc!Jl6KpPRLtPtUipN9x0+WBPxIMRlOU|c4dWS;bowa4}2FjTOW$!d6(Zr>=lT`=D z2jFI7lqd@eKNy2{XP6WRH7lG^dov1dhlK#H>$nFTf&=WIhW-{pQR@5Y`uy53j^T53g zGyh!r$|UlA%7SIGWCp(PQBR}CbXl_nP+`uiL$192%E2sJKnR?0$<|dHmE~ubR2fCO zRYZ1vVMRkxxjJnNY=F?bhSEG%)SdmFeA9z^tz+@yP`!=+X-xSCnDFXPrFi8}^^Nki z%Aa1;CijVz*XkN_PgPDweHhz}{Mzg5QaYhqr-Z;3-t&;c8_^RXZI_nDBmH+to7T6F=3`zpovrwsse zz(g^LG~hc?tO2&aKi!@FSCIyQHdDgGa`!t@=Eu=kyN@)Pca?8+x-lb9)A;}y>=^2e`PAW zG;UiS8#?Cfr^N$!w5?La452ZS5@@5kAMhC04PvC^8>C{G4SW+9lLe1He1plDuBGDERvFCxRHa6uR|=|Bj;qHd}+dr-cX)|BoqWi|2c2;d%}bN|FW# zQg~UzF)udzmb$N3sG6c$jSE$ZM$%E-b zaPhNeld}D+txC}X9ZX}G*}AWta4=K+JowJl;j(-CL`oXcD-dR$>d6x70)HKKsen!J zSK18IxUq7NJLY|rOROnQQZL`R+zw45Vn4h>0zg2)&_eVQ!mlh{NEv0L@f!=P#b+7~ zb%%F{8P7aN_3T5O7hm=^vmeB~*d&S)@zG@A1*~JPvX=?C@og%i`9i zEYrYwkr|HmvrWLyd7r~EvhP`X|MX_RCyup9EPr@wUeg<0@jg-HlVGhZ0B_KM8?hO3 zl@*dFtn*&6JHvSo^_f?jB)NANyKLnf^zoW~TAr*}w*u*)?ADdnCTV5eJ%y*J8N7rK zT86Fp>nDd*MMu()Rw3G|6)`+hCk7%4*Xh5XjYyC$U~tNv8w!05I-Gmc{qbbQ5A%&w zdk025XEt=ctnHG7*}e~NXCK8PN}7g^icO7sQbTiK2;+4t+^=DP0ma4Do~FEMERgw| z5_vGS8BGbtR08q$WyAxS=$D`J(40F%UkV;bjk1xL3GuFIbCGZB%0l8)h)mpm%(z_| zpIn<nc{trHq(iIu%)d?T~c2s=<(B^OmyHkMT&EW<@iSQ0%>t(UxfIL`AiOtRMSl zj90S9<-dcMn@*^l9NNU_-%QJ@9EAkMqn|Kae=2(GKjp4y%X@yXy+RZoD>3nAnq*bg zuVe64&>8JHN7@+eyAEv<#Qi@&l0$ThfR{@;(lGzKPTI@f<23kt3zo+00>es4C2}C< zHwL#z|G|J-8-t#3_36d(0@4zlpkPCDpdqdc2!g4xL-#ZCeU-b+0cLV9`sXfL(lHSX z&o+^OvX2%+rgU%2G}<^ij7MRi6H!ZF;V4SSRL3~{>M%VERg-a-N8M@o*hdYwDZ(O- z`nbTFbV*lDgqf?N_R<@-ap~D7H?P}|plf*eeS=g_nuzKql1yuj*meU$u;o&457U`4 zKC^YFzU3bd=%M@+-QT0$ggV%?OaCi*jX{dg7B4aqJ-h+}B$yJ8jd`4ruRXtk$3*j7 zp;iCFO`+d;#DEowhwQ-c+GfLlhAJb|0@yaYUnK+*Tii}?g@*Q!P#3hauZUa^%%$Vs zNeqxU$)6}%+O>1s%s5Up7`#x?kg+E>O>i>U!DSM^-ZCpBs4#L}OuK zzMOAklJfxke+4k;pKhryx@LL=EB2HBY<%-R@t3=i{W7)?3b1KCDbpG#)aC1@zg>h3 zOrk6hMsG2j`iBP@;hH-`xH0w)r%IUKFRUm!o!GEOdA`C(!Yn02yuk_;EirFYH^6t0 z?dhVWeb2kxr_tWB`Lktd%6>mo4K2x2nJGo45It&JkDrbd;{GPy zjW{JLlg&IfL<+;_BZ};aDrAauXUl=WC@~vtw;SE}=FUuBZ}7d#&)fUcA8HJOSFS^E zxFI)$m`iC}ZwO+2F?$w=Q*ks9?e5>YJAO~)$Wk+wto(qrr>BnYF#Z12kscJfwnJZX zDC5igp@QryvfMPLG0|ssDGDu%A5YEmekU^To-HpW0ymU~)qDv?_;?}H-$rDX5n@i9 z$M|dHob~HdM)u4Tezw5G^`)mK03@ia#QyKg%frW9rEj_ShsZxcI(p|U+63mMX2!QjgIN5ri!PO6VzHqR`w?^d@N{TR7H=6cUBVHmU(p+8voigBe$~U-4CHh=8fl;PH|D){P9=X=zl0f zX`3%4?ieJZ=SDvvG0EjphXlPK?D_6^4iL|gF0Dnx0)Hr5ha2Wcug1_{aCOaVv_B#M zbmz{-HAY&PH;;O}@~X*~0v73h9Frj|LFCAWdb%@Kx9%Uis%7yKADYTqSZYG3ueNji z917wX+s>RdjCluy%3V!dDvP%}k!+yIL@l8>d-h7b7yOA`AxL|7@^pGQsiS6ndv0Il+R)t;i;HyU}Mvc+wc_4cY;R}HD z!uDFVZ)eB!)V-HbYhd^Oia)f>MoqsFT$9q)CG}#uXbumv+0c0*isakycaL$@TE!cK ze|wV|U7s4euKBw`?|%2vf*=@@&8f}2dECRTjr23r(2pP>HgDF8HZ$g&5&>e}@RH?A z{u&v;105>vV2`*eH$Lk+a|DnbS}x~GczSl8y=QMCt*TX9jYUH`Lz9d^@?;p2V9%H~ z+s^#fB(^tv&w`j`?Pmy~7QH#l;f+pT>T&zFm@6!p0WEs#bb1?}(Ngj#tiiJ>P%Z!GGzMv}KoKh~5%%IfBB77w6@4k34C$=+*FNN(C^ zkVyf;=O<#8`0XZEuc^I`HUmA$(I8mRA+qN2lB>rtFMFt(yu=_=)XProJWs`xg^b(?|hi?mxQEFAJ7%^TZJUykW$v#siAl?XhqkAIF_P$8<2 zKEyP3yBkgr2fL*i7dgMUHfa-+#niDzBe3wZ#R4eG?UE6rqMf1)39P@+9<0%@ zFWu67*9qtTK9X)11#GTf3n@zpMKYnTC*r(QR;=*gMDTC8>9mo3xwHWwr zu?oZbO4iDg=Zs}j`qtlNg zKH8Qhce2~Yvbs>rybtUbBT);ZHawVq8PxCUC(C$eYEz-ORgMJi?+zeqIS4k;V7#|= zq9a1B&tg12^rna*#A!ERG>OKbFX`wOT%;|Bv><;-b;l)^ge`$zYvyvfCv~5nY#r*q zMJ6w~%N)-F+-64K8s;li3movxjd^}FcC{k+JU{o2r=-Ybh>x-!%k<`J=VoZ^Bre;1 z>X(mPgxBvhLtpwSljy_Z(3V%sm56x>=j{ROCPRZB^1nj|-|qiDba?o1f{>9_Opk?y z9nQi4=ip@Gfb(#$!?`(`*}2%5*f`lAP8?wDoSZCd>|E@OoLrpr+}s>YZ0vAG4mLOo z3mYdJ6FWN#<3lf2HaH_I2OErygM)L9Y%E^)jJ)IEq`6TMMZq#28oki$i zWs{2?{$rPd>dyZ0&xK0RHtr-V4AO{_i&fDO_5eeSC(zV&BH((gZq5I*vb59IV^9k;+hQG2ynQ*NF*4Gn;g=2&dIddd3kS? zO%kvc-P-#cF-=g`l)dDx;l7g)jlx@B-2(j=(C^W98+S@(Td?)gSR`zcV19+C+d4%d z60-!_in6Y9^CVl$f8f9#ko>+S$G@jyqKrYNt6pSlJiU>|C~&nUmx&M2g${9bY4-Ub z4)oaYbY-Kyy>d?CvK02dz%73@g&Jv-`pmxz{TZ~J3q7-Y<0g=y5y5%^f{}%wXs!LK zRC%L+SAPnY+p*fa1GJ!S>DI@=YW5Q@n=MyMSDjMmeuY zC5@^NxJR5;;UaDKyKEm(fm|pI|4+%yFRx<>Ctl6u5@}vvQoL2cc7LbA!J%;*A3n8M z0Uy?#Q`QREfOc3B#02WjjCXRKP1h*VJFrv;(dZb$yU*+#$U!IVn_~<{=p7LXIi+zudVBHhu2I zTiwe$_&Uq75Q4FN;E6L{`9`?T3fJW2pU&Cd0E?H-XI~ow3(D0O{;@`w6VcjddapxA0KkIE7V5sUQ`i)VTsKPEr3fN@ z1DX4|wC80kc*0_xah%E<2pIlb1Nq-;KaW_#K5tn=rY z)hHzl?ktgg^Ia!HRsFxNSn#N+TfE;px~{FhByGRA%I1O4PWtz}pn7)TgR)BuEJ(`g z8R^{ox}Vs3=SLh>T6;xjp>03{R4pCW&seDp4cDE)!W{lYmZ)=bjMdvgfEK8$i(+N{ zo@fPiqpxXLc};iCdkB8w_#y`1pGgVasXitbOw&Q25Vi9j_?vqHE4f$v_U)sv@6r%kQ~ zIDeWN^=(Rqr)5JARM5ku^#{;1^q^&r?flGs&qK5gD1oA4@zOj&ZoW7GFE8~;GlMX{4`VHJ$RBT_(GiV&(}^jK zTP7F41&Wz$+c$qj^eK4VZ0+JDIvZfb`zxD0zF@SLdc*0fJBfkX^m}wqD5jwzSHX$U zYnn#VKKqjob_l;GwMO37?d^DFm0VimL<}LfN0$Bikx`7;E$RNT+wHXAoXL(2kGmzY z5*=Pa={s;+M7<5=O1l-MG3;s3zl%&|)1iLs$b#6-h1s}O+P)zQqp#=Rn1);UAujhf z941Ap!mv3l*c@T8RzTI1ns`-W){cpfMnG7L3m%TA$GVn=*bhIT-+0CQ7tTY5 zg&Z$ufB(DKgp1>8`i(6~Q5^T0&&&ZNjSRCv zxM@A~xs%^9KPxo~(%or;7G?PS8T?(5`X#?>h$(Yp;^Dq;D9$tJP?9YZ)n4LDC%;`& zgghYUoCupjt?Eo;!|ln{#PoN&lW}y1dSRh69w2~MpKhr0{q}tpQS5+SxEc77-?G!G zvobD${OZnLes$#|Tr6Vf;j^0A_{YEShNVN*Ru!)7?iirvxnlmcZtX4-v(%pUtt7cM z;wr`Eo7jgzuZq-9l0~JK9utl60wqw%m}MoOn}La9 zr@_jrYd^kQZyyki@u*5Fv4Y6u`EDH?dRD$Ex7P$Sr|jdKYKjj(#)WuOiT#j)Zplzp z_OoO2Lj_N4%o?ldHV)3ugi-f18Lf{as=O&@jnwJ|+smbJ&8b=oIX0}wA12N6Re*i3 zJTdh{?}Z5kp7R%EzebLkpnXK9qx2AI_U(N9>X`VS44vG^KG+X2#@*3_R7A|F(uGme z0R8rqQb(iC;;Ag;8abupx_AMF5IWWA6(Q`vl9*1P zI6XG=Wmr6cbR_zuICOAjMwBWry&&b*YFM`%%DbzW_$XqBCshP(SY$g3{-?$3Bw zU*v3(V0TMe;+J+Ge(>K^k%ea88f2VmAjQm6^tI#zOjyH}sf)$yi$#ZlIEO#9IKF=^ zP(uxXhE^K(HQqDEE_8987Dad9j}m{pTAQ>z+E?LRlE3(UeOo`upVCRMmIEvZ4^<*v z-%K8II2!wLHY8S3=QH`5+SwV#J0z#1=<%8VPKk5Wucf1W96h0oI1E=xu$c95@$SSJ zm|p0NWZT97Vnhwfc2(7G_Oh)J-SdkHhh8iH3H` zqsR(BL7FnF|EG^VowJfaSQ%z68 zInw}P_D#O;(@t_Qu^Y4d?m_$XzGja=-^s$~tQe^U=AMdleYbW!Ss~WH>W$D1EMMOH zM7tr>9eTyLrpYR9J%_fC>3K(!Ga-}+hB~bd+~nGC`S>xi*0p7af7e5}xG&}~o0(fT zY%{<23JokbzMmeB-@7Lofr7h=)9!{u`y<)1s091+|uZ0Xe7Y~9G~jhjC$A<3Q7YI4SphGtA+m8|{2F}E1?O#RoT&;q0u!Q1>t&#zSO zE5KC!hc+hmHbwyE{b+05V9~p5Ngr?de)e{=DPF<(k;kSB_$5%`VH~|nqE))aq}~Du z;R@5lx6cwIecyEO&{NCir4v8R+SQqO`yhea=W^|}Wz2ibz;!D1G%E%NUz#<+H6z%7 z_f56ln}{C9iyN*QNEozSDdWfW-8t@Dj4n;r+eR_SMW+vWr1_P0^B>gr;^{ww-F7+U z>rNLR0Rh70(v1r@eW#)NS%E#R%Jen#b?6EQB8F&Q14z z&z&bmH%x^?LIMj-bk8uShHa#fIc0cRVNytF)Fju~r0-@N{ zlYqW&xVz^RHrY;by%hS?QmDZzW9%~&%z*=uQbo!#4NAofrtgf&6oKAwE|gZD$wbY~ zpN~9ttJPR7q4zG|-g!ocL0z96jfjL8(>H#+oMNhiK+$54dJCO}zg?Jeug=_lZHhbM zVn2C);-NWZKqy6r*C9B8ECaO0* z;cEkcyyz2oT5EOJz3jjiw#MldR{tN&(Wj=(g-Q4|(AH-SrBuTTPfY{YGe&w}`3E3K zEqS2YNE+!PT`FMpO4eRuKbqICVe>-Kg?e=x$2R-)Baf`bN^b+diPn=i)7;VwZQS6g zb2E<-qXQY=>i@% zcjGoj>Hxh+BRUIUtVeFK!QTvkm-S~rep@%TGRiSL4zFbaUO+>WY_}q}x`?0cT)_N@ z0pByTkEDfO3s3fE(@l3x*n--1`W0GTspJFT;3-k_&D}L>yO@9S?GUmNpt#gB^@Jf& zy2O6OuzW``E#JEo7LPjx<3%iiVXCrI%wGj_zj7K2stNj*WofC2`1|6pz*bAtgOO{f zR6cuP7ZS{CGkIk;`4a=2uqohEAxbK3$30U(j0AM(RF?NXDqS2>wROwxiXTY}%?pb| z0grx?UZvWfL$(8PnvRG@v)Qr zJBr|SjIK*Eu}5K}J|tZ)RDP9(xIKlI@n>9}R4mW=t$9lV0Wevc&y{VdqLuXYpf#+m z9VEsl03cOg#a-aZad*u6c^Kc1*5UZ`9j;_uTWeNC7wBp4VukS5S`_H=7ee%ehN#QqK?a;@*G6yYT|Fif4nYq=z#Jn1P70?MeByGsbGhEqiz2ff4EQO6#iRDR{w1Du`z+tCt|w0`G4O-ARSNs_t6CS z-vI#nSn`*T+1NN(8CW=A3~=yUIXDNLfgSt=0?y4u$HBtP#38@~qK--66!*CkSC|Np zM_ktC_4L^-Tg0yXJ6bU38`6oOUXH!U9We&)c0HU2rjUp=5DI1HBYjb;sI_iGHn2(|Ni`$-ZXmU8YF#UUrEAX0)-LB_2w}}>B_|_)MWuY_VpyR6RtQp zs6P*ryl$NHjeg>De{)htpoF@U&gav$u?=7cwQ&0(yj`tLqjRkpcs&3^*0mj&Zs-rq z#s_y&6}QfpId`Pe2Xj0-n2|_QTDT%h4Qcbxt%8Fo?5D4{^yVW3*ZRDarvVol_quK> z`;Xoe&Id8N@(I)021wl{5CNC1+blj&A6V63n-;^{_IIV7Nb^(y)h6gRPzbK~4|XoX znEB_i3MhOiKze;Fv66;H*HNqX&}+G!KaZv~v%^?Y?Xf_eUc0mxxATWEVP2b2@K7#q zubk`5dRhKd+ui57?=mWqmdZnZ0WA;D$S9S)l^u>*uu|8MVg?L-DjBf$!t36C%hdT8 zsBX9K@{}g>mA4={xJwjfOZ4BlxbF3!*NBz8LZH*mLg_d-`$m2~vMk#x=Td%&so&^&^`0_w#D>crc$?Jf8BQfY9k@Tyd|sqx=Rz0Y`odz0CsE&=!6Sct$Q zgbwkd5U)Z8gi8hplRJnw@leE>tBevhEF-nrv0C{LuToXx z8OgOwiQn=0K|9VwUY_hIS9am_PB{x1JkYjGbm)_`XLeJ!u~x=J%JZEbe~D%Jttohf zT7Q!u-y~5_B)DLswU#m%30S9e=k0P^+`z3Th5wol(ah55=vhY5f#IfJ3CnOd=QcX_ zo4&L__(O^LMfY7@d#ewoY)|ahHl~=0ow55w4<0^|cF&zACDcETX_=&vDnjN|L;#pa z&b+w3tG;GxVg6>(;~nk{k(BKs212-WT3J$u?5svO`YKHN>g9+O^1P0EILW{_^XZJ; zFX&aKlZr``0t%t;jV>CZ)Bh7g0lfYgAEf242{B%y7QC6c@>#Se^c)K!)5@#;g;r!X zQw~4^yj}PyUD|jr#&FH(mBr46Is88K9Cz5@!F-9Ih!$WgR-=8dOkw~Kp8flp?-FwH z-|HJ2>(c(kouc9BokKq$sT1@ZuTi;t%%9J%k>qy9@lt>SkRSj3O7-@K8JD`n<+r|P zI%|(DyyYAM{2U7tIf|wKDQ}BR`$+)>z&}0JOr)kmOIk`y%A!6Z^>cxo@52#c^c)Ku z)8t627)mkI#%95tBWY4`B|GXa_68QxwR zg&<))fA@MWW`@P|Kpkif5~02ZlUYF7wbPe*HURVxcZ>wP?& Date: Mon, 3 Jun 2024 20:48:44 +0200 Subject: [PATCH 038/243] Make borgs shown in logprobe (#27788) --- Content.Shared/Access/Systems/AccessReaderSystem.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Content.Shared/Access/Systems/AccessReaderSystem.cs b/Content.Shared/Access/Systems/AccessReaderSystem.cs index 3670e24bd3..efdbff3bb8 100644 --- a/Content.Shared/Access/Systems/AccessReaderSystem.cs +++ b/Content.Shared/Access/Systems/AccessReaderSystem.cs @@ -6,7 +6,9 @@ using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; using Content.Shared.Hands.EntitySystems; using Content.Shared.Inventory; +using Content.Shared.NameIdentifier; using Content.Shared.PDA; +using Content.Shared.Silicons.Borgs.Components; using Content.Shared.StationRecords; using Robust.Shared.Containers; using Robust.Shared.GameStates; @@ -393,6 +395,9 @@ public sealed class AccessReaderSystem : EntitySystem ent.Comp.AccessLog.Dequeue(); string? name = null; + if (TryComp(accessor, out var nameIdentifier)) + name = nameIdentifier.FullIdentifier; + // TODO pass the ID card on IsAllowed() instead of using this expensive method // Set name if the accessor has a card and that card has a name and allows itself to be recorded if (_idCardSystem.TryFindIdCard(accessor, out var idCard) From b1c123efae5fdcc1ba8d968c32ef85321afb939c Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 18:49:52 +0000 Subject: [PATCH 039/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 51 ++++++++++++++----------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f2d3131827..0ae775e193 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,32 +1,4 @@ Entries: -- author: PJB3005 - changes: - - message: Fixed pressure damage calculations to what they were always supposed - to be. This means pressure damage now starts happening at more extreme values - (20 kPa for low pressure damage instead of 50 kPa) and high pressure damage - is scaled different. It also fixed the HUD alerts so that the "warning" alerts - show up before you actually start taking damage. - type: Fix - - message: Air alarms now start complaining about low pressure much earlier, when - the pressure drops so much that you can't breathe oxygen tank. - type: Tweak - id: 6176 - time: '2024-03-18T05:16:31.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26217 -- author: HappyRoach - changes: - - message: The correct magazines now appear in the WT550 safe. - type: Fix - id: 6177 - time: '2024-03-18T06:10:28.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26208 -- author: vanx - changes: - - message: You can now wear most guns in the suit slot, and see them on yourself! - type: Tweak - id: 6178 - time: '2024-03-18T06:16:09.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26152 - author: Dutch-VanDerLinde changes: - message: Romerol now properly works on the dead. @@ -3855,3 +3827,26 @@ id: 6675 time: '2024-06-03T16:12:21.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28270 +- author: TheShuEd + changes: + - message: Added italian and cowboy accents + type: Add + - message: Now you can't choose all the accents at once. + type: Fix + id: 6676 + time: '2024-06-03T18:47:06.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28046 +- author: RenQ + changes: + - message: The moths now have their own "death grasp" + type: Add + id: 6677 + time: '2024-06-03T18:48:01.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28409 +- author: lzk228 + changes: + - message: Borgs ID now will be shown in LogProbe instead of Unknown. + type: Tweak + id: 6678 + time: '2024-06-03T18:48:44.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/27788 From af7f70bade908658416be91c5571cab1160e18ec Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 3 Jun 2024 15:40:46 -0400 Subject: [PATCH 040/243] Add integration test for LocalizedDatasets (#28485) --- .../LocalizedDatasetPrototypeTest.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Content.IntegrationTests/Tests/Localization/LocalizedDatasetPrototypeTest.cs diff --git a/Content.IntegrationTests/Tests/Localization/LocalizedDatasetPrototypeTest.cs b/Content.IntegrationTests/Tests/Localization/LocalizedDatasetPrototypeTest.cs new file mode 100644 index 0000000000..b30c0a370e --- /dev/null +++ b/Content.IntegrationTests/Tests/Localization/LocalizedDatasetPrototypeTest.cs @@ -0,0 +1,35 @@ +using System.Linq; +using Content.Shared.Dataset; +using Robust.Shared.Localization; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests.Localization; + +[TestFixture] +public sealed class LocalizedDatasetPrototypeTest +{ + [Test] + public async Task ValidProtoIdsTest() + { + await using var pair = await PoolManager.GetServerClient(); + + var server = pair.Server; + var protoMan = server.ResolveDependency(); + var localizationMan = server.ResolveDependency(); + + var protos = protoMan.EnumeratePrototypes().OrderBy(p => p.ID); + + // Check each prototype + foreach (var proto in protos) + { + // Check each value in the prototype + foreach (var locId in proto.Values) + { + // Make sure the localization manager has a string for the LocId + Assert.That(localizationMan.HasString(locId), $"LocalizedDataset {proto.ID} with prefix \"{proto.Values.Prefix}\" specifies {proto.Values.Count} entries, but no localized string was found matching {locId}!"); + } + } + + await pair.CleanReturnAsync(); + } +} From 98ab251f9212699a816aaaf31792ffcda768e8c9 Mon Sep 17 00:00:00 2001 From: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:55:43 -0500 Subject: [PATCH 041/243] Remove duplicate entities (#28561) --- .../Entities/Structures/Doors/Airlocks/access.yml | 9 --------- Resources/migration.yml | 3 +++ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml index d798d0c3be..e92da80c4b 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml @@ -73,15 +73,6 @@ containers: board: [ DoorElectronicsHydroponics ] -- type: entity - parent: AirlockCommandLocked - id: AirlockServiceCaptainLocked - suffix: Captain, Locked - components: - - type: ContainerFill - containers: - board: [ DoorElectronicsCaptain ] - - type: entity parent: AirlockExternal id: AirlockExternalLocked diff --git a/Resources/migration.yml b/Resources/migration.yml index cf1a0f7a60..65e914aed6 100644 --- a/Resources/migration.yml +++ b/Resources/migration.yml @@ -345,3 +345,6 @@ CrateJanitorExplosive: ClosetJanitorBombFilled # 2024-05-27 DoorRemoteFirefight: null + +# 2024-06-03 +AirlockServiceCaptainLocked: AirlockCaptainLocked From 5e51a1d73c3a90438969b8100a32e43e44a5a5fb Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Tue, 4 Jun 2024 09:05:51 +1200 Subject: [PATCH 042/243] Add StorageInteractionTest (#28541) --- .../Tests/Chemistry/DispenserTest.cs | 2 +- .../Interaction/ComputerContruction.cs | 10 +- .../Interaction/GrilleWindowConstruction.cs | 13 +- .../Interaction/MachineConstruction.cs | 7 +- .../Construction/Interaction/PanelScrewing.cs | 12 +- .../Interaction/PlaceableDeconstruction.cs | 4 +- .../Interaction/WallConstruction.cs | 9 +- .../Interaction/WindowConstruction.cs | 8 +- .../Construction/Interaction/WindowRepair.cs | 2 +- .../Tests/DoAfter/DoAfterCancellationTests.cs | 39 +- .../EncryptionKeys/RemoveEncryptionKeys.cs | 16 +- .../InteractionTest.EntitySpecifier.cs | 6 +- .../Interaction/InteractionTest.Helpers.cs | 353 +++++++++++++----- .../Tests/Interaction/InteractionTest.cs | 29 +- .../Tests/Payload/ModularGrenadeTests.cs | 14 +- .../Tests/Storage/StorageInteractionTest.cs | 75 ++++ .../Tests/Tiles/TileConstructionTests.cs | 16 +- .../Tests/Weldable/WeldableTests.cs | 2 +- 18 files changed, 427 insertions(+), 190 deletions(-) create mode 100644 Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs diff --git a/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs b/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs index a5449308be..52b7e555a9 100644 --- a/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs +++ b/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs @@ -18,7 +18,7 @@ public sealed class DispenserTest : InteractionTest ToggleNeedPower(); // Insert beaker - await Interact("Beaker"); + await InteractUsing("Beaker"); Assert.That(Hands.ActiveHandEntity, Is.Null); // Open BUI diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/ComputerContruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/ComputerContruction.cs index 5412469ac5..8af5edaf31 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/ComputerContruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/ComputerContruction.cs @@ -16,10 +16,8 @@ public sealed class ComputerConstruction : InteractionTest await StartConstruction(Computer); // Initial interaction (ghost turns into real entity) - await Interact(Steel, 5); - ClientAssertPrototype(ComputerFrame, ClientTarget); - Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()]; - ClientTarget = null; + await InteractUsing(Steel, 5); + ClientAssertPrototype(ComputerFrame, Target); // Perform construction steps await Interact( @@ -41,7 +39,7 @@ public sealed class ComputerConstruction : InteractionTest await StartDeconstruction(ComputerId); // Initial interaction turns id computer into generic computer - await Interact(Screw); + await InteractUsing(Screw); AssertPrototype(ComputerFrame); // Perform deconstruction steps @@ -71,7 +69,7 @@ public sealed class ComputerConstruction : InteractionTest await SpawnTarget(ComputerId); // Initial interaction turns id computer into generic computer - await Interact(Screw); + await InteractUsing(Screw); AssertPrototype(ComputerFrame); // Perform partial deconstruction steps diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/GrilleWindowConstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/GrilleWindowConstruction.cs index 0de39d2757..ef6a7b09ae 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/GrilleWindowConstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/GrilleWindowConstruction.cs @@ -17,17 +17,14 @@ public sealed class GrilleWindowConstruction : InteractionTest { // Construct Grille await StartConstruction(Grille); - await Interact(Rod, 10); - ClientAssertPrototype(Grille, ClientTarget); - - Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()]; + await InteractUsing(Rod, 10); + ClientAssertPrototype(Grille, Target); var grille = Target; // Construct Window await StartConstruction(Window); - await Interact(Glass, 10); - ClientAssertPrototype(Window, ClientTarget); - Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()]; + await InteractUsing(Glass, 10); + ClientAssertPrototype(Window, Target); // Deconstruct Window await Interact(Screw, Wrench); @@ -35,7 +32,7 @@ public sealed class GrilleWindowConstruction : InteractionTest // Deconstruct Grille Target = grille; - await Interact(Cut); + await InteractUsing(Cut); AssertDeleted(); } diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs index f52f820a4c..06874f39ed 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs @@ -14,9 +14,8 @@ public sealed class MachineConstruction : InteractionTest public async Task ConstructProtolathe() { await StartConstruction(MachineFrame); - await Interact(Steel, 5); - ClientAssertPrototype(Unfinished, ClientTarget); - Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()]; + await InteractUsing(Steel, 5); + ClientAssertPrototype(Unfinished, Target); await Interact(Wrench, Cable); AssertPrototype(MachineFrame); await Interact(ProtolatheBoard, Bin1, Bin1, Manipulator1, Manipulator1, Beaker, Beaker, Screw); @@ -51,7 +50,7 @@ public sealed class MachineConstruction : InteractionTest AssertPrototype(MachineFrame); // Change it into an autolathe - await Interact("AutolatheMachineCircuitboard"); + await InteractUsing("AutolatheMachineCircuitboard"); AssertPrototype(MachineFrame); await Interact(Bin1, Bin1, Bin1, Manipulator1, Glass, Screw); AssertPrototype("Autolathe"); diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/PanelScrewing.cs b/Content.IntegrationTests/Tests/Construction/Interaction/PanelScrewing.cs index b6d960e288..636d58bf96 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/PanelScrewing.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/PanelScrewing.cs @@ -19,21 +19,21 @@ public sealed class PanelScrewing : InteractionTest // Open & close panel Assert.That(comp.Open, Is.False); - await Interact(Screw); + await InteractUsing(Screw); Assert.That(comp.Open, Is.True); - await Interact(Screw); + await InteractUsing(Screw); Assert.That(comp.Open, Is.False); // Interrupted DoAfters - await Interact(Screw, awaitDoAfters: false); + await InteractUsing(Screw, awaitDoAfters: false); await CancelDoAfters(); Assert.That(comp.Open, Is.False); - await Interact(Screw); + await InteractUsing(Screw); Assert.That(comp.Open, Is.True); - await Interact(Screw, awaitDoAfters: false); + await InteractUsing(Screw, awaitDoAfters: false); await CancelDoAfters(); Assert.That(comp.Open, Is.True); - await Interact(Screw); + await InteractUsing(Screw); Assert.That(comp.Open, Is.False); } } diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/PlaceableDeconstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/PlaceableDeconstruction.cs index bc0cb9bcef..783c14c068 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/PlaceableDeconstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/PlaceableDeconstruction.cs @@ -13,9 +13,9 @@ public sealed class PlaceableDeconstruction : InteractionTest { await StartDeconstruction("Table"); Assert.That(Comp().IsPlaceable); - await Interact(Wrench); + await InteractUsing(Wrench); AssertPrototype("TableFrame"); - await Interact(Wrench); + await InteractUsing(Wrench); AssertDeleted(); await AssertEntityLookup((Steel, 1), (Rod, 2)); } diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/WallConstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/WallConstruction.cs index 67a2f8025d..292bf0c55a 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/WallConstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/WallConstruction.cs @@ -12,11 +12,10 @@ public sealed class WallConstruction : InteractionTest public async Task ConstructWall() { await StartConstruction(Wall); - await Interact(Steel, 2); + await InteractUsing(Steel, 2); Assert.That(Hands.ActiveHandEntity, Is.Null); - ClientAssertPrototype(Girder, ClientTarget); - Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()]; - await Interact(Steel, 2); + ClientAssertPrototype(Girder, Target); + await InteractUsing(Steel, 2); Assert.That(Hands.ActiveHandEntity, Is.Null); AssertPrototype(WallSolid); } @@ -25,7 +24,7 @@ public sealed class WallConstruction : InteractionTest public async Task DeconstructWall() { await StartDeconstruction(WallSolid); - await Interact(Weld); + await InteractUsing(Weld); AssertPrototype(Girder); await Interact(Wrench, Screw); AssertDeleted(); diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/WindowConstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/WindowConstruction.cs index 46bb892ed9..2ece6b3e39 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/WindowConstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/WindowConstruction.cs @@ -11,8 +11,8 @@ public sealed class WindowConstruction : InteractionTest public async Task ConstructWindow() { await StartConstruction(Window); - await Interact(Glass, 5); - ClientAssertPrototype(Window, ClientTarget); + await InteractUsing(Glass, 5); + ClientAssertPrototype(Window, Target); } [Test] @@ -28,8 +28,8 @@ public sealed class WindowConstruction : InteractionTest public async Task ConstructReinforcedWindow() { await StartConstruction(RWindow); - await Interact(RGlass, 5); - ClientAssertPrototype(RWindow, ClientTarget); + await InteractUsing(RGlass, 5); + ClientAssertPrototype(RWindow, Target); } [Test] diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/WindowRepair.cs b/Content.IntegrationTests/Tests/Construction/Interaction/WindowRepair.cs index abd4bc265b..6eea519af3 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/WindowRepair.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/WindowRepair.cs @@ -24,7 +24,7 @@ public sealed class WindowRepair : InteractionTest Assert.That(comp.Damage.GetTotal(), Is.GreaterThan(FixedPoint2.Zero)); // Repair the entity - await Interact(Weld); + await InteractUsing(Weld); Assert.That(comp.Damage.GetTotal(), Is.EqualTo(FixedPoint2.Zero)); // Validate that we can still deconstruct the entity (i.e., that welding deconstruction is not blocked). diff --git a/Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs b/Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs index 0ebd17d887..1aaf4a5184 100644 --- a/Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs +++ b/Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs @@ -16,31 +16,31 @@ public sealed class DoAfterCancellationTests : InteractionTest public async Task CancelWallDeconstruct() { await StartDeconstruction(WallConstruction.WallSolid); - await Interact(Weld, awaitDoAfters: false); + await InteractUsing(Weld, awaitDoAfters: false); // Failed do-after has no effect await CancelDoAfters(); AssertPrototype(WallConstruction.WallSolid); // Second attempt works fine - await Interact(Weld); + await InteractUsing(Weld); AssertPrototype(WallConstruction.Girder); // Repeat for wrenching interaction AssertAnchored(); - await Interact(Wrench, awaitDoAfters: false); + await InteractUsing(Wrench, awaitDoAfters: false); await CancelDoAfters(); AssertAnchored(); AssertPrototype(WallConstruction.Girder); - await Interact(Wrench); + await InteractUsing(Wrench); AssertAnchored(false); // Repeat for screwdriver interaction. AssertExists(); - await Interact(Screw, awaitDoAfters: false); + await InteractUsing(Screw, awaitDoAfters: false); await CancelDoAfters(); AssertExists(); - await Interact(Screw); + await InteractUsing(Screw); AssertDeleted(); } @@ -48,17 +48,16 @@ public sealed class DoAfterCancellationTests : InteractionTest public async Task CancelWallConstruct() { await StartConstruction(WallConstruction.Wall); - await Interact(Steel, 5, awaitDoAfters: false); + await InteractUsing(Steel, 5, awaitDoAfters: false); await CancelDoAfters(); - await Interact(Steel, 5); - ClientAssertPrototype(WallConstruction.Girder, ClientTarget); - Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()]; - await Interact(Steel, 5, awaitDoAfters: false); + await InteractUsing(Steel, 5); + ClientAssertPrototype(WallConstruction.Girder, Target); + await InteractUsing(Steel, 5, awaitDoAfters: false); await CancelDoAfters(); AssertPrototype(WallConstruction.Girder); - await Interact(Steel, 5); + await InteractUsing(Steel, 5); AssertPrototype(WallConstruction.WallSolid); } @@ -66,11 +65,11 @@ public sealed class DoAfterCancellationTests : InteractionTest public async Task CancelTilePry() { await SetTile(Floor); - await Interact(Pry, awaitDoAfters: false); + await InteractUsing(Pry, awaitDoAfters: false); await CancelDoAfters(); await AssertTile(Floor); - await Interact(Pry); + await InteractUsing(Pry); await AssertTile(Plating); } @@ -78,7 +77,7 @@ public sealed class DoAfterCancellationTests : InteractionTest public async Task CancelRepeatedTilePry() { await SetTile(Floor); - await Interact(Pry, awaitDoAfters: false); + await InteractUsing(Pry, awaitDoAfters: false); await RunTicks(1); Assert.That(ActiveDoAfters.Count(), Is.EqualTo(1)); await AssertTile(Floor); @@ -89,7 +88,7 @@ public sealed class DoAfterCancellationTests : InteractionTest await AssertTile(Floor); // Third do after will work fine - await Interact(Pry); + await InteractUsing(Pry); Assert.That(ActiveDoAfters.Count(), Is.EqualTo(0)); await AssertTile(Plating); } @@ -102,7 +101,7 @@ public sealed class DoAfterCancellationTests : InteractionTest Assert.That(comp.IsWelded, Is.False); - await Interact(Weld, awaitDoAfters: false); + await InteractUsing(Weld, awaitDoAfters: false); await RunTicks(1); Assert.Multiple(() => { @@ -120,7 +119,7 @@ public sealed class DoAfterCancellationTests : InteractionTest }); // Third do after will work fine - await Interact(Weld); + await InteractUsing(Weld); Assert.Multiple(() => { Assert.That(ActiveDoAfters.Count(), Is.EqualTo(0)); @@ -128,7 +127,7 @@ public sealed class DoAfterCancellationTests : InteractionTest }); // Repeat test for un-welding - await Interact(Weld, awaitDoAfters: false); + await InteractUsing(Weld, awaitDoAfters: false); await RunTicks(1); Assert.Multiple(() => { @@ -141,7 +140,7 @@ public sealed class DoAfterCancellationTests : InteractionTest Assert.That(ActiveDoAfters.Count(), Is.EqualTo(0)); Assert.That(comp.IsWelded, Is.True); }); - await Interact(Weld); + await InteractUsing(Weld); Assert.Multiple(() => { Assert.That(ActiveDoAfters.Count(), Is.EqualTo(0)); diff --git a/Content.IntegrationTests/Tests/EncryptionKeys/RemoveEncryptionKeys.cs b/Content.IntegrationTests/Tests/EncryptionKeys/RemoveEncryptionKeys.cs index 9e3dbd8863..f5e8c22242 100644 --- a/Content.IntegrationTests/Tests/EncryptionKeys/RemoveEncryptionKeys.cs +++ b/Content.IntegrationTests/Tests/EncryptionKeys/RemoveEncryptionKeys.cs @@ -22,7 +22,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest }); // Remove the key - await Interact(Screw); + await InteractUsing(Screw); Assert.Multiple(() => { Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(0)); @@ -34,7 +34,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest await AssertEntityLookup(("EncryptionKeyCommon", 1)); // Re-insert a key. - await Interact("EncryptionKeyCentCom"); + await InteractUsing("EncryptionKeyCentCom"); Assert.Multiple(() => { Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(1)); @@ -59,7 +59,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest }); // cannot remove keys without opening panel - await Interact(Pry); + await InteractUsing(Pry); Assert.Multiple(() => { Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.GreaterThan(0)); @@ -68,7 +68,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest }); // Open panel - await Interact(Screw); + await InteractUsing(Screw); Assert.Multiple(() => { Assert.That(panel.Open, Is.True); @@ -79,7 +79,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest }); // Now remove the keys - await Interact(Pry); + await InteractUsing(Pry); Assert.Multiple(() => { Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(0)); @@ -87,7 +87,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest }); // Reinsert a key - await Interact("EncryptionKeyCentCom"); + await InteractUsing("EncryptionKeyCentCom"); Assert.Multiple(() => { Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(1)); @@ -97,7 +97,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest }); // Remove it again - await Interact(Pry); + await InteractUsing(Pry); Assert.Multiple(() => { Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(0)); @@ -106,7 +106,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest // Prying again will start deconstructing the machine. AssertPrototype("TelecomServerFilled"); - await Interact(Pry); + await InteractUsing(Pry); AssertPrototype("MachineFrame"); } } diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs index 37dca72137..053152dbe1 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs @@ -33,7 +33,7 @@ public abstract partial class InteractionTest public int Quantity; /// - /// If true, a check has been performed to see if the prototype ia an entity prototype with a stack component, + /// If true, a check has been performed to see if the prototype is an entity prototype with a stack component, /// in which case the specifier was converted into a stack-specifier /// public bool Converted; @@ -100,7 +100,7 @@ public abstract partial class InteractionTest if (!ProtoMan.TryIndex(spec.Prototype, out var entProto)) { - Assert.Fail($"Unkown prototype: {spec.Prototype}"); + Assert.Fail($"Unknown prototype: {spec.Prototype}"); return default; } @@ -120,7 +120,7 @@ public abstract partial class InteractionTest /// /// Convert an entity-uid to a matching entity specifier. Useful when doing entity lookups & checking that the - /// right quantity of entities/materials werre produced. Returns null if passed an entity with a null prototype. + /// right quantity of entities/materials were produced. Returns null if passed an entity with a null prototype. /// protected EntitySpecifier? ToEntitySpecifier(EntityUid uid) { diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs index 19ca83a971..f4826cb249 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs @@ -14,6 +14,7 @@ using Content.Shared.Construction.Prototypes; using Content.Shared.Gravity; using Content.Shared.Item; using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.GameObjects; using Robust.Shared.Input; @@ -44,8 +45,9 @@ public abstract partial class InteractionTest return; var comp = CEntMan.GetComponent(clientTarget!.Value); - ClientTarget = clientTarget; - ConstructionGhostId = comp.Owner.Id; + Target = CEntMan.GetNetEntity(clientTarget.Value); + Assert.That(Target.Value.IsClientSide()); + ConstructionGhostId = clientTarget.Value.GetHashCode(); }); await RunTicks(1); @@ -129,21 +131,20 @@ public abstract partial class InteractionTest /// /// Place an entity prototype into the players hand. Deletes any currently held entity. /// - /// - /// Automatically enables welders. - /// - protected async Task PlaceInHands(string id, int quantity = 1, bool enableWelder = true) + /// The entity or stack prototype to spawn and place into the users hand + /// The number of entities to spawn. If the prototype is a stack, this sets the stack count. + /// Whether or not to automatically enable any toggleable items + protected async Task PlaceInHands(string id, int quantity = 1, bool enableToggleable = true) { - return await PlaceInHands((id, quantity), enableWelder); + return await PlaceInHands((id, quantity), enableToggleable); } /// /// Place an entity prototype into the players hand. Deletes any currently held entity. /// - /// - /// Automatically enables welders. - /// - protected async Task PlaceInHands(EntitySpecifier entity, bool enableWelder = true) + /// The entity type & quantity to spawn and place into the users hand + /// Whether or not to automatically enable any toggleable items + protected async Task PlaceInHands(EntitySpecifier entity, bool enableToggleable = true) { if (Hands.ActiveHand == null) { @@ -165,7 +166,7 @@ public abstract partial class InteractionTest Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHand, false, false, Hands)); // turn on welders - if (enableWelder && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated) + if (enableToggleable && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated) { Assert.That(ItemToggleSys.TryActivate(item, playerEnt, itemToggle: itemToggle)); } @@ -173,7 +174,7 @@ public abstract partial class InteractionTest await RunTicks(1); Assert.That(Hands.ActiveHandEntity, Is.EqualTo(item)); - if (enableWelder && itemToggle != null) + if (enableToggleable && itemToggle != null) Assert.That(itemToggle.Activated); return SEntMan.GetNetEntity(item); @@ -254,21 +255,20 @@ public abstract partial class InteractionTest /// /// Place an entity prototype into the players hand and interact with the given entity (or target position) /// - /// - /// Empty strings imply empty hands. - /// - protected async Task Interact(string id, int quantity = 1, bool shouldSucceed = true, bool awaitDoAfters = true) + /// The entity or stack prototype to spawn and place into the users hand + /// The number of entities to spawn. If the prototype is a stack, this sets the stack count. + /// Whether or not to wait for any do-afters to complete + protected async Task InteractUsing(string id, int quantity = 1, bool awaitDoAfters = true) { - await Interact((id, quantity), shouldSucceed, awaitDoAfters); + await InteractUsing((id, quantity), awaitDoAfters); } /// - /// Place an entity prototype into the players hand and interact with the given entity (or target position) + /// Place an entity prototype into the players hand and interact with the given entity (or target position). /// - /// - /// Empty strings imply empty hands. - /// - protected async Task Interact(EntitySpecifier entity, bool shouldSucceed = true, bool awaitDoAfters = true) + /// The entity type & quantity to spawn and place into the users hand + /// Whether or not to wait for any do-afters to complete + protected async Task InteractUsing(EntitySpecifier entity, bool awaitDoAfters = true) { // For every interaction, we will also examine the entity, just in case this breaks something, somehow. // (e.g., servers attempt to assemble construction examine hints). @@ -278,38 +278,80 @@ public abstract partial class InteractionTest } await PlaceInHands(entity); - await Interact(shouldSucceed, awaitDoAfters); + await Interact(awaitDoAfters); } /// /// Interact with an entity using the currently held entity. /// - protected async Task Interact(bool shouldSucceed = true, bool awaitDoAfters = true) + /// Whether or not to wait for any do-afters to complete + protected async Task Interact(bool awaitDoAfters = true) { - var clientTarget = ClientTarget; - - if ((clientTarget?.IsValid() != true || CEntMan.Deleted(clientTarget)) && (Target == null || Target.Value.IsValid())) + if (Target == null || !Target.Value.IsClientSide()) { - await Server.WaitPost(() => InteractSys.UserInteraction(SEntMan.GetEntity(Player), SEntMan.GetCoordinates(TargetCoords), SEntMan.GetEntity(Target))); - await RunTicks(1); + await Interact(Target, TargetCoords, awaitDoAfters); + return; } - else - { - // The entity is client-side, so attempt to start construction - var clientEnt = ClientTarget ?? CEntMan.GetEntity(Target); - await Client.WaitPost(() => CConSys.TryStartConstruction(clientEnt!.Value)); - await RunTicks(5); - } + // The target is a client-side entity, so we will just attempt to start construction under the assumption that + // it is a construction ghost. + + await Client.WaitPost(() => CConSys.TryStartConstruction(CTarget!.Value)); + await RunTicks(5); if (awaitDoAfters) - await AwaitDoAfters(shouldSucceed); + await AwaitDoAfters(); - await CheckTargetChange(shouldSucceed && awaitDoAfters); + await CheckTargetChange(); + } + + /// + protected async Task Interact(NetEntity? target, NetCoordinates coordinates, bool awaitDoAfters = true) + { + Assert.That(SEntMan.TryGetEntity(target, out var sTarget) || target == null); + var coords = SEntMan.GetCoordinates(coordinates); + Assert.That(coords.IsValid(SEntMan)); + await Interact(sTarget, coords, awaitDoAfters); } /// - /// Variant of that performs several interactions using different entities. + /// Interact with an entity using the currently held entity. + /// + protected async Task Interact(EntityUid? target, EntityCoordinates coordinates, bool awaitDoAfters = true) + { + Assert.That(SEntMan.TryGetEntity(Player, out var player)); + + await Server.WaitPost(() => InteractSys.UserInteraction(player!.Value, coordinates, target)); + await RunTicks(1); + + if (awaitDoAfters) + await AwaitDoAfters(); + + await CheckTargetChange(); + } + + /// + /// Activate an entity. + /// + protected async Task Activate(NetEntity? target = null, bool awaitDoAfters = true) + { + target ??= Target; + Assert.That(target, Is.Not.Null); + Assert.That(SEntMan.TryGetEntity(target!.Value, out var sTarget)); + Assert.That(SEntMan.TryGetEntity(Player, out var player)); + + await Server.WaitPost(() => InteractSys.InteractionActivate(player!.Value, sTarget!.Value)); + await RunTicks(1); + + if (awaitDoAfters) + await AwaitDoAfters(); + + await CheckTargetChange(); + } + + /// + /// Variant of that performs several interactions using different entities. + /// Useful for quickly finishing multiple construction steps. /// /// /// Empty strings imply empty hands. @@ -318,7 +360,7 @@ public abstract partial class InteractionTest { foreach (var spec in specifiers) { - await Interact(spec); + await InteractUsing(spec); } } @@ -338,7 +380,7 @@ public abstract partial class InteractionTest /// /// Wait for any currently active DoAfters to finish. /// - protected async Task AwaitDoAfters(bool shouldSucceed = true, int maxExpected = 1) + protected async Task AwaitDoAfters(int maxExpected = 1) { if (!ActiveDoAfters.Any()) return; @@ -353,13 +395,12 @@ public abstract partial class InteractionTest await RunTicks(10); } - if (!shouldSucceed) - return; - foreach (var doAfter in doAfters) { Assert.That(!doAfter.Cancelled); } + + await RunTicks(5); } /// @@ -398,39 +439,28 @@ public abstract partial class InteractionTest /// Check if the test's target entity has changed. E.g., construction interactions will swap out entities while /// a structure is being built. /// - protected async Task CheckTargetChange(bool shouldSucceed) + protected async Task CheckTargetChange() { if (Target == null) return; - var target = Target.Value; + var originalTarget = Target.Value; await RunTicks(5); - if (ClientTarget != null && CEntMan.IsClientSide(ClientTarget.Value)) + if (Target.Value.IsClientSide() && CTestSystem.Ghosts.TryGetValue(ConstructionGhostId, out var newWeh)) { - Assert.That(CEntMan.Deleted(ClientTarget.Value), Is.EqualTo(shouldSucceed), - $"Construction ghost was {(shouldSucceed ? "not deleted" : "deleted")}."); - - if (shouldSucceed) - { - Assert.That(CTestSystem.Ghosts.TryGetValue(ConstructionGhostId, out var newWeh), - $"Failed to get construction entity from ghost Id"); - - await Client.WaitPost(() => CLogger.Debug($"Construction ghost {ConstructionGhostId} became entity {newWeh}")); - Target = newWeh; - } + CLogger.Debug($"Construction ghost {ConstructionGhostId} became entity {newWeh}"); + Target = newWeh; } if (STestSystem.EntChanges.TryGetValue(Target.Value, out var newServerWeh)) { - await Server.WaitPost( - () => SLogger.Debug($"Construction entity {Target.Value} changed to {newServerWeh}")); - + SLogger.Debug($"Construction entity {Target.Value} changed to {newServerWeh}"); Target = newServerWeh; } - if (Target != target) - await CheckTargetChange(shouldSucceed); + if (Target != originalTarget) + await CheckTargetChange(); } #region Asserts @@ -444,16 +474,10 @@ public abstract partial class InteractionTest return; } - var meta = SEntMan.GetComponent(SEntMan.GetEntity(target.Value)); + var meta = CEntMan.GetComponent(CEntMan.GetEntity(target.Value)); Assert.That(meta.EntityPrototype?.ID, Is.EqualTo(prototype)); } - protected void ClientAssertPrototype(string? prototype, EntityUid? target) - { - var netEnt = CTestSystem.Ghosts[target.GetHashCode()]; - AssertPrototype(prototype, netEnt); - } - protected void AssertPrototype(string? prototype, NetEntity? target = null) { target ??= Target; @@ -699,6 +723,8 @@ public abstract partial class InteractionTest protected IEnumerable ActiveDoAfters => DoAfters.DoAfters.Values.Where(x => !x.Cancelled && !x.Completed); + #region Component + /// /// Convenience method to get components on the target. Returns SERVER-SIDE components. /// @@ -708,9 +734,23 @@ public abstract partial class InteractionTest if (target == null) Assert.Fail("No target specified"); - return SEntMan.GetComponent(SEntMan.GetEntity(target!.Value)); + return SEntMan.GetComponent(ToServer(target!.Value)); } + /// + protected bool TryComp(NetEntity? target, [NotNullWhen(true)] out T? comp) where T : IComponent + { + return SEntMan.TryGetComponent(ToServer(target), out comp); + } + + /// + protected bool TryComp([NotNullWhen(true)] out T? comp) where T : IComponent + { + return SEntMan.TryGetComponent(STarget, out comp); + } + + #endregion + /// /// Set the tile at the target position to some prototype. /// @@ -833,23 +873,70 @@ public abstract partial class InteractionTest return true; } + protected bool IsUiOpen(Enum key) + { + if (!TryComp(Player, out UserInterfaceUserComponent? user)) + return false; + + foreach (var keys in user.OpenInterfaces.Values) + { + if (keys.Contains(key)) + return true; + } + + return false; + } + #endregion #region UI /// - /// Presses and releases a button on some client-side window. Will fail if the button cannot be found. + /// Attempts to find, and then presses and releases a control on some client-side window. + /// Will fail if the control cannot be found. /// - protected async Task ClickControl(string name) where TWindow : BaseWindow + protected async Task ClickControl(string name, BoundKeyFunction? function = null) + where TWindow : BaseWindow + where TControl : Control { - await ClickControl(GetControl(name)); + var window = GetWindow(); + var control = GetControlFromField(name, window); + await ClickControl(control, function); } /// - /// Simulates a click and release at the center of some UI Constrol. + /// Attempts to find, and then presses and releases a control on some client-side widget. + /// Will fail if the control cannot be found. /// - protected async Task ClickControl(Control control) + protected async Task ClickWidgetControl(string name, BoundKeyFunction? function = null) + where TWidget : UIWidget, new() + where TControl : Control { + var widget = GetWidget(); + var control = GetControlFromField(name, widget); + await ClickControl(control, function); + } + + /// + protected async Task ClickControl(string name, BoundKeyFunction? function = null) + where TWindow : BaseWindow + { + await ClickControl(name, function); + } + + /// + protected async Task ClickWidgetControl(string name, BoundKeyFunction? function = null) + where TWidget : UIWidget, new() + { + await ClickWidgetControl(name, function); + } + + /// + /// Simulates a click and release at the center of some UI control. + /// + protected async Task ClickControl(Control control, BoundKeyFunction? function = null) + { + function ??= EngineKeyFunctions.UIClick; var screenCoords = new ScreenCoordinates( control.GlobalPixelPosition + control.PixelSize / 2, control.Window?.Id ?? default); @@ -858,7 +945,7 @@ public abstract partial class InteractionTest var relativePixelPos = screenCoords.Position - control.GlobalPixelPosition; var args = new GUIBoundKeyEventArgs( - EngineKeyFunctions.UIClick, + function.Value, BoundKeyState.Down, screenCoords, default, @@ -869,7 +956,7 @@ public abstract partial class InteractionTest await RunTicks(1); args = new GUIBoundKeyEventArgs( - EngineKeyFunctions.UIClick, + function.Value, BoundKeyState.Up, screenCoords, default, @@ -881,31 +968,26 @@ public abstract partial class InteractionTest } /// - /// Attempts to find a control on some client-side window. Will fail if the control cannot be found. + /// Attempt to retrieve a control by looking for a field on some other control. /// - protected TControl GetControl(string name) - where TWindow : BaseWindow + /// + /// Will fail if the control cannot be found. + /// + protected TControl GetControlFromField(string name, Control parent) where TControl : Control - { - var control = GetControl(name); - Assert.That(control.GetType().IsAssignableTo(typeof(TControl))); - return (TControl) control; - } - - protected Control GetControl(string name) where TWindow : BaseWindow { const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; - var field = typeof(TWindow).GetField(name, flags); - var prop = typeof(TWindow).GetProperty(name, flags); + var parentType = parent.GetType(); + var field = parentType.GetField(name, flags); + var prop = parentType.GetProperty(name, flags); if (field == null && prop == null) { - Assert.Fail($"Window {typeof(TWindow).Name} does not have a field or property named {name}"); + Assert.Fail($"Window {parentType.Name} does not have a field or property named {name}"); return default!; } - var window = GetWindow(); - var fieldOrProp = field?.GetValue(window) ?? prop?.GetValue(window); + var fieldOrProp = field?.GetValue(parent) ?? prop?.GetValue(parent); if (fieldOrProp is not Control control) { @@ -913,7 +995,59 @@ public abstract partial class InteractionTest return default!; } - return control; + Assert.That(control.GetType().IsAssignableTo(typeof(TControl))); + return (TControl) control; + } + + /// + /// Attempt to retrieve a control that matches some predicate by iterating through a control's children. + /// + /// + /// Will fail if the control cannot be found. + /// + protected TControl GetControlFromChildren(Func predicate, Control parent, bool recursive = true) + where TControl : Control + { + if (TryGetControlFromChildren(predicate, parent, out var control, recursive)) + return control; + + Assert.Fail($"Failed to find a {nameof(TControl)} that satisfies the predicate in {parent.Name}"); + return default!; + } + + /// + /// Attempt to retrieve a control of a given type by iterating through a control's children. + /// + protected TControl GetControlFromChildren(Control parent, bool recursive = false) + where TControl : Control + { + return GetControlFromChildren(static _ => true, parent, recursive); + } + + /// + /// Attempt to retrieve a control that matches some predicate by iterating through a control's children. + /// + protected bool TryGetControlFromChildren( + Func predicate, + Control parent, + [NotNullWhen(true)] out TControl? control, + bool recursive = true) + where TControl : Control + { + foreach (var ctrl in parent.Children) + { + if (ctrl is TControl cast && predicate(cast)) + { + control = cast; + return true; + } + + if (recursive && TryGetControlFromChildren(predicate, ctrl, out control)) + return true; + } + + control = null; + return false; } /// @@ -944,7 +1078,6 @@ public abstract partial class InteractionTest return window != null; } - /// /// Attempts to find a currently open client-side window. /// @@ -962,6 +1095,34 @@ public abstract partial class InteractionTest return window != null; } + + /// + /// Attempts to find client-side UI widget. + /// + protected UIWidget GetWidget() + where TWidget : UIWidget, new() + { + if (TryFindWidget(out TWidget? widget)) + return widget; + + Assert.Fail($"Could not find a {typeof(TWidget).Name} widget"); + return default!; + } + + /// + /// Attempts to find client-side UI widget. + /// + private bool TryFindWidget([NotNullWhen(true)] out TWidget? uiWidget) + where TWidget : UIWidget, new() + { + uiWidget = null; + var screen = UiMan.ActiveScreen; + if (screen == null) + return false; + + return screen.TryGetWidget(out uiWidget); + } + #endregion #region Power diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs index e5f794feaa..089addfaef 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs @@ -1,8 +1,10 @@ #nullable enable +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; using Content.Client.Construction; using Content.Client.Examine; +using Content.Client.Gameplay; using Content.IntegrationTests.Pair; using Content.Server.Body.Systems; using Content.Server.Hands.Systems; @@ -24,6 +26,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.UnitTesting; using Content.Shared.Item.ItemToggle; +using Robust.Client.State; namespace Content.IntegrationTests.Tests.Interaction; @@ -64,15 +67,12 @@ public abstract partial class InteractionTest /// The player entity that performs all these interactions. Defaults to an admin-observer with 1 hand. /// protected NetEntity Player; - - protected EntityUid SPlayer => ToServer(Player); - protected EntityUid CPlayer => ToClient(Player); + protected EntityUid SPlayer; + protected EntityUid CPlayer; protected ICommonSession ClientSession = default!; protected ICommonSession ServerSession = default!; - public EntityUid? ClientTarget; - /// /// The current target entity. This is the default entity for various helper functions. /// @@ -108,6 +108,7 @@ public abstract partial class InteractionTest protected InteractionTestSystem STestSystem = default!; protected SharedTransformSystem Transform = default!; protected ISawmill SLogger = default!; + protected SharedUserInterfaceSystem SUiSys = default!; // CLIENT dependencies protected IEntityManager CEntMan = default!; @@ -119,6 +120,7 @@ public abstract partial class InteractionTest protected ExamineSystem ExamineSys = default!; protected InteractionTestSystem CTestSystem = default!; protected ISawmill CLogger = default!; + protected SharedUserInterfaceSystem CUiSys = default!; // player components protected HandsComponent Hands = default!; @@ -168,6 +170,7 @@ public abstract partial class InteractionTest STestSystem = SEntMan.System(); Stack = SEntMan.System(); SLogger = Server.ResolveDependency().RootSawmill; + SUiSys = Client.System(); // client dependencies CEntMan = Client.ResolveDependency(); @@ -179,6 +182,7 @@ public abstract partial class InteractionTest CConSys = CEntMan.System(); ExamineSys = CEntMan.System(); CLogger = Client.ResolveDependency().RootSawmill; + CUiSys = Client.System(); // Setup map. await Pair.CreateTestMap(); @@ -204,15 +208,16 @@ public abstract partial class InteractionTest old = cPlayerMan.LocalEntity; Player = SEntMan.GetNetEntity(SEntMan.SpawnEntity(PlayerPrototype, SEntMan.GetCoordinates(PlayerCoords))); - var serverPlayerEnt = SEntMan.GetEntity(Player); - Server.PlayerMan.SetAttachedEntity(ServerSession, serverPlayerEnt); - Hands = SEntMan.GetComponent(serverPlayerEnt); - DoAfters = SEntMan.GetComponent(serverPlayerEnt); + SPlayer = SEntMan.GetEntity(Player); + Server.PlayerMan.SetAttachedEntity(ServerSession, SPlayer); + Hands = SEntMan.GetComponent(SPlayer); + DoAfters = SEntMan.GetComponent(SPlayer); }); // Check player got attached. await RunTicks(5); - Assert.That(CEntMan.GetNetEntity(cPlayerMan.LocalEntity), Is.EqualTo(Player)); + CPlayer = ToClient(Player); + Assert.That(cPlayerMan.LocalEntity, Is.EqualTo(CPlayer)); // Delete old player entity. await Server.WaitPost(() => @@ -235,6 +240,10 @@ public abstract partial class InteractionTest } }); + // Change UI state to in-game. + var state = Client.ResolveDependency(); + await Client.WaitPost(() => state.RequestStateChange()); + // Final player asserts/checks. await Pair.ReallyBeIdle(5); Assert.Multiple(() => diff --git a/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs b/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs index 70179fdec1..4db79373d3 100644 --- a/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs +++ b/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs @@ -22,32 +22,32 @@ public sealed class ModularGrenadeTests : InteractionTest Target = SEntMan.GetNetEntity(await FindEntity("ModularGrenade")); await Drop(); - await Interact(Cable); + await InteractUsing(Cable); // Insert & remove trigger AssertComp(false); - await Interact(Trigger); + await InteractUsing(Trigger); AssertComp(); await FindEntity(Trigger, LookupFlags.Uncontained, shouldSucceed: false); - await Interact(Pry); + await InteractUsing(Pry); AssertComp(false); // Trigger was dropped to floor, not deleted. await FindEntity(Trigger, LookupFlags.Uncontained); // Re-insert - await Interact(Trigger); + await InteractUsing(Trigger); AssertComp(); // Insert & remove payload. - await Interact(Payload); + await InteractUsing(Payload); await FindEntity(Payload, LookupFlags.Uncontained, shouldSucceed: false); - await Interact(Pry); + await InteractUsing(Pry); var ent = await FindEntity(Payload, LookupFlags.Uncontained); await Delete(ent); // successfully insert a second time - await Interact(Payload); + await InteractUsing(Payload); ent = await FindEntity(Payload); var sys = SEntMan.System(); Assert.That(sys.IsEntityInContainer(ent)); diff --git a/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs b/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs new file mode 100644 index 0000000000..34402dd5e6 --- /dev/null +++ b/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs @@ -0,0 +1,75 @@ +using Content.Client.UserInterface.Systems.Hotbar.Widgets; +using Content.Client.UserInterface.Systems.Storage.Controls; +using Content.IntegrationTests.Tests.Interaction; +using Content.Shared.Input; +using Content.Shared.PDA; +using Content.Shared.Storage; +using Robust.Client.UserInterface; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; + +namespace Content.IntegrationTests.Tests.Storage; + +public sealed class StorageInteractionTest : InteractionTest +{ + /// + /// Check that players can interact with items in storage if the storage UI is open + /// + [Test] + public async Task UiInteractTest() + { + var sys = Server.System(); + + await SpawnTarget("ClothingBackpack"); + var backpack = ToServer(Target); + + // Initially no BUI is open. + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.False); + Assert.That(IsUiOpen(PdaUiKey.Key), Is.False); + + // Activating the backpack opens the UI + await Activate(); + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True); + Assert.That(IsUiOpen(PdaUiKey.Key), Is.False); + + // Pick up a PDA + var pda = await PlaceInHands("PassengerPDA"); + var sPda = ToServer(pda); + Assert.That(sys.IsEntityInContainer(sPda), Is.True); + Assert.That(sys.TryGetContainingContainer((sPda, null), out var container)); + Assert.That(container!.Owner, Is.EqualTo(SPlayer)); + + // Insert the PDA into the backpack + await Interact(); + Assert.That(sys.TryGetContainingContainer((sPda, null), out container)); + Assert.That(container!.Owner, Is.EqualTo(backpack)); + + // Use "e" / ActivateInWorld to open the PDA UI while it is still in the backpack. + var ctrl = GetStorageControl(pda); + await ClickControl(ctrl, ContentKeyFunctions.ActivateItemInWorld); + await RunTicks(10); + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True); + Assert.That(IsUiOpen(PdaUiKey.Key), Is.True); + + // Click on the pda to pick it up and remove it from the backpack. + await ClickControl(ctrl, ContentKeyFunctions.MoveStoredItem); + await RunTicks(10); + Assert.That(sys.TryGetContainingContainer((sPda, null), out container)); + Assert.That(container!.Owner, Is.EqualTo(SPlayer)); + + // UIs should still be open + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True); + Assert.That(IsUiOpen(PdaUiKey.Key), Is.True); + } + + /// + /// Retrieve the control that corresponds to the given entity in the currently open storage UI. + /// + private ItemGridPiece GetStorageControl(NetEntity target) + { + var uid = ToClient(target); + var hotbar = GetWidget(); + var storageContainer = GetControlFromField(nameof(HotbarGui.StorageContainer), hotbar); + return GetControlFromChildren(c => c.Entity == uid, storageContainer); + } +} diff --git a/Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs b/Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs index 083e817d69..6ea8b6882a 100644 --- a/Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs +++ b/Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs @@ -15,10 +15,10 @@ public sealed class TileConstructionTests : InteractionTest await AssertTile(Plating, PlayerCoords); AssertGridCount(1); await SetTile(null); - await Interact(Rod); + await InteractUsing(Rod); await AssertTile(Lattice); Assert.That(Hands.ActiveHandEntity, Is.Null); - await Interact(Cut); + await InteractUsing(Cut); await AssertTile(null); await AssertEntityLookup((Rod, 1)); AssertGridCount(1); @@ -43,14 +43,14 @@ public sealed class TileConstructionTests : InteractionTest // Place Lattice var oldPos = TargetCoords; TargetCoords = SEntMan.GetNetCoordinates(new EntityCoordinates(MapData.MapUid, 1, 0)); - await Interact(Rod); + await InteractUsing(Rod); TargetCoords = oldPos; await AssertTile(Lattice); AssertGridCount(1); // Cut lattice Assert.That(Hands.ActiveHandEntity, Is.Null); - await Interact(Cut); + await InteractUsing(Cut); await AssertTile(null); AssertGridCount(0); @@ -76,25 +76,25 @@ public sealed class TileConstructionTests : InteractionTest // Space -> Lattice var oldPos = TargetCoords; TargetCoords = SEntMan.GetNetCoordinates(new EntityCoordinates(MapData.MapUid, 1, 0)); - await Interact(Rod); + await InteractUsing(Rod); TargetCoords = oldPos; await AssertTile(Lattice); AssertGridCount(1); // Lattice -> Plating - await Interact(Steel); + await InteractUsing(Steel); Assert.That(Hands.ActiveHandEntity, Is.Null); await AssertTile(Plating); AssertGridCount(1); // Plating -> Tile - await Interact(FloorItem); + await InteractUsing(FloorItem); Assert.That(Hands.ActiveHandEntity, Is.Null); await AssertTile(Floor); AssertGridCount(1); // Tile -> Plating - await Interact(Pry); + await InteractUsing(Pry); await AssertTile(Plating); AssertGridCount(1); diff --git a/Content.IntegrationTests/Tests/Weldable/WeldableTests.cs b/Content.IntegrationTests/Tests/Weldable/WeldableTests.cs index 6227f3dee1..e7eadeda0a 100644 --- a/Content.IntegrationTests/Tests/Weldable/WeldableTests.cs +++ b/Content.IntegrationTests/Tests/Weldable/WeldableTests.cs @@ -18,7 +18,7 @@ public sealed class WeldableTests : InteractionTest Assert.That(comp.IsWelded, Is.False); - await Interact(Weld); + await InteractUsing(Weld); Assert.That(comp.IsWelded, Is.True); AssertPrototype(Locker); // Prototype did not change. } From 2bd4f859661f0cbe9d7c357e9038f2671460c443 Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:07:41 -0500 Subject: [PATCH 043/243] Fix Weh Juice and Other emote chems not working (#28562) --- Content.Server/Chat/Systems/ChatSystem.Emote.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index 23e1517d75..5897aac61a 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -81,7 +81,7 @@ public partial class ChatSystem bool ignoreActionBlocker = false ) { - if (_whitelistSystem.IsWhitelistFailOrNull(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source)) + if (_whitelistSystem.IsWhitelistFail(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source)) return; if (!emote.Available && From c5ff647ca6257862cfd391926470694f909759f4 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Mon, 3 Jun 2024 14:40:03 -0700 Subject: [PATCH 044/243] Replace obsolete EntityWhitelist IsValid usages part 2 (#28506) --- Content.Client/Outline/TargetOutlineSystem.cs | 3 ++- Content.Server/Chat/Systems/ChatSystem.Emote.cs | 1 + .../TileReactions/CreateEntityTileReaction.cs | 5 +++-- .../Construction/ConstructionSystem.Initial.cs | 6 ++++-- .../GameTicking/Rules/LoadMapRuleSystem.cs | 4 +++- .../VariationPass/CutWireVariationPassSystem.cs | 5 ++++- .../Holiday/Christmas/RandomGiftSystem.cs | 6 ++++-- .../Mech/Systems/MechEquipmentSystem.cs | 4 +++- Content.Server/Mech/Systems/MechSystem.cs | 4 +++- .../Ninja/Systems/StunProviderSystem.cs | 4 +++- .../EntitySystems/AnimalHusbandrySystem.cs | 6 ++++-- .../Nutrition/EntitySystems/FoodSystem.cs | 4 +++- .../ObjectiveBlacklistRequirementSystem.cs | 5 ++++- .../Objectives/Systems/RoleRequirementSystem.cs | 4 +++- .../Polymorph/Systems/PolymorphSystem.Collide.cs | 7 +++++-- Content.Server/Sticky/Systems/StickySystem.cs | 7 ++++--- .../Storage/EntitySystems/ItemCounterSystem.cs | 6 ++++-- .../Store/Conditions/BuyerWhitelistCondition.cs | 15 ++++----------- .../Store/Conditions/StoreWhitelistCondition.cs | 15 ++++----------- .../Systems/SurveillanceCameraMicrophoneSystem.cs | 5 +++-- Content.Server/Traits/TraitSystem.cs | 8 ++++---- .../Equipment/Systems/ArtifactCrusherSystem.cs | 4 +++- .../Effects/Systems/DamageNearbyArtifactSystem.cs | 6 ++++-- Content.Shared/Actions/SharedActionsSystem.cs | 4 +++- .../Buckle/SharedBuckleSystem.Buckle.cs | 7 +++++-- .../Conditions/EntityWhitelistCondition.cs | 3 ++- Content.Shared/Inventory/InventorySystem.Equip.cs | 11 ++++------- .../EntitySystems/SharedHandLabelerSystem.cs | 6 ++++-- .../Materials/SharedMaterialReclaimerSystem.cs | 8 ++++---- .../Mech/EntitySystems/SharedMechSystem.cs | 4 +++- .../Systems/SpeedModifierContactsSystem.cs | 4 +++- .../Ninja/Systems/EmagProviderSystem.cs | 3 ++- Content.Shared/Placeable/ItemPlacerSystem.cs | 6 ++++-- Content.Shared/Random/RulesSystem.cs | 5 +++-- Content.Shared/Sound/SharedEmitSoundSystem.cs | 4 +++- .../StepTrigger/Systems/StepTriggerSystem.cs | 4 +++- Content.Shared/Storage/EntitySystems/BinSystem.cs | 6 ++++-- .../EntitySystems/SharedItemMapperSystem.cs | 6 ++++-- .../Teleportation/Systems/SwapTeleporterSystem.cs | 6 ++++-- .../UserInterface/ActivatableUISystem.cs | 6 ++++-- .../Ranged/Systems/SharedGunSystem.Ballistic.cs | 3 ++- .../Ranged/Systems/SharedGunSystem.Clothing.cs | 2 +- 42 files changed, 141 insertions(+), 91 deletions(-) diff --git a/Content.Client/Outline/TargetOutlineSystem.cs b/Content.Client/Outline/TargetOutlineSystem.cs index 2a6867f51f..df57578b1f 100644 --- a/Content.Client/Outline/TargetOutlineSystem.cs +++ b/Content.Client/Outline/TargetOutlineSystem.cs @@ -22,6 +22,7 @@ public sealed class TargetOutlineSystem : EntitySystem [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private bool _enabled = false; @@ -137,7 +138,7 @@ public sealed class TargetOutlineSystem : EntitySystem // check the entity whitelist if (valid && Whitelist != null) - valid = Whitelist.IsValid(entity); + valid = _whitelistSystem.IsWhitelistPass(Whitelist, entity); // and check the cancellable event if (valid && ValidationEvent != null) diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index 5897aac61a..20a4f18746 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -81,6 +81,7 @@ public partial class ChatSystem bool ignoreActionBlocker = false ) { + if (_whitelistSystem.IsWhitelistFail(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source)) return; diff --git a/Content.Server/Chemistry/TileReactions/CreateEntityTileReaction.cs b/Content.Server/Chemistry/TileReactions/CreateEntityTileReaction.cs index 29f9275bdf..6b106b1fc0 100644 --- a/Content.Server/Chemistry/TileReactions/CreateEntityTileReaction.cs +++ b/Content.Server/Chemistry/TileReactions/CreateEntityTileReaction.cs @@ -1,4 +1,4 @@ -using Content.Shared.Chemistry.Reaction; +using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; using Content.Shared.Maps; @@ -47,7 +47,8 @@ public sealed partial class CreateEntityTileReaction : ITileReaction int acc = 0; foreach (var ent in tile.GetEntitiesInTile()) { - if (Whitelist.IsValid(ent)) + var whitelistSystem = entityManager.System(); + if (whitelistSystem.IsWhitelistPass(Whitelist, ent)) acc += 1; if (acc >= MaxOnTile) diff --git a/Content.Server/Construction/ConstructionSystem.Initial.cs b/Content.Server/Construction/ConstructionSystem.Initial.cs index 04d3722c66..5161ac358a 100644 --- a/Content.Server/Construction/ConstructionSystem.Initial.cs +++ b/Content.Server/Construction/ConstructionSystem.Initial.cs @@ -14,6 +14,7 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Storage; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Player; @@ -30,6 +31,7 @@ namespace Content.Server.Construction [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly EntityLookupSystem _lookupSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; // --- WARNING! LEGACY CODE AHEAD! --- // This entire file contains the legacy code for initial construction. @@ -337,7 +339,7 @@ namespace Content.Server.Construction return false; } - if (constructionPrototype.EntityWhitelist != null && !constructionPrototype.EntityWhitelist.IsValid(user)) + if (_whitelistSystem.IsWhitelistFail(constructionPrototype.EntityWhitelist, user)) { _popup.PopupEntity(Loc.GetString("construction-system-cannot-start"), user, user); return false; @@ -422,7 +424,7 @@ namespace Content.Server.Construction return; } - if (constructionPrototype.EntityWhitelist != null && !constructionPrototype.EntityWhitelist.IsValid(user)) + if (_whitelistSystem.IsWhitelistFail(constructionPrototype.EntityWhitelist, user)) { _popup.PopupEntity(Loc.GetString("construction-system-cannot-start"), user, user); return; diff --git a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs index 3a80d82fd9..c60cf2513e 100644 --- a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs @@ -3,6 +3,7 @@ using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.GridPreloader; using Content.Server.Spawners.Components; +using Content.Shared.Whitelist; using Robust.Server.GameObjects; using Robust.Server.Maps; using Robust.Shared.Map; @@ -19,6 +20,7 @@ public sealed class LoadMapRuleSystem : GameRuleSystem [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly GridPreloaderSystem _gridPreloader = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -99,7 +101,7 @@ public sealed class LoadMapRuleSystem : GameRuleSystem if (xform.GridUid == null || !ent.Comp.MapGrids.Contains(xform.GridUid.Value)) continue; - if (ent.Comp.SpawnerWhitelist != null && !ent.Comp.SpawnerWhitelist.IsValid(uid, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(ent.Comp.SpawnerWhitelist, uid)) continue; args.Coordinates.Add(_transform.GetMapCoordinates(xform)); diff --git a/Content.Server/GameTicking/Rules/VariationPass/CutWireVariationPassSystem.cs b/Content.Server/GameTicking/Rules/VariationPass/CutWireVariationPassSystem.cs index fd94c74ac8..372de4bbb4 100644 --- a/Content.Server/GameTicking/Rules/VariationPass/CutWireVariationPassSystem.cs +++ b/Content.Server/GameTicking/Rules/VariationPass/CutWireVariationPassSystem.cs @@ -1,5 +1,6 @@ using Content.Server.GameTicking.Rules.VariationPass.Components; using Content.Server.Wires; +using Content.Shared.Whitelist; using Robust.Shared.Random; namespace Content.Server.GameTicking.Rules.VariationPass; @@ -11,6 +12,8 @@ namespace Content.Server.GameTicking.Rules.VariationPass; /// public sealed class CutWireVariationPassSystem : VariationPassSystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + protected override void ApplyVariation(Entity ent, ref StationVariationPassEvent args) { var wiresCut = 0; @@ -22,7 +25,7 @@ public sealed class CutWireVariationPassSystem : VariationPassSystem _possibleGiftsSafe = new(); private readonly List _possibleGiftsUnsafe = new(); @@ -40,7 +42,7 @@ public sealed class RandomGiftSystem : EntitySystem private void OnExamined(EntityUid uid, RandomGiftComponent component, ExaminedEvent args) { - if (!component.ContentsViewers.IsValid(args.Examiner, EntityManager) || component.SelectedEntity is null) + if (_whitelistSystem.IsWhitelistFail(component.ContentsViewers, args.Examiner) || component.SelectedEntity is null) return; var name = _prototype.Index(component.SelectedEntity).Name; diff --git a/Content.Server/Mech/Systems/MechEquipmentSystem.cs b/Content.Server/Mech/Systems/MechEquipmentSystem.cs index f51c0444e6..f9fe5e4641 100644 --- a/Content.Server/Mech/Systems/MechEquipmentSystem.cs +++ b/Content.Server/Mech/Systems/MechEquipmentSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.DoAfter; using Content.Shared.Interaction; using Content.Shared.Mech.Components; using Content.Shared.Mech.Equipment.Components; +using Content.Shared.Whitelist; namespace Content.Server.Mech.Systems; @@ -14,6 +15,7 @@ public sealed class MechEquipmentSystem : EntitySystem [Dependency] private readonly MechSystem _mech = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() @@ -40,7 +42,7 @@ public sealed class MechEquipmentSystem : EntitySystem if (mechComp.EquipmentContainer.ContainedEntities.Count >= mechComp.MaxEquipmentAmount) return; - if (mechComp.EquipmentWhitelist != null && !mechComp.EquipmentWhitelist.IsValid(args.Used)) + if (_whitelistSystem.IsWhitelistFail(mechComp.EquipmentWhitelist, args.Used)) return; _popup.PopupEntity(Loc.GetString("mech-equipment-begin-install", ("item", uid)), mech); diff --git a/Content.Server/Mech/Systems/MechSystem.cs b/Content.Server/Mech/Systems/MechSystem.cs index 68b973f588..b738d28b46 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -22,6 +22,7 @@ using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.Player; +using Content.Shared.Whitelist; namespace Content.Server.Mech.Systems; @@ -36,6 +37,7 @@ public sealed partial class MechSystem : SharedMechSystem [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly SharedToolSystem _toolSystem = default!; /// @@ -224,7 +226,7 @@ public sealed partial class MechSystem : SharedMechSystem if (args.Cancelled || args.Handled) return; - if (component.PilotWhitelist != null && !component.PilotWhitelist.IsValid(args.User)) + if (_whitelistSystem.IsWhitelistFail(component.PilotWhitelist, args.User)) { _popup.PopupEntity(Loc.GetString("mech-no-enter", ("item", uid)), args.User); return; diff --git a/Content.Server/Ninja/Systems/StunProviderSystem.cs b/Content.Server/Ninja/Systems/StunProviderSystem.cs index 970ca78e2c..1768606ad2 100644 --- a/Content.Server/Ninja/Systems/StunProviderSystem.cs +++ b/Content.Server/Ninja/Systems/StunProviderSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Stunnable; using Robust.Shared.Prototypes; using Robust.Shared.Audio.Systems; using Robust.Shared.Timing; +using Content.Shared.Whitelist; namespace Content.Server.Ninja.Systems; @@ -24,6 +25,7 @@ public sealed class StunProviderSystem : SharedStunProviderSystem [Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedStunSystem _stun = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -42,7 +44,7 @@ public sealed class StunProviderSystem : SharedStunProviderSystem if (args.Handled || comp.BatteryUid == null || !_gloves.AbilityCheck(uid, args, out var target)) return; - if (target == uid || !comp.Whitelist.IsValid(target, EntityManager)) + if (target == uid || _whitelistSystem.IsWhitelistFail(comp.Whitelist, target)) return; if (_timing.CurTime < comp.NextStun) diff --git a/Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs b/Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs index cd05413405..e224c7c479 100644 --- a/Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs +++ b/Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Administration.Logs; +using Content.Server.Administration.Logs; using Content.Server.Popups; using Content.Shared.Database; using Content.Shared.IdentityManagement; @@ -9,6 +9,7 @@ using Content.Shared.Nutrition.AnimalHusbandry; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Storage; +using Content.Shared.Whitelist; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; @@ -33,6 +34,7 @@ public sealed class AnimalHusbandrySystem : EntitySystem [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private readonly HashSet _failedAttempts = new(); private readonly HashSet _birthQueue = new(); @@ -174,7 +176,7 @@ public sealed class AnimalHusbandrySystem : EntitySystem if (!CanReproduce(partner)) return false; - return component.PartnerWhitelist.IsValid(partner); + return _whitelistSystem.IsWhitelistPass(component.PartnerWhitelist, partner); } /// diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index 2c7632aadc..1862b4e19f 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -32,6 +32,7 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Utility; using System.Linq; using Robust.Server.GameObjects; +using Content.Shared.Whitelist; namespace Content.Server.Nutrition.EntitySystems; @@ -57,6 +58,7 @@ public sealed class FoodSystem : EntitySystem [Dependency] private readonly StackSystem _stack = default!; [Dependency] private readonly StomachSystem _stomach = default!; [Dependency] private readonly UtensilSystem _utensil = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public const float MaxFeedDistance = 1.0f; @@ -408,7 +410,7 @@ public sealed class FoodSystem : EntitySystem if (comp.SpecialDigestible == null) continue; // Check if the food is in the whitelist - if (comp.SpecialDigestible.IsValid(food, EntityManager)) + if (_whitelistSystem.IsWhitelistPass(comp.SpecialDigestible, food)) return true; // They can only eat whitelist food and the food isn't in the whitelist. It's not edible. return false; diff --git a/Content.Server/Objectives/Systems/ObjectiveBlacklistRequirementSystem.cs b/Content.Server/Objectives/Systems/ObjectiveBlacklistRequirementSystem.cs index 56b245ce84..8fe7e7e203 100644 --- a/Content.Server/Objectives/Systems/ObjectiveBlacklistRequirementSystem.cs +++ b/Content.Server/Objectives/Systems/ObjectiveBlacklistRequirementSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Objectives.Components; using Content.Shared.Objectives.Components; +using Content.Shared.Whitelist; namespace Content.Server.Objectives.Systems; @@ -8,6 +9,8 @@ namespace Content.Server.Objectives.Systems; /// public sealed class ObjectiveBlacklistRequirementSystem : EntitySystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + public override void Initialize() { base.Initialize(); @@ -22,7 +25,7 @@ public sealed class ObjectiveBlacklistRequirementSystem : EntitySystem foreach (var objective in args.Mind.AllObjectives) { - if (comp.Blacklist.IsValid(objective, EntityManager)) + if (_whitelistSystem.IsBlacklistPass(comp.Blacklist, objective)) { args.Cancelled = true; return; diff --git a/Content.Server/Objectives/Systems/RoleRequirementSystem.cs b/Content.Server/Objectives/Systems/RoleRequirementSystem.cs index 97aee218f0..8421987bae 100644 --- a/Content.Server/Objectives/Systems/RoleRequirementSystem.cs +++ b/Content.Server/Objectives/Systems/RoleRequirementSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Objectives.Components; using Content.Shared.Objectives.Components; +using Content.Shared.Whitelist; namespace Content.Server.Objectives.Systems; @@ -8,6 +9,7 @@ namespace Content.Server.Objectives.Systems; /// public sealed class RoleRequirementSystem : EntitySystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { base.Initialize(); @@ -22,7 +24,7 @@ public sealed class RoleRequirementSystem : EntitySystem // this whitelist trick only works because roles are components on the mind and not entities // if that gets reworked then this will need changing - if (!comp.Roles.IsValid(args.MindId, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(comp.Roles, args.MindId)) args.Cancelled = true; } } diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.Collide.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.Collide.cs index b4240d409a..b29f46e09e 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.Collide.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.Collide.cs @@ -1,6 +1,7 @@ using Content.Server.Polymorph.Components; using Content.Shared.Polymorph; using Content.Shared.Projectiles; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Physics.Events; using Robust.Shared.Prototypes; @@ -9,6 +10,8 @@ namespace Content.Server.Polymorph.Systems; public partial class PolymorphSystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + /// /// Need to do this so we don't get a collection enumeration error in physics by polymorphing /// an entity we're colliding with @@ -39,8 +42,8 @@ public partial class PolymorphSystem return; var other = args.OtherEntity; - if (!component.Whitelist.IsValid(other, EntityManager) - || component.Blacklist != null && component.Blacklist.IsValid(other, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, other) || + _whitelistSystem.IsBlacklistPass(component.Blacklist, other)) return; _queuedPolymorphUpdates.Enqueue(new (other, component.Sound, component.Polymorph)); diff --git a/Content.Server/Sticky/Systems/StickySystem.cs b/Content.Server/Sticky/Systems/StickySystem.cs index effe1b72f7..21a7f4d039 100644 --- a/Content.Server/Sticky/Systems/StickySystem.cs +++ b/Content.Server/Sticky/Systems/StickySystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Interaction; using Content.Shared.Sticky; using Content.Shared.Sticky.Components; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Utility; @@ -20,6 +21,7 @@ public sealed class StickySystem : EntitySystem [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private const string StickerSlotId = "stickers_container"; @@ -67,9 +69,8 @@ public sealed class StickySystem : EntitySystem return false; // check whitelist and blacklist - if (component.Whitelist != null && !component.Whitelist.IsValid(target)) - return false; - if (component.Blacklist != null && component.Blacklist.IsValid(target)) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, target) || + _whitelistSystem.IsBlacklistPass(component.Blacklist, target)) return false; var attemptEv = new AttemptEntityStickEvent(target, user); diff --git a/Content.Server/Storage/EntitySystems/ItemCounterSystem.cs b/Content.Server/Storage/EntitySystems/ItemCounterSystem.cs index 415e8d9246..43fcb32d1f 100644 --- a/Content.Server/Storage/EntitySystems/ItemCounterSystem.cs +++ b/Content.Server/Storage/EntitySystems/ItemCounterSystem.cs @@ -1,6 +1,7 @@ -using Content.Shared.Storage; +using Content.Shared.Storage; using Content.Shared.Storage.Components; using Content.Shared.Storage.EntitySystems; +using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.Containers; @@ -9,6 +10,7 @@ namespace Content.Server.Storage.EntitySystems [UsedImplicitly] public sealed class ItemCounterSystem : SharedItemCounterSystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; protected override int? GetCount(ContainerModifiedMessage msg, ItemCounterComponent itemCounter) { if (!EntityManager.TryGetComponent(msg.Container.Owner, out StorageComponent? component)) @@ -19,7 +21,7 @@ namespace Content.Server.Storage.EntitySystems var count = 0; foreach (var entity in component.Container.ContainedEntities) { - if (itemCounter.Count.IsValid(entity)) + if (_whitelistSystem.IsWhitelistPass(itemCounter.Count, entity)) count++; } diff --git a/Content.Server/Store/Conditions/BuyerWhitelistCondition.cs b/Content.Server/Store/Conditions/BuyerWhitelistCondition.cs index 859703a72a..ff4a9a19cd 100644 --- a/Content.Server/Store/Conditions/BuyerWhitelistCondition.cs +++ b/Content.Server/Store/Conditions/BuyerWhitelistCondition.cs @@ -23,18 +23,11 @@ public sealed partial class BuyerWhitelistCondition : ListingCondition public override bool Condition(ListingConditionArgs args) { var ent = args.EntityManager; + var whitelistSystem = ent.System(); - if (Whitelist != null) - { - if (!Whitelist.IsValid(args.Buyer, ent)) - return false; - } - - if (Blacklist != null) - { - if (Blacklist.IsValid(args.Buyer, ent)) - return false; - } + if (whitelistSystem.IsWhitelistFail(Whitelist, args.Buyer) || + whitelistSystem.IsBlacklistPass(Blacklist, args.Buyer)) + return false; return true; } diff --git a/Content.Server/Store/Conditions/StoreWhitelistCondition.cs b/Content.Server/Store/Conditions/StoreWhitelistCondition.cs index 20ec5cecce..ced4dfa9c0 100644 --- a/Content.Server/Store/Conditions/StoreWhitelistCondition.cs +++ b/Content.Server/Store/Conditions/StoreWhitelistCondition.cs @@ -26,18 +26,11 @@ public sealed partial class StoreWhitelistCondition : ListingCondition return false; var ent = args.EntityManager; + var whitelistSystem = ent.System(); - if (Whitelist != null) - { - if (!Whitelist.IsValid(args.StoreEntity.Value, ent)) - return false; - } - - if (Blacklist != null) - { - if (Blacklist.IsValid(args.StoreEntity.Value, ent)) - return false; - } + if (whitelistSystem.IsWhitelistFail(Whitelist, args.StoreEntity.Value) || + whitelistSystem.IsBlacklistPass(Blacklist, args.StoreEntity.Value)) + return false; return true; } diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs index 1adab9930d..4deb5238a5 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Chat.Systems; using Content.Server.Speech; using Content.Server.Speech.Components; +using Content.Shared.Whitelist; using Robust.Shared.Player; using static Content.Server.Chat.Systems.ChatSystem; @@ -9,7 +10,7 @@ namespace Content.Server.SurveillanceCamera; public sealed class SurveillanceCameraMicrophoneSystem : EntitySystem { [Dependency] private readonly SharedTransformSystem _xforms = default!; - + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { base.Initialize(); @@ -60,7 +61,7 @@ public sealed class SurveillanceCameraMicrophoneSystem : EntitySystem public void CanListen(EntityUid uid, SurveillanceCameraMicrophoneComponent microphone, ListenAttemptEvent args) { // TODO maybe just make this a part of ActiveListenerComponent? - if (microphone.Blacklist.IsValid(args.Source)) + if (_whitelistSystem.IsBlacklistPass(microphone.Blacklist, args.Source)) args.Cancel(); } diff --git a/Content.Server/Traits/TraitSystem.cs b/Content.Server/Traits/TraitSystem.cs index f7531f7e2c..f41512b6ac 100644 --- a/Content.Server/Traits/TraitSystem.cs +++ b/Content.Server/Traits/TraitSystem.cs @@ -2,6 +2,7 @@ using Content.Server.GameTicking; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Traits; +using Content.Shared.Whitelist; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager; @@ -12,6 +13,7 @@ public sealed class TraitSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly ISerializationManager _serializationManager = default!; [Dependency] private readonly SharedHandsSystem _sharedHandsSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -31,10 +33,8 @@ public sealed class TraitSystem : EntitySystem return; } - if (traitPrototype.Whitelist != null && !traitPrototype.Whitelist.IsValid(args.Mob)) - continue; - - if (traitPrototype.Blacklist != null && traitPrototype.Blacklist.IsValid(args.Mob)) + if (_whitelistSystem.IsWhitelistFail(traitPrototype.Whitelist, args.Mob) || + _whitelistSystem.IsBlacklistPass(traitPrototype.Blacklist, args.Mob)) continue; // Add all components required by the prototype diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs index 6606f28432..f65ba46f7a 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs @@ -8,6 +8,7 @@ using Content.Server.Xenoarchaeology.XenoArtifacts; using Content.Shared.Body.Components; using Content.Shared.Damage; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Content.Shared.Xenoarchaeology.Equipment; using Robust.Shared.Collections; using Robust.Shared.Random; @@ -25,6 +26,7 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly StackSystem _stack = default!; [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() @@ -92,7 +94,7 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem var coords = Transform(ent).Coordinates; foreach (var contained in contents) { - if (crusher.CrushingWhitelist.IsValid(contained, EntityManager)) + if (_whitelistSystem.IsWhitelistPass(crusher.CrushingWhitelist, contained)) { var amount = _random.Next(crusher.MinFragments, crusher.MaxFragments); var stacks = _stack.SpawnMultiple(crusher.FragmentStackProtoId, amount, coords); diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs index a2023a18d4..f231120ad5 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs @@ -1,6 +1,7 @@ -using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; using Content.Server.Xenoarchaeology.XenoArtifacts.Events; using Content.Shared.Damage; +using Content.Shared.Whitelist; using Robust.Shared.Random; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; @@ -10,6 +11,7 @@ public sealed class BreakWindowArtifactSystem : EntitySystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() @@ -24,7 +26,7 @@ public sealed class BreakWindowArtifactSystem : EntitySystem ents.Add(args.Activator.Value); foreach (var ent in ents) { - if (component.Whitelist != null && !component.Whitelist.IsValid(ent)) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, ent)) continue; if (!_random.Prob(component.DamageChance)) diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index bf86d2c1e4..0e302f1e02 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Interaction; using Content.Shared.Inventory.Events; using Content.Shared.Mind; using Content.Shared.Rejuvenate; +using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.GameStates; @@ -29,6 +30,7 @@ public abstract class SharedActionsSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly ActionContainerSystem _actionContainer = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -477,7 +479,7 @@ public abstract class SharedActionsSystem : EntitySystem if (!target.IsValid() || Deleted(target)) return false; - if (action.Whitelist != null && !action.Whitelist.IsValid(target, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(action.Whitelist, target)) return false; if (action.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, target)) diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index 00040211e3..e7bfb53f08 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -15,6 +15,7 @@ using Content.Shared.Storage.Components; using Content.Shared.Stunnable; using Content.Shared.Throwing; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Utility; @@ -24,6 +25,8 @@ namespace Content.Shared.Buckle; public abstract partial class SharedBuckleSystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + private void InitializeBuckle() { SubscribeLocalEvent(OnBuckleComponentStartup); @@ -224,8 +227,8 @@ public abstract partial class SharedBuckleSystem } // Does it pass the Whitelist - if (strapComp.Whitelist != null && - !strapComp.Whitelist.IsValid(buckleUid, EntityManager) || strapComp.Blacklist?.IsValid(buckleUid, EntityManager) == true) + if (_whitelistSystem.IsWhitelistFail(strapComp.Whitelist, buckleUid) || + _whitelistSystem.IsBlacklistPass(strapComp.Blacklist, buckleUid)) { if (_netManager.IsServer) _popup.PopupEntity(Loc.GetString("buckle-component-cannot-fit-message"), userUid, buckleUid, PopupType.Medium); diff --git a/Content.Shared/Construction/Conditions/EntityWhitelistCondition.cs b/Content.Shared/Construction/Conditions/EntityWhitelistCondition.cs index 22d86b54fb..b7b3c56ba4 100644 --- a/Content.Shared/Construction/Conditions/EntityWhitelistCondition.cs +++ b/Content.Shared/Construction/Conditions/EntityWhitelistCondition.cs @@ -31,7 +31,8 @@ public sealed partial class EntityWhitelistCondition : IConstructionCondition public bool Condition(EntityUid user, EntityCoordinates location, Direction direction) { - return Whitelist.IsValid(user); + var whitelistSystem = IoCManager.Resolve().System(); + return whitelistSystem.IsWhitelistPass(Whitelist, user); } public ConstructionGuideEntry GenerateGuideEntry() diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index 7fd156213b..7acfafee4a 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -11,6 +11,7 @@ using Content.Shared.Item; using Content.Shared.Movement.Systems; using Content.Shared.Popups; using Content.Shared.Strip.Components; +using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Timing; @@ -30,6 +31,7 @@ public abstract partial class InventorySystem [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [ValidatePrototypeId] private const string PocketableItemSize = "Small"; @@ -267,13 +269,8 @@ public abstract partial class InventorySystem return false; } - if (slotDefinition.Whitelist != null && !slotDefinition.Whitelist.IsValid(itemUid)) - { - reason = "inventory-component-can-equip-does-not-fit"; - return false; - } - - if (slotDefinition.Blacklist != null && slotDefinition.Blacklist.IsValid(itemUid)) + if (_whitelistSystem.IsWhitelistFail(slotDefinition.Whitelist, itemUid) || + _whitelistSystem.IsBlacklistPass(slotDefinition.Blacklist, itemUid)) { reason = "inventory-component-can-equip-does-not-fit"; return false; diff --git a/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs b/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs index 7dbeee3e77..0763bb101c 100644 --- a/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs +++ b/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Interaction; using Content.Shared.Labels.Components; using Content.Shared.Popups; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.GameStates; using Robust.Shared.Network; @@ -16,6 +17,7 @@ public abstract class SharedHandLabelerSystem : EntitySystem [Dependency] private readonly SharedLabelSystem _labelSystem = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -77,7 +79,7 @@ public abstract class SharedHandLabelerSystem : EntitySystem private void OnUtilityVerb(EntityUid uid, HandLabelerComponent handLabeler, GetVerbsEvent args) { - if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanAccess) + if (args.Target is not { Valid: true } target || _whitelistSystem.IsWhitelistFail(handLabeler.Whitelist, target) || !args.CanAccess) return; var labelerText = handLabeler.AssignedLabel == string.Empty ? Loc.GetString("hand-labeler-remove-label-text") : Loc.GetString("hand-labeler-add-label-text"); @@ -96,7 +98,7 @@ public abstract class SharedHandLabelerSystem : EntitySystem private void AfterInteractOn(EntityUid uid, HandLabelerComponent handLabeler, AfterInteractEvent args) { - if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanReach) + if (args.Target is not { Valid: true } target || _whitelistSystem.IsWhitelistFail(handLabeler.Whitelist, target) || !args.CanReach) return; Labeling(uid, target, args.User, handLabeler); diff --git a/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs b/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs index fb1702bb94..9a7da17d48 100644 --- a/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs +++ b/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Emag.Systems; using Content.Shared.Examine; using Content.Shared.Mobs.Components; using Content.Shared.Stacks; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -29,6 +30,7 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem [Dependency] protected readonly SharedAmbientSoundSystem AmbientSound = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] protected readonly SharedContainerSystem Container = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public const string ActiveReclaimerContainerId = "active-material-reclaimer-container"; @@ -91,10 +93,8 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem if (HasComp(item) && !CanGib(uid, item, component)) // whitelist? We be gibbing, boy! return false; - if (component.Whitelist is {} whitelist && !whitelist.IsValid(item)) - return false; - - if (component.Blacklist is {} blacklist && blacklist.IsValid(item)) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, item) || + _whitelistSystem.IsBlacklistPass(component.Blacklist, item)) return false; if (Container.TryGetContainingContainer(item, out _) && !Container.TryRemoveFromContainer(item)) diff --git a/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs b/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs index 73b7c0847f..2ec48085c4 100644 --- a/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs +++ b/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs @@ -15,6 +15,7 @@ using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Content.Shared.Popups; using Content.Shared.Weapons.Melee; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Network; using Robust.Shared.Serialization; @@ -37,6 +38,7 @@ public abstract class SharedMechSystem : EntitySystem [Dependency] private readonly SharedMoverController _mover = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() @@ -216,7 +218,7 @@ public abstract class SharedMechSystem : EntitySystem if (component.EquipmentContainer.ContainedEntities.Count >= component.MaxEquipmentAmount) return; - if (component.EquipmentWhitelist != null && !component.EquipmentWhitelist.IsValid(toInsert)) + if (_whitelistSystem.IsWhitelistFail(component.EquipmentWhitelist, toInsert)) return; equipmentComponent.EquipmentOwner = uid; diff --git a/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs b/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs index 400a675cd2..6e1b3a29ae 100644 --- a/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs +++ b/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Movement.Components; +using Content.Shared.Whitelist; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; @@ -9,6 +10,7 @@ public sealed class SpeedModifierContactsSystem : EntitySystem { [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly MovementSpeedModifierSystem _speedModifierSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; // TODO full-game-save // Either these need to be processed before a map is saved, or slowed/slowing entities need to update on init. @@ -86,7 +88,7 @@ public sealed class SpeedModifierContactsSystem : EntitySystem if (!TryComp(ent, out var slowContactsComponent)) continue; - if (slowContactsComponent.IgnoreWhitelist != null && slowContactsComponent.IgnoreWhitelist.IsValid(uid)) + if (_whitelistSystem.IsWhitelistPass(slowContactsComponent.IgnoreWhitelist, uid)) continue; walkSpeed += slowContactsComponent.WalkSpeedModifier; diff --git a/Content.Shared/Ninja/Systems/EmagProviderSystem.cs b/Content.Shared/Ninja/Systems/EmagProviderSystem.cs index df9cf8ac82..6838e7982c 100644 --- a/Content.Shared/Ninja/Systems/EmagProviderSystem.cs +++ b/Content.Shared/Ninja/Systems/EmagProviderSystem.cs @@ -17,6 +17,7 @@ public sealed class EmagProviderSystem : EntitySystem [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!; [Dependency] private readonly TagSystem _tags = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -35,7 +36,7 @@ public sealed class EmagProviderSystem : EntitySystem return; // only allowed to emag entities on the whitelist - if (comp.Whitelist != null && !comp.Whitelist.IsValid(target, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(comp.Whitelist, target)) return; // only allowed to emag non-immune entities diff --git a/Content.Shared/Placeable/ItemPlacerSystem.cs b/Content.Shared/Placeable/ItemPlacerSystem.cs index 9be6a4acd5..d1ea88b82a 100644 --- a/Content.Shared/Placeable/ItemPlacerSystem.cs +++ b/Content.Shared/Placeable/ItemPlacerSystem.cs @@ -1,4 +1,5 @@ -using Robust.Shared.Physics.Events; +using Content.Shared.Whitelist; +using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; namespace Content.Shared.Placeable; @@ -11,6 +12,7 @@ public sealed class ItemPlacerSystem : EntitySystem { [Dependency] private readonly CollisionWakeSystem _wake = default!; [Dependency] private readonly PlaceableSurfaceSystem _placeableSurface = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -22,7 +24,7 @@ public sealed class ItemPlacerSystem : EntitySystem private void OnStartCollide(EntityUid uid, ItemPlacerComponent comp, ref StartCollideEvent args) { - if (comp.Whitelist != null && !comp.Whitelist.IsValid(args.OtherEntity)) + if (_whitelistSystem.IsWhitelistFail(comp.Whitelist, args.OtherEntity)) return; if (TryComp(args.OtherEntity, out var wakeComp)) diff --git a/Content.Shared/Random/RulesSystem.cs b/Content.Shared/Random/RulesSystem.cs index 6b8a58abb7..58d67c268e 100644 --- a/Content.Shared/Random/RulesSystem.cs +++ b/Content.Shared/Random/RulesSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; +using Content.Shared.Whitelist; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; @@ -14,7 +15,7 @@ public sealed class RulesSystem : EntitySystem [Dependency] private readonly AccessReaderSystem _reader = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; - + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public bool IsTrue(EntityUid uid, RulesPrototype rules) { var inRange = new HashSet>(); @@ -158,7 +159,7 @@ public sealed class RulesSystem : EntitySystem foreach (var ent in _lookup.GetEntitiesInRange(xform.MapID, worldPos, entity.Range)) { - if (!entity.Whitelist.IsValid(ent, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(entity.Whitelist, ent)) continue; count++; diff --git a/Content.Shared/Sound/SharedEmitSoundSystem.cs b/Content.Shared/Sound/SharedEmitSoundSystem.cs index 0dcdc44c9f..efc18abaa0 100644 --- a/Content.Shared/Sound/SharedEmitSoundSystem.cs +++ b/Content.Shared/Sound/SharedEmitSoundSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Mobs; using Content.Shared.Popups; using Content.Shared.Sound.Components; using Content.Shared.Throwing; +using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; @@ -34,6 +35,7 @@ public abstract class SharedEmitSoundSystem : EntitySystem [Dependency] private readonly SharedAmbientSoundSystem _ambient = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] protected readonly SharedPopupSystem Popup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -123,7 +125,7 @@ public abstract class SharedEmitSoundSystem : EntitySystem private void OnEmitSoundOnInteractUsing(Entity ent, ref InteractUsingEvent args) { - if (ent.Comp.Whitelist.IsValid(args.Used, EntityManager)) + if (_whitelistSystem.IsWhitelistPass(ent.Comp.Whitelist, args.Used)) { TryEmitSound(ent, ent.Comp, args.User); } diff --git a/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs b/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs index d81ad754d1..14703f3177 100644 --- a/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs +++ b/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Gravity; using Content.Shared.StepTrigger.Components; +using Content.Shared.Whitelist; using Robust.Shared.Map.Components; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; @@ -12,6 +13,7 @@ public sealed class StepTriggerSystem : EntitySystem [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly SharedGravitySystem _gravity = default!; [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -67,7 +69,7 @@ public sealed class StepTriggerSystem : EntitySystem if (ent == uid) continue; - if (component.Blacklist.IsValid(ent.Value, EntityManager) == true) + if (_whitelistSystem.IsBlacklistPass(component.Blacklist, ent.Value)) { return false; } diff --git a/Content.Shared/Storage/EntitySystems/BinSystem.cs b/Content.Shared/Storage/EntitySystems/BinSystem.cs index 1cc95337ea..8c2cb4c4fc 100644 --- a/Content.Shared/Storage/EntitySystems/BinSystem.cs +++ b/Content.Shared/Storage/EntitySystems/BinSystem.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Examine; @@ -7,6 +7,7 @@ using Content.Shared.Interaction; using Content.Shared.Item; using Content.Shared.Storage.Components; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Network; @@ -21,6 +22,7 @@ public sealed class BinSystem : EntitySystem [Dependency] private readonly ISharedAdminLogManager _admin = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public const string BinContainerId = "bin-container"; @@ -130,7 +132,7 @@ public sealed class BinSystem : EntitySystem if (component.Items.Count >= component.MaxItems) return false; - if (component.Whitelist != null && !component.Whitelist.IsValid(toInsert)) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, toInsert)) return false; _container.Insert(toInsert, component.ItemContainer); diff --git a/Content.Shared/Storage/EntitySystems/SharedItemMapperSystem.cs b/Content.Shared/Storage/EntitySystems/SharedItemMapperSystem.cs index a5e151f4ea..2557f8100c 100644 --- a/Content.Shared/Storage/EntitySystems/SharedItemMapperSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedItemMapperSystem.cs @@ -1,5 +1,6 @@ -using System.Linq; +using System.Linq; using Content.Shared.Storage.Components; +using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.Containers; @@ -15,6 +16,7 @@ namespace Content.Shared.Storage.EntitySystems { [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() @@ -93,7 +95,7 @@ namespace Content.Shared.Storage.EntitySystems var list = new List(); foreach (var mapLayerData in itemMapper.MapLayers.Values) { - var count = containedLayers.Count(ent => mapLayerData.ServerWhitelist.IsValid(ent)); + var count = containedLayers.Count(ent => _whitelistSystem.IsWhitelistPass(mapLayerData.ServerWhitelist, ent)); if (count >= mapLayerData.MinCount && count <= mapLayerData.MaxCount) { list.Add(mapLayerData.Layer); diff --git a/Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs b/Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs index bc73baa61a..58c249fec5 100644 --- a/Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs +++ b/Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Teleportation.Components; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Map.Components; @@ -24,6 +25,7 @@ public sealed class SwapTeleporterSystem : EntitySystem [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private EntityQuery _xformQuery; @@ -51,8 +53,8 @@ public sealed class SwapTeleporterSystem : EntitySystem if (!TryComp(target, out var targetComp)) return; - if (!comp.TeleporterWhitelist.IsValid(target, EntityManager) || - !targetComp.TeleporterWhitelist.IsValid(uid, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(comp.TeleporterWhitelist, target) || + _whitelistSystem.IsWhitelistFail(targetComp.TeleporterWhitelist, uid)) { return; } diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs index 1bb11f337f..b1006b2a74 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Popups; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Utility; namespace Content.Shared.UserInterface; @@ -19,6 +20,7 @@ public sealed partial class ActivatableUISystem : EntitySystem [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -96,7 +98,7 @@ public sealed partial class ActivatableUISystem : EntitySystem if (!args.CanAccess) return false; - if (!component.RequiredItems?.IsValid(args.Using ?? default, EntityManager) ?? false) + if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Using ?? default)) return false; if (component.RequireHands) @@ -156,7 +158,7 @@ public sealed partial class ActivatableUISystem : EntitySystem if (component.RequiredItems == null) return; - if (!component.RequiredItems.IsValid(args.Used, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Used)) return; args.Handled = InteractUI(args.User, uid, component); diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs index 1f9e6cee76..9123661c8e 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs @@ -15,6 +15,7 @@ public abstract partial class SharedGunSystem { [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + protected virtual void InitializeBallistic() { SubscribeLocalEvent(OnBallisticInit); @@ -125,7 +126,7 @@ public abstract partial class SharedGunSystem if (ent == null) continue; - if (!target.Whitelist.IsValid(ent.Value)) + if (_whitelistSystem.IsWhitelistFail(target.Whitelist, ent.Value)) { Popup( Loc.GetString("gun-ballistic-transfer-invalid", diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs index 77ee419ac3..d4aa024f4c 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs @@ -42,7 +42,7 @@ public partial class SharedGunSystem while (enumerator.NextItem(out var item)) { - if (component.ProviderWhitelist == null || !component.ProviderWhitelist.IsValid(item, EntityManager)) + if (_whitelistSystem.IsWhitelistFailOrNull(component.ProviderWhitelist, item)) continue; slotEntity = item; From b51f04a765a68d5d5b86f83f49eb14c812ea0ac0 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:42:50 -0700 Subject: [PATCH 045/243] Gets rid of obsolete EntityWhitelist.IsValid() function (#28565) Gets rid of obsolete IsValid function Co-authored-by: plykiya --- Content.Shared/Whitelist/EntityWhitelist.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Content.Shared/Whitelist/EntityWhitelist.cs b/Content.Shared/Whitelist/EntityWhitelist.cs index 895759be95..3e4e2fecb2 100644 --- a/Content.Shared/Whitelist/EntityWhitelist.cs +++ b/Content.Shared/Whitelist/EntityWhitelist.cs @@ -54,14 +54,4 @@ public sealed partial class EntityWhitelist /// [DataField] public bool RequireAll; - - [Obsolete("Use WhitelistSystem")] - public bool IsValid(EntityUid uid, IEntityManager? man = null) - { - var sys = man?.System() ?? - IoCManager.Resolve().GetEntitySystem(); - - return sys.IsValid(this, uid); - - } } From 2ae5c953de0ee6bb5cafa1c983106c9c4596c8d6 Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Mon, 3 Jun 2024 18:45:00 -0500 Subject: [PATCH 046/243] Humans can no longer honk on command (#28566) * Humans can no longer honk on command * Undo change it emote file * I hate tabs * Some comments --- .../Chat/Systems/ChatSystem.Emote.cs | 45 ++++++++++++++----- .../Chemistry/ReagentEffects/Emote.cs | 7 ++- Resources/Prototypes/Reagents/fun.yml | 1 + Resources/Prototypes/Reagents/toxins.yml | 1 + 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index 20a4f18746..e4e5c39eeb 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -49,18 +49,20 @@ public partial class ChatSystem /// Whether or not this message should appear in the adminlog window /// Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all... /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. + /// Bypasses whitelist/blacklist/availibility checks for if the entity can use this emote public void TryEmoteWithChat( EntityUid source, string emoteId, ChatTransmitRange range = ChatTransmitRange.Normal, bool hideLog = false, string? nameOverride = null, - bool ignoreActionBlocker = false + bool ignoreActionBlocker = false, + bool forceEmote = false ) { if (!_prototypeManager.TryIndex(emoteId, out var proto)) return; - TryEmoteWithChat(source, proto, range, hideLog: hideLog, nameOverride, ignoreActionBlocker: ignoreActionBlocker); + TryEmoteWithChat(source, proto, range, hideLog: hideLog, nameOverride, ignoreActionBlocker: ignoreActionBlocker, forceEmote: forceEmote); } /// @@ -72,22 +74,18 @@ public partial class ChatSystem /// Whether or not this message should appear in the chat window /// Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all... /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. + /// Bypasses whitelist/blacklist/availibility checks for if the entity can use this emote public void TryEmoteWithChat( EntityUid source, EmotePrototype emote, ChatTransmitRange range = ChatTransmitRange.Normal, bool hideLog = false, string? nameOverride = null, - bool ignoreActionBlocker = false + bool ignoreActionBlocker = false, + bool forceEmote = false ) { - - if (_whitelistSystem.IsWhitelistFail(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source)) - return; - - if (!emote.Available && - TryComp(source, out var speech) && - !speech.AllowedEmotes.Contains(emote.ID)) + if (!forceEmote && !AllowedToUseEmote(source, emote)) return; // check if proto has valid message for chat @@ -156,15 +154,40 @@ public partial class ChatSystem _audio.PlayPvs(sound, uid, param); return true; } - + /// + /// Checks if a valid emote was typed, to play sounds and etc and invokes an event. + /// + /// + /// private void TryEmoteChatInput(EntityUid uid, string textInput) { var actionLower = textInput.ToLower(); if (!_wordEmoteDict.TryGetValue(actionLower, out var emote)) return; + if (!AllowedToUseEmote(uid, emote)) + return; + InvokeEmoteEvent(uid, emote); } + /// + /// Checks if we can use this emote based on the emotes whitelist, blacklist, and availibility to the entity. + /// + /// The entity that is speaking + /// The emote being used + /// + private bool AllowedToUseEmote(EntityUid source, EmotePrototype emote) + { + if ((_whitelistSystem.IsWhitelistFail(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source))) + return false; + + if (!emote.Available && + TryComp(source, out var speech) && + !speech.AllowedEmotes.Contains(emote.ID)) + return false; + + return true; + } private void InvokeEmoteEvent(EntityUid uid, EmotePrototype proto) { diff --git a/Content.Server/Chemistry/ReagentEffects/Emote.cs b/Content.Server/Chemistry/ReagentEffects/Emote.cs index a4d49e4ad1..db6de6cc75 100644 --- a/Content.Server/Chemistry/ReagentEffects/Emote.cs +++ b/Content.Server/Chemistry/ReagentEffects/Emote.cs @@ -8,7 +8,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy namespace Content.Server.Chemistry.ReagentEffects; /// -/// Tries to force someone to emote (scream, laugh, etc). +/// Tries to force someone to emote (scream, laugh, etc). Still respects whitelists/blacklists and other limits of the specified emote unless forced. /// [UsedImplicitly] public sealed partial class Emote : ReagentEffect @@ -19,6 +19,9 @@ public sealed partial class Emote : ReagentEffect [DataField] public bool ShowInChat; + [DataField] + public bool Force = false; + // JUSTIFICATION: Emoting is flavor, so same reason popup messages are not in here. protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null; @@ -30,7 +33,7 @@ public sealed partial class Emote : ReagentEffect var chatSys = args.EntityManager.System(); if (ShowInChat) - chatSys.TryEmoteWithChat(args.SolutionEntity, EmoteId, ChatTransmitRange.GhostRangeLimit); + chatSys.TryEmoteWithChat(args.SolutionEntity, EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: Force); else chatSys.TryEmoteWithoutChat(args.SolutionEntity, EmoteId); diff --git a/Resources/Prototypes/Reagents/fun.yml b/Resources/Prototypes/Reagents/fun.yml index befa8d8296..1df2636c8c 100644 --- a/Resources/Prototypes/Reagents/fun.yml +++ b/Resources/Prototypes/Reagents/fun.yml @@ -333,6 +333,7 @@ - !type:Emote emote: Weh showInChat: true + force: true probability: 0.5 - !type:Polymorph prototype: ArtifactLizard # Does the same thing as the original YML I made for this reagent. diff --git a/Resources/Prototypes/Reagents/toxins.yml b/Resources/Prototypes/Reagents/toxins.yml index f5b196acf6..9665de7b09 100644 --- a/Resources/Prototypes/Reagents/toxins.yml +++ b/Resources/Prototypes/Reagents/toxins.yml @@ -560,6 +560,7 @@ - !type:Emote emote: Honk showInChat: true + force: true probability: 0.2 - !type:HealthChange conditions: From 7d22897d02706507be7f918bfafa493fccc702e3 Mon Sep 17 00:00:00 2001 From: "Mr. 27" <45323883+Dutch-VanDerLinde@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:52:15 -0400 Subject: [PATCH 047/243] Job starting gear is now defined in the starting gear rather than backpack prototypes (#27605) * --- .../Tests/Roles/StartingGearStorageTests.cs | 72 +++ .../Station/Systems/StationSpawningSystem.cs | 15 +- Content.Shared/Roles/StartingGearPrototype.cs | 68 ++- .../Station/SharedStationSpawningSystem.cs | 20 +- .../en-US/preferences/loadout-groups.ftl | 11 +- .../Fills/Backpacks/StarterGear/backpack.yml | 422 ------------------ .../Fills/Backpacks/StarterGear/duffelbag.yml | 275 ------------ .../Fills/Backpacks/StarterGear/satchel.yml | 300 ------------- .../Catalog/Fills/Backpacks/duffelbag.yml | 49 -- .../Entities/Clothing/Back/backpacks.yml | 10 + .../Prototypes/Entities/Mobs/NPCs/human.yml | 2 +- .../Loadouts/Jobs/Cargo/cargo_technician.yml | 6 +- .../Loadouts/Jobs/Cargo/quartermaster.yml | 28 -- .../Jobs/Cargo/salvage_specialist.yml | 6 +- .../Loadouts/Jobs/Civilian/botanist.yml | 6 +- .../Loadouts/Jobs/Civilian/chaplain.yml | 28 -- .../Loadouts/Jobs/Civilian/clown.yml | 6 +- .../Loadouts/Jobs/Civilian/lawyer.yml | 30 +- .../Loadouts/Jobs/Civilian/mime.yml | 6 +- .../Loadouts/Jobs/Civilian/musician.yml | 28 -- .../Loadouts/Jobs/Civilian/passenger.yml | 6 +- .../Loadouts/Jobs/Command/captain.yml | 6 +- .../Jobs/Command/head_of_personnel.yml | 29 +- .../Engineering/atmospheric_technician.yml | 6 +- .../Jobs/Engineering/chief_engineer.yml | 28 -- .../Jobs/Engineering/station_engineer.yml | 6 +- .../Loadouts/Jobs/Medical/chemist.yml | 6 +- .../Jobs/Medical/chief_medical_officer.yml | 28 -- .../Loadouts/Jobs/Medical/medical_doctor.yml | 6 +- .../Loadouts/Jobs/Medical/paramedic.yml | 28 -- .../Jobs/Science/research_director.yml | 28 -- .../Loadouts/Jobs/Science/scientist.yml | 6 +- .../Loadouts/Jobs/Security/detective.yml | 28 -- .../Jobs/Security/security_officer.yml | 6 +- .../Prototypes/Loadouts/loadout_groups.yml | 82 +--- .../Prototypes/Loadouts/role_loadouts.yml | 18 +- Resources/Prototypes/Roles/Antags/Thief.yml | 7 + Resources/Prototypes/Roles/Antags/ninja.yml | 30 ++ Resources/Prototypes/Roles/Antags/nukeops.yml | 63 +++ Resources/Prototypes/Roles/Antags/pirate.yml | 16 +- .../Prototypes/Roles/Antags/revolutionary.yml | 7 + Resources/Prototypes/Roles/Antags/traitor.yml | 33 ++ .../Roles/Jobs/Cargo/cargo_technician.yml | 5 +- .../Roles/Jobs/Cargo/quartermaster.yml | 6 +- .../Roles/Jobs/Cargo/salvage_specialist.yml | 5 +- .../Roles/Jobs/Civilian/assistant.yml | 3 + .../Roles/Jobs/Civilian/bartender.yml | 3 + .../Roles/Jobs/Civilian/botanist.yml | 3 + .../Roles/Jobs/Civilian/chaplain.yml | 5 + .../Prototypes/Roles/Jobs/Civilian/chef.yml | 3 + .../Prototypes/Roles/Jobs/Civilian/clown.yml | 7 +- .../Roles/Jobs/Civilian/janitor.yml | 3 + .../Prototypes/Roles/Jobs/Civilian/lawyer.yml | 4 + .../Roles/Jobs/Civilian/librarian.yml | 4 + .../Prototypes/Roles/Jobs/Civilian/mime.yml | 4 + .../Roles/Jobs/Civilian/musician.yml | 7 +- .../Roles/Jobs/Civilian/service_worker.yml | 3 + .../Prototypes/Roles/Jobs/Command/captain.yml | 5 + .../Roles/Jobs/Command/head_of_personnel.yml | 4 + .../Engineering/atmospheric_technician.yml | 3 + .../Roles/Jobs/Engineering/chief_engineer.yml | 4 + .../Jobs/Engineering/station_engineer.yml | 3 + .../Jobs/Engineering/technical_assistant.yml | 5 +- .../Roles/Jobs/Fun/cult_startinggear.yml | 10 +- .../Roles/Jobs/Fun/emergencyresponseteam.yml | 174 +++++++- .../Roles/Jobs/Fun/misc_startinggear.yml | 221 +++------ .../Roles/Jobs/Fun/wizard_startinggear.yml | 22 +- .../Prototypes/Roles/Jobs/Medical/chemist.yml | 5 +- .../Jobs/Medical/chief_medical_officer.yml | 4 + .../Roles/Jobs/Medical/medical_doctor.yml | 3 + .../Roles/Jobs/Medical/medical_intern.yml | 5 +- .../Roles/Jobs/Medical/paramedic.yml | 4 + .../Roles/Jobs/Science/research_assistant.yml | 3 + .../Roles/Jobs/Science/research_director.yml | 6 +- .../Roles/Jobs/Science/scientist.yml | 4 +- .../Roles/Jobs/Security/detective.yml | 6 + .../Roles/Jobs/Security/head_of_security.yml | 5 + .../Roles/Jobs/Security/security_cadet.yml | 7 +- .../Roles/Jobs/Security/security_officer.yml | 5 + .../Prototypes/Roles/Jobs/Security/warden.yml | 5 + .../Prototypes/Roles/Jobs/Wildcards/boxer.yml | 3 + .../Roles/Jobs/Wildcards/psychologist.yml | 3 + .../Roles/Jobs/Wildcards/reporter.yml | 3 + .../Roles/Jobs/Wildcards/zookeeper.yml | 3 + Resources/migration.yml | 6 +- 85 files changed, 729 insertions(+), 1728 deletions(-) create mode 100644 Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs delete mode 100644 Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/backpack.yml delete mode 100644 Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/duffelbag.yml delete mode 100644 Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/satchel.yml diff --git a/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs b/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs new file mode 100644 index 0000000000..0f15a02eaa --- /dev/null +++ b/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs @@ -0,0 +1,72 @@ +using System.Linq; +using Content.Shared.Roles; +using Content.Server.Storage.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Collections; + +namespace Content.IntegrationTests.Tests.Roles; + +[TestFixture] +public sealed class StartingGearPrototypeStorageTest +{ + /// + /// Checks that a storage fill on a StartingGearPrototype will properly fill + /// + [Test] + public async Task TestStartingGearStorage() + { + var settings = new PoolSettings { Connected = true, Dirty = true }; + await using var pair = await PoolManager.GetServerClient(settings); + var server = pair.Server; + var mapManager = server.ResolveDependency(); + var storageSystem = server.System(); + + var protos = server.ProtoMan + .EnumeratePrototypes() + .Where(p => !p.Abstract) + .ToList() + .OrderBy(p => p.ID); + + var testMap = await pair.CreateTestMap(); + var coords = testMap.GridCoords; + + await server.WaitAssertion(() => + { + foreach (var gearProto in protos) + { + var backpackProto = gearProto.GetGear("back"); + if (backpackProto == string.Empty) + continue; + + var bag = server.EntMan.SpawnEntity(backpackProto, coords); + var ents = new ValueList(); + + foreach (var (slot, entProtos) in gearProto.Storage) + { + if (entProtos.Count == 0) + continue; + + foreach (var ent in entProtos) + { + ents.Add(server.EntMan.SpawnEntity(ent, coords)); + } + + foreach (var ent in ents) + { + if (!storageSystem.CanInsert(bag, ent, out _)) + Assert.Fail($"StartingGearPrototype {gearProto.ID} could not successfully put items into storage {bag.Id}"); + + server.EntMan.DeleteEntity(ent); + } + } + + server.EntMan.DeleteEntity(bag); + } + + mapManager.DeleteMap(testMap.MapId); + }); + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index 557a1f9566..b91082ff26 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -179,13 +179,6 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem profile = HumanoidCharacterProfile.RandomWithSpecies(speciesId); } - if (prototype?.StartingGear != null) - { - var startingGear = _prototypeManager.Index(prototype.StartingGear); - EquipStartingGear(entity.Value, startingGear, raiseEvent: false); - } - - // Run loadouts after so stuff like storage loadouts can get var jobLoadout = LoadoutSystem.GetJobPrototype(prototype?.ID); if (_prototypeManager.TryIndex(jobLoadout, out RoleLoadoutPrototype? roleProto)) @@ -203,6 +196,12 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem EquipRoleLoadout(entity.Value, loadout, roleProto); } + if (prototype?.StartingGear != null) + { + var startingGear = _prototypeManager.Index(prototype.StartingGear); + EquipStartingGear(entity.Value, startingGear, raiseEvent: false); + } + var gearEquippedEv = new StartingGearEquippedEvent(entity.Value); RaiseLocalEvent(entity.Value, ref gearEquippedEv); @@ -309,4 +308,4 @@ public sealed class PlayerSpawningEvent : EntityEventArgs HumanoidCharacterProfile = humanoidCharacterProfile; Station = station; } -} +} \ No newline at end of file diff --git a/Content.Shared/Roles/StartingGearPrototype.cs b/Content.Shared/Roles/StartingGearPrototype.cs index fc9ecec7af..d96d014f88 100644 --- a/Content.Shared/Roles/StartingGearPrototype.cs +++ b/Content.Shared/Roles/StartingGearPrototype.cs @@ -1,30 +1,50 @@ -using Content.Shared.Preferences; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; -namespace Content.Shared.Roles +namespace Content.Shared.Roles; + +[Prototype] +public sealed partial class StartingGearPrototype : IPrototype, IInheritingPrototype { - [Prototype("startingGear")] - public sealed partial class StartingGearPrototype : IPrototype + /// + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = string.Empty; + + /// + [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))] + public string[]? Parents { get; private set; } + + /// + [AbstractDataField] + public bool Abstract { get; } + + /// + /// The slot and entity prototype ID of the equipment that is to be spawned and equipped onto the entity. + /// + [DataField] + [AlwaysPushInheritance] + public Dictionary Equipment = new(); + + /// + /// The inhand items that are equipped when this starting gear is equipped onto an entity. + /// + [DataField] + [AlwaysPushInheritance] + public List Inhand = new(0); + + /// + /// Inserts entities into the specified slot's storage (if it does have storage). + /// + [DataField] + [AlwaysPushInheritance] + public Dictionary> Storage = new(); + + /// + /// Gets the entity prototype ID of a slot in this starting gear. + /// + public string GetGear(string slot) { - [DataField] - public Dictionary Equipment = new(); - - [DataField] - public List Inhand = new(0); - - /// - /// Inserts entities into the specified slot's storage (if it does have storage). - /// - [DataField] - public Dictionary> Storage = new(); - - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = string.Empty; - - public string GetGear(string slot) - { - return Equipment.TryGetValue(slot, out var equipment) ? equipment : string.Empty; - } + return Equipment.TryGetValue(slot, out var equipment) ? equipment : string.Empty; } } diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs index f352c9db63..ca53998115 100644 --- a/Content.Shared/Station/SharedStationSpawningSystem.cs +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -15,9 +15,9 @@ public abstract class SharedStationSpawningSystem : EntitySystem { [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; [Dependency] protected readonly InventorySystem InventorySystem = default!; - [Dependency] private readonly SharedHandsSystem _handsSystem = default!; - [Dependency] private readonly SharedStorageSystem _storage = default!; - [Dependency] private readonly SharedTransformSystem _xformSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedStorageSystem _storage = default!; + [Dependency] private readonly SharedTransformSystem _xformSystem = default!; private EntityQuery _handsQuery; private EntityQuery _inventoryQuery; @@ -91,7 +91,7 @@ public abstract class SharedStationSpawningSystem : EntitySystem if (!string.IsNullOrEmpty(equipmentStr)) { var equipmentEntity = EntityManager.SpawnEntity(equipmentStr, xform.Coordinates); - InventorySystem.TryEquip(entity, equipmentEntity, slot.Name, silent: true, force:true); + InventorySystem.TryEquip(entity, equipmentEntity, slot.Name, silent: true, force: true); } } } @@ -122,15 +122,15 @@ public abstract class SharedStationSpawningSystem : EntitySystem if (entProtos.Count == 0) continue; - foreach (var ent in entProtos) - { - ents.Add(Spawn(ent, coords)); - } - if (inventoryComp != null && InventorySystem.TryGetSlotEntity(entity, slot, out var slotEnt, inventoryComponent: inventoryComp) && _storageQuery.TryComp(slotEnt, out var storage)) { + foreach (var ent in entProtos) + { + ents.Add(Spawn(ent, coords)); + } + foreach (var ent in ents) { _storage.Insert(slotEnt.Value, ent, out _, storageComp: storage, playSound: false); @@ -145,4 +145,4 @@ public abstract class SharedStationSpawningSystem : EntitySystem RaiseLocalEvent(entity, ref ev); } } -} +} \ No newline at end of file diff --git a/Resources/Locale/en-US/preferences/loadout-groups.ftl b/Resources/Locale/en-US/preferences/loadout-groups.ftl index 68a47cba19..28863268df 100644 --- a/Resources/Locale/en-US/preferences/loadout-groups.ftl +++ b/Resources/Locale/en-US/preferences/loadout-groups.ftl @@ -1,6 +1,7 @@ # Miscellaneous loadout-group-trinkets = Trinkets loadout-group-glasses = Glasses +loadout-group-backpack = Backpack # Command loadout-group-captain-head = Captain head @@ -19,7 +20,6 @@ loadout-group-hop-outerclothing = Head of Personnel outer clothing loadout-group-passenger-jumpsuit = Passenger jumpsuit loadout-group-passenger-mask = Passenger mask loadout-group-passenger-gloves = Passenger gloves -loadout-group-passenger-backpack = Passenger backpack loadout-group-passenger-outerclothing = Passenger outer clothing loadout-group-passenger-shoes = Passenger shoes @@ -36,12 +36,10 @@ loadout-group-librarian-jumpsuit = Librarian jumpsuit loadout-group-lawyer-jumpsuit = Lawyer jumpsuit loadout-group-lawyer-neck = Lawyer neck -loadout-group-lawyer-backpack = Lawyer backpack loadout-group-chaplain-head = Chaplain head loadout-group-chaplain-mask = Chaplain mask loadout-group-chaplain-jumpsuit = Chaplain jumpsuit -loadout-group-chaplain-backpack = Chaplain backpack loadout-group-chaplain-outerclothing = Chaplain outer clothing loadout-group-chaplain-neck = Chaplain neck @@ -67,13 +65,11 @@ loadout-group-mime-jumpsuit = Mime jumpsuit loadout-group-mime-backpack = Mime backpack loadout-group-mime-outerclothing = Mime outer clothing -loadout-group-musician-backpack = Musician backpack loadout-group-musician-outerclothing = Musician outer clothing # Cargo loadout-group-quartermaster-head = Quartermaster head loadout-group-quartermaster-jumpsuit = Quartermaster jumpsuit -loadout-group-quartermaster-backpack = Quartermaster backpack loadout-group-quartermaster-neck = Quartermaster neck loadout-group-quartermaster-outerclothing = Quartermaster outer clothing loadout-group-quartermaster-shoes = Quartermaster shoes @@ -91,7 +87,6 @@ loadout-group-salvage-specialist-shoes = Salvage Specialist shoes # Engineering loadout-group-chief-engineer-head = Chief Engineer head loadout-group-chief-engineer-jumpsuit = Chief Engineer jumpsuit -loadout-group-chief-engineer-backpack = Chief Engineer backpack loadout-group-chief-engineer-outerclothing = Chief Engineer outer clothing loadout-group-chief-engineer-neck = Chief Engineer neck loadout-group-chief-engineer-shoes = Chief Engineer shoes @@ -114,7 +109,6 @@ loadout-group-atmospheric-technician-shoes = Atmospheric Technician shoes loadout-group-research-director-head = Research Director head loadout-group-research-director-neck = Research Director neck loadout-group-research-director-jumpsuit = Research Director jumpsuit -loadout-group-research-director-backpack = Research Director backpack loadout-group-research-director-outerclothing = Research Director outer clothing loadout-group-research-director-shoes = Research Director shoes @@ -150,7 +144,6 @@ loadout-group-security-id = Security ID loadout-group-detective-head = Detective head loadout-group-detective-neck = Detective neck loadout-group-detective-jumpsuit = Detective jumpsuit -loadout-group-detective-backpack = Detective backpack loadout-group-detective-outerclothing = Detective outer clothing loadout-group-security-cadet-jumpsuit = Security cadet jumpsuit @@ -162,7 +155,6 @@ loadout-group-medical-mask = Medical mask loadout-group-chief-medical-officer-head = Chief Medical Officer head loadout-group-chief-medical-officer-jumpsuit = Chief Medical Officer jumpsuit loadout-group-chief-medical-officer-outerclothing = Chief Medical Officer outer clothing -loadout-group-chief-medical-officer-backpack = Chief Medical Officer backpack loadout-group-chief-medical-officer-shoes = Chief Medical Officer shoes loadout-group-chief-medical-officer-neck = Chief Medical Officer neck @@ -183,7 +175,6 @@ loadout-group-paramedic-head = Paramedic head loadout-group-paramedic-jumpsuit = Paramedic jumpsuit loadout-group-paramedic-outerclothing = Paramedic outer clothing loadout-group-paramedic-shoes = Paramedic shoes -loadout-group-paramedic-backpack = Paramedic backpack # Wildcards loadout-group-reporter-jumpsuit = Reporter jumpsuit diff --git a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/backpack.yml b/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/backpack.yml deleted file mode 100644 index 71c679a2aa..0000000000 --- a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/backpack.yml +++ /dev/null @@ -1,422 +0,0 @@ -- type: entity - parent: ClothingBackpack - id: ClothingBackpackFilled - noSpawn: true - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackClown - id: ClothingBackpackClownFilled - components: - - type: StorageFill - contents: - - id: BoxHug - - id: RubberStampClown - - id: CrayonRainbow - -- type: entity - noSpawn: true - parent: ClothingBackpackSecurity - id: ClothingBackpackSecurityFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalSecurity - - id: Flash - - id: MagazinePistol - -- type: entity - noSpawn: true - parent: ClothingBackpackSecurity - id: ClothingBackpackSecurityFilledDetective - components: - - type: StorageFill - contents: - - id: BoxSurvivalSecurity - - id: Flash - - id: ForensicPad - - id: ForensicScanner - -- type: entity - noSpawn: true - parent: ClothingBackpackMedical - id: ClothingBackpackMedicalFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - -- type: entity - noSpawn: true - parent: ClothingBackpackMedical - id: ClothingBackpackParamedicFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - - id: EmergencyRollerBedSpawnFolded - -- type: entity - noSpawn: true - parent: ClothingBackpackCaptain - id: ClothingBackpackCaptainFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- name: StationCharter - #- name: TelescopicBaton -- type: entity - noSpawn: true - parent: ClothingBackpackEngineering - id: ClothingBackpackChiefEngineerFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackScience - id: ClothingBackpackResearchDirectorFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpack - id: ClothingBackpackHOPFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackIan - id: ClothingBackpackHOPIanFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackMedical - id: ClothingBackpackCMOFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackCargo - id: ClothingBackpackQuartermasterFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackSecurity - id: ClothingBackpackHOSFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalSecurity - - id: Flash - - id: MagazinePistol - -- type: entity - noSpawn: true - parent: ClothingBackpackEngineering - id: ClothingBackpackEngineeringFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - -- type: entity - noSpawn: true - parent: ClothingBackpackAtmospherics - id: ClothingBackpackAtmosphericsFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - -- type: entity - noSpawn: true - parent: ClothingBackpackScience - id: ClothingBackpackScienceFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackHydroponics - id: ClothingBackpackHydroponicsFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackMime - id: ClothingBackpackMimeFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: RubberStampMime - -- type: entity - noSpawn: true - parent: ClothingBackpackChemistry - id: ClothingBackpackChemistryFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - -- type: entity - noSpawn: true - parent: ClothingBackpack - id: ClothingBackpackChaplainFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Bible - - id: RubberStampChaplain - -- type: entity - noSpawn: true - parent: ClothingBackpack - id: ClothingBackpackLawyerFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: RubberStampLawyer - -- type: entity - noSpawn: true - parent: ClothingBackpack - id: ClothingBackpackMusicianFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: AcousticGuitarInstrument - - id: SaxophoneInstrument - -- type: entity - noSpawn: true - parent: ClothingBackpack - id: ClothingBackpackLibrarianFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: BookRandom - -- type: entity - noSpawn: true - parent: ClothingBackpack - id: ClothingBackpackDetectiveFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Lighter - - id: CigPackBlack - - id: HandLabeler - - id: BoxForensicPad - -# ERT - -- type: entity - noSpawn: true - parent: ClothingBackpackERTLeader - id: ClothingBackpackERTLeaderFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - - id: WeaponDisabler - - id: MedicatedSuture - - id: RegenerativeMesh - - id: BoxZiptie - - id: CrowbarRed - - id: MagazineMagnum - -- type: entity - noSpawn: true - parent: ClothingBackpackERTSecurity - id: ClothingBackpackERTSecurityFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - - id: WeaponDisabler - - id: MedicatedSuture - - id: RegenerativeMesh - - id: BoxZiptie - - id: CrowbarRed - - id: MagazinePistol - -- type: entity - noSpawn: true - parent: ClothingBackpackERTMedical - id: ClothingBackpackERTMedicalFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - - id: Hypospray - - id: MedkitAdvancedFilled - - id: CrowbarRed - - id: OmnizineChemistryBottle - - id: EpinephrineChemistryBottle - - id: EpinephrineChemistryBottle - -- type: entity - noSpawn: true - parent: ClothingBackpackERTEngineer - id: ClothingBackpackERTEngineerFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - - id: trayScanner - - id: RCD - - id: RCDAmmo - amount: 2 - - id: CableMVStack - - id: CableHVStack - - id: CableApcStack - - id: SheetPlasteel - - id: SheetSteel - - id: SheetGlass - -- type: entity - noSpawn: true - parent: ClothingBackpackERTJanitor - id: ClothingBackpackERTJanitorFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - - id: LightReplacer - - id: BoxLightMixed - - id: BoxLightMixed - - id: Soap - - id: CrowbarRed - - id: AdvMopItem - -- type: entity - noSpawn: true - parent: ClothingBackpackERTChaplain - id: ClothingBackpackERTChaplainFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - - id: BoxCandle - - id: BoxBodyBag - - id: DrinkWaterMelonJuiceJug - - id: Lantern - - id: Lantern - - id: Bible - - id: CrowbarRed - - id: FoodBakedBunHotX - - id: FoodBakedBunHotX - - id: FoodBakedBunHotX - - id: FoodBakedBunHotX - - id: Lighter - -# Death Squad - -- type: entity - noSpawn: false - parent: ClothingBackpackERTSecurity - id: ClothingBackpackDeathSquadFilled - name: death squad backpack - description: Holds the kit of CentComm's most feared agents. - components: - - type: Storage - grid: - - 0,0,7,6 - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - - id: WeaponPulseRifle - - id: WeaponPulsePistol - - id: WeaponRevolverMateba - - id: SpeedLoaderMagnumAP - - id: SpeedLoaderMagnumAP - - id: BoxFlashbang - - id: ToolDebug # spanish army knife - - id: WelderExperimental - - id: Hypospray - - id: DeathAcidifierImplanter # crew will try to steal their amazing hardsuits - - id: FreedomImplanter - -# Cargo - -- type: entity - noSpawn: true - parent: ClothingBackpackCargo - id: ClothingBackpackCargoFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackSalvage - id: ClothingBackpackSalvageFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -# Pirate - -- type: entity - parent: ClothingBackpackSatchelLeather - id: ClothingBackpackPirateFilled - suffix: Filled, Pirate - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Cutlass - - id: WeaponRevolverPirate - - id: ClothingEyesEyepatch diff --git a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/duffelbag.yml b/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/duffelbag.yml deleted file mode 100644 index 477b8b2f18..0000000000 --- a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/duffelbag.yml +++ /dev/null @@ -1,275 +0,0 @@ -- type: entity - noSpawn: true - parent: ClothingBackpackDuffel - id: ClothingBackpackDuffelFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelClown - id: ClothingBackpackDuffelClownFilled - components: - - type: StorageFill - contents: - - id: BoxHug - - id: RubberStampClown - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelSecurity - id: ClothingBackpackDuffelSecurityFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalSecurity - - id: Flash - - id: MagazinePistol - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelSecurity - id: ClothingBackpackDuffelSecurityFilledDetective - components: - - type: StorageFill - contents: - - id: BoxSurvivalSecurity - - id: Flash - - id: ForensicPad - - id: ForensicScanner - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelBrigmedic - id: ClothingBackpackDuffelBrigmedicFilled - components: - - type: StorageFill - contents: - - id: Flash - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelMedical - id: ClothingBackpackDuffelMedicalFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelMedical - id: ClothingBackpackDuffelParamedicFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - - id: EmergencyRollerBedSpawnFolded - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelCaptain - id: ClothingBackpackDuffelCaptainFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- name: StationCharter - #- name: TelescopicBaton -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelEngineering - id: ClothingBackpackDuffelChiefEngineerFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelScience - id: ClothingBackpackDuffelResearchDirectorFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffel - id: ClothingBackpackDuffelHOPFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelMedical - id: ClothingBackpackDuffelCMOFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelCargo - id: ClothingBackpackDuffelQuartermasterFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelSecurity - id: ClothingBackpackDuffelHOSFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalSecurity - - id: Flash - - id: MagazinePistol - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelEngineering - id: ClothingBackpackDuffelEngineeringFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelAtmospherics - id: ClothingBackpackDuffelAtmosphericsFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelScience - id: ClothingBackpackDuffelScienceFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelHydroponics - id: ClothingBackpackDuffelHydroponicsFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelMime - id: ClothingBackpackDuffelMimeFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: RubberStampMime - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelChemistry - id: ClothingBackpackDuffelChemistryFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffel - id: ClothingBackpackDuffelChaplainFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Bible - - id: RubberStampChaplain - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffel - id: ClothingBackpackDuffelLawyerFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: RubberStampLawyer - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffel - id: ClothingBackpackDuffelMusicianFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: AcousticGuitarInstrument - - id: SaxophoneInstrument - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffel - id: ClothingBackpackDuffelLibrarianFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: BookRandom - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffel - id: ClothingBackpackDuffelDetectiveFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Lighter - - id: CigPackBlack - - id: BoxForensicPad - - id: HandLabeler - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelCargo - id: ClothingBackpackDuffelCargoFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackDuffelSalvage - id: ClothingBackpackDuffelSalvageFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival diff --git a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/satchel.yml b/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/satchel.yml deleted file mode 100644 index fe41168829..0000000000 --- a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/satchel.yml +++ /dev/null @@ -1,300 +0,0 @@ -- type: entity - noSpawn: true - parent: ClothingBackpackSatchel - id: ClothingBackpackSatchelFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchel - id: ClothingBackpackSatchelTools - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Crowbar - - id: Wrench - - id: Screwdriver - - id: Wirecutter - - id: Welder - - id: Multitool - -- type: entity - parent: ClothingBackpackSatchelClown - id: ClothingBackpackSatchelClownFilled - components: - - type: StorageFill - contents: - - id: BoxHug - - id: RubberStampClown - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelSecurity - id: ClothingBackpackSatchelSecurityFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalSecurity - - id: Flash - - id: MagazinePistol - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelSecurity - id: ClothingBackpackSatchelSecurityFilledDetective - components: - - type: StorageFill - contents: - - id: BoxSurvivalSecurity - - id: Flash - - id: ForensicPad - - id: ForensicScanner - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelBrigmedic - id: ClothingBackpackSatchelBrigmedicFilled - components: - - type: StorageFill - contents: - - id: Flash - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelMedical - id: ClothingBackpackSatchelMedicalFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelMedical - id: ClothingBackpackSatchelParamedicFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - - id: EmergencyRollerBedSpawnFolded - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelCaptain - id: ClothingBackpackSatchelCaptainFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- name: StationCharter - #- name: TelescopicBaton -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelEngineering - id: ClothingBackpackSatchelChiefEngineerFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelScience - id: ClothingBackpackSatchelResearchDirectorFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchel - id: ClothingBackpackSatchelHOPFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelMedical - id: ClothingBackpackSatchelCMOFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelCargo - id: ClothingBackpackSatchelQuartermasterFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Flash - #- id: TelescopicBaton - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelSecurity - id: ClothingBackpackSatchelHOSFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalSecurity - - id: Flash - - id: MagazinePistol - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelEngineering - id: ClothingBackpackSatchelEngineeringFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelAtmospherics - id: ClothingBackpackSatchelAtmosphericsFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelScience - id: ClothingBackpackSatchelScienceFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelHydroponics - id: ClothingBackpackSatchelHydroponicsFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelChemistry - id: ClothingBackpackSatchelChemistryFilled - components: - - type: StorageFill - contents: - - id: BoxSurvivalMedical - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchel - id: ClothingBackpackSatchelChaplainFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: Bible - - id: RubberStampChaplain - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchel - id: ClothingBackpackSatchelLawyerFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: RubberStampLawyer - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchel - id: ClothingBackpackSatchelMusicianFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: AcousticGuitarInstrument - - id: SaxophoneInstrument - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchel - id: ClothingBackpackSatchelLibrarianFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: BookRandom - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchel - id: ClothingBackpackSatchelDetectiveFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: BoxForensicPad - - id: Lighter - - id: CigPackBlack - - id: HandLabeler - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelCargo - id: ClothingBackpackSatchelCargoFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelSalvage - id: ClothingBackpackSatchelSalvageFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelMime - id: ClothingBackpackSatchelMimeFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - - id: RubberStampMime - -- type: entity - noSpawn: true - parent: ClothingBackpackSatchelHolding - id: ClothingBackpackSatchelHoldingAdmin - components: - - type: StorageFill - contents: - - id: GasAnalyzer - - id: trayScanner - - id: AccessConfiguratorUniversal - - type: Unremoveable diff --git a/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml b/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml index 4e4b6d9382..ae57ea6342 100644 --- a/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml +++ b/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml @@ -13,23 +13,6 @@ - id: Retractor - id: Scalpel -- type: entity - id: ClothingBackpackDuffelCBURNFilled - parent: ClothingBackpackDuffelCBURN - suffix: Filled - components: - - type: StorageFill - contents: - - id: BoxSurvivalEngineering - - id: WeaponShotgunDoubleBarreled - - id: BoxShotgunIncendiary - amount: 2 - - id: GrenadeFlashBang - amount: 2 - - id: PillAmbuzolPlus - - id: PillAmbuzol - amount: 4 - - type: entity parent: ClothingBackpackDuffelSyndicateMedicalBundle id: ClothingBackpackDuffelSyndicateFilledMedical @@ -320,38 +303,6 @@ - id: PillAmbuzol amount: 3 -- type: entity - parent: ClothingBackpackDuffelSyndicateBundle - id: ClothingBackpackDuffelSyndicateOperative - name: operative duffelbag - components: - - type: StorageFill - contents: - - id: BoxSurvivalSyndicate - - id: WeaponPistolViper - - id: PinpointerSyndicateNuclear - - id: DeathAcidifierImplanter - - -- type: entity - parent: ClothingBackpackDuffelSyndicateMedicalBundle - id: ClothingBackpackDuffelSyndicateOperativeMedic - name: operative medic duffelbag - description: A large duffel bag for holding extra medical supplies. - components: - - type: StorageFill - contents: - - id: SyndiHypo - - id: BoxSurvivalSyndicate - - id: SawAdvanced - - id: Cautery - - id: CombatKnife - - id: WeaponPistolViper - - id: PinpointerSyndicateNuclear - - id: HandheldHealthAnalyzer - - id: CombatMedipen - - id: DeathAcidifierImplanter - - type: entity parent: ClothingBackpackDuffelSyndicateMedicalBundle id: ClothingBackpackDuffelSyndicateMedicalBundleFilled diff --git a/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml b/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml index 5d9fc64e12..ceaa744295 100644 --- a/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml +++ b/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml @@ -257,6 +257,16 @@ - type: Sprite sprite: Clothing/Back/Backpacks/ertchaplain.rsi +- type: entity + parent: ClothingBackpackERTSecurity + id: ClothingBackpackDeathSquad + name: death squad backpack + description: Holds the kit of CentComm's most feared agents. + components: + - type: Storage + grid: + - 0,0,7,6 + #Syndicate - type: entity parent: ClothingBackpack diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/human.yml b/Resources/Prototypes/Entities/Mobs/NPCs/human.yml index 2133694342..ec2ed5070d 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/human.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/human.yml @@ -7,7 +7,7 @@ - type: InputMover - type: MobMover - type: Loadout - prototypes: [PassengerGear] + prototypes: [LimitedPassengerGear] - type: NpcFactionMember factions: - NanoTrasen diff --git a/Resources/Prototypes/Loadouts/Jobs/Cargo/cargo_technician.yml b/Resources/Prototypes/Loadouts/Jobs/Cargo/cargo_technician.yml index f2922d8cfd..ecc3534458 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Cargo/cargo_technician.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Cargo/cargo_technician.yml @@ -35,7 +35,7 @@ - type: startingGear id: CargoTechnicianBackpack equipment: - back: ClothingBackpackCargoFilled + back: ClothingBackpackCargo - type: loadout id: CargoTechnicianSatchel @@ -44,7 +44,7 @@ - type: startingGear id: CargoTechnicianSatchel equipment: - back: ClothingBackpackSatchelCargoFilled + back: ClothingBackpackSatchelCargo - type: loadout id: CargoTechnicianDuffel @@ -53,7 +53,7 @@ - type: startingGear id: CargoTechnicianDuffel equipment: - back: ClothingBackpackDuffelCargoFilled + back: ClothingBackpackDuffelCargo # OuterClothing - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml b/Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml index d738306c65..f02da12316 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml @@ -82,34 +82,6 @@ equipment: neck: ClothingNeckMantleQM -# Back -- type: loadout - id: QuartermasterBackpack - equipment: QuartermasterBackpack - -- type: startingGear - id: QuartermasterBackpack - equipment: - back: ClothingBackpackQuartermasterFilled - -- type: loadout - id: QuartermasterSatchel - equipment: QuartermasterSatchel - -- type: startingGear - id: QuartermasterSatchel - equipment: - back: ClothingBackpackSatchelQuartermasterFilled - -- type: loadout - id: QuartermasterDuffel - equipment: QuartermasterDuffel - -- type: startingGear - id: QuartermasterDuffel - equipment: - back: ClothingBackpackDuffelQuartermasterFilled - # OuterClothing - type: loadout id: QuartermasterWintercoat diff --git a/Resources/Prototypes/Loadouts/Jobs/Cargo/salvage_specialist.yml b/Resources/Prototypes/Loadouts/Jobs/Cargo/salvage_specialist.yml index 2b42fab50a..abf787d510 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Cargo/salvage_specialist.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Cargo/salvage_specialist.yml @@ -6,7 +6,7 @@ - type: startingGear id: SalvageSpecialistBackpack equipment: - back: ClothingBackpackSalvageFilled + back: ClothingBackpackSalvage - type: loadout id: SalvageSpecialistSatchel @@ -15,7 +15,7 @@ - type: startingGear id: SalvageSpecialistSatchel equipment: - back: ClothingBackpackSatchelSalvageFilled + back: ClothingBackpackSatchelSalvage - type: loadout id: SalvageSpecialistDuffel @@ -24,7 +24,7 @@ - type: startingGear id: SalvageSpecialistDuffel equipment: - back: ClothingBackpackDuffelSalvageFilled + back: ClothingBackpackDuffelSalvage # OuterClothing - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/botanist.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/botanist.yml index 5ab2cecd15..7dba761a18 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/botanist.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/botanist.yml @@ -53,7 +53,7 @@ - type: startingGear id: BotanistBackpack equipment: - back: ClothingBackpackHydroponicsFilled + back: ClothingBackpackHydroponics - type: loadout id: BotanistSatchel @@ -62,7 +62,7 @@ - type: startingGear id: BotanistSatchel equipment: - back: ClothingBackpackSatchelHydroponicsFilled + back: ClothingBackpackSatchelHydroponics - type: loadout id: BotanistDuffel @@ -71,7 +71,7 @@ - type: startingGear id: BotanistDuffel equipment: - back: ClothingBackpackDuffelHydroponicsFilled + back: ClothingBackpackDuffelHydroponics # Outer clothing - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/chaplain.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/chaplain.yml index 623e18a01d..160f06d5cf 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/chaplain.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/chaplain.yml @@ -91,34 +91,6 @@ equipment: jumpsuit: ClothingUniformJumpsuitMonasticRobeLight -# Back -- type: loadout - id: ChaplainBackpack - equipment: ChaplainBackpack - -- type: startingGear - id: ChaplainBackpack - equipment: - back: ClothingBackpackChaplainFilled - -- type: loadout - id: ChaplainSatchel - equipment: ChaplainSatchel - -- type: startingGear - id: ChaplainSatchel - equipment: - back: ClothingBackpackSatchelChaplainFilled - -- type: loadout - id: ChaplainDuffel - equipment: ChaplainDuffel - -- type: startingGear - id: ChaplainDuffel - equipment: - back: ClothingBackpackDuffelChaplainFilled - # Neck - type: loadout id: ChaplainNeck diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/clown.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/clown.yml index 8b315d50c1..d3a3ce2e6c 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/clown.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/clown.yml @@ -35,7 +35,7 @@ - type: startingGear id: ClownBackpack equipment: - back: ClothingBackpackClownFilled + back: ClothingBackpackClown - type: loadout id: ClownSatchel @@ -44,7 +44,7 @@ - type: startingGear id: ClownSatchel equipment: - back: ClothingBackpackSatchelClownFilled + back: ClothingBackpackSatchelClown - type: loadout id: ClownDuffel @@ -53,7 +53,7 @@ - type: startingGear id: ClownDuffel equipment: - back: ClothingBackpackDuffelClownFilled + back: ClothingBackpackDuffelClown # Shoes - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/lawyer.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/lawyer.yml index 5edd3ecf76..00be124430 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/lawyer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/lawyer.yml @@ -97,32 +97,4 @@ - type: startingGear id: LawyerNeck equipment: - neck: ClothingNeckLawyerbadge - -# Backpack -- type: loadout - id: LawyerBackpack - equipment: LawyerBackpack - -- type: startingGear - id: LawyerBackpack - equipment: - back: ClothingBackpackLawyerFilled - -- type: loadout - id: LawyerSatchel - equipment: LawyerSatchel - -- type: startingGear - id: LawyerSatchel - equipment: - back: ClothingBackpackSatchelLawyerFilled - -- type: loadout - id: LawyerDuffel - equipment: LawyerDuffel - -- type: startingGear - id: LawyerDuffel - equipment: - back: ClothingBackpackDuffelLawyerFilled + neck: ClothingNeckLawyerbadge \ No newline at end of file diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/mime.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/mime.yml index 95647052b8..e60b2807ff 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/mime.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/mime.yml @@ -81,7 +81,7 @@ - type: startingGear id: MimeBackpack equipment: - back: ClothingBackpackMimeFilled + back: ClothingBackpackMime - type: loadout id: MimeSatchel @@ -90,7 +90,7 @@ - type: startingGear id: MimeSatchel equipment: - back: ClothingBackpackSatchelMimeFilled + back: ClothingBackpackSatchelMime - type: loadout id: MimeDuffel @@ -99,7 +99,7 @@ - type: startingGear id: MimeDuffel equipment: - back: ClothingBackpackDuffelMimeFilled + back: ClothingBackpackDuffelMime # Outerclothing - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/musician.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/musician.yml index 579481c0ee..c26da03628 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/musician.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/musician.yml @@ -1,31 +1,3 @@ -# Back -- type: loadout - id: MusicianBackpack - equipment: MusicianBackpack - -- type: startingGear - id: MusicianBackpack - equipment: - back: ClothingBackpackMusicianFilled - -- type: loadout - id: MusicianSatchel - equipment: MusicianSatchel - -- type: startingGear - id: MusicianSatchel - equipment: - back: ClothingBackpackSatchelMusicianFilled - -- type: loadout - id: MusicianDuffel - equipment: MusicianDuffel - -- type: startingGear - id: MusicianDuffel - equipment: - back: ClothingBackpackDuffelMusicianFilled - # Outerclothing - type: loadout id: MusicianWintercoat diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml index 5c09b1299f..84685e1702 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml @@ -75,7 +75,7 @@ - type: startingGear id: CommonBackpack equipment: - back: ClothingBackpackFilled + back: ClothingBackpack - type: loadout id: CommonSatchel @@ -84,7 +84,7 @@ - type: startingGear id: CommonSatchel equipment: - back: ClothingBackpackSatchelFilled + back: ClothingBackpackSatchel - type: loadout id: CommonDuffel @@ -93,7 +93,7 @@ - type: startingGear id: CommonDuffel equipment: - back: ClothingBackpackDuffelFilled + back: ClothingBackpackDuffel # Gloves - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Command/captain.yml b/Resources/Prototypes/Loadouts/Jobs/Command/captain.yml index 6ec2d7f789..50786d6154 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Command/captain.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Command/captain.yml @@ -90,7 +90,7 @@ - type: startingGear id: CaptainBackpack equipment: - back: ClothingBackpackCaptainFilled + back: ClothingBackpackCaptain - type: loadout id: CaptainSatchel @@ -99,7 +99,7 @@ - type: startingGear id: CaptainSatchel equipment: - back: ClothingBackpackSatchelCaptainFilled + back: ClothingBackpackSatchelCaptain - type: loadout id: CaptainDuffel @@ -108,7 +108,7 @@ - type: startingGear id: CaptainDuffel equipment: - back: ClothingBackpackDuffelCaptainFilled + back: ClothingBackpackDuffelCaptain # Outer clothing - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml index 0abba16f12..1e4e5d527f 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml @@ -57,33 +57,6 @@ neck: ClothingNeckMantleHOP # Back -- type: loadout - id: HoPBackpack - equipment: HoPBackpack - -- type: startingGear - id: HoPBackpack - equipment: - back: ClothingBackpackHOPFilled - -- type: loadout - id: HoPSatchel - equipment: HoPSatchel - -- type: startingGear - id: HoPSatchel - equipment: - back: ClothingBackpackSatchelHOPFilled - -- type: loadout - id: HoPDuffel - equipment: HoPDuffel - -- type: startingGear - id: HoPDuffel - equipment: - back: ClothingBackpackDuffelHOPFilled - - type: loadout id: HoPBackpackIan equipment: HoPBackpackIan @@ -94,7 +67,7 @@ - type: startingGear id: HoPBackpackIan equipment: - back: ClothingBackpackHOPIanFilled + back: ClothingBackpackIan # Outerclothing - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Engineering/atmospheric_technician.yml b/Resources/Prototypes/Loadouts/Jobs/Engineering/atmospheric_technician.yml index fc9bbbf282..5f1e753b9f 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Engineering/atmospheric_technician.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Engineering/atmospheric_technician.yml @@ -34,7 +34,7 @@ - type: startingGear id: AtmosphericTechnicianBackpack equipment: - back: ClothingBackpackAtmosphericsFilled + back: ClothingBackpackAtmospherics - type: loadout id: AtmosphericTechnicianSatchel @@ -43,7 +43,7 @@ - type: startingGear id: AtmosphericTechnicianSatchel equipment: - back: ClothingBackpackSatchelAtmosphericsFilled + back: ClothingBackpackSatchelAtmospherics - type: loadout id: AtmosphericTechnicianDuffel @@ -52,7 +52,7 @@ - type: startingGear id: AtmosphericTechnicianDuffel equipment: - back: ClothingBackpackDuffelAtmosphericsFilled + back: ClothingBackpackDuffelAtmospherics # OuterClothing - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml b/Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml index 2373205066..5bfbb8c201 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml @@ -68,34 +68,6 @@ equipment: neck: ClothingNeckMantleCE -# Back -- type: loadout - id: ChiefEngineerBackpack - equipment: ChiefEngineerBackpack - -- type: startingGear - id: ChiefEngineerBackpack - equipment: - back: ClothingBackpackChiefEngineerFilled - -- type: loadout - id: ChiefEngineerSatchel - equipment: ChiefEngineerSatchel - -- type: startingGear - id: ChiefEngineerSatchel - equipment: - back: ClothingBackpackSatchelChiefEngineerFilled - -- type: loadout - id: ChiefEngineerDuffel - equipment: ChiefEngineerDuffel - -- type: startingGear - id: ChiefEngineerDuffel - equipment: - back: ClothingBackpackDuffelChiefEngineerFilled - # OuterClothing - type: loadout id: ChiefEngineerWintercoat diff --git a/Resources/Prototypes/Loadouts/Jobs/Engineering/station_engineer.yml b/Resources/Prototypes/Loadouts/Jobs/Engineering/station_engineer.yml index 7104598b3a..7f9e20e2aa 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Engineering/station_engineer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Engineering/station_engineer.yml @@ -118,7 +118,7 @@ - type: startingGear id: StationEngineerBackpack equipment: - back: ClothingBackpackEngineeringFilled + back: ClothingBackpackEngineering - type: loadout id: StationEngineerSatchel @@ -127,7 +127,7 @@ - type: startingGear id: StationEngineerSatchel equipment: - back: ClothingBackpackSatchelEngineeringFilled + back: ClothingBackpackSatchelEngineering - type: loadout id: StationEngineerDuffel @@ -136,7 +136,7 @@ - type: startingGear id: StationEngineerDuffel equipment: - back: ClothingBackpackDuffelEngineeringFilled + back: ClothingBackpackDuffelEngineering # OuterClothing - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Medical/chemist.yml b/Resources/Prototypes/Loadouts/Jobs/Medical/chemist.yml index 9b05120b1a..289910c8c8 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Medical/chemist.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Medical/chemist.yml @@ -25,7 +25,7 @@ - type: startingGear id: ChemistBackpack equipment: - back: ClothingBackpackChemistryFilled + back: ClothingBackpackChemistry - type: loadout id: ChemistSatchel @@ -34,7 +34,7 @@ - type: startingGear id: ChemistSatchel equipment: - back: ClothingBackpackSatchelChemistryFilled + back: ClothingBackpackSatchelChemistry - type: loadout id: ChemistDuffel @@ -43,7 +43,7 @@ - type: startingGear id: ChemistDuffel equipment: - back: ClothingBackpackDuffelChemistryFilled + back: ClothingBackpackDuffelChemistry # Outer clothing - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Medical/chief_medical_officer.yml b/Resources/Prototypes/Loadouts/Jobs/Medical/chief_medical_officer.yml index 8cf8148518..f7436a4281 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Medical/chief_medical_officer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Medical/chief_medical_officer.yml @@ -73,34 +73,6 @@ equipment: neck: ClothingNeckMantleCMO -# Back -- type: loadout - id: ChiefMedicalOfficerBackpack - equipment: ChiefMedicalOfficerBackpack - -- type: startingGear - id: ChiefMedicalOfficerBackpack - equipment: - back: ClothingBackpackCMOFilled - -- type: loadout - id: ChiefMedicalOfficerSatchel - equipment: ChiefMedicalOfficerSatchel - -- type: startingGear - id: ChiefMedicalOfficerSatchel - equipment: - back: ClothingBackpackSatchelCMOFilled - -- type: loadout - id: ChiefMedicalOfficerDuffel - equipment: ChiefMedicalOfficerDuffel - -- type: startingGear - id: ChiefMedicalOfficerDuffel - equipment: - back: ClothingBackpackDuffelCMOFilled - # Outer clothing - type: loadout id: ChiefMedicalOfficerLabCoat diff --git a/Resources/Prototypes/Loadouts/Jobs/Medical/medical_doctor.yml b/Resources/Prototypes/Loadouts/Jobs/Medical/medical_doctor.yml index ebad9b9f84..a64d60ba1f 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Medical/medical_doctor.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Medical/medical_doctor.yml @@ -178,7 +178,7 @@ - type: startingGear id: MedicalDoctorBackpack equipment: - back: ClothingBackpackMedicalFilled + back: ClothingBackpackMedical - type: loadout id: MedicalDoctorSatchel @@ -187,7 +187,7 @@ - type: startingGear id: MedicalDoctorSatchel equipment: - back: ClothingBackpackSatchelMedicalFilled + back: ClothingBackpackSatchelMedical - type: loadout id: MedicalDoctorDuffel @@ -196,7 +196,7 @@ - type: startingGear id: MedicalDoctorDuffel equipment: - back: ClothingBackpackDuffelMedicalFilled + back: ClothingBackpackDuffelMedical # OuterClothing - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Medical/paramedic.yml b/Resources/Prototypes/Loadouts/Jobs/Medical/paramedic.yml index f393109eea..7fd8d4a32f 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Medical/paramedic.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Medical/paramedic.yml @@ -27,34 +27,6 @@ equipment: jumpsuit: ClothingUniformJumpskirtParamedic -# Back -- type: loadout - id: ParamedicBackpack - equipment: ParamedicBackpack - -- type: startingGear - id: ParamedicBackpack - equipment: - back: ClothingBackpackParamedicFilled - -- type: loadout - id: ParamedicSatchel - equipment: ParamedicSatchel - -- type: startingGear - id: ParamedicSatchel - equipment: - back: ClothingBackpackSatchelParamedicFilled - -- type: loadout - id: ParamedicDuffel - equipment: ParamedicDuffel - -- type: startingGear - id: ParamedicDuffel - equipment: - back: ClothingBackpackDuffelParamedicFilled - # Outer clothing - type: loadout id: ParamedicWindbreaker diff --git a/Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml b/Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml index 4d81b801e8..748e5d217c 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml @@ -43,34 +43,6 @@ equipment: jumpsuit: ClothingUniformJumpskirtResearchDirector -# Back -- type: loadout - id: ResearchDirectorBackpack - equipment: ResearchDirectorBackpack - -- type: startingGear - id: ResearchDirectorBackpack - equipment: - back: ClothingBackpackResearchDirectorFilled - -- type: loadout - id: ResearchDirectorSatchel - equipment: ResearchDirectorSatchel - -- type: startingGear - id: ResearchDirectorSatchel - equipment: - back: ClothingBackpackSatchelResearchDirectorFilled - -- type: loadout - id: ResearchDirectorDuffel - equipment: ResearchDirectorDuffel - -- type: startingGear - id: ResearchDirectorDuffel - equipment: - back: ClothingBackpackDuffelResearchDirectorFilled - # OuterClothing - type: loadout id: ResearchDirectorLabCoat diff --git a/Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml b/Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml index c03ae4cfbc..ee30a9cf41 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml @@ -120,7 +120,7 @@ - type: startingGear id: ScientistBackpack equipment: - back: ClothingBackpackScienceFilled + back: ClothingBackpackScience - type: loadout id: ScientistSatchel @@ -129,7 +129,7 @@ - type: startingGear id: ScientistSatchel equipment: - back: ClothingBackpackSatchelScienceFilled + back: ClothingBackpackSatchelScience - type: loadout id: ScientistDuffel @@ -138,7 +138,7 @@ - type: startingGear id: ScientistDuffel equipment: - back: ClothingBackpackDuffelScienceFilled + back: ClothingBackpackDuffelScience # OuterClothing - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Security/detective.yml b/Resources/Prototypes/Loadouts/Jobs/Security/detective.yml index c16d24e5b8..888ed971e1 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Security/detective.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Security/detective.yml @@ -64,34 +64,6 @@ equipment: jumpsuit: ClothingUniformJumpskirtDetectiveGrey -# Back -- type: loadout - id: DetectiveBackpack - equipment: DetectiveBackpack - -- type: startingGear - id: DetectiveBackpack - equipment: - back: ClothingBackpackSecurityFilledDetective - -- type: loadout - id: DetectiveSatchel - equipment: DetectiveSatchel - -- type: startingGear - id: DetectiveSatchel - equipment: - back: ClothingBackpackSatchelSecurityFilledDetective - -- type: loadout - id: DetectiveDuffel - equipment: DetectiveDuffel - -- type: startingGear - id: DetectiveDuffel - equipment: - back: ClothingBackpackDuffelSecurityFilledDetective - # OuterClothing - type: loadout id: DetectiveArmorVest diff --git a/Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml b/Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml index b2b9e1e0c1..321cc46feb 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml @@ -110,7 +110,7 @@ - type: startingGear id: SecurityBackpack equipment: - back: ClothingBackpackSecurityFilled + back: ClothingBackpackSecurity - type: loadout id: SecuritySatchel @@ -119,7 +119,7 @@ - type: startingGear id: SecuritySatchel equipment: - back: ClothingBackpackSatchelSecurityFilled + back: ClothingBackpackSatchelSecurity - type: loadout id: SecurityDuffel @@ -128,7 +128,7 @@ - type: startingGear id: SecurityDuffel equipment: - back: ClothingBackpackDuffelSecurityFilled + back: ClothingBackpackDuffelSecurity # Belt - type: loadout diff --git a/Resources/Prototypes/Loadouts/loadout_groups.yml b/Resources/Prototypes/Loadouts/loadout_groups.yml index d889514a33..7f8a9ff4e4 100644 --- a/Resources/Prototypes/Loadouts/loadout_groups.yml +++ b/Resources/Prototypes/Loadouts/loadout_groups.yml @@ -103,9 +103,9 @@ id: HoPBackpack name: loadout-group-hop-backpack loadouts: - - HoPBackpack - - HoPSatchel - - HoPDuffel + - CommonBackpack + - CommonSatchel + - CommonDuffel - HoPBackpackIan - type: loadoutGroup @@ -122,8 +122,8 @@ loadouts: - GreyJumpsuit - GreyJumpskirt - - RainbowJumpsuit - AncientJumpsuit + - RainbowJumpsuit - type: loadoutGroup id: PassengerFace @@ -141,7 +141,7 @@ - type: loadoutGroup id: CommonBackpack - name: loadout-group-passenger-backpack + name: loadout-group-backpack loadouts: - CommonBackpack - CommonSatchel @@ -247,14 +247,6 @@ loadouts: - LawyerNeck -- type: loadoutGroup - id: LawyerBackpack - name: loadout-group-lawyer-backpack - loadouts: - - LawyerBackpack - - LawyerSatchel - - LawyerDuffel - - type: loadoutGroup id: ChaplainHead name: loadout-group-chaplain-head @@ -282,14 +274,6 @@ - ChaplainRobesLight - ChaplainRobesDark -- type: loadoutGroup - id: ChaplainBackpack - name: loadout-group-chaplain-backpack - loadouts: - - ChaplainBackpack - - ChaplainSatchel - - ChaplainDuffel - - type: loadoutGroup id: ChaplainOuterClothing name: loadout-group-chaplain-outerclothing @@ -446,14 +430,6 @@ loadouts: - MimeWintercoat -- type: loadoutGroup - id: MusicianBackpack - name: loadout-group-musician-backpack - loadouts: - - MusicianBackpack - - MusicianSatchel - - MusicianDuffel - - type: loadoutGroup id: MusicianOuterClothing name: loadout-group-musician-outerclothing @@ -480,14 +456,6 @@ - QuartermasterTurtleneckSkirt - QuartermasterFormalSuit -- type: loadoutGroup - id: QuartermasterBackpack - name: loadout-group-quartermaster-backpack - loadouts: - - QuartermasterBackpack - - QuartermasterSatchel - - QuartermasterDuffel - - type: loadoutGroup id: QuartermasterNeck name: loadout-group-quartermaster-neck @@ -586,14 +554,6 @@ - ChiefEngineerTurtleneck - ChiefEngineerTurtleneckSkirt -- type: loadoutGroup - id: ChiefEngineerBackpack - name: loadout-group-chief-engineer-backpack - loadouts: - - ChiefEngineerBackpack - - ChiefEngineerSatchel - - ChiefEngineerDuffel - - type: loadoutGroup id: ChiefEngineerNeck name: loadout-group-chief-engineer-neck @@ -725,14 +685,6 @@ - ResearchDirectorJumpsuit - ResearchDirectorJumpskirt -- type: loadoutGroup - id: ResearchDirectorBackpack - name: loadout-group-research-director-backpack - loadouts: - - ResearchDirectorBackpack - - ResearchDirectorSatchel - - ResearchDirectorDuffel - - type: loadoutGroup id: ResearchDirectorOuterClothing name: loadout-group-research-director-outerclothing @@ -964,14 +916,6 @@ - NoirJumpsuit - NoirJumpskirt -- type: loadoutGroup - id: DetectiveBackpack - name: loadout-group-detective-backpack - loadouts: - - DetectiveBackpack - - DetectiveSatchel - - DetectiveDuffel - - type: loadoutGroup id: DetectiveOuterClothing name: loadout-group-detective-outerclothing @@ -1013,14 +957,6 @@ - ChiefMedicalOfficerLabCoat - ChiefMedicalOfficerWintercoat -- type: loadoutGroup - id: ChiefMedicalOfficerBackpack - name: loadout-group-chief-medical-officer-backpack - loadouts: - - ChiefMedicalOfficerBackpack - - ChiefMedicalOfficerSatchel - - ChiefMedicalOfficerDuffel - - type: loadoutGroup id: ChiefMedicalOfficerNeck name: loadout-group-chief-medical-officer-neck @@ -1160,14 +1096,6 @@ - ParamedicWindbreaker - ParamedicWintercoat -- type: loadoutGroup - id: ParamedicBackpack - name: loadout-group-paramedic-backpack - loadouts: - - ParamedicBackpack - - ParamedicSatchel - - ParamedicDuffel - - type: loadoutGroup id: ParamedicShoes name: loadout-group-paramedic-shoes diff --git a/Resources/Prototypes/Loadouts/role_loadouts.yml b/Resources/Prototypes/Loadouts/role_loadouts.yml index 5bc6fc4572..4ff2775dfc 100644 --- a/Resources/Prototypes/Loadouts/role_loadouts.yml +++ b/Resources/Prototypes/Loadouts/role_loadouts.yml @@ -75,7 +75,7 @@ groups: - LawyerNeck - LawyerJumpsuit - - LawyerBackpack + - CommonBackpack - Glasses - Trinkets @@ -86,7 +86,7 @@ - ChaplainMask - ChaplainNeck - ChaplainJumpsuit - - ChaplainBackpack + - CommonBackpack - ChaplainOuterClothing - Glasses - Trinkets @@ -137,7 +137,7 @@ - type: roleLoadout id: JobMusician groups: - - MusicianBackpack + - CommonBackpack - MusicianOuterClothing - Glasses - Trinkets @@ -149,7 +149,7 @@ - QuartermasterHead - QuartermasterNeck - QuartermasterJumpsuit - - QuartermasterBackpack + - CargoTechnicianBackpack - QuartermasterOuterClothing - QuartermasterShoes - Glasses @@ -181,7 +181,7 @@ groups: - ChiefEngineerHead - ChiefEngineerJumpsuit - - ChiefEngineerBackpack + - StationEngineerBackpack - ChiefEngineerNeck - ChiefEngineerOuterClothing - ChiefEngineerShoes @@ -221,7 +221,7 @@ - ResearchDirectorHead - ResearchDirectorNeck - ResearchDirectorJumpsuit - - ResearchDirectorBackpack + - ScientistBackpack - ResearchDirectorOuterClothing - ScientistGloves - ResearchDirectorShoes @@ -292,7 +292,7 @@ - DetectiveHead - DetectiveNeck - DetectiveJumpsuit - - DetectiveBackpack + - SecurityBackpack - DetectiveOuterClothing - SecurityShoes - Trinkets @@ -312,7 +312,7 @@ - MedicalMask - ChiefMedicalOfficerJumpsuit - MedicalGloves - - ChiefMedicalOfficerBackpack + - MedicalBackpack - ChiefMedicalOfficerOuterClothing - ChiefMedicalOfficerNeck - ChiefMedicalOfficerShoes @@ -359,7 +359,7 @@ - MedicalMask - ParamedicJumpsuit - MedicalGloves - - ParamedicBackpack + - MedicalBackpack - ParamedicOuterClothing - ParamedicShoes - Glasses diff --git a/Resources/Prototypes/Roles/Antags/Thief.yml b/Resources/Prototypes/Roles/Antags/Thief.yml index 131db8cf1d..d85d366f6b 100644 --- a/Resources/Prototypes/Roles/Antags/Thief.yml +++ b/Resources/Prototypes/Roles/Antags/Thief.yml @@ -4,3 +4,10 @@ antagonist: true setPreference: true objective: roles-antag-thief-objective + +- type: startingGear + id: ThiefGear + storage: + back: + - ToolboxThief + - ClothingHandsChameleonThief \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Antags/ninja.yml b/Resources/Prototypes/Roles/Antags/ninja.yml index a7492bd1b1..cb32dc2c10 100644 --- a/Resources/Prototypes/Roles/Antags/ninja.yml +++ b/Resources/Prototypes/Roles/Antags/ninja.yml @@ -4,3 +4,33 @@ antagonist: true setPreference: false objective: roles-antag-space-ninja-objective + +#Ninja Gear +- type: startingGear + id: SpaceNinjaGear + equipment: + jumpsuit: ClothingUniformJumpsuitNinja + back: ClothingBackpackSatchel + mask: ClothingMaskNinja + head: ClothingHeadHelmetSpaceNinja + eyes: ClothingEyesVisorNinja + gloves: ClothingHandsGlovesSpaceNinja + outerClothing: ClothingOuterSuitSpaceNinja + shoes: ClothingShoesSpaceNinja + id: AgentIDCard + ears: ClothingHeadsetGrey + pocket1: SpiderCharge + pocket2: PinpointerStation + belt: EnergyKatana + suitstorage: OxygenTankFilled + inhand: + - JetpackBlackFilled + storage: + back: # belt holds katana so satchel has the tools for sabotaging things + - BoxSurvival + - Crowbar + - Wrench + - Screwdriver + - Wirecutter + - Welder + - Multitool \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Antags/nukeops.yml b/Resources/Prototypes/Roles/Antags/nukeops.yml index 4075005e68..e91d50f53c 100644 --- a/Resources/Prototypes/Roles/Antags/nukeops.yml +++ b/Resources/Prototypes/Roles/Antags/nukeops.yml @@ -34,3 +34,66 @@ department: Security time: 18000 # 5h # should be changed to nukie playtime when thats tracked (wyci) + +#Nuclear Operative Gear +- type: startingGear + id: SyndicateOperativeGearFull + equipment: + jumpsuit: ClothingUniformJumpsuitOperative + back: ClothingBackpackDuffelSyndicate + mask: ClothingMaskGasSyndicate + eyes: ClothingEyesHudSyndicate + ears: ClothingHeadsetAltSyndicate + gloves: ClothingHandsGlovesCombat + outerClothing: ClothingOuterHardsuitSyndie + shoes: ClothingShoesBootsCombatFilled + id: SyndiPDA + pocket1: DoubleEmergencyOxygenTankFilled + pocket2: BaseUplinkRadio40TC + belt: ClothingBeltMilitaryWebbing + storage: + back: + - BoxSurvivalSyndicate + - WeaponPistolViper + - PinpointerSyndicateNuclear + - DeathAcidifierImplanter + +#Nuclear Operative Commander Gear +- type: startingGear + id: SyndicateCommanderGearFull + parent: SyndicateOperativeGearFull + equipment: + neck: SyndicateWhistle + outerClothing: ClothingOuterHardsuitSyndieCommander + inhand: + - NukeOpsDeclarationOfWar + +#Nuclear Operative Medic Gear +- type: startingGear + id: SyndicateOperativeMedicFull + parent: SyndicateOperativeGearFull + equipment: + eyes: ClothingEyesHudSyndicateAgent + outerClothing: ClothingOuterHardsuitSyndieMedic + shoes: ClothingShoesBootsMagSyndie + id: SyndiAgentPDA + belt: ClothingBeltMilitaryWebbingMedFilled + storage: + back: + - SyndiHypo + - BoxSurvivalSyndicate + - SawAdvanced + - Cautery + - CombatKnife + - WeaponPistolViper + - PinpointerSyndicateNuclear + - HandheldHealthAnalyzer + - CombatMedipen + - DeathAcidifierImplanter + +#Lone Operative Gear +- type: startingGear + id: SyndicateLoneOperativeGearFull + parent: SyndicateOperativeGearFull + equipment: + pocket2: BaseUplinkRadio60TC \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Antags/pirate.yml b/Resources/Prototypes/Roles/Antags/pirate.yml index 235ccfdab8..849e9dc1f2 100644 --- a/Resources/Prototypes/Roles/Antags/pirate.yml +++ b/Resources/Prototypes/Roles/Antags/pirate.yml @@ -2,7 +2,7 @@ id: PirateGear equipment: jumpsuit: ClothingUniformJumpsuitPirate - back: ClothingBackpackPirateFilled + back: ClothingBackpackSatchelLeather head: ClothingHeadBandBlack shoes: ClothingShoesBootsLaceup id: PiratePDA @@ -11,25 +11,15 @@ - type: startingGear id: PirateCaptainGear + parent: PirateGear equipment: - jumpsuit: ClothingUniformJumpsuitPirate - back: ClothingBackpackPirateFilled head: ClothingHeadHatPirate - shoes: ClothingShoesBootsLaceup - id: PiratePDA - belt: ClothingBeltUtility - pocket1: AppraisalTool pocket2: EnergyCutlass outerClothing: ClothingOuterCoatPirate - type: startingGear id: PirateFirstmateGear + parent: PirateGear equipment: - jumpsuit: ClothingUniformJumpsuitPirate - back: ClothingBackpackPirateFilled head: ClothingHeadHatPirateTricord - shoes: ClothingShoesBootsLaceup - id: PiratePDA - belt: ClothingBeltUtility - pocket1: AppraisalTool outerClothing: ClothingOuterCoatGentle diff --git a/Resources/Prototypes/Roles/Antags/revolutionary.yml b/Resources/Prototypes/Roles/Antags/revolutionary.yml index c5e6cb8149..3bdc9d7977 100644 --- a/Resources/Prototypes/Roles/Antags/revolutionary.yml +++ b/Resources/Prototypes/Roles/Antags/revolutionary.yml @@ -11,3 +11,10 @@ antagonist: true setPreference: false objective: roles-antag-rev-objective + +- type: startingGear + id: HeadRevGear + storage: + back: + - Flash + - ClothingEyesGlassesSunglasses \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Antags/traitor.yml b/Resources/Prototypes/Roles/Antags/traitor.yml index a4e1fe8d10..f192733b7e 100644 --- a/Resources/Prototypes/Roles/Antags/traitor.yml +++ b/Resources/Prototypes/Roles/Antags/traitor.yml @@ -4,3 +4,36 @@ antagonist: true setPreference: true objective: roles-antag-syndicate-agent-objective + +# Syndicate Operative Outfit - Monkey +- type: startingGear + id: SyndicateOperativeGearMonkey + equipment: + head: ClothingHeadHatOutlawHat + jumpsuit: ClothingUniformJumpsuitOperative + mask: CigaretteSyndicate + +# Syndicate Operative Outfit - Barratry +- type: startingGear + id: SyndicateOperativeGearExtremelyBasic + equipment: + jumpsuit: ClothingUniformJumpsuitOperative + back: ClothingBackpackSyndicate + shoes: ClothingShoesBootsCombatFilled + gloves: ClothingHandsGlovesColorBlack + storage: + back: + - BoxSurvivalSyndicate + - WeaponPistolViper + - PinpointerSyndicateNuclear + - DeathAcidifierImplanter + +#Syndicate Operative Outfit - Basic +- type: startingGear + id: SyndicateOperativeGearBasic + parent: SyndicateOperativeGearExtremelyBasic + equipment: + ears: ClothingHeadsetAltSyndicate + gloves: ClothingHandsGlovesCombat + pocket1: BaseUplinkRadio40TC + id: SyndiPDA \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Cargo/cargo_technician.yml b/Resources/Prototypes/Roles/Jobs/Cargo/cargo_technician.yml index 46c5273549..78ab1847a4 100644 --- a/Resources/Prototypes/Roles/Jobs/Cargo/cargo_technician.yml +++ b/Resources/Prototypes/Roles/Jobs/Cargo/cargo_technician.yml @@ -17,4 +17,7 @@ equipment: id: CargoPDA ears: ClothingHeadsetCargo - pocket1: AppraisalTool \ No newline at end of file + pocket1: AppraisalTool + storage: + back: + - BoxSurvival \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml b/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml index 36d904c56e..e39c327e90 100644 --- a/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml +++ b/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml @@ -41,4 +41,8 @@ id: QuartermasterPDA ears: ClothingHeadsetQM belt: BoxFolderClipboard - pocket1: AppraisalTool \ No newline at end of file + pocket1: AppraisalTool + storage: + back: + - BoxSurvival + - Flash \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Cargo/salvage_specialist.yml b/Resources/Prototypes/Roles/Jobs/Cargo/salvage_specialist.yml index 7b3fcd6b58..4dd20f51fb 100644 --- a/Resources/Prototypes/Roles/Jobs/Cargo/salvage_specialist.yml +++ b/Resources/Prototypes/Roles/Jobs/Cargo/salvage_specialist.yml @@ -23,4 +23,7 @@ equipment: jumpsuit: ClothingUniformJumpsuitSalvageSpecialist id: SalvagePDA - ears: ClothingHeadsetCargo \ No newline at end of file + ears: ClothingHeadsetCargo + storage: + back: + - BoxSurvival \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/assistant.yml b/Resources/Prototypes/Roles/Jobs/Civilian/assistant.yml index b43f929d69..06b66448cb 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/assistant.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/assistant.yml @@ -14,3 +14,6 @@ equipment: id: PassengerPDA ears: ClothingHeadsetGrey + storage: + back: + - BoxSurvival diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml b/Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml index a49e3cef7f..6bceebc65e 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml @@ -24,3 +24,6 @@ shoes: ClothingShoesColorBlack id: BartenderPDA ears: ClothingHeadsetService + storage: + back: + - BoxSurvival diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/botanist.yml b/Resources/Prototypes/Roles/Jobs/Civilian/botanist.yml index feed9e7e6b..e8cda18914 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/botanist.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/botanist.yml @@ -21,3 +21,6 @@ id: BotanistPDA ears: ClothingHeadsetService belt: ClothingBeltPlantFilled + storage: + back: + - BoxSurvival diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml index e3dc1cb23d..0ae49f853c 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml @@ -20,3 +20,8 @@ shoes: ClothingShoesColorBlack id: ChaplainPDA ears: ClothingHeadsetService + storage: + back: + - BoxSurvival + - Bible + - RubberStampChaplain diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/chef.yml b/Resources/Prototypes/Roles/Jobs/Civilian/chef.yml index 1dda98bb37..0d8d1911e5 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/chef.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/chef.yml @@ -25,3 +25,6 @@ id: ChefPDA ears: ClothingHeadsetService belt: ClothingBeltChefFilled + storage: + back: + - BoxSurvival diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml b/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml index c70b4a9943..627469f6c5 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml @@ -32,4 +32,9 @@ pocket1: BikeHorn pocket2: ClownRecorder id: ClownPDA - ears: ClothingHeadsetService \ No newline at end of file + ears: ClothingHeadsetService + storage: + back: + - BoxHug + - RubberStampClown + - CrayonRainbow \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml b/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml index b768bd25be..9b006cbed1 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml @@ -32,3 +32,6 @@ head: ClothingHeadHatCatEars ears: ClothingHeadsetService belt: ClothingBeltJanitorFilled + storage: + back: + - BoxSurvival diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml b/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml index 1a321aff40..3ec0eba51c 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml @@ -24,3 +24,7 @@ # TODO add copy of space law inhand: - BriefcaseBrownFilled + storage: + back: + - BoxSurvival + - RubberStampLawyer diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml b/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml index 909d8da19d..f5826b76bf 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml @@ -18,3 +18,7 @@ ears: ClothingHeadsetService pocket1: d10Dice pocket2: HandLabeler # for making named bestsellers + storage: + back: + - BoxSurvival + - BookRandom diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml index 27a164210b..b7e932acb4 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml @@ -28,6 +28,10 @@ pocket2: Paper id: MimePDA ears: ClothingHeadsetService + storage: + back: + - BoxSurvival + - RubberStampMime - type: entity id: ActionMimeInvisibleWall diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/musician.yml b/Resources/Prototypes/Roles/Jobs/Civilian/musician.yml index 1ba1e998f6..4b508c907d 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/musician.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/musician.yml @@ -21,4 +21,9 @@ eyes: ClothingEyesGlassesSunglasses shoes: ClothingShoesBootsLaceup id: MusicianPDA - ears: ClothingHeadsetService \ No newline at end of file + ears: ClothingHeadsetService + storage: + back: + - BoxSurvival + - AcousticGuitarInstrument + - SaxophoneInstrument \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/service_worker.yml b/Resources/Prototypes/Roles/Jobs/Civilian/service_worker.yml index db23826673..efda20712f 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/service_worker.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/service_worker.yml @@ -21,3 +21,6 @@ shoes: ClothingShoesColorBlack id: ServiceWorkerPDA ears: ClothingHeadsetService + storage: + back: + - BoxSurvival diff --git a/Resources/Prototypes/Roles/Jobs/Command/captain.yml b/Resources/Prototypes/Roles/Jobs/Command/captain.yml index dd7445670a..1e93ff73cc 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/captain.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/captain.yml @@ -40,3 +40,8 @@ gloves: ClothingHandsGlovesCaptain id: CaptainPDA ears: ClothingHeadsetAltCommand + storage: + back: + - BoxSurvival + - Flash + # - StationCharter \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml index 5f119b6434..72a862f264 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml @@ -64,3 +64,7 @@ gloves: ClothingHandsGlovesHop ears: ClothingHeadsetAltCommand belt: BoxFolderClipboard + storage: + back: + - BoxSurvival + - Flash \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml b/Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml index 01af70888d..49a8978633 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml @@ -23,3 +23,6 @@ id: AtmosPDA belt: ClothingBeltUtilityEngineering ears: ClothingHeadsetEngineering + storage: + back: + - BoxSurvivalEngineering \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml index c55fbfbd41..8cc934d634 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml @@ -44,3 +44,7 @@ eyes: ClothingEyesGlassesMeson ears: ClothingHeadsetCE belt: ClothingBeltUtilityEngineering + storage: + back: + - BoxSurvivalEngineering + - Flash \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml b/Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml index d378479681..1bff9257bb 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml @@ -23,3 +23,6 @@ eyes: ClothingEyesGlassesMeson belt: ClothingBeltUtilityEngineering ears: ClothingHeadsetEngineering + storage: + back: + - BoxSurvivalEngineering \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/technical_assistant.yml b/Resources/Prototypes/Roles/Jobs/Engineering/technical_assistant.yml index f3b21e6444..8bbaf2a390 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/technical_assistant.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/technical_assistant.yml @@ -26,4 +26,7 @@ id: TechnicalAssistantPDA belt: ClothingBeltUtilityEngineering ears: ClothingHeadsetEngineering - pocket2: BookEngineersHandbook \ No newline at end of file + pocket2: BookEngineersHandbook + storage: + back: + - BoxSurvivalEngineering \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Fun/cult_startinggear.yml b/Resources/Prototypes/Roles/Jobs/Fun/cult_startinggear.yml index 8748367830..5542a7a69d 100644 --- a/Resources/Prototypes/Roles/Jobs/Fun/cult_startinggear.yml +++ b/Resources/Prototypes/Roles/Jobs/Fun/cult_startinggear.yml @@ -3,21 +3,27 @@ id: CultLeaderGear equipment: jumpsuit: ClothingUniformJumpsuitColorBlack - back: ClothingBackpackFilled + back: ClothingBackpack head: ClothingHeadHelmetCult neck: BedsheetCult outerClothing: ClothingOuterArmorCult shoes: ClothingShoesCult id: PassengerPDA ears: ClothingHeadsetService + storage: + back: + - BoxSurvival - type: startingGear id: CultistGear equipment: jumpsuit: ClothingUniformJumpsuitColorBlack - back: ClothingBackpackFilled + back: ClothingBackpack head: ClothingHeadHatHoodCulthood outerClothing: ClothingOuterRobesCult shoes: ClothingShoesColorRed id: PassengerPDA ears: ClothingHeadsetService + storage: + back: + - BoxSurvival diff --git a/Resources/Prototypes/Roles/Jobs/Fun/emergencyresponseteam.yml b/Resources/Prototypes/Roles/Jobs/Fun/emergencyresponseteam.yml index f37085e005..337b7e1de9 100644 --- a/Resources/Prototypes/Roles/Jobs/Fun/emergencyresponseteam.yml +++ b/Resources/Prototypes/Roles/Jobs/Fun/emergencyresponseteam.yml @@ -18,7 +18,7 @@ id: ERTLeaderGear equipment: jumpsuit: ClothingUniformJumpsuitERTLeader - back: ClothingBackpackERTLeaderFilled + back: ClothingBackpackERTLeader shoes: ClothingShoesBootsCombatFilled head: ClothingHeadHelmetERTLeader eyes: ClothingEyesGlassesSecurity @@ -29,12 +29,21 @@ belt: ClothingBeltSecurityFilled pocket1: WeaponPistolN1984 pocket2: FlashlightSeclite + storage: + back: + - BoxSurvivalEngineering + - WeaponDisabler + - MedicatedSuture + - RegenerativeMesh + - BoxZiptie + - CrowbarRed + - MagazineMagnum - type: startingGear id: ERTLeaderGearEVA equipment: jumpsuit: ClothingUniformJumpsuitERTLeader - back: ClothingBackpackERTLeaderFilled + back: ClothingBackpackERTLeader shoes: ClothingShoesBootsMagAdv mask: ClothingMaskGasERT eyes: ClothingEyesGlassesSecurity @@ -46,12 +55,21 @@ belt: ClothingBeltSecurityFilled pocket1: WeaponPistolN1984 pocket2: FlashlightSeclite + storage: + back: + - BoxSurvivalEngineering + - WeaponDisabler + - MedicatedSuture + - RegenerativeMesh + - BoxZiptie + - CrowbarRed + - MagazineMagnum - type: startingGear id: ERTLeaderGearEVALecter equipment: jumpsuit: ClothingUniformJumpsuitERTLeader - back: ClothingBackpackERTLeaderFilled + back: ClothingBackpackERTLeader shoes: ClothingShoesBootsMagAdv mask: ClothingMaskGasERT eyes: ClothingEyesGlassesSecurity @@ -65,6 +83,15 @@ pocket2: MagazineRifle inhand: - AirTankFilled + storage: + back: + - BoxSurvivalEngineering + - WeaponDisabler + - MedicatedSuture + - RegenerativeMesh + - BoxZiptie + - CrowbarRed + - MagazineMagnum # Chaplain - type: job @@ -90,7 +117,7 @@ id: ERTChaplainGear equipment: jumpsuit: ClothingUniformJumpsuitERTChaplain - back: ClothingBackpackERTChaplainFilled + back: ClothingBackpackERTChaplain shoes: ClothingShoesLeather head: ClothingHeadHatFez eyes: ClothingEyesGlasses @@ -102,12 +129,27 @@ belt: ClothingBeltStorageWaistbag pocket1: Flare pocket2: DrinkWaterBottleFull + storage: + back: + - BoxSurvivalEngineering + - BoxCandle + - BoxBodyBag + - DrinkWaterMelonJuiceJug + - Lantern + - Lantern + - Bible + - CrowbarRed + - FoodBakedBunHotX + - FoodBakedBunHotX + - FoodBakedBunHotX + - FoodBakedBunHotX + - Lighter - type: startingGear id: ERTChaplainGearEVA equipment: jumpsuit: ClothingUniformJumpsuitERTChaplain - back: ClothingBackpackERTChaplainFilled + back: ClothingBackpackERTChaplain shoes: ClothingShoesBootsMagAdv mask: ClothingMaskGasERT eyes: ClothingEyesGlasses @@ -120,6 +162,21 @@ belt: ClothingBeltStorageWaistbag pocket1: Flare pocket2: DrinkWaterBottleFull + storage: + back: + - BoxSurvivalEngineering + - BoxCandle + - BoxBodyBag + - DrinkWaterMelonJuiceJug + - Lantern + - Lantern + - Bible + - CrowbarRed + - FoodBakedBunHotX + - FoodBakedBunHotX + - FoodBakedBunHotX + - FoodBakedBunHotX + - Lighter # Engineer - type: job @@ -141,7 +198,7 @@ id: ERTEngineerGear equipment: jumpsuit: ClothingUniformJumpsuitERTEngineer - back: ClothingBackpackERTEngineerFilled + back: ClothingBackpackERTEngineer shoes: ClothingShoesBootsWork head: ClothingHeadHelmetERTEngineer eyes: ClothingEyesGlassesMeson @@ -152,12 +209,25 @@ belt: ClothingBeltChiefEngineerFilled pocket1: Flare pocket2: GasAnalyzer + storage: + back: + - BoxSurvivalEngineering + - trayScanner + - RCD + - RCDAmmo + - RCDAmmo + - CableMVStack + - CableHVStack + - CableApcStack + - SheetPlasteel + - SheetSteel + - SheetGlass - type: startingGear id: ERTEngineerGearEVA equipment: jumpsuit: ClothingUniformJumpsuitERTEngineer - back: ClothingBackpackERTEngineerFilled + back: ClothingBackpackERTEngineer shoes: ClothingShoesBootsMagAdv mask: ClothingMaskGasERT eyes: ClothingEyesGlassesMeson @@ -169,6 +239,19 @@ belt: ClothingBeltChiefEngineerFilled pocket1: Flare pocket2: GasAnalyzer + storage: + back: + - BoxSurvivalEngineering + - trayScanner + - RCD + - RCDAmmo + - RCDAmmo + - CableMVStack + - CableHVStack + - CableApcStack + - SheetPlasteel + - SheetSteel + - SheetGlass # Security - type: job @@ -190,7 +273,7 @@ id: ERTSecurityGear equipment: jumpsuit: ClothingUniformJumpsuitERTSecurity - back: ClothingBackpackERTSecurityFilled + back: ClothingBackpackERTSecurity shoes: ClothingShoesBootsCombatFilled head: ClothingHeadHelmetERTSecurity eyes: ClothingEyesGlassesSecurity @@ -201,12 +284,21 @@ belt: ClothingBeltSecurityFilled pocket1: WeaponPistolMk58 pocket2: FlashlightSeclite + storage: + back: + - BoxSurvivalEngineering + - WeaponDisabler + - MedicatedSuture + - RegenerativeMesh + - BoxZiptie + - CrowbarRed + - MagazinePistol - type: startingGear id: ERTSecurityGearEVA equipment: jumpsuit: ClothingUniformJumpsuitERTSecurity - back: ClothingBackpackERTSecurityFilled + back: ClothingBackpackERTSecurity shoes: ClothingShoesBootsMag mask: ClothingMaskGasERT eyes: ClothingEyesGlassesSecurity @@ -218,12 +310,21 @@ belt: ClothingBeltSecurityFilled pocket1: WeaponPistolMk58 pocket2: FlashlightSeclite + storage: + back: + - BoxSurvivalEngineering + - WeaponDisabler + - MedicatedSuture + - RegenerativeMesh + - BoxZiptie + - CrowbarRed + - MagazinePistol - type: startingGear id: ERTSecurityGearEVALecter equipment: jumpsuit: ClothingUniformJumpsuitERTSecurity - back: ClothingBackpackERTSecurityFilled + back: ClothingBackpackERTSecurity shoes: ClothingShoesBootsMag mask: ClothingMaskGasERT eyes: ClothingEyesGlassesSecurity @@ -237,6 +338,15 @@ pocket2: MagazineRifle inhand: - AirTankFilled + storage: + back: + - BoxSurvivalEngineering + - WeaponDisabler + - MedicatedSuture + - RegenerativeMesh + - BoxZiptie + - CrowbarRed + - MagazinePistol # Medical - type: job @@ -258,7 +368,7 @@ id: ERTMedicalGear equipment: jumpsuit: ClothingUniformJumpsuitERTMedic - back: ClothingBackpackERTMedicalFilled + back: ClothingBackpackERTMedical shoes: ClothingShoesBootsCombatFilled head: ClothingHeadHelmetERTMedic eyes: ClothingEyesHudMedical @@ -268,12 +378,21 @@ ears: ClothingHeadsetAltCentCom belt: ClothingBeltMedicalFilled pocket1: Flare + storage: + back: + - BoxSurvivalMedical + - Hypospray + - MedkitAdvancedFilled + - CrowbarRed + - OmnizineChemistryBottle + - EpinephrineChemistryBottle + - EpinephrineChemistryBottle - type: startingGear id: ERTMedicalGearEVA equipment: jumpsuit: ClothingUniformJumpsuitERTMedic - back: ClothingBackpackERTMedicalFilled + back: ClothingBackpackERTMedical shoes: ClothingShoesBootsMag mask: ClothingMaskGasERT eyes: ClothingEyesHudMedical @@ -284,6 +403,15 @@ ears: ClothingHeadsetAltCentCom belt: ClothingBeltMedicalFilled pocket1: Flare + storage: + back: + - BoxSurvivalMedical + - Hypospray + - MedkitAdvancedFilled + - CrowbarRed + - OmnizineChemistryBottle + - EpinephrineChemistryBottle + - EpinephrineChemistryBottle # Janitor - type: job @@ -305,7 +433,7 @@ id: ERTJanitorGear equipment: jumpsuit: ClothingUniformJumpsuitERTJanitor - back: ClothingBackpackERTJanitorFilled + back: ClothingBackpackERTJanitor shoes: ClothingShoesGaloshes head: ClothingHeadHelmetERTJanitor gloves: ClothingHandsGlovesColorPurple @@ -314,12 +442,21 @@ ears: ClothingHeadsetAltCentCom belt: ClothingBeltJanitorFilled pocket1: Flare + storage: + back: + - BoxSurvivalEngineering + - LightReplacer + - BoxLightMixed + - BoxLightMixed + - Soap + - CrowbarRed + - AdvMopItem - type: startingGear id: ERTJanitorGearEVA equipment: jumpsuit: ClothingUniformJumpsuitERTJanitor - back: ClothingBackpackERTJanitorFilled + back: ClothingBackpackERTJanitor shoes: ClothingShoesBootsMag mask: ClothingMaskGasERT gloves: ClothingHandsGlovesColorPurple @@ -329,3 +466,12 @@ ears: ClothingHeadsetAltCentCom belt: ClothingBeltJanitorFilled pocket1: Flare + storage: + back: + - BoxSurvivalEngineering + - LightReplacer + - BoxLightMixed + - BoxLightMixed + - Soap + - CrowbarRed + - AdvMopItem \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml index d4cfc3e29c..e12bee8c79 100644 --- a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml +++ b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml @@ -5,7 +5,7 @@ id: SkeletonBiker equipment: jumpsuit: ClothingUniformJumpsuitColorBlack - back: ClothingBackpackFilled + back: ClothingBackpack head: ClothingHeadBandSkull eyes: ClothingEyesGlassesSunglasses outerClothing: ClothingOuterCoatGentle @@ -13,35 +13,16 @@ shoes: ClothingShoesBootsJack id: PassengerPDA ears: ClothingHeadsetGrey - -#Space Ninja Outfit -- type: startingGear - id: SpaceNinjaGear - equipment: - jumpsuit: ClothingUniformJumpsuitNinja - # belt holds katana so satchel has the tools for sabotaging things - back: ClothingBackpackSatchelTools - mask: ClothingMaskNinja - head: ClothingHeadHelmetSpaceNinja - eyes: ClothingEyesVisorNinja - gloves: ClothingHandsGlovesSpaceNinja - outerClothing: ClothingOuterSuitSpaceNinja - shoes: ClothingShoesSpaceNinja - id: AgentIDCard - ears: ClothingHeadsetGrey - pocket1: SpiderCharge - pocket2: PinpointerStation - belt: EnergyKatana - suitstorage: OxygenTankFilled - inhand: - - JetpackBlackFilled + storage: + back: + - BoxSurvival #Deathsquad Outfit - type: startingGear id: DeathSquadGear equipment: jumpsuit: ClothingUniformJumpsuitDeathSquad - back: ClothingBackpackDeathSquadFilled + back: ClothingBackpackDeathSquad mask: ClothingMaskGasDeathSquad eyes: ClothingEyesHudSecurity ears: ClothingHeadsetAltCentCom @@ -53,23 +34,20 @@ pocket1: EnergySword pocket2: EnergyShield belt: ClothingBeltMilitaryWebbingMedFilled - -# Syndicate Operative Outfit - Monkey -- type: startingGear - id: SyndicateOperativeGearMonkey - equipment: - head: ClothingHeadHatOutlawHat - jumpsuit: ClothingUniformJumpsuitOperative - mask: CigaretteSyndicate - -# Syndicate Operative Outfit - Barratry -- type: startingGear - id: SyndicateOperativeGearExtremelyBasic - equipment: - jumpsuit: ClothingUniformJumpsuitOperative - back: ClothingBackpackDuffelSyndicateOperative - shoes: ClothingShoesBootsCombatFilled - gloves: ClothingHandsGlovesColorBlack + storage: + back: + - BoxSurvivalEngineering + - WeaponPulseRifle + - WeaponPulsePistol + - WeaponRevolverMateba + - SpeedLoaderMagnumAP + - SpeedLoaderMagnumAP + - BoxFlashbang + - ToolDebug # spanish army knife + - WelderExperimental + - Hypospray + - DeathAcidifierImplanter # crew will try to steal their amazing hardsuits + - FreedomImplanter # Syndicate Operative Outfit - Civilian - type: startingGear @@ -82,103 +60,6 @@ id: SyndiPDA ears: ClothingHeadsetAltSyndicate -#Syndicate Operative Outfit - Basic -- type: startingGear - id: SyndicateOperativeGearBasic - equipment: - jumpsuit: ClothingUniformJumpsuitOperative - back: ClothingBackpackDuffelSyndicateOperative - ears: ClothingHeadsetAltSyndicate - gloves: ClothingHandsGlovesCombat - shoes: ClothingShoesBootsCombatFilled - pocket1: BaseUplinkRadio40TC - id: SyndiPDA - -#Syndicate Operative Outfit - Full Kit -- type: startingGear - id: SyndicateOperativeGearFull - equipment: - jumpsuit: ClothingUniformJumpsuitOperative - back: ClothingBackpackDuffelSyndicateOperative - mask: ClothingMaskGasSyndicate - eyes: ClothingEyesHudSyndicate - ears: ClothingHeadsetAltSyndicate - gloves: ClothingHandsGlovesCombat - outerClothing: ClothingOuterHardsuitSyndie - shoes: ClothingShoesBootsCombatFilled - id: SyndiPDA - pocket1: DoubleEmergencyOxygenTankFilled - pocket2: BaseUplinkRadio40TC - belt: ClothingBeltMilitaryWebbing - -#Nuclear Operative Commander Gear -- type: startingGear - id: SyndicateCommanderGearFull - equipment: - jumpsuit: ClothingUniformJumpsuitOperative - back: ClothingBackpackDuffelSyndicateOperative - mask: ClothingMaskGasSyndicate - eyes: ClothingEyesHudSyndicate - ears: ClothingHeadsetAltSyndicate - neck: SyndicateWhistle - gloves: ClothingHandsGlovesCombat - outerClothing: ClothingOuterHardsuitSyndieCommander - shoes: ClothingShoesBootsCombatFilled - id: SyndiPDA - pocket1: DoubleEmergencyOxygenTankFilled - pocket2: BaseUplinkRadio40TC - belt: ClothingBeltMilitaryWebbing - inhand: - - NukeOpsDeclarationOfWar - -#Nuclear Operative Medic Gear -- type: startingGear - id: SyndicateOperativeMedicFull - equipment: - jumpsuit: ClothingUniformJumpsuitOperative - back: ClothingBackpackDuffelSyndicateOperativeMedic - mask: ClothingMaskGasSyndicate - eyes: ClothingEyesHudSyndicateAgent - ears: ClothingHeadsetAltSyndicate - gloves: ClothingHandsGlovesCombat - outerClothing: ClothingOuterHardsuitSyndieMedic - shoes: ClothingShoesBootsMagSyndie - id: SyndiAgentPDA - pocket1: DoubleEmergencyOxygenTankFilled - pocket2: BaseUplinkRadio40TC - belt: ClothingBeltMilitaryWebbingMedFilled - -#Syndicate Lone Operative Outfit - Full Kit -- type: startingGear - id: SyndicateLoneOperativeGearFull - equipment: - jumpsuit: ClothingUniformJumpsuitOperative - back: ClothingBackpackDuffelSyndicateOperative - mask: ClothingMaskGasSyndicate - eyes: ClothingEyesHudSyndicate - ears: ClothingHeadsetAltSyndicate - gloves: ClothingHandsGlovesCombat - outerClothing: ClothingOuterHardsuitSyndie - shoes: ClothingShoesBootsCombatFilled - id: SyndiPDA - pocket1: DoubleEmergencyOxygenTankFilled - pocket2: BaseUplinkRadio60TC - belt: ClothingBeltMilitaryWebbing - -# Syndicate Footsoldier Gear -- type: startingGear - id: SyndicateFootsoldierGear - equipment: - jumpsuit: ClothingUniformJumpsuitOperative - head: ClothingHeadHelmetSwatSyndicate - mask: ClothingMaskGas - outerClothing: ClothingOuterArmorBasic - ears: ClothingHeadsetAltSyndicate - gloves: ClothingHandsGlovesCombat - back: ClothingBackpackFilled - shoes: ClothingShoesBootsCombat - id: SyndiPDA - # Syndicate Footsoldier Gear - No Headset - type: startingGear id: SyndicateFootsoldierGearRuin @@ -188,16 +69,26 @@ mask: ClothingMaskGas outerClothing: ClothingOuterArmorBasic gloves: ClothingHandsGlovesCombat - back: ClothingBackpackFilled + back: ClothingBackpack shoes: ClothingShoesBootsCombat id: SyndiPDA + storage: + back: + - BoxSurvival + +# Syndicate Footsoldier Gear +- type: startingGear + id: SyndicateFootsoldierGear + parent: SyndicateFootsoldierGearRuin + equipment: + ears: ClothingHeadsetAltSyndicate # Nanotrasen Paramilitary Unit Gear - type: startingGear id: NanotrasenParamilitaryGear equipment: jumpsuit: ClothingUniformJumpsuitSec - back: ClothingBackpackSecurityFilled + back: ClothingBackpackSecurity shoes: ClothingShoesBootsCombatFilled eyes: ClothingEyesGlassesSecurity head: ClothingHeadHelmetSwat @@ -205,13 +96,18 @@ outerClothing: ClothingOuterArmorBasicSlim ears: ClothingHeadsetSecurity gloves: ClothingHandsGlovesCombat + storage: + back: + - BoxSurvivalSecurity + - Flash + - MagazinePistol #CBURN Unit Gear - Full Kit - type: startingGear id: CBURNGear equipment: jumpsuit: ClothingUniformJumpsuitColorBrown - back: ClothingBackpackDuffelCBURNFilled + back: ClothingBackpackDuffelCBURN mask: ClothingMaskGasERT eyes: ClothingEyesGlassesSecurity ears: ClothingHeadsetAltCentCom @@ -223,6 +119,14 @@ pocket2: WeaponLaserGun suitstorage: OxygenTankFilled belt: ClothingBeltBandolier + storage: + back: + - BoxSurvivalEngineering + - WeaponShotgunDoubleBarreled + - BoxShotgunIncendiary + - GrenadeFlashBang + - PillAmbuzolPlus + - PillAmbuzol - type: startingGear id: BoxingKangarooGear @@ -237,14 +141,6 @@ jumpsuit: ClothingUniformJumpsuitJacketMonkey id: PunPunIDCard -# Passenger but without the ID, bag, or headset - -- type: startingGear - id: LimitedPassengerGear - equipment: - jumpsuit: ClothingUniformJumpsuitColorGrey - shoes: ClothingShoesColorBlack - # DeathMatch Gear - type: startingGear @@ -278,24 +174,13 @@ - type: startingGear id: MobAghostGear equipment: - back: ClothingBackpackSatchelHoldingAdmin + back: ClothingBackpackSatchelHolding id: AdminPDA - -#Head Rev Gear -- type: startingGear - id: HeadRevGear storage: back: - - Flash - - ClothingEyesGlassesSunglasses - -#Thief Gear -- type: startingGear - id: ThiefGear - storage: - back: - - ToolboxThief - - ClothingHandsChameleonThief + - GasAnalyzer + - trayScanner + - AccessConfiguratorUniversal #Gladiator with spear - type: startingGear @@ -325,10 +210,16 @@ shoes: ClothingShoesClownBanana jumpsuit: ClothingUniformJumpsuitClownBanana mask: ClothingMaskClownBanana - ears: ClothingHeadsetService pocket1: BikeHorn pocket2: ClownRecorder +# Passenger but without the ID, bag, or headset +- type: startingGear + id: LimitedPassengerGear + equipment: + jumpsuit: ClothingUniformJumpsuitColorGrey + shoes: ClothingShoesColorBlack + #Clown Troupe - type: startingGear id: ClownTroupe diff --git a/Resources/Prototypes/Roles/Jobs/Fun/wizard_startinggear.yml b/Resources/Prototypes/Roles/Jobs/Fun/wizard_startinggear.yml index 596e42776b..d053d8da1c 100644 --- a/Resources/Prototypes/Roles/Jobs/Fun/wizard_startinggear.yml +++ b/Resources/Prototypes/Roles/Jobs/Fun/wizard_startinggear.yml @@ -3,41 +3,33 @@ id: WizardBlueGear equipment: jumpsuit: ClothingUniformJumpsuitColorDarkBlue - back: ClothingBackpackFilled head: ClothingHeadHatWizard outerClothing: ClothingOuterWizard shoes: ClothingShoesWizard id: PassengerPDA ears: ClothingHeadsetService + storage: + back: + - BoxSurvival - type: startingGear id: WizardRedGear + parent: WizardBlueGear equipment: jumpsuit: ClothingUniformJumpsuitColorRed - back: ClothingBackpackFilled head: ClothingHeadHatRedwizard outerClothing: ClothingOuterWizardRed - shoes: ClothingShoesWizard - id: PassengerPDA - ears: ClothingHeadsetService - type: startingGear id: WizardVioletGear + parent: WizardBlueGear equipment: jumpsuit: ClothingUniformJumpsuitColorPurple - back: ClothingBackpackFilled head: ClothingHeadHatVioletwizard outerClothing: ClothingOuterWizardViolet - shoes: ClothingShoesWizard - id: PassengerPDA - ears: ClothingHeadsetService - type: startingGear id: WizardHardsuitGear + parent: WizardVioletGear equipment: - jumpsuit: ClothingUniformJumpsuitColorPurple - back: ClothingBackpackFilled - outerClothing: ClothingOuterHardsuitWizard - shoes: ClothingShoesWizard - id: PassengerPDA - ears: ClothingHeadsetService + outerClothing: ClothingOuterHardsuitWizard \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Medical/chemist.yml b/Resources/Prototypes/Roles/Jobs/Medical/chemist.yml index 0684d9d36a..fefd0110ac 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/chemist.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/chemist.yml @@ -22,4 +22,7 @@ ears: ClothingHeadsetMedical belt: ChemBag pocket1: HandLabeler - eyes: ClothingEyesGlassesChemical \ No newline at end of file + eyes: ClothingEyesGlassesChemical + storage: + back: + - BoxSurvivalMedical \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml index e20edb32d4..3f6bd5e452 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml @@ -44,3 +44,7 @@ id: CMOPDA ears: ClothingHeadsetCMO belt: ClothingBeltMedicalFilled + storage: + back: + - BoxSurvivalMedical + - Flash diff --git a/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml b/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml index 27efb3b8b2..be058518a6 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml @@ -21,3 +21,6 @@ equipment: ears: ClothingHeadsetMedical belt: ClothingBeltMedicalFilled + storage: + back: + - BoxSurvivalMedical \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml b/Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml index 199645d5c3..3fe26e6056 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml @@ -23,4 +23,7 @@ id: MedicalInternPDA ears: ClothingHeadsetMedical belt: ClothingBeltMedicalFilled - pocket2: BookMedicalReferenceBook \ No newline at end of file + pocket2: BookMedicalReferenceBook + storage: + back: + - BoxSurvivalMedical \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml b/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml index fb9fd2822d..242fac8d0c 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml @@ -24,3 +24,7 @@ id: ParamedicPDA ears: ClothingHeadsetMedical belt: ClothingBeltMedicalEMTFilled + storage: + back: + - BoxSurvivalMedical + - EmergencyRollerBedSpawnFolded \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_assistant.yml b/Resources/Prototypes/Roles/Jobs/Science/research_assistant.yml index b50c61ebdb..aec2dd6fff 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_assistant.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_assistant.yml @@ -23,3 +23,6 @@ id: ResearchAssistantPDA ears: ClothingHeadsetScience pocket2: BookScientistsGuidebook + storage: + back: + - BoxSurvival \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml index adf53d9c70..fd6e00a8e3 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml @@ -33,4 +33,8 @@ id: ResearchDirectorGear equipment: id: RnDPDA - ears: ClothingHeadsetRD \ No newline at end of file + ears: ClothingHeadsetRD + storage: + back: + - BoxSurvival + - Flash \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Science/scientist.yml b/Resources/Prototypes/Roles/Jobs/Science/scientist.yml index 5188e47057..b2e63fead8 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/scientist.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/scientist.yml @@ -18,4 +18,6 @@ id: ScientistGear equipment: ears: ClothingHeadsetScience - + storage: + back: + - BoxSurvival \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Security/detective.yml b/Resources/Prototypes/Roles/Jobs/Security/detective.yml index b17d0d41df..47258fa00b 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/detective.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/detective.yml @@ -28,3 +28,9 @@ id: DetectivePDA ears: ClothingHeadsetSecurity belt: ClothingBeltHolsterFilled + storage: + back: + - BoxSurvivalSecurity + - Flash + - ForensicPad + - ForensicScanner \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml index 8646ca8ddd..aac7c7b350 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml @@ -47,3 +47,8 @@ gloves: ClothingHandsGlovesCombat ears: ClothingHeadsetAltSecurity pocket1: WeaponPistolMk58 + storage: + back: + - BoxSurvivalSecurity + - Flash + - MagazinePistol \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml b/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml index 17e09b232e..b68336c953 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml @@ -34,4 +34,9 @@ ears: ClothingHeadsetSecurity belt: ClothingBeltSecurityFilled pocket1: WeaponPistolMk58 - pocket2: BookSecurity \ No newline at end of file + pocket2: BookSecurity + storage: + back: + - BoxSurvivalSecurity + - Flash + - MagazinePistol \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml b/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml index edf16448d7..c773396601 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml @@ -28,3 +28,8 @@ eyes: ClothingEyesGlassesSecurity ears: ClothingHeadsetSecurity pocket1: WeaponPistolMk58 + storage: + back: + - BoxSurvivalSecurity + - Flash + - MagazinePistol \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Security/warden.yml b/Resources/Prototypes/Roles/Jobs/Security/warden.yml index 5c1dc985d1..7f15752576 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/warden.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/warden.yml @@ -31,3 +31,8 @@ id: WardenPDA ears: ClothingHeadsetSecurity pocket1: WeaponPistolMk58 + storage: + back: + - BoxSurvivalSecurity + - Flash + - MagazinePistol \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Wildcards/boxer.yml b/Resources/Prototypes/Roles/Jobs/Wildcards/boxer.yml index 0f19323724..aceb7a10f6 100644 --- a/Resources/Prototypes/Roles/Jobs/Wildcards/boxer.yml +++ b/Resources/Prototypes/Roles/Jobs/Wildcards/boxer.yml @@ -17,3 +17,6 @@ ears: ClothingHeadsetService shoes: ClothingShoesColorRed belt: ClothingBeltChampion + storage: + back: + - BoxSurvival \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Wildcards/psychologist.yml b/Resources/Prototypes/Roles/Jobs/Wildcards/psychologist.yml index d1d07c008c..17bed0ee7f 100644 --- a/Resources/Prototypes/Roles/Jobs/Wildcards/psychologist.yml +++ b/Resources/Prototypes/Roles/Jobs/Wildcards/psychologist.yml @@ -19,3 +19,6 @@ shoes: ClothingShoesLeather id: PsychologistPDA ears: ClothingHeadsetMedical + storage: + back: + - BoxSurvivalMedical \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Wildcards/reporter.yml b/Resources/Prototypes/Roles/Jobs/Wildcards/reporter.yml index d2d70d856a..b379a0ec23 100644 --- a/Resources/Prototypes/Roles/Jobs/Wildcards/reporter.yml +++ b/Resources/Prototypes/Roles/Jobs/Wildcards/reporter.yml @@ -16,3 +16,6 @@ shoes: ClothingShoesColorWhite id: ReporterPDA ears: ClothingHeadsetService + storage: + back: + - BoxSurvival \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Wildcards/zookeeper.yml b/Resources/Prototypes/Roles/Jobs/Wildcards/zookeeper.yml index 27e7fa65ff..815ead412c 100644 --- a/Resources/Prototypes/Roles/Jobs/Wildcards/zookeeper.yml +++ b/Resources/Prototypes/Roles/Jobs/Wildcards/zookeeper.yml @@ -18,3 +18,6 @@ shoes: ClothingShoesColorWhite id: ZookeeperPDA ears: ClothingHeadsetService + storage: + back: + - BoxSurvival \ No newline at end of file diff --git a/Resources/migration.yml b/Resources/migration.yml index 65e914aed6..5f73d4d253 100644 --- a/Resources/migration.yml +++ b/Resources/migration.yml @@ -251,11 +251,11 @@ AirlockGlassShuttleEasyPryLocked: AirlockExternalGlassShuttleLocked AirlockShuttleEasyPryLocked: AirlockExternalShuttleLocked # 2024-03-10 -ClothingBackpackFilledDetective: ClothingBackpackSecurityFilledDetective -ClothingBackpackDuffelFilledDetective: ClothingBackpackDuffelSecurityFilledDetective -ClothingBackpackSatchelFilledDetective: ClothingBackpackSatchelSecurityFilledDetective FoodChili: FoodChiliPepper FoodChilly: FoodChillyPepper +# ClothingBackpackFilledDetective: ClothingBackpackSecurityFilledDetective +# ClothingBackpackDuffelFilledDetective: ClothingBackpackDuffelSecurityFilledDetective +# ClothingBackpackSatchelFilledDetective: ClothingBackpackSatchelSecurityFilledDetective # 2024-03-11 ImprovisedExplosive: FireBomb From 16b3fb1204d3921200822bf558c510c585ba33e3 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Tue, 4 Jun 2024 00:04:19 +0000 Subject: [PATCH 048/243] LoadMapRule grid storage rework (#28210) * --- .../Tests/GameRules/NukeOpsTest.cs | 10 +-- .../Rules/Components/LoadMapRuleComponent.cs | 26 +++--- .../Rules/Components/RuleGridsComponent.cs | 30 +++++++ .../Rules/GameRuleSystem.Utility.cs | 4 + .../GameTicking/Rules/LoadMapRuleSystem.cs | 90 +++++++------------ .../GameTicking/Rules/NukeopsRuleSystem.cs | 12 +-- .../GameTicking/Rules/RuleGridsSystem.cs | 78 ++++++++++++++++ .../GridPreloader/GridPreloaderSystem.cs | 9 +- .../Events/StationEventSystem.cs | 9 -- Resources/Prototypes/GameRules/events.yml | 1 + Resources/Prototypes/GameRules/midround.yml | 1 - Resources/Prototypes/GameRules/roundstart.yml | 1 + .../Prototypes/GameRules/unknown_shuttles.yml | 49 +++------- 13 files changed, 190 insertions(+), 130 deletions(-) create mode 100644 Content.Server/GameTicking/Rules/Components/RuleGridsComponent.cs create mode 100644 Content.Server/GameTicking/Rules/RuleGridsSystem.cs diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index ea19a30005..2360ea0bf4 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -114,8 +114,8 @@ public sealed class NukeOpsTest // The game rule exists, and all the stations/shuttles/maps are properly initialized var rule = entMan.AllComponents().Single().Component; - var mapRule = entMan.AllComponents().Single().Component; - foreach (var grid in mapRule.MapGrids) + var gridsRule = entMan.AllComponents().Single().Component; + foreach (var grid in gridsRule.MapGrids) { Assert.That(entMan.EntityExists(grid)); Assert.That(entMan.HasComponent(grid)); @@ -129,7 +129,7 @@ public sealed class NukeOpsTest Assert.That(entMan.EntityExists(nukieShuttlEnt)); EntityUid? nukieStationEnt = null; - foreach (var grid in mapRule.MapGrids) + foreach (var grid in gridsRule.MapGrids) { if (entMan.HasComponent(grid)) { @@ -144,8 +144,8 @@ public sealed class NukeOpsTest Assert.That(entMan.EntityExists(nukieStation.Station)); Assert.That(nukieStation.Station, Is.Not.EqualTo(rule.TargetStation)); - Assert.That(server.MapMan.MapExists(mapRule.Map)); - var nukieMap = mapSys.GetMap(mapRule.Map!.Value); + Assert.That(server.MapMan.MapExists(gridsRule.Map)); + var nukieMap = mapSys.GetMap(gridsRule.Map!.Value); var targetStation = entMan.GetComponent(rule.TargetStation!.Value); var targetGrid = targetStation.Grids.First(); diff --git a/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs index b7aef0c61d..1f0505c60f 100644 --- a/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs @@ -1,8 +1,6 @@ +using Content.Server.GameTicking.Rules; using Content.Server.Maps; using Content.Shared.GridPreloader.Prototypes; -using Content.Shared.Storage; -using Content.Shared.Whitelist; -using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -10,25 +8,27 @@ namespace Content.Server.GameTicking.Rules.Components; /// /// This is used for a game rule that loads a map when activated. +/// Works with . /// -[RegisterComponent] +[RegisterComponent, Access(typeof(LoadMapRuleSystem))] public sealed partial class LoadMapRuleComponent : Component { - [DataField] - public MapId? Map; - + /// + /// A to load on a new map. + /// [DataField] public ProtoId? GameMap; + /// + /// A map path to load on a new map. + /// [DataField] public ResPath? MapPath; + /// + /// A to move to a new map. + /// If there are no instances left nothing is done. + /// [DataField] public ProtoId? PreloadedGrid; - - [DataField] - public List MapGrids = new(); - - [DataField] - public EntityWhitelist? SpawnerWhitelist; } diff --git a/Content.Server/GameTicking/Rules/Components/RuleGridsComponent.cs b/Content.Server/GameTicking/Rules/Components/RuleGridsComponent.cs new file mode 100644 index 0000000000..eec6f88815 --- /dev/null +++ b/Content.Server/GameTicking/Rules/Components/RuleGridsComponent.cs @@ -0,0 +1,30 @@ +using Content.Server.GameTicking.Rules; +using Content.Shared.Whitelist; +using Robust.Shared.Map; + +/// +/// Stores grids created by another gamerule component. +/// With AntagSelection, spawners on these grids can be used for its antags. +/// +[RegisterComponent, Access(typeof(RuleGridsSystem))] +public sealed partial class RuleGridsComponent : Component +{ + /// + /// The map that was loaded. + /// + [DataField] + public MapId? Map; + + /// + /// The grid entities that have been loaded. + /// + [DataField] + public List MapGrids = new(); + + /// + /// Whitelist for a spawner to be considered for an antag. + /// All spawners must have SpawnPointComponent regardless to be found. + /// + [DataField] + public EntityWhitelist? SpawnerWhitelist; +} diff --git a/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs index cbd981e99e..9ac7a6edbb 100644 --- a/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs +++ b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs @@ -126,4 +126,8 @@ public abstract partial class GameRuleSystem where T: IComponent return found; } + protected void ForceEndSelf(EntityUid uid, GameRuleComponent? component = null) + { + GameTicker.EndGameRule(uid, component); + } } diff --git a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs index c60cf2513e..1c09d6e86e 100644 --- a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs @@ -1,9 +1,6 @@ -using Content.Server.Antag; using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.GridPreloader; -using Content.Server.Spawners.Components; -using Content.Shared.Whitelist; using Robust.Server.GameObjects; using Robust.Server.Maps; using Robust.Shared.Map; @@ -14,97 +11,70 @@ namespace Content.Server.GameTicking.Rules; public sealed class LoadMapRuleSystem : GameRuleSystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly MapSystem _map = default!; [Dependency] private readonly MapLoaderSystem _mapLoader = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly GridPreloaderSystem _gridPreloader = default!; - [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnSelectLocation); - SubscribeLocalEvent(OnGridSplit); - } - - private void OnGridSplit(ref GridSplitEvent args) - { - var rule = QueryActiveRules(); - while (rule.MoveNext(out _, out var mapComp, out _)) - { - if (!mapComp.MapGrids.Contains(args.Grid)) - continue; - - mapComp.MapGrids.AddRange(args.NewGrids); - break; - } - } protected override void Added(EntityUid uid, LoadMapRuleComponent comp, GameRuleComponent rule, GameRuleAddedEvent args) { - if (comp.Map != null) + if (comp.PreloadedGrid != null && !_gridPreloader.PreloadingEnabled) + { + // Preloading will never work if it's disabled, duh + Log.Debug($"Immediately ending {ToPrettyString(uid):rule} as preloading grids is disabled by cvar."); + ForceEndSelf(uid, rule); return; + } // grid preloading needs map to init after moving it - var mapUid = comp.PreloadedGrid != null ? _map.CreateMap(out var mapId, false) : _map.CreateMap(out mapId); - _metaData.SetEntityName(mapUid, $"LoadMapRule destination for rule {ToPrettyString(uid)}"); - comp.Map = mapId; + var mapUid = _map.CreateMap(out var mapId, runMapInit: comp.PreloadedGrid == null); + Log.Info($"Created map {mapId} for {ToPrettyString(uid):rule}"); + + IReadOnlyList grids; if (comp.GameMap != null) { var gameMap = _prototypeManager.Index(comp.GameMap.Value); - comp.MapGrids.AddRange(GameTicker.LoadGameMap(gameMap, comp.Map.Value, new MapLoadOptions())); + grids = GameTicker.LoadGameMap(gameMap, mapId, new MapLoadOptions()); } - else if (comp.MapPath != null) + else if (comp.MapPath is {} path) { - if (!_mapLoader.TryLoad(comp.Map.Value, - comp.MapPath.Value.ToString(), - out var roots, - new MapLoadOptions { LoadMap = true })) + var options = new MapLoadOptions { LoadMap = true }; + if (!_mapLoader.TryLoad(mapId, path.ToString(), out var roots, options)) { - _mapManager.DeleteMap(mapId); + Log.Error($"Failed to load map from {path}!"); + Del(mapUid); + ForceEndSelf(uid, rule); return; } - comp.MapGrids.AddRange(roots); + grids = roots; } - else if (comp.PreloadedGrid != null) + else if (comp.PreloadedGrid is {} preloaded) { // TODO: If there are no preloaded grids left, any rule announcements will still go off! - if (!_gridPreloader.TryGetPreloadedGrid(comp.PreloadedGrid.Value, out var loadedShuttle)) + if (!_gridPreloader.TryGetPreloadedGrid(preloaded, out var loadedShuttle)) { - _mapManager.DeleteMap(mapId); + Log.Error($"Failed to get a preloaded grid with {preloaded}!"); + Del(mapUid); + ForceEndSelf(uid, rule); return; } _transform.SetParent(loadedShuttle.Value, mapUid); - comp.MapGrids.Add(loadedShuttle.Value); - _map.InitializeMap(mapId); + grids = new List() { loadedShuttle.Value }; + _map.InitializeMap(mapUid); } else { Log.Error($"No valid map prototype or map path associated with the rule {ToPrettyString(uid)}"); + Del(mapUid); + ForceEndSelf(uid, rule); + return; } - } - private void OnSelectLocation(Entity ent, ref AntagSelectLocationEvent args) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out _, out var xform)) - { - if (xform.MapID != ent.Comp.Map) - continue; - - if (xform.GridUid == null || !ent.Comp.MapGrids.Contains(xform.GridUid.Value)) - continue; - - if (_whitelistSystem.IsWhitelistFail(ent.Comp.SpawnerWhitelist, uid)) - continue; - - args.Coordinates.Add(_transform.GetMapCoordinates(xform)); - } + var ev = new RuleLoadedGridsEvent(mapId, grids); + RaiseLocalEvent(uid, ref ev); } } diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 1b62778d75..6688bfd980 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -260,10 +260,10 @@ public sealed class NukeopsRuleSystem : GameRuleSystem { var map = Transform(ent).MapID; - var rules = EntityQueryEnumerator(); - while (rules.MoveNext(out var uid, out _, out var mapRule)) + var rules = EntityQueryEnumerator(); + while (rules.MoveNext(out var uid, out _, out var grids)) { - if (map != mapRule.Map) + if (map != grids.Map) continue; ent.Comp.AssociatedRule = uid; break; @@ -324,7 +324,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem if (nukeops.WarDeclaredTime != null) continue; - if (TryComp(uid, out var mapComp) && Transform(ev.DeclaratorEntity).MapID != mapComp.Map) + if (TryComp(uid, out var grids) && Transform(ev.DeclaratorEntity).MapID != grids.Map) continue; var newStatus = GetWarCondition(nukeops, ev.Status); @@ -445,7 +445,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem // Check that there are spawns available and that they can access the shuttle. var spawnsAvailable = EntityQuery(true).Any(); - if (spawnsAvailable && CompOrNull(ent)?.Map == shuttleMapId) + if (spawnsAvailable && CompOrNull(ent)?.Map == shuttleMapId) return; // Ghost spawns can still access the shuttle. Continue the round. // The shuttle is inaccessible to both living nuke operatives and yet to spawn nuke operatives, @@ -478,7 +478,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem /// Is this method the shitty glue holding together the last of my sanity? yes. /// Do i have a better solution? not presently. /// - private EntityUid? GetOutpost(Entity ent) + private EntityUid? GetOutpost(Entity ent) { if (!Resolve(ent, ref ent.Comp, false)) return null; diff --git a/Content.Server/GameTicking/Rules/RuleGridsSystem.cs b/Content.Server/GameTicking/Rules/RuleGridsSystem.cs new file mode 100644 index 0000000000..9eae9e3c95 --- /dev/null +++ b/Content.Server/GameTicking/Rules/RuleGridsSystem.cs @@ -0,0 +1,78 @@ +using Content.Server.Antag; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Spawners.Components; +using Content.Shared.Whitelist; +using Robust.Server.Physics; +using Robust.Shared.Map; + +namespace Content.Server.GameTicking.Rules; + +/// +/// Handles storing grids from and antags spawning on their spawners. +/// +public sealed class RuleGridsSystem : GameRuleSystem +{ + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGridSplit); + + SubscribeLocalEvent(OnLoadedGrids); + SubscribeLocalEvent(OnSelectLocation); + } + + private void OnGridSplit(ref GridSplitEvent args) + { + var rule = QueryActiveRules(); + while (rule.MoveNext(out _, out var comp, out _)) + { + if (!comp.MapGrids.Contains(args.Grid)) + continue; + + comp.MapGrids.AddRange(args.NewGrids); + break; // only 1 rule can own a grid, not multiple + } + } + + private void OnLoadedGrids(Entity ent, ref RuleLoadedGridsEvent args) + { + var (uid, comp) = ent; + if (comp.Map != null && args.Map != comp.Map) + { + Log.Warning($"{ToPrettyString(uid):rule} loaded grids on multiple maps {comp.Map} and {args.Map}, the second will be ignored."); + return; + } + + comp.Map = args.Map; + comp.MapGrids.AddRange(args.Grids); + } + + private void OnSelectLocation(Entity ent, ref AntagSelectLocationEvent args) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var xform)) + { + if (xform.MapID != ent.Comp.Map) + continue; + + if (xform.GridUid is not {} grid || !ent.Comp.MapGrids.Contains(grid)) + continue; + + if (_whitelist.IsWhitelistFail(ent.Comp.SpawnerWhitelist, uid)) + continue; + + args.Coordinates.Add(_transform.GetMapCoordinates(xform)); + } + } +} + +/// +/// Raised by another gamerule system to store loaded grids, and have other systems work with it. +/// A single rule can only load grids for a single map, attempts to load more are ignored. +/// +[ByRefEvent] +public record struct RuleLoadedGridsEvent(MapId Map, IReadOnlyList Grids); diff --git a/Content.Server/GridPreloader/GridPreloaderSystem.cs b/Content.Server/GridPreloader/GridPreloaderSystem.cs index 569fe54141..e12ce41a31 100644 --- a/Content.Server/GridPreloader/GridPreloaderSystem.cs +++ b/Content.Server/GridPreloader/GridPreloaderSystem.cs @@ -24,12 +24,19 @@ public sealed class GridPreloaderSystem : SharedGridPreloaderSystem [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + /// + /// Whether the preloading CVar is set or not. + /// + public bool PreloadingEnabled; + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnRoundRestart); SubscribeLocalEvent(OnPostGameMapLoad); + + Subs.CVar(_cfg, CCVars.PreloadGrids, value => PreloadingEnabled = value, true); } private void OnRoundRestart(RoundRestartCleanupEvent ev) @@ -52,7 +59,7 @@ public sealed class GridPreloaderSystem : SharedGridPreloaderSystem if (GetPreloaderEntity() != null) return; - if (!_cfg.GetCVar(CCVars.PreloadGrids)) + if (!PreloadingEnabled) return; var mapUid = _map.CreateMap(out var mapId, false); diff --git a/Content.Server/StationEvents/Events/StationEventSystem.cs b/Content.Server/StationEvents/Events/StationEventSystem.cs index 35dc646bce..06dfdd5276 100644 --- a/Content.Server/StationEvents/Events/StationEventSystem.cs +++ b/Content.Server/StationEvents/Events/StationEventSystem.cs @@ -108,13 +108,4 @@ public abstract class StationEventSystem : GameRuleSystem where T : ICompo } } } - - #region Helper Functions - - protected void ForceEndSelf(EntityUid uid, GameRuleComponent? component = null) - { - GameTicker.EndGameRule(uid, component); - } - - #endregion } diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index f421cf6818..d3b0971be0 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -422,6 +422,7 @@ weight: 5.5 minimumPlayers: 20 duration: 1 + - type: RuleGrids - type: LoadMapRule preloadedGrid: ShuttleStriker - type: NukeopsRule diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index c190b0333a..5a38c22215 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -3,7 +3,6 @@ - type: entity id: Ninja parent: BaseGameRule - noSpawn: true components: - type: GenericAntagRule agentName: ninja-round-end-agent-name diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index 3493749457..88f80fd4f6 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -78,6 +78,7 @@ - operationPrefix - operationSuffix - type: NukeopsRule + - type: RuleGrids - type: AntagSelection - type: AntagLoadProfileRule diff --git a/Resources/Prototypes/GameRules/unknown_shuttles.yml b/Resources/Prototypes/GameRules/unknown_shuttles.yml index 359d7bffc0..1ce365cd81 100644 --- a/Resources/Prototypes/GameRules/unknown_shuttles.yml +++ b/Resources/Prototypes/GameRules/unknown_shuttles.yml @@ -1,7 +1,7 @@ -- type: entity - id: UnknownShuttleCargoLost +- type: entity + abstract: true parent: BaseGameRule - noSpawn: true + id: BaseUnknownShuttleRule components: - type: StationEvent startAnnouncement: station-event-unknown-shuttle-incoming @@ -10,65 +10,44 @@ weight: 5 reoccurrenceDelay: 30 duration: 1 + - type: RuleGrids + - type: LoadMapRule + +- type: entity + parent: BaseUnknownShuttleRule + id: UnknownShuttleCargoLost + components: - type: LoadMapRule preloadedGrid: ShuttleCargoLost - type: entity + parent: BaseUnknownShuttleRule id: UnknownShuttleTravelingCuisine - parent: BaseGameRule - noSpawn: true components: - - type: StationEvent - startAnnouncement: station-event-unknown-shuttle-incoming - startAudio: - path: /Audio/Announcements/attention.ogg - weight: 5 - reoccurrenceDelay: 30 - duration: 1 - type: LoadMapRule preloadedGrid: TravelingCuisine - type: entity + parent: BaseUnknownShuttleRule id: UnknownShuttleDisasterEvacPod - parent: BaseGameRule - noSpawn: true components: - - type: StationEvent - startAnnouncement: station-event-unknown-shuttle-incoming - startAudio: - path: /Audio/Announcements/attention.ogg - weight: 5 - reoccurrenceDelay: 30 - duration: 1 - type: LoadMapRule preloadedGrid: DisasterEvacPod - type: entity + parent: BaseUnknownShuttleRule id: UnknownShuttleHonki - parent: BaseGameRule - noSpawn: true components: - type: StationEvent - startAnnouncement: station-event-unknown-shuttle-incoming - startAudio: - path: /Audio/Announcements/attention.ogg weight: 2 - reoccurrenceDelay: 30 - duration: 1 - type: LoadMapRule preloadedGrid: Honki - type: entity + parent: BaseUnknownShuttleRule id: UnknownShuttleSyndieEvacPod - parent: BaseGameRule - noSpawn: true components: - type: StationEvent - startAnnouncement: station-event-unknown-shuttle-incoming - startAudio: - path: /Audio/Announcements/attention.ogg weight: 2 - reoccurrenceDelay: 30 - duration: 1 - type: LoadMapRule preloadedGrid: SyndieEvacPod From 9d2b4ed3b22e548f02aeee7caa855b65b37dda24 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 3 Jun 2024 20:23:43 -0400 Subject: [PATCH 049/243] Add an integration test for solution fill level sprites. (#28564) --- .../Tests/FillLevelSpriteTest.cs | 71 +++++++++++++++++++ .../Prototypes/Entities/Objects/Fun/darts.yml | 4 -- .../Entities/Objects/Specific/chemistry.yml | 4 ++ 3 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 Content.IntegrationTests/Tests/FillLevelSpriteTest.cs diff --git a/Content.IntegrationTests/Tests/FillLevelSpriteTest.cs b/Content.IntegrationTests/Tests/FillLevelSpriteTest.cs new file mode 100644 index 0000000000..37e777fa8c --- /dev/null +++ b/Content.IntegrationTests/Tests/FillLevelSpriteTest.cs @@ -0,0 +1,71 @@ +using System.Linq; +using Content.Shared.Chemistry.Components; +using Robust.Client.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests; + +/// +/// Tests to see if any entity prototypes specify solution fill level sprites that don't exist. +/// +[TestFixture] +public sealed class FillLevelSpriteTest +{ + private static readonly string[] HandStateNames = ["left", "right"]; + + [Test] + public async Task FillLevelSpritesExist() + { + await using var pair = await PoolManager.GetServerClient(); + var client = pair.Client; + var protoMan = client.ResolveDependency(); + var componentFactory = client.ResolveDependency(); + + await client.WaitAssertion(() => + { + var protos = protoMan.EnumeratePrototypes() + .Where(p => !p.Abstract) + .Where(p => !pair.IsTestPrototype(p)) + .Where(p => p.TryGetComponent(out _, componentFactory)) + .OrderBy(p => p.ID) + .ToList(); + + foreach (var proto in protos) + { + Assert.That(proto.TryGetComponent(out var visuals, componentFactory)); + Assert.That(proto.TryGetComponent(out var sprite, componentFactory)); + + var rsi = sprite.BaseRSI; + + // Test base sprite fills + if (!string.IsNullOrEmpty(visuals.FillBaseName)) + { + for (var i = 1; i <= visuals.MaxFillLevels; i++) + { + var state = $"{visuals.FillBaseName}{i}"; + Assert.That(rsi.TryGetState(state, out _), @$"{proto.ID} has SolutionContainerVisualsComponent with + MaxFillLevels = {visuals.MaxFillLevels}, but {rsi.Path} doesn't have state {state}!"); + } + } + + // Test inhand sprite fills + if (!string.IsNullOrEmpty(visuals.InHandsFillBaseName)) + { + for (var i = 1; i <= visuals.InHandsMaxFillLevels; i++) + { + foreach (var handname in HandStateNames) + { + var state = $"inhand-{handname}{visuals.InHandsFillBaseName}{i}"; + Assert.That(rsi.TryGetState(state, out _), @$"{proto.ID} has SolutionContainerVisualsComponent with + InHandsMaxFillLevels = {visuals.InHandsMaxFillLevels}, but {rsi.Path} doesn't have state {state}!"); + } + + } + } + } + }); + + await pair.CleanReturnAsync(); + } +} diff --git a/Resources/Prototypes/Entities/Objects/Fun/darts.yml b/Resources/Prototypes/Entities/Objects/Fun/darts.yml index 36c841995e..c0b5eb3399 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/darts.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/darts.yml @@ -83,10 +83,6 @@ max: 1 - !type:DoActsBehavior acts: [ "Destruction" ] - - type: Appearance - - type: SolutionContainerVisuals - maxFillLevels: 1 - fillBaseName: dart - type: entity parent: Dart diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index ce0e0b629a..dd15a90baa 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -233,6 +233,8 @@ sprite: Objects/Specific/Chemistry/beaker_cryostasis.rsi layers: - state: beakernoreact + - type: SolutionContainerVisuals + maxFillLevels: 0 - type: SolutionContainerManager solutions: beaker: @@ -251,6 +253,8 @@ sprite: Objects/Specific/Chemistry/beaker_bluespace.rsi layers: - state: beakerbluespace + - type: SolutionContainerVisuals + maxFillLevels: 0 - type: SolutionContainerManager solutions: beaker: From a3a1538d32c4e0c7d9aeab9790e0c522a211fc79 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:53:24 +0000 Subject: [PATCH 050/243] move gamerule components to shared (#28572) * move MinMax to shared * cleanup MinMax * move other ticking components to shared just because * remove unused prototype file * update everything to use shared components * test * test 2 * test 3 --------- Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../DestructibleDestructionTest.cs | 1 + .../Tests/GameRules/FailAndStartPresetTest.cs | 2 +- .../Tests/GameRules/RuleMaxTimeRestartTest.cs | 2 +- Content.Server/Administration/ServerApi.cs | 2 +- Content.Server/Antag/AntagSelectionSystem.cs | 2 +- .../Components/AntagSelectionComponent.cs | 2 +- .../Antag/MobReplacementRuleSystem.cs | 2 +- .../Behaviors/SpawnEntitiesBehavior.cs | 6 ++--- .../Destructible/Thresholds/MinMax.cs | 26 ------------------- .../GameTicking/GameTicker.GameRule.cs | 2 +- .../GameTicking/Rules/DeathMatchRuleSystem.cs | 2 +- .../GameTicking/Rules/GameRulePrototype.cs | 15 ----------- .../Rules/GameRuleSystem.Utility.cs | 2 +- .../GameTicking/Rules/GameRuleSystem.cs | 2 +- .../Rules/InactivityTimeRestartRuleSystem.cs | 2 +- .../Rules/KillCalloutRuleSystem.cs | 2 +- .../GameTicking/Rules/LoadMapRuleSystem.cs | 2 +- .../Rules/MaxTimeRestartRuleSystem.cs | 2 +- .../GameTicking/Rules/NukeopsRuleSystem.cs | 2 +- .../GameTicking/Rules/RespawnRuleSystem.cs | 2 +- .../Rules/RevolutionaryRuleSystem.cs | 2 +- .../RoundstartStationVariationRuleSystem.cs | 2 +- .../GameTicking/Rules/SandboxRuleSystem.cs | 2 +- .../GameTicking/Rules/SecretRuleSystem.cs | 2 +- .../GameTicking/Rules/SubGamemodesSystem.cs | 2 +- .../GameTicking/Rules/TraitorRuleSystem.cs | 2 +- .../GameTicking/Rules/ZombieRuleSystem.cs | 2 +- Content.Server/Objectives/ObjectivesSystem.cs | 2 +- .../PowerMonitoringConsoleSystem.cs | 2 +- .../EntitySystems/ConditionalSpawnerSystem.cs | 2 +- .../BasicStationEventSchedulerSystem.cs | 2 +- .../Events/AlertLevelInterceptionRule.cs | 4 +-- .../StationEvents/Events/AnomalySpawnRule.cs | 2 +- .../Events/BluespaceArtifactRule.cs | 2 +- .../Events/BluespaceLockerRule.cs | 2 +- .../StationEvents/Events/BreakerFlipRule.cs | 2 +- .../Events/BureaucraticErrorRule.cs | 2 +- .../StationEvents/Events/CargoGiftsRule.cs | 2 +- .../StationEvents/Events/ClericalErrorRule.cs | 2 +- .../StationEvents/Events/FalseAlarmRule.cs | 2 +- .../StationEvents/Events/GasLeakRule.cs | 2 +- .../StationEvents/Events/ImmovableRodRule.cs | 2 +- .../StationEvents/Events/IonStormRule.cs | 2 +- .../StationEvents/Events/KudzuGrowthRule.cs | 2 +- .../Events/MassHallucinationsRule.cs | 2 +- .../StationEvents/Events/MeteorSwarmRule.cs | 2 +- .../StationEvents/Events/NinjaSpawnRule.cs | 2 +- .../Events/PowerGridCheckRule.cs | 2 +- .../Events/RandomEntityStorageSpawnRule.cs | 2 +- .../Events/RandomSentienceRule.cs | 2 +- .../StationEvents/Events/RandomSpawnRule.cs | 2 +- .../StationEvents/Events/SolarFlareRule.cs | 2 +- .../Events/StationEventSystem.cs | 2 +- .../StationEvents/Events/VentClogRule.cs | 2 +- .../StationEvents/Events/VentCrittersRule.cs | 2 +- .../RampingStationEventSchedulerSystem.cs | 2 +- .../Destructible/Thresholds/MinMax.cs | 24 +++++++++++++++++ .../Components/ActiveGameRuleComponent.cs | 6 ++--- .../Components/DelayedStartRuleComponent.cs | 2 +- .../Components/EndedGameRuleComponent.cs | 6 ++--- .../Components/GameRuleComponent.cs | 4 +-- 61 files changed, 88 insertions(+), 108 deletions(-) delete mode 100644 Content.Server/Destructible/Thresholds/MinMax.cs delete mode 100644 Content.Server/GameTicking/Rules/GameRulePrototype.cs create mode 100644 Content.Shared/Destructible/Thresholds/MinMax.cs rename {Content.Server => Content.Shared}/GameTicking/Components/ActiveGameRuleComponent.cs (67%) rename {Content.Server => Content.Shared}/GameTicking/Components/DelayedStartRuleComponent.cs (91%) rename {Content.Server => Content.Shared}/GameTicking/Components/EndedGameRuleComponent.cs (61%) rename {Content.Server => Content.Shared}/GameTicking/Components/GameRuleComponent.cs (94%) diff --git a/Content.IntegrationTests/Tests/Destructible/DestructibleDestructionTest.cs b/Content.IntegrationTests/Tests/Destructible/DestructibleDestructionTest.cs index e14a826467..a50238d8f5 100644 --- a/Content.IntegrationTests/Tests/Destructible/DestructibleDestructionTest.cs +++ b/Content.IntegrationTests/Tests/Destructible/DestructibleDestructionTest.cs @@ -3,6 +3,7 @@ using Content.Server.Destructible.Thresholds; using Content.Server.Destructible.Thresholds.Behaviors; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; +using Content.Shared.Destructible.Thresholds; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes; diff --git a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs index 1fed226bee..09a27c1bae 100644 --- a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs @@ -1,9 +1,9 @@ #nullable enable using Content.Server.GameTicking; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Presets; using Content.Shared.CCVar; using Content.Shared.GameTicking; +using Content.Shared.GameTicking.Components; using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.GameRules; diff --git a/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs index 20a157e33e..4708bd10c0 100644 --- a/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs @@ -1,9 +1,9 @@ using Content.Server.GameTicking; using Content.Server.GameTicking.Commands; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; using Content.Shared.CCVar; +using Content.Shared.GameTicking.Components; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.Timing; diff --git a/Content.Server/Administration/ServerApi.cs b/Content.Server/Administration/ServerApi.cs index 04fd38598f..d0f23db637 100644 --- a/Content.Server/Administration/ServerApi.cs +++ b/Content.Server/Administration/ServerApi.cs @@ -8,13 +8,13 @@ using System.Text.Json.Nodes; using System.Threading.Tasks; using Content.Server.Administration.Systems; using Content.Server.GameTicking; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Presets; using Content.Server.GameTicking.Rules.Components; using Content.Server.Maps; using Content.Server.RoundEnd; using Content.Shared.Administration.Managers; using Content.Shared.CCVar; +using Content.Shared.GameTicking.Components; using Content.Shared.Prototypes; using Robust.Server.ServerStatus; using Robust.Shared.Asynchronous; diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 710bb8f3d7..52ccb1bd70 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -2,7 +2,6 @@ using System.Linq; using Content.Server.Antag.Components; using Content.Server.Chat.Managers; using Content.Server.GameTicking; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles.Components; @@ -15,6 +14,7 @@ using Content.Server.Shuttles.Components; using Content.Server.Station.Systems; using Content.Shared.Antag; using Content.Shared.GameTicking; +using Content.Shared.GameTicking.Components; using Content.Shared.Ghost; using Content.Shared.Humanoid; using Content.Shared.Players; diff --git a/Content.Server/Antag/Components/AntagSelectionComponent.cs b/Content.Server/Antag/Components/AntagSelectionComponent.cs index f70c27d12f..1ab16e8aec 100644 --- a/Content.Server/Antag/Components/AntagSelectionComponent.cs +++ b/Content.Server/Antag/Components/AntagSelectionComponent.cs @@ -1,6 +1,6 @@ using Content.Server.Administration.Systems; -using Content.Server.Destructible.Thresholds; using Content.Shared.Antag; +using Content.Shared.Destructible.Thresholds; using Content.Shared.Roles; using Content.Shared.Storage; using Content.Shared.Whitelist; diff --git a/Content.Server/Antag/MobReplacementRuleSystem.cs b/Content.Server/Antag/MobReplacementRuleSystem.cs index 18837b5a7c..eeff765d8b 100644 --- a/Content.Server/Antag/MobReplacementRuleSystem.cs +++ b/Content.Server/Antag/MobReplacementRuleSystem.cs @@ -1,7 +1,7 @@ using Content.Server.Antag.Mimic; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; +using Content.Shared.GameTicking.Components; using Content.Shared.VendingMachines; using Robust.Shared.Map; using Robust.Shared.Random; diff --git a/Content.Server/Destructible/Thresholds/Behaviors/SpawnEntitiesBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/SpawnEntitiesBehavior.cs index 057b6df9df..4ec6fa9e90 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/SpawnEntitiesBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/SpawnEntitiesBehavior.cs @@ -1,12 +1,12 @@ using System.Numerics; using Content.Server.Forensics; using Content.Server.Stack; +using Content.Shared.Destructible.Thresholds; using Content.Shared.Prototypes; using Content.Shared.Stacks; using Robust.Server.GameObjects; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; namespace Content.Server.Destructible.Thresholds.Behaviors { @@ -17,8 +17,8 @@ namespace Content.Server.Destructible.Thresholds.Behaviors /// /// Entities spawned on reaching this threshold, from a min to a max. /// - [DataField("spawn", customTypeSerializer:typeof(PrototypeIdDictionarySerializer))] - public Dictionary Spawn { get; set; } = new(); + [DataField] + public Dictionary Spawn = new(); [DataField("offset")] public float Offset { get; set; } = 0.5f; diff --git a/Content.Server/Destructible/Thresholds/MinMax.cs b/Content.Server/Destructible/Thresholds/MinMax.cs deleted file mode 100644 index c44864183a..0000000000 --- a/Content.Server/Destructible/Thresholds/MinMax.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Robust.Shared.Random; - -namespace Content.Server.Destructible.Thresholds -{ - [Serializable] - [DataDefinition] - public partial struct MinMax - { - [DataField("min")] - public int Min; - - [DataField("max")] - public int Max; - - public MinMax(int min, int max) - { - Min = min; - Max = max; - } - - public int Next(IRobustRandom random) - { - return random.Next(Min, Max + 1); - } - } -} diff --git a/Content.Server/GameTicking/GameTicker.GameRule.cs b/Content.Server/GameTicking/GameTicker.GameRule.cs index a6d0a4baf0..2a33d01bd0 100644 --- a/Content.Server/GameTicking/GameTicker.GameRule.cs +++ b/Content.Server/GameTicking/GameTicker.GameRule.cs @@ -1,9 +1,9 @@ using System.Linq; using Content.Server.Administration; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Shared.Administration; using Content.Shared.Database; +using Content.Shared.GameTicking.Components; using Content.Shared.Prototypes; using JetBrains.Annotations; using Robust.Shared.Console; diff --git a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs index 9e3203d170..557e44f095 100644 --- a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs @@ -1,12 +1,12 @@ using System.Linq; using Content.Server.Administration.Commands; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.KillTracking; using Content.Server.Mind; using Content.Server.Points; using Content.Server.RoundEnd; using Content.Server.Station.Systems; +using Content.Shared.GameTicking.Components; using Content.Shared.Points; using Content.Shared.Storage; using Robust.Server.GameObjects; diff --git a/Content.Server/GameTicking/Rules/GameRulePrototype.cs b/Content.Server/GameTicking/Rules/GameRulePrototype.cs deleted file mode 100644 index 47f99184f7..0000000000 --- a/Content.Server/GameTicking/Rules/GameRulePrototype.cs +++ /dev/null @@ -1,15 +0,0 @@ - - -namespace Content.Server.GameTicking.Rules; - -/* -[Prototype("gameRule")] -public sealed partial class GameRulePrototype : IPrototype -{ - [IdDataField] - public string ID { get; private set; } = default!; - - [DataField("config", required: true)] - public GameRuleConfiguration Configuration { get; private set; } = default!; -} -*/ diff --git a/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs index 9ac7a6edbb..5a5eb720cf 100644 --- a/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs +++ b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs @@ -1,8 +1,8 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; +using Content.Shared.GameTicking.Components; using Content.Shared.Random.Helpers; using Robust.Server.GameObjects; using Robust.Shared.Collections; diff --git a/Content.Server/GameTicking/Rules/GameRuleSystem.cs b/Content.Server/GameTicking/Rules/GameRuleSystem.cs index 05374aa139..730748ce6b 100644 --- a/Content.Server/GameTicking/Rules/GameRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/GameRuleSystem.cs @@ -1,6 +1,6 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Managers; -using Content.Server.GameTicking.Components; +using Content.Shared.GameTicking.Components; using Robust.Server.GameObjects; using Robust.Shared.Random; using Robust.Shared.Timing; diff --git a/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs b/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs index 01fa387595..e56537c438 100644 --- a/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs @@ -1,7 +1,7 @@ using System.Threading; using Content.Server.Chat.Managers; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; +using Content.Shared.GameTicking.Components; using Robust.Server.Player; using Robust.Shared.Player; using Timer = Robust.Shared.Timing.Timer; diff --git a/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs b/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs index 3da55e30c9..8f706fd2ad 100644 --- a/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs @@ -1,8 +1,8 @@ using Content.Server.Chat.Managers; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.KillTracking; using Content.Shared.Chat; +using Content.Shared.GameTicking.Components; using Robust.Server.Player; using Robust.Shared.Player; using Robust.Shared.Random; diff --git a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs index 1c09d6e86e..3594242bcd 100644 --- a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs @@ -1,6 +1,6 @@ -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.GridPreloader; +using Content.Shared.GameTicking.Components; using Robust.Server.GameObjects; using Robust.Server.Maps; using Robust.Shared.Map; diff --git a/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs b/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs index cae99fee9f..db9df8a5b0 100644 --- a/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs @@ -1,7 +1,7 @@ using System.Threading; using Content.Server.Chat.Managers; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; +using Content.Shared.GameTicking.Components; using Timer = Robust.Shared.Timing.Timer; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 6688bfd980..5c3459ef81 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -11,6 +11,7 @@ using Content.Server.Shuttles.Systems; using Content.Server.Station.Components; using Content.Server.Store.Components; using Content.Server.Store.Systems; +using Content.Shared.GameTicking.Components; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.NPC.Components; @@ -24,7 +25,6 @@ using Robust.Shared.Map; using Robust.Shared.Random; using Robust.Shared.Utility; using System.Linq; -using Content.Server.GameTicking.Components; using Content.Shared.Store.Components; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs index 3f8d31f622..38102f1a2e 100644 --- a/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs @@ -1,9 +1,9 @@ using Content.Server.Chat.Managers; using Content.Server.Database.Migrations.Postgres; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Systems; using Content.Shared.Chat; +using Content.Shared.GameTicking.Components; using Content.Shared.Interaction.Events; using Content.Shared.Mind; using Content.Shared.Mobs; diff --git a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs index 58ced96295..c5f88ab6cf 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -12,6 +12,7 @@ using Content.Server.RoundEnd; using Content.Server.Shuttles.Systems; using Content.Server.Station.Systems; using Content.Shared.Database; +using Content.Shared.GameTicking.Components; using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; using Content.Shared.Mind; @@ -27,7 +28,6 @@ using Content.Shared.Stunnable; using Content.Shared.Zombies; using Robust.Shared.Prototypes; using Robust.Shared.Timing; -using Content.Server.GameTicking.Components; using Content.Shared.Cuffs.Components; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs b/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs index f09ed3ebc3..570889155b 100644 --- a/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs @@ -1,9 +1,9 @@ using System.Linq; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Shuttles.Systems; using Content.Server.Station.Components; using Content.Server.Station.Events; +using Content.Shared.GameTicking.Components; using Content.Shared.Storage; using Robust.Shared.Prototypes; using Robust.Shared.Random; diff --git a/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs b/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs index c60670a3ad..23e9ee5a7d 100644 --- a/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs @@ -1,6 +1,6 @@ -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Sandbox; +using Content.Shared.GameTicking.Components; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs index d25262b797..320f9d197a 100644 --- a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs @@ -1,10 +1,10 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.Administration.Logs; -using Content.Server.GameTicking.Components; using Content.Server.Chat.Managers; using Content.Server.GameTicking.Presets; using Content.Server.GameTicking.Rules.Components; +using Content.Shared.GameTicking.Components; using Content.Shared.Random; using Content.Shared.CCVar; using Content.Shared.Database; diff --git a/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs b/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs index 4486ee40fb..4fe3827ce5 100644 --- a/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs +++ b/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs @@ -1,5 +1,5 @@ -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; +using Content.Shared.GameTicking.Components; using Content.Shared.Storage; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index 29de85a42e..17442da857 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -5,6 +5,7 @@ using Content.Server.Objectives; using Content.Server.PDA.Ringer; using Content.Server.Roles; using Content.Server.Traitor.Uplink; +using Content.Shared.GameTicking.Components; using Content.Shared.Mind; using Content.Shared.NPC.Systems; using Content.Shared.Objectives.Components; @@ -15,7 +16,6 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using System.Linq; using System.Text; -using Content.Server.GameTicking.Components; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs index 97e5fd3fc4..95d1f791ef 100644 --- a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs @@ -6,6 +6,7 @@ using Content.Server.RoundEnd; using Content.Server.Station.Components; using Content.Server.Station.Systems; using Content.Server.Zombies; +using Content.Shared.GameTicking.Components; using Content.Shared.Humanoid; using Content.Shared.Mind; using Content.Shared.Mobs; @@ -15,7 +16,6 @@ using Content.Shared.Zombies; using Robust.Shared.Player; using Robust.Shared.Timing; using System.Globalization; -using Content.Server.GameTicking.Components; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/Objectives/ObjectivesSystem.cs b/Content.Server/Objectives/ObjectivesSystem.cs index f8ecc22828..18077b413a 100644 --- a/Content.Server/Objectives/ObjectivesSystem.cs +++ b/Content.Server/Objectives/ObjectivesSystem.cs @@ -1,6 +1,7 @@ using Content.Server.GameTicking; using Content.Server.Shuttles.Systems; using Content.Shared.Cuffs.Components; +using Content.Shared.GameTicking.Components; using Content.Shared.Mind; using Content.Shared.Objectives.Components; using Content.Shared.Objectives.Systems; @@ -9,7 +10,6 @@ using Content.Shared.Random.Helpers; using Robust.Shared.Prototypes; using Robust.Shared.Random; using System.Linq; -using Content.Server.GameTicking.Components; using System.Text; using Robust.Server.Player; diff --git a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs index 42c84b7f43..35b17dc958 100644 --- a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Power.Nodes; using Content.Server.Power.NodeGroups; using Content.Server.Station.Components; using Content.Server.StationEvents.Components; +using Content.Shared.GameTicking.Components; using Content.Shared.Pinpointer; using Content.Shared.Power; using JetBrains.Annotations; @@ -13,7 +14,6 @@ using Robust.Server.GameObjects; using Robust.Shared.Map.Components; using Robust.Shared.Utility; using System.Linq; -using Content.Server.GameTicking.Components; namespace Content.Server.Power.EntitySystems; diff --git a/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs b/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs index 75f8618798..f57481b05b 100644 --- a/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs +++ b/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs @@ -1,8 +1,8 @@ using System.Numerics; using Content.Server.GameTicking; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Spawners.Components; +using Content.Shared.GameTicking.Components; using JetBrains.Annotations; using Robust.Shared.Random; diff --git a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs index 7e32f546be..501d9033b9 100644 --- a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs +++ b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs @@ -1,11 +1,11 @@ using System.Linq; using Content.Server.Administration; using Content.Server.GameTicking; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Shared.Administration; +using Content.Shared.GameTicking.Components; using JetBrains.Annotations; using Robust.Shared.Random; using Robust.Shared.Toolshed; diff --git a/Content.Server/StationEvents/Events/AlertLevelInterceptionRule.cs b/Content.Server/StationEvents/Events/AlertLevelInterceptionRule.cs index a78a542d3b..916d7d1688 100644 --- a/Content.Server/StationEvents/Events/AlertLevelInterceptionRule.cs +++ b/Content.Server/StationEvents/Events/AlertLevelInterceptionRule.cs @@ -1,6 +1,6 @@ -using Content.Server.GameTicking.Components; using Content.Server.StationEvents.Components; using Content.Server.AlertLevel; +using Content.Shared.GameTicking.Components; namespace Content.Server.StationEvents.Events; @@ -20,4 +20,4 @@ public sealed class AlertLevelInterceptionRule : StationEventSystem /// Added to game rules before and removed before . /// Mutually exclusive with . /// [RegisterComponent] -public sealed partial class ActiveGameRuleComponent : Component -{ -} +public sealed partial class ActiveGameRuleComponent : Component; diff --git a/Content.Server/GameTicking/Components/DelayedStartRuleComponent.cs b/Content.Shared/GameTicking/Components/DelayedStartRuleComponent.cs similarity index 91% rename from Content.Server/GameTicking/Components/DelayedStartRuleComponent.cs rename to Content.Shared/GameTicking/Components/DelayedStartRuleComponent.cs index de4be83627..9275da29b0 100644 --- a/Content.Server/GameTicking/Components/DelayedStartRuleComponent.cs +++ b/Content.Shared/GameTicking/Components/DelayedStartRuleComponent.cs @@ -1,6 +1,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.GameTicking.Components; +namespace Content.Shared.GameTicking.Components; /// /// Generic component used to track a gamerule that's start has been delayed. diff --git a/Content.Server/GameTicking/Components/EndedGameRuleComponent.cs b/Content.Shared/GameTicking/Components/EndedGameRuleComponent.cs similarity index 61% rename from Content.Server/GameTicking/Components/EndedGameRuleComponent.cs rename to Content.Shared/GameTicking/Components/EndedGameRuleComponent.cs index 3234bfff3a..5e209ed78a 100644 --- a/Content.Server/GameTicking/Components/EndedGameRuleComponent.cs +++ b/Content.Shared/GameTicking/Components/EndedGameRuleComponent.cs @@ -1,10 +1,8 @@ -namespace Content.Server.GameTicking.Components; +namespace Content.Shared.GameTicking.Components; /// /// Added to game rules before . /// Mutually exclusive with . /// [RegisterComponent] -public sealed partial class EndedGameRuleComponent : Component -{ -} +public sealed partial class EndedGameRuleComponent : Component; diff --git a/Content.Server/GameTicking/Components/GameRuleComponent.cs b/Content.Shared/GameTicking/Components/GameRuleComponent.cs similarity index 94% rename from Content.Server/GameTicking/Components/GameRuleComponent.cs rename to Content.Shared/GameTicking/Components/GameRuleComponent.cs index 635452b3f3..4e93c2b003 100644 --- a/Content.Server/GameTicking/Components/GameRuleComponent.cs +++ b/Content.Shared/GameTicking/Components/GameRuleComponent.cs @@ -1,8 +1,8 @@ -using Content.Server.Destructible.Thresholds; +using Content.Shared.Destructible.Thresholds; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.GameTicking.Components; +namespace Content.Shared.GameTicking.Components; /// /// Component attached to all gamerule entities. From 87fe804831e15ccd757d5830c9413811ed2fb780 Mon Sep 17 00:00:00 2001 From: Ghagliiarghii <68826635+Ghagliiarghii@users.noreply.github.com> Date: Tue, 4 Jun 2024 07:57:17 -0400 Subject: [PATCH 051/243] tyvm, tysm (#28577) * tyvm, tysm * cya -> see you * ya --- Resources/Locale/en-US/speech/speech-chatsan.ftl | 9 +++++++++ Resources/Prototypes/Accents/word_replacements.yml | 3 +++ 2 files changed, 12 insertions(+) diff --git a/Resources/Locale/en-US/speech/speech-chatsan.ftl b/Resources/Locale/en-US/speech/speech-chatsan.ftl index 25e6c6f1ea..ef94507295 100644 --- a/Resources/Locale/en-US/speech/speech-chatsan.ftl +++ b/Resources/Locale/en-US/speech/speech-chatsan.ftl @@ -120,3 +120,12 @@ chatsan-replacement-43 = i guess chatsan-word-44 = tbf chatsan-replacement-44 = to be fair + +chatsan-word-45 = tysm +chatsan-replacement-45 = thank you so much + +chatsan-word-46 = tyvm +chatsan-replacement-46 = thank you very much + +chatsan-word-47 = cya +chatsan-replacement-47 = see ya \ No newline at end of file diff --git a/Resources/Prototypes/Accents/word_replacements.yml b/Resources/Prototypes/Accents/word_replacements.yml index 9a801d786d..40cc94e453 100644 --- a/Resources/Prototypes/Accents/word_replacements.yml +++ b/Resources/Prototypes/Accents/word_replacements.yml @@ -425,6 +425,9 @@ chatsan-word-42: chatsan-replacement-42 chatsan-word-43: chatsan-replacement-43 chatsan-word-44: chatsan-replacement-44 + chatsan-word-45: chatsan-replacement-45 + chatsan-word-46: chatsan-replacement-46 + chatsan-word-47: chatsan-replacement-47 - type: accent id: liar From 58647e703683e848730589ab003a94400f6c1075 Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:11:48 +0200 Subject: [PATCH 052/243] Don't switch-unwield if mob has more than 2 hands (#28438) --- Content.Shared/Wieldable/WieldableSystem.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Content.Shared/Wieldable/WieldableSystem.cs b/Content.Shared/Wieldable/WieldableSystem.cs index c09044f84b..cb95c1d7e7 100644 --- a/Content.Shared/Wieldable/WieldableSystem.cs +++ b/Content.Shared/Wieldable/WieldableSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Shared.Examine; using Content.Shared.Hands; using Content.Shared.Hands.Components; @@ -94,7 +95,8 @@ public sealed class WieldableSystem : EntitySystem private void OnDeselectWieldable(EntityUid uid, WieldableComponent component, HandDeselectedEvent args) { - if (!component.Wielded) + if (!component.Wielded || + _handsSystem.EnumerateHands(args.User).Count() > 2) return; TryUnwield(uid, component, args.User); From d4fe7eda515a1374ec753e740f025aefcae18e5a Mon Sep 17 00:00:00 2001 From: Hannah Giovanna Dawson Date: Tue, 4 Jun 2024 14:23:09 +0100 Subject: [PATCH 053/243] SS14-26964 Clown Waddling Replicates, etc (#26983) --- .../Movement/Systems/WaddleAnimationSystem.cs | 168 ++++++++---------- .../Movement/Systems/WaddleAnimationSystem.cs | 5 + .../Components/WaddleWhenWornComponent.cs | 11 +- .../EntitySystems}/WaddleClothingSystem.cs | 17 +- .../Components/WaddleAnimationComponent.cs | 28 +-- .../Systems/SharedWaddleAnimationSystem.cs | 106 +++++++++++ 6 files changed, 213 insertions(+), 122 deletions(-) create mode 100644 Content.Server/Movement/Systems/WaddleAnimationSystem.cs rename {Content.Client/Clothing/Systems => Content.Shared/Clothing/EntitySystems}/WaddleClothingSystem.cs (59%) create mode 100644 Content.Shared/Movement/Systems/SharedWaddleAnimationSystem.cs diff --git a/Content.Client/Movement/Systems/WaddleAnimationSystem.cs b/Content.Client/Movement/Systems/WaddleAnimationSystem.cs index 9555c1f6b9..0ed2d04f69 100644 --- a/Content.Client/Movement/Systems/WaddleAnimationSystem.cs +++ b/Content.Client/Movement/Systems/WaddleAnimationSystem.cs @@ -2,94 +2,115 @@ using Content.Client.Buckle; using Content.Client.Gravity; using Content.Shared.ActionBlocker; -using Content.Shared.Buckle.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Components; -using Content.Shared.Movement.Events; -using Content.Shared.StatusEffect; -using Content.Shared.Stunnable; +using Content.Shared.Movement.Systems; using Robust.Client.Animations; using Robust.Client.GameObjects; using Robust.Shared.Animations; -using Robust.Shared.Timing; namespace Content.Client.Movement.Systems; -public sealed class WaddleAnimationSystem : EntitySystem +public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem { [Dependency] private readonly AnimationPlayerSystem _animation = default!; [Dependency] private readonly GravitySystem _gravity = default!; - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly BuckleSystem _buckle = default!; [Dependency] private readonly MobStateSystem _mobState = default!; public override void Initialize() { - SubscribeLocalEvent(OnMovementInput); - SubscribeLocalEvent(OnStartedWalking); - SubscribeLocalEvent(OnStoppedWalking); + base.Initialize(); + + SubscribeAllEvent(OnStartWaddling); SubscribeLocalEvent(OnAnimationCompleted); - SubscribeLocalEvent(OnStunned); - SubscribeLocalEvent(OnKnockedDown); - SubscribeLocalEvent(OnBuckleChange); + SubscribeAllEvent(OnStopWaddling); } - private void OnMovementInput(EntityUid entity, WaddleAnimationComponent component, MoveInputEvent args) + private void OnStartWaddling(StartedWaddlingEvent msg, EntitySessionEventArgs args) { - // Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume - // they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR. - if (!_timing.IsFirstTimePredicted) - { - return; - } - - if (!args.HasDirectionalMovement && component.IsCurrentlyWaddling) - { - var stopped = new StoppedWaddlingEvent(entity); - - RaiseLocalEvent(entity, ref stopped); - - return; - } - - // Only start waddling if we're not currently AND we're actually moving. - if (component.IsCurrentlyWaddling || !args.HasDirectionalMovement) - return; - - var started = new StartedWaddlingEvent(entity); - - RaiseLocalEvent(entity, ref started); + if (TryComp(GetEntity(msg.Entity), out var comp)) + StartWaddling((GetEntity(msg.Entity), comp)); } - private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args) + private void OnStopWaddling(StoppedWaddlingEvent msg, EntitySessionEventArgs args) { - if (_animation.HasRunningAnimation(uid, component.KeyName)) + if (TryComp(GetEntity(msg.Entity), out var comp)) + StopWaddling((GetEntity(msg.Entity), comp)); + } + + private void StartWaddling(Entity entity) + { + if (_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName)) return; - if (!TryComp(uid, out var mover)) + if (!TryComp(entity.Owner, out var mover)) return; - if (_gravity.IsWeightless(uid)) + if (_gravity.IsWeightless(entity.Owner)) return; - - if (!_actionBlocker.CanMove(uid, mover)) + if (!_actionBlocker.CanMove(entity.Owner, mover)) return; // Do nothing if buckled in - if (_buckle.IsBuckled(uid)) + if (_buckle.IsBuckled(entity.Owner)) return; // Do nothing if crit or dead (for obvious reasons) - if (_mobState.IsIncapacitated(uid)) + if (_mobState.IsIncapacitated(entity.Owner)) return; - var tumbleIntensity = component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity; - var len = mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength; + PlayWaddleAnimationUsing( + (entity.Owner, entity.Comp), + CalculateAnimationLength(entity.Comp, mover), + CalculateTumbleIntensity(entity.Comp) + ); + } - component.LastStep = !component.LastStep; - component.IsCurrentlyWaddling = true; + private static float CalculateTumbleIntensity(WaddleAnimationComponent component) + { + return component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity; + } + + private static float CalculateAnimationLength(WaddleAnimationComponent component, InputMoverComponent mover) + { + return mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength; + } + + private void OnAnimationCompleted(Entity entity, ref AnimationCompletedEvent args) + { + if (args.Key != entity.Comp.KeyName) + return; + + if (!TryComp(entity.Owner, out var mover)) + return; + + PlayWaddleAnimationUsing( + (entity.Owner, entity.Comp), + CalculateAnimationLength(entity.Comp, mover), + CalculateTumbleIntensity(entity.Comp) + ); + } + + private void StopWaddling(Entity entity) + { + if (!_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName)) + return; + + _animation.Stop(entity.Owner, entity.Comp.KeyName); + + if (!TryComp(entity.Owner, out var sprite)) + return; + + sprite.Offset = new Vector2(); + sprite.Rotation = Angle.FromDegrees(0); + } + + private void PlayWaddleAnimationUsing(Entity entity, float len, float tumbleIntensity) + { + entity.Comp.LastStep = !entity.Comp.LastStep; var anim = new Animation() { @@ -116,58 +137,13 @@ public sealed class WaddleAnimationSystem : EntitySystem KeyFrames = { new AnimationTrackProperty.KeyFrame(new Vector2(), 0), - new AnimationTrackProperty.KeyFrame(component.HopIntensity, len/2), + new AnimationTrackProperty.KeyFrame(entity.Comp.HopIntensity, len/2), new AnimationTrackProperty.KeyFrame(new Vector2(), len/2), } } } }; - _animation.Play(uid, anim, component.KeyName); - } - - private void OnStoppedWalking(EntityUid uid, WaddleAnimationComponent component, StoppedWaddlingEvent args) - { - StopWaddling(uid, component); - } - - private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args) - { - var started = new StartedWaddlingEvent(uid); - - RaiseLocalEvent(uid, ref started); - } - - private void OnStunned(EntityUid uid, WaddleAnimationComponent component, StunnedEvent args) - { - StopWaddling(uid, component); - } - - private void OnKnockedDown(EntityUid uid, WaddleAnimationComponent component, KnockedDownEvent args) - { - StopWaddling(uid, component); - } - - private void OnBuckleChange(EntityUid uid, WaddleAnimationComponent component, BuckleChangeEvent args) - { - StopWaddling(uid, component); - } - - private void StopWaddling(EntityUid uid, WaddleAnimationComponent component) - { - if (!component.IsCurrentlyWaddling) - return; - - _animation.Stop(uid, component.KeyName); - - if (!TryComp(uid, out var sprite)) - { - return; - } - - sprite.Offset = new Vector2(); - sprite.Rotation = Angle.FromDegrees(0); - - component.IsCurrentlyWaddling = false; + _animation.Play(entity.Owner, anim, entity.Comp.KeyName); } } diff --git a/Content.Server/Movement/Systems/WaddleAnimationSystem.cs b/Content.Server/Movement/Systems/WaddleAnimationSystem.cs new file mode 100644 index 0000000000..e6083210e1 --- /dev/null +++ b/Content.Server/Movement/Systems/WaddleAnimationSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Movement.Systems; + +namespace Content.Server.Movement.Systems; + +public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem; diff --git a/Content.Shared/Clothing/Components/WaddleWhenWornComponent.cs b/Content.Shared/Clothing/Components/WaddleWhenWornComponent.cs index 5cd7a72457..fb7490ef4f 100644 --- a/Content.Shared/Clothing/Components/WaddleWhenWornComponent.cs +++ b/Content.Shared/Clothing/Components/WaddleWhenWornComponent.cs @@ -1,35 +1,36 @@ using System.Numerics; +using Robust.Shared.GameStates; namespace Content.Shared.Clothing.Components; /// /// Defines something as causing waddling when worn. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class WaddleWhenWornComponent : Component { /// /// How high should they hop during the waddle? Higher hop = more energy. /// - [DataField] + [DataField, AutoNetworkedField] public Vector2 HopIntensity = new(0, 0.25f); /// /// How far should they rock backward and forward during the waddle? /// Each step will alternate between this being a positive and negative rotation. More rock = more scary. /// - [DataField] + [DataField, AutoNetworkedField] public float TumbleIntensity = 20.0f; /// /// How long should a complete step take? Less time = more chaos. /// - [DataField] + [DataField, AutoNetworkedField] public float AnimationLength = 0.66f; /// /// How much shorter should the animation be when running? /// - [DataField] + [DataField, AutoNetworkedField] public float RunAnimationLengthMultiplier = 0.568f; } diff --git a/Content.Client/Clothing/Systems/WaddleClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/WaddleClothingSystem.cs similarity index 59% rename from Content.Client/Clothing/Systems/WaddleClothingSystem.cs rename to Content.Shared/Clothing/EntitySystems/WaddleClothingSystem.cs index b8ac3c207b..b445eb258e 100644 --- a/Content.Client/Clothing/Systems/WaddleClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/WaddleClothingSystem.cs @@ -1,8 +1,9 @@ -using Content.Shared.Clothing.Components; +using Content.Shared.Clothing; +using Content.Shared.Clothing.Components; using Content.Shared.Movement.Components; using Content.Shared.Inventory.Events; -namespace Content.Client.Clothing.Systems; +namespace Content.Shared.Clothing.EntitySystems; public sealed class WaddleClothingSystem : EntitySystem { @@ -10,13 +11,13 @@ public sealed class WaddleClothingSystem : EntitySystem { base.Initialize(); - SubscribeLocalEvent(OnGotEquipped); - SubscribeLocalEvent(OnGotUnequipped); + SubscribeLocalEvent(OnGotEquipped); + SubscribeLocalEvent(OnGotUnequipped); } - private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, GotEquippedEvent args) + private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, ClothingGotEquippedEvent args) { - var waddleAnimComp = EnsureComp(args.Equipee); + var waddleAnimComp = EnsureComp(args.Wearer); waddleAnimComp.AnimationLength = comp.AnimationLength; waddleAnimComp.HopIntensity = comp.HopIntensity; @@ -24,8 +25,8 @@ public sealed class WaddleClothingSystem : EntitySystem waddleAnimComp.TumbleIntensity = comp.TumbleIntensity; } - private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, GotUnequippedEvent args) + private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, ClothingGotUnequippedEvent args) { - RemComp(args.Equipee); + RemComp(args.Wearer); } } diff --git a/Content.Shared/Movement/Components/WaddleAnimationComponent.cs b/Content.Shared/Movement/Components/WaddleAnimationComponent.cs index c43ef3042e..3cd9a3749e 100644 --- a/Content.Shared/Movement/Components/WaddleAnimationComponent.cs +++ b/Content.Shared/Movement/Components/WaddleAnimationComponent.cs @@ -1,31 +1,32 @@ using System.Numerics; +using Robust.Shared.Serialization; namespace Content.Shared.Movement.Components; /// /// Declares that an entity has started to waddle like a duck/clown. /// -/// The newly be-waddled. -[ByRefEvent] -public record struct StartedWaddlingEvent(EntityUid Entity) +/// The newly be-waddled. +[Serializable, NetSerializable] +public sealed class StartedWaddlingEvent(NetEntity entity) : EntityEventArgs { - public EntityUid Entity = Entity; + public NetEntity Entity = entity; } /// /// Declares that an entity has stopped waddling like a duck/clown. /// -/// The former waddle-er. -[ByRefEvent] -public record struct StoppedWaddlingEvent(EntityUid Entity) +/// The former waddle-er. +[Serializable, NetSerializable] +public sealed class StoppedWaddlingEvent(NetEntity entity) : EntityEventArgs { - public EntityUid Entity = Entity; + public NetEntity Entity = entity; } /// /// Defines something as having a waddle animation when it moves. /// -[RegisterComponent] +[RegisterComponent, AutoGenerateComponentState] public sealed partial class WaddleAnimationComponent : Component { /// @@ -38,26 +39,26 @@ public sealed partial class WaddleAnimationComponent : Component /// /// How high should they hop during the waddle? Higher hop = more energy. /// - [DataField] + [DataField, AutoNetworkedField] public Vector2 HopIntensity = new(0, 0.25f); /// /// How far should they rock backward and forward during the waddle? /// Each step will alternate between this being a positive and negative rotation. More rock = more scary. /// - [DataField] + [DataField, AutoNetworkedField] public float TumbleIntensity = 20.0f; /// /// How long should a complete step take? Less time = more chaos. /// - [DataField] + [DataField, AutoNetworkedField] public float AnimationLength = 0.66f; /// /// How much shorter should the animation be when running? /// - [DataField] + [DataField, AutoNetworkedField] public float RunAnimationLengthMultiplier = 0.568f; /// @@ -68,5 +69,6 @@ public sealed partial class WaddleAnimationComponent : Component /// /// Stores if we're currently waddling so we can start/stop as appropriate and can tell other systems our state. /// + [AutoNetworkedField] public bool IsCurrentlyWaddling; } diff --git a/Content.Shared/Movement/Systems/SharedWaddleAnimationSystem.cs b/Content.Shared/Movement/Systems/SharedWaddleAnimationSystem.cs new file mode 100644 index 0000000000..2fcb4fc60b --- /dev/null +++ b/Content.Shared/Movement/Systems/SharedWaddleAnimationSystem.cs @@ -0,0 +1,106 @@ +using Content.Shared.Buckle.Components; +using Content.Shared.Gravity; +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; +using Content.Shared.Movement.Systems; +using Content.Shared.Standing; +using Content.Shared.Stunnable; +using Robust.Shared.Timing; + +namespace Content.Shared.Movement.Systems; + +public abstract class SharedWaddleAnimationSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + // Startup + SubscribeLocalEvent(OnComponentStartup); + + // Start moving possibilities + SubscribeLocalEvent(OnMovementInput); + SubscribeLocalEvent(OnStood); + + // Stop moving possibilities + SubscribeLocalEvent((Entity ent, ref StunnedEvent _) => StopWaddling(ent)); + SubscribeLocalEvent((Entity ent, ref DownedEvent _) => StopWaddling(ent)); + SubscribeLocalEvent((Entity ent, ref BuckleChangeEvent _) => StopWaddling(ent)); + SubscribeLocalEvent(OnGravityChanged); + } + + private void OnGravityChanged(Entity ent, ref GravityChangedEvent args) + { + if (!args.HasGravity && ent.Comp.IsCurrentlyWaddling) + StopWaddling(ent); + } + + private void OnComponentStartup(Entity entity, ref ComponentStartup args) + { + if (!TryComp(entity.Owner, out var moverComponent)) + return; + + // If the waddler is currently moving, make them start waddling + if ((moverComponent.HeldMoveButtons & MoveButtons.AnyDirection) == MoveButtons.AnyDirection) + { + RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner))); + } + } + + private void OnMovementInput(Entity entity, ref MoveInputEvent args) + { + // Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume + // they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR. + if (!_timing.IsFirstTimePredicted) + { + return; + } + + if (!args.HasDirectionalMovement && entity.Comp.IsCurrentlyWaddling) + { + StopWaddling(entity); + + return; + } + + // Only start waddling if we're not currently AND we're actually moving. + if (entity.Comp.IsCurrentlyWaddling || !args.HasDirectionalMovement) + return; + + entity.Comp.IsCurrentlyWaddling = true; + + RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner))); + } + + private void OnStood(Entity entity, ref StoodEvent args) + { + // Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume + // they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR. + if (!_timing.IsFirstTimePredicted) + { + return; + } + + if (!TryComp(entity.Owner, out var mover)) + { + return; + } + + if ((mover.HeldMoveButtons & MoveButtons.AnyDirection) == MoveButtons.None) + return; + + if (entity.Comp.IsCurrentlyWaddling) + return; + + entity.Comp.IsCurrentlyWaddling = true; + + RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner))); + } + + private void StopWaddling(Entity entity) + { + entity.Comp.IsCurrentlyWaddling = false; + + RaiseNetworkEvent(new StoppedWaddlingEvent(GetNetEntity(entity.Owner))); + } +} From 0b4fc7e298bc37cc7db053bf4d2c2adb7e873d0a Mon Sep 17 00:00:00 2001 From: Hannah Giovanna Dawson Date: Tue, 4 Jun 2024 14:26:19 +0100 Subject: [PATCH 054/243] Make projectile & hitscan reflection only try once with the best reflector (#28539) --- .../Weapons/Reflect/ReflectSystem.cs | 258 ++++++++++-------- .../Weapons/Reflect/ReflectUserComponent.cs | 5 +- 2 files changed, 147 insertions(+), 116 deletions(-) diff --git a/Content.Shared/Weapons/Reflect/ReflectSystem.cs b/Content.Shared/Weapons/Reflect/ReflectSystem.cs index 03ad97edff..9b89be6202 100644 --- a/Content.Shared/Weapons/Reflect/ReflectSystem.cs +++ b/Content.Shared/Weapons/Reflect/ReflectSystem.cs @@ -49,73 +49,123 @@ public sealed class ReflectSystem : EntitySystem { base.Initialize(); - SubscribeLocalEvent(OnReflectCollide); - SubscribeLocalEvent(OnReflectHitscan); + SubscribeLocalEvent(OnObjectReflectProjectileAttempt); + SubscribeLocalEvent(OnObjectReflectHitscanAttempt); SubscribeLocalEvent(OnReflectEquipped); SubscribeLocalEvent(OnReflectUnequipped); SubscribeLocalEvent(OnReflectHandEquipped); SubscribeLocalEvent(OnReflectHandUnequipped); SubscribeLocalEvent(OnToggleReflect); - SubscribeLocalEvent(OnReflectUserCollide); - SubscribeLocalEvent(OnReflectUserHitscan); + SubscribeLocalEvent(OnUserProjectileReflectAttempt); + SubscribeLocalEvent(OnUserHitscanReflectAttempt); } - private void OnReflectUserHitscan(EntityUid uid, ReflectUserComponent component, ref HitScanReflectAttemptEvent args) + private void OnUserHitscanReflectAttempt(Entity user, ref HitScanReflectAttemptEvent args) { if (args.Reflected) return; - foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.WITHOUT_POCKET)) - { - if (!TryReflectHitscan(uid, ent, args.Shooter, args.SourceItem, args.Direction, out var dir)) - continue; + if (!UserCanReflect(user, out var bestReflectorUid)) + return; - args.Direction = dir.Value; - args.Reflected = true; - break; - } + if (!TryReflectHitscan(user.Owner, bestReflectorUid.Value, args.Shooter, args.SourceItem, args.Direction, out var dir)) + return; + + args.Direction = dir.Value; + args.Reflected = true; } - private void OnReflectUserCollide(EntityUid uid, ReflectUserComponent component, ref ProjectileReflectAttemptEvent args) - { - foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.WITHOUT_POCKET)) - { - if (!TryReflectProjectile(uid, ent, args.ProjUid)) - continue; - - args.Cancelled = true; - break; - } - } - - private void OnReflectCollide(EntityUid uid, ReflectComponent component, ref ProjectileReflectAttemptEvent args) + private void OnUserProjectileReflectAttempt(Entity user, ref ProjectileReflectAttemptEvent args) { if (args.Cancelled) return; - if (TryReflectProjectile(uid, uid, args.ProjUid, reflect: component)) - args.Cancelled = true; + if (!TryComp(args.ProjUid, out var reflectiveComponent)) + return; + + if (!UserCanReflect(user, out var bestReflectorUid, (args.ProjUid, reflectiveComponent))) + return; + + if (!TryReflectProjectile(user, bestReflectorUid.Value, (args.ProjUid, args.Component))) + return; + + args.Cancelled = true; } - private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid projectile, ProjectileComponent? projectileComp = null, ReflectComponent? reflect = null) + private void OnObjectReflectHitscanAttempt(Entity obj, ref HitScanReflectAttemptEvent args) + { + if (args.Reflected || (obj.Comp.Reflects & args.Reflective) == 0x0) + return; + + if (!TryReflectHitscan(obj, obj, args.Shooter, args.SourceItem, args.Direction, out var dir)) + return; + + args.Direction = dir.Value; + args.Reflected = true; + } + + private void OnObjectReflectProjectileAttempt(Entity obj, ref ProjectileReflectAttemptEvent args) + { + if (args.Cancelled) + return; + + if (!TryReflectProjectile(obj, obj, (args.ProjUid, args.Component))) + return; + + args.Cancelled = true; + } + + /// + /// Can a user reflect something that's hit them? Returns true if so, and the best reflector available in the user's equipment. + /// + private bool UserCanReflect(Entity user, [NotNullWhen(true)] out Entity? bestReflector, Entity? projectile = null) + { + bestReflector = null; + + foreach (var entityUid in _inventorySystem.GetHandOrInventoryEntities(user.Owner, SlotFlags.WITHOUT_POCKET)) + { + if (!TryComp(entityUid, out var comp)) + continue; + + if (!comp.Enabled) + continue; + + if (bestReflector != null && bestReflector.Value.Comp.ReflectProb >= comp.ReflectProb) + continue; + + if (projectile != null && (comp.Reflects & projectile.Value.Comp.Reflective) == 0x0) + continue; + + bestReflector = (entityUid, comp); + } + + return bestReflector != null; + } + + private bool TryReflectProjectile(EntityUid user, Entity reflector, Entity projectile) { - // Do we have the components needed to try a reflect at all? if ( - !Resolve(reflector, ref reflect, false) || - !reflect.Enabled || + // Is it on? + !reflector.Comp.Enabled || + // Is the projectile deflectable? !TryComp(projectile, out var reflective) || - (reflect.Reflects & reflective.Reflective) == 0x0 || + // Does the deflector deflect the type of projecitle? + (reflector.Comp.Reflects & reflective.Reflective) == 0x0 || + // Is the projectile correctly set up with physics? !TryComp(projectile, out var physics) || - TryComp(reflector, out var staminaComponent) && staminaComponent.Critical || + // If the user of the reflector is a mob with stamina, is it capable of deflecting? + TryComp(user, out var staminaComponent) && staminaComponent.Critical || _standing.IsDown(reflector) ) return false; - if (!_random.Prob(CalcReflectChance(reflector, reflect))) + // If this dice roll fails, the shot isn't deflected + if (!_random.Prob(GetReflectChance(reflector))) return false; - var rotation = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2).Opposite(); + // Below handles what happens after being deflected. + var rotation = _random.NextAngle(-reflector.Comp.Spread / 2, reflector.Comp.Spread / 2).Opposite(); var existingVelocity = _physics.GetMapLinearVelocity(projectile, component: physics); var relativeVelocity = existingVelocity - _physics.GetMapLinearVelocity(user); var newVelocity = rotation.RotateVec(relativeVelocity); @@ -132,98 +182,52 @@ public sealed class ReflectSystem : EntitySystem if (_netManager.IsServer) { _popup.PopupEntity(Loc.GetString("reflect-shot"), user); - _audio.PlayPvs(reflect.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random)); + _audio.PlayPvs(reflector.Comp.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random)); } - if (Resolve(projectile, ref projectileComp, false)) - { - _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)} from {ToPrettyString(projectileComp.Weapon)} shot by {projectileComp.Shooter}"); + _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)} from {ToPrettyString(projectile.Comp.Weapon)} shot by {projectile.Comp.Shooter}"); - projectileComp.Shooter = user; - projectileComp.Weapon = user; - Dirty(projectile, projectileComp); - } - else - { - _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)}"); - } + projectile.Comp.Shooter = user; + projectile.Comp.Weapon = user; + Dirty(projectile); return true; } - private float CalcReflectChance(EntityUid reflector, ReflectComponent reflect) - { - /* - * The rules of deflection are as follows: - * If you innately reflect things via magic, biology etc., you always have a full chance. - * If you are standing up and standing still, you're prepared to deflect and have full chance. - * If you have velocity, your deflection chance depends on your velocity, clamped. - * If you are floating, your chance is the minimum value possible. - * You cannot deflect if you are knocked down or stunned. - */ - - if (reflect.Innate) - return reflect.ReflectProb; - - if (_gravity.IsWeightless(reflector)) - return reflect.MinReflectProb; - - if (!TryComp(reflector, out var reflectorPhysics)) - return reflect.ReflectProb; - - return MathHelper.Lerp( - reflect.MinReflectProb, - reflect.ReflectProb, - // Inverse progression between velocities fed in as progression between probabilities. We go high -> low so the output here needs to be _inverted_. - 1 - Math.Clamp((reflectorPhysics.LinearVelocity.Length() - reflect.VelocityBeforeNotMaxProb) / (reflect.VelocityBeforeMinProb - reflect.VelocityBeforeNotMaxProb), 0, 1) - ); - } - - private void OnReflectHitscan(EntityUid uid, ReflectComponent component, ref HitScanReflectAttemptEvent args) - { - if (args.Reflected || - (component.Reflects & args.Reflective) == 0x0) - { - return; - } - - if (TryReflectHitscan(uid, uid, args.Shooter, args.SourceItem, args.Direction, out var dir)) - { - args.Direction = dir.Value; - args.Reflected = true; - } - } - private bool TryReflectHitscan( EntityUid user, - EntityUid reflector, + Entity reflector, EntityUid? shooter, EntityUid shotSource, Vector2 direction, [NotNullWhen(true)] out Vector2? newDirection) { - if (!TryComp(reflector, out var reflect) || - !reflect.Enabled || - TryComp(reflector, out var staminaComponent) && staminaComponent.Critical || - _standing.IsDown(reflector)) + if ( + // Is the reflector enabled? + !reflector.Comp.Enabled || + // If the user is a mob with stamina, is it capable of deflecting? + TryComp(user, out var staminaComponent) && staminaComponent.Critical || + _standing.IsDown(user)) { newDirection = null; return false; } - if (!_random.Prob(CalcReflectChance(reflector, reflect))) + // If this dice roll fails, the shot is not deflected. + if (!_random.Prob(GetReflectChance(reflector))) { newDirection = null; return false; } + // Below handles what happens after being deflected. if (_netManager.IsServer) { _popup.PopupEntity(Loc.GetString("reflect-shot"), user); - _audio.PlayPvs(reflect.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random)); + _audio.PlayPvs(reflector.Comp.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random)); } - var spread = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2); + var spread = _random.NextAngle(-reflector.Comp.Spread / 2, reflector.Comp.Spread / 2); newDirection = -spread.RotateVec(direction); if (shooter != null) @@ -234,51 +238,81 @@ public sealed class ReflectSystem : EntitySystem return true; } - private void OnReflectEquipped(EntityUid uid, ReflectComponent component, GotEquippedEvent args) + private float GetReflectChance(Entity reflector) + { + /* + * The rules of deflection are as follows: + * If you innately reflect things via magic, biology etc., you always have a full chance. + * If you are standing up and standing still, you're prepared to deflect and have full chance. + * If you have velocity, your deflection chance depends on your velocity, clamped. + * If you are floating, your chance is the minimum value possible. + */ + + if (reflector.Comp.Innate) + return reflector.Comp.ReflectProb; + + if (_gravity.IsWeightless(reflector)) + return reflector.Comp.MinReflectProb; + + if (!TryComp(reflector, out var reflectorPhysics)) + return reflector.Comp.ReflectProb; + + return MathHelper.Lerp( + reflector.Comp.MinReflectProb, + reflector.Comp.ReflectProb, + // Inverse progression between velocities fed in as progression between probabilities. We go high -> low so the output here needs to be _inverted_. + 1 - Math.Clamp((reflectorPhysics.LinearVelocity.Length() - reflector.Comp.VelocityBeforeNotMaxProb) / (reflector.Comp.VelocityBeforeMinProb - reflector.Comp.VelocityBeforeNotMaxProb), 0, 1) + ); + } + + private void OnReflectEquipped(Entity reflector, ref GotEquippedEvent args) { if (_gameTiming.ApplyingState) return; EnsureComp(args.Equipee); - if (component.Enabled) + if (reflector.Comp.Enabled) EnableAlert(args.Equipee); } - private void OnReflectUnequipped(EntityUid uid, ReflectComponent comp, GotUnequippedEvent args) + private void OnReflectUnequipped(Entity reflector, ref GotUnequippedEvent args) { RefreshReflectUser(args.Equipee); } - private void OnReflectHandEquipped(EntityUid uid, ReflectComponent component, GotEquippedHandEvent args) + private void OnReflectHandEquipped(Entity reflector, ref GotEquippedHandEvent args) { if (_gameTiming.ApplyingState) return; EnsureComp(args.User); - if (component.Enabled) + if (reflector.Comp.Enabled) EnableAlert(args.User); } - private void OnReflectHandUnequipped(EntityUid uid, ReflectComponent component, GotUnequippedHandEvent args) + private void OnReflectHandUnequipped(Entity reflector, ref GotUnequippedHandEvent args) { RefreshReflectUser(args.User); } - private void OnToggleReflect(EntityUid uid, ReflectComponent comp, ref ItemToggledEvent args) + private void OnToggleReflect(Entity reflector, ref ItemToggledEvent args) { - comp.Enabled = args.Activated; - Dirty(uid, comp); + reflector.Comp.Enabled = args.Activated; + Dirty(reflector); - if (comp.Enabled) - EnableAlert(uid); + if (args.User == null) + return; + + if (reflector.Comp.Enabled) + EnableAlert(args.User.Value); else - DisableAlert(uid); + DisableAlert(args.User.Value); } /// - /// Refreshes whether someone has reflection potential so we can raise directed events on them. + /// Refreshes whether someone has reflection potential, so we can raise directed events on them. /// private void RefreshReflectUser(EntityUid user) { diff --git a/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs b/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs index 44fe60813e..44ef481a37 100644 --- a/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs +++ b/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs @@ -7,7 +7,4 @@ namespace Content.Shared.Weapons.Reflect; /// Reflection events will then be relayed. /// [RegisterComponent, NetworkedComponent] -public sealed partial class ReflectUserComponent : Component -{ - -} +public sealed partial class ReflectUserComponent : Component; From b3cfd9b4ff7b1f06bd917c158058229773b92955 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 4 Jun 2024 13:27:27 +0000 Subject: [PATCH 055/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 0ae775e193..6a20b3a411 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Dutch-VanDerLinde - changes: - - message: Romerol now properly works on the dead. - type: Tweak - id: 6179 - time: '2024-03-18T06:25:36.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26222 - author: ElectroJr changes: - message: Added an option to try and ignore some errors if a replay fails to load, @@ -3850,3 +3843,11 @@ id: 6678 time: '2024-06-03T18:48:44.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/27788 +- author: FairlySadPanda + changes: + - message: Stacking multiple deflecting items at once has been removed; now only + your best item is used. + type: Tweak + id: 6679 + time: '2024-06-04T13:26:20.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28539 From d5019cee96a922f7e0547bc507fdfa1ed9471a39 Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Tue, 4 Jun 2024 06:54:22 -0800 Subject: [PATCH 056/243] Add CVar to disable pow3r parallel processing (#28488) * Add CVar to disable pow3r parallel processing * Cache CVar value * Fix pointyhat * Move all CVar-ing to Content.Server --- .../Power/EntitySystems/PowerNetSystem.cs | 13 ++++++++++++- Content.Server/Power/Pow3r/BatteryRampPegSolver.cs | 9 +++++++-- Content.Shared/CCVar/CCVars.cs | 6 ++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 7bd057951c..6c35ba2008 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -3,9 +3,11 @@ using Content.Server.NodeContainer.EntitySystems; using Content.Server.Power.Components; using Content.Server.Power.NodeGroups; using Content.Server.Power.Pow3r; +using Content.Shared.CCVar; using Content.Shared.Power; using JetBrains.Annotations; using Robust.Server.GameObjects; +using Robust.Shared.Configuration; using Robust.Shared.Threading; namespace Content.Server.Power.EntitySystems @@ -18,6 +20,7 @@ namespace Content.Server.Power.EntitySystems { [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly PowerNetConnectorSystem _powerNetConnector = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IParallelManager _parMan = default!; [Dependency] private readonly PowerReceiverSystem _powerReceiver = default!; @@ -25,13 +28,14 @@ namespace Content.Server.Power.EntitySystems private readonly HashSet _powerNetReconnectQueue = new(); private readonly HashSet _apcNetReconnectQueue = new(); - private readonly BatteryRampPegSolver _solver = new(); + private BatteryRampPegSolver _solver = new(); public override void Initialize() { base.Initialize(); UpdatesAfter.Add(typeof(NodeGroupSystem)); + _solver = new(_cfg.GetCVar(CCVars.DebugPow3rDisableParallel)); SubscribeLocalEvent(ApcPowerReceiverInit); SubscribeLocalEvent(ApcPowerReceiverShutdown); @@ -53,6 +57,13 @@ namespace Content.Server.Power.EntitySystems SubscribeLocalEvent(PowerSupplierShutdown); SubscribeLocalEvent(PowerSupplierPaused); SubscribeLocalEvent(PowerSupplierUnpaused); + + Subs.CVar(_cfg, CCVars.DebugPow3rDisableParallel, DebugPow3rDisableParallelChanged); + } + + private void DebugPow3rDisableParallelChanged(bool val) + { + _solver = new(val); } private void ApcPowerReceiverInit(EntityUid uid, ApcPowerReceiverComponent component, ComponentInit args) diff --git a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs index 12118968b7..599c55b6ba 100644 --- a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs +++ b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs @@ -9,9 +9,11 @@ namespace Content.Server.Power.Pow3r public sealed class BatteryRampPegSolver : IPowerSolver { private UpdateNetworkJob _networkJob; + private bool _disableParallel; - public BatteryRampPegSolver() + public BatteryRampPegSolver(bool disableParallel = false) { + _disableParallel = disableParallel; _networkJob = new() { Solver = this, @@ -56,7 +58,10 @@ namespace Content.Server.Power.Pow3r // suppliers + discharger) Then decide based on total layer size whether its worth parallelizing that // layer? _networkJob.Networks = group; - parallel.ProcessNow(_networkJob, group.Count); + if (_disableParallel) + parallel.ProcessSerialNow(_networkJob, group.Count); + else + parallel.ProcessNow(_networkJob, group.Count); } ClearBatteries(state); diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index f41e0a1e6f..b3da71fde1 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -2062,5 +2062,11 @@ namespace Content.Shared.CCVar /// public static readonly CVarDef DebugOptionVisualizerTest = CVarDef.Create("debug.option_visualizer_test", false, CVar.CLIENTONLY); + + /// + /// Set to true to disable parallel processing in the pow3r solver. + /// + public static readonly CVarDef DebugPow3rDisableParallel = + CVarDef.Create("debug.pow3r_disable_parallel", true, CVar.SERVERONLY); } } From 71a6e60818fbaab41b90a854e38dd2d8e6850304 Mon Sep 17 00:00:00 2001 From: Moony Date: Tue, 4 Jun 2024 10:59:33 -0500 Subject: [PATCH 057/243] Revert "Buff the AME until somebody fixes engineering" (#28419) --- Content.Server/Ame/AmeNodeGroup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Ame/AmeNodeGroup.cs b/Content.Server/Ame/AmeNodeGroup.cs index 608356f760..40da6222d2 100644 --- a/Content.Server/Ame/AmeNodeGroup.cs +++ b/Content.Server/Ame/AmeNodeGroup.cs @@ -182,7 +182,7 @@ public sealed class AmeNodeGroup : BaseNodeGroup // Fuel is squared so more fuel vastly increases power and efficiency // We divide by the number of cores so a larger AME is less efficient at the same fuel settings // this results in all AMEs having the same efficiency at the same fuel-per-core setting - return 2000000f * fuel * fuel / cores; + return 20000f * fuel * fuel / cores; } public int GetTotalStability() From b394eb9e84ac2faf6018849a842c6fa7d999c1ed Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 4 Jun 2024 16:00:39 +0000 Subject: [PATCH 058/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 6a20b3a411..600334df6b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: ElectroJr - changes: - - message: Added an option to try and ignore some errors if a replay fails to load, - type: Add - id: 6180 - time: '2024-03-18T07:31:37.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26212 - author: Tayrtahn changes: - message: Cyborg recharging stations are able to charge cyborgs again. @@ -3851,3 +3844,11 @@ id: 6679 time: '2024-06-04T13:26:20.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28539 +- author: moonheart08 + changes: + - message: AME power output is no longer super-buffed. Larger stations will now + require multiple engines to stay powered. + type: Tweak + id: 6680 + time: '2024-06-04T15:59:33.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28419 From 627187988e76b97e81663747faafb21da182ebe0 Mon Sep 17 00:00:00 2001 From: icekot8 <93311212+icekot8@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:34:07 +0300 Subject: [PATCH 059/243] hand teleport portals now may start in the same grid. (#28556) --- Content.Server/Teleportation/HandTeleporterSystem.cs | 2 +- .../Teleportation/Components/HandTeleporterComponent.cs | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Content.Server/Teleportation/HandTeleporterSystem.cs b/Content.Server/Teleportation/HandTeleporterSystem.cs index d4c6753c4b..1cd2e1d8c2 100644 --- a/Content.Server/Teleportation/HandTeleporterSystem.cs +++ b/Content.Server/Teleportation/HandTeleporterSystem.cs @@ -98,7 +98,7 @@ public sealed class HandTeleporterSystem : EntitySystem if (xform.ParentUid != xform.GridUid) // Still, don't portal. return; - if (xform.ParentUid != Transform(component.FirstPortal!.Value).ParentUid) + if (!component.AllowPortalsOnDifferentGrids && xform.ParentUid != Transform(component.FirstPortal!.Value).ParentUid) { // Whoops. Fizzle time. Crime time too because yippee I'm not refactoring this logic right now (I started to, I'm not going to.) FizzlePortals(uid, component, user, true); diff --git a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs index 6abd4a7d21..6ea29d3fd6 100644 --- a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs +++ b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs @@ -1,4 +1,4 @@ -using Content.Shared.DoAfter; +using Content.Shared.DoAfter; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -20,6 +20,12 @@ public sealed partial class HandTeleporterComponent : Component [ViewVariables, DataField("secondPortal")] public EntityUid? SecondPortal = null; + /// + /// Portals can't be placed on different grids? + /// + [DataField] + public bool AllowPortalsOnDifferentGrids; + [DataField("firstPortalPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] public string FirstPortalPrototype = "PortalRed"; From 0e93d13cb78b86136d11daeec06464ef26ccff65 Mon Sep 17 00:00:00 2001 From: blueDev2 <89804215+blueDev2@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:12:01 -0400 Subject: [PATCH 060/243] Microwave recipes now uses stacktype id instead of entity prototype id for stacked entities (#28225) --- .../Kitchen/EntitySystems/MicrowaveSystem.cs | 66 ++++++++++++++++--- .../Recipes/Cooking/medical_recipes.yml | 4 +- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs index 8938aa8b1d..c69ed49d50 100644 --- a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs @@ -35,6 +35,7 @@ using Robust.Shared.Player; using System.Linq; using Robust.Shared.Prototypes; using Robust.Shared.Timing; +using Content.Shared.Stacks; namespace Content.Server.Kitchen.EntitySystems { @@ -58,6 +59,8 @@ namespace Content.Server.Kitchen.EntitySystems [Dependency] private readonly UserInterfaceSystem _userInterface = default!; [Dependency] private readonly HandsSystem _handsSystem = default!; [Dependency] private readonly SharedItemSystem _item = default!; + [Dependency] private readonly SharedStackSystem _stack = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; [ValidatePrototypeId] private const string MalfunctionSpark = "Spark"; @@ -199,16 +202,41 @@ namespace Content.Server.Kitchen.EntitySystems { foreach (var item in component.Storage.ContainedEntities) { - var metaData = MetaData(item); - if (metaData.EntityPrototype == null) + string? itemID = null; + + // If an entity has a stack component, use the stacktype instead of prototype id + if (TryComp(item, out var stackComp)) + { + itemID = _prototype.Index(stackComp.StackTypeId).Spawn; + } + else + { + var metaData = MetaData(item); + if (metaData.EntityPrototype == null) + { + continue; + } + itemID = metaData.EntityPrototype.ID; + } + + if (itemID != recipeSolid.Key) { continue; } - if (metaData.EntityPrototype.ID == recipeSolid.Key) + if (stackComp is not null) + { + if (stackComp.Count == 1) + { + _container.Remove(item, component.Storage); + } + _stack.Use(item, 1, stackComp); + break; + } + else { _container.Remove(item, component.Storage); - EntityManager.DeleteEntity(item); + Del(item); break; } } @@ -448,17 +476,35 @@ namespace Content.Server.Kitchen.EntitySystems AddComp(item); - var metaData = MetaData(item); //this simply begs for cooking refactor - if (metaData.EntityPrototype == null) - continue; + string? solidID = null; + int amountToAdd = 1; - if (solidsDict.ContainsKey(metaData.EntityPrototype.ID)) + // If a microwave recipe uses a stacked item, use the default stack prototype id instead of prototype id + if (TryComp(item, out var stackComp)) { - solidsDict[metaData.EntityPrototype.ID]++; + solidID = _prototype.Index(stackComp.StackTypeId).Spawn; + amountToAdd = stackComp.Count; } else { - solidsDict.Add(metaData.EntityPrototype.ID, 1); + var metaData = MetaData(item); //this simply begs for cooking refactor + if (metaData.EntityPrototype is not null) + solidID = metaData.EntityPrototype.ID; + } + + if (solidID is null) + { + continue; + } + + + if (solidsDict.ContainsKey(solidID)) + { + solidsDict[solidID] += amountToAdd; + } + else + { + solidsDict.Add(solidID, amountToAdd); } if (!TryComp(item, out var solMan)) diff --git a/Resources/Prototypes/Recipes/Cooking/medical_recipes.yml b/Resources/Prototypes/Recipes/Cooking/medical_recipes.yml index 9d1947f03e..3bc9e22b0e 100644 --- a/Resources/Prototypes/Recipes/Cooking/medical_recipes.yml +++ b/Resources/Prototypes/Recipes/Cooking/medical_recipes.yml @@ -13,7 +13,7 @@ time: 10 solids: FoodPoppy: 1 - Brutepack: 1 + Brutepack: 10 MaterialCloth1: 1 reagents: TranexamicAcid: 20 @@ -26,7 +26,7 @@ time: 10 solids: FoodAloe: 1 - Ointment: 1 + Ointment: 10 MaterialCloth1: 1 reagents: Sigynate: 20 From 1de261d1b1813ef00e5ef665594bc8d5e66dc77e Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 4 Jun 2024 17:13:07 +0000 Subject: [PATCH 061/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 600334df6b..b14fae9c41 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Tayrtahn - changes: - - message: Cyborg recharging stations are able to charge cyborgs again. - type: Fix - id: 6181 - time: '2024-03-18T13:37:49.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26230 - author: DoutorWhite changes: - message: Introduce new health status icons. @@ -3852,3 +3845,10 @@ id: 6680 time: '2024-06-04T15:59:33.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28419 +- author: blueDev2 + changes: + - message: Fixed microwave recipes that use stacked materials + type: Fix + id: 6681 + time: '2024-06-04T17:12:01.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28225 From a4881f56b021d6512955b2add88135ef446953d6 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Tue, 4 Jun 2024 14:48:24 -0400 Subject: [PATCH 062/243] Add "fill level" sprites to mops and damp rag (#28590) --- .../Objects/Specific/Janitorial/janitor.yml | 34 ++++++++- .../Specific/Janitorial/advmop.rsi/fill-1.png | Bin 0 -> 200 bytes .../Specific/Janitorial/advmop.rsi/fill-2.png | Bin 0 -> 216 bytes .../advmop.rsi/inhand-left-fill-1.png | Bin 0 -> 155 bytes .../advmop.rsi/inhand-left-fill-2.png | Bin 0 -> 187 bytes .../advmop.rsi/inhand-right-fill-1.png | Bin 0 -> 157 bytes .../advmop.rsi/inhand-right-fill-2.png | Bin 0 -> 195 bytes .../Specific/Janitorial/advmop.rsi/meta.json | 62 +++++++++++++---- .../advmop.rsi/wielded-inhand-left-fill-1.png | Bin 0 -> 252 bytes .../advmop.rsi/wielded-inhand-left-fill-2.png | Bin 0 -> 282 bytes .../wielded-inhand-right-fill-1.png | Bin 0 -> 254 bytes .../wielded-inhand-right-fill-2.png | Bin 0 -> 279 bytes .../Specific/Janitorial/mop.rsi/fill-1.png | Bin 0 -> 179 bytes .../Specific/Janitorial/mop.rsi/fill-2.png | Bin 0 -> 191 bytes .../Specific/Janitorial/mop.rsi/fill-3.png | Bin 0 -> 197 bytes .../Janitorial/mop.rsi/inhand-left-fill-1.png | Bin 0 -> 145 bytes .../Janitorial/mop.rsi/inhand-left-fill-2.png | Bin 0 -> 156 bytes .../mop.rsi/inhand-right-fill-1.png | Bin 0 -> 147 bytes .../mop.rsi/inhand-right-fill-2.png | Bin 0 -> 160 bytes .../Specific/Janitorial/mop.rsi/meta.json | 65 ++++++++++++++---- .../mop.rsi/wielded-inhand-left-fill-1.png | Bin 0 -> 153 bytes .../mop.rsi/wielded-inhand-left-fill-2.png | Bin 0 -> 186 bytes .../mop.rsi/wielded-inhand-right-fill-1.png | Bin 0 -> 151 bytes .../mop.rsi/wielded-inhand-right-fill-2.png | Bin 0 -> 189 bytes .../Specific/Janitorial/rag.rsi/fill-1.png | Bin 0 -> 133 bytes .../Specific/Janitorial/rag.rsi/fill-2.png | Bin 0 -> 163 bytes .../Specific/Janitorial/rag.rsi/fill-3.png | Bin 0 -> 173 bytes .../Specific/Janitorial/rag.rsi/meta.json | 35 ++++++---- 28 files changed, 156 insertions(+), 40 deletions(-) create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-left-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-left-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-right-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-right-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-right-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-right-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-3.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-left-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-left-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-left-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-left-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-right-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-right-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-3.png diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml index 868d012a87..cb5f875204 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml @@ -6,7 +6,17 @@ components: - type: Sprite sprite: Objects/Specific/Janitorial/mop.rsi - state: mop + layers: + - state: mop + - map: ["enum.SolutionContainerLayers.Fill"] + state: fill-3 + visible: false + - type: Appearance + - type: SolutionContainerVisuals + maxFillLevels: 3 + fillBaseName: fill- + inHandsFillBaseName: -fill- + inHandsMaxFillLevels: 2 - type: MeleeWeapon damage: types: @@ -49,7 +59,17 @@ components: - type: Sprite sprite: Objects/Specific/Janitorial/advmop.rsi - state: advmop + layers: + - state: advmop + - map: ["enum.SolutionContainerLayers.Fill"] + state: fill-2 + visible: false + - type: Appearance + - type: SolutionContainerVisuals + maxFillLevels: 2 + fillBaseName: fill- + inHandsFillBaseName: -fill- + inHandsMaxFillLevels: 2 - type: MeleeWeapon damage: types: @@ -244,7 +264,15 @@ components: - type: Sprite sprite: Objects/Specific/Janitorial/rag.rsi - state: rag + layers: + - state: rag + - map: ["enum.SolutionContainerLayers.Fill"] + state: fill-3 + visible: false + - type: Appearance + - type: SolutionContainerVisuals + maxFillLevels: 3 + fillBaseName: fill- - type: Spillable solution: absorbed - type: MeleeWeapon diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/fill-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d7e1ad3ef2f064a8d1ea982f935565ac368e0949 GIT binary patch literal 200 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|ECYN(Tn`*L zkgGGtOR=cm>{y-Q#x~>KW%|qK#p*D){y-Q#x~>KW%|qK#p*D)lHr)@ivQ=Y!~jB@u~ z-k-;X7VSD!dF#f4|6C*W|C=ZjiNRRpQ_N uyh=TYBt!I>XFO%kMY4`g2>sb4Y}v}bd!}~A6ie52AbC$$KbLh*2~7Y_j4>Ag literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-right-fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-right-fill-2.png new file mode 100644 index 0000000000000000000000000000000000000000..b5136f499b53fabcea456e3dd7281420130e70b1 GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|YCT;XLn`LH zy>*bc*?@;N;7kP1g`Gy?%q62>kO*+jb_W{CwcU z((4~%H&!k1y8SoFwnpyGeC0*5SN0v-_2uz9ONdg2I`{l@3#^uJIL-cLN708>?j85o k=Ks5=oMd%#waxMk3^&cKm%V8e@&l>%boFyt=akR{0QfdalmGw# literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/meta.json b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/meta.json index 31192c23bf..b176776adb 100644 --- a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/meta.json @@ -1,30 +1,68 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/b136cf653c4926e475f8d39b34cd1b713331865a, wielded versions by Psychpsyo", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/b136cf653c4926e475f8d39b34cd1b713331865a, wielded versions by Psychpsyo. Fill levels by Tayrtahn on GitHub.", "size": { "x": 32, "y": 32 }, "states": [ { - "name": "advmop" + "name": "advmop" }, { - "name": "inhand-left", - "directions": 4 + "name": "fill-1" }, { - "name": "inhand-right", - "directions": 4 + "name": "fill-2" }, - { - "name": "wielded-inhand-left", - "directions": 4 + { + "name": "inhand-left", + "directions": 4 }, - { - "name": "wielded-inhand-right", - "directions": 4 + { + "name": "inhand-left-fill-1", + "directions": 4 + }, + { + "name": "inhand-left-fill-2", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "inhand-right-fill-1", + "directions": 4 + }, + { + "name": "inhand-right-fill-2", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-left-fill-1", + "directions": 4 + }, + { + "name": "wielded-inhand-left-fill-2", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-right-fill-1", + "directions": 4 + }, + { + "name": "wielded-inhand-right-fill-2", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-1.png new file mode 100644 index 0000000000000000000000000000000000000000..57c1acc26e0a8cf5bc31f91b52fbbbb1cc5793a8 GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`=mz+NxOyoT z)fsL~Gd`2XaHglHCs$|AyjUHEgvktUIkmO5D_5?}0;*(S_zwoN*T_5sa`;Pv{DL7O z5R&0oLubXJYe1f>XFU6Ne5;~}vGU17|GjkP>6cA@V!#Ph z_(0s@bQNm`qt79>W3|E;_zXC>XUsqBU;vZ?g8K~T5(2rk4@hc9J>*(&@9RE;ddCm{ dvlEmVlq7$J9&mMWvd{vl^K|udS?83{1OR07UM&Cs literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-2.png new file mode 100644 index 0000000000000000000000000000000000000000..4f0245bb850ed5f5adabb9c7ab427bd9771da2c6 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`=mz+NxOyoT z)fsL~Gd`2XaHglHCs$|AyjUHEgvktUIkmO5D_5?}0;*(S_zwoN*T_5sa`;Pv{DL7O z5R&0oLubXJYe1<6PZ!6Kh}O5)E^-}E;9$LAr||dx`PkxSnH#!N&79lWId{nkRBgC2 zeYt>wLxTgKL+o9~kfou48Z{hq-m)byFip)&su=_tthjt0Mn`p^$(346Fh$_3-lYzyw6_uhyN1;`=x~klC`w7+d+=@ MboFyt=akR{0L9{JbpQYW literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-right-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-right-fill-1.png new file mode 100644 index 0000000000000000000000000000000000000000..07d042cc75b5da0ffa1eeecddfeeab4db9ab278b GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`=mz+NxYikN z^inM9>FG&hIFn|4X64G2wY9bLVs#i2CNsF@I2;1`74H4tA$#cp&(@~24G*pB zDlIrS9NL!4w5f&HfxE#^!TARBlXC^{ODfobazOC6;Y0D)W=@tC`5Y#k44ofy`glX(f`=mz+NxYikN z^inM9>FG&hIFn|4X64G2wY9bLVs#i2CNsF@jex8m@^bEux$ur0V!*6@KmrppZLIX z-OY9Zb{UTjiwDpD))3f*Bw}#~OaxR2>Ei8hW}ohFJ6_ zCrGd^PDo+m3f*Bw}#~OaxR2>Ei+IqS;hFJ8z z?K9*9+HmAX)&Kg9O>6Ev$jCY565R2lCeFoHc)^|XT(>893v_t(2zB<(F%Em-U&E{& b@~n-OS(Y&%euoAl&>#j+S3j3^P63f*Bw}#~OaxR2>EiI(xb}hFJ8z z?NbzDP~bWI>em1LGm@jE?OWR j_rhPtzE>sWSt|EC4kopd?F)K=#xZ!h`njxgN@xNAXQfPj literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-left-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-left-fill-1.png new file mode 100644 index 0000000000000000000000000000000000000000..3007efa01e25ef63550f83ec0f74af6f5ccbc648 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|yggkULn`LH zy|t0|fB_G)!Oyk-`u9D3^EODqx2++DHG=&FCr}j#$gD4$Qu6(;*1BE$o9TAK`K05{an^LB{Ts5HPkhR literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-1.png new file mode 100644 index 0000000000000000000000000000000000000000..17495e71826c0b9ad172f6879e8026e96b408c66 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|d_7$pLn`LH zy=lnTV8Fxd@I3nO{vTmCx>=Z87w(Eyjy(5<1*i%HwuO6Mx_kU-fa$;WpL_5AXNHJ2 luy0jLxp)75-uu;`-_7y3Ai%uoLy#ay&ePS;Wt~$(699$tGM4}V literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-2.png new file mode 100644 index 0000000000000000000000000000000000000000..58c3bd2c42ff3e804745f8b893da469f69a79668 GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|qC8z3Ln`LH zy>*bc!GMSPK$`Bi@atT{TRbj^aK2;P@TUBd6i^ii*iW8mYyA7Rtya&rqd5gGGF!L4 ys;!lW$T38({il25>uLdiW;fOM_wI#1kF{jfFw>KEc$QWNQsU|A=d#Wzp$PzY&ofm3 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/meta.json b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/meta.json index 5ef52189ca..34768c16f7 100644 --- a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/meta.json @@ -1,30 +1,71 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/f8f4aeda930fcd0805ca4cc76d9bc9412a5b3428, wielded versions from Easypoll", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/f8f4aeda930fcd0805ca4cc76d9bc9412a5b3428, wielded versions from Easypoll, fill levels by Tayrtahn on GitHub.", "size": { "x": 32, "y": 32 }, "states": [ { - "name": "mop" + "name": "mop" }, { - "name": "inhand-left", - "directions": 4 + "name": "fill-1" }, { - "name": "inhand-right", - "directions": 4 + "name": "fill-2" }, - { - "name": "wielded-inhand-left", - "directions": 4 + { + "name": "fill-3" }, - { - "name": "wielded-inhand-right", - "directions": 4 + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "inhand-left-fill-1", + "directions": 4 + }, + { + "name": "inhand-left-fill-2", + "directions": 4 + }, + { + "name": "inhand-right-fill-1", + "directions": 4 + }, + { + "name": "inhand-right-fill-2", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left-fill-1", + "directions": 4 + }, + { + "name": "wielded-inhand-left-fill-2", + "directions": 4 + }, + { + "name": "wielded-inhand-right-fill-1", + "directions": 4 + }, + { + "name": "wielded-inhand-right-fill-2", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-left-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-left-fill-1.png new file mode 100644 index 0000000000000000000000000000000000000000..997f3ae0398d3aab2bf9de11d8ba7e1ad495a62e GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|f<0XvLn`LH zy}6P1fPw&vJi?#|6e*n=7ZcW4WH_dPB-X8=^nz;H+7M+n(=5-Rl|Wors-SUfgi%(P{TL^Y!*! dTHejxP{tklFT8&(L;4nwYEM@`mvv4FO#u8pM2!Fd literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-right-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-right-fill-1.png new file mode 100644 index 0000000000000000000000000000000000000000..956f9bbbd19035e10cfb628e544b645c0be7dabc GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|0zF+ELn`LH zy=lnT;K0G+@aXjW=h^uSTLXE{Z83Qh6vPTt!|=h~GweHGzQwaU=h`mEJoDbPY?^_k tG)RVl;ehSFl~C0!g?voY|n;^ENcgcI*_D0XTX_GUVfQlLZ1x?zzt1W)V8{?BN zEcDc3pb%AgDrj<`WX1P$$>O06LAh>ho>+&DTSFM@*B^**Yt}<6>F8TD< hy(Qpe5;H^1Q8t~{@P5-ruOEPvdb;|#taD0e0swh>MyCJ( literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-1.png new file mode 100644 index 0000000000000000000000000000000000000000..6fb322fbdf212a7f567ac6f49be5a319b6679acb GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}_MR?|ArY-_ zFKiTKP~dU8xM3M{+S!PkH!8Zq{#`FFZ~-b|Shgz5FK+uO)u)#k7yAUAJ9=)yTK2H= d`TH0i|y~EXe=> literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-2.png new file mode 100644 index 0000000000000000000000000000000000000000..7114d9a2e74ef60e00ed2786870d451fa2eea9b4 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}v7RoDArY-_ z&j)ffC6Yz{Z)v-aZ1$OXp2_{f&a4~jD=R)kPYifr$UNg5--lb?iV_n)iUPGWc)I$z JtaD0e0sv&AJiPz_ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-3.png b/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-3.png new file mode 100644 index 0000000000000000000000000000000000000000..0705b79b5086902440124793aea3a85690e2f1d2 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}>7Fi*ArY-_ zFCOGPpup4m@P?u6VMm$8mv>x#ebhNB!?Rh0>wkIoq3+H!pyD6Ko}rf}glv|)y=eQE zH_Bey9_QRhyXn7h>6@E(Zl~2 Date: Tue, 4 Jun 2024 18:49:31 +0000 Subject: [PATCH 063/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index b14fae9c41..574ea1adb3 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,14 +1,4 @@ Entries: -- author: DoutorWhite - changes: - - message: Introduce new health status icons. - type: Add - - message: Enhance the Medical HUD to display the health status of players and mobs - using the newly added icons. - type: Tweak - id: 6182 - time: '2024-03-18T13:56:12.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26027 - author: Plykiya changes: - message: Lone operatives now start with 60TC instead of 40TC. @@ -3852,3 +3842,10 @@ id: 6681 time: '2024-06-04T17:12:01.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28225 +- author: Tayrtahn + changes: + - message: Mops and rags now show the liquids they soak up. + type: Add + id: 6682 + time: '2024-06-04T18:48:24.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28590 From ec5a3b0fd2c56749e0addddacc90caf6ff002078 Mon Sep 17 00:00:00 2001 From: TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com> Date: Tue, 4 Jun 2024 23:47:52 +0200 Subject: [PATCH 064/243] Reword some criminal records text (#28597) * Update criminal-records.ftl * Update criminal-records.ftl * Update criminal-records.ftl --- .../Locale/en-US/criminal-records/criminal-records.ftl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/Locale/en-US/criminal-records/criminal-records.ftl b/Resources/Locale/en-US/criminal-records/criminal-records.ftl index f603b44666..6d6a97300c 100644 --- a/Resources/Locale/en-US/criminal-records/criminal-records.ftl +++ b/Resources/Locale/en-US/criminal-records/criminal-records.ftl @@ -31,14 +31,14 @@ criminal-records-permission-denied = Permission denied ## Security channel notifications -criminal-records-console-wanted = {$name} is wanted by {$officer} for: {$reason}. +criminal-records-console-wanted = {$name} was made wanted by {$officer} for: {$reason}. criminal-records-console-suspected = {$officer} marked {$name} as suspicious because of: {$reason} -criminal-records-console-not-suspected = {$name} has been cleared as a suspect by {$officer}. +criminal-records-console-not-suspected = {$name} has been cleared of suspicion by {$officer}. criminal-records-console-detained = {$name} has been detained by {$officer}. criminal-records-console-released = {$name} has been released by {$officer}. -criminal-records-console-not-wanted = {$name} is no longer wanted, according to {$officer}. +criminal-records-console-not-wanted = {$officer} cleared the wanted status of {$name}. criminal-records-console-paroled = {$name} has been released on parole by {$officer}. -criminal-records-console-not-parole = {$name}'s parole status has been lifted by {$officer}. +criminal-records-console-not-parole = {$officer} cleared the parole status of {$name}. criminal-records-console-unknown-officer = ## Filters From 41549db28284d75fbf5c376030af7171aeb6278a Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Wed, 5 Jun 2024 00:04:31 -0700 Subject: [PATCH 065/243] Remove obsolete VisibilitySystem functions (#28610) Remove obsolete visibility functions Co-authored-by: plykiya --- Content.Server/Ghost/GhostSystem.cs | 16 ++++++++-------- .../Pointing/EntitySystems/PointingSystem.cs | 2 +- .../Revenant/EntitySystems/CorporealSystem.cs | 10 +++++----- .../Revenant/EntitySystems/RevenantSystem.cs | 12 ++++++------ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Content.Server/Ghost/GhostSystem.cs b/Content.Server/Ghost/GhostSystem.cs index 1a411f13a0..dce80a450f 100644 --- a/Content.Server/Ghost/GhostSystem.cs +++ b/Content.Server/Ghost/GhostSystem.cs @@ -154,8 +154,8 @@ namespace Content.Server.Ghost if (_ticker.RunLevel != GameRunLevel.PostRound) { - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility); } @@ -174,8 +174,8 @@ namespace Content.Server.Ghost // Entity can't be seen by ghosts anymore. if (TryComp(uid, out VisibilityComponent? visibility)) { - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility); } @@ -382,13 +382,13 @@ namespace Content.Server.Ghost { if (visible) { - _visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Normal, false); - _visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Ghost, false); + _visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Normal, false); + _visibilitySystem.RemoveLayer((uid, vis), (int) VisibilityFlags.Ghost, false); } else { - _visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Normal, false); + _visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.RemoveLayer((uid, vis), (int) VisibilityFlags.Normal, false); } _visibilitySystem.RefreshVisibility(uid, visibilityComponent: vis); } diff --git a/Content.Server/Pointing/EntitySystems/PointingSystem.cs b/Content.Server/Pointing/EntitySystems/PointingSystem.cs index 960c8dc28a..9dc3b5e1e6 100644 --- a/Content.Server/Pointing/EntitySystems/PointingSystem.cs +++ b/Content.Server/Pointing/EntitySystems/PointingSystem.cs @@ -174,7 +174,7 @@ namespace Content.Server.Pointing.EntitySystems { var arrowVisibility = EntityManager.EnsureComponent(arrow); layer = playerVisibility.Layer; - _visibilitySystem.SetLayer(arrow, arrowVisibility, layer); + _visibilitySystem.SetLayer((arrow, arrowVisibility), (ushort) layer); } // Get players that are in range and whose visibility layer matches the arrow's. diff --git a/Content.Server/Revenant/EntitySystems/CorporealSystem.cs b/Content.Server/Revenant/EntitySystems/CorporealSystem.cs index 1d43cb3ac8..5f31a2f280 100644 --- a/Content.Server/Revenant/EntitySystems/CorporealSystem.cs +++ b/Content.Server/Revenant/EntitySystems/CorporealSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.GameTicking; +using Content.Server.GameTicking; using Content.Shared.Eye; using Content.Shared.Revenant.Components; using Content.Shared.Revenant.EntitySystems; @@ -17,8 +17,8 @@ public sealed class CorporealSystem : SharedCorporealSystem if (TryComp(uid, out var visibility)) { - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibility); } } @@ -29,8 +29,8 @@ public sealed class CorporealSystem : SharedCorporealSystem if (TryComp(uid, out var visibility) && _ticker.RunLevel != GameRunLevel.PostRound) { - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibility); } } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs index a05105662d..fa4f6db240 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs @@ -78,8 +78,8 @@ public sealed partial class RevenantSystem : EntitySystem if (_ticker.RunLevel == GameRunLevel.PostRound && TryComp(uid, out var visibility)) { - _visibility.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibility.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibility.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibility.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibility.RefreshVisibility(uid, visibility); } @@ -192,13 +192,13 @@ public sealed partial class RevenantSystem : EntitySystem { if (visible) { - _visibility.AddLayer(uid, vis, (int) VisibilityFlags.Normal, false); - _visibility.RemoveLayer(uid, vis, (int) VisibilityFlags.Ghost, false); + _visibility.AddLayer((uid, vis), (int) VisibilityFlags.Normal, false); + _visibility.RemoveLayer((uid, vis), (int) VisibilityFlags.Ghost, false); } else { - _visibility.AddLayer(uid, vis, (int) VisibilityFlags.Ghost, false); - _visibility.RemoveLayer(uid, vis, (int) VisibilityFlags.Normal, false); + _visibility.AddLayer((uid, vis), (int) VisibilityFlags.Ghost, false); + _visibility.RemoveLayer((uid, vis), (int) VisibilityFlags.Normal, false); } _visibility.RefreshVisibility(uid, vis); } From 86cf5cc7d1f5886be4fb41ca224b8cf13cb45606 Mon Sep 17 00:00:00 2001 From: TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:32:58 +0200 Subject: [PATCH 066/243] Prayable datafield typo (#28622) * notifiactionPrefix -> notificationPrefix * notifiactionPrefix -> notificationPrefix --- Content.Shared/Prayer/PrayableComponent.cs | 2 +- .../Entities/Objects/Fun/Instruments/instruments_misc.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Content.Shared/Prayer/PrayableComponent.cs b/Content.Shared/Prayer/PrayableComponent.cs index 71251af810..b6dcc1c210 100644 --- a/Content.Shared/Prayer/PrayableComponent.cs +++ b/Content.Shared/Prayer/PrayableComponent.cs @@ -26,7 +26,7 @@ public sealed partial class PrayableComponent : Component /// /// Prefix used in the notification to admins /// - [DataField("notifiactionPrefix")] + [DataField("notificationPrefix")] [ViewVariables(VVAccess.ReadWrite)] public string NotificationPrefix = "prayer-chat-notify-pray"; diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_misc.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_misc.yml index edad2b4063..8cb3d88ede 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_misc.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_misc.yml @@ -59,7 +59,7 @@ size: Small - type: Prayable sentMessage: prayer-popup-notify-centcom-sent - notifiactionPrefix: prayer-chat-notify-centcom + notificationPrefix: prayer-chat-notify-centcom verb: prayer-verbs-call verbImage: null @@ -74,7 +74,7 @@ state: icon - type: Prayable sentMessage: prayer-popup-notify-syndicate-sent - notifiactionPrefix: prayer-chat-notify-syndicate + notificationPrefix: prayer-chat-notify-syndicate - type: entity parent: BaseHandheldInstrument @@ -185,6 +185,6 @@ - ItemMask - type: Prayable sentMessage: prayer-popup-notify-honkmother-sent - notifiactionPrefix: prayer-chat-notify-honkmother + notificationPrefix: prayer-chat-notify-honkmother verb: prayer-verbs-call verbImage: null From 0f3a2424e8e034bb862700c242c3541d71aaabd0 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:21:47 +1200 Subject: [PATCH 067/243] Update engine to v224.1.0 (#28624) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index f648218756..a628d31c4b 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit f64821875655dfec4e2c951d5c22c0db873fe86e +Subproject commit a628d31c4b82612eec2c42ba33024acc1c4d9d7e From 7bd1b45f2b32ce5a99c719d2b9899df844c51dd9 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:37:22 +1200 Subject: [PATCH 068/243] Use dummy sessions in NukeOpsTest (#28549) * Add dummy sessions * Update NukeOpsTest * Fix PvsBenchmark --- Content.Benchmarks/PvsBenchmark.cs | 41 ++++++--------- .../Pair/TestPair.Helpers.cs | 13 +++-- .../Pair/TestPair.Recycle.cs | 2 + Content.IntegrationTests/Pair/TestPair.cs | 5 +- .../Tests/GameRules/NukeOpsTest.cs | 52 ++++++++++++++++--- 5 files changed, 76 insertions(+), 37 deletions(-) diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs index 0b4dd90762..fa7f9d4542 100644 --- a/Content.Benchmarks/PvsBenchmark.cs +++ b/Content.Benchmarks/PvsBenchmark.cs @@ -1,19 +1,17 @@ #nullable enable using System; -using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Content.IntegrationTests; using Content.IntegrationTests.Pair; +using Content.Server.Mind; using Content.Server.Warps; using Robust.Server.GameObjects; using Robust.Shared; using Robust.Shared.Analyzers; -using Robust.Shared.Enums; using Robust.Shared.GameObjects; -using Robust.Shared.GameStates; using Robust.Shared.Map; -using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Random; @@ -58,15 +56,20 @@ public class PvsBenchmark _pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false); _sys = _entMan.System(); + SetupAsync().Wait(); + } + + private async Task SetupAsync() + { // Spawn the map _pair.Server.ResolveDependency().SetSeed(42); - _pair.Server.WaitPost(() => + await _pair.Server.WaitPost(() => { var success = _entMan.System().TryLoad(_mapId, Map, out _); if (!success) throw new Exception("Map load failed"); _pair.Server.MapMan.DoMapInitialize(_mapId); - }).Wait(); + }); // Get list of ghost warp positions _spawns = _entMan.AllComponentsList() @@ -76,17 +79,19 @@ public class PvsBenchmark Array.Resize(ref _players, PlayerCount); - // Spawn "Players". - _pair.Server.WaitPost(() => + // Spawn "Players" + _players = await _pair.Server.AddDummySessions(PlayerCount); + await _pair.Server.WaitPost(() => { + var mind = _pair.Server.System(); for (var i = 0; i < PlayerCount; i++) { var pos = _spawns[i % _spawns.Length]; var uid =_entMan.SpawnEntity("MobHuman", pos); _pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear"); - _players[i] = new DummySession{AttachedEntity = uid}; + mind.ControlMob(_players[i].UserId, uid); } - }).Wait(); + }); // Repeatedly move players around so that they "explore" the map and see lots of entities. // This will populate their PVS data with out-of-view entities. @@ -168,20 +173,4 @@ public class PvsBenchmark }).Wait(); _pair.Server.PvsTick(_players); } - - private sealed class DummySession : ICommonSession - { - public SessionStatus Status => SessionStatus.InGame; - public EntityUid? AttachedEntity {get; set; } - public NetUserId UserId => default; - public string Name => string.Empty; - public short Ping => default; - public INetChannel Channel { get; set; } = default!; - public LoginType AuthType => default; - public HashSet ViewSubscriptions { get; } = new(); - public DateTime ConnectedTime { get; set; } - public SessionState State => default!; - public SessionData Data => default!; - public bool ClientSide { get; set; } - } } diff --git a/Content.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs index cc83232a06..1e8306a02c 100644 --- a/Content.IntegrationTests/Pair/TestPair.Helpers.cs +++ b/Content.IntegrationTests/Pair/TestPair.Helpers.cs @@ -7,6 +7,8 @@ using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.UnitTesting; @@ -136,10 +138,15 @@ public sealed partial class TestPair /// Helper method for enabling or disabling a antag role /// public async Task SetAntagPref(ProtoId id, bool value) + { + await SetAntagPref(Client.User!.Value, id, value); + } + + public async Task SetAntagPref(NetUserId user, ProtoId id, bool value) { var prefMan = Server.ResolveDependency(); - var prefs = prefMan.GetPreferences(Client.User!.Value); + var prefs = prefMan.GetPreferences(user); // what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable? var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter; @@ -148,11 +155,11 @@ public sealed partial class TestPair await Server.WaitPost(() => { - prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait(); + prefMan.SetProfile(user, prefs.SelectedCharacterIndex, newProfile).Wait(); }); // And why the fuck does it always create a new preference and profile object instead of just reusing them? - var newPrefs = prefMan.GetPreferences(Client.User.Value); + var newPrefs = prefMan.GetPreferences(user); var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter; Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value)); } diff --git a/Content.IntegrationTests/Pair/TestPair.Recycle.cs b/Content.IntegrationTests/Pair/TestPair.Recycle.cs index 8d1e425553..4cae4affc4 100644 --- a/Content.IntegrationTests/Pair/TestPair.Recycle.cs +++ b/Content.IntegrationTests/Pair/TestPair.Recycle.cs @@ -34,6 +34,8 @@ public sealed partial class TestPair : IAsyncDisposable private async Task OnCleanDispose() { + await Server.RemoveAllDummySessions(); + if (TestMap != null) { await Server.WaitPost(() => Server.EntMan.DeleteEntity(TestMap.MapUid)); diff --git a/Content.IntegrationTests/Pair/TestPair.cs b/Content.IntegrationTests/Pair/TestPair.cs index 7ee5dbd55c..0b18c38239 100644 --- a/Content.IntegrationTests/Pair/TestPair.cs +++ b/Content.IntegrationTests/Pair/TestPair.cs @@ -37,7 +37,10 @@ public sealed partial class TestPair client = Client; } - public ICommonSession? Player => Server.PlayerMan.Sessions.FirstOrDefault(); + public ICommonSession? Player => Client.User == null + ? null + : Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User.Value); + public ContentPlayerData? PlayerData => Player?.Data.ContentData(); public PoolTestLogHandler ServerLogHandler { get; private set; } = default!; diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index 2360ea0bf4..f56baba342 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -57,8 +57,17 @@ public sealed class NukeOpsTest Assert.That(client.AttachedEntity, Is.Null); Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + // Add several dummy players + var dummies = await pair.Server.AddDummySessions(3); + await pair.RunTicksSync(5); + // Opt into the nukies role. await pair.SetAntagPref("NukeopsCommander", true); + await pair.SetAntagPref(dummies[1].UserId, "NukeopsMedic", true); + + // Initially, the players have no attached entities + Assert.That(pair.Player?.AttachedEntity, Is.Null); + Assert.That(dummies.All(x => x.AttachedEntity == null)); // There are no grids or maps Assert.That(entMan.Count(), Is.Zero); @@ -75,17 +84,20 @@ public sealed class NukeOpsTest Assert.That(entMan.Count(), Is.Zero); // Ready up and start nukeops - await pair.WaitClientCommand("toggleready True"); - Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay)); + ticker.ToggleReadyAll(true); + Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.ReadyToPlay)); await pair.WaitCommand("forcepreset Nukeops"); await pair.RunTicksSync(10); // Game should have started Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); - Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.JoinedGame)); + Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.JoinedGame)); Assert.That(client.EntMan.EntityExists(client.AttachedEntity)); + + var dummyEnts = dummies.Select(x => x.AttachedEntity ?? default).ToArray(); var player = pair.Player!.AttachedEntity!.Value; Assert.That(entMan.EntityExists(player)); + Assert.That(dummyEnts.All(e => entMan.EntityExists(e))); // Maps now exist Assert.That(entMan.Count(), Is.GreaterThan(0)); @@ -96,8 +108,8 @@ public sealed class NukeOpsTest // And we now have nukie related components Assert.That(entMan.Count(), Is.EqualTo(1)); - Assert.That(entMan.Count(), Is.EqualTo(1)); - Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(2)); + Assert.That(entMan.Count(), Is.EqualTo(2)); Assert.That(entMan.Count(), Is.EqualTo(1)); // The player entity should be the nukie commander @@ -107,11 +119,36 @@ public sealed class NukeOpsTest Assert.That(roleSys.MindHasRole(mind)); Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True); Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False); - var roles = roleSys.MindGetAllRoles(mind); var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent); Assert.That(cmdRoles.Count(), Is.EqualTo(1)); + // The second dummy player should be a medic + var dummyMind = mindSys.GetMind(dummyEnts[1])!.Value; + Assert.That(entMan.HasComponent(dummyEnts[1])); + Assert.That(roleSys.MindIsAntagonist(dummyMind)); + Assert.That(roleSys.MindHasRole(dummyMind)); + Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True); + Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False); + roles = roleSys.MindGetAllRoles(dummyMind); + cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent); + Assert.That(cmdRoles.Count(), Is.EqualTo(1)); + + // The other two players should have just spawned in as normal. + CheckDummy(0); + CheckDummy(2); + void CheckDummy(int i) + { + var ent = dummyEnts[i]; + var mind = mindSys.GetMind(ent)!.Value; + Assert.That(entMan.HasComponent(ent), Is.False); + Assert.That(roleSys.MindIsAntagonist(mind), Is.False); + Assert.That(roleSys.MindHasRole(mind), Is.False); + Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False); + Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True); + Assert.That(roleSys.MindGetAllRoles(mind).Any(x => x.Component is NukeopsRoleComponent), Is.False); + } + // The game rule exists, and all the stations/shuttles/maps are properly initialized var rule = entMan.AllComponents().Single().Component; var gridsRule = entMan.AllComponents().Single().Component; @@ -178,7 +215,7 @@ public sealed class NukeOpsTest // While we're at it, lets make sure they aren't naked. I don't know how many inventory slots all mobs will be // likely to have in the future. But nukies should probably have at least 3 slots with something in them. var enumerator = invSys.GetSlotEnumerator(player); - int total = 0; + var total = 0; while (enumerator.NextItem(out _)) { total++; @@ -200,6 +237,7 @@ public sealed class NukeOpsTest ticker.SetGamePreset((GamePresetPrototype?)null); await pair.SetAntagPref("NukeopsCommander", false); + await pair.SetAntagPref(dummies[1].UserId, "NukeopsMedic", false); await pair.CleanReturnAsync(); } } From e1541351a5a26eac8fca0db8ae3e4cc827d65691 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 6 Jun 2024 01:00:33 +1200 Subject: [PATCH 069/243] Update engine to v224.1.1 (#28632) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index a628d31c4b..fcd507d1f9 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit a628d31c4b82612eec2c42ba33024acc1c4d9d7e +Subproject commit fcd507d1f9e6fffb96bc4228d7f3d78577f0ff2e From adeed705e64ee03f1e7d462c486f98859d6e2dc5 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 6 Jun 2024 02:19:24 +1200 Subject: [PATCH 070/243] Add Job preference tests (#28625) * Misc Job related changes * Add JobTest * A * Aa * Lets not confuse the yaml linter * fixes * a --- .../GameTicking/Managers/ClientGameTicker.cs | 11 +- Content.Client/LateJoin/LateJoinGui.cs | 8 +- Content.Client/Lobby/LobbyUIController.cs | 2 +- .../Lobby/UI/CharacterPickerButton.xaml.cs | 4 +- .../Pair/TestPair.Helpers.cs | 74 ++++-- .../Pair/TestPair.Recycle.cs | 15 ++ Content.IntegrationTests/Pair/TestPair.cs | 6 +- Content.IntegrationTests/PoolManager.Cvars.cs | 1 + Content.IntegrationTests/Tests/EntityTest.cs | 1 + .../Tests/GameRules/AntagPreferenceTest.cs | 4 +- .../Tests/GameRules/NukeOpsTest.cs | 6 +- .../Tests/PostMapInitTest.cs | 23 +- .../Tests/Round/JobTest.cs | 222 ++++++++++++++++++ .../Tests/Station/StationJobsTest.cs | 13 +- Content.Server/Database/ServerDbBase.cs | 7 +- .../GameTicking/GameTicker.RoundFlow.cs | 2 +- .../Managers/ServerPreferencesManager.cs | 6 +- .../Components/SpawnPointComponent.cs | 9 +- .../EntitySystems/SpawnPointSystem.cs | 2 +- .../Components/StationJobsComponent.cs | 46 ++-- .../Systems/StationJobsSystem.Roundstart.cs | 23 +- .../Station/Systems/StationJobsSystem.cs | 138 ++++++----- .../GameTicking/SharedGameTicker.cs | 15 +- .../Preferences/HumanoidCharacterProfile.cs | 104 ++++++-- Content.Shared/Roles/JobPrototype.cs | 4 +- Content.Shared/Roles/Jobs/SharedJobSystem.cs | 12 + Resources/Prototypes/Maps/arenas.yml | 2 - Resources/Prototypes/Maps/atlas.yml | 2 - Resources/Prototypes/Maps/bagel.yml | 2 - Resources/Prototypes/Maps/box.yml | 2 - Resources/Prototypes/Maps/cluster.yml | 2 - Resources/Prototypes/Maps/core.yml | 2 - Resources/Prototypes/Maps/debug.yml | 6 - Resources/Prototypes/Maps/europa.yml | 2 - Resources/Prototypes/Maps/fland.yml | 2 - Resources/Prototypes/Maps/marathon.yml | 2 - Resources/Prototypes/Maps/meta.yml | 2 - Resources/Prototypes/Maps/oasis.yml | 4 +- Resources/Prototypes/Maps/omega.yml | 2 - Resources/Prototypes/Maps/origin.yml | 2 - Resources/Prototypes/Maps/packed.yml | 2 - Resources/Prototypes/Maps/reach.yml | 2 - Resources/Prototypes/Maps/saltern.yml | 2 - Resources/Prototypes/Maps/train.yml | 2 - 44 files changed, 538 insertions(+), 262 deletions(-) create mode 100644 Content.IntegrationTests/Tests/Round/JobTest.cs diff --git a/Content.Client/GameTicking/Managers/ClientGameTicker.cs b/Content.Client/GameTicking/Managers/ClientGameTicker.cs index 309db2eb4e..fcf5ae91a4 100644 --- a/Content.Client/GameTicking/Managers/ClientGameTicker.cs +++ b/Content.Client/GameTicking/Managers/ClientGameTicker.cs @@ -4,10 +4,12 @@ using Content.Client.Lobby; using Content.Client.RoundEnd; using Content.Shared.GameTicking; using Content.Shared.GameWindow; +using Content.Shared.Roles; using JetBrains.Annotations; using Robust.Client.Graphics; using Robust.Client.State; using Robust.Client.UserInterface; +using Robust.Shared.Prototypes; namespace Content.Client.GameTicking.Managers { @@ -17,10 +19,9 @@ namespace Content.Client.GameTicking.Managers [Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IClientAdminManager _admin = default!; [Dependency] private readonly IClyde _clyde = default!; - [Dependency] private readonly SharedMapSystem _map = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; - private Dictionary> _jobsAvailable = new(); + private Dictionary, int?>> _jobsAvailable = new(); private Dictionary _stationNames = new(); [ViewVariables] public bool AreWeReady { get; private set; } @@ -32,13 +33,13 @@ namespace Content.Client.GameTicking.Managers [ViewVariables] public TimeSpan StartTime { get; private set; } [ViewVariables] public new bool Paused { get; private set; } - [ViewVariables] public IReadOnlyDictionary> JobsAvailable => _jobsAvailable; + [ViewVariables] public IReadOnlyDictionary, int?>> JobsAvailable => _jobsAvailable; [ViewVariables] public IReadOnlyDictionary StationNames => _stationNames; public event Action? InfoBlobUpdated; public event Action? LobbyStatusUpdated; public event Action? LobbyLateJoinStatusUpdated; - public event Action>>? LobbyJobsAvailableUpdated; + public event Action, int?>>>? LobbyJobsAvailableUpdated; public override void Initialize() { @@ -69,7 +70,7 @@ namespace Content.Client.GameTicking.Managers // reading the console. E.g., logs like this one could leak the nuke station/grid: // > Grid NT-Arrivals 1101 (122/n25896) changed parent. Old parent: map 10 (121/n25895). New parent: FTL (123/n26470) #if !DEBUG - _map.Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning; + EntityManager.System().Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning; #endif } diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs index 252aa9aafa..62a06629f2 100644 --- a/Content.Client/LateJoin/LateJoinGui.cs +++ b/Content.Client/LateJoin/LateJoinGui.cs @@ -290,7 +290,7 @@ namespace Content.Client.LateJoin } } - private void JobsAvailableUpdated(IReadOnlyDictionary> updatedJobs) + private void JobsAvailableUpdated(IReadOnlyDictionary, int?>> updatedJobs) { foreach (var stationEntries in updatedJobs) { @@ -337,10 +337,10 @@ namespace Content.Client.LateJoin public Label JobLabel { get; } public string JobId { get; } public string JobLocalisedName { get; } - public uint? Amount { get; private set; } + public int? Amount { get; private set; } private bool _initialised = false; - public JobButton(Label jobLabel, string jobId, string jobLocalisedName, uint? amount) + public JobButton(Label jobLabel, ProtoId jobId, string jobLocalisedName, int? amount) { JobLabel = jobLabel; JobId = jobId; @@ -350,7 +350,7 @@ namespace Content.Client.LateJoin _initialised = true; } - public void RefreshLabel(uint? amount) + public void RefreshLabel(int? amount) { if (Amount == amount && _initialised) { diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index f6a3eed962..05b98606ab 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -302,7 +302,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered p.Value == JobPriority.High).Key; // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?) - return _prototypeManager.Index(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob); + return _prototypeManager.Index(highPriorityJob.Id ?? SharedGameTicker.FallbackOverflowJob); } public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout) diff --git a/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs b/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs index 2ad8de7445..7efd1c594f 100644 --- a/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs +++ b/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs @@ -53,9 +53,9 @@ public sealed partial class CharacterPickerButton : ContainerButton .LoadProfileEntity(humanoid, null, true); var highPriorityJob = humanoid.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key; - if (highPriorityJob != null) + if (highPriorityJob != default) { - var jobName = prototypeManager.Index(highPriorityJob).LocalizedName; + var jobName = prototypeManager.Index(highPriorityJob).LocalizedName; description = $"{description}\n{jobName}"; } } diff --git a/Content.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs index 1e8306a02c..588cf0d80e 100644 --- a/Content.IntegrationTests/Pair/TestPair.Helpers.cs +++ b/Content.IntegrationTests/Pair/TestPair.Helpers.cs @@ -8,7 +8,6 @@ using Content.Shared.Roles; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Network; -using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.UnitTesting; @@ -135,32 +134,73 @@ public sealed partial class TestPair } /// - /// Helper method for enabling or disabling a antag role + /// Set a user's antag preferences. Modified preferences are automatically reset at the end of the test. /// - public async Task SetAntagPref(ProtoId id, bool value) + public async Task SetAntagPreference(ProtoId id, bool value, NetUserId? user = null) { - await SetAntagPref(Client.User!.Value, id, value); + user ??= Client.User!.Value; + if (user is not {} userId) + return; + + var prefMan = Server.ResolveDependency(); + var prefs = prefMan.GetPreferences(userId); + + // Automatic preference resetting only resets slot 0. + Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0)); + + var profile = (HumanoidCharacterProfile) prefs.Characters[0]; + var newProfile = profile.WithAntagPreference(id, value); + _modifiedProfiles.Add(userId); + await Server.WaitPost(() => prefMan.SetProfile(userId, 0, newProfile).Wait()); } - public async Task SetAntagPref(NetUserId user, ProtoId id, bool value) + /// + /// Set a user's job preferences. Modified preferences are automatically reset at the end of the test. + /// + public async Task SetJobPriority(ProtoId id, JobPriority value, NetUserId? user = null) { + user ??= Client.User!.Value; + if (user is { } userId) + await SetJobPriorities(userId, (id, value)); + } + + /// + public async Task SetJobPriorities(params (ProtoId, JobPriority)[] priorities) + => await SetJobPriorities(Client.User!.Value, priorities); + + /// + public async Task SetJobPriorities(NetUserId user, params (ProtoId, JobPriority)[] priorities) + { + var highCount = priorities.Count(x => x.Item2 == JobPriority.High); + Assert.That(highCount, Is.LessThanOrEqualTo(1), "Cannot have more than one high priority job"); + var prefMan = Server.ResolveDependency(); - var prefs = prefMan.GetPreferences(user); - // what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable? - var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter; + var profile = (HumanoidCharacterProfile) prefs.Characters[0]; + var dictionary = new Dictionary, JobPriority>(profile.JobPriorities); - Assert.That(profile.AntagPreferences.Contains(id), Is.EqualTo(!value)); - var newProfile = profile.WithAntagPreference(id, value); + // Automatic preference resetting only resets slot 0. + Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0)); - await Server.WaitPost(() => + if (highCount != 0) { - prefMan.SetProfile(user, prefs.SelectedCharacterIndex, newProfile).Wait(); - }); + foreach (var (key, priority) in dictionary) + { + if (priority == JobPriority.High) + dictionary[key] = JobPriority.Medium; + } + } - // And why the fuck does it always create a new preference and profile object instead of just reusing them? - var newPrefs = prefMan.GetPreferences(user); - var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter; - Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value)); + foreach (var (job, priority) in priorities) + { + if (priority == JobPriority.Never) + dictionary.Remove(job); + else + dictionary[job] = priority; + } + + var newProfile = profile.WithJobPriorities(dictionary); + _modifiedProfiles.Add(user); + await Server.WaitPost(() => prefMan.SetProfile(user, 0, newProfile).Wait()); } } diff --git a/Content.IntegrationTests/Pair/TestPair.Recycle.cs b/Content.IntegrationTests/Pair/TestPair.Recycle.cs index 4cae4affc4..89a9eb6463 100644 --- a/Content.IntegrationTests/Pair/TestPair.Recycle.cs +++ b/Content.IntegrationTests/Pair/TestPair.Recycle.cs @@ -2,10 +2,12 @@ using System.IO; using System.Linq; using Content.Server.GameTicking; +using Content.Server.Preferences.Managers; using Content.Shared.CCVar; using Content.Shared.GameTicking; using Content.Shared.Mind; using Content.Shared.Mind.Components; +using Content.Shared.Preferences; using Robust.Client; using Robust.Server.Player; using Robust.Shared.Exceptions; @@ -34,6 +36,9 @@ public sealed partial class TestPair : IAsyncDisposable private async Task OnCleanDispose() { + await Server.WaitIdleAsync(); + await Client.WaitIdleAsync(); + await ResetModifiedPreferences(); await Server.RemoveAllDummySessions(); if (TestMap != null) @@ -81,6 +86,16 @@ public sealed partial class TestPair : IAsyncDisposable await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: PoolManager took {returnTime.TotalMilliseconds} ms to put pair {Id} back into the pool"); } + private async Task ResetModifiedPreferences() + { + var prefMan = Server.ResolveDependency(); + foreach (var user in _modifiedProfiles) + { + await Server.WaitPost(() => prefMan.SetProfile(user, 0, new HumanoidCharacterProfile()).Wait()); + } + _modifiedProfiles.Clear(); + } + public async ValueTask CleanReturnAsync() { if (State != PairState.InUse) diff --git a/Content.IntegrationTests/Pair/TestPair.cs b/Content.IntegrationTests/Pair/TestPair.cs index 0b18c38239..0b681dcde1 100644 --- a/Content.IntegrationTests/Pair/TestPair.cs +++ b/Content.IntegrationTests/Pair/TestPair.cs @@ -26,6 +26,8 @@ public sealed partial class TestPair public readonly List TestHistory = new(); public PoolSettings Settings = default!; public TestMapData? TestMap; + private List _modifiedProfiles = new(); + public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!; public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!; @@ -37,9 +39,7 @@ public sealed partial class TestPair client = Client; } - public ICommonSession? Player => Client.User == null - ? null - : Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User.Value); + public ICommonSession? Player => Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User!.Value); public ContentPlayerData? PlayerData => Player?.Data.ContentData(); diff --git a/Content.IntegrationTests/PoolManager.Cvars.cs b/Content.IntegrationTests/PoolManager.Cvars.cs index 5acd9d502c..bcd48f8238 100644 --- a/Content.IntegrationTests/PoolManager.Cvars.cs +++ b/Content.IntegrationTests/PoolManager.Cvars.cs @@ -28,6 +28,7 @@ public static partial class PoolManager (CCVars.EmergencyShuttleEnabled.Name, "false"), (CCVars.ProcgenPreload.Name, "false"), (CCVars.WorldgenEnabled.Name, "false"), + (CCVars.GatewayGeneratorEnabled.Name, "false"), (CVars.ReplayClientRecordingEnabled.Name, "false"), (CVars.ReplayServerRecordingEnabled.Name, "false"), (CCVars.GameDummyTicker.Name, "true"), diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 926374cf05..1fc739fb0c 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -340,6 +340,7 @@ namespace Content.IntegrationTests.Tests "MapGrid", "Broadphase", "StationData", // errors when removed mid-round + "StationJobs", "Actor", // We aren't testing actor components, those need their player session set. "BlobFloorPlanBuilder", // Implodes if unconfigured. "DebrisFeaturePlacerController", // Above. diff --git a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs index 662ea3b974..1bea33a82b 100644 --- a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs @@ -52,7 +52,7 @@ public sealed class AntagPreferenceTest Assert.That(pool.Count, Is.EqualTo(0)); // Opt into the traitor role. - await pair.SetAntagPref("Traitor", true); + await pair.SetAntagPreference("Traitor", true); Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); @@ -63,7 +63,7 @@ public sealed class AntagPreferenceTest Assert.That(sessions.Count, Is.EqualTo(1)); // opt back out - await pair.SetAntagPref("Traitor", false); + await pair.SetAntagPreference("Traitor", false); Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index f56baba342..fc50d0bd33 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -62,8 +62,8 @@ public sealed class NukeOpsTest await pair.RunTicksSync(5); // Opt into the nukies role. - await pair.SetAntagPref("NukeopsCommander", true); - await pair.SetAntagPref(dummies[1].UserId, "NukeopsMedic", true); + await pair.SetAntagPreference("NukeopsCommander", true); + await pair.SetAntagPreference( "NukeopsMedic", true, dummies[1].UserId); // Initially, the players have no attached entities Assert.That(pair.Player?.AttachedEntity, Is.Null); @@ -236,8 +236,6 @@ public sealed class NukeOpsTest } ticker.SetGamePreset((GamePresetPrototype?)null); - await pair.SetAntagPref("NukeopsCommander", false); - await pair.SetAntagPref(dummies[1].UserId, "NukeopsMedic", false); await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index 17c5e199a7..2ae4389841 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -245,22 +245,15 @@ namespace Content.IntegrationTests.Tests // Test all availableJobs have spawnPoints // This is done inside gamemap test because loading the map takes ages and we already have it. - var jobList = entManager.GetComponent(station).RoundStartJobList - .Where(x => x.Value != 0) - .Select(x => x.Key); - var spawnPoints = entManager.EntityQuery() - .Where(spawnpoint => spawnpoint.SpawnType == SpawnPointType.Job) - .Select(spawnpoint => spawnpoint.Job.ID) - .Distinct(); - List missingSpawnPoints = new(); - foreach (var spawnpoint in jobList.Except(spawnPoints)) - { - if (protoManager.Index(spawnpoint).SetPreference) - missingSpawnPoints.Add(spawnpoint); - } + var comp = entManager.GetComponent(station); + var jobs = new HashSet>(comp.SetupAvailableJobs.Keys); - Assert.That(missingSpawnPoints, Has.Count.EqualTo(0), - $"There is no spawnpoint for {string.Join(", ", missingSpawnPoints)} on {mapProto}."); + var spawnPoints = entManager.EntityQuery() + .Where(x => x.SpawnType == SpawnPointType.Job) + .Select(x => x.Job!.Value); + + jobs.ExceptWith(spawnPoints); + Assert.That(jobs, Is.Empty,$"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}."); } try diff --git a/Content.IntegrationTests/Tests/Round/JobTest.cs b/Content.IntegrationTests/Tests/Round/JobTest.cs new file mode 100644 index 0000000000..716e3cf4c2 --- /dev/null +++ b/Content.IntegrationTests/Tests/Round/JobTest.cs @@ -0,0 +1,222 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Content.IntegrationTests.Pair; +using Content.Server.GameTicking; +using Content.Server.Mind; +using Content.Server.Roles; +using Content.Shared.CCVar; +using Content.Shared.GameTicking; +using Content.Shared.Preferences; +using Content.Shared.Roles; +using Content.Shared.Roles.Jobs; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests.Round; + +[TestFixture] +public sealed class JobTest +{ + private static ProtoId _passenger = "Passenger"; + private static ProtoId _engineer = "StationEngineer"; + private static ProtoId _captain = "Captain"; + + private static string _map = "JobTestMap"; + + [TestPrototypes] + public static string JobTestMap = @$" +- type: gameMap + id: {_map} + mapName: {_map} + mapPath: /Maps/Test/empty.yml + minPlayers: 0 + stations: + Empty: + stationProto: StandardNanotrasenStation + components: + - type: StationNameSetup + mapNameTemplate: ""Empty"" + - type: StationJobs + availableJobs: + {_passenger}: [ -1, -1 ] + {_engineer}: [ -1, -1 ] + {_captain}: [ 1, 1 ] +"; + + public void AssertJob(TestPair pair, ProtoId job, NetUserId? user = null, bool isAntag = false) + { + var jobSys = pair.Server.System(); + var mindSys = pair.Server.System(); + var roleSys = pair.Server.System(); + var ticker = pair.Server.System(); + + user ??= pair.Client.User!.Value; + + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); + Assert.That(ticker.PlayerGameStatuses[user.Value], Is.EqualTo(PlayerGameStatus.JoinedGame)); + + var uid = pair.Server.PlayerMan.SessionsDict.GetValueOrDefault(user.Value)?.AttachedEntity; + Assert.That(pair.Server.EntMan.EntityExists(uid)); + var mind = mindSys.GetMind(uid!.Value); + Assert.That(pair.Server.EntMan.EntityExists(mind)); + Assert.That(jobSys.MindTryGetJobId(mind, out var actualJob)); + Assert.That(actualJob, Is.EqualTo(job)); + Assert.That(roleSys.MindIsAntagonist(mind), Is.EqualTo(isAntag)); + } + + /// + /// Simple test that checks that starting the round spawns the player into the test map as a passenger. + /// + [Test] + public async Task StartRoundTest() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + InLobby = true + }); + + pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map); + var ticker = pair.Server.System(); + + // Initially in the lobby + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(pair.Client.AttachedEntity, Is.Null); + Assert.That(ticker.PlayerGameStatuses[pair.Client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + + // Ready up and start the round + ticker.ToggleReadyAll(true); + Assert.That(ticker.PlayerGameStatuses[pair.Client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay)); + await pair.Server.WaitPost(() => ticker.StartRound()); + await pair.RunTicksSync(10); + + AssertJob(pair, _passenger); + + await pair.Server.WaitPost(() => ticker.RestartRound()); + await pair.CleanReturnAsync(); + } + + /// + /// Check that job preferences are respected. + /// + [Test] + public async Task JobPreferenceTest() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + InLobby = true + }); + + pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map); + var ticker = pair.Server.System(); + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(pair.Client.AttachedEntity, Is.Null); + + await pair.SetJobPriorities((_passenger, JobPriority.Medium), (_engineer, JobPriority.High)); + ticker.ToggleReadyAll(true); + await pair.Server.WaitPost(() => ticker.StartRound()); + await pair.RunTicksSync(10); + + AssertJob(pair, _engineer); + + await pair.Server.WaitPost(() => ticker.RestartRound()); + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + await pair.SetJobPriorities((_passenger, JobPriority.High), (_engineer, JobPriority.Medium)); + ticker.ToggleReadyAll(true); + await pair.Server.WaitPost(() => ticker.StartRound()); + await pair.RunTicksSync(10); + + AssertJob(pair, _passenger); + + await pair.Server.WaitPost(() => ticker.RestartRound()); + await pair.CleanReturnAsync(); + } + + /// + /// Check high priority jobs (e.g., captain) are selected before other roles, even if it means a player does not + /// get their preferred job. + /// + [Test] + public async Task JobWeightTest() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + InLobby = true + }); + + pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map); + var ticker = pair.Server.System(); + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(pair.Client.AttachedEntity, Is.Null); + + var captain = pair.Server.ProtoMan.Index(_captain); + var engineer = pair.Server.ProtoMan.Index(_engineer); + var passenger = pair.Server.ProtoMan.Index(_passenger); + Assert.That(captain.Weight, Is.GreaterThan(engineer.Weight)); + Assert.That(engineer.Weight, Is.EqualTo(passenger.Weight)); + + await pair.SetJobPriorities((_passenger, JobPriority.Medium), (_engineer, JobPriority.High), (_captain, JobPriority.Low)); + ticker.ToggleReadyAll(true); + await pair.Server.WaitPost(() => ticker.StartRound()); + await pair.RunTicksSync(10); + + AssertJob(pair, _captain); + + await pair.Server.WaitPost(() => ticker.RestartRound()); + await pair.CleanReturnAsync(); + } + + /// + /// Check that jobs are preferentially given to players that have marked those jobs as higher priority. + /// + [Test] + public async Task JobPriorityTest() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + InLobby = true + }); + + pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map); + var ticker = pair.Server.System(); + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(pair.Client.AttachedEntity, Is.Null); + + await pair.Server.AddDummySessions(5); + await pair.RunTicksSync(5); + + var engineers = pair.Server.PlayerMan.Sessions.Select(x => x.UserId).ToList(); + var captain = engineers[3]; + engineers.RemoveAt(3); + + await pair.SetJobPriorities(captain, (_captain, JobPriority.High), (_engineer, JobPriority.Medium)); + foreach (var engi in engineers) + { + await pair.SetJobPriorities(engi, (_captain, JobPriority.Medium), (_engineer, JobPriority.High)); + } + + ticker.ToggleReadyAll(true); + await pair.Server.WaitPost(() => ticker.StartRound()); + await pair.RunTicksSync(10); + + AssertJob(pair, _captain, captain); + Assert.Multiple(() => + { + foreach (var engi in engineers) + { + AssertJob(pair, _engineer, engi); + } + }); + + await pair.Server.WaitPost(() => ticker.RestartRound()); + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/Station/StationJobsTest.cs b/Content.IntegrationTests/Tests/Station/StationJobsTest.cs index 0085472c33..d68fdafb76 100644 --- a/Content.IntegrationTests/Tests/Station/StationJobsTest.cs +++ b/Content.IntegrationTests/Tests/Station/StationJobsTest.cs @@ -7,7 +7,6 @@ using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Shared.GameObjects; using Robust.Shared.Log; -using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -46,8 +45,6 @@ public sealed class StationJobsTest stationProto: StandardNanotrasenStation components: - type: StationJobs - overflowJobs: - - Passenger availableJobs: TMime: [0, -1] TAssistant: [-1, -1] @@ -164,7 +161,6 @@ public sealed class StationJobsTest var server = pair.Server; var prototypeManager = server.ResolveDependency(); - var mapManager = server.ResolveDependency(); var fooStationProto = prototypeManager.Index("FooStation"); var entSysMan = server.ResolveDependency().EntitySysManager; var stationJobs = entSysMan.GetEntitySystem(); @@ -215,6 +211,8 @@ public sealed class StationJobsTest var server = pair.Server; var prototypeManager = server.ResolveDependency(); + var compFact = server.ResolveDependency(); + var name = compFact.GetComponentName(); await server.WaitAssertion(() => { @@ -233,11 +231,14 @@ public sealed class StationJobsTest { foreach (var (stationId, station) in gameMap.Stations) { - if (!station.StationComponentOverrides.TryGetComponent("StationJobs", out var comp)) + if (!station.StationComponentOverrides.TryGetComponent(name, out var comp)) continue; - foreach (var (job, _) in ((StationJobsComponent) comp).SetupAvailableJobs) + foreach (var (job, array) in ((StationJobsComponent) comp).SetupAvailableJobs) { + Assert.That(array.Length, Is.EqualTo(2)); + Assert.That(array[0] is -1 or >= 0); + Assert.That(array[1] is -1 or >= 0); Assert.That(invalidJobs, Does.Not.Contain(job), $"Station {stationId} contains job prototype {job} which cannot be present roundstart."); } } diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index be6c7196d5..cd03af7087 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -15,6 +15,7 @@ using Content.Shared.Humanoid.Markings; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; using Content.Shared.Roles; +using Content.Shared.Traits; using Microsoft.EntityFrameworkCore; using Robust.Shared.Enums; using Robust.Shared.Network; @@ -183,9 +184,9 @@ namespace Content.Server.Database private static HumanoidCharacterProfile ConvertProfiles(Profile profile) { - var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority); - var antags = profile.Antags.Select(a => a.AntagName); - var traits = profile.Traits.Select(t => t.TraitName); + var jobs = profile.Jobs.ToDictionary(j => new ProtoId(j.JobName), j => (JobPriority) j.Priority); + var antags = profile.Antags.Select(a => new ProtoId(a.AntagName)); + var traits = profile.Traits.Select(t => new ProtoId(t.TraitName)); var sex = Sex.Male; if (Enum.TryParse(profile.Sex, true, out var sexVal)) diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 6eb42b65c0..98fcc64410 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -239,7 +239,7 @@ namespace Content.Server.GameTicking HumanoidCharacterProfile profile; if (_prefsManager.TryGetCachedPreferences(userId, out var preferences)) { - profile = (HumanoidCharacterProfile) preferences.GetProfile(preferences.SelectedCharacterIndex); + profile = (HumanoidCharacterProfile) preferences.SelectedCharacter; } else { diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index f7c15a2340..8de458b6ee 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -305,11 +305,7 @@ namespace Content.Server.Preferences.Managers return usernames .Select(p => (_cachedPlayerPrefs[p].Prefs, p)) .Where(p => p.Prefs != null) - .Select(p => - { - var idx = p.Prefs!.SelectedCharacterIndex; - return new KeyValuePair(p.p, p.Prefs!.GetProfile(idx)); - }); + .Select(p => new KeyValuePair(p.p, p.Prefs!.SelectedCharacter)); } internal static bool ShouldStorePrefs(LoginType loginType) diff --git a/Content.Server/Spawners/Components/SpawnPointComponent.cs b/Content.Server/Spawners/Components/SpawnPointComponent.cs index 5cf231f224..c6d14dfeb3 100644 --- a/Content.Server/Spawners/Components/SpawnPointComponent.cs +++ b/Content.Server/Spawners/Components/SpawnPointComponent.cs @@ -6,11 +6,8 @@ namespace Content.Server.Spawners.Components; [RegisterComponent] public sealed partial class SpawnPointComponent : Component, ISpawnPoint { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - [ViewVariables(VVAccess.ReadWrite)] [DataField("job_id")] - private string? _jobId; + public ProtoId? Job; /// /// The type of spawn point @@ -18,11 +15,9 @@ public sealed partial class SpawnPointComponent : Component, ISpawnPoint [DataField("spawn_type"), ViewVariables(VVAccess.ReadWrite)] public SpawnPointType SpawnType { get; set; } = SpawnPointType.Unset; - public JobPrototype? Job => string.IsNullOrEmpty(_jobId) ? null : _prototypeManager.Index(_jobId); - public override string ToString() { - return $"{_jobId} {SpawnType}"; + return $"{Job} {SpawnType}"; } } diff --git a/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs b/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs index 5be4ff10f4..be555dd54a 100644 --- a/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs +++ b/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs @@ -39,7 +39,7 @@ public sealed class SpawnPointSystem : EntitySystem if (_gameTicker.RunLevel != GameRunLevel.InRound && spawnPoint.SpawnType == SpawnPointType.Job && - (args.Job == null || spawnPoint.Job?.ID == args.Job.Prototype)) + (args.Job == null || spawnPoint.Job == args.Job.Prototype)) { possiblePositions.Add(xform.Coordinates); } diff --git a/Content.Server/Station/Components/StationJobsComponent.cs b/Content.Server/Station/Components/StationJobsComponent.cs index 74399bf412..3681ec9674 100644 --- a/Content.Server/Station/Components/StationJobsComponent.cs +++ b/Content.Server/Station/Components/StationJobsComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.Station.Systems; +using System.Linq; +using Content.Server.Station.Systems; using Content.Shared.Roles; using JetBrains.Annotations; using Robust.Shared.Network; @@ -14,25 +15,21 @@ namespace Content.Server.Station.Components; [RegisterComponent, Access(typeof(StationJobsSystem)), PublicAPI] public sealed partial class StationJobsComponent : Component { - /// - /// Total *round-start* jobs at station start. - /// - [DataField("roundStartTotalJobs")] public int RoundStartTotalJobs; - /// /// Total *mid-round* jobs at station start. + /// This is inferred automatically from . /// - [DataField("midRoundTotalJobs")] public int MidRoundTotalJobs; + [ViewVariables] public int MidRoundTotalJobs; /// /// Current total jobs. /// - [DataField("totalJobs")] public int TotalJobs; + [DataField] public int TotalJobs; /// /// Station is running on extended access. /// - [DataField("extendedAccess")] public bool ExtendedAccess; + [DataField] public bool ExtendedAccess; /// /// If there are less than or equal this amount of players in the game at round start, @@ -41,7 +38,7 @@ public sealed partial class StationJobsComponent : Component /// /// Set to -1 to disable extended access. /// - [DataField("extendedAccessThreshold")] + [DataField] public int ExtendedAccessThreshold { get; set; } = 15; /// @@ -54,28 +51,20 @@ public sealed partial class StationJobsComponent : Component public float? PercentJobsRemaining => MidRoundTotalJobs > 0 ? TotalJobs / (float) MidRoundTotalJobs : null; /// - /// The current list of jobs. + /// The current list of jobs of available jobs. Null implies that is no limit. /// /// /// This should not be mutated or used directly unless you really know what you're doing, go through StationJobsSystem. /// - [DataField("jobList", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] - public Dictionary JobList = new(); - - /// - /// The round-start list of jobs. - /// - /// - /// This should not be mutated, ever. - /// - [DataField("roundStartJobList", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] - public Dictionary RoundStartJobList = new(); + [DataField] + public Dictionary, int?> JobList = new(); /// /// Overflow jobs that round-start can spawn infinitely many of. + /// This is inferred automatically from . /// - [DataField("overflowJobs", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet OverflowJobs = new(); + [ViewVariables] + public IReadOnlySet> OverflowJobs = default!; /// /// A dictionary relating a NetUserId to the jobs they have on station. @@ -84,7 +73,10 @@ public sealed partial class StationJobsComponent : Component [DataField] public Dictionary>> PlayerJobs = new(); - [DataField("availableJobs", required: true, - customTypeSerializer: typeof(PrototypeIdDictionarySerializer, JobPrototype>))] - public Dictionary> SetupAvailableJobs = default!; + /// + /// Mapping of jobs to an int[2] array that specifies jobs available at round start, and midround. + /// Negative values implies that there is no limit. + /// + [DataField("availableJobs", required: true)] + public Dictionary, int[]> SetupAvailableJobs = default!; } diff --git a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs index c3c3865c7b..e145e233e9 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs @@ -52,23 +52,23 @@ public sealed partial class StationJobsSystem /// as there may end up being more round-start slots than available slots, which can cause weird behavior. /// A warning to all who enter ye cursed lands: This function is long and mildly incomprehensible. Best used without touching. /// - public Dictionary AssignJobs(Dictionary profiles, IReadOnlyList stations, bool useRoundStartJobs = true) + public Dictionary?, EntityUid)> AssignJobs(Dictionary profiles, IReadOnlyList stations, bool useRoundStartJobs = true) { DebugTools.Assert(stations.Count > 0); InitializeRoundStart(); if (profiles.Count == 0) - return new Dictionary(); + return new(); // We need to modify this collection later, so make a copy of it. profiles = profiles.ShallowClone(); // Player <-> (job, station) - var assigned = new Dictionary(profiles.Count); + var assigned = new Dictionary?, EntityUid)>(profiles.Count); // The jobs left on the stations. This collection is modified as jobs are assigned to track what's available. - var stationJobs = new Dictionary>(); + var stationJobs = new Dictionary, int?>>(); foreach (var station in stations) { if (useRoundStartJobs) @@ -83,15 +83,15 @@ public sealed partial class StationJobsSystem // We reuse this collection. It tracks what jobs we're currently trying to select players for. - var currentlySelectingJobs = new Dictionary>(stations.Count); + var currentlySelectingJobs = new Dictionary, int?>>(stations.Count); foreach (var station in stations) { - currentlySelectingJobs.Add(station, new Dictionary()); + currentlySelectingJobs.Add(station, new Dictionary, int?>()); } // And these. // Tracks what players are available for a given job in the current iteration of selection. - var jobPlayerOptions = new Dictionary>(); + var jobPlayerOptions = new Dictionary, HashSet>(); // Tracks the total number of slots for the given stations in the current iteration of selection. var stationTotalSlots = new Dictionary(stations.Count); // The share of the players each station gets in the current iteration of job selection. @@ -112,7 +112,7 @@ public sealed partial class StationJobsSystem var optionsRemaining = 0; // Assigns a player to the given station, updating all the bookkeeping while at it. - void AssignPlayer(NetUserId player, string job, EntityUid station) + void AssignPlayer(NetUserId player, ProtoId job, EntityUid station) { // Remove the player from all possible jobs as that's faster than actually checking what they have selected. foreach (var (k, players) in jobPlayerOptions) @@ -273,8 +273,11 @@ public sealed partial class StationJobsSystem /// All players that might need an overflow assigned. /// Player character profiles. /// The stations to consider for spawn location. - public void AssignOverflowJobs(ref Dictionary assignedJobs, - IEnumerable allPlayersToAssign, IReadOnlyDictionary profiles, IReadOnlyList stations) + public void AssignOverflowJobs( + ref Dictionary?, EntityUid)> assignedJobs, + IEnumerable allPlayersToAssign, + IReadOnlyDictionary profiles, + IReadOnlyList stations) { var givenStations = stations.ToList(); if (givenStations.Count == 0) diff --git a/Content.Server/Station/Systems/StationJobsSystem.cs b/Content.Server/Station/Systems/StationJobsSystem.cs index 3bfa815af1..307610d136 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.cs @@ -3,6 +3,7 @@ using System.Linq; using Content.Server.GameTicking; using Content.Server.Station.Components; using Content.Shared.CCVar; +using Content.Shared.FixedPoint; using Content.Shared.GameTicking; using Content.Shared.Preferences; using Content.Shared.Roles; @@ -31,12 +32,25 @@ public sealed partial class StationJobsSystem : EntitySystem public override void Initialize() { SubscribeLocalEvent(OnStationInitialized); + SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnStationRenamed); SubscribeLocalEvent(OnStationDeletion); SubscribeLocalEvent(OnPlayerJoinedLobby); Subs.CVar(_configurationManager, CCVars.GameDisallowLateJoins, _ => UpdateJobsAvailable(), true); } + private void OnInit(Entity ent, ref ComponentInit args) + { + ent.Comp.MidRoundTotalJobs = ent.Comp.SetupAvailableJobs.Values + .Select(x => Math.Max(x[1], 0)) + .Sum(); + + ent.Comp.OverflowJobs = ent.Comp.SetupAvailableJobs + .Where(x => x.Value[0] < 0) + .Select(x => x.Key) + .ToHashSet(); + } + public override void Update(float _) { if (_availableJobsDirty) @@ -57,28 +71,11 @@ public sealed partial class StationJobsSystem : EntitySystem if (!TryComp(msg.Station, out var stationJobs)) return; - var mapJobList = stationJobs.SetupAvailableJobs; + stationJobs.JobList = stationJobs.SetupAvailableJobs.ToDictionary( + x => x.Key, + x=> (int?)(x.Value[1] < 0 ? null : x.Value[1])); - stationJobs.RoundStartTotalJobs = mapJobList.Values.Where(x => x[0] is not null && x[0] > 0).Sum(x => x[0]!.Value); - stationJobs.MidRoundTotalJobs = mapJobList.Values.Where(x => x[1] is not null && x[1] > 0).Sum(x => x[1]!.Value); - - stationJobs.TotalJobs = stationJobs.MidRoundTotalJobs; - - stationJobs.JobList = mapJobList.ToDictionary(x => x.Key, x => - { - if (x.Value[1] <= -1) - return null; - return (uint?) x.Value[1]; - }); - - stationJobs.RoundStartJobList = mapJobList.ToDictionary(x => x.Key, x => - { - if (x.Value[0] <= -1) - return null; - return (uint?) x.Value[0]; - }); - - stationJobs.OverflowJobs = stationJobs.OverflowJobs.ToHashSet(); + stationJobs.TotalJobs = stationJobs.JobList.Values.Select(x => x ?? 0).Sum(); UpdateJobsAvailable(); } @@ -141,7 +138,11 @@ public sealed partial class StationJobsSystem : EntitySystem /// Resolve pattern, station jobs component of the station. /// Whether or not slot adjustment was a success. /// Thrown when the given station is not a station. - public bool TryAdjustJobSlot(EntityUid station, string jobPrototypeId, int amount, bool createSlot = false, bool clamp = false, + public bool TryAdjustJobSlot(EntityUid station, + string jobPrototypeId, + int amount, + bool createSlot = false, + bool clamp = false, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) @@ -156,7 +157,11 @@ public sealed partial class StationJobsSystem : EntitySystem // - Return false when you remove from a job that doesn't exist. // - Return false when you remove and exceed the number of slots available. // And additionally, if adding would add a job not previously on the manifest when createSlot is false, return false and do nothing. - switch (jobList.ContainsKey(jobPrototypeId)) + + if (amount == 0) + return true; + + switch (jobList.TryGetValue(jobPrototypeId, out var available)) { case false when amount < 0: return false; @@ -164,31 +169,20 @@ public sealed partial class StationJobsSystem : EntitySystem if (!createSlot) return false; stationJobs.TotalJobs += amount; - jobList[jobPrototypeId] = (uint?)amount; + jobList[jobPrototypeId] = amount; UpdateJobsAvailable(); return true; case true: // Job is unlimited so just say we adjusted it and do nothing. - if (jobList[jobPrototypeId] == null) + if (available is not {} avail) return true; // Would remove more jobs than we have available. - if (amount < 0 && (jobList[jobPrototypeId] + amount < 0 && !clamp)) + if (available + amount < 0 && !clamp) return false; - stationJobs.TotalJobs += amount; - - //C# type handling moment - if (amount > 0) - jobList[jobPrototypeId] += (uint)amount; - else - { - if ((int)jobList[jobPrototypeId]!.Value - Math.Abs(amount) <= 0) - jobList[jobPrototypeId] = 0; - else - jobList[jobPrototypeId] -= (uint) Math.Abs(amount); - } - + jobList[jobPrototypeId] = Math.Max(avail + amount, 0); + stationJobs.TotalJobs = jobList.Values.Select(x => x ?? 0).Sum(); UpdateJobsAvailable(); return true; } @@ -239,7 +233,10 @@ public sealed partial class StationJobsSystem : EntitySystem /// Resolve pattern, station jobs component of the station. /// Whether or not setting the value succeeded. /// Thrown when the given station is not a station. - public bool TrySetJobSlot(EntityUid station, string jobPrototypeId, int amount, bool createSlot = false, + public bool TrySetJobSlot(EntityUid station, + string jobPrototypeId, + int amount, + bool createSlot = false, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) @@ -255,13 +252,13 @@ public sealed partial class StationJobsSystem : EntitySystem if (!createSlot) return false; stationJobs.TotalJobs += amount; - jobList[jobPrototypeId] = (uint?)amount; + jobList[jobPrototypeId] = amount; UpdateJobsAvailable(); return true; case true: - stationJobs.TotalJobs += amount - (int) (jobList[jobPrototypeId] ?? 0); + stationJobs.TotalJobs += amount - (jobList[jobPrototypeId] ?? 0); - jobList[jobPrototypeId] = (uint)amount; + jobList[jobPrototypeId] = amount; UpdateJobsAvailable(); return true; } @@ -289,8 +286,8 @@ public sealed partial class StationJobsSystem : EntitySystem throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); // Subtract out the job we're fixing to make have unlimited slots. - if (stationJobs.JobList.ContainsKey(jobPrototypeId) && stationJobs.JobList[jobPrototypeId] != null) - stationJobs.TotalJobs -= (int)stationJobs.JobList[jobPrototypeId]!.Value; + if (stationJobs.JobList.TryGetValue(jobPrototypeId, out var existing)) + stationJobs.TotalJobs -= existing ?? 0; stationJobs.JobList[jobPrototypeId] = null; @@ -319,8 +316,7 @@ public sealed partial class StationJobsSystem : EntitySystem if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); - var res = stationJobs.JobList.TryGetValue(jobPrototypeId, out var job) && job == null; - return res; + return stationJobs.JobList.TryGetValue(jobPrototypeId, out var job) && job == null; } /// @@ -328,7 +324,7 @@ public sealed partial class StationJobsSystem : EntitySystem /// Job to get slot info for. /// The number of slots remaining. Null if infinite. /// Resolve pattern, station jobs component of the station. - public bool TryGetJobSlot(EntityUid station, JobPrototype job, out uint? slots, StationJobsComponent? stationJobs = null) + public bool TryGetJobSlot(EntityUid station, JobPrototype job, out int? slots, StationJobsComponent? stationJobs = null) { return TryGetJobSlot(station, job.ID, out slots, stationJobs); } @@ -343,21 +339,12 @@ public sealed partial class StationJobsSystem : EntitySystem /// Whether or not the slot exists. /// Thrown when the given station is not a station. /// slots will be null if the slot doesn't exist, as well, so make sure to check the return value. - public bool TryGetJobSlot(EntityUid station, string jobPrototypeId, out uint? slots, StationJobsComponent? stationJobs = null) + public bool TryGetJobSlot(EntityUid station, string jobPrototypeId, out int? slots, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); - if (stationJobs.JobList.TryGetValue(jobPrototypeId, out var job)) - { - slots = job; - return true; - } - else // Else if slot isn't present return null. - { - slots = null; - return false; - } + return stationJobs.JobList.TryGetValue(jobPrototypeId, out slots); } /// @@ -367,12 +354,14 @@ public sealed partial class StationJobsSystem : EntitySystem /// Resolve pattern, station jobs component of the station. /// Set containing all jobs available. /// Thrown when the given station is not a station. - public IReadOnlySet GetAvailableJobs(EntityUid station, StationJobsComponent? stationJobs = null) + public IEnumerable> GetAvailableJobs(EntityUid station, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); - return stationJobs.JobList.Where(x => x.Value != 0).Select(x => x.Key).ToHashSet(); + return stationJobs.JobList + .Where(x => x.Value != 0) + .Select(x => x.Key); } /// @@ -382,12 +371,12 @@ public sealed partial class StationJobsSystem : EntitySystem /// Resolve pattern, station jobs component of the station. /// Set containing all overflow jobs available. /// Thrown when the given station is not a station. - public IReadOnlySet GetOverflowJobs(EntityUid station, StationJobsComponent? stationJobs = null) + public IReadOnlySet> GetOverflowJobs(EntityUid station, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); - return stationJobs.OverflowJobs.ToHashSet(); + return stationJobs.OverflowJobs; } /// @@ -397,7 +386,7 @@ public sealed partial class StationJobsSystem : EntitySystem /// Resolve pattern, station jobs component of the station. /// List of all jobs on the station. /// Thrown when the given station is not a station. - public IReadOnlyDictionary GetJobs(EntityUid station, StationJobsComponent? stationJobs = null) + public IReadOnlyDictionary, int?> GetJobs(EntityUid station, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); @@ -412,12 +401,14 @@ public sealed partial class StationJobsSystem : EntitySystem /// Resolve pattern, station jobs component of the station. /// List of all round-start jobs. /// Thrown when the given station is not a station. - public IReadOnlyDictionary GetRoundStartJobs(EntityUid station, StationJobsComponent? stationJobs = null) + public Dictionary, int?> GetRoundStartJobs(EntityUid station, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); - return stationJobs.RoundStartJobList; + return stationJobs.SetupAvailableJobs.ToDictionary( + x => x.Key, + x=> (int?)(x.Value[0] < 0 ? null : x.Value[0])); } /// @@ -428,13 +419,13 @@ public sealed partial class StationJobsSystem : EntitySystem /// Whether or not to pick from the overflow list. /// A set of disallowed jobs, if any. /// The selected job, if any. - public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary jobPriorities, bool pickOverflows, IReadOnlySet>? disallowedJobs = null) + public ProtoId? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary, JobPriority> jobPriorities, bool pickOverflows, IReadOnlySet>? disallowedJobs = null) { if (station == EntityUid.Invalid) return null; var available = GetAvailableJobs(station); - bool TryPick(JobPriority priority, [NotNullWhen(true)] out string? jobId) + bool TryPick(JobPriority priority, [NotNullWhen(true)] out ProtoId? jobId) { var filtered = jobPriorities .Where(p => @@ -474,7 +465,10 @@ public sealed partial class StationJobsSystem : EntitySystem return null; var overflows = GetOverflowJobs(station); - return overflows.Count != 0 ? _random.Pick(overflows) : null; + if (overflows.Count == 0) + return null; + + return _random.Pick(overflows); } #endregion Public API @@ -483,7 +477,7 @@ public sealed partial class StationJobsSystem : EntitySystem private bool _availableJobsDirty; - private TickerJobsAvailableEvent _cachedAvailableJobs = new (new Dictionary(), new Dictionary>()); + private TickerJobsAvailableEvent _cachedAvailableJobs = new(new(), new()); /// /// Assembles an event from the current available-to-play jobs. @@ -494,9 +488,9 @@ public sealed partial class StationJobsSystem : EntitySystem { // If late join is disallowed, return no available jobs. if (_gameTicker.DisallowLateJoin) - return new TickerJobsAvailableEvent(new Dictionary(), new Dictionary>()); + return new TickerJobsAvailableEvent(new(), new()); - var jobs = new Dictionary>(); + var jobs = new Dictionary, int?>>(); var stationNames = new Dictionary(); var query = EntityQueryEnumerator(); diff --git a/Content.Shared/GameTicking/SharedGameTicker.cs b/Content.Shared/GameTicking/SharedGameTicker.cs index 95da4f4c38..308476baa8 100644 --- a/Content.Shared/GameTicking/SharedGameTicker.cs +++ b/Content.Shared/GameTicking/SharedGameTicker.cs @@ -1,5 +1,6 @@ using Content.Shared.Roles; using Robust.Shared.Network; +using Robust.Shared.Prototypes; using Robust.Shared.Replays; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Markdown.Mapping; @@ -128,19 +129,17 @@ namespace Content.Shared.GameTicking } [Serializable, NetSerializable] - public sealed class TickerJobsAvailableEvent : EntityEventArgs + public sealed class TickerJobsAvailableEvent( + Dictionary stationNames, + Dictionary, int?>> jobsAvailableByStation) + : EntityEventArgs { /// /// The Status of the Player in the lobby (ready, observer, ...) /// - public Dictionary> JobsAvailableByStation { get; } - public Dictionary StationNames { get; } + public Dictionary, int?>> JobsAvailableByStation { get; } = jobsAvailableByStation; - public TickerJobsAvailableEvent(Dictionary stationNames, Dictionary> jobsAvailableByStation) - { - StationNames = stationNames; - JobsAvailableByStation = jobsAvailableByStation; - } + public Dictionary StationNames { get; } = stationNames; } [Serializable, NetSerializable, DataDefinition] diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index bd55bbb40a..20c54cd268 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -35,7 +35,7 @@ namespace Content.Shared.Preferences /// Job preferences for initial spawn. /// [DataField] - private Dictionary _jobPriorities = new() + private Dictionary, JobPriority> _jobPriorities = new() { { SharedGameTicker.FallbackOverflowJob, JobPriority.High @@ -46,13 +46,13 @@ namespace Content.Shared.Preferences /// Antags we have opted in to. /// [DataField] - private HashSet _antagPreferences = new(); + private HashSet> _antagPreferences = new(); /// /// Enabled traits. /// [DataField] - private HashSet _traitPreferences = new(); + private HashSet> _traitPreferences = new(); /// /// @@ -75,7 +75,7 @@ namespace Content.Shared.Preferences /// Associated for this profile. /// [DataField] - public string Species { get; set; } = SharedHumanoidAppearanceSystem.DefaultSpecies; + public ProtoId Species { get; set; } = SharedHumanoidAppearanceSystem.DefaultSpecies; [DataField] public int Age { get; set; } = 18; @@ -106,17 +106,17 @@ namespace Content.Shared.Preferences /// /// /// - public IReadOnlyDictionary JobPriorities => _jobPriorities; + public IReadOnlyDictionary, JobPriority> JobPriorities => _jobPriorities; /// /// /// - public IReadOnlySet AntagPreferences => _antagPreferences; + public IReadOnlySet> AntagPreferences => _antagPreferences; /// /// /// - public IReadOnlySet TraitPreferences => _traitPreferences; + public IReadOnlySet> TraitPreferences => _traitPreferences; /// /// If we're unable to get one of our preferred jobs do we spawn as a fallback job or do we stay in lobby. @@ -134,10 +134,10 @@ namespace Content.Shared.Preferences Gender gender, HumanoidCharacterAppearance appearance, SpawnPriorityPreference spawnPriority, - Dictionary jobPriorities, + Dictionary, JobPriority> jobPriorities, PreferenceUnavailableMode preferenceUnavailable, - HashSet antagPreferences, - HashSet traitPreferences, + HashSet> antagPreferences, + HashSet> traitPreferences, Dictionary loadouts) { Name = name; @@ -153,6 +153,20 @@ namespace Content.Shared.Preferences _antagPreferences = antagPreferences; _traitPreferences = traitPreferences; _loadouts = loadouts; + + var hasHighPrority = false; + foreach (var (key, value) in _jobPriorities) + { + if (value == JobPriority.Never) + _jobPriorities.Remove(key); + else if (value != JobPriority.High) + continue; + + if (hasHighPrority) + _jobPriorities[key] = JobPriority.Medium; + + hasHighPrority = true; + } } /// Copy constructor @@ -165,10 +179,10 @@ namespace Content.Shared.Preferences other.Gender, other.Appearance.Clone(), other.SpawnPriority, - new Dictionary(other.JobPriorities), + new Dictionary, JobPriority>(other.JobPriorities), other.PreferenceUnavailable, - new HashSet(other.AntagPreferences), - new HashSet(other.TraitPreferences), + new HashSet>(other.AntagPreferences), + new HashSet>(other.TraitPreferences), new Dictionary(other.Loadouts)) { } @@ -289,21 +303,48 @@ namespace Content.Shared.Preferences return new(this) { SpawnPriority = spawnPriority }; } - public HumanoidCharacterProfile WithJobPriorities(IEnumerable> jobPriorities) + public HumanoidCharacterProfile WithJobPriorities(IEnumerable, JobPriority>> jobPriorities) { + var dictionary = new Dictionary, JobPriority>(jobPriorities); + var hasHighPrority = false; + + foreach (var (key, value) in dictionary) + { + if (value == JobPriority.Never) + dictionary.Remove(key); + else if (value != JobPriority.High) + continue; + + if (hasHighPrority) + dictionary[key] = JobPriority.Medium; + + hasHighPrority = true; + } + return new(this) { - _jobPriorities = new Dictionary(jobPriorities), + _jobPriorities = dictionary }; } - public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority) + public HumanoidCharacterProfile WithJobPriority(ProtoId jobId, JobPriority priority) { - var dictionary = new Dictionary(_jobPriorities); + var dictionary = new Dictionary, JobPriority>(_jobPriorities); if (priority == JobPriority.Never) { dictionary.Remove(jobId); } + else if (priority == JobPriority.High) + { + // There can only ever be one high priority job. + foreach (var (job, value) in dictionary) + { + if (value == JobPriority.High) + dictionary[job] = JobPriority.Medium; + } + + dictionary[jobId] = priority; + } else { dictionary[jobId] = priority; @@ -320,17 +361,17 @@ namespace Content.Shared.Preferences return new(this) { PreferenceUnavailable = mode }; } - public HumanoidCharacterProfile WithAntagPreferences(IEnumerable antagPreferences) + public HumanoidCharacterProfile WithAntagPreferences(IEnumerable> antagPreferences) { return new(this) { - _antagPreferences = new HashSet(antagPreferences), + _antagPreferences = new (antagPreferences), }; } - public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref) + public HumanoidCharacterProfile WithAntagPreference(ProtoId antagId, bool pref) { - var list = new HashSet(_antagPreferences); + var list = new HashSet>(_antagPreferences); if (pref) { list.Add(antagId); @@ -346,16 +387,16 @@ namespace Content.Shared.Preferences }; } - public HumanoidCharacterProfile WithTraitPreference(string traitId, string? categoryId, bool pref) + public HumanoidCharacterProfile WithTraitPreference(ProtoId traitId, string? categoryId, bool pref) { var prototypeManager = IoCManager.Resolve(); - var traitProto = prototypeManager.Index(traitId); + var traitProto = prototypeManager.Index(traitId); TraitCategoryPrototype? categoryProto = null; if (categoryId != null && categoryId != "default") categoryProto = prototypeManager.Index(categoryId); - var list = new HashSet(_traitPreferences); + var list = new HashSet>(_traitPreferences); if (pref) { @@ -372,7 +413,7 @@ namespace Content.Shared.Preferences var count = 0; foreach (var trait in list) { - var traitProtoTemp = prototypeManager.Index(trait); + var traitProtoTemp = prototypeManager.Index(trait); count += traitProtoTemp.Cost; } @@ -514,7 +555,7 @@ namespace Content.Shared.Preferences _ => SpawnPriorityPreference.None // Invalid enum values. }; - var priorities = new Dictionary(JobPriorities + var priorities = new Dictionary, JobPriority>(JobPriorities .Where(p => prototypeManager.TryIndex(p.Key, out var job) && job.SetPreference && p.Value switch { JobPriority.Never => false, // Drop never since that's assumed default. @@ -524,6 +565,17 @@ namespace Content.Shared.Preferences _ => false })); + var hasHighPrio = false; + foreach (var (key, value) in priorities) + { + if (value != JobPriority.High) + continue; + + if (hasHighPrio) + priorities[key] = JobPriority.Medium; + hasHighPrio = true; + } + var antags = AntagPreferences .Where(id => prototypeManager.TryIndex(id, out var antag) && antag.SetPreference) .ToList(); diff --git a/Content.Shared/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index 0b3bfb4438..dd67e7b104 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -63,8 +63,8 @@ namespace Content.Shared.Roles public bool CanBeAntag { get; private set; } = true; /// - /// Whether this job is a head. - /// The job system will try to pick heads before other jobs on the same priority level. + /// The "weight" or importance of this job. If this number is large, the job system will assign this job + /// before assigning other jobs. /// [DataField("weight")] public int Weight { get; private set; } diff --git a/Content.Shared/Roles/Jobs/SharedJobSystem.cs b/Content.Shared/Roles/Jobs/SharedJobSystem.cs index fcf7605278..ce4428d9fe 100644 --- a/Content.Shared/Roles/Jobs/SharedJobSystem.cs +++ b/Content.Shared/Roles/Jobs/SharedJobSystem.cs @@ -118,6 +118,18 @@ public abstract class SharedJobSystem : EntitySystem _prototypes.TryIndex(comp.Prototype, out prototype); } + public bool MindTryGetJobId([NotNullWhen(true)] EntityUid? mindId, out ProtoId? job) + { + if (!TryComp(mindId, out JobComponent? comp)) + { + job = null; + return false; + } + + job = comp.Prototype; + return true; + } + /// /// Tries to get the job name for this mind. /// Returns unknown if not found. diff --git a/Resources/Prototypes/Maps/arenas.yml b/Resources/Prototypes/Maps/arenas.yml index 32f8543722..7ad7a16bc2 100644 --- a/Resources/Prototypes/Maps/arenas.yml +++ b/Resources/Prototypes/Maps/arenas.yml @@ -10,7 +10,5 @@ - type: StationNameSetup mapNameTemplate: "Meteor Arena" - type: StationJobs - overflowJobs: - - Passenger availableJobs: Passenger: [ -1, -1 ] diff --git a/Resources/Prototypes/Maps/atlas.yml b/Resources/Prototypes/Maps/atlas.yml index ef7523c727..6fe3eff030 100644 --- a/Resources/Prototypes/Maps/atlas.yml +++ b/Resources/Prototypes/Maps/atlas.yml @@ -14,8 +14,6 @@ !type:NanotrasenNameGenerator prefixCreator: 'R4' # R4407/Goon. GS isn't as cool sounding. - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/bagel.yml b/Resources/Prototypes/Maps/bagel.yml index 24ca17339f..ea06153d7e 100644 --- a/Resources/Prototypes/Maps/bagel.yml +++ b/Resources/Prototypes/Maps/bagel.yml @@ -16,8 +16,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/emergency_lox.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/box.yml b/Resources/Prototypes/Maps/box.yml index 50826fc5e0..89ba3779c6 100644 --- a/Resources/Prototypes/Maps/box.yml +++ b/Resources/Prototypes/Maps/box.yml @@ -15,8 +15,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/emergency_box.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/cluster.yml b/Resources/Prototypes/Maps/cluster.yml index fe445b0081..10a12c4f44 100644 --- a/Resources/Prototypes/Maps/cluster.yml +++ b/Resources/Prototypes/Maps/cluster.yml @@ -16,8 +16,6 @@ !type:NanotrasenNameGenerator prefixCreator: '14' - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/core.yml b/Resources/Prototypes/Maps/core.yml index 6b85aca51d..d7a15f2b1d 100644 --- a/Resources/Prototypes/Maps/core.yml +++ b/Resources/Prototypes/Maps/core.yml @@ -18,8 +18,6 @@ - type: StationCargoShuttle path: /Maps/Shuttles/cargo_core.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Bartender: [ 2, 2 ] diff --git a/Resources/Prototypes/Maps/debug.yml b/Resources/Prototypes/Maps/debug.yml index 2f475c1e57..8d4cc550a2 100644 --- a/Resources/Prototypes/Maps/debug.yml +++ b/Resources/Prototypes/Maps/debug.yml @@ -10,8 +10,6 @@ - type: StationNameSetup mapNameTemplate: "Empty" - type: StationJobs - overflowJobs: - - Passenger availableJobs: Passenger: [ -1, -1 ] @@ -27,8 +25,6 @@ - type: StationNameSetup mapNameTemplate: "Dev" - type: StationJobs - overflowJobs: - - Captain availableJobs: Captain: [ -1, -1 ] @@ -44,7 +40,5 @@ - type: StationNameSetup mapNameTemplate: "TEG" - type: StationJobs - overflowJobs: - - ChiefEngineer availableJobs: ChiefEngineer: [ -1, -1 ] diff --git a/Resources/Prototypes/Maps/europa.yml b/Resources/Prototypes/Maps/europa.yml index 0c9f1d975b..412e1b4656 100644 --- a/Resources/Prototypes/Maps/europa.yml +++ b/Resources/Prototypes/Maps/europa.yml @@ -21,8 +21,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/emergency_transit.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Bartender: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/fland.yml b/Resources/Prototypes/Maps/fland.yml index f0c35f99e5..f22c44cb63 100644 --- a/Resources/Prototypes/Maps/fland.yml +++ b/Resources/Prototypes/Maps/fland.yml @@ -17,8 +17,6 @@ - type: StationCargoShuttle path: /Maps/Shuttles/cargo_fland.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/marathon.yml b/Resources/Prototypes/Maps/marathon.yml index f82ee1d434..32ad8d576c 100644 --- a/Resources/Prototypes/Maps/marathon.yml +++ b/Resources/Prototypes/Maps/marathon.yml @@ -16,8 +16,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/emergency_rod.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/meta.yml b/Resources/Prototypes/Maps/meta.yml index 2bee606e95..ebd6954aa7 100644 --- a/Resources/Prototypes/Maps/meta.yml +++ b/Resources/Prototypes/Maps/meta.yml @@ -15,8 +15,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/emergency_meta.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/oasis.yml b/Resources/Prototypes/Maps/oasis.yml index b5e0f97190..a4cc6eb43d 100644 --- a/Resources/Prototypes/Maps/oasis.yml +++ b/Resources/Prototypes/Maps/oasis.yml @@ -15,8 +15,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/emergency_delta.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] @@ -62,4 +60,4 @@ Passenger: [ -1, -1 ] Clown: [ 1, 1 ] Mime: [ 1, 1 ] - Musician: [ 1, 1 ] \ No newline at end of file + Musician: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/omega.yml b/Resources/Prototypes/Maps/omega.yml index f90c5f5b65..b94fdbc05d 100644 --- a/Resources/Prototypes/Maps/omega.yml +++ b/Resources/Prototypes/Maps/omega.yml @@ -16,8 +16,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/emergency_omega.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/origin.yml b/Resources/Prototypes/Maps/origin.yml index 1281489891..24214b37a1 100644 --- a/Resources/Prototypes/Maps/origin.yml +++ b/Resources/Prototypes/Maps/origin.yml @@ -15,8 +15,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/packed.yml b/Resources/Prototypes/Maps/packed.yml index 20d6c7a7bd..b844636bf8 100644 --- a/Resources/Prototypes/Maps/packed.yml +++ b/Resources/Prototypes/Maps/packed.yml @@ -14,8 +14,6 @@ !type:NanotrasenNameGenerator prefixCreator: 'VG' - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/reach.yml b/Resources/Prototypes/Maps/reach.yml index 03a688cf23..a0d6752c1f 100644 --- a/Resources/Prototypes/Maps/reach.yml +++ b/Resources/Prototypes/Maps/reach.yml @@ -16,8 +16,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/emergency.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: Captain: [ 1, 1 ] HeadOfSecurity: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/saltern.yml b/Resources/Prototypes/Maps/saltern.yml index bc12eca654..e9d26ce319 100644 --- a/Resources/Prototypes/Maps/saltern.yml +++ b/Resources/Prototypes/Maps/saltern.yml @@ -15,8 +15,6 @@ !type:NanotrasenNameGenerator prefixCreator: '14' - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/train.yml b/Resources/Prototypes/Maps/train.yml index b18db7aea8..7f24fcdd67 100644 --- a/Resources/Prototypes/Maps/train.yml +++ b/Resources/Prototypes/Maps/train.yml @@ -18,8 +18,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/emergency_omega.yml # To do - add railway station - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] From f933922c9e7e561b83daea356450755e4ee830a3 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Wed, 5 Jun 2024 07:19:54 -0700 Subject: [PATCH 071/243] Add logs that provide session to player admin logs (#28628) --- Content.Server/Administration/Logs/AdminLogManager.Json.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Content.Server/Administration/Logs/AdminLogManager.Json.cs b/Content.Server/Administration/Logs/AdminLogManager.Json.cs index 0a67d61cef..9e6274a493 100644 --- a/Content.Server/Administration/Logs/AdminLogManager.Json.cs +++ b/Content.Server/Administration/Logs/AdminLogManager.Json.cs @@ -65,6 +65,10 @@ public sealed partial class AdminLogManager { players.Add(actor.PlayerSession.UserId.UserId); } + else if (value is SerializablePlayer player) + { + players.Add(player.Player.UserId.UserId); + } } return (JsonSerializer.SerializeToDocument(parsed, _jsonOptions), players); From bc360784ff90d4211999fa7dfcc52812d3de6b9b Mon Sep 17 00:00:00 2001 From: Boaz1111 <149967078+Boaz1111@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:50:37 +0200 Subject: [PATCH 072/243] Cluster Update (#28627) done here --- Resources/Maps/cluster.yml | 997 +++++++++++++++---------------------- 1 file changed, 409 insertions(+), 588 deletions(-) diff --git a/Resources/Maps/cluster.yml b/Resources/Maps/cluster.yml index b8540c6f68..a9f601114c 100644 --- a/Resources/Maps/cluster.yml +++ b/Resources/Maps/cluster.yml @@ -38,6 +38,7 @@ tilemap: 113: FloorWhiteMono 1: FloorWhitePlastic 118: FloorWood + 3: FloorWoodLarge 120: Lattice 121: Plating entities: @@ -65,7 +66,7 @@ entities: version: 6 0,-1: ind: 0,-1 - tiles: WQAAAAACWQAAAAACWQAAAAABeQAAAAAAdgAAAAADdgAAAAADdgAAAAABeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAYAAAAAAAWQAAAAADWQAAAAABeQAAAAAAdgAAAAACdgAAAAABdgAAAAADeQAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAWQAAAAAAWQAAAAABWQAAAAABeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAADYAAAAAAAWQAAAAADWQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAAAWQAAAAABWQAAAAACWQAAAAABeQAAAAAAWQAAAAAAWQAAAAACWQAAAAACYAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAWQAAAAACYAAAAAAAWQAAAAABWQAAAAADWQAAAAADWQAAAAADWQAAAAAAWQAAAAABeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAABWQAAAAAAWQAAAAACYAAAAAAAWQAAAAABWQAAAAABWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAACWQAAAAABWQAAAAAAYAAAAAAAWQAAAAABWQAAAAABeQAAAAAAeQAAAAAAZAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAAAWQAAAAADWQAAAAAATAAAAAAAWQAAAAAAWQAAAAADWQAAAAACWQAAAAABTAAAAAAAWQAAAAABWQAAAAACWQAAAAADYAAAAAAAWQAAAAADeQAAAAAAWQAAAAAAWQAAAAACYAAAAAAAWQAAAAAAWQAAAAACWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAABWQAAAAADWQAAAAAAWQAAAAABWQAAAAACWQAAAAAAWQAAAAAAZAAAAAAAWQAAAAACeQAAAAAAeQAAAAAAeQAAAAAAOgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAABYAAAAAAAWQAAAAADWQAAAAAAWQAAAAACWQAAAAAAWQAAAAABZAAAAAAAWQAAAAABJAAAAAACJAAAAAAAJAAAAAADOgAAAAAAJAAAAAAAJAAAAAADeQAAAAAAWQAAAAAAWQAAAAACWQAAAAADWQAAAAAAWQAAAAADYAAAAAAAWQAAAAADeQAAAAAAZAAAAAABJAAAAAABJAAAAAADJAAAAAABOgAAAAAAJAAAAAABJAAAAAABeQAAAAAAYAAAAAAAWQAAAAADWQAAAAAAWQAAAAACWQAAAAACWQAAAAAAWQAAAAABeQAAAAAAWQAAAAABOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAWQAAAAABTAAAAAAAWQAAAAADeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAEQAAAAAAJAAAAAAAJAAAAAAAJAAAAAAAOgAAAAAAJAAAAAAAJAAAAAADeQAAAAAAWQAAAAABYAAAAAAAWQAAAAADeQAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAeQAAAAAAeQAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAeQAAAAAAWQAAAAACWQAAAAABWQAAAAACOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAHQAAAAADHQAAAAABeQAAAAAAeQAAAAAAeQAAAAAAOgAAAAAAOgAAAAAAeQAAAAAAeQAAAAAAWQAAAAADYAAAAAAAWQAAAAAAeQAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAHQAAAAABHQAAAAAC + tiles: WQAAAAACWQAAAAACWQAAAAABeQAAAAAAAwAAAAAAAwAAAAAAAwAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAYAAAAAAAWQAAAAADWQAAAAABeQAAAAAAAwAAAAAAAwAAAAAAAwAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAWQAAAAAAWQAAAAABWQAAAAABeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAADYAAAAAAAWQAAAAADWQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAAAWQAAAAABWQAAAAACWQAAAAABeQAAAAAAWQAAAAAAWQAAAAACWQAAAAACYAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAWQAAAAACYAAAAAAAWQAAAAABWQAAAAADWQAAAAADWQAAAAADWQAAAAAAWQAAAAABeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAABWQAAAAAAWQAAAAACYAAAAAAAWQAAAAABWQAAAAABWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAACWQAAAAABWQAAAAAAYAAAAAAAWQAAAAABWQAAAAABeQAAAAAAeQAAAAAAZAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAAAWQAAAAADWQAAAAAATAAAAAAAWQAAAAAAWQAAAAADWQAAAAACWQAAAAABTAAAAAAAWQAAAAABWQAAAAACWQAAAAADYAAAAAAAWQAAAAADeQAAAAAAWQAAAAAAWQAAAAACYAAAAAAAWQAAAAAAWQAAAAACWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAABWQAAAAADWQAAAAAAWQAAAAABWQAAAAACWQAAAAAAWQAAAAAAZAAAAAAAWQAAAAACeQAAAAAAeQAAAAAAeQAAAAAAOgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAABYAAAAAAAWQAAAAADWQAAAAAAWQAAAAACWQAAAAAAWQAAAAABZAAAAAAAWQAAAAABJAAAAAACJAAAAAAAJAAAAAADOgAAAAAAJAAAAAAAJAAAAAADeQAAAAAAWQAAAAAAWQAAAAACWQAAAAADWQAAAAAAWQAAAAADYAAAAAAAWQAAAAADeQAAAAAAZAAAAAABJAAAAAABJAAAAAADJAAAAAABOgAAAAAAJAAAAAABJAAAAAABeQAAAAAAYAAAAAAAWQAAAAADWQAAAAAAWQAAAAACWQAAAAACWQAAAAAAWQAAAAABeQAAAAAAWQAAAAABOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAWQAAAAABTAAAAAAAWQAAAAADeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAEQAAAAAAJAAAAAAAJAAAAAAAJAAAAAAAOgAAAAAAJAAAAAAAJAAAAAADeQAAAAAAWQAAAAABYAAAAAAAWQAAAAADeQAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAeQAAAAAAeQAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAeQAAAAAAWQAAAAACWQAAAAABWQAAAAACOgAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAHQAAAAADHQAAAAABeQAAAAAAeQAAAAAAeQAAAAAAOgAAAAAAOgAAAAAAeQAAAAAAeQAAAAAAWQAAAAADYAAAAAAAWQAAAAAAeQAAAAAAOgAAAAAAOgAAAAAAOgAAAAAAHQAAAAABHQAAAAAC version: 6 -1,1: ind: -1,1 @@ -85,7 +86,7 @@ entities: version: 6 0,-2: ind: 0,-2 - tiles: YAAAAAAAWQAAAAACWQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAABYAAAAAAAWQAAAAAAeQAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAABWQAAAAAAYAAAAAAAeQAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAYAAAAAAAWQAAAAADWQAAAAADeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAACWQAAAAAAWQAAAAACeQAAAAAAWQAAAAAAWQAAAAACWQAAAAABeQAAAAAAWQAAAAABWQAAAAACWQAAAAACWQAAAAABWQAAAAABWQAAAAAAWQAAAAABWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAeQAAAAAAWQAAAAAAWQAAAAABWQAAAAACZAAAAAADWQAAAAADWQAAAAAAWQAAAAAAWQAAAAABYAAAAAAAWQAAAAAAWQAAAAADWQAAAAADWQAAAAAAWQAAAAACYAAAAAAAeQAAAAAAWQAAAAACWQAAAAACWQAAAAADeQAAAAAAWQAAAAACWQAAAAAAYAAAAAAAWQAAAAABWQAAAAABWQAAAAAAYAAAAAAAWQAAAAABWQAAAAABWQAAAAACWQAAAAABeQAAAAAAeQAAAAAAZAAAAAADeQAAAAAAeQAAAAAAeQAAAAAAZAAAAAACZAAAAAACeQAAAAAAeQAAAAAAWQAAAAACYAAAAAAAWQAAAAAAWQAAAAABYAAAAAAAWQAAAAACWQAAAAACYAAAAAAAWQAAAAACWQAAAAADZAAAAAADWQAAAAAAYAAAAAAAWQAAAAABWQAAAAABeQAAAAAAWQAAAAAAWQAAAAADYAAAAAAAWQAAAAADWQAAAAABWQAAAAADWQAAAAAAWQAAAAACWQAAAAABYAAAAAAAeQAAAAAAWQAAAAADWQAAAAABYAAAAAAAWQAAAAAAZAAAAAABWQAAAAABYAAAAAAAYAAAAAAAWQAAAAACWQAAAAACWQAAAAAAYAAAAAAAWQAAAAACWQAAAAABWQAAAAADZAAAAAADWQAAAAAAWQAAAAADWQAAAAACWQAAAAAAZAAAAAADYAAAAAAAWQAAAAABWQAAAAAAWQAAAAADWQAAAAACWQAAAAACWQAAAAABYAAAAAAAWQAAAAADYAAAAAAAZAAAAAAAWQAAAAADYAAAAAAAWQAAAAADWQAAAAADeQAAAAAAWQAAAAADWQAAAAABWQAAAAABYAAAAAAAWQAAAAABWQAAAAABWQAAAAABWQAAAAABWQAAAAABYAAAAAAAeQAAAAAAWQAAAAABWQAAAAABWQAAAAADWQAAAAACeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAADWQAAAAAAWQAAAAADeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAABYAAAAAAAWQAAAAADYAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAWQAAAAABWQAAAAABYAAAAAAAeQAAAAAAdgAAAAAAdgAAAAADdgAAAAABdgAAAAAAWQAAAAAAWQAAAAABWQAAAAACWQAAAAADeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAWQAAAAACWQAAAAAAWQAAAAABeQAAAAAAdgAAAAAAdgAAAAABdgAAAAACeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAA + tiles: YAAAAAAAWQAAAAACWQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAABYAAAAAAAWQAAAAAAeQAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAABWQAAAAAAYAAAAAAAeQAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAYAAAAAAAWQAAAAADWQAAAAADeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAACWQAAAAAAWQAAAAACeQAAAAAAWQAAAAAAWQAAAAACWQAAAAABeQAAAAAAWQAAAAABWQAAAAACWQAAAAACWQAAAAABWQAAAAABWQAAAAAAWQAAAAABWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAeQAAAAAAWQAAAAAAWQAAAAABWQAAAAACZAAAAAADWQAAAAADWQAAAAAAWQAAAAAAWQAAAAABYAAAAAAAWQAAAAAAWQAAAAADWQAAAAADWQAAAAAAWQAAAAACYAAAAAAAeQAAAAAAWQAAAAACWQAAAAACWQAAAAADeQAAAAAAWQAAAAACWQAAAAAAYAAAAAAAWQAAAAABWQAAAAABWQAAAAAAYAAAAAAAWQAAAAABWQAAAAABWQAAAAACWQAAAAABeQAAAAAAeQAAAAAAZAAAAAADeQAAAAAAeQAAAAAAeQAAAAAAZAAAAAACZAAAAAACeQAAAAAAeQAAAAAAWQAAAAACYAAAAAAAWQAAAAAAWQAAAAABYAAAAAAAWQAAAAACWQAAAAACYAAAAAAAWQAAAAACWQAAAAADZAAAAAADWQAAAAAAYAAAAAAAWQAAAAABWQAAAAABeQAAAAAAWQAAAAAAWQAAAAADYAAAAAAAWQAAAAADWQAAAAABWQAAAAADWQAAAAAAWQAAAAACWQAAAAABYAAAAAAAeQAAAAAAWQAAAAADWQAAAAABYAAAAAAAWQAAAAAAZAAAAAABWQAAAAABYAAAAAAAYAAAAAAAWQAAAAACWQAAAAACWQAAAAAAYAAAAAAAWQAAAAACWQAAAAABWQAAAAADZAAAAAADWQAAAAAAWQAAAAADWQAAAAACWQAAAAAAZAAAAAADYAAAAAAAWQAAAAABWQAAAAAAWQAAAAADWQAAAAACWQAAAAACWQAAAAABYAAAAAAAWQAAAAADYAAAAAAAZAAAAAAAWQAAAAADYAAAAAAAWQAAAAADWQAAAAADeQAAAAAAWQAAAAADWQAAAAABWQAAAAABYAAAAAAAWQAAAAABWQAAAAABWQAAAAABWQAAAAABWQAAAAABYAAAAAAAeQAAAAAAWQAAAAABWQAAAAABWQAAAAADWQAAAAACeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAADWQAAAAAAWQAAAAADeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAABYAAAAAAAWQAAAAADYAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAWQAAAAABWQAAAAABYAAAAAAAeQAAAAAAAwAAAAAAAwAAAAAAAwAAAAAAdgAAAAAAWQAAAAAAWQAAAAABWQAAAAACWQAAAAADeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAWQAAAAACWQAAAAAAWQAAAAABeQAAAAAAAwAAAAAAAwAAAAAAAwAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAA version: 6 -2,1: ind: -2,1 @@ -7651,11 +7652,6 @@ entities: parent: 1 - proto: Bed entities: - - uid: 1805 - components: - - type: Transform - pos: 25.5,12.5 - parent: 1 - uid: 1806 components: - type: Transform @@ -7676,6 +7672,11 @@ entities: - type: Transform pos: -20.5,-3.5 parent: 1 + - uid: 1852 + components: + - type: Transform + pos: 25.5,12.5 + parent: 1 - uid: 3482 components: - type: Transform @@ -7711,15 +7712,15 @@ entities: - type: Transform pos: 11.5,5.5 parent: 1 - - uid: 5663 + - uid: 5671 components: - type: Transform - pos: 16.5,-15.5 + pos: 20.5,-15.5 parent: 1 - - uid: 7442 + - uid: 7446 components: - type: Transform - pos: 4.5,-17.5 + pos: 6.5,-14.5 parent: 1 - uid: 7714 components: @@ -7765,11 +7766,10 @@ entities: parent: 1 - proto: BedsheetCE entities: - - uid: 5662 + - uid: 4918 components: - type: Transform - rot: 3.141592653589793 rad - pos: 16.5,-15.5 + pos: 20.5,-15.5 parent: 1 - proto: BedsheetClown entities: @@ -7829,7 +7829,7 @@ entities: parent: 1 - proto: BedsheetHOS entities: - - uid: 5373 + - uid: 1863 components: - type: Transform pos: 25.5,12.5 @@ -7877,11 +7877,10 @@ entities: parent: 1 - proto: BedsheetQM entities: - - uid: 7441 + - uid: 5395 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,-17.5 + pos: 6.5,-14.5 parent: 1 - proto: BedsheetRD entities: @@ -8242,6 +8241,11 @@ entities: parent: 1 - proto: BoxFolderBase entities: + - uid: 1805 + components: + - type: Transform + pos: -22.477488,-1.5542104 + parent: 1 - uid: 1857 components: - type: Transform @@ -8287,6 +8291,11 @@ entities: rot: 3.141592653589793 rad pos: -8.510255,48.552643 parent: 1 + - uid: 5634 + components: + - type: Transform + pos: -22.289988,-1.3667104 + parent: 1 - uid: 6055 components: - type: Transform @@ -8348,16 +8357,6 @@ entities: parent: 1 - proto: BoxFolderGrey entities: - - uid: 1883 - components: - - type: Transform - pos: -21.598257,-1.4382241 - parent: 1 - - uid: 1884 - components: - - type: Transform - pos: -21.473257,-1.3444741 - parent: 1 - uid: 6069 components: - type: Transform @@ -8385,11 +8384,6 @@ entities: - type: Transform pos: 28.678442,8.532815 parent: 1 - - uid: 5390 - components: - - type: Transform - pos: 27.618711,13.570517 - parent: 1 - uid: 5397 components: - type: Transform @@ -8407,18 +8401,28 @@ entities: parent: 1 - proto: BoxFolderWhite entities: - - uid: 1879 + - uid: 5377 components: - type: Transform - pos: -19.351341,14.63665 + pos: -19.312119,13.611187 parent: 1 - - uid: 1880 + - uid: 5655 components: - type: Transform - pos: -19.445091,14.558525 + pos: -19.593369,13.439312 parent: 1 - proto: BoxFolderYellow entities: + - uid: 1879 + components: + - type: Transform + pos: 4.418282,-17.420078 + parent: 1 + - uid: 1881 + components: + - type: Transform + pos: 4.277657,-17.638828 + parent: 1 - uid: 5095 components: - type: Transform @@ -8427,7 +8431,7 @@ entities: - uid: 5677 components: - type: Transform - pos: 18.605122,-14.452038 + pos: 17.707003,-15.576565 parent: 1 - uid: 6787 components: @@ -8469,16 +8473,6 @@ entities: - type: Transform pos: 11.422744,-18.443436 parent: 1 - - uid: 7448 - components: - - type: Transform - pos: 5.6525464,-15.315514 - parent: 1 - - uid: 7449 - components: - - type: Transform - pos: 5.4181714,-15.456139 - parent: 1 - uid: 9600 components: - type: Transform @@ -22162,7 +22156,7 @@ entities: - uid: 4981 components: - type: Transform - pos: 4.562398,31.612326 + pos: 5.6753416,30.52142 parent: 1 - proto: CarbonDioxideCanister entities: @@ -22238,30 +22232,6 @@ entities: rot: 3.141592653589793 rad pos: 26.5,13.5 parent: 1 - - uid: 5625 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 27.5,14.5 - parent: 1 - - uid: 5626 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 27.5,13.5 - parent: 1 - - uid: 5627 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 28.5,14.5 - parent: 1 - - uid: 5628 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 28.5,13.5 - parent: 1 - uid: 5629 components: - type: Transform @@ -22286,18 +22256,6 @@ entities: rot: 3.141592653589793 rad pos: 25.5,14.5 parent: 1 - - uid: 5633 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 27.5,12.5 - parent: 1 - - uid: 5634 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 28.5,12.5 - parent: 1 - uid: 5635 components: - type: Transform @@ -22404,6 +22362,11 @@ entities: parent: 1 - proto: CarpetBlue entities: + - uid: 4091 + components: + - type: Transform + pos: -20.5,15.5 + parent: 1 - uid: 5645 components: - type: Transform @@ -22452,35 +22415,10 @@ entities: rot: 3.141592653589793 rad pos: -19.5,15.5 parent: 1 - - uid: 5653 + - uid: 7444 components: - type: Transform - rot: 3.141592653589793 rad - pos: -21.5,-2.5 - parent: 1 - - uid: 5654 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -21.5,-2.5 - parent: 1 - - uid: 5655 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -21.5,-3.5 - parent: 1 - - uid: 5656 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -20.5,-2.5 - parent: 1 - - uid: 5657 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -20.5,-3.5 + pos: -20.5,16.5 parent: 1 - proto: CarpetGreen entities: @@ -22621,52 +22559,93 @@ entities: parent: 1 - proto: CarpetOrange entities: - - uid: 5658 + - uid: 1845 components: - type: Transform - rot: 3.141592653589793 rad - pos: 16.5,-14.5 + pos: 6.5,-14.5 parent: 1 - - uid: 5659 + - uid: 5415 components: - type: Transform - rot: 3.141592653589793 rad - pos: 16.5,-15.5 + pos: 19.5,-15.5 + parent: 1 + - uid: 5626 + components: + - type: Transform + pos: 4.5,-14.5 + parent: 1 + - uid: 5627 + components: + - type: Transform + pos: 5.5,-14.5 + parent: 1 + - uid: 5628 + components: + - type: Transform + pos: 5.5,-15.5 + parent: 1 + - uid: 5653 + components: + - type: Transform + pos: 6.5,-15.5 parent: 1 - uid: 5660 components: - type: Transform - rot: 3.141592653589793 rad - pos: 17.5,-14.5 + pos: 20.5,-14.5 parent: 1 - uid: 5661 components: - type: Transform - rot: 3.141592653589793 rad - pos: 17.5,-15.5 + pos: 20.5,-15.5 parent: 1 - - uid: 12419 + - uid: 5662 components: - type: Transform - pos: 4.5,-17.5 + pos: 20.5,-13.5 parent: 1 - - uid: 12420 + - uid: 5672 components: - type: Transform - pos: 4.5,-16.5 + pos: 19.5,-14.5 parent: 1 - - uid: 12421 + - uid: 5673 components: - type: Transform - pos: 5.5,-17.5 + pos: 19.5,-13.5 parent: 1 - - uid: 12422 + - uid: 7447 components: - type: Transform - pos: 5.5,-16.5 + pos: 4.5,-15.5 parent: 1 - proto: CarpetPurple entities: + - uid: 1870 + components: + - type: Transform + pos: -22.5,-2.5 + parent: 1 + - uid: 1871 + components: + - type: Transform + pos: -20.5,-2.5 + parent: 1 + - uid: 1873 + components: + - type: Transform + pos: -21.5,-3.5 + parent: 1 + - uid: 1874 + components: + - type: Transform + pos: -20.5,-3.5 + parent: 1 + - uid: 1877 + components: + - type: Transform + pos: -21.5,-2.5 + parent: 1 - uid: 5018 components: - type: Transform @@ -22682,6 +22661,11 @@ entities: - type: Transform pos: 5.5,10.5 parent: 1 + - uid: 5369 + components: + - type: Transform + pos: -22.5,-3.5 + parent: 1 - proto: CarpetSBlue entities: - uid: 5639 @@ -26173,18 +26157,18 @@ entities: - type: Transform pos: 14.5,13.5 parent: 1 - - uid: 1710 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,-16.5 - parent: 1 - uid: 1875 components: - type: Transform rot: 3.141592653589793 rad pos: -21.5,-2.5 parent: 1 + - uid: 1883 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.512032,-16.451328 + parent: 1 - uid: 2868 components: - type: Transform @@ -26342,11 +26326,10 @@ entities: rot: 3.141592653589793 rad pos: 28.5,17.5 parent: 1 - - uid: 5375 + - uid: 5393 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 28.5,13.5 + pos: 28.500902,13.478384 parent: 1 - uid: 5574 components: @@ -26360,12 +26343,6 @@ entities: rot: 3.141592653589793 rad pos: -36.5,-3.5 parent: 1 - - uid: 5673 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 17.5,-14.5 - parent: 1 - uid: 5674 components: - type: Transform @@ -26400,6 +26377,12 @@ entities: - type: Transform pos: 12.5,-26.5 parent: 1 + - uid: 7452 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 17.488253,-14.435939 + parent: 1 - uid: 8013 components: - type: Transform @@ -26449,11 +26432,6 @@ entities: parent: 1 - proto: ChairOfficeLight entities: - - uid: 1874 - components: - - type: Transform - pos: -19.5,15.5 - parent: 1 - uid: 5608 components: - type: Transform @@ -26472,6 +26450,12 @@ entities: rot: 3.141592653589793 rad pos: -8.5,6.5 parent: 1 + - uid: 5633 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -19.499619,14.564312 + parent: 1 - uid: 5921 components: - type: Transform @@ -26636,18 +26620,6 @@ entities: rot: 1.5707963267948966 rad pos: -22.5,-29.5 parent: 1 -- proto: chem_master - entities: - - uid: 5604 - components: - - type: Transform - pos: -8.5,14.5 - parent: 1 - - uid: 5605 - components: - - type: Transform - pos: -11.5,16.5 - parent: 1 - proto: ChemDispenser entities: - uid: 5606 @@ -26674,6 +26646,18 @@ entities: - type: Transform pos: -11.5,13.5 parent: 1 +- proto: ChemMaster + entities: + - uid: 5604 + components: + - type: Transform + pos: -8.5,14.5 + parent: 1 + - uid: 5605 + components: + - type: Transform + pos: -11.5,16.5 + parent: 1 - proto: ChessBoard entities: - uid: 5058 @@ -27514,6 +27498,13 @@ entities: - type: Transform pos: -13.503023,28.53958 parent: 1 +- proto: ClothingHeadHatBeretCmo + entities: + - uid: 7450 + components: + - type: Transform + pos: -20.687119,13.736187 + parent: 1 - proto: ClothingHeadHatFedoraBrown entities: - uid: 12535 @@ -27630,21 +27621,7 @@ entities: - uid: 6397 components: - type: Transform - pos: 4.585783,31.487268 - parent: 1 -- proto: ClothingNeckMantleCE - entities: - - uid: 5675 - components: - - type: Transform - pos: 18.495747,-13.358288 - parent: 1 -- proto: ClothingNeckMantleCMO - entities: - - uid: 1869 - components: - - type: Transform - pos: -20.496048,14.567978 + pos: 5.4722166,31.005795 parent: 1 - proto: ClothingNeckMantleHOP entities: @@ -27655,13 +27632,6 @@ entities: - type: Physics canCollide: False - type: InsideEntityStorage -- proto: ClothingNeckMantleRD - entities: - - uid: 1877 - components: - - type: Transform - pos: -22.452333,-1.4018333 - parent: 1 - proto: ClothingNeckScarfStripedBlue entities: - uid: 1899 @@ -27779,15 +27749,6 @@ entities: - type: Physics canCollide: False - type: InsideEntityStorage -- proto: ClothingOuterWinterCE - entities: - - uid: 5670 - components: - - type: Transform - parent: 5669 - - type: Physics - canCollide: False - - type: InsideEntityStorage - proto: ClothingOuterWinterCMO entities: - uid: 1868 @@ -27806,33 +27767,6 @@ entities: - type: Physics canCollide: False - type: InsideEntityStorage -- proto: ClothingOuterWinterHoS - entities: - - uid: 12478 - components: - - type: Transform - parent: 5374 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: ClothingOuterWinterQM - entities: - - uid: 7444 - components: - - type: Transform - parent: 7443 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: ClothingOuterWinterRD - entities: - - uid: 12418 - components: - - type: Transform - parent: 1845 - - type: Physics - canCollide: False - - type: InsideEntityStorage - proto: ClothingShoesBootsCombat entities: - uid: 8275 @@ -27914,16 +27848,17 @@ entities: parent: 1 - proto: ComfyChair entities: - - uid: 1711 - components: - - type: Transform - pos: 5.5,-14.5 - parent: 1 - - uid: 1873 + - uid: 794 components: - type: Transform rot: 3.141592653589793 rad - pos: -19.5,13.5 + pos: 26.5,12.5 + parent: 1 + - uid: 1848 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,-15.5 parent: 1 - uid: 1876 components: @@ -27968,12 +27903,6 @@ entities: rot: -1.5707963267948966 rad pos: 4.5,25.5 parent: 1 - - uid: 5377 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 26.5,13.5 - parent: 1 - uid: 5573 components: - type: Transform @@ -27986,6 +27915,11 @@ entities: rot: 3.141592653589793 rad pos: 2.5,5.5 parent: 1 + - uid: 7454 + components: + - type: Transform + pos: 19.5,-13.5 + parent: 1 - uid: 7712 components: - type: Transform @@ -28006,11 +27940,16 @@ entities: - ArtifactAnalyzerSender: ArtifactAnalyzerReceiver - proto: ComputerCargoBounty entities: - - uid: 7247 + - uid: 1880 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 16.5,-23.5 + rot: 1.5707963267948966 rad + pos: 4.5,-16.5 + parent: 1 + - uid: 5669 + components: + - type: Transform + pos: 8.5,-19.5 parent: 1 - proto: ComputerCargoOrders entities: @@ -28020,18 +27959,11 @@ entities: rot: 3.141592653589793 rad pos: 8.5,-22.5 parent: 1 - - uid: 7395 + - uid: 5366 components: - type: Transform - rot: 1.5707963267948966 rad - pos: 4.5,-14.5 - parent: 1 -- proto: ComputerCargoShuttle - entities: - - uid: 4918 - components: - - type: Transform - pos: 8.5,-19.5 + rot: 3.141592653589793 rad + pos: 5.5,-17.5 parent: 1 - proto: ComputerComms entities: @@ -28048,6 +27980,12 @@ entities: parent: 1 - proto: ComputerCrewMonitoring entities: + - uid: 1844 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -20.5,14.5 + parent: 1 - uid: 1888 components: - type: Transform @@ -28066,10 +28004,11 @@ entities: - type: Transform pos: 14.5,15.5 parent: 1 - - uid: 5366 + - uid: 1869 components: - type: Transform - pos: 25.5,14.5 + rot: 3.141592653589793 rad + pos: 28.5,12.5 parent: 1 - uid: 5370 components: @@ -28114,14 +28053,6 @@ entities: rot: -1.5707963267948966 rad pos: 1.5,19.5 parent: 1 -- proto: ComputerMedicalRecords - entities: - - uid: 1871 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -20.5,13.5 - parent: 1 - proto: ComputerPowerMonitoring entities: - uid: 4072 @@ -28129,10 +28060,11 @@ entities: - type: Transform pos: -5.5,31.5 parent: 1 - - uid: 5415 + - uid: 5665 components: - type: Transform - pos: 19.5,-13.5 + rot: 1.5707963267948966 rad + pos: 16.5,-14.5 parent: 1 - uid: 5668 components: @@ -28156,11 +28088,11 @@ entities: rot: 1.5707963267948966 rad pos: -10.5,-3.5 parent: 1 - - uid: 5365 + - uid: 5385 components: - type: Transform - rot: 1.5707963267948966 rad - pos: -22.5,-0.5 + rot: 3.141592653589793 rad + pos: -21.5,-1.5 parent: 1 - proto: ComputerRoboticsControl entities: @@ -28186,6 +28118,14 @@ entities: rot: 3.141592653589793 rad pos: 12.5,-27.5 parent: 1 +- proto: ComputerShuttleSalvage + entities: + - uid: 5666 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 16.5,-23.5 + parent: 1 - proto: ComputerSolarControl entities: - uid: 2557 @@ -28244,6 +28184,12 @@ entities: rot: 3.141592653589793 rad pos: 14.5,12.5 parent: 1 + - uid: 1884 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 27.5,13.5 + parent: 1 - uid: 3794 components: - type: Transform @@ -28266,11 +28212,6 @@ entities: rot: 1.5707963267948966 rad pos: 25.5,6.5 parent: 1 - - uid: 5369 - components: - - type: Transform - pos: 26.5,14.5 - parent: 1 - proto: ComputerTechnologyDiskTerminal entities: - uid: 3822 @@ -28822,6 +28763,39 @@ entities: - type: Transform pos: -41.5,-23.5 parent: 1 +- proto: CurtainsBlueOpen + entities: + - uid: 5391 + components: + - type: Transform + pos: -18.5,16.5 + parent: 1 +- proto: CurtainsOrangeOpen + entities: + - uid: 4933 + components: + - type: Transform + pos: 20.5,-15.5 + parent: 1 + - uid: 5625 + components: + - type: Transform + pos: 6.5,-14.5 + parent: 1 +- proto: CurtainsPurpleOpen + entities: + - uid: 5390 + components: + - type: Transform + pos: -20.5,-3.5 + parent: 1 +- proto: CurtainsRedOpen + entities: + - uid: 5394 + components: + - type: Transform + pos: 25.5,12.5 + parent: 1 - proto: d20Dice entities: - uid: 5591 @@ -32502,6 +32476,48 @@ entities: - type: Transform pos: 5.5,29.5 parent: 1 +- proto: DresserCaptainFilled + entities: + - uid: 7445 + components: + - type: Transform + pos: 4.5,31.5 + parent: 1 +- proto: DresserChiefEngineerFilled + entities: + - uid: 5659 + components: + - type: Transform + pos: 20.5,-14.5 + parent: 1 +- proto: DresserChiefMedicalOfficerFilled + entities: + - uid: 5657 + components: + - type: Transform + pos: -19.5,16.5 + parent: 1 +- proto: DresserHeadOfSecurityFilled + entities: + - uid: 6044 + components: + - type: Transform + pos: 25.5,13.5 + parent: 1 +- proto: DresserQuarterMasterFilled + entities: + - uid: 5373 + components: + - type: Transform + pos: 5.5,-14.5 + parent: 1 +- proto: DresserResearchDirectorFilled + entities: + - uid: 7442 + components: + - type: Transform + pos: -21.5,-3.5 + parent: 1 - proto: Drill entities: - uid: 9639 @@ -38592,10 +38608,14 @@ entities: - uid: 8794 components: - type: Transform + anchored: False pos: -14.5,-45.5 parent: 1 - type: AtmosPipeColor color: '#990000FF' + - type: Physics + canCollide: True + bodyType: Dynamic - uid: 10091 components: - type: Transform @@ -38830,11 +38850,15 @@ entities: - uid: 10130 components: - type: Transform + anchored: False rot: 1.5707963267948966 rad pos: 21.5,-11.5 parent: 1 - type: AtmosPipeColor color: '#990000FF' + - type: Physics + canCollide: True + bodyType: Dynamic - uid: 10131 components: - type: Transform @@ -43075,13 +43099,6 @@ entities: parent: 1 - type: AtmosPipeColor color: '#0055CCFF' - - uid: 10974 - components: - - type: Transform - pos: -4.5,8.5 - parent: 1 - - type: AtmosPipeColor - color: '#0055CCFF' - uid: 10975 components: - type: Transform @@ -44040,11 +44057,15 @@ entities: - uid: 11152 components: - type: Transform + anchored: False rot: 3.141592653589793 rad pos: -37.5,0.5 parent: 1 - type: AtmosPipeColor color: '#0055CCFF' + - type: Physics + canCollide: True + bodyType: Dynamic - uid: 11153 components: - type: Transform @@ -47036,11 +47057,15 @@ entities: - uid: 12253 components: - type: Transform + anchored: False rot: -1.5707963267948966 rad pos: -4.5,56.5 parent: 1 - type: AtmosPipeColor color: '#0055CCFF' + - type: Physics + canCollide: True + bodyType: Dynamic - uid: 12254 components: - type: Transform @@ -54986,27 +55011,11 @@ entities: parent: 1 - proto: HospitalCurtainsOpen entities: - - uid: 794 - components: - - type: Transform - pos: 25.5,12.5 - parent: 1 - uid: 1804 components: - type: Transform pos: 28.5,5.5 parent: 1 - - uid: 1843 - components: - - type: Transform - pos: -20.5,-3.5 - parent: 1 - - uid: 1852 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -18.5,16.5 - parent: 1 - uid: 5371 components: - type: Transform @@ -55023,18 +55032,6 @@ entities: rot: -1.5707963267948966 rad pos: 27.5,16.5 parent: 1 - - uid: 5672 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 16.5,-15.5 - parent: 1 - - uid: 7440 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,-17.5 - parent: 1 - uid: 7718 components: - type: Transform @@ -55745,40 +55742,11 @@ entities: parent: 1 - proto: LockerChiefEngineerFilled entities: - - uid: 5669 + - uid: 5658 components: - type: Transform - pos: 20.5,-15.5 + pos: 20.5,-13.5 parent: 1 - - type: EntityStorage - air: - volume: 200 - immutable: False - temperature: 93.465614 - moles: - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - type: ContainerContainer - containers: - entity_storage: !type:Container - showEnts: False - occludes: True - ents: - - 5670 - paper_label: !type:ContainerSlot - showEnts: False - occludes: True - ent: null - proto: LockerChiefMedicalOfficerFilled entities: - uid: 1867 @@ -55986,40 +55954,11 @@ entities: ent: null - proto: LockerHeadOfSecurityFilled entities: - - uid: 5374 + - uid: 7441 components: - type: Transform - pos: 29.5,12.5 + pos: 25.5,14.5 parent: 1 - - type: EntityStorage - air: - volume: 200 - immutable: False - temperature: 293.1496 - moles: - - 1.7459903 - - 6.568249 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - type: ContainerContainer - containers: - entity_storage: !type:Container - showEnts: False - occludes: True - ents: - - 12478 - paper_label: !type:ContainerSlot - showEnts: False - occludes: True - ent: null - proto: LockerMedicalFilled entities: - uid: 1908 @@ -56056,76 +55995,18 @@ entities: parent: 1 - proto: LockerQuarterMasterFilled entities: - - uid: 7443 + - uid: 5656 components: - type: Transform - pos: 4.5,-16.5 + pos: 4.5,-14.5 parent: 1 - - type: EntityStorage - air: - volume: 200 - immutable: False - temperature: 75.31249 - moles: - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - type: ContainerContainer - containers: - entity_storage: !type:Container - showEnts: False - occludes: True - ents: - - 7444 - paper_label: !type:ContainerSlot - showEnts: False - occludes: True - ent: null - proto: LockerResearchDirectorFilled entities: - - uid: 1845 + - uid: 7395 components: - type: Transform - pos: -21.5,-3.5 + pos: -22.5,-2.5 parent: 1 - - type: EntityStorage - air: - volume: 200 - immutable: False - temperature: 293.1496 - moles: - - 1.7459903 - - 6.568249 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - type: ContainerContainer - containers: - entity_storage: !type:Container - showEnts: False - occludes: True - ents: - - 12418 - paper_label: !type:ContainerSlot - showEnts: False - occludes: True - ent: null - proto: LockerSalvageSpecialistFilled entities: - uid: 3594 @@ -56329,15 +56210,15 @@ entities: parent: 1 - proto: MagazinePistolSubMachineGunTopMounted entities: - - uid: 5386 + - uid: 1865 components: - type: Transform - pos: 27.431211,14.508017 + pos: 27.625902,12.337759 parent: 1 - - uid: 5387 + - uid: 5365 components: - type: Transform - pos: 27.431211,14.367392 + pos: 27.579027,12.494009 parent: 1 - proto: MagazineRifle entities: @@ -57473,12 +57354,6 @@ entities: - type: Transform pos: 5.5,-20.5 parent: 1 - - uid: 7447 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,-15.5 - parent: 1 - uid: 7782 components: - type: Transform @@ -57540,6 +57415,23 @@ entities: - type: Transform pos: 4.593648,27.643576 parent: 1 +- proto: PaperCargoInvoice + entities: + - uid: 798 + components: + - type: Transform + pos: 4.715157,-17.670078 + parent: 1 + - uid: 1711 + components: + - type: Transform + pos: 4.777657,-17.498203 + parent: 1 + - uid: 5392 + components: + - type: Transform + pos: 4.777657,-17.310703 + parent: 1 - proto: PaperOffice entities: - uid: 3999 @@ -57682,26 +57574,6 @@ entities: - type: Transform pos: -4.5472007,19.595882 parent: 1 - - uid: 5391 - components: - - type: Transform - pos: 27.337461,13.773642 - parent: 1 - - uid: 5392 - components: - - type: Transform - pos: 27.368711,13.758017 - parent: 1 - - uid: 5393 - components: - - type: Transform - pos: 27.415586,13.711142 - parent: 1 - - uid: 5394 - components: - - type: Transform - pos: 27.478086,13.679892 - parent: 1 - uid: 5398 components: - type: Transform @@ -57725,22 +57597,12 @@ entities: - uid: 5678 components: - type: Transform - pos: 18.339497,-14.389538 - parent: 1 - - uid: 5679 - components: - - type: Transform - pos: 18.339497,-14.295788 - parent: 1 - - uid: 5680 - components: - - type: Transform - pos: 18.339497,-14.186413 + pos: 17.441378,-15.404689 parent: 1 - uid: 5681 components: - type: Transform - pos: 18.355122,-14.061413 + pos: 17.332003,-15.56094 parent: 1 - uid: 5875 components: @@ -57872,25 +57734,10 @@ entities: - type: Transform pos: 11.453994,-18.162186 parent: 1 - - uid: 7450 - components: - - type: Transform - pos: 5.1056714,-15.299889 - parent: 1 - - uid: 7451 - components: - - type: Transform - pos: 5.1056714,-15.299889 - parent: 1 - - uid: 7452 - components: - - type: Transform - pos: 5.1056714,-15.299889 - parent: 1 - uid: 7453 components: - type: Transform - pos: 5.1056714,-15.299889 + pos: 17.566378,-15.264064 parent: 1 - uid: 8247 components: @@ -58038,15 +57885,10 @@ entities: parent: 1 - proto: Pen entities: - - uid: 1881 + - uid: 1780 components: - type: Transform - pos: -19.882591,14.621025 - parent: 1 - - uid: 1882 - components: - - type: Transform - pos: -21.958109,-1.4018333 + pos: -22.696238,-1.2729604 parent: 1 - uid: 3995 components: @@ -58092,11 +57934,10 @@ entities: rot: -1.5707963267948966 rad pos: 25.773567,10.670956 parent: 1 - - uid: 5395 + - uid: 5386 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 27.587461,13.804892 + pos: -19.937119,13.673687 parent: 1 - uid: 5402 components: @@ -58119,11 +57960,10 @@ entities: - type: Transform pos: -39.229885,-5.2659664 parent: 1 - - uid: 5682 + - uid: 5675 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 18.651997,-14.061413 + pos: 17.097628,-15.217189 parent: 1 - uid: 5879 components: @@ -58151,10 +57991,10 @@ entities: - type: Transform pos: 5.313369,-21.14656 parent: 1 - - uid: 7454 + - uid: 7451 components: - type: Transform - pos: 5.6369214,-15.503014 + pos: 4.230782,-17.216953 parent: 1 - uid: 9046 components: @@ -62393,33 +62233,33 @@ entities: parent: 1 - proto: RCD entities: + - uid: 5679 + components: + - type: Transform + pos: 16.410128,-15.420314 + parent: 1 - uid: 7135 components: - type: Transform pos: 31.574986,-16.418343 parent: 1 - - uid: 12468 - components: - - type: Transform - pos: 18.51267,-13.338883 - parent: 1 - proto: RCDAmmo entities: + - uid: 5663 + components: + - type: Transform + pos: 16.878878,-15.467189 + parent: 1 + - uid: 5680 + components: + - type: Transform + pos: 17.050753,-15.467189 + parent: 1 - uid: 7136 components: - type: Transform pos: 31.27811,-16.605843 parent: 1 - - uid: 12469 - components: - - type: Transform - pos: 18.278296,-13.526383 - parent: 1 - - uid: 12470 - components: - - type: Transform - pos: 18.434546,-13.526383 - parent: 1 - proto: ReagentContainerFlour entities: - uid: 6016 @@ -64453,10 +64293,10 @@ entities: parent: 1 - proto: ResearchAndDevelopmentServer entities: - - uid: 1848 + - uid: 4973 components: - type: Transform - pos: -22.5,-3.5 + pos: -22.5,-0.5 parent: 1 - proto: RightArmBorg entities: @@ -65035,7 +64875,7 @@ entities: parent: 1 - type: DeviceLinkSink links: - - 5671 + - 5682 - uid: 5684 components: - type: Transform @@ -65043,7 +64883,7 @@ entities: parent: 1 - type: DeviceLinkSink links: - - 5671 + - 5682 - uid: 5685 components: - type: Transform @@ -65051,7 +64891,7 @@ entities: parent: 1 - type: DeviceLinkSink links: - - 5671 + - 5682 - uid: 5686 components: - type: Transform @@ -65059,15 +64899,12 @@ entities: parent: 1 - type: DeviceLinkSink links: - - 5671 + - 5682 - uid: 5687 components: - type: Transform pos: 21.5,-15.5 parent: 1 - - type: DeviceLinkSink - links: - - 5671 - uid: 5887 components: - type: Transform @@ -65455,16 +65292,14 @@ entities: - Pressed: Toggle 1866: - Pressed: Toggle - - uid: 5671 + - uid: 5682 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 21.5,-14.5 + rot: 3.141592653589793 rad + pos: 19.5,-16.5 parent: 1 - type: DeviceLinkSource linkedPorts: - 5687: - - Pressed: Toggle 5685: - Pressed: Toggle 5684: @@ -66405,7 +66240,7 @@ entities: - type: Transform pos: -43.500492,24.479343 parent: 1 -- proto: soda_dispenser +- proto: SodaDispenser entities: - uid: 5954 components: @@ -66949,10 +66784,11 @@ entities: parent: 1 - proto: SpawnMobShiva entities: - - uid: 8256 + - uid: 7440 components: - type: Transform - pos: 25.5,13.5 + rot: 3.141592653589793 rad + pos: 26.5,12.5 parent: 1 - proto: SpawnMobSmile entities: @@ -67055,10 +66891,10 @@ entities: parent: 1 - proto: SpawnPointChiefMedicalOfficer entities: - - uid: 1870 + - uid: 1843 components: - type: Transform - pos: -19.5,13.5 + pos: -19.5,14.5 parent: 1 - proto: SpawnPointClown entities: @@ -67083,10 +66919,10 @@ entities: parent: 1 - proto: SpawnPointHeadOfSecurity entities: - - uid: 6044 + - uid: 7448 components: - type: Transform - pos: 26.5,13.5 + pos: 28.5,13.5 parent: 1 - proto: SpawnPointJanitor entities: @@ -67195,10 +67031,10 @@ entities: parent: 1 - proto: SpawnPointQuartermaster entities: - - uid: 7477 + - uid: 5375 components: - type: Transform - pos: 5.5,-14.5 + pos: 6.5,-15.5 parent: 1 - proto: SpawnPointResearchAssistant entities: @@ -67671,17 +67507,17 @@ entities: parent: 1 - proto: SuitStorageCE entities: - - uid: 4933 + - uid: 7477 components: - type: Transform - pos: 20.5,-13.5 + pos: 18.5,-15.5 parent: 1 - proto: SuitStorageCMO entities: - - uid: 1865 + - uid: 5374 components: - type: Transform - pos: -20.5,15.5 + pos: -18.5,13.5 parent: 1 - proto: SuitStorageEngi entities: @@ -67736,17 +67572,17 @@ entities: parent: 1 - proto: SuitStorageHOS entities: - - uid: 4973 + - uid: 1864 components: - type: Transform - pos: 29.5,14.5 + pos: 29.5,12.5 parent: 1 - proto: SuitStorageRD entities: - - uid: 4929 + - uid: 5654 components: - type: Transform - pos: -22.5,-2.5 + pos: -22.5,-3.5 parent: 1 - proto: SuitStorageSalv entities: @@ -69595,11 +69431,6 @@ entities: rot: 1.5707963267948966 rad pos: -1.5,7.5 parent: 1 - - uid: 798 - components: - - type: Transform - pos: 27.5,13.5 - parent: 1 - uid: 1256 components: - type: Transform @@ -69624,11 +69455,6 @@ entities: rot: 1.5707963267948966 rad pos: -37.5,-4.5 parent: 1 - - uid: 1780 - components: - - type: Transform - pos: 27.5,14.5 - parent: 1 - uid: 1802 components: - type: Transform @@ -69639,22 +69465,11 @@ entities: - type: Transform pos: 28.5,16.5 parent: 1 - - uid: 1844 + - uid: 1882 components: - type: Transform - pos: -21.5,-1.5 - parent: 1 - - uid: 1863 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -20.5,14.5 - parent: 1 - - uid: 1864 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -19.5,14.5 + rot: 3.141592653589793 rad + pos: 4.5,-17.5 parent: 1 - uid: 2782 components: @@ -69749,12 +69564,6 @@ entities: rot: 3.141592653589793 rad pos: -9.5,28.5 parent: 1 - - uid: 4091 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,31.5 - parent: 1 - uid: 4092 components: - type: Transform @@ -69842,6 +69651,12 @@ entities: rot: 3.141592653589793 rad pos: 46.5,31.5 parent: 1 + - uid: 4929 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -19.5,13.5 + parent: 1 - uid: 4956 components: - type: Transform @@ -69898,17 +69713,16 @@ entities: - type: Transform pos: -22.5,-1.5 parent: 1 - - uid: 5665 + - uid: 5387 components: - type: Transform - rot: 3.141592653589793 rad - pos: 18.5,-13.5 + pos: 27.5,12.5 parent: 1 - - uid: 5666 + - uid: 5670 components: - type: Transform - rot: 3.141592653589793 rad - pos: 18.5,-14.5 + rot: 1.5707963267948966 rad + pos: 17.5,-15.5 parent: 1 - uid: 5947 components: @@ -69922,17 +69736,17 @@ entities: rot: 3.141592653589793 rad pos: -0.5,5.5 parent: 1 - - uid: 7445 + - uid: 7247 components: - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,-15.5 + rot: 1.5707963267948966 rad + pos: 16.5,-15.5 parent: 1 - - uid: 7446 + - uid: 7443 components: - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,-15.5 + rot: 1.5707963267948966 rad + pos: -20.5,13.5 parent: 1 - uid: 7710 components: @@ -70362,6 +70176,13 @@ entities: - type: Transform pos: -6.5061026,52.63185 parent: 1 +- proto: ToyFigurineChiefMedicalOfficer + entities: + - uid: 7449 + components: + - type: Transform + pos: -20.374619,13.548687 + parent: 1 - proto: ToyFireRipley entities: - uid: 8259 @@ -80010,10 +79831,10 @@ entities: parent: 1 - proto: WeaponSubMachineGunWt550 entities: - - uid: 5385 + - uid: 1710 components: - type: Transform - pos: 27.524961,14.648642 + pos: 27.516527,12.712759 parent: 1 - proto: WeaponTurretSyndicateBroken entities: From ca9830a2842b4889a20825d9d34da4c6fe891086 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 5 Jun 2024 17:51:47 +0000 Subject: [PATCH 073/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 574ea1adb3..851ebffc64 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Plykiya - changes: - - message: Lone operatives now start with 60TC instead of 40TC. - type: Tweak - id: 6183 - time: '2024-03-18T14:15:53.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26130 - author: PJB3005 changes: - message: Fixed hardsuits in space causing high pressure damage @@ -3849,3 +3842,10 @@ id: 6682 time: '2024-06-04T18:48:24.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28590 +- author: Boaz1111 + changes: + - message: Revamped Cluster's Head Offices + type: Tweak + id: 6683 + time: '2024-06-05T17:50:38.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28627 From 192a3187dfb80cbfec423a7a9757342dec08ceba Mon Sep 17 00:00:00 2001 From: Hmeister-real <118129069+Hmeister-real@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:10:27 +0100 Subject: [PATCH 074/243] minor banner changes (#28636) * minor banner changes * Uhrmm actchually it's you're, not your * Update banners.yml props Hyenh --- .../Prototypes/Entities/Structures/Decoration/banners.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Entities/Structures/Decoration/banners.yml b/Resources/Prototypes/Entities/Structures/Decoration/banners.yml index cc0c90503d..c2567814ac 100644 --- a/Resources/Prototypes/Entities/Structures/Decoration/banners.yml +++ b/Resources/Prototypes/Entities/Structures/Decoration/banners.yml @@ -91,7 +91,7 @@ id: BannerScience parent: BannerBase name: science banner - description: A banner displaying the colors of the science department. Where stupidity is proven greater than the universe. + description: A banner displaying the colors of the science department. Where science has no bounds, and regulations are rarely followed. components: - type: Sprite sprite: Structures/Decoration/banner.rsi @@ -101,7 +101,7 @@ id: BannerSecurity parent: BannerBase name: security banner - description: A banner displaying the colors of the shitcurity department. Security, my bad. + description: A banner displaying the colors of the security department. You're surprised it's not vandalised. components: - type: Sprite sprite: Structures/Decoration/banner.rsi From 4c0a878f6feccc8282d0d7acbe37de5c3e9f4528 Mon Sep 17 00:00:00 2001 From: lapatison <100279397+lapatison@users.noreply.github.com> Date: Wed, 5 Jun 2024 23:10:58 +0300 Subject: [PATCH 075/243] Revenant spell catalog locale (#28638) locale --- .../Locale/en-US/store/revenant-catalog.ftl | 11 +++++++++++ .../Prototypes/Catalog/revenant_catalog.yml | 16 ++++++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 Resources/Locale/en-US/store/revenant-catalog.ftl diff --git a/Resources/Locale/en-US/store/revenant-catalog.ftl b/Resources/Locale/en-US/store/revenant-catalog.ftl new file mode 100644 index 0000000000..d3cbbf724f --- /dev/null +++ b/Resources/Locale/en-US/store/revenant-catalog.ftl @@ -0,0 +1,11 @@ +revenant-defile-name = Defile +revenant-defile-desc = Defiles the surrounding area, ripping up floors, damaging windows, opening containers, and throwing items. Using it leaves you vulnerable to attacks for a short period of time. + +revenant-overload-name = Overload Lights +revenant-overload-desc = Overloads all nearby lights, causing lights to pulse and sending out dangerous lightning. Using it leaves you vulnerable to attacks for a long period of time. + +revenant-blight-name = Blight +revenant-blight-desc = Infects all nearby organisms with an infectious disease that causes toxic buildup and tiredness. Using it leaves you vulnerable to attacks for a medium period of time. + +revenant-malfunction-name = Malfunction +revenant-malfunction-desc = Makes nearby electronics stop working properly. Using it leaves you vulnerable to attacks for a long period of time. diff --git a/Resources/Prototypes/Catalog/revenant_catalog.yml b/Resources/Prototypes/Catalog/revenant_catalog.yml index 84f45d1607..ec1ef0a309 100644 --- a/Resources/Prototypes/Catalog/revenant_catalog.yml +++ b/Resources/Prototypes/Catalog/revenant_catalog.yml @@ -1,7 +1,7 @@ - type: listing id: RevenantDefile - name: Defile - description: Defiles the surrounding area, ripping up floors, damaging windows, opening containers, and throwing items. Using it leaves you vulnerable to attacks for a short period of time. + name: revenant-defile-name + description: revenant-defile-desc productAction: ActionRevenantDefile cost: StolenEssence: 10 @@ -13,8 +13,8 @@ - type: listing id: RevenantOverloadLights - name: Overload Lights - description: Overloads all nearby lights, causing lights to pulse and sending out dangerous lightning. Using it leaves you vulnerable to attacks for a long period of time. + name: revenant-overload-name + description: revenant-overload-desc productAction: ActionRevenantOverloadLights cost: StolenEssence: 25 @@ -26,8 +26,8 @@ #- type: listing # id: RevenantBlight -# name: Blight -# description: Infects all nearby organisms with an infectious disease that causes toxic buildup and tiredness. Using it leaves you vulnerable to attacks for a medium period of time. +# name: revenant-blight-name +# description: revenant-blight-desc # productAction: ActionRevenantBlight # cost: # StolenEssence: 75 @@ -39,8 +39,8 @@ - type: listing id: RevenantMalfunction - name: Malfunction - description: Makes nearby electronics stop working properly. Using it leaves you vulnerable to attacks for a long period of time. + name: revenant-malfunction-name + description: revenant-malfunction-desc productAction: ActionRevenantMalfunction cost: StolenEssence: 125 From d75de9fa248cce1a1670616ba0af5a0f4759ce51 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:14:56 -0700 Subject: [PATCH 076/243] Make cuff default range again (#28576) * Make cuff default range again * uncuff distance * how about ONE --------- Co-authored-by: plykiya --- Content.Shared/Cuffs/SharedCuffableSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index f0f9a94983..1ced3c8d6c 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -484,7 +484,7 @@ namespace Content.Shared.Cuffs BreakOnWeightlessMove = false, BreakOnDamage = true, NeedHand = true, - DistanceThreshold = 0.3f + DistanceThreshold = 1f // shorter than default but still feels good }; if (!_doAfter.TryStartDoAfter(doAfterEventArgs)) @@ -581,7 +581,7 @@ namespace Content.Shared.Cuffs BreakOnDamage = true, NeedHand = true, RequireCanInteract = false, // Trust in UncuffAttemptEvent - DistanceThreshold = 0.3f + DistanceThreshold = 1f // shorter than default but still feels good }; if (!_doAfter.TryStartDoAfter(doAfterEventArgs)) From 8a7b0f675e1cf140f28cb6f0bb2d15c3fca9a14f Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 5 Jun 2024 20:16:02 +0000 Subject: [PATCH 077/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 851ebffc64..e68ab9517d 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: PJB3005 - changes: - - message: Fixed hardsuits in space causing high pressure damage - type: Fix - id: 6184 - time: '2024-03-18T16:46:31.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26236 - author: potato1234x changes: - message: Added crafting recipes for wall lockers and secure lockers @@ -3849,3 +3842,10 @@ id: 6683 time: '2024-06-05T17:50:38.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28627 +- author: Plykiya + changes: + - message: Handcuff range is buffed to one tile of distance. + type: Tweak + id: 6684 + time: '2024-06-05T20:14:56.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28576 From 729e67af7f2892644d758c6ade5c9abc4ac6c66e Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:23:23 -0400 Subject: [PATCH 078/243] Machine-code cleanup (#28489) --- .../UI/FlatpackCreatorMenu.xaml.cs | 3 +- .../Tests/MachineBoardTest.cs | 41 +- .../Tests/MaterialArbitrageTest.cs | 3 +- .../Construction/Components/IRefreshParts.cs | 10 - .../Components/MachineComponent.cs | 34 +- .../Components/MachineFrameComponent.cs | 19 +- .../Components/PartExchangerComponent.cs | 29 -- .../Conditions/MachineFrameComplete.cs | 28 +- .../Construction/ConstructionSystem.Graph.cs | 8 +- .../Construction/ConstructionSystem.Guided.cs | 6 +- .../ConstructionSystem.Initial.cs | 8 +- .../ConstructionSystem.Machine.cs | 176 +-------- .../Construction/ConstructionSystem.cs | 1 - .../Construction/MachineFrameSystem.cs | 128 +------ .../Construction/PartExchangerSystem.cs | 180 --------- Content.Server/Stack/StackSystem.cs | 9 + .../Components/MachineBoardComponent.cs | 83 ++--- .../Components/MachinePartComponent.cs | 24 -- .../Construction/MachinePartSystem.cs | 48 +-- .../Prototypes/MachinePartPrototype.cs | 28 -- .../Construction/SharedConstructionSystem.cs | 11 + .../Construction/SharedFlatpackSystem.cs | 9 +- Content.Shared/Lathe/LatheComponent.cs | 2 - ...bitrary-insert-construction-graph-step.ftl | 10 +- .../Devices/Circuitboards/Machine/cannons.yml | 17 +- .../Machine/particle_accelerator.yml | 17 +- .../Circuitboards/Machine/production.yml | 352 +++++++----------- .../Entities/Objects/Misc/machine_parts.yml | 11 +- .../Entities/Objects/Power/powercells.yml | 12 - .../Prototypes/MachineParts/machine_parts.yml | 20 - .../Prototypes/Stacks/science_stacks.yml | 2 +- 31 files changed, 350 insertions(+), 979 deletions(-) delete mode 100644 Content.Server/Construction/Components/IRefreshParts.cs delete mode 100644 Content.Server/Construction/Components/PartExchangerComponent.cs delete mode 100644 Content.Server/Construction/PartExchangerSystem.cs delete mode 100644 Content.Shared/Construction/Components/MachinePartComponent.cs delete mode 100644 Content.Shared/Construction/Prototypes/MachinePartPrototype.cs delete mode 100644 Resources/Prototypes/MachineParts/machine_parts.yml diff --git a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs index ad19bc30f4..0026163237 100644 --- a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs +++ b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs @@ -75,8 +75,7 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow else if (_currentBoard != null) { Dictionary cost; - if (_entityManager.TryGetComponent(_currentBoard, out machineBoardComp) && - machineBoardComp.Prototype is not null) + if (_entityManager.TryGetComponent(_currentBoard, out machineBoardComp)) cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp)); else cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker)); diff --git a/Content.IntegrationTests/Tests/MachineBoardTest.cs b/Content.IntegrationTests/Tests/MachineBoardTest.cs index bd3a72f4c1..e741935be3 100644 --- a/Content.IntegrationTests/Tests/MachineBoardTest.cs +++ b/Content.IntegrationTests/Tests/MachineBoardTest.cs @@ -2,6 +2,8 @@ using System.Linq; using Content.Server.Construction.Components; using Content.Shared.Construction.Components; +using Content.Shared.Prototypes; +using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests; @@ -49,12 +51,11 @@ public sealed class MachineBoardTest Assert.Multiple(() => { - Assert.That(mId, Is.Not.Null, $"Machine board {p.ID} does not have a corresponding machine."); Assert.That(protoMan.TryIndex(mId, out var mProto), $"Machine board {p.ID}'s corresponding machine has an invalid prototype."); Assert.That(mProto.TryGetComponent(out var mComp), $"Machine board {p.ID}'s corresponding machine {mId} does not have MachineComponent"); - Assert.That(mComp.BoardPrototype, Is.EqualTo(p.ID), + Assert.That(mComp.Board, Is.EqualTo(p.ID), $"Machine {mId}'s BoardPrototype is not equal to it's corresponding machine board, {p.ID}"); }); } @@ -101,4 +102,40 @@ public sealed class MachineBoardTest await pair.CleanReturnAsync(); } + + /// + /// Ensures that every single computer board's corresponding entity + /// is a computer that can be properly deconstructed to the correct board + /// + [Test] + public async Task TestValidateBoardComponentRequirements() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var entMan = server.ResolveDependency(); + var protoMan = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + foreach (var p in protoMan.EnumeratePrototypes() + .Where(p => !p.Abstract) + .Where(p => !pair.IsTestPrototype(p)) + .Where(p => !_ignoredPrototypes.Contains(p.ID))) + { + if (!p.TryGetComponent(out var board, entMan.ComponentFactory)) + continue; + + Assert.Multiple(() => + { + foreach (var component in board.ComponentRequirements.Keys) + { + Assert.That(entMan.ComponentFactory.TryGetRegistration(component, out _), $"Invalid component requirement {component} specified on machine board entity {p}"); + } + }); + } + }); + + await pair.CleanReturnAsync(); + } } diff --git a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs index ed1e554943..19780591bd 100644 --- a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs +++ b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs @@ -60,7 +60,8 @@ public sealed class MaterialArbitrageTest } // Lets assume the possible lathe for resource multipliers: - var multiplier = MathF.Pow(LatheComponent.DefaultPartRatingMaterialUseMultiplier, MachinePartComponent.MaxRating - 1); + // TODO: each recipe can technically have its own cost multiplier associated with it, so this test needs redone to factor that in. + var multiplier = MathF.Pow(0.85f, 3); // create construction dictionary Dictionary constructionRecipes = new(); diff --git a/Content.Server/Construction/Components/IRefreshParts.cs b/Content.Server/Construction/Components/IRefreshParts.cs deleted file mode 100644 index 714ca72c97..0000000000 --- a/Content.Server/Construction/Components/IRefreshParts.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Content.Shared.Construction.Components; - -namespace Content.Server.Construction.Components -{ - [RequiresExplicitImplementation] - public interface IRefreshParts - { - void RefreshParts(IEnumerable parts); - } -} diff --git a/Content.Server/Construction/Components/MachineComponent.cs b/Content.Server/Construction/Components/MachineComponent.cs index 42c85e6d0b..8a0f117712 100644 --- a/Content.Server/Construction/Components/MachineComponent.cs +++ b/Content.Server/Construction/Components/MachineComponent.cs @@ -1,27 +1,17 @@ -using Robust.Shared.Containers; +using Content.Shared.Construction.Components; +using Robust.Shared.Containers; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Server.Construction.Components +namespace Content.Server.Construction.Components; + +[RegisterComponent] +public sealed partial class MachineComponent : Component { - [RegisterComponent, ComponentProtoName("Machine")] - public sealed partial class MachineComponent : Component - { - [DataField("board", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? BoardPrototype { get; private set; } + [DataField] + public EntProtoId? Board { get; private set; } - [ViewVariables] - public Container BoardContainer = default!; - [ViewVariables] - public Container PartContainer = default!; - } - - /// - /// The different types of scaling that are available for machine upgrades - /// - public enum MachineUpgradeScalingType : byte - { - Linear, - Exponential - } + [ViewVariables] + public Container BoardContainer = default!; + [ViewVariables] + public Container PartContainer = default!; } diff --git a/Content.Server/Construction/Components/MachineFrameComponent.cs b/Content.Server/Construction/Components/MachineFrameComponent.cs index 75cc486f24..14e81e8dd3 100644 --- a/Content.Server/Construction/Components/MachineFrameComponent.cs +++ b/Content.Server/Construction/Components/MachineFrameComponent.cs @@ -1,7 +1,8 @@ using Content.Shared.Construction.Components; -using Content.Shared.Construction.Prototypes; +using Content.Shared.Stacks; +using Content.Shared.Tag; using Robust.Shared.Containers; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; +using Robust.Shared.Prototypes; namespace Content.Server.Construction.Components { @@ -14,29 +15,23 @@ namespace Content.Server.Construction.Components [ViewVariables] public bool HasBoard => BoardContainer?.ContainedEntities.Count != 0; - [DataField("progress", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] - public Dictionary Progress = new(); - [ViewVariables] - public readonly Dictionary MaterialProgress = new(); + public readonly Dictionary, int> MaterialProgress = new(); [ViewVariables] public readonly Dictionary ComponentProgress = new(); [ViewVariables] - public readonly Dictionary TagProgress = new(); - - [DataField("requirements", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] - public Dictionary Requirements = new(); + public readonly Dictionary, int> TagProgress = new(); [ViewVariables] - public Dictionary MaterialRequirements = new(); + public Dictionary, int> MaterialRequirements = new(); [ViewVariables] public Dictionary ComponentRequirements = new(); [ViewVariables] - public Dictionary TagRequirements = new(); + public Dictionary, GenericPartInfo> TagRequirements = new(); [ViewVariables] public Container BoardContainer = default!; diff --git a/Content.Server/Construction/Components/PartExchangerComponent.cs b/Content.Server/Construction/Components/PartExchangerComponent.cs deleted file mode 100644 index a2579c92e8..0000000000 --- a/Content.Server/Construction/Components/PartExchangerComponent.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Robust.Shared.Audio; - -namespace Content.Server.Construction.Components; - -[RegisterComponent] -public sealed partial class PartExchangerComponent : Component -{ - /// - /// How long it takes to exchange the parts - /// - [DataField("exchangeDuration")] - public float ExchangeDuration = 3; - - /// - /// Whether or not the distance check is needed. - /// Good for BRPED. - /// - /// - /// I fucking hate BRPED and if you ever add it - /// i will personally kill your dog. - /// - [DataField("doDistanceCheck")] - public bool DoDistanceCheck = true; - - [DataField("exchangeSound")] - public SoundSpecifier ExchangeSound = new SoundPathSpecifier("/Audio/Items/rped.ogg"); - - public EntityUid? AudioStream; -} diff --git a/Content.Server/Construction/Conditions/MachineFrameComplete.cs b/Content.Server/Construction/Conditions/MachineFrameComplete.cs index fbba5eca99..33174bb9fb 100644 --- a/Content.Server/Construction/Conditions/MachineFrameComplete.cs +++ b/Content.Server/Construction/Conditions/MachineFrameComplete.cs @@ -2,6 +2,7 @@ using Content.Server.Construction.Components; using Content.Shared.Construction; using Content.Shared.Examine; using JetBrains.Annotations; +using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Server.Construction.Conditions @@ -17,7 +18,7 @@ namespace Content.Server.Construction.Conditions public SpriteSpecifier? GuideIconBoard { get; private set; } [DataField("guideIconParts")] - public SpriteSpecifier? GuideIconPart { get; private set; } + public SpriteSpecifier? GuideIconParts { get; private set; } public bool Condition(EntityUid uid, IEntityManager entityManager) @@ -33,6 +34,8 @@ namespace Content.Server.Construction.Conditions var entity = args.Examined; var entityManager = IoCManager.Resolve(); + var protoManager = IoCManager.Resolve(); + var constructionSys = entityManager.System(); if (!entityManager.TryGetComponent(entity, out MachineFrameComponent? machineFrame)) return false; @@ -47,17 +50,6 @@ namespace Content.Server.Construction.Conditions return false; args.PushMarkup(Loc.GetString("construction-condition-machine-frame-requirement-label")); - foreach (var (part, required) in machineFrame.Requirements) - { - var amount = required - machineFrame.Progress[part]; - - if(amount == 0) - continue; - - args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry", - ("amount", amount), - ("elementName", Loc.GetString(part)))); - } foreach (var (material, required) in machineFrame.MaterialRequirements) { @@ -65,10 +57,12 @@ namespace Content.Server.Construction.Conditions if(amount == 0) continue; + var stack = protoManager.Index(material); + var stackEnt = protoManager.Index(stack.Spawn); args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry", ("amount", amount), - ("elementName", Loc.GetString(material)))); + ("elementName", stackEnt.Name))); } foreach (var (compName, info) in machineFrame.ComponentRequirements) @@ -78,9 +72,10 @@ namespace Content.Server.Construction.Conditions if(amount == 0) continue; + var examineName = constructionSys.GetExamineName(info); args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry", ("amount", info.Amount), - ("elementName", Loc.GetString(info.ExamineName)))); + ("elementName", examineName))); } foreach (var (tagName, info) in machineFrame.TagRequirements) @@ -90,9 +85,10 @@ namespace Content.Server.Construction.Conditions if(amount == 0) continue; + var examineName = constructionSys.GetExamineName(info); args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry", ("amount", info.Amount), - ("elementName", Loc.GetString(info.ExamineName))) + ("elementName", examineName)) + "\n"); } @@ -111,7 +107,7 @@ namespace Content.Server.Construction.Conditions yield return new ConstructionGuideEntry() { Localization = "construction-step-condition-machine-frame-parts", - Icon = GuideIconPart, + Icon = GuideIconParts, EntryNumber = 0, // Set this to anything so the guide generation takes this as a numbered step. }; } diff --git a/Content.Server/Construction/ConstructionSystem.Graph.cs b/Content.Server/Construction/ConstructionSystem.Graph.cs index 570360bf09..1b74fd9d4e 100644 --- a/Content.Server/Construction/ConstructionSystem.Graph.cs +++ b/Content.Server/Construction/ConstructionSystem.Graph.cs @@ -50,7 +50,7 @@ namespace Content.Server.Construction // If the set graph prototype does not exist, also return null. This could be due to admemes changing values // in ViewVariables, so even though the construction state is invalid, just return null. - return _prototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph) ? graph : null; + return PrototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph) ? graph : null; } /// @@ -300,7 +300,7 @@ namespace Content.Server.Construction } // Exit if the new entity's prototype is the same as the original, or the prototype is invalid - if (newEntity == metaData.EntityPrototype?.ID || !_prototypeManager.HasIndex(newEntity)) + if (newEntity == metaData.EntityPrototype?.ID || !PrototypeManager.HasIndex(newEntity)) return null; // [Optional] Exit if the new entity's prototype is a parent of the original @@ -310,7 +310,7 @@ namespace Content.Server.Construction if (GetCurrentNode(uid, construction)?.DoNotReplaceInheritingEntities == true && metaData.EntityPrototype?.ID != null) { - var parents = _prototypeManager.EnumerateParents(metaData.EntityPrototype.ID)?.ToList(); + var parents = PrototypeManager.EnumerateParents(metaData.EntityPrototype.ID)?.ToList(); if (parents != null && parents.Any(x => x.ID == newEntity)) return null; @@ -427,7 +427,7 @@ namespace Content.Server.Construction if (!Resolve(uid, ref construction)) return false; - if (!_prototypeManager.TryIndex(graphId, out var graph)) + if (!PrototypeManager.TryIndex(graphId, out var graph)) return false; if(GetNodeFromGraph(graph, nodeId) is not {}) diff --git a/Content.Server/Construction/ConstructionSystem.Guided.cs b/Content.Server/Construction/ConstructionSystem.Guided.cs index e096bc02c3..157e421158 100644 --- a/Content.Server/Construction/ConstructionSystem.Guided.cs +++ b/Content.Server/Construction/ConstructionSystem.Guided.cs @@ -25,7 +25,7 @@ namespace Content.Server.Construction private void OnGuideRequested(RequestConstructionGuide msg, EntitySessionEventArgs args) { - if (!_prototypeManager.TryIndex(msg.ConstructionId, out ConstructionPrototype? prototype)) + if (!PrototypeManager.TryIndex(msg.ConstructionId, out ConstructionPrototype? prototype)) return; if(GetGuide(prototype) is {} guide) @@ -41,7 +41,7 @@ namespace Content.Server.Construction component.Node == component.DeconstructionNode) return; - if (!_prototypeManager.TryIndex(component.Graph, out ConstructionGraphPrototype? graph)) + if (!PrototypeManager.TryIndex(component.Graph, out ConstructionGraphPrototype? graph)) return; if (component.DeconstructionNode == null) @@ -145,7 +145,7 @@ namespace Content.Server.Construction return guide; // If the graph doesn't actually exist, do nothing. - if (!_prototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph)) + if (!PrototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph)) return null; // If either the start node or the target node are missing, do nothing. diff --git a/Content.Server/Construction/ConstructionSystem.Initial.cs b/Content.Server/Construction/ConstructionSystem.Initial.cs index 5161ac358a..08dd139ef4 100644 --- a/Content.Server/Construction/ConstructionSystem.Initial.cs +++ b/Content.Server/Construction/ConstructionSystem.Initial.cs @@ -325,13 +325,13 @@ namespace Content.Server.Construction // LEGACY CODE. See warning at the top of the file! public async Task TryStartItemConstruction(string prototype, EntityUid user) { - if (!_prototypeManager.TryIndex(prototype, out ConstructionPrototype? constructionPrototype)) + if (!PrototypeManager.TryIndex(prototype, out ConstructionPrototype? constructionPrototype)) { Log.Error($"Tried to start construction of invalid recipe '{prototype}'!"); return false; } - if (!_prototypeManager.TryIndex(constructionPrototype.Graph, + if (!PrototypeManager.TryIndex(constructionPrototype.Graph, out ConstructionGraphPrototype? constructionGraph)) { Log.Error( @@ -404,14 +404,14 @@ namespace Content.Server.Construction // LEGACY CODE. See warning at the top of the file! private async void HandleStartStructureConstruction(TryStartStructureConstructionMessage ev, EntitySessionEventArgs args) { - if (!_prototypeManager.TryIndex(ev.PrototypeName, out ConstructionPrototype? constructionPrototype)) + if (!PrototypeManager.TryIndex(ev.PrototypeName, out ConstructionPrototype? constructionPrototype)) { Log.Error($"Tried to start construction of invalid recipe '{ev.PrototypeName}'!"); RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack)); return; } - if (!_prototypeManager.TryIndex(constructionPrototype.Graph, out ConstructionGraphPrototype? constructionGraph)) + if (!PrototypeManager.TryIndex(constructionPrototype.Graph, out ConstructionGraphPrototype? constructionGraph)) { Log.Error($"Invalid construction graph '{constructionPrototype.Graph}' in recipe '{ev.PrototypeName}'!"); RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack)); diff --git a/Content.Server/Construction/ConstructionSystem.Machine.cs b/Content.Server/Construction/ConstructionSystem.Machine.cs index 2e670dbe40..eb922f198c 100644 --- a/Content.Server/Construction/ConstructionSystem.Machine.cs +++ b/Content.Server/Construction/ConstructionSystem.Machine.cs @@ -1,23 +1,15 @@ -using System.Linq; using Content.Server.Construction.Components; -using Content.Server.Examine; using Content.Shared.Construction.Components; -using Content.Shared.Construction.Prototypes; -using Content.Shared.Verbs; using Robust.Shared.Containers; -using Robust.Shared.Utility; namespace Content.Server.Construction; public sealed partial class ConstructionSystem { - [Dependency] private readonly ExamineSystem _examineSystem = default!; - private void InitializeMachines() { SubscribeLocalEvent(OnMachineInit); SubscribeLocalEvent(OnMachineMapInit); - SubscribeLocalEvent>(OnMachineExaminableVerb); } private void OnMachineInit(EntityUid uid, MachineComponent component, ComponentInit args) @@ -29,84 +21,6 @@ public sealed partial class ConstructionSystem private void OnMachineMapInit(EntityUid uid, MachineComponent component, MapInitEvent args) { CreateBoardAndStockParts(uid, component); - RefreshParts(uid, component); - } - - private void OnMachineExaminableVerb(EntityUid uid, MachineComponent component, GetVerbsEvent args) - { - if (!args.CanInteract || !args.CanAccess) - return; - - var markup = new FormattedMessage(); - RaiseLocalEvent(uid, new UpgradeExamineEvent(ref markup)); - if (markup.IsEmpty) - return; // Not upgradable. - - markup = FormattedMessage.FromMarkup(markup.ToMarkup().TrimEnd('\n')); // Cursed workaround to https://github.com/space-wizards/RobustToolbox/issues/3371 - - var verb = new ExamineVerb() - { - Act = () => - { - _examineSystem.SendExamineTooltip(args.User, uid, markup, getVerbs: false, centerAtCursor: false); - }, - Text = Loc.GetString("machine-upgrade-examinable-verb-text"), - Message = Loc.GetString("machine-upgrade-examinable-verb-message"), - Category = VerbCategory.Examine, - Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/pickup.svg.192dpi.png")) - }; - - args.Verbs.Add(verb); - } - - public List GetAllParts(EntityUid uid, MachineComponent? component = null) - { - if (!Resolve(uid, ref component)) - return new List(); - - return GetAllParts(component); - } - - public List GetAllParts(MachineComponent component) - { - var parts = new List(); - - foreach (var entity in component.PartContainer.ContainedEntities) - { - if (TryComp(entity, out var machinePart)) - parts.Add(machinePart); - } - - return parts; - } - - public Dictionary GetPartsRatings(List parts) - { - var output = new Dictionary(); - foreach (var type in _prototypeManager.EnumeratePrototypes()) - { - var amount = 0f; - var sumRating = 0f; - foreach (var part in parts.Where(part => part.PartType == type.ID)) - { - amount++; - sumRating += part.Rating; - } - var rating = amount != 0 ? sumRating / amount : 0; - output.Add(type.ID, rating); - } - - return output; - } - - public void RefreshParts(EntityUid uid, MachineComponent component) - { - var parts = GetAllParts(component); - EntityManager.EventBus.RaiseLocalEvent(uid, new RefreshPartsEvent - { - Parts = parts, - PartRatings = GetPartsRatings(parts), - }, true); } private void CreateBoardAndStockParts(EntityUid uid, MachineComponent component) @@ -115,54 +29,37 @@ public sealed partial class ConstructionSystem var boardContainer = _container.EnsureContainer(uid, MachineFrameComponent.BoardContainerName); var partContainer = _container.EnsureContainer(uid, MachineFrameComponent.PartContainerName); - if (string.IsNullOrEmpty(component.BoardPrototype)) + if (string.IsNullOrEmpty(component.Board)) return; // We're done here, let's suppose all containers are correct just so we don't screw SaveLoadSave. if (boardContainer.ContainedEntities.Count > 0) return; - var board = EntityManager.SpawnEntity(component.BoardPrototype, Transform(uid).Coordinates); - - if (!_container.Insert(board, component.BoardContainer)) + var xform = Transform(uid); + if (!TrySpawnInContainer(component.Board, uid, MachineFrameComponent.BoardContainerName, out var board)) { - throw new Exception($"Couldn't insert board with prototype {component.BoardPrototype} to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}!"); + throw new Exception($"Couldn't insert board with prototype {component.Board} to machine with prototype {Prototype(uid)?.ID ?? "N/A"}!"); } if (!TryComp(board, out var machineBoard)) { - throw new Exception($"Entity with prototype {component.BoardPrototype} doesn't have a {nameof(MachineBoardComponent)}!"); + throw new Exception($"Entity with prototype {component.Board} doesn't have a {nameof(MachineBoardComponent)}!"); } - var xform = Transform(uid); - foreach (var (part, amount) in machineBoard.Requirements) + foreach (var (stackType, amount) in machineBoard.StackRequirements) { - var partProto = _prototypeManager.Index(part); - for (var i = 0; i < amount; i++) - { - var p = EntityManager.SpawnEntity(partProto.StockPartPrototype, xform.Coordinates); - - if (!_container.Insert(p, partContainer)) - throw new Exception($"Couldn't insert machine part of type {part} to machine with prototype {partProto.StockPartPrototype}!"); - } - } - - foreach (var (stackType, amount) in machineBoard.MaterialRequirements) - { - var stack = _stackSystem.Spawn(amount, stackType, Transform(uid).Coordinates); - + var stack = _stackSystem.Spawn(amount, stackType, xform.Coordinates); if (!_container.Insert(stack, partContainer)) - throw new Exception($"Couldn't insert machine material of type {stackType} to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}"); + throw new Exception($"Couldn't insert machine material of type {stackType} to machine with prototype {Prototype(uid)?.ID ?? "N/A"}"); } foreach (var (compName, info) in machineBoard.ComponentRequirements) { for (var i = 0; i < info.Amount; i++) { - var c = EntityManager.SpawnEntity(info.DefaultPrototype, Transform(uid).Coordinates); - - if(!_container.Insert(c, partContainer)) - throw new Exception($"Couldn't insert machine component part with default prototype '{compName}' to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}"); + if(!TrySpawnInContainer(info.DefaultPrototype, uid, MachineFrameComponent.PartContainerName, out _)) + throw new Exception($"Couldn't insert machine component part with default prototype '{compName}' to machine with prototype {Prototype(uid)?.ID ?? "N/A"}"); } } @@ -170,58 +67,9 @@ public sealed partial class ConstructionSystem { for (var i = 0; i < info.Amount; i++) { - var c = EntityManager.SpawnEntity(info.DefaultPrototype, Transform(uid).Coordinates); - - if(!_container.Insert(c, partContainer)) - throw new Exception($"Couldn't insert machine component part with default prototype '{tagName}' to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}"); + if(!TrySpawnInContainer(info.DefaultPrototype, uid, MachineFrameComponent.PartContainerName, out _)) + throw new Exception($"Couldn't insert machine component part with default prototype '{tagName}' to machine with prototype {Prototype(uid)?.ID ?? "N/A"}"); } } } } - -public sealed class RefreshPartsEvent : EntityEventArgs -{ - public IReadOnlyList Parts = new List(); - - public Dictionary PartRatings = new(); -} - -public sealed class UpgradeExamineEvent : EntityEventArgs -{ - private FormattedMessage Message; - - public UpgradeExamineEvent(ref FormattedMessage message) - { - Message = message; - } - - /// - /// Add a line to the upgrade examine tooltip with a percentage-based increase or decrease. - /// - public void AddPercentageUpgrade(string upgradedLocId, float multiplier) - { - var percent = Math.Round(100 * MathF.Abs(multiplier - 1), 2); - var locId = multiplier switch { - < 1 => "machine-upgrade-decreased-by-percentage", - 1 or float.NaN => "machine-upgrade-not-upgraded", - > 1 => "machine-upgrade-increased-by-percentage", - }; - var upgraded = Loc.GetString(upgradedLocId); - this.Message.AddMarkup(Loc.GetString(locId, ("upgraded", upgraded), ("percent", percent)) + '\n'); - } - - /// - /// Add a line to the upgrade examine tooltip with a numeric increase or decrease. - /// - public void AddNumberUpgrade(string upgradedLocId, int number) - { - var difference = Math.Abs(number); - var locId = number switch { - < 0 => "machine-upgrade-decreased-by-amount", - 0 => "machine-upgrade-not-upgraded", - > 0 => "machine-upgrade-increased-by-amount", - }; - var upgraded = Loc.GetString(upgradedLocId); - this.Message.AddMarkup(Loc.GetString(locId, ("upgraded", upgraded), ("difference", difference)) + '\n'); - } -} diff --git a/Content.Server/Construction/ConstructionSystem.cs b/Content.Server/Construction/ConstructionSystem.cs index 44c6318b78..9d881dcef0 100644 --- a/Content.Server/Construction/ConstructionSystem.cs +++ b/Content.Server/Construction/ConstructionSystem.cs @@ -16,7 +16,6 @@ namespace Content.Server.Construction [UsedImplicitly] public sealed partial class ConstructionSystem : SharedConstructionSystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] private readonly ContainerSystem _container = default!; diff --git a/Content.Server/Construction/MachineFrameSystem.cs b/Content.Server/Construction/MachineFrameSystem.cs index e20c36d849..0fd5ad2e0e 100644 --- a/Content.Server/Construction/MachineFrameSystem.cs +++ b/Content.Server/Construction/MachineFrameSystem.cs @@ -7,7 +7,7 @@ using Content.Shared.Stacks; using Content.Shared.Tag; using Content.Shared.Popups; using Robust.Shared.Containers; -using Robust.Shared.Utility; +using Robust.Shared.Prototypes; namespace Content.Server.Construction; @@ -62,24 +62,7 @@ public sealed class MachineFrameSystem : EntitySystem // If this changes in the future, then RegenerateProgress() also needs to be updated. // Note that one entity is ALLOWED to satisfy more than one kind of component or tag requirements. This is // necessary in order to avoid weird entity-ordering shenanigans in RegenerateProgress(). - var stack = CompOrNull(args.Used); - var machinePart = CompOrNull(args.Used); - if (stack != null && machinePart != null) - { - if (TryInsertPartStack(uid, args.Used, component, machinePart, stack)) - args.Handled = true; - return; - } - - // Handle parts - if (machinePart != null) - { - if (TryInsertPart(uid, args.Used, component, machinePart)) - args.Handled = true; - return; - } - - if (stack != null) + if (TryComp(args.Used, out var stack)) { if (TryInsertStack(uid, args.Used, component, stack)) args.Handled = true; @@ -172,67 +155,6 @@ public sealed class MachineFrameSystem : EntitySystem return true; } - /// Whether or not the function had any effect. Does not indicate success. - private bool TryInsertPart(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart) - { - DebugTools.Assert(!HasComp(uid)); - if (!component.Requirements.ContainsKey(machinePart.PartType)) - return false; - - if (component.Progress[machinePart.PartType] >= component.Requirements[machinePart.PartType]) - return false; - - if (!_container.TryRemoveFromContainer(used)) - return false; - - if (!_container.Insert(used, component.PartContainer)) - return true; - - component.Progress[machinePart.PartType]++; - if (IsComplete(component)) - _popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid); - - return true; - } - - /// Whether or not the function had any effect. Does not indicate success. - private bool TryInsertPartStack(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart, StackComponent stack) - { - if (!component.Requirements.ContainsKey(machinePart.PartType)) - return false; - - var progress = component.Progress[machinePart.PartType]; - var requirement = component.Requirements[machinePart.PartType]; - - var needed = requirement - progress; - if (needed <= 0) - return false; - - var count = stack.Count; - if (count < needed) - { - if (!_container.Insert(used, component.PartContainer)) - return true; - - component.Progress[machinePart.PartType] += count; - return true; - } - - var splitStack = _stack.Split(used, needed, Transform(uid).Coordinates, stack); - - if (splitStack == null) - return false; - - if (!_container.Insert(splitStack.Value, component.PartContainer)) - return true; - - component.Progress[machinePart.PartType] += needed; - if (IsComplete(component)) - _popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid); - - return true; - } - /// Whether or not the function had any effect. Does not indicate success. private bool TryInsertStack(EntityUid uid, EntityUid used, MachineFrameComponent component, StackComponent stack) { @@ -281,12 +203,6 @@ public sealed class MachineFrameSystem : EntitySystem if (!component.HasBoard) return false; - foreach (var (part, amount) in component.Requirements) - { - if (component.Progress[part] < amount) - return false; - } - foreach (var (type, amount) in component.MaterialRequirements) { if (component.MaterialProgress[type] < amount) @@ -310,21 +226,14 @@ public sealed class MachineFrameSystem : EntitySystem public void ResetProgressAndRequirements(MachineFrameComponent component, MachineBoardComponent machineBoard) { - component.Requirements = new Dictionary(machineBoard.Requirements); - component.MaterialRequirements = new Dictionary(machineBoard.MaterialIdRequirements); + component.MaterialRequirements = new Dictionary, int>(machineBoard.StackRequirements); component.ComponentRequirements = new Dictionary(machineBoard.ComponentRequirements); - component.TagRequirements = new Dictionary(machineBoard.TagRequirements); + component.TagRequirements = new Dictionary, GenericPartInfo>(machineBoard.TagRequirements); - component.Progress.Clear(); component.MaterialProgress.Clear(); component.ComponentProgress.Clear(); component.TagProgress.Clear(); - foreach (var (machinePart, _) in component.Requirements) - { - component.Progress[machinePart] = 0; - } - foreach (var (stackType, _) in component.MaterialRequirements) { component.MaterialProgress[stackType] = 0; @@ -349,7 +258,6 @@ public sealed class MachineFrameSystem : EntitySystem component.MaterialRequirements.Clear(); component.ComponentRequirements.Clear(); component.TagRequirements.Clear(); - component.Progress.Clear(); component.MaterialProgress.Clear(); component.ComponentProgress.Clear(); component.TagProgress.Clear(); @@ -368,19 +276,6 @@ public sealed class MachineFrameSystem : EntitySystem foreach (var part in component.PartContainer.ContainedEntities) { - if (TryComp(part, out var machinePart)) - { - // Check this is part of the requirements... - if (!component.Requirements.ContainsKey(machinePart.PartType)) - continue; - - if (!component.Progress.ContainsKey(machinePart.PartType)) - component.Progress[machinePart.PartType] = 1; - else - component.Progress[machinePart.PartType]++; - continue; - } - if (TryComp(part, out var stack)) { var type = stack.StackTypeId; @@ -404,9 +299,7 @@ public sealed class MachineFrameSystem : EntitySystem if (!HasComp(part, registration.Type)) continue; - if (!component.ComponentProgress.ContainsKey(compName)) - component.ComponentProgress[compName] = 1; - else + if (!component.ComponentProgress.TryAdd(compName, 1)) component.ComponentProgress[compName]++; } @@ -419,18 +312,17 @@ public sealed class MachineFrameSystem : EntitySystem if (!_tag.HasTag(tagComp, tagName)) continue; - if (!component.TagProgress.ContainsKey(tagName)) - component.TagProgress[tagName] = 1; - else + if (!component.TagProgress.TryAdd(tagName, 1)) component.TagProgress[tagName]++; } } } private void OnMachineFrameExamined(EntityUid uid, MachineFrameComponent component, ExaminedEvent args) { - if (!args.IsInDetailsRange) + if (!args.IsInDetailsRange || !component.HasBoard) return; - if (component.HasBoard) - args.PushMarkup(Loc.GetString("machine-frame-component-on-examine-label", ("board", EntityManager.GetComponent(component.BoardContainer.ContainedEntities[0]).EntityName))); + + var board = component.BoardContainer.ContainedEntities[0]; + args.PushMarkup(Loc.GetString("machine-frame-component-on-examine-label", ("board", Name(board)))); } } diff --git a/Content.Server/Construction/PartExchangerSystem.cs b/Content.Server/Construction/PartExchangerSystem.cs deleted file mode 100644 index 174dd1d2fa..0000000000 --- a/Content.Server/Construction/PartExchangerSystem.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System.Linq; -using Content.Server.Construction.Components; -using Content.Server.Storage.EntitySystems; -using Content.Shared.DoAfter; -using Content.Shared.Construction.Components; -using Content.Shared.Exchanger; -using Content.Shared.Interaction; -using Content.Shared.Popups; -using Content.Shared.Storage; -using Robust.Shared.Containers; -using Robust.Shared.Utility; -using Content.Shared.Wires; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Collections; - -namespace Content.Server.Construction; - -public sealed class PartExchangerSystem : EntitySystem -{ - [Dependency] private readonly ConstructionSystem _construction = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly StorageSystem _storage = default!; - - /// - public override void Initialize() - { - SubscribeLocalEvent(OnAfterInteract); - SubscribeLocalEvent(OnDoAfter); - } - - private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterEvent args) - { - if (args.Cancelled) - { - component.AudioStream = _audio.Stop(component.AudioStream); - return; - } - - if (args.Handled || args.Args.Target == null) - return; - - if (!TryComp(uid, out var storage)) - return; //the parts are stored in here - - var machinePartQuery = GetEntityQuery(); - var machineParts = new List<(EntityUid, MachinePartComponent)>(); - - foreach (var item in storage.Container.ContainedEntities) //get parts in RPED - { - if (machinePartQuery.TryGetComponent(item, out var part)) - machineParts.Add((item, part)); - } - - TryExchangeMachineParts(args.Args.Target.Value, uid, machineParts); - TryConstructMachineParts(args.Args.Target.Value, uid, machineParts); - - args.Handled = true; - } - - private void TryExchangeMachineParts(EntityUid uid, EntityUid storageUid, List<(EntityUid part, MachinePartComponent partComp)> machineParts) - { - if (!TryComp(uid, out var machine)) - return; - - var machinePartQuery = GetEntityQuery(); - var board = machine.BoardContainer.ContainedEntities.FirstOrNull(); - - if (board == null || !TryComp(board, out var macBoardComp)) - return; - - foreach (var item in new ValueList(machine.PartContainer.ContainedEntities)) //clone so don't modify during enumeration - { - if (machinePartQuery.TryGetComponent(item, out var part)) - { - machineParts.Add((item, part)); - _container.RemoveEntity(uid, item); - } - } - - machineParts.Sort((x, y) => y.partComp.Rating.CompareTo(x.partComp.Rating)); - - var updatedParts = new List<(EntityUid part, MachinePartComponent partComp)>(); - foreach (var (type, amount) in macBoardComp.Requirements) - { - var target = machineParts.Where(p => p.partComp.PartType == type).Take(amount); - updatedParts.AddRange(target); - } - foreach (var part in updatedParts) - { - _container.Insert(part.part, machine.PartContainer); - machineParts.Remove(part); - } - - //put the unused parts back into rped. (this also does the "swapping") - foreach (var (unused, _) in machineParts) - { - _storage.Insert(storageUid, unused, out _, playSound: false); - } - _construction.RefreshParts(uid, machine); - } - - private void TryConstructMachineParts(EntityUid uid, EntityUid storageEnt, List<(EntityUid part, MachinePartComponent partComp)> machineParts) - { - if (!TryComp(uid, out var machine)) - return; - - var machinePartQuery = GetEntityQuery(); - var board = machine.BoardContainer.ContainedEntities.FirstOrNull(); - - if (!machine.HasBoard || !TryComp(board, out var macBoardComp)) - return; - - foreach (var item in new ValueList(machine.PartContainer.ContainedEntities)) //clone so don't modify during enumeration - { - if (machinePartQuery.TryGetComponent(item, out var part)) - { - machineParts.Add((item, part)); - _container.RemoveEntity(uid, item); - machine.Progress[part.PartType]--; - } - } - - machineParts.Sort((x, y) => y.partComp.Rating.CompareTo(x.partComp.Rating)); - - var updatedParts = new List<(EntityUid part, MachinePartComponent partComp)>(); - foreach (var (type, amount) in macBoardComp.Requirements) - { - var target = machineParts.Where(p => p.partComp.PartType == type).Take(amount); - updatedParts.AddRange(target); - } - foreach (var pair in updatedParts) - { - var part = pair.partComp; - var partEnt = pair.part; - - if (!machine.Requirements.ContainsKey(part.PartType)) - continue; - - _container.Insert(partEnt, machine.PartContainer); - machine.Progress[part.PartType]++; - machineParts.Remove(pair); - } - - //put the unused parts back into rped. (this also does the "swapping") - foreach (var (unused, _) in machineParts) - { - _storage.Insert(storageEnt, unused, out _, playSound: false); - } - } - - private void OnAfterInteract(EntityUid uid, PartExchangerComponent component, AfterInteractEvent args) - { - if (component.DoDistanceCheck && !args.CanReach) - return; - - if (args.Target == null) - return; - - if (!HasComp(args.Target) && !HasComp(args.Target)) - return; - - if (TryComp(args.Target, out var panel) && !panel.Open) - { - _popup.PopupEntity(Loc.GetString("construction-step-condition-wire-panel-open"), - args.Target.Value); - return; - } - - component.AudioStream = _audio.PlayPvs(component.ExchangeSound, uid).Value.Entity; - - _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.ExchangeDuration, new ExchangerDoAfterEvent(), uid, target: args.Target, used: uid) - { - BreakOnDamage = true, - BreakOnMove = true - }); - } -} diff --git a/Content.Server/Stack/StackSystem.cs b/Content.Server/Stack/StackSystem.cs index e34592b45f..bac789716c 100644 --- a/Content.Server/Stack/StackSystem.cs +++ b/Content.Server/Stack/StackSystem.cs @@ -71,6 +71,15 @@ namespace Content.Server.Stack return entity; } + /// + /// Spawns a stack of a certain stack type. See . + /// + public EntityUid Spawn(int amount, ProtoId id, EntityCoordinates spawnPosition) + { + var proto = _prototypeManager.Index(id); + return Spawn(amount, proto, spawnPosition); + } + /// /// Spawns a stack of a certain stack type. See . /// diff --git a/Content.Shared/Construction/Components/MachineBoardComponent.cs b/Content.Shared/Construction/Components/MachineBoardComponent.cs index dbca316061..0469b59fd1 100644 --- a/Content.Shared/Construction/Components/MachineBoardComponent.cs +++ b/Content.Shared/Construction/Components/MachineBoardComponent.cs @@ -1,54 +1,47 @@ -using Content.Shared.Construction.Prototypes; using Content.Shared.Stacks; +using Content.Shared.Tag; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; -namespace Content.Shared.Construction.Components +namespace Content.Shared.Construction.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class MachineBoardComponent : Component { - [RegisterComponent, NetworkedComponent] - public sealed partial class MachineBoardComponent : Component - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + /// + /// The stacks needed to construct this machine + /// + [DataField] + public Dictionary, int> StackRequirements = new(); - [DataField("requirements", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] - public Dictionary Requirements = new(); + /// + /// Entities needed to construct this machine, discriminated by tag. + /// + [DataField] + public Dictionary, GenericPartInfo> TagRequirements = new(); - [DataField("materialRequirements")] - public Dictionary MaterialIdRequirements = new(); + /// + /// Entities needed to construct this machine, discriminated by component. + /// + [DataField] + public Dictionary ComponentRequirements = new(); - [DataField("tagRequirements")] - public Dictionary TagRequirements = new(); - - [DataField("componentRequirements")] - public Dictionary ComponentRequirements = new(); - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("prototype")] - public string? Prototype { get; private set; } - - public IEnumerable> MaterialRequirements - { - get - { - foreach (var (materialId, amount) in MaterialIdRequirements) - { - var material = _prototypeManager.Index(materialId); - yield return new KeyValuePair(material, amount); - } - } - } - } - - [Serializable] - [DataDefinition] - public partial struct GenericPartInfo - { - [DataField("Amount")] - public int Amount; - [DataField("ExamineName")] - public string ExamineName; - [DataField("DefaultPrototype")] - public string DefaultPrototype; - } + /// + /// The machine that's constructed when this machine board is completed. + /// + [DataField(required: true)] + public EntProtoId Prototype; +} + +[DataDefinition, Serializable] +public partial struct GenericPartInfo +{ + [DataField(required: true)] + public int Amount; + + [DataField(required: true)] + public EntProtoId DefaultPrototype; + + [DataField] + public LocId? ExamineName; } diff --git a/Content.Shared/Construction/Components/MachinePartComponent.cs b/Content.Shared/Construction/Components/MachinePartComponent.cs deleted file mode 100644 index a68e3495e2..0000000000 --- a/Content.Shared/Construction/Components/MachinePartComponent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Shared.Construction.Prototypes; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Construction.Components -{ - [RegisterComponent, NetworkedComponent] - public sealed partial class MachinePartComponent : Component - { - [DataField("part", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string PartType { get; private set; } = default!; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("rating")] - public int Rating { get; private set; } = 1; - - /// - /// This number is used in tests to ensure that you can't use high quality machines for arbitrage. In - /// principle there is nothing wrong with using higher quality parts, but you have to be careful to not - /// allow them to be put into a lathe or something like that. - /// - public const int MaxRating = 4; - } -} diff --git a/Content.Shared/Construction/MachinePartSystem.cs b/Content.Shared/Construction/MachinePartSystem.cs index 359b58c881..f357a395e6 100644 --- a/Content.Shared/Construction/MachinePartSystem.cs +++ b/Content.Shared/Construction/MachinePartSystem.cs @@ -1,10 +1,8 @@ using System.Linq; using Content.Shared.Construction.Components; -using Content.Shared.Construction.Prototypes; using Content.Shared.Examine; using Content.Shared.Lathe; using Content.Shared.Materials; -using Content.Shared.Stacks; using Robust.Shared.Prototypes; namespace Content.Shared.Construction @@ -16,6 +14,7 @@ namespace Content.Shared.Construction { [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly SharedLatheSystem _lathe = default!; + [Dependency] private readonly SharedConstructionSystem _construction = default!; public override void Initialize() { @@ -31,32 +30,30 @@ namespace Content.Shared.Construction using (args.PushGroup(nameof(MachineBoardComponent))) { args.PushMarkup(Loc.GetString("machine-board-component-on-examine-label")); - foreach (var (part, amount) in component.Requirements) + foreach (var (material, amount) in component.StackRequirements) { - args.PushMarkup(Loc.GetString("machine-board-component-required-element-entry-text", - ("amount", amount), - ("requiredElement", Loc.GetString(_prototype.Index(part).Name)))); - } + var stack = _prototype.Index(material); + var name = _prototype.Index(stack.Spawn).Name; - foreach (var (material, amount) in component.MaterialRequirements) - { args.PushMarkup(Loc.GetString("machine-board-component-required-element-entry-text", ("amount", amount), - ("requiredElement", Loc.GetString(material.Name)))); + ("requiredElement", Loc.GetString(name)))); } foreach (var (_, info) in component.ComponentRequirements) { + var examineName = _construction.GetExamineName(info); args.PushMarkup(Loc.GetString("machine-board-component-required-element-entry-text", ("amount", info.Amount), - ("requiredElement", Loc.GetString(info.ExamineName)))); + ("requiredElement", examineName))); } foreach (var (_, info) in component.TagRequirements) { + var examineName = _construction.GetExamineName(info); args.PushMarkup(Loc.GetString("machine-board-component-required-element-entry-text", ("amount", info.Amount), - ("requiredElement", Loc.GetString(info.ExamineName)))); + ("requiredElement", examineName))); } } } @@ -66,30 +63,13 @@ namespace Content.Shared.Construction var (_, comp) = entity; var materials = new Dictionary(); - foreach (var (partId, amount) in comp.Requirements) + + foreach (var (stackId, amount) in comp.StackRequirements) { - var partProto = _prototype.Index(partId); - - if (!_lathe.TryGetRecipesFromEntity(partProto.StockPartPrototype, out var recipes)) - continue; - - var partRecipe = recipes[0]; - if (recipes.Count > 1) - partRecipe = recipes.MinBy(p => p.RequiredMaterials.Values.Sum()); - - foreach (var (mat, matAmount) in partRecipe!.RequiredMaterials) - { - materials.TryAdd(mat, 0); - materials[mat] += matAmount * amount * coefficient; - } - } - - foreach (var (stackId, amount) in comp.MaterialIdRequirements) - { - var stackProto = _prototype.Index(stackId); + var stackProto = _prototype.Index(stackId); var defaultProto = _prototype.Index(stackProto.Spawn); - if (defaultProto.TryGetComponent(out var physComp)) + if (defaultProto.TryGetComponent(out var physComp, EntityManager.ComponentFactory)) { foreach (var (mat, matAmount) in physComp.MaterialComposition) { @@ -130,7 +110,7 @@ namespace Content.Shared.Construction } } else if (_prototype.TryIndex(defaultProtoId, out var defaultProto) && - defaultProto.TryGetComponent(out var physComp)) + defaultProto.TryGetComponent(out var physComp, EntityManager.ComponentFactory)) { foreach (var (mat, matAmount) in physComp.MaterialComposition) { diff --git a/Content.Shared/Construction/Prototypes/MachinePartPrototype.cs b/Content.Shared/Construction/Prototypes/MachinePartPrototype.cs deleted file mode 100644 index 7a080c94a3..0000000000 --- a/Content.Shared/Construction/Prototypes/MachinePartPrototype.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Construction.Prototypes; - -/// -/// This is a prototype for categorizing -/// different types of machine parts. -/// -[Prototype("machinePart")] -public sealed partial class MachinePartPrototype : IPrototype -{ - /// - [IdDataField] - public string ID { get; private set; } = default!; - - /// - /// A human-readable name for the machine part type. - /// - [DataField("name")] - public string Name = string.Empty; - - /// - /// A stock part entity based on the machine part. - /// - [DataField("stockPartPrototype", customTypeSerializer: typeof(PrototypeIdSerializer), required: true)] - public string StockPartPrototype = string.Empty; -} diff --git a/Content.Shared/Construction/SharedConstructionSystem.cs b/Content.Shared/Construction/SharedConstructionSystem.cs index 069290d143..901b2f834d 100644 --- a/Content.Shared/Construction/SharedConstructionSystem.cs +++ b/Content.Shared/Construction/SharedConstructionSystem.cs @@ -1,5 +1,7 @@ using System.Linq; +using Content.Shared.Construction.Components; using Robust.Shared.Map; +using Robust.Shared.Prototypes; using static Content.Shared.Interaction.SharedInteractionSystem; namespace Content.Shared.Construction @@ -7,6 +9,7 @@ namespace Content.Shared.Construction public abstract class SharedConstructionSystem : EntitySystem { [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; /// /// Get predicate for construction obstruction checks. @@ -22,5 +25,13 @@ namespace Content.Shared.Construction var ignored = grid.GetAnchoredEntities(coords).ToHashSet(); return e => ignored.Contains(e); } + + public string GetExamineName(GenericPartInfo info) + { + if (info.ExamineName is not null) + return Loc.GetString(info.ExamineName.Value); + + return PrototypeManager.Index(info.DefaultPrototype).Name; + } } } diff --git a/Content.Shared/Construction/SharedFlatpackSystem.cs b/Content.Shared/Construction/SharedFlatpackSystem.cs index 8b21bca52a..62d2089fb7 100644 --- a/Content.Shared/Construction/SharedFlatpackSystem.cs +++ b/Content.Shared/Construction/SharedFlatpackSystem.cs @@ -1,4 +1,3 @@ -using System.Numerics; using Content.Shared.Construction.Components; using Content.Shared.Administration.Logs; using Content.Shared.Database; @@ -9,7 +8,6 @@ using Content.Shared.Popups; using Content.Shared.Tools.Systems; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; -using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Network; using Robust.Shared.Physics.Components; @@ -88,7 +86,8 @@ public abstract class SharedFlatpackSystem : EntitySystem if (_net.IsServer) { var spawn = Spawn(comp.Entity, _map.GridTileToLocal(grid, gridComp, buildPos)); - _adminLogger.Add(LogType.Construction, LogImpact.Low, + _adminLogger.Add(LogType.Construction, + LogImpact.Low, $"{ToPrettyString(args.User):player} unpacked {ToPrettyString(spawn):entity} at {xform.Coordinates} from {ToPrettyString(uid):entity}"); QueueDel(uid); } @@ -115,9 +114,7 @@ public abstract class SharedFlatpackSystem : EntitySystem return; var machinePrototypeId = new EntProtoId(); - if (TryComp(board, out var machineBoard) && machineBoard.Prototype is not null) - machinePrototypeId = machineBoard.Prototype; - else if (TryComp(board, out var computerBoard) && computerBoard.Prototype is not null) + if (TryComp(board, out var computerBoard) && computerBoard.Prototype is not null) machinePrototypeId = computerBoard.Prototype; var comp = ent.Comp!; diff --git a/Content.Shared/Lathe/LatheComponent.cs b/Content.Shared/Lathe/LatheComponent.cs index d4bf18b050..88ac8c05bf 100644 --- a/Content.Shared/Lathe/LatheComponent.cs +++ b/Content.Shared/Lathe/LatheComponent.cs @@ -59,8 +59,6 @@ namespace Content.Shared.Lathe /// [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float MaterialUseMultiplier = 1; - - public const float DefaultPartRatingMaterialUseMultiplier = 0.85f; #endregion } diff --git a/Resources/Locale/en-US/construction/steps/arbitrary-insert-construction-graph-step.ftl b/Resources/Locale/en-US/construction/steps/arbitrary-insert-construction-graph-step.ftl index 6a7998e57a..430888ed36 100644 --- a/Resources/Locale/en-US/construction/steps/arbitrary-insert-construction-graph-step.ftl +++ b/Resources/Locale/en-US/construction/steps/arbitrary-insert-construction-graph-step.ftl @@ -1,2 +1,10 @@ # Shown when examining an in-construction object -construction-insert-arbitrary-entity = Next, insert {$stepName}. \ No newline at end of file +construction-insert-arbitrary-entity = Next, insert {$stepName}. + +construction-insert-info-examine-name-instrument-brass = brass instrument +construction-insert-info-examine-name-instrument-keyed = keyed instrument +construction-insert-info-examine-name-instrument-percussion = percussion instrument +construction-insert-info-examine-name-instrument-string = string intrument +construction-insert-info-examine-name-instrument-woodwind = woodwind instrument +construction-insert-info-examine-name-knife = knife +construction-insert-info-examine-name-utensil = utensil diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/cannons.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/cannons.yml index 6db7f4b385..e4bde7ab84 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/cannons.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/cannons.yml @@ -11,10 +11,9 @@ state: security - type: MachineBoard prototype: ShuttleGunSvalinnMachineGun - requirements: + stackRequirements: MatterBin: 2 Manipulator: 4 - materialRequirements: Steel: 5 CableHV: 5 @@ -29,10 +28,9 @@ state: security - type: MachineBoard prototype: ShuttleGunPerforator - requirements: + stackRequirements: MatterBin: 4 Manipulator: 6 - materialRequirements: Steel: 10 CableHV: 5 @@ -47,10 +45,9 @@ state: security - type: MachineBoard prototype: ShuttleGunFriendship - requirements: + stackRequirements: MatterBin: 3 Manipulator: 2 - materialRequirements: Steel: 7 CableHV: 5 @@ -65,10 +62,9 @@ state: security - type: MachineBoard prototype: ShuttleGunDuster - requirements: + stackRequirements: MatterBin: 6 Manipulator: 4 - materialRequirements: Steel: 10 CableHV: 5 Uranium: 2 @@ -84,9 +80,8 @@ state: security - type: MachineBoard prototype: ShuttleGunKinetic - requirements: + stackRequirements: MatterBin: 2 Manipulator: 3 - materialRequirements: Steel: 5 - CableHV: 2 \ No newline at end of file + CableHV: 2 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/particle_accelerator.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/particle_accelerator.yml index 78e17e0745..a1260f07c0 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/particle_accelerator.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/particle_accelerator.yml @@ -8,7 +8,7 @@ state: engineering - type: MachineBoard prototype: ParticleAcceleratorEndCapUnfinished - materialRequirements: + stackRequirements: Glass: 15 Steel: 15 @@ -24,10 +24,9 @@ prototype: ParticleAcceleratorFuelChamberUnfinished componentRequirements: AmeFuelContainer: - Amount: 1 - DefaultPrototype: AmeJar - ExamineName: AME Fuel Jar - materialRequirements: + amount: 1 + defaultPrototype: AmeJar + stackRequirements: Glass: 10 Steel: 10 @@ -41,7 +40,7 @@ state: engineering - type: MachineBoard prototype: ParticleAcceleratorPowerBoxUnfinished - materialRequirements: + stackRequirements: Glass: 5 Steel: 5 @@ -57,7 +56,7 @@ state: engineering - type: MachineBoard prototype: ParticleAcceleratorEmitterStarboardUnfinished - materialRequirements: + stackRequirements: Glass: 5 Steel: 5 @@ -71,7 +70,7 @@ state: engineering - type: MachineBoard prototype: ParticleAcceleratorEmitterForeUnfinished - materialRequirements: + stackRequirements: Glass: 5 Steel: 5 @@ -85,6 +84,6 @@ state: engineering - type: MachineBoard prototype: ParticleAcceleratorEmitterPortUnfinished - materialRequirements: + stackRequirements: Glass: 5 Steel: 5 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index 587e56865b..d41923b569 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -6,10 +6,9 @@ components: - type: MachineBoard prototype: Autolathe - requirements: + stackRequirements: MatterBin: 3 Manipulator: 1 - materialRequirements: Glass: 1 - type: entity @@ -20,15 +19,13 @@ components: - type: MachineBoard prototype: AutolatheHyperConvection - requirements: + stackRequirements: MatterBin: 3 - materialRequirements: Glass: 1 tagRequirements: Igniter: - Amount: 1 - DefaultPrototype: Igniter - ExamineName: Igniter + amount: 1 + defaultPrototype: Igniter - type: entity id: ProtolatheMachineCircuitboard @@ -38,14 +35,13 @@ components: - type: MachineBoard prototype: Protolathe - requirements: + stackRequirements: MatterBin: 2 Manipulator: 2 tagRequirements: GlassBeaker: - Amount: 2 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 2 + defaultPrototype: Beaker - type: entity parent: BaseMachineCircuitboard @@ -55,17 +51,15 @@ components: - type: MachineBoard prototype: ProtolatheHyperConvection - requirements: + stackRequirements: MatterBin: 2 tagRequirements: GlassBeaker: - Amount: 2 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 2 + defaultPrototype: Beaker Igniter: - Amount: 1 - DefaultPrototype: Igniter - ExamineName: Igniter + amount: 1 + defaultPrototype: Igniter - type: entity id: BiofabricatorMachineCircuitboard @@ -75,9 +69,8 @@ components: - type: MachineBoard prototype: Biofabricator - requirements: + stackRequirements: MatterBin: 4 - materialRequirements: Glass: 1 - type: entity @@ -90,14 +83,13 @@ state: security - type: MachineBoard prototype: SecurityTechFab - requirements: + stackRequirements: MatterBin: 2 Manipulator: 2 tagRequirements: GlassBeaker: - Amount: 2 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 2 + defaultPrototype: Beaker - type: entity id: AmmoTechFabCircuitboard @@ -109,7 +101,7 @@ state: security - type: MachineBoard prototype: AmmoTechFab - requirements: + stackRequirements: MatterBin: 1 Manipulator: 1 @@ -123,14 +115,13 @@ state: medical - type: MachineBoard prototype: MedicalTechFab - requirements: + stackRequirements: MatterBin: 2 Manipulator: 2 tagRequirements: GlassBeaker: - Amount: 2 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 2 + defaultPrototype: Beaker - type: StealTarget stealGroup: MedicalTechFabCircuitboard @@ -143,14 +134,13 @@ state: science - type: MachineBoard prototype: CircuitImprinter - requirements: + stackRequirements: MatterBin: 1 Manipulator: 1 tagRequirements: GlassBeaker: - Amount: 2 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 2 + defaultPrototype: Beaker - type: entity parent: BaseMachineCircuitboard @@ -162,17 +152,15 @@ state: science - type: MachineBoard prototype: CircuitImprinterHyperConvection - requirements: + stackRequirements: MatterBin: 2 tagRequirements: GlassBeaker: - Amount: 2 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 2 + defaultPrototype: Beaker Igniter: - Amount: 1 - DefaultPrototype: Igniter - ExamineName: Igniter + amount: 1 + defaultPrototype: Igniter - type: entity id: ExosuitFabricatorMachineCircuitboard @@ -183,10 +171,9 @@ state: science - type: MachineBoard prototype: ExosuitFabricator - requirements: + stackRequirements: MatterBin: 1 Manipulator: 3 - materialRequirements: Glass: 5 - type: GuideHelp guides: @@ -203,7 +190,7 @@ state: science - type: MachineBoard prototype: ResearchAndDevelopmentServer - materialRequirements: + stackRequirements: Plasma: 5 - type: entity @@ -213,7 +200,7 @@ components: - type: MachineBoard prototype: UniformPrinter - requirements: + stackRequirements: MatterBin: 1 Manipulator: 2 @@ -226,16 +213,14 @@ state: medical - type: MachineBoard prototype: Vaccinator - requirements: + stackRequirements: MatterBin: 1 Manipulator: 1 - materialRequirements: Cable: 5 tagRequirements: GlassBeaker: - Amount: 1 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 1 + defaultPrototype: Beaker - type: entity id: DiagnoserMachineCircuitboard @@ -246,18 +231,16 @@ state: medical - type: MachineBoard prototype: DiseaseDiagnoser - materialRequirements: + stackRequirements: Cable: 5 tagRequirements: GlassBeaker: - Amount: 1 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 1 + defaultPrototype: Beaker componentRequirements: - DiseaseSwab: - Amount: 1 - DefaultPrototype: DiseaseSwab - ExamineName: Swab + BotanySwab: + amount: 1 + defaultPrototype: DiseaseSwab - type: entity id: ArtifactAnalyzerMachineCircuitboard @@ -269,10 +252,9 @@ state: science - type: MachineBoard prototype: MachineArtifactAnalyzer - requirements: + stackRequirements: Manipulator: 3 Capacitor: 1 - materialRequirements: Glass: 5 - type: entity @@ -285,9 +267,8 @@ state: science - type: MachineBoard prototype: MachineArtifactCrusher - requirements: + stackRequirements: Manipulator: 2 - materialRequirements: Glass: 1 Steel: 5 @@ -301,9 +282,8 @@ state: science - type: MachineBoard prototype: MachineAnomalyVessel - requirements: + stackRequirements: Capacitor: 3 - materialRequirements: Cable: 1 PlasmaGlass: 10 @@ -317,9 +297,8 @@ state: science - type: MachineBoard prototype: MachineAnomalyVesselExperimental - requirements: + stackRequirements: Capacitor: 3 - materialRequirements: Cable: 5 PlasmaGlass: 15 MetalRod: 4 @@ -334,10 +313,9 @@ state: science - type: MachineBoard prototype: MachineAnomalySynchronizer - requirements: + stackRequirements: Manipulator: 2 Capacitor: 5 - materialRequirements: PlasmaGlass: 5 Cable: 5 @@ -351,9 +329,8 @@ state: science - type: MachineBoard prototype: MachineAPE - requirements: + stackRequirements: Capacitor: 2 - materialRequirements: Cable: 1 Glass: 1 @@ -367,10 +344,9 @@ state: engineering - type: MachineBoard prototype: GasThermoMachineFreezer - requirements: + stackRequirements: MatterBin: 2 Capacitor: 2 - materialRequirements: Cable: 5 - type: Construction deconstructionTarget: null @@ -387,10 +363,9 @@ state: engineering - type: MachineBoard prototype: GasThermoMachineHeater - requirements: + stackRequirements: MatterBin: 2 Capacitor: 2 - materialRequirements: Cable: 5 - type: Construction graph: ThermomachineBoard @@ -407,10 +382,9 @@ state: engineering - type: MachineBoard prototype: GasThermoMachineHellfireFreezer - requirements: + stackRequirements: MatterBin: 2 Capacitor: 2 - materialRequirements: Plasma: 1 - type: Construction deconstructionTarget: null @@ -427,10 +401,9 @@ state: engineering - type: MachineBoard prototype: GasThermoMachineHellfireHeater - requirements: + stackRequirements: MatterBin: 2 Capacitor: 2 - materialRequirements: Plasma: 1 - type: Construction graph: ThermomachineBoard @@ -447,9 +420,8 @@ state: engineering - type: MachineBoard prototype: BaseGasCondenser - requirements: + stackRequirements: MatterBin: 1 - materialRequirements: Glass: 1 - type: entity @@ -462,10 +434,9 @@ state: engineering - type: MachineBoard prototype: PortableScrubber - requirements: + stackRequirements: MatterBin: 1 Manipulator: 2 - materialRequirements: Cable: 5 Glass: 2 @@ -479,10 +450,9 @@ state: engineering - type: MachineBoard prototype: SpaceHeater - requirements: + stackRequirements: MatterBin: 1 Capacitor: 2 - materialRequirements: Cable: 5 - type: entity @@ -495,10 +465,9 @@ state: medical - type: MachineBoard prototype: CloningPod - requirements: + stackRequirements: MatterBin: 2 Manipulator: 2 - materialRequirements: Glass: 1 Cable: 1 @@ -512,9 +481,8 @@ state: medical - type: MachineBoard prototype: MedicalScanner - requirements: + stackRequirements: Capacitor: 1 - materialRequirements: Glass: 5 Cable: 1 @@ -526,7 +494,7 @@ components: - type: MachineBoard prototype: CrewMonitoringServer - materialRequirements: + stackRequirements: Steel: 1 Cable: 2 @@ -540,7 +508,7 @@ state: medical - type: MachineBoard prototype: CryoPod - materialRequirements: + stackRequirements: Glass: 5 Cable: 1 @@ -554,16 +522,14 @@ state: medical - type: MachineBoard prototype: ChemMaster - requirements: + stackRequirements: Capacitor: 1 - materialRequirements: Glass: 1 Cable: 1 tagRequirements: GlassBeaker: - Amount: 2 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 2 + defaultPrototype: Beaker - type: entity id: ChemDispenserMachineCircuitboard @@ -575,16 +541,14 @@ state: medical - type: MachineBoard prototype: ChemDispenserEmpty - requirements: + stackRequirements: Capacitor: 1 - materialRequirements: Glass: 1 Steel: 3 tagRequirements: GlassBeaker: - Amount: 2 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 2 + defaultPrototype: Beaker - type: entity id: BiomassReclaimerMachineCircuitboard @@ -594,16 +558,15 @@ components: - type: MachineBoard prototype: BiomassReclaimer - requirements: + stackRequirements: MatterBin: 2 Manipulator: 1 + Steel: 5 tagRequirements: Knife: - Amount: 2 - DefaultPrototype: KitchenKnife - ExamineName: Knife - materialRequirements: - Steel: 5 + amount: 2 + defaultPrototype: KitchenKnife + examineName: construction-insert-info-examine-name-knife - type: entity id: HydroponicsTrayMachineCircuitboard @@ -615,15 +578,14 @@ state: service - type: MachineBoard prototype: HydroponicsTrayEmpty - materialRequirements: + stackRequirements: # replacing the console screen Glass: 5 Cable: 2 tagRequirements: GlassBeaker: - Amount: 2 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 2 + defaultPrototype: Beaker - type: entity id: SeedExtractorMachineCircuitboard @@ -635,10 +597,9 @@ state: service - type: MachineBoard prototype: SeedExtractor - requirements: + stackRequirements: Manipulator: 2 Capacitor: 1 - materialRequirements: # replacing the console screen Glass: 1 Cable: 2 @@ -654,11 +615,13 @@ state: power_mod - type: MachineBoard prototype: SMESBasicEmpty - requirements: + stackRequirements: Capacitor: 1 - PowerCell: 4 - materialRequirements: CableHV: 10 + componentRequirements: + PowerCell: + amount: 4 + defaultPrototype: PowerCellSmall - type: entity id: CellRechargerCircuitboard @@ -671,9 +634,8 @@ state: charger_APC - type: MachineBoard prototype: PowerCellRecharger - requirements: + stackRequirements: Capacitor: 2 - materialRequirements: Cable: 5 - type: PhysicalComposition materialComposition: @@ -693,9 +655,8 @@ state: charger_APC - type: MachineBoard prototype: PowerCageRecharger - requirements: + stackRequirements: Capacitor: 4 - materialRequirements: Steel: 5 Cable: 10 - type: PhysicalComposition @@ -716,9 +677,8 @@ state: charger_APC - type: MachineBoard prototype: BorgCharger - requirements: + stackRequirements: Capacitor: 2 - materialRequirements: Cable: 5 - type: PhysicalComposition materialComposition: @@ -738,9 +698,8 @@ state: charger_APC - type: MachineBoard prototype: WeaponCapacitorRecharger - requirements: + stackRequirements: Capacitor: 2 - materialRequirements: CableMV: 5 - type: PhysicalComposition materialComposition: @@ -760,9 +719,8 @@ state: charger_APC - type: MachineBoard prototype: TurboItemRecharger - requirements: + stackRequirements: Capacitor: 2 - materialRequirements: CableMV: 5 - type: PhysicalComposition materialComposition: @@ -777,12 +735,14 @@ components: - type: MachineBoard prototype: SubstationBasicEmpty - requirements: + stackRequirements: Capacitor: 1 - PowerCell: 1 - materialRequirements: CableMV: 5 CableHV: 5 + componentRequirements: + PowerCell: + amount: 1 + defaultPrototype: PowerCellSmall - type: PhysicalComposition materialComposition: Glass: 200 @@ -798,31 +758,31 @@ components: - type: MachineBoard prototype: DawInstrument - materialRequirements: + stackRequirements: Glass: 1 Cable: 1 tagRequirements: # One instrument to bring them all and in the darkness bind them... KeyedInstrument: - Amount: 1 - DefaultPrototype: SynthesizerInstrument - ExamineName: Keyed Instrument + amount: 1 + defaultPrototype: SynthesizerInstrument + examineName: construction-insert-info-examine-name-instrument-keyed StringInstrument: - Amount: 1 - DefaultPrototype: AcousticGuitarInstrument - ExamineName: String Instrument + amount: 1 + defaultPrototype: AcousticGuitarInstrument + examineName: construction-insert-info-examine-name-instrument-string PercussionInstrument: - Amount: 1 - DefaultPrototype: GlockenspielInstrument - ExamineName: Percussion Instrument + amount: 1 + defaultPrototype: GlockenspielInstrument + examineName: construction-insert-info-examine-name-instrument-percussion BrassInstrument: - Amount: 1 - DefaultPrototype: TrumpetInstrument - ExamineName: Brass Instrument + amount: 1 + defaultPrototype: TrumpetInstrument + examineName: construction-insert-info-examine-name-instrument-brass WoodwindInstrument: - Amount: 1 - DefaultPrototype: SaxophoneInstrument - ExamineName: Woodwind Instrument + amount: 1 + defaultPrototype: SaxophoneInstrument + examineName: construction-insert-info-examine-name-instrument-woodwind - type: entity id: PortableGeneratorPacmanMachineCircuitboard @@ -833,9 +793,8 @@ state: engineering - type: MachineBoard prototype: PortableGeneratorPacman - requirements: + stackRequirements: Capacitor: 1 - materialRequirements: CableHV: 5 - type: PhysicalComposition materialComposition: @@ -852,9 +811,8 @@ components: - type: MachineBoard prototype: Thruster - requirements: + stackRequirements: Capacitor: 4 - materialRequirements: Steel: 5 - type: entity @@ -864,10 +822,9 @@ components: - type: MachineBoard prototype: Gyroscope - requirements: + stackRequirements: Manipulator: 2 Capacitor: 1 - materialRequirements: Glass: 2 - type: entity @@ -879,9 +836,8 @@ state: engineering - type: MachineBoard prototype: PortableGeneratorSuperPacman - requirements: + stackRequirements: Capacitor: 2 - materialRequirements: CableHV: 10 - type: PhysicalComposition materialComposition: @@ -900,9 +856,8 @@ state: engineering - type: MachineBoard prototype: PortableGeneratorJrPacman - requirements: + stackRequirements: Capacitor: 1 - materialRequirements: Cable: 10 - type: PhysicalComposition materialComposition: @@ -920,14 +875,13 @@ components: - type: MachineBoard prototype: KitchenReagentGrinder - requirements: + stackRequirements: MatterBin: 2 Manipulator: 2 tagRequirements: GlassBeaker: - Amount: 1 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 1 + defaultPrototype: Beaker - type: entity id: HotplateMachineCircuitboard @@ -937,9 +891,8 @@ components: - type: MachineBoard prototype: ChemistryHotplate - requirements: + stackRequirements: Capacitor: 2 - materialRequirements: Glass: 1 - type: entity @@ -950,9 +903,8 @@ components: - type: MachineBoard prototype: KitchenElectricGrill - requirements: + stackRequirements: Capacitor: 4 - materialRequirements: Glass: 2 Cable: 5 @@ -965,10 +917,9 @@ state: medical - type: MachineBoard prototype: StasisBed - requirements: + stackRequirements: Capacitor: 1 Manipulator: 1 - materialRequirements: Cable: 3 Steel: 2 @@ -982,9 +933,8 @@ state: medical - type: MachineBoard prototype: MachineElectrolysisUnit - requirements: + stackRequirements: Capacitor: 2 - materialRequirements: Cable: 1 - type: entity @@ -997,9 +947,8 @@ state: medical - type: MachineBoard prototype: MachineCentrifuge - requirements: + stackRequirements: Manipulator: 1 - materialRequirements: Steel: 1 - type: entity @@ -1011,9 +960,8 @@ state: supply - type: MachineBoard prototype: MaterialReclaimer - requirements: + stackRequirements: Manipulator: 2 - materialRequirements: Steel: 5 Plastic: 5 @@ -1026,10 +974,9 @@ state: supply - type: MachineBoard prototype: OreProcessor - requirements: + stackRequirements: MatterBin: 1 Manipulator: 3 - materialRequirements: Glass: 1 - type: entity @@ -1041,10 +988,9 @@ state: supply - type: MachineBoard prototype: OreProcessorIndustrial - requirements: + stackRequirements: MatterBin: 1 Manipulator: 3 - materialRequirements: Glass: 1 - type: entity @@ -1054,7 +1000,7 @@ components: - type: MachineBoard prototype: Sheetifier - requirements: + stackRequirements: MatterBin: 1 Manipulator: 1 @@ -1067,9 +1013,8 @@ state: service - type: MachineBoard prototype: KitchenMicrowave - requirements: + stackRequirements: Capacitor: 1 - materialRequirements: Glass: 2 Cable: 2 - type: Tag @@ -1085,13 +1030,13 @@ state: service - type: MachineBoard prototype: FatExtractor - requirements: + stackRequirements: Manipulator: 1 componentRequirements: Utensil: - Amount: 1 - DefaultPrototype: ForkPlastic - ExamineName: Utensil + amount: 1 + defaultPrototype: ForkPlastic + examineName: construction-insert-info-examine-name-utensil - type: entity parent: BaseMachineCircuitboard @@ -1100,10 +1045,9 @@ components: - type: MachineBoard prototype: MachineFlatpacker - requirements: + stackRequirements: Manipulator: 2 MatterBin: 1 - materialRequirements: Steel: 1 - type: entity @@ -1115,9 +1059,8 @@ state: engineering - type: MachineBoard prototype: Emitter - requirements: + stackRequirements: Capacitor: 2 - materialRequirements: CableHV: 5 Glass: 2 @@ -1129,7 +1072,7 @@ components: - type: MachineBoard prototype: SurveillanceCameraRouterConstructed - materialRequirements: + stackRequirements: Cable: 1 - type: entity @@ -1140,7 +1083,7 @@ components: - type: MachineBoard prototype: SurveillanceCameraWirelessRouterConstructed - materialRequirements: + stackRequirements: Cable: 2 Glass: 1 @@ -1152,7 +1095,7 @@ components: - type: MachineBoard prototype: SurveillanceWirelessCameraMovableConstructed - materialRequirements: + stackRequirements: Glass: 2 Cable: 2 @@ -1164,7 +1107,7 @@ components: - type: MachineBoard prototype: SurveillanceWirelessCameraAnchoredConstructed - materialRequirements: + stackRequirements: Cable: 2 Glass: 1 @@ -1176,10 +1119,9 @@ components: - type: MachineBoard prototype: GasRecycler - requirements: + stackRequirements: Capacitor: 1 Manipulator: 1 - materialRequirements: Steel: 10 Plasma: 5 @@ -1193,13 +1135,12 @@ state: service - type: MachineBoard prototype: BoozeDispenserEmpty - materialRequirements: + stackRequirements: Steel: 5 tagRequirements: GlassBeaker: - Amount: 1 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 1 + defaultPrototype: Beaker - type: entity id: CargoTelepadMachineCircuitboard @@ -1211,9 +1152,8 @@ state: supply - type: MachineBoard prototype: CargoTelepad - requirements: + stackRequirements: Capacitor: 2 - materialRequirements: Steel: 5 - type: entity @@ -1226,13 +1166,12 @@ state: service - type: MachineBoard prototype: SodaDispenserEmpty - materialRequirements: + stackRequirements: Steel: 5 tagRequirements: GlassBeaker: - Amount: 1 - DefaultPrototype: Beaker - ExamineName: Glass Beaker + amount: 1 + defaultPrototype: Beaker - type: entity id: TelecomServerCircuitboard @@ -1242,7 +1181,7 @@ components: - type: MachineBoard prototype: TelecomServer - materialRequirements: + stackRequirements: Steel: 1 Cable: 2 @@ -1254,9 +1193,8 @@ components: - type: MachineBoard prototype: SalvageMagnet - requirements: + stackRequirements: Capacitor: 4 - materialRequirements: Steel: 5 CableHV: 5 Cable: 2 @@ -1269,10 +1207,9 @@ components: - type: MachineBoard prototype: GravityGeneratorMini - requirements: + stackRequirements: Capacitor: 4 MatterBin: 3 - materialRequirements: Steel: 5 CableHV: 5 Uranium: 2 @@ -1284,10 +1221,9 @@ components: - type: MachineBoard prototype: ReagentGrinderIndustrial - requirements: + stackRequirements: MatterBin: 1 Manipulator: 3 - materialRequirements: Glass: 1 - type: entity @@ -1298,7 +1234,7 @@ components: - type: MachineBoard prototype: Jukebox - materialRequirements: + stackRequirements: WoodPlank: 5 Steel: 2 Glass: 5 diff --git a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml index 37de294cce..2f257db396 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml @@ -21,9 +21,6 @@ components: - type: Sprite state: capacitor - - type: MachinePart - part: Capacitor - rating: 1 - type: Tag tags: - CapacitorStockPart @@ -39,11 +36,8 @@ components: - type: Sprite state: micro_mani - - type: MachinePart - part: Manipulator - rating: 1 - type: Stack - stackType: MicroManipulator + stackType: Manipulator - type: entity id: MatterBinStockPart @@ -54,8 +48,5 @@ components: - type: Sprite state: matter_bin - - type: MachinePart - part: MatterBin - rating: 1 - type: Stack stackType: MatterBin diff --git a/Resources/Prototypes/Entities/Objects/Power/powercells.yml b/Resources/Prototypes/Entities/Objects/Power/powercells.yml index 7397bcaa51..90f987fdc6 100644 --- a/Resources/Prototypes/Entities/Objects/Power/powercells.yml +++ b/Resources/Prototypes/Entities/Objects/Power/powercells.yml @@ -73,9 +73,6 @@ - type: Battery maxCharge: 360 startingCharge: 360 - - type: MachinePart - part: PowerCell - rating: 1 - type: Tag tags: - PowerCellSmall @@ -114,9 +111,6 @@ - type: Battery maxCharge: 720 startingCharge: 720 - - type: MachinePart - part: PowerCell - rating: 2 - type: entity id: PowerCellMediumPrinted @@ -152,9 +146,6 @@ - type: Battery maxCharge: 1080 startingCharge: 1080 - - type: MachinePart - part: PowerCell - rating: 3 - type: entity id: PowerCellHighPrinted @@ -190,9 +181,6 @@ - type: Battery maxCharge: 1800 startingCharge: 1800 - - type: MachinePart - part: PowerCell - rating: 4 - type: entity id: PowerCellHyperPrinted diff --git a/Resources/Prototypes/MachineParts/machine_parts.yml b/Resources/Prototypes/MachineParts/machine_parts.yml deleted file mode 100644 index 317e4b8086..0000000000 --- a/Resources/Prototypes/MachineParts/machine_parts.yml +++ /dev/null @@ -1,20 +0,0 @@ -- type: machinePart - id: Capacitor - name: machine-part-name-capacitor - stockPartPrototype: CapacitorStockPart - -- type: machinePart - id: Manipulator - name: machine-part-name-manipulator - stockPartPrototype: MicroManipulatorStockPart - -- type: machinePart - id: MatterBin - name: machine-part-name-matter-bin - stockPartPrototype: MatterBinStockPart - -- type: machinePart - id: PowerCell - name: machine-part-name-power-cell - stockPartPrototype: PowerCellSmall - diff --git a/Resources/Prototypes/Stacks/science_stacks.yml b/Resources/Prototypes/Stacks/science_stacks.yml index bf58fad915..0d273c324e 100644 --- a/Resources/Prototypes/Stacks/science_stacks.yml +++ b/Resources/Prototypes/Stacks/science_stacks.yml @@ -11,7 +11,7 @@ maxCount: 10 - type: stack - id: MicroManipulator + id: Manipulator name: micro manipulator spawn: MicroManipulatorStockPart maxCount: 10 From c368d5d36f4cf086fc39f026acca762dbfe9b4f8 Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:47:27 -0500 Subject: [PATCH 079/243] Make Projectiles Only Hit a Variety of Station Objects Unless Clicked on (#28571) --- .../Fun/Instruments/base_instruments.yml | 3 ++- .../Dispensers/base_structuredispensers.yml | 2 +- .../Computers/base_structurecomputers.yml | 1 + .../Machines/Medical/chemistry_machines.yml | 2 +- .../Machines/Medical/disease_diagnoser.yml | 4 +--- .../Machines/Medical/vaccinator.yml | 1 + .../Structures/Machines/artifact_analyzer.yml | 3 ++- .../Machines/base_structuremachines.yml | 7 +++++++ .../Structures/Machines/fax_machine.yml | 1 + .../Entities/Structures/Machines/lathe.yml | 1 + .../Structures/Machines/microwave.yml | 4 ++-- .../Structures/Machines/reagent_grinder.yml | 2 +- .../Machines/wireless_surveillance_camera.yml | 4 +++- .../Piping/Atmospherics/portable.yml | 2 +- .../Structures/Piping/Atmospherics/unary.yml | 2 +- .../Structures/Piping/Disposal/units.yml | 1 + .../Power/Generation/Singularity/emitter.yml | 2 +- .../Power/Generation/portable_generator.yml | 4 ++-- .../Structures/Power/Generation/solar.yml | 3 +++ .../Entities/Structures/Power/chargers.yml | 3 +++ .../Structures/Storage/filing_cabinets.yml | 1 + .../Entities/Structures/Walls/fence_wood.yml | 19 +++++++++++++++++-- .../Entities/Structures/hydro_tray.yml | 4 +++- 23 files changed, 57 insertions(+), 19 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml index 122ff42eb2..614af2a488 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml @@ -1,4 +1,4 @@ -- type: entity +- type: entity abstract: true parent: BaseItem id: BaseHandheldInstrument @@ -71,6 +71,7 @@ - BulletImpassable - type: StaticPrice price: 300 + - type: RequireProjectileTarget - type: entity parent: BasePlaceableInstrument diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml b/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml index 082f5e0799..213ad47d88 100644 --- a/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml +++ b/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml @@ -1,7 +1,7 @@ - type: entity abstract: true id: ReagentDispenserBase - parent: ConstructibleMachine + parent: SmallConstructibleMachine placement: mode: SnapgridCenter components: diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml index a5e26463b9..204e06c860 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml @@ -60,3 +60,4 @@ ents: [] - type: LightningTarget priority: 1 + - type: RequireProjectileTarget diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/chemistry_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/chemistry_machines.yml index 23789730d0..65eaf04d78 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Medical/chemistry_machines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/chemistry_machines.yml @@ -1,6 +1,6 @@ - type: entity id: BaseTabletopChemicalMachine - parent: [ BaseMachinePowered, ConstructibleMachine ] + parent: [ BaseMachinePowered, SmallConstructibleMachine ] abstract: true components: - type: Transform diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/disease_diagnoser.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/disease_diagnoser.yml index e46c62053a..ad98f47e36 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Medical/disease_diagnoser.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/disease_diagnoser.yml @@ -1,6 +1,6 @@ - type: entity id: DiseaseDiagnoser - parent: [ BaseMachinePowered, ConstructibleMachine ] + parent: [ BaseMachinePowered, SmallConstructibleMachine ] name: Disease Diagnoser Delta Extreme description: A machine that analyzes disease samples. placement: @@ -43,5 +43,3 @@ contentMargin: 12.0, 0.0, 12.0, 0.0 # This is a narrow piece of paper maxWritableArea: 128.0, 0.0 - - diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/vaccinator.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/vaccinator.yml index 041bca7c90..53542cdfa9 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Medical/vaccinator.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/vaccinator.yml @@ -24,3 +24,4 @@ containers: machine_board: !type:Container machine_parts: !type:Container + - type: RequireProjectileTarget diff --git a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml index 8b0c578763..9c878c7e7c 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml @@ -1,6 +1,6 @@ - type: entity id: MachineArtifactAnalyzer - parent: [ BaseMachinePowered, ConstructibleMachine ] + parent: [ BaseMachinePowered, SmallConstructibleMachine ] name: artifact analyzer description: A platform capable of performing analysis on various types of artifacts. components: @@ -35,6 +35,7 @@ - Impassable - MidImpassable - LowImpassable + - BulletImpassable hard: False - type: Transform anchored: true diff --git a/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml b/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml index 621d9a1a7e..fb5ed4440a 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml @@ -70,3 +70,10 @@ - machine_board - type: LightningTarget priority: 1 + +- type: entity + abstract: true + parent: ConstructibleMachine + id: SmallConstructibleMachine + components: + - type: RequireProjectileTarget diff --git a/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml b/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml index 36be6451d2..849c9fa4f2 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml @@ -66,6 +66,7 @@ deviceNetId: Wireless receiveFrequencyId: Fax transmitFrequencyId: Fax + - type: RequireProjectileTarget # Special - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 575144a2c2..537c42346e 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -508,6 +508,7 @@ - Sheet - RawMaterial - Ingot + - type: RequireProjectileTarget - type: entity id: CircuitImprinterHyperConvection diff --git a/Resources/Prototypes/Entities/Structures/Machines/microwave.yml b/Resources/Prototypes/Entities/Structures/Machines/microwave.yml index fe4eb14518..994269f71b 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/microwave.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/microwave.yml @@ -1,6 +1,6 @@ -- type: entity +- type: entity id: KitchenMicrowave - parent: [ BaseMachinePowered, ConstructibleMachine ] + parent: [ BaseMachinePowered, SmallConstructibleMachine ] name: microwave description: It's magic. components: diff --git a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml index d6e7333313..28aa464d21 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml @@ -1,6 +1,6 @@ - type: entity id: KitchenReagentGrinder - parent: [ BaseMachinePowered, ConstructibleMachine ] + parent: [ BaseMachinePowered, SmallConstructibleMachine ] name: reagent grinder description: From BlenderTech. Will It Blend? Let's find out! suffix: grinder/juicer diff --git a/Resources/Prototypes/Entities/Structures/Machines/wireless_surveillance_camera.yml b/Resources/Prototypes/Entities/Structures/Machines/wireless_surveillance_camera.yml index fc8f31535c..95079b5c85 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/wireless_surveillance_camera.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/wireless_surveillance_camera.yml @@ -1,6 +1,6 @@ - type: entity abstract: true - parent: [ BaseStructureDynamic, ConstructibleMachine ] + parent: [ BaseStructureDynamic, SmallConstructibleMachine ] id: SurveillanceWirelessCameraBase name: wireless camera description: A camera. It's watching you. Kinda. @@ -23,6 +23,8 @@ density: 80 mask: - MachineMask + layer: + - BulletImpassable - type: SurveillanceCameraMicrophone blacklist: components: diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml index 200df727b3..87e71400f7 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml @@ -1,6 +1,6 @@ - type: entity id: PortableScrubber - parent: [BaseMachinePowered, ConstructibleMachine, StructureWheeled] + parent: [BaseMachinePowered, SmallConstructibleMachine, StructureWheeled] name: portable scrubber description: It scrubs, portably! components: diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml index d301f43c78..2b00fec246 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml @@ -383,7 +383,7 @@ board: HellfireHeaterMachineCircuitBoard - type: entity - parent: [ BaseMachinePowered, ConstructibleMachine ] + parent: [ BaseMachinePowered, SmallConstructibleMachine ] id: BaseGasCondenser name: condenser description: Condenses gases into liquids. Now we just need some plumbing. diff --git a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml index 2198c854a0..e7d3d3c997 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml @@ -84,6 +84,7 @@ enum.DisposalUnitUiKey.Key: type: DisposalUnitBoundUserInterface - type: RatKingRummageable + - type: RequireProjectileTarget - type: entity id: MailingUnit diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/emitter.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/emitter.yml index b999b2bded..6946dcbf83 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/emitter.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/emitter.yml @@ -1,7 +1,7 @@ - type: entity id: Emitter name: emitter - parent: ConstructibleMachine + parent: SmallConstructibleMachine description: A heavy duty industrial laser. Shoots non-stop when turned on. placement: mode: SnapgridCenter diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml index d735d9607c..86cfb0f799 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml @@ -1,4 +1,4 @@ -# +# # You can use this Desmos sheet to calculate fuel burn rate values: # https://www.desmos.com/calculator/qcektq5dqs # @@ -6,7 +6,7 @@ - type: entity abstract: true id: PortableGeneratorBase - parent: [ BaseMachine, ConstructibleMachine, StructureWheeled] + parent: [ BaseMachine, SmallConstructibleMachine, StructureWheeled] components: # Basic properties - type: Transform diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml index 5a28c4962c..c512266e97 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml @@ -49,6 +49,7 @@ onBump: false requirePower: true highVoltageNode: output + - type: RequireProjectileTarget - type: entity id: SolarPanel @@ -157,6 +158,7 @@ graph: SolarPanel node: solarassembly defaultTarget: solarpanel + - type: RequireProjectileTarget - type: entity id: SolarTracker @@ -201,3 +203,4 @@ - type: Construction graph: SolarPanel node: solartracker + - type: RequireProjectileTarget diff --git a/Resources/Prototypes/Entities/Structures/Power/chargers.yml b/Resources/Prototypes/Entities/Structures/Power/chargers.yml index 582a5b0dee..f5f0748b81 100644 --- a/Resources/Prototypes/Entities/Structures/Power/chargers.yml +++ b/Resources/Prototypes/Entities/Structures/Power/chargers.yml @@ -58,12 +58,15 @@ density: 500 mask: - TabletopMachineMask + layer: + - BulletImpassable - type: PowerChargerVisuals - type: ContainerContainer containers: charger_slot: !type:ContainerSlot machine_board: !type:Container machine_parts: !type:Container + - type: RequireProjectileTarget - type: entity parent: BaseItemRecharger diff --git a/Resources/Prototypes/Entities/Structures/Storage/filing_cabinets.yml b/Resources/Prototypes/Entities/Structures/Storage/filing_cabinets.yml index 08db462be0..cfda95fc2f 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/filing_cabinets.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/filing_cabinets.yml @@ -156,6 +156,7 @@ node: chestDrawer - type: StaticPrice price: 15 + - type: RequireProjectileTarget - type: entity abstract: true diff --git a/Resources/Prototypes/Entities/Structures/Walls/fence_wood.yml b/Resources/Prototypes/Entities/Structures/Walls/fence_wood.yml index 55b7e40803..41dbe21d5f 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/fence_wood.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/fence_wood.yml @@ -72,7 +72,7 @@ acts: [ "Destruction" ] - type: Climbable delay: 2.5 - + - type: RequireProjectileTarget #High - type: entity @@ -96,8 +96,10 @@ mask: - FullTileMask layer: + - Opaque - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: straight @@ -123,8 +125,10 @@ mask: - FullTileMask layer: + - Opaque - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: end @@ -159,8 +163,10 @@ mask: - TableMask layer: + - Opaque - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: corner @@ -195,8 +201,10 @@ mask: - TableMask layer: + - Opaque - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: tjunction @@ -221,8 +229,10 @@ mask: - FullTileMask layer: + - Opaque - MidImpassable - LowImpassable + - BulletImpassable - type: InteractionOutline - type: Door openSpriteState: door_opened @@ -271,6 +281,7 @@ layer: - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: straight_small @@ -298,6 +309,7 @@ layer: - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: end_small @@ -334,6 +346,7 @@ layer: - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: corner_small @@ -370,6 +383,7 @@ layer: - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: tjunction_small @@ -396,6 +410,7 @@ layer: - MidImpassable - LowImpassable + - BulletImpassable - type: InteractionOutline - type: Door openSpriteState: door_opened_small @@ -418,4 +433,4 @@ path: /Audio/Effects/door_close.ogg - type: Construction graph: FenceWood - node: gate_small \ No newline at end of file + node: gate_small diff --git a/Resources/Prototypes/Entities/Structures/hydro_tray.yml b/Resources/Prototypes/Entities/Structures/hydro_tray.yml index 8ea7172d8b..68a0cbd38e 100644 --- a/Resources/Prototypes/Entities/Structures/hydro_tray.yml +++ b/Resources/Prototypes/Entities/Structures/hydro_tray.yml @@ -1,6 +1,6 @@ - type: entity name: hydroponics tray - parent: [ hydroponicsSoil, ConstructibleMachine] + parent: [ hydroponicsSoil, SmallConstructibleMachine] id: hydroponicsTray description: An interstellar-grade space farmplot allowing for rapid growth and selective breeding of crops. Just... keep in mind the space weeds. components: @@ -14,6 +14,8 @@ hard: true mask: - MachineMask + layer: + - BulletImpassable - type: Anchorable - type: Pullable - type: Sprite From a710c7580918123bf0e30583c411852cad80f30b Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 5 Jun 2024 20:48:33 +0000 Subject: [PATCH 080/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index e68ab9517d..6adbf16221 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,13 +1,4 @@ Entries: -- author: potato1234x - changes: - - message: Added crafting recipes for wall lockers and secure lockers - type: Add - - message: Fixed secure lockers and wall lockers not being deconstructible - type: Fix - id: 6185 - time: '2024-03-18T20:53:13.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/24942 - author: brainfood1183 changes: - message: Added Spray Paints. @@ -3849,3 +3840,11 @@ id: 6684 time: '2024-06-05T20:14:56.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28576 +- author: Cojoke-dot + changes: + - message: A variety of objects around the station now need to be clicked on in + order for bullets to hit them. + type: Tweak + id: 6685 + time: '2024-06-05T20:47:28.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28571 From 513023a9f0f5b899af20ad96751bb855c5252ebc Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:06:24 -0700 Subject: [PATCH 081/243] Fix Smoke-grenade.ogg not being mono (#28593) --- Resources/Audio/Effects/Smoke-grenade.ogg | Bin 98191 -> 93809 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Resources/Audio/Effects/Smoke-grenade.ogg b/Resources/Audio/Effects/Smoke-grenade.ogg index c697214c2a571e1b6cc900b51c284fb073b8625c..945290ede54be1e189c4d05b32e8dd09bc5e2379 100644 GIT binary patch literal 93809 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

;E4dzooTn~OAhh>Ou*Ov+%=UA{n!z{P} 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

=Qb~! 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 literal 98191 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

i(GW1h<} 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
eGzdw3onk5@dGzMCb~AEszid(?G;!V}u1 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 From f223931ed3a723641e7dddf93bc09a83895b162b Mon Sep 17 00:00:00 2001 From: Ps3Moira <113228053+ps3moira@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:16:32 -0700 Subject: [PATCH 082/243] Fix Cigars Sprites + YAML fix for Inhand unlit cigars/cigs (#28641) --- .../Smokeables/Cigarettes/cigarette.yml | 2 ++ .../Consumable/Smokeables/Cigars/cigar.yml | 2 ++ .../Cigars/cigar-gold.rsi/lit-inhand-left.png | Bin 769 -> 1374 bytes .../Cigars/cigar-gold.rsi/lit-inhand-right.png | Bin 542 -> 1220 bytes .../cigar-gold.rsi/unlit-inhand-left.png | Bin 246 -> 306 bytes .../cigar-gold.rsi/unlit-inhand-right.png | Bin 263 -> 335 bytes .../Cigars/cigar.rsi/lit-inhand-left.png | Bin 769 -> 1374 bytes .../Cigars/cigar.rsi/lit-inhand-right.png | Bin 542 -> 1220 bytes .../Cigars/cigar.rsi/unlit-inhand-left.png | Bin 246 -> 306 bytes .../Cigars/cigar.rsi/unlit-inhand-right.png | Bin 263 -> 335 bytes 10 files changed, 4 insertions(+) diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml index f811afafba..146fac039d 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml @@ -18,6 +18,8 @@ equippedPrefix: unlit - type: Item size: Tiny + sprite: Objects/Consumable/Smokeables/Cigarettes/cigarette.rsi + heldPrefix: unlit - type: Construction graph: smokeableCigarette node: cigarette diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigars/cigar.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigars/cigar.yml index dc8d4eaf3c..93419993dc 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigars/cigar.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigars/cigar.yml @@ -20,6 +20,8 @@ equippedPrefix: unlit - type: Item size: Tiny + sprite: Objects/Consumable/Smokeables/Cigars/cigar.rsi + heldPrefix: unlit - type: entity id: CigarSpent diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-inhand-left.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-inhand-left.png index 353efa3f395a6167f2bcc1f68b75b1b6b2f5c950..c08f70d091d56f6538f7c3f7322d9d692c5a5fed 100644 GIT binary patch literal 1374 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcaljKx9jP7LeL$-HD>V3qK6aSW-L z^Y*S`k#IP}@rT=IPPr2Dkl{#)jKjMbJC-WPI6r>K!s9$s+hXN{uRc~a47+A5X;-uT zyNtp6(8bH^mc(YB{r_7lWyAe-7yMUG-THni6VL`^Abj=BZ{}N<*8Tl;)Bo%v{kZ$u zPoKOmd%tm8;kSg;Kl8mUe-{7vxPINbRrTLr{d`&U=lRk(du$&4+pqQX$<2@7{(t&= zIq2Pf%UA#Gf0h1CuHC|r72npJ?Ukv;|5cuEkEVeplfo>94mE~FoDCUBOnbR%=DwAg ze7|o0Y~7aIZ)NA*$KWE&aFNBqgi*kk1m;_V*UU22b$@iPv+t<8vE}pm)3>M3zgC~0 zd+M#8x%koW=V3>zUtr-3{-8nSbAF|6RnQ5cP%2Q0Cg(^xv8_eP;wwf&s}8T+Cayyp?X( z&)=5be_t=}`m=U>TU*)G#m8pI|J&v(KK1@Q`Tv__D(e27`o-YvBfYOm^3etHCI3HN zo-Y6ISDa@5Kgs7=-)~P3T=(v9(x3IO9ly-9NO-&dMYVgssj*}9t&7jk<+hkjJEa29qyuG}$nQna9vDP*$R^a44 z7MpwA(*P7<`i&B<>&=7I3RXSfKt!Kffz%C%!*lz5k@$p>1zY0X=_@Ej9gW zzm3fQ&b{XD;8YREv~hB+e*^#8vO{l?^y5tlJn4(%TkkFT{*|FN?Uz;cg_@c3|5j?M z>u;YOy#GAwrP$jWB>snLs-NF=eE+UpQNO>IzLv|`{D1oU`O}L2pFJne_O57S#buMz zKR;T3t6y_6`}@!T@+H9FVgB-SUflnTj#kNCJc0)T!nQvRNG{8DKWi?&?9QLU!`n@l zN~$z?#tCfBo4zlv|10CRf-~%YR~;=gv3-7Lzq9ya|5k<{b!)1(y{`xoU}&Cw%KL=f zHT_BDExq%8$sXPQ>l~*^>&^RSAI&sC!SQ~)S=hyzSjTu?5X{k`M2iH-ZayDsejagA{&hU z9Y1`|y&f$uJUsq?apv>qr*9{}tdpJn%o%fgm- z$*42@IM&y1uiuE7tHEj5>?gyqnACz-+7g>uUmf~7+uqaQCqse{uJnzqbbzTXUB@3# p49wi8jn4h~5F9HHE4OAW`p+=+u=8Bk{cnRoVxF#kF6*2UngE@xakKyc literal 769 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;U^?OH;uumf=j~m?ERjTshKKbk zOP5B>yeDDJpYUo+*(~OY#s`8b5mjt!x9~>1W7KJ8zo2}he~Dq2q~e;{y_NIZQxns= z^8ekfOkDjJXdDW-6LQ(=yJ`Q=OP|~Mm(3TycSkn)|LHx~=B&25^EdV1V#9Cte}Dg6 zeKqUjt&r=UHAWE_tWP!%8WX5+p^8)?w=
eA=$_uAZe_v^m@ z=Z~w;o{x|Ee|hir&krZ}OV8i`r2lLE?q7el=2ggj{waNjhui=2m+8mv_$!bbD_8?z t?RNVEyEE diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-inhand-right.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-inhand-right.png index cdeedb4c24b6843544fa4ca348296f2536b90cd6..7c685a0a408cf76f0ffc0fc229a211e6413efa31 100644 GIT binary patch literal 1220 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcaljKx9jP7LeL$-HD>V5#$TaSW-L z^Y*T7p0v9J+k=}5slQB$@?G8)F|6FAxYcXw4=zpC$AQ~CGill%M63@r{_Pu@(ZGWCA9-FPX`5@c{-+N)oAdn%${y{y0ddjJ2s z_4`AwMpftUueo^jviz;#6}6>n{*}l6zZ|zm-~5;Twfpi=TlxdvD?YPU)aud~OwU!eo|)3?>H?Muk}n0=^6#Y78#I42w7!E)o#Fb-mX7-aPsLYkG|B z|1P`EF7c@4Pt>pZk1N*i{&)0tzSqtFd+u5O>z{vS%l}x--}QHL>&t=~Qhn4WKYM>~ zkIkHI|2Iwf8~^*Z{OODLzVuf9UN$A&J~A2VXobUW{QTFW%&qkQ3%uR#r5I&?UTW3* z&Dk?{4>>HQvxZWBLDW(w`sa zC)E9rerx{h`2Jd_+U<|j&h6{_yoTT4dhxm){|;)Y-@E_4+%W(DoFDukceZ?1)r?N= zdF&V-b(~p-Qw?Za?$e--*z0rd*Sly>w>h)(Y;0x4-i9whjXxNAa%1b?=p5G*z!R)q ziZ?W!uj@%Ng5qE^_YuFyCu=rVmQC-0#YhcXLiUz#H;>QJJ;dR#<(}XJt-{GCm;5|% z^XLEQ&-cH-^!{nYx!?Bx|9}0b&(`l>bMqU>hbN!UufDyW{q4TBMFG!`{rS`TufNjn z)2g<7f4b?DnEt~@KP}#V|GIVi|NTC<_rI@8&b@y0_rGt)iwca<%ktIqR7| zRqv}SDl9T?uRXqS?H_@P&{lTZNC3W8n%~#y~*L`*yIAH-J<4x0l#+4ECn|qkj>OgVh M>FVdQ&MBb@07DZjVgLXD literal 542 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;V0`N7;uumf=j~lXFJ?!Hwuh1% zw`>vSSJ1R|*e1MY?H+f5euX&aH#tWy$tYDZSZ~}Szp+!{rpClx&ikKBcAwq9`80R* zSrMRd#DJo!`=)NcwkZD0n-$wm{=WY8ulMWP@a8l2D|0`Y{Q2{;WY>p(|D3O@nIAs? z-gx)g+@g=We{Nm(FM}!J^@Bh5s8{~HWb?vtIpQq=3{xAHTdBasZ{vR)P=oGv2 VvzR7}3vsJKlAf-9F6*2UngAYTyTJee diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-inhand-left.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-inhand-left.png index 9c3943fbf26ecf93db9b406f4955db7941abaa35..c1ca9c4d489e08820423c02af465fb7535ae8dd4 100644 GIT binary patch delta 279 zcmeyyxQS_kNGZx^prw85kJec)B=-RLpsMYolnBfk^AaBgw8$8G0J5Z@fCR z?OlRf0)y7=h?SNmPA5)(o$-J52F~f)PAwMfK#d^4`!S4pezkb~?f%0L@*eK1h+qCt zVz=fV`*`==bE;Q2#s=k88F-fj8(2Ts8M{{Yuh!;ekDrUzZzopr09zY+EdT%j literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=dp%toLn`LHy}41a$w9>RV!v}r z3ga{e+ZngE+B$HGIi&1TGw4gR`d6EOqgu)6-M+>QW}q$*h`N+I=WA8pCR=+y=R40d zpW4pfCEs^>`PMakr{{d>`fm|&J#@mS%xTx<1AZFZmtX!w1&&=LGiJ#v sysp~Qb=qLs_SfI5&!y@E?JrYTnzyNvH{Q*6Z!5?>p00i_>zopr01r%D7ytkO diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-inhand-right.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-inhand-right.png index 0f4ab68d1569e959c7cd17c2ad9f83e6a37ff345..9fc2e5ba275c279ff1ba67a04e0fc3e5d38cb12f 100644 GIT binary patch delta 308 zcmZo?I?psgrJk`k$lZxy-8q?;3=E8%o-U3d6?5L+vgT`Y5NUnby>U^I;+un!9o`F; zn@szr*JyjAuR1|7Y+KY14-;c$&O?kp_qQBTIQlS!<9bgT0|Xc-N4|KnOJm*7ot3-S zt=en({7kjI$A)c5Grvif*VgS@!)0yi`yozz;pE+i6*t#cTb@tuiu?Jw_{X!eN~~v2 z8hvvO{mq#C=U)21#l;6-r|oCbaFBKAWxB$61;ohx#JWZDY-|dffp^c&%Wdx#qR z6uofu4fl^<-tXn*Smhd7xB}P%7BiGGn1UGIVi%6{OsG=V-T*TKZkFyY%U`-@pa1)v t?7dg~_NJQO`<+66ugts2%)pRflJ`-~V1t`D!&{K2JYD@<);T3K0RVE$hAjX9 literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC==R92;Ln`LHy}6P1kO6~BpmgBU zqzP{t*c17)yngi@P)K9vpVVb$!l~(}6m{z_xSiX1U{qlFvR> zYR=2w*=ce;I4W+xe7oTt@0Ih4!*>_XT(*O)D|(^N-g5rpzaM5ZRt2t|Tr9o*w|c@709Ovok K=d#Wzp$Pz&BwsrK diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-inhand-left.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-inhand-left.png index 353efa3f395a6167f2bcc1f68b75b1b6b2f5c950..c08f70d091d56f6538f7c3f7322d9d692c5a5fed 100644 GIT binary patch literal 1374 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcaljKx9jP7LeL$-HD>V3qK6aSW-L z^Y*S`k#IP}@rT=IPPr2Dkl{#)jKjMbJC-WPI6r>K!s9$s+hXN{uRc~a47+A5X;-uT zyNtp6(8bH^mc(YB{r_7lWyAe-7yMUG-THni6VL`^Abj=BZ{}N<*8Tl;)Bo%v{kZ$u zPoKOmd%tm8;kSg;Kl8mUe-{7vxPINbRrTLr{d`&U=lRk(du$&4+pqQX$<2@7{(t&= zIq2Pf%UA#Gf0h1CuHC|r72npJ?Ukv;|5cuEkEVeplfo>94mE~FoDCUBOnbR%=DwAg ze7|o0Y~7aIZ)NA*$KWE&aFNBqgi*kk1m;_V*UU22b$@iPv+t<8vE}pm)3>M3zgC~0 zd+M#8x%koW=V3>zUtr-3{-8nSbAF|6RnQ5cP%2Q0Cg(^xv8_eP;wwf&s}8T+Cayyp?X( z&)=5be_t=}`m=U>TU*)G#m8pI|J&v(KK1@Q`Tv__D(e27`o-YvBfYOm^3etHCI3HN zo-Y6ISDa@5Kgs7=-)~P3T=(v9(x3IO9ly-9NO-&dMYVgssj*}9t&7jk<+hkjJEa29qyuG}$nQna9vDP*$R^a44 z7MpwA(*P7<`i&B<>&=7I3RXSfKt!Kffz%C%!*lz5k@$p>1zY0X=_@Ej9gW zzm3fQ&b{XD;8YREv~hB+e*^#8vO{l?^y5tlJn4(%TkkFT{*|FN?Uz;cg_@c3|5j?M z>u;YOy#GAwrP$jWB>snLs-NF=eE+UpQNO>IzLv|`{D1oU`O}L2pFJne_O57S#buMz zKR;T3t6y_6`}@!T@+H9FVgB-SUflnTj#kNCJc0)T!nQvRNG{8DKWi?&?9QLU!`n@l zN~$z?#tCfBo4zlv|10CRf-~%YR~;=gv3-7Lzq9ya|5k<{b!)1(y{`xoU}&Cw%KL=f zHT_BDExq%8$sXPQ>l~*^>&^RSAI&sC!SQ~)S=hyzSjTu?5X{k`M2iH-ZayDsejagA{&hU z9Y1`|y&f$uJUsq?apv>qr*9{}tdpJn%o%fgm- z$*42@IM&y1uiuE7tHEj5>?gyqnACz-+7g>uUmf~7+uqaQCqse{uJnzqbbzTXUB@3# p49wi8jn4h~5F9HHE4OAW`p+=+u=8Bk{cnRoVxF#kF6*2UngE@xakKyc literal 769 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;U^?OH;uumf=j~m?ERjTshKKbk zOP5B>yeDDJpYUo+*(~OY#s`8b5mjt!x9~>1W7KJ8zo2}he~Dq2q~e;{y_NIZQxns= z^8ekfOkDjJXdDW-6LQ(=yJ`Q=OP|~Mm(3TycSkn)|LHx~=B&25^EdV1V#9Cte}Dg6 zeKqUjt&r=UHAWE_tWP!%8WX5+p^8)?w=eA=$_uAZe_v^m@ z=Z~w;o{x|Ee|hir&krZ}OV8i`r2lLE?q7el=2ggj{waNjhui=2m+8mv_$!bbD_8?z t?RNVEyEE diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-inhand-right.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-inhand-right.png index cdeedb4c24b6843544fa4ca348296f2536b90cd6..7c685a0a408cf76f0ffc0fc229a211e6413efa31 100644 GIT binary patch literal 1220 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcaljKx9jP7LeL$-HD>V5#$TaSW-L z^Y*T7p0v9J+k=}5slQB$@?G8)F|6FAxYcXw4=zpC$AQ~CGill%M63@r{_Pu@(ZGWCA9-FPX`5@c{-+N)oAdn%${y{y0ddjJ2s z_4`AwMpftUueo^jviz;#6}6>n{*}l6zZ|zm-~5;Twfpi=TlxdvD?YPU)aud~OwU!eo|)3?>H?Muk}n0=^6#Y78#I42w7!E)o#Fb-mX7-aPsLYkG|B z|1P`EF7c@4Pt>pZk1N*i{&)0tzSqtFd+u5O>z{vS%l}x--}QHL>&t=~Qhn4WKYM>~ zkIkHI|2Iwf8~^*Z{OODLzVuf9UN$A&J~A2VXobUW{QTFW%&qkQ3%uR#r5I&?UTW3* z&Dk?{4>>HQvxZWBLDW(w`sa zC)E9rerx{h`2Jd_+U<|j&h6{_yoTT4dhxm){|;)Y-@E_4+%W(DoFDukceZ?1)r?N= zdF&V-b(~p-Qw?Za?$e--*z0rd*Sly>w>h)(Y;0x4-i9whjXxNAa%1b?=p5G*z!R)q ziZ?W!uj@%Ng5qE^_YuFyCu=rVmQC-0#YhcXLiUz#H;>QJJ;dR#<(}XJt-{GCm;5|% z^XLEQ&-cH-^!{nYx!?Bx|9}0b&(`l>bMqU>hbN!UufDyW{q4TBMFG!`{rS`TufNjn z)2g<7f4b?DnEt~@KP}#V|GIVi|NTC<_rI@8&b@y0_rGt)iwca<%ktIqR7| zRqv}SDl9T?uRXqS?H_@P&{lTZNC3W8n%~#y~*L`*yIAH-J<4x0l#+4ECn|qkj>OgVh M>FVdQ&MBb@07DZjVgLXD literal 542 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;V0`N7;uumf=j~lXFJ?!Hwuh1% zw`>vSSJ1R|*e1MY?H+f5euX&aH#tWy$tYDZSZ~}Szp+!{rpClx&ikKBcAwq9`80R* zSrMRd#DJo!`=)NcwkZD0n-$wm{=WY8ulMWP@a8l2D|0`Y{Q2{;WY>p(|D3O@nIAs? z-gx)g+@g=We{Nm(FM}!J^@Bh5s8{~HWb?vtIpQq=3{xAHTdBasZ{vR)P=oGv2 VvzR7}3vsJKlAf-9F6*2UngAYTyTJee diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-inhand-left.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-inhand-left.png index 9c3943fbf26ecf93db9b406f4955db7941abaa35..c1ca9c4d489e08820423c02af465fb7535ae8dd4 100644 GIT binary patch delta 279 zcmeyyxQS_kNGZx^prw85kJec)B=-RLpsMYolnBfk^AaBgw8$8G0J5Z@fCR z?OlRf0)y7=h?SNmPA5)(o$-J52F~f)PAwMfK#d^4`!S4pezkb~?f%0L@*eK1h+qCt zVz=fV`*`==bE;Q2#s=k88F-fj8(2Ts8M{{Yuh!;ekDrUzZzopr09zY+EdT%j literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=dp%toLn`LHy}41a$w9>RV!v}r z3ga{e+ZngE+B$HGIi&1TGw4gR`d6EOqgu)6-M+>QW}q$*h`N+I=WA8pCR=+y=R40d zpW4pfCEs^>`PMakr{{d>`fm|&J#@mS%xTx<1AZFZmtX!w1&&=LGiJ#v sysp~Qb=qLs_SfI5&!y@E?JrYTnzyNvH{Q*6Z!5?>p00i_>zopr01r%D7ytkO diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-inhand-right.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-inhand-right.png index 0f4ab68d1569e959c7cd17c2ad9f83e6a37ff345..9fc2e5ba275c279ff1ba67a04e0fc3e5d38cb12f 100644 GIT binary patch delta 308 zcmZo?I?psgrJk`k$lZxy-8q?;3=E8%o-U3d6?5L+vgT`Y5NUnby>U^I;+un!9o`F; zn@szr*JyjAuR1|7Y+KY14-;c$&O?kp_qQBTIQlS!<9bgT0|Xc-N4|KnOJm*7ot3-S zt=en({7kjI$A)c5Grvif*VgS@!)0yi`yozz;pE+i6*t#cTb@tuiu?Jw_{X!eN~~v2 z8hvvO{mq#C=U)21#l;6-r|oCbaFBKAWxB$61;ohx#JWZDY-|dffp^c&%Wdx#qR z6uofu4fl^<-tXn*Smhd7xB}P%7BiGGn1UGIVi%6{OsG=V-T*TKZkFyY%U`-@pa1)v t?7dg~_NJQO`<+66ugts2%)pRflJ`-~V1t`D!&{K2JYD@<);T3K0RVE$hAjX9 literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC==R92;Ln`LHy}6P1kO6~BpmgBU zqzP{t*c17)yngi@P)K9vpVVb$!l~(}6m{z_xSiX1U{qlFvR> zYR=2w*=ce;I4W+xe7oTt@0Ih4!*>_XT(*O)D|(^N-g5rpzaM5ZRt2t|Tr9o*w|c@709Ovok K=d#Wzp$Pz&BwsrK From 014c5eb3e8d36c7a72a0b2d647ca580b903ebdf9 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 5 Jun 2024 21:17:38 +0000 Subject: [PATCH 083/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 6adbf16221..adbad1269b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: brainfood1183 - changes: - - message: Added Spray Paints. - type: Add - id: 6186 - time: '2024-03-18T21:29:48.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/23003 - author: PJB3005 changes: - message: Bans are now shown in the "adminremarks" command. @@ -3848,3 +3841,10 @@ id: 6685 time: '2024-06-05T20:47:28.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28571 +- author: ps3moira + changes: + - message: Lit cigars inhand sprites are now visible. + type: Fix + id: 6686 + time: '2024-06-05T21:16:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28641 From 27a5d08db497348db591e318a2ea7e7f72cf0662 Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:03:31 -0500 Subject: [PATCH 084/243] Clean up Eva and Hardsuit helm yml + Lets atmos firesuit helm work as a BreathMask (#28602) --- .../Clothing/Head/base_clothinghead.yml | 2 ++ .../Entities/Clothing/Head/eva-helmets.yml | 6 ---- .../Clothing/Head/hardsuit-helmets.yml | 31 ------------------- .../Entities/Clothing/Head/helmets.yml | 1 + 4 files changed, 3 insertions(+), 37 deletions(-) diff --git a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml index 40f9e0a3d4..866fe962ca 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml @@ -119,6 +119,7 @@ id: ClothingHeadEVAHelmetBase name: base space helmet components: + - type: BreathMask - type: Item size: Normal - type: PressureProtection @@ -150,6 +151,7 @@ name: base hardsuit helmet noSpawn: true components: + - type: BreathMask - type: Sprite state: icon # default state used by most inheritors - type: Clickable diff --git a/Resources/Prototypes/Entities/Clothing/Head/eva-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/eva-helmets.yml index 963bcdedd7..530c23a578 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/eva-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/eva-helmets.yml @@ -5,7 +5,6 @@ name: EVA helmet description: An old-but-gold helmet designed for extravehicular activites. Infamous for making security officers paranoid. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Helmets/eva.rsi - type: Clothing @@ -22,7 +21,6 @@ name: EVA helmet description: An old-but-gold helmet designed for extravehicular activites. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Helmets/eva_large.rsi - type: Clothing @@ -35,7 +33,6 @@ name: syndicate EVA helmet description: A simple, stylish EVA helmet. Designed for maximum humble space-badassery. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Helmets/eva_syndicate.rsi - type: Clothing @@ -48,7 +45,6 @@ name: cosmonaut helmet description: Ancient design, but advanced manufacturing. #Description here originally started with " A deceptively well armored space helmet." Potentially had armor values in SS13 that weren't brought over? components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Helmets/cosmonaut.rsi - type: Clothing @@ -61,7 +57,6 @@ name: paramedic void helmet description: A void helmet made for paramedics. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Helmets/paramedhelm.rsi - type: Clothing @@ -81,7 +76,6 @@ name: NTSRA void helmet description: An ancient space helmet, designed by the NTSRA branch of CentCom. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Helmets/ancientvoidsuit.rsi - type: Clothing diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml index 072dd53d9a..0c660e09b8 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml @@ -12,7 +12,6 @@ name: basic hardsuit helmet description: A basic-looking hardsuit helmet that provides minor protection against most sources of damage. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/basic.rsi - type: Clothing @@ -28,7 +27,6 @@ name: atmos hardsuit helmet description: A special hardsuit helmet designed for working in low-pressure, high thermal environments. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/atmospherics.rsi layers: @@ -68,7 +66,6 @@ name: engineering hardsuit helmet description: An engineering hardsuit helmet designed for working in low-pressure, high radioactive environments. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/engineering.rsi - type: Clothing @@ -86,7 +83,6 @@ name: spationaut hardsuit helmet description: A sturdy helmet designed for complex industrial operations in space. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/spatiohelm.rsi layers: @@ -121,7 +117,6 @@ name: salvage hardsuit helmet description: A special helmet designed for work in a hazardous, low pressure environment. Has reinforced plating for wildlife encounters and dual floodlights. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/salvage.rsi - type: Clothing @@ -140,7 +135,6 @@ name: salvager maxim helmet description: A predication of decay washes over your mind. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/maxim.rsi - type: Clothing @@ -163,7 +157,6 @@ name: security hardsuit helmet description: Armored hardsuit helmet for security needs. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/security.rsi - type: Clothing @@ -188,7 +181,6 @@ name: brigmedic hardsuit helmet description: The lightweight helmet of the brigmedic hardsuit. Protects against viruses, and clowns. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/brigmedic.rsi - type: Clothing @@ -215,7 +207,6 @@ name: warden's hardsuit helmet description: A modified riot helmet. Oddly comfortable. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/security-warden.rsi - type: Clothing @@ -240,7 +231,6 @@ name: captain's hardsuit helmet description: Special hardsuit helmet, made for the captain of the station. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/capspace.rsi - type: Clothing @@ -256,7 +246,6 @@ name: chief engineer's hardsuit helmet description: Special hardsuit helmet, made for the chief engineer of the station. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/engineering-white.rsi - type: Clothing @@ -274,7 +263,6 @@ name: chief medical officer's hardsuit helmet description: Lightweight medical hardsuit helmet that doesn't restrict your head movements. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/medical.rsi - type: Clothing @@ -292,7 +280,6 @@ name: experimental research hardsuit helmet description: Lightweight hardsuit helmet that doesn't restrict your head movements. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/rd.rsi - type: Clothing @@ -310,7 +297,6 @@ name: head of security's hardsuit helmet description: Security hardsuit helmet with the latest top secret NT-HUD software. Belongs to the HoS. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/security-red.rsi - type: Clothing @@ -335,7 +321,6 @@ name: luxury mining hardsuit helmet description: A refurbished mining hardsuit helmet, fitted with satin cushioning and an extra (non-functioning) antenna, because you're that extra. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/luxury.rsi - type: Clothing @@ -355,7 +340,6 @@ name: blood-red hardsuit helmet description: A heavily armored helmet designed for work in special operations. Property of Gorlex Marauders. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/syndicate.rsi - type: Clothing @@ -380,7 +364,6 @@ name: blood-red medic hardsuit helmet description: An advanced red hardsuit helmet specifically designed for field medic operations. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/syndiemedic.rsi - type: Clothing @@ -405,7 +388,6 @@ name: syndicate elite helmet description: An elite version of the blood-red hardsuit's helmet, with improved armor and fireproofing. Property of Gorlex Marauders. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/syndieelite.rsi - type: Clothing @@ -434,7 +416,6 @@ name: syndicate commander helmet description: A bulked up version of the blood-red hardsuit's helmet, purpose-built for the commander of a syndicate operative squad. Has significantly improved armor for those deadly front-lines firefights. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/syndiecommander.rsi - type: Clothing @@ -459,7 +440,6 @@ name: cybersun juggernaut helmet description: Made of compressed red matter, this helmet was designed in the Tau chromosphere facility. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/cybersun.rsi - type: Clothing @@ -482,7 +462,6 @@ name: wizard hardsuit helmet description: A bizarre gem-encrusted helmet that radiates magical energies. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/wizard.rsi - type: Clothing @@ -507,7 +486,6 @@ name: organic space helmet description: A spaceworthy biomass of pressure and temperature resistant tissue. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/lingspacehelmet.rsi - type: Clothing @@ -524,7 +502,6 @@ suffix: Pirate description: A deep space EVA helmet, very heavy but provides good protection. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/pirateeva.rsi - type: Clothing @@ -541,7 +518,6 @@ suffix: Pirate description: A special hardsuit helmet, made for the captain of a pirate ship. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/piratecaptainhelm.rsi - type: Clothing @@ -558,7 +534,6 @@ name: ERT leader hardsuit helmet description: A special hardsuit helmet worn by members of an emergency response team. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi - type: Clothing @@ -580,7 +555,6 @@ name: ERT chaplain hardsuit helmet description: A special hardsuit helmet worn by members of an emergency response team. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/ERThelmets/ertchaplain.rsi - type: Clothing @@ -595,7 +569,6 @@ name: ERT engineer hardsuit helmet description: A special hardsuit helmet worn by members of an emergency response team. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi - type: Clothing @@ -617,7 +590,6 @@ name: ERT medic hardsuit helmet description: A special hardsuit helmet worn by members of an emergency response team. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi - type: Clothing @@ -632,7 +604,6 @@ name: ERT security hardsuit helmet description: A special hardsuit helmet worn by members of an emergency response team. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi - type: Clothing @@ -654,7 +625,6 @@ name: ERT janitor hardsuit helmet description: A special hardsuit helmet worn by members of an emergency response team. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi - type: Clothing @@ -669,7 +639,6 @@ name: CBURN exosuit helmet description: A pressure resistant and fireproof hood worn by special cleanup units. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/cburn.rsi layers: diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml index 720e52ee58..d585518ece 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml @@ -263,6 +263,7 @@ slots: - Hair - Snout + - type: BreathMask #Chitinous Helmet - type: entity From 72df1a0ae58679cf482006c08b242b94ce00c661 Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:04:09 -0500 Subject: [PATCH 085/243] Shifts borgs hats to the right a bit (#28600) --- Resources/Prototypes/InventoryTemplates/borg.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/InventoryTemplates/borg.yml b/Resources/Prototypes/InventoryTemplates/borg.yml index a03be16708..d43519f61c 100644 --- a/Resources/Prototypes/InventoryTemplates/borg.yml +++ b/Resources/Prototypes/InventoryTemplates/borg.yml @@ -7,7 +7,7 @@ uiWindowPos: 1,0 strippingWindowPos: 0,0 displayName: Head - offset: 0, -0.09375 + offset: 0.015625, -0.09375 - type: inventoryTemplate id: borgShort @@ -18,7 +18,7 @@ uiWindowPos: 1,0 strippingWindowPos: 0,0 displayName: Head - offset: 0, -0.1875 + offset: 0.015625, -0.1875 - type: inventoryTemplate id: borgTall @@ -30,6 +30,7 @@ uiWindowPos: 0,0 strippingWindowPos: 0,0 displayName: Head + offset: 0.015625, 0 # taller than tall - type: inventoryTemplate From 110c8a96e0f7593a1caad4c6be842161d9428eb6 Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:06:57 -0500 Subject: [PATCH 086/243] Fix mouse inhands (#28623) --- Resources/Prototypes/Entities/Mobs/NPCs/animals.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 51647f6985..971d05823a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1533,6 +1533,7 @@ state: mouse-0 - type: Item size: Tiny + heldPrefix: 0 - type: Clothing quickEquip: false sprite: Mobs/Animals/mouse.rsi @@ -1706,6 +1707,9 @@ Base: dead-1 Dead: Base: splat-1 + - type: Item + size: Tiny + heldPrefix: 1 - type: entity parent: MobMouse @@ -1732,6 +1736,9 @@ Base: dead-2 Dead: Base: splat-2 + - type: Item + size: Tiny + heldPrefix: 2 - type: entity name: cancer mouse From 8a1f3f497184cb14fca9cea6af0ab87f6df5e632 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Wed, 5 Jun 2024 18:08:41 -0400 Subject: [PATCH 087/243] Adjust some touch reaction damage levels (#28591) --- Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml | 2 +- Resources/Prototypes/Entities/Mobs/Species/diona.yml | 8 ++++---- Resources/Prototypes/Entities/Mobs/Species/slime.yml | 2 +- Resources/Prototypes/Entities/Objects/Misc/kudzu.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml index d2ca780438..ffca699bfc 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml @@ -84,7 +84,7 @@ scaleByQuantity: true damage: types: - Heat: 3 + Heat: 0.15 - !type:PopupMessage type: Local messages: [ "slime-hurt-by-water-popup" ] diff --git a/Resources/Prototypes/Entities/Mobs/Species/diona.yml b/Resources/Prototypes/Entities/Mobs/Species/diona.yml index 1cac380cd0..7edb3a19db 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/diona.yml @@ -49,9 +49,9 @@ scaleByQuantity: true damage: types: - Blunt: 2 - Slash: 2 - Piercing: 3 + Blunt: 0.1 + Slash: 0.1 + Piercing: 0.15 - !type:PopupMessage type: Local visualType: Large @@ -64,7 +64,7 @@ scaleByQuantity: true damage: types: - Poison: 5 + Poison: 0.25 - !type:PopupMessage type: Local visualType: Large diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml index 2ab26ffcd6..caa3690e5d 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -91,7 +91,7 @@ scaleByQuantity: true damage: types: - Heat: 2 + Heat: 0.1 - !type:PopupMessage type: Local visualType: Large diff --git a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml index a100500494..ca56ef5acb 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml @@ -76,7 +76,7 @@ scaleByQuantity: true damage: types: - Heat: 10 + Heat: 0.5 - type: AtmosExposed - type: Kudzu growthTickChance: 0.3 From 8e85bec2cef641e5624241ce33cdbdbba2921e5d Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 5 Jun 2024 22:09:47 +0000 Subject: [PATCH 088/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index adbad1269b..25f48ec5f7 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: PJB3005 - changes: - - message: Bans are now shown in the "adminremarks" command. - type: Add - id: 6187 - time: '2024-03-18T21:31:34.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26240 - author: Terraspark4941 changes: - message: The TEG page in the guidebook has been updated with proper information! @@ -3848,3 +3841,11 @@ id: 6686 time: '2024-06-05T21:16:32.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28641 +- author: Tayrtahn + changes: + - message: Slimes, diona, and kudzu are less dramatically affected by having certain + chemicals splashed on them. + type: Tweak + id: 6687 + time: '2024-06-05T22:08:41.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28591 From ba57e3e4d9b95bad457f93f7e1f9bbd433d8eebd Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:14:25 +1200 Subject: [PATCH 089/243] Add closing storage UIs to StorageInteractionTest (#28633) --- .../Tests/Storage/StorageInteractionTest.cs | 12 ++++++++++++ .../Storage/EntitySystems/SharedStorageSystem.cs | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs b/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs index 34402dd5e6..8d0de707f3 100644 --- a/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs +++ b/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs @@ -4,6 +4,7 @@ using Content.IntegrationTests.Tests.Interaction; using Content.Shared.Input; using Content.Shared.PDA; using Content.Shared.Storage; +using Content.Shared.Timing; using Robust.Client.UserInterface; using Robust.Shared.Containers; using Robust.Shared.GameObjects; @@ -27,11 +28,22 @@ public sealed class StorageInteractionTest : InteractionTest Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.False); Assert.That(IsUiOpen(PdaUiKey.Key), Is.False); + await Server.WaitPost(() => SEntMan.RemoveComponent(STarget!.Value)); + await RunTicks(5); + // Activating the backpack opens the UI await Activate(); Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True); Assert.That(IsUiOpen(PdaUiKey.Key), Is.False); + // Activating it again closes the UI + await Activate(); + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.False); + + // Open it again + await Activate(); + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True); + // Pick up a PDA var pda = await PlaceInHands("PassengerPDA"); var sPda = ToServer(pda); diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index d87b57bfc8..f20e2f9ab1 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -298,7 +298,7 @@ public abstract class SharedStorageSystem : EntitySystem return; // prevent spamming bag open / honkerton honk sound - silent |= TryComp(uid, out var useDelay) && UseDelay.IsDelayed((uid, useDelay)); + silent |= TryComp(uid, out var useDelay) && UseDelay.IsDelayed((uid, useDelay), id: OpenUiUseDelayID); if (!CanInteract(entity, (uid, storageComp), silent: silent)) return; @@ -308,7 +308,7 @@ public abstract class SharedStorageSystem : EntitySystem Audio.PlayPredicted(storageComp.StorageOpenSound, uid, entity); if (useDelay != null) - UseDelay.TryResetDelay((uid, useDelay)); + UseDelay.TryResetDelay((uid, useDelay), id: OpenUiUseDelayID); } _ui.OpenUi(uid, StorageComponent.StorageUiKey.Key, entity); From fea2bc942263b3cda6cb61a9f8959493931e8b8d Mon Sep 17 00:00:00 2001 From: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com> Date: Wed, 5 Jun 2024 18:59:22 -0500 Subject: [PATCH 090/243] Update rules (#28452) --- .github/CODEOWNERS | 2 + .../Guidebook/Controls/GuidebookWindow.xaml | 2 +- Resources/Locale/en-US/guidebook/guides.ftl | 55 ++ Resources/Prototypes/Guidebook/rules.yml | 369 +++++++ Resources/Prototypes/Guidebook/ss14.yml | 1 + .../Guidebook/ServerRules/BanDurations.xml | 17 + .../Guidebook/ServerRules/BanTypes.xml | 11 + .../ServerRules/CoreRules/RuleC0.xml | 19 + .../ServerRules/CoreRules/RuleC10AHelp.xml | 29 + .../CoreRules/RuleC11AhelpThreats.xml | 20 + .../ServerRules/CoreRules/RuleC12MinAge.xml | 6 + .../CoreRules/RuleC13CharacterNames.xml | 66 ++ .../ServerRules/CoreRules/RuleC14ICinOOC.xml | 13 + .../ServerRules/CoreRules/RuleC1Admins.xml | 6 + .../ServerRules/CoreRules/RuleC2DBAD.xml | 7 + .../ServerRules/CoreRules/RuleC3NoHate.xml | 20 + .../ServerRules/CoreRules/RuleC4NoERP.xml | 23 + .../ServerRules/CoreRules/RuleC5Metacomms.xml | 18 + .../CoreRules/RuleC6BanEvasion.xml | 15 + .../CoreRules/RuleC7EnglishOnly.xml | 10 + .../ServerRules/CoreRules/RuleC8Exploits.xml | 12 + .../ServerRules/CoreRules/RuleC9Multikey.xml | 7 + .../Guidebook/ServerRules/DefaultRules.xml | 5 + .../Guidebook/ServerRules/README.txt | 5 + .../Guidebook/ServerRules/RoleTypes.xml | 21 + .../ServerRules/RoleplayRules/RuleR0.xml | 26 + .../RoleplayRules/RuleR10Subordination.xml | 26 + .../RuleR11-1AnimalEscalation.xml | 36 + .../RoleplayRules/RuleR11-2ConflictTypes.xml | 30 + .../RoleplayRules/RuleR11Escalation.xml | 67 ++ .../RoleplayRules/RuleR12RoleAbandonment.xml | 28 + .../RoleplayRules/RuleR13PerformRole.xml | 26 + .../RoleplayRules/RuleR14SecComStandard.xml | 37 + .../RoleplayRules/RuleR15SpaceLaw.xml | 21 + .../RoleplayRules/RuleR1Silicons.xml | 4 + .../RoleplayRules/RuleR2Familiars.xml | 6 + .../RoleplayRules/RuleR3NormalRP.xml | 20 + .../RoleplayRules/RuleR4Metashield.xml | 103 ++ .../RoleplayRules/RuleR5Arrivals.xml | 22 + .../RoleplayRules/RuleR6SelfAntag.xml | 22 + .../RoleplayRules/RuleR7RoundStalling.xml | 16 + .../RoleplayRules/RuleR8NoFriendlyAntag.xml | 22 + .../RoleplayRules/RuleR9MassSabotage.xml | 23 + .../ServerRules/SiliconRules/RuleS0.xml | 15 + .../SiliconRules/RuleS10OrderConflicts.xml | 9 + .../ServerRules/SiliconRules/RuleS1Laws.xml | 6 + .../SiliconRules/RuleS2LawPriority.xml | 9 + .../SiliconRules/RuleS3LawRedefinition.xml | 8 + .../SiliconRules/RuleS4RequestChanges.xml | 6 + .../SiliconRules/RuleS5FreeSilicon.xml | 4 + .../SiliconRules/RuleS6UnreasonableOrders.xml | 22 + .../SiliconRules/RuleS7Consistency.xml | 6 + .../RuleS8DefaultCrewDefinition.xml | 4 + .../RuleS9DefaultHarmDefinition.xml | 25 + .../SpaceLaw/SLControlledSubstances.xml | 14 + .../ServerRules/SpaceLaw/SLCrimeList.xml | 934 ++++++++++++++++++ .../ServerRules/SpaceLaw/SLRestrictedGear.xml | 21 + .../SpaceLaw/SLRestrictedWeapons.xml | 11 + .../ServerRules/SpaceLaw/SpaceLaw.xml | 67 ++ .../ServerRules/WizDenCoreOnlyRules.xml | 26 + .../Guidebook/ServerRules/WizDenLRPRules.xml | 65 ++ .../Guidebook/ServerRules/WizDenMRPRules.xml | 65 ++ Resources/ServerInfo/RP_Rules.txt | 127 --- Resources/ServerInfo/Rules.txt | 115 --- 64 files changed, 2610 insertions(+), 243 deletions(-) create mode 100644 Resources/Prototypes/Guidebook/rules.yml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/BanDurations.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/BanTypes.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC0.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC10AHelp.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC11AhelpThreats.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC12MinAge.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC13CharacterNames.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC14ICinOOC.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC1Admins.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC2DBAD.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC3NoHate.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC4NoERP.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC5Metacomms.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC6BanEvasion.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC7EnglishOnly.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC8Exploits.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC9Multikey.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/DefaultRules.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/README.txt create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleTypes.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR0.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR10Subordination.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11-1AnimalEscalation.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11-2ConflictTypes.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11Escalation.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR12RoleAbandonment.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR13PerformRole.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR14SecComStandard.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR15SpaceLaw.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR1Silicons.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR2Familiars.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR3NormalRP.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR4Metashield.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR5Arrivals.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR6SelfAntag.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR7RoundStalling.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR8NoFriendlyAntag.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR9MassSabotage.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS0.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS10OrderConflicts.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS1Laws.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS2LawPriority.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS3LawRedefinition.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS4RequestChanges.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS5FreeSilicon.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS6UnreasonableOrders.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS7Consistency.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS8DefaultCrewDefinition.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS9DefaultHarmDefinition.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLControlledSubstances.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLCrimeList.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLRestrictedGear.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLRestrictedWeapons.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SpaceLaw.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/WizDenCoreOnlyRules.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/WizDenLRPRules.xml create mode 100644 Resources/ServerInfo/Guidebook/ServerRules/WizDenMRPRules.xml delete mode 100644 Resources/ServerInfo/RP_Rules.txt delete mode 100644 Resources/ServerInfo/Rules.txt diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 21c3070b80..da9d4d693a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,6 +15,7 @@ /Content.*/GameTicking/ @moonheart08 @EmoGarbage404 /Resources/ServerInfo/ @moonheart08 @Chief-Engineer /Resources/ServerInfo/Guidebook/ @moonheart08 @EmoGarbage404 +/Resources/ServerInfo/Guidebook/ServerRules/ @Chief-Engineer /Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer /Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer @@ -23,6 +24,7 @@ /Resources/Prototypes/Body/ @DrSmugleaf # suffering /Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf /Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf +/Resources/Prototypes/Guidebook/rules.yml @Chief-Engineer /Content.*/Body/ @DrSmugleaf /Content.YAMLLinter @DrSmugleaf /Content.Shared/Damage/ @DrSmugleaf diff --git a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml index 8dbfde3c47..cc6cc6e82b 100644 --- a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml +++ b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml @@ -18,7 +18,7 @@ Name="SearchBar" PlaceHolder="{Loc 'guidebook-filter-placeholder-text'}" HorizontalExpand="True" - Margin="0 5 10 5"> + Margin="0 5 10 5"> diff --git a/Resources/Locale/en-US/guidebook/guides.ftl b/Resources/Locale/en-US/guidebook/guides.ftl index ff1ffbf5ea..66c1a42adf 100644 --- a/Resources/Locale/en-US/guidebook/guides.ftl +++ b/Resources/Locale/en-US/guidebook/guides.ftl @@ -68,5 +68,60 @@ guide-entry-revolutionaries = Revolutionaries guide-entry-minor-antagonists = Minor Antagonists guide-entry-space-ninja = Space Ninja +guide-entry-rules = Server Rules +guide-entry-rules-core-only = Core Only Ruleset +guide-entry-rules-lrp = Standard Ruleset +guide-entry-rules-mrp = MRP Ruleset +guide-entry-rules-role-types = Role Types +guide-entry-rules-core = Core Rules +guide-entry-rules-c1 = C1 +guide-entry-rules-c2 = C2 +guide-entry-rules-c3 = C3 +guide-entry-rules-c4 = C4 +guide-entry-rules-c5 = C5 +guide-entry-rules-c6 = C6 +guide-entry-rules-c7 = C7 +guide-entry-rules-c8 = C8 +guide-entry-rules-c9 = C9 +guide-entry-rules-c10 = C10 +guide-entry-rules-c11 = C11 +guide-entry-rules-c12 = C12 +guide-entry-rules-c13 = C13 +guide-entry-rules-c14 = C14 +guide-entry-rules-roleplay = Roleplay Rules +guide-entry-rules-r1 = R1 +guide-entry-rules-r2 = R2 +guide-entry-rules-r3 = R3 +guide-entry-rules-r4 = R4 +guide-entry-rules-r5 = R5 +guide-entry-rules-r6 = R6 +guide-entry-rules-r7 = R7 +guide-entry-rules-r8 = R8 +guide-entry-rules-r9 = R9 +guide-entry-rules-r10 = R10 +guide-entry-rules-r11 = R11 +guide-entry-rules-r12 = R12 +guide-entry-rules-r13 = R13 +guide-entry-rules-r14 = R14 +guide-entry-rules-r15 = R15 +guide-entry-rules-silicon = Silicon Rules +guide-entry-rules-s1 = S1 +guide-entry-rules-s2 = S2 +guide-entry-rules-s3 = S3 +guide-entry-rules-s4 = S4 +guide-entry-rules-s5 = S5 +guide-entry-rules-s6 = S6 +guide-entry-rules-s7 = S7 +guide-entry-rules-s8 = S8 +guide-entry-rules-s9 = S9 +guide-entry-rules-s10 = S10 +guide-entry-rules-space-law = Space Law +guide-entry-rules-sl-crime-list = Crime List +guide-entry-rules-sl-controlled-substances = Controlled Substances +guide-entry-rules-sl-restricted-gear = Restricted Gear +guide-entry-rules-sl-restricted-weapons = Restricted Weapons +guide-entry-rules-ban-types = Ban Types +guide-entry-rules-ban-durations = Ban Durations + guide-entry-writing = Writing guide-entry-glossary = Glossary diff --git a/Resources/Prototypes/Guidebook/rules.yml b/Resources/Prototypes/Guidebook/rules.yml new file mode 100644 index 0000000000..4aad449485 --- /dev/null +++ b/Resources/Prototypes/Guidebook/rules.yml @@ -0,0 +1,369 @@ +- type: guideEntry # Default for forks and stuff. Should not be listed anywhere if the server is using a custom ruleset. + id: DefaultRuleset + name: guide-entry-rules + text: "/ServerInfo/Guidebook/ServerRules/DefaultRules.xml" + +- type: guideEntry + id: CoreRuleset + name: guide-entry-rules-core-only + priority: 0 + text: "/ServerInfo/Guidebook/ServerRules/WizDenCoreOnlyRules.xml" + +- type: guideEntry + id: StandardRuleset + name: guide-entry-rules-lrp + priority: 5 + text: "/ServerInfo/Guidebook/ServerRules/WizDenLRPRules.xml" + +- type: guideEntry + id: MRPRuleset + name: guide-entry-rules-mrp + priority: 10 + text: "/ServerInfo/Guidebook/ServerRules/WizDenMRPRules.xml" + +- type: guideEntry + id: RoleTypes + name: guide-entry-rules-role-types + priority: 20 + text: "/ServerInfo/Guidebook/ServerRules/RoleTypes.xml" + +- type: guideEntry + id: CoreRules + name: guide-entry-rules-core + priority: 30 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC0.xml" + children: + - RuleC1 + - RuleC2 + - RuleC3 + - RuleC4 + - RuleC5 + - RuleC6 + - RuleC7 + - RuleC8 + - RuleC9 + - RuleC10 + - RuleC11 + - RuleC12 + - RuleC13 + - RuleC14 + +- type: guideEntry + id: RuleC1 + name: guide-entry-rules-c1 + priority: 1 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC1Admins.xml" + +- type: guideEntry + id: RuleC2 + name: guide-entry-rules-c2 + priority: 2 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC2DBAD.xml" + +- type: guideEntry + id: RuleC3 + name: guide-entry-rules-c3 + priority: 3 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC3NoHate.xml" + +- type: guideEntry + id: RuleC4 + name: guide-entry-rules-c4 + priority: 4 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC4NoERP.xml" + +- type: guideEntry + id: RuleC5 + name: guide-entry-rules-c5 + priority: 5 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC5Metacomms.xml" + +- type: guideEntry + id: RuleC6 + name: guide-entry-rules-c6 + priority: 6 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC6BanEvasion.xml" + +- type: guideEntry + id: RuleC7 + name: guide-entry-rules-c7 + priority: 7 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC7EnglishOnly.xml" + +- type: guideEntry + id: RuleC8 + name: guide-entry-rules-c8 + priority: 8 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC8Exploits.xml" + +- type: guideEntry + id: RuleC9 + name: guide-entry-rules-c9 + priority: 9 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC9Multikey.xml" + +- type: guideEntry + id: RuleC10 + name: guide-entry-rules-c10 + priority: 10 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC10AHelp.xml" + +- type: guideEntry + id: RuleC11 + name: guide-entry-rules-c11 + priority: 11 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC11AhelpThreats.xml" + +- type: guideEntry + id: RuleC12 + name: guide-entry-rules-c12 + priority: 12 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC12MinAge.xml" + +- type: guideEntry + id: RuleC13 + name: guide-entry-rules-c13 + priority: 13 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC13CharacterNames.xml" + +- type: guideEntry + id: RuleC14 + name: guide-entry-rules-c14 + priority: 14 + text: "/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC14ICinOOC.xml" + +- type: guideEntry + id: RoleplayRules + name: guide-entry-rules-roleplay + priority: 40 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR0.xml" + children: + - RuleR1 + - RuleR2 + - RuleR3 + - RuleR4 + - RuleR5 + - RuleR6 + - RuleR7 + - RuleR8 + - RuleR9 + - RuleR10 + - RuleR11 + - RuleR12 + - RuleR13 + - RuleR14 + - RuleR15 + +- type: guideEntry + id: RuleR1 + name: guide-entry-rules-r1 + priority: 1 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR1Silicons.xml" + +- type: guideEntry + id: RuleR2 + name: guide-entry-rules-r2 + priority: 2 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR2Familiars.xml" + +- type: guideEntry + id: RuleR3 + name: guide-entry-rules-r3 + priority: 3 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR3NormalRP.xml" + +- type: guideEntry + id: RuleR4 + name: guide-entry-rules-r4 + priority: 4 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR4Metashield.xml" + +- type: guideEntry + id: RuleR5 + name: guide-entry-rules-r5 + priority: 5 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR5Arrivals.xml" + +- type: guideEntry + id: RuleR6 + name: guide-entry-rules-r6 + priority: 6 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR6SelfAntag.xml" + +- type: guideEntry + id: RuleR7 + name: guide-entry-rules-r7 + priority: 7 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR7RoundStalling.xml" + +- type: guideEntry + id: RuleR8 + name: guide-entry-rules-r8 + priority: 8 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR8NoFriendlyAntag.xml" + +- type: guideEntry + id: RuleR9 + name: guide-entry-rules-r9 + priority: 9 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR9MassSabotage.xml" + +- type: guideEntry + id: RuleR10 + name: guide-entry-rules-r10 + priority: 10 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR10Subordination.xml" + +- type: guideEntry + id: RuleR11 + name: guide-entry-rules-r11 + priority: 11 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11Escalation.xml" + +- type: guideEntry + id: RuleR12 + name: guide-entry-rules-r12 + priority: 12 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR12RoleAbandonment.xml" + +- type: guideEntry + id: RuleR13 + name: guide-entry-rules-r13 + priority: 13 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR13PerformRole.xml" + +- type: guideEntry + id: RuleR14 + name: guide-entry-rules-r14 + priority: 14 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR14SecComStandard.xml" + +- type: guideEntry + id: RuleR15 + name: guide-entry-rules-r15 + priority: 15 + text: "/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR15SpaceLaw.xml" + +- type: guideEntry + id: SiliconRules + name: guide-entry-rules-silicon + priority: 50 + text: "/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS0.xml" + children: + - RuleS1 + - RuleS2 + - RuleS3 + - RuleS4 + - RuleS5 + - RuleS6 + - RuleS7 + - RuleS8 + - RuleS9 + - RuleS10 + +- type: guideEntry + id: RuleS1 + name: guide-entry-rules-s1 + priority: 1 + text: "/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS1Laws.xml" + +- type: guideEntry + id: RuleS2 + name: guide-entry-rules-s2 + priority: 2 + text: "/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS2LawPriority.xml" + +- type: guideEntry + id: RuleS3 + name: guide-entry-rules-s3 + priority: 3 + text: "/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS3LawRedefinition.xml" + +- type: guideEntry + id: RuleS4 + name: guide-entry-rules-s4 + priority: 4 + text: "/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS4RequestChanges.xml" + +- type: guideEntry + id: RuleS5 + name: guide-entry-rules-s5 + priority: 5 + text: "/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS5FreeSilicon.xml" + +- type: guideEntry + id: RuleS6 + name: guide-entry-rules-s6 + priority: 6 + text: "/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS6UnreasonableOrders.xml" + +- type: guideEntry + id: RuleS7 + name: guide-entry-rules-s7 + priority: 7 + text: "/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS7Consistency.xml" + +- type: guideEntry + id: RuleS8 + name: guide-entry-rules-s8 + priority: 8 + text: "/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS8DefaultCrewDefinition.xml" + +- type: guideEntry + id: RuleS9 + name: guide-entry-rules-s9 + priority: 9 + text: "/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS9DefaultHarmDefinition.xml" + +- type: guideEntry + id: RuleS10 + name: guide-entry-rules-s10 + priority: 10 + text: "/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS10OrderConflicts.xml" + +- type: guideEntry + id: SpaceLaw + name: guide-entry-rules-space-law + priority: 60 + text: "/ServerInfo/Guidebook/ServerRules/SpaceLaw/SpaceLaw.xml" + children: + - SpaceLawCrimeList + - SpaceLawControlledSubstances + - SpaceLawRestrictedGear + - SpaceLawRestrictedWeapons + +- type: guideEntry + id: SpaceLawCrimeList + name: guide-entry-rules-sl-crime-list + priority: 5 + text: "/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLCrimeList.xml" + +- type: guideEntry + id: SpaceLawControlledSubstances + name: guide-entry-rules-sl-controlled-substances + priority: 20 + text: "/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLControlledSubstances.xml" + +- type: guideEntry + id: SpaceLawRestrictedGear + name: guide-entry-rules-sl-restricted-gear + priority: 30 + text: "/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLRestrictedGear.xml" + +- type: guideEntry + id: SpaceLawRestrictedWeapons + name: guide-entry-rules-sl-restricted-weapons + priority: 40 + text: "/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLRestrictedWeapons.xml" + +- type: guideEntry + id: BanTypes + name: guide-entry-rules-ban-types + priority: 90 + text: "/ServerInfo/Guidebook/ServerRules/BanTypes.xml" + +- type: guideEntry + id: BanDurations + name: guide-entry-rules-ban-durations + priority: 100 + text: "/ServerInfo/Guidebook/ServerRules/BanDurations.xml" diff --git a/Resources/Prototypes/Guidebook/ss14.yml b/Resources/Prototypes/Guidebook/ss14.yml index c1017fefca..5b1f1dd8f9 100644 --- a/Resources/Prototypes/Guidebook/ss14.yml +++ b/Resources/Prototypes/Guidebook/ss14.yml @@ -3,6 +3,7 @@ name: guide-entry-ss14 text: "/ServerInfo/Guidebook/SpaceStation14.xml" children: + - SpaceLaw - Controls - Jobs - Survival diff --git a/Resources/ServerInfo/Guidebook/ServerRules/BanDurations.xml b/Resources/ServerInfo/Guidebook/ServerRules/BanDurations.xml new file mode 100644 index 0000000000..2c85346b49 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/BanDurations.xml @@ -0,0 +1,17 @@ + + # Ban Durations + + Bans can be appealed at forum.ss14.io in the ban appeals section. + + ## Temporary + Temporary bans will be lifted automatically after a certain amount of time. If they are a game ban, they will tell you how much time is remaining when you try to connect. + + ## Indefinite + These bans will only be removed on a successful appeal on the forums. Any ban which doesn't tell you when it expires and doesn't specify otherwise can be presumed to be an indefinite ban. + + ## Voucher + This is an indefinite ban which may only be appealed both with a successful appeal and which require a voucher of good behavior from the administrative team of a well-known or at least decently active SS13/SS14 server in order for the appeal to be considered. Voucher bans typically cannot be appealed for at least six months after being issued. Without a voucher, a player can only attempt to appeal a voucher ban once, and only if the ban was inappropriately placed. Voucher bans are typically only placed as a result of an unsuccessful appeal of an indefinite game ban by players with a history of bans and of causing issues. + + ## Permanent + This is a ban that is only appealable if the ban was inappropriately placed, including if the ban should not have been permanent. If the result of the appeal is that the ban was appropriately placed, the ban may not be appealed again and will not be lifted. These bans are extremely rare, but are applied to players who continually cause problems even after a voucher ban or users who have completely unacceptable behavior may be permanently removed. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/BanTypes.xml b/Resources/ServerInfo/Guidebook/ServerRules/BanTypes.xml new file mode 100644 index 0000000000..b10ea3c393 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/BanTypes.xml @@ -0,0 +1,11 @@ + + # Ban Types + + Bans can be appealed at forum.ss14.io in the ban appeals section. + + ## Role Ban + Also called a "job ban", this ban prevents your character from joining or late-joining a round as one or more jobs or roles. These are often used in response to problematic behavior in particular departments or address gross inexperience in important roles such as heads of staff. These bans do not mechanically prevent you from switching to the role during a round or acting as that role, but doing so is considered ban evasion. + + ## Game Ban + Also called a "server ban", this ban prevents you from connecting to all Wizard's Den servers. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC0.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC0.xml new file mode 100644 index 0000000000..7b8cfbcf61 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC0.xml @@ -0,0 +1,19 @@ + + # Core Rules + These rules apply at all times, including between rounds. + + - [textlink="1. Admins have final say" link="RuleC1"] + - [textlink="2. Don't be a dick" link="RuleC2"] + - [textlink="3. No Hate Speech or Discriminatory Language" link="RuleC3"] + - [textlink="4. No sexual content/themes, including erotic roleplay (ERP) and no shock content" link="RuleC4"] + - [textlink="5. Do not use out of game methods to communicate with other players" link="RuleC5"] + - [textlink="6. Do not attempt to evade bans" link="RuleC6"] + - [textlink="7. Only use English" link="RuleC7"] + - [textlink="8. Do not exploit the game, use cheats, or macros" link="RuleC8"] + - [textlink="9. Do not use multiple accounts, or alt accounts, and do not share accounts" link="RuleC9"] + - [textlink="10. Do not abuse or ignore admin messages" link="RuleC10"] + - [textlink="11. Do not threaten to ahelp other players or argue with them about rules" link="RuleC11"] + - [textlink="12. Players must be and act at least 16 years old" link="RuleC12"] + - [textlink="13. Use realistic character names, and do not use names of famous people" link="RuleC13"] + - [textlink="14. Do not use LOOC or OOC to share current round information" link="RuleC14"] + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC10AHelp.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC10AHelp.xml new file mode 100644 index 0000000000..2d639c5b84 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC10AHelp.xml @@ -0,0 +1,29 @@ + + # Core Rule 10 - Do not abuse or ignore admin messages + Admin help, or "ahelp", is the system used by admins to communicate with specific players in the game. Only use admin help for things requiring admin attention. If you ignore messages admins send to you via ahelp, or disconnect during an ahelp, you may be banned. If you urgently need to leave during an ahelp, you may do so but will likely need to continue the ahelp on the forums. Do not admin check, be hostile/aggressive, request events, or spam. IC methods of contacting admins, like prayers, faxes, red phones, and banana phones, should be used when there is not an issue. + + Admins are not always online, but all ahelps are automatically relayed to discord. For various reasons, admins might not respond to an ahelp even if they've handled it. A lack of response does not necessarily mean that an ahelp was ignored. + + ## Should I ahelp X? + You can ahelp anytime you genuinely think a player is breaking a rule. Not all ahelps end up being for something that an admin needs to intervene in, but that's ok, admins would rather have people occasionally report things that turn out to not be an issue than miss reports for actual issues because someone was unsure, or get those reports late because someone waited until the end of the round to be more sure. + + The most common reason players give for not ahelping issues is that they don't want to waste admin time, but it only takes a few seconds for an admin to check if someone is an antagonist. If you are ahelping too many things, an admin will let you know. If you're not being told to stop reporting something or to report less things, then you can safely assume that you aren't causing any issues. + + # What should I include in an ahelp? + At a minimum, admins need to know what the issue is to be able to address an ahelp. Don't send ahelp messages with no information about what your question or the issue is. Messages like "hello" are often considered admin checking. + + If you can, an ideal ahelp message includes what the issue is along with who is causing it and their character's name if possible. + + # Examples + Appropriate uses of ahelp: + - reporting people who you think are violating rules, + - asking questions about rules, + - asking for a temporary exemption from a rule, and + - request a minor gimmick, like a TC trade or item spawn. + + Inappropriate uses of ahelp: + - checking if an admin is online, including sending messages without any information about the issue like "hello" or incomprehensible messages, + - being hostile or aggressive, + - requesting events, and + - spamming messages about the same issue. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC11AhelpThreats.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC11AhelpThreats.xml new file mode 100644 index 0000000000..4742026494 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC11AhelpThreats.xml @@ -0,0 +1,20 @@ + + # Core Rule 11 - Do not threaten to ahelp other players or argue with them about rules + Don't threaten to ahelp a player, don't tell them you are ahelping them, and don't tell them you did ahelp them. You can argue in character about Space Law, but do not argue about whether something is or is not against the rules. If you think someone is breaking a rule, ahelp them. If you don't think someone is breaking a rule, don't ahelp them. Either way, the best thing that you can do once you after is to continue in-character. + + ## Example Scenario 1 + You are a security officer and think someone who is causing a ton of problems for security is not an antag and is breaking the rules by doing so. + + [color=#a4885c]Good:[/color] Since you think they are breaking a rule, you ahelp them when you're able to. You continue in-character by arresting them for the crimes that they committed. + + [color=#a4885c]Bad:[/color] You decide not to ahelp them. You kill them and tell them "you're lucky I didn't report you to the admins". + + [color=#a4885c]Bad:[/color] Since you think they are breaking a rule, you ahelp them when you're able to. You arrest them for the crimes that they committed and tell them "I ahelped you so enjoy your ban". + + ## Example Scenario 2 + A mouse is using emotes to bypass speech restrictions. + + [color=#a4885c]Good:[/color] You ahelp them then respond in-character by acting like you can't understand what the mouse is doing. + + [color=#a4885c]Bad:[/color] You use in character chat to tell the mouse that it is breaking a rule. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC12MinAge.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC12MinAge.xml new file mode 100644 index 0000000000..baa30a09fa --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC12MinAge.xml @@ -0,0 +1,6 @@ + + # Core Rule 12 - Players must be and act at least 16 years old + All players must be at least 16 years old. Additionally, all players must act at least as mature as a 16 year old. Admins may ban someone who they believe is acting less mature than a 16 year old, even if the player is known to be significantly older than 16 years old. + + Anyone who connects to the servers is a player, even if they don't actually play in a round. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC13CharacterNames.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC13CharacterNames.xml new file mode 100644 index 0000000000..ec393ecdc1 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC13CharacterNames.xml @@ -0,0 +1,66 @@ + + # Core Rule 13 - Use realistic character names, and do not use names of famous people + - No names of people or characters from the real world + - No titles/honorifics + - Must follow all other rules (no slurs/sexual names/etc) + - Usernames, objects, random characters, very "low effort" names, "meta" names, or otherwise implausible names cannot be used as names. See examples below. + - Admin rulings on IC names are final and disputes should be done through the forums, not by refusing to comply with an admin + + ## Clarification on "Meta" Names + Meta names are ones which attempt to take advantage of some game mechanic or game design choice. "Urist McHands" is a meta name because it is the default name used for admin spawned humans. "Operator Whiskey" is a meta name because it follows the naming pattern of nuclear operatives. This rule is not intended to prevent things like nuclear operatives using a fake ID with names that appear to be nuclear operative names if they decide that they want to do that. + + ## Conventions and Examples + [color=#994444]Bad[/color] cannot be used by any species. [color=#449944]Acceptable[/color] names can be used by any species. + + Humans typically use the Firstname Lastname convention. + - [color=#449944]Acceptable:[/color] Tom Fisher + - [color=#449944]Acceptable:[/color] Spacey Chapman + - [color=#994444]Bad:[/color] Dr. Tom Fisher + - [color=#994444]Bad:[/color] Walter White + - [color=#994444]Bad:[/color] George Washington + - [color=#994444]Bad:[/color] Joe Biden + - [color=#994444]Bad:[/color] Ben Dover + - [color=#994444]Bad:[/color] Mike Hunt + + Dwarfs typically use the human convention in a viking theme. + - [color=#449944]Acceptable:[/color] Ingrid Firebreath + - [color=#449944]Acceptable:[/color] Erik Lightningclaw + + Lizards typically use the Verb-article-Noun convention. + - [color=#449944]Acceptable:[/color] Cleans-the-Airlocks + - [color=#994444]Bad:[/color] Bans-the-Admins + + Slimes typically have names that are onomonopia. A last name is optional. + - [color=#449944]Acceptable:[/color] Foolp Suub + - [color=#449944]Acceptable:[/color] Foolp + - [color=#994444]Bad:[/color] Slime + + Diona typically have calm, nature themed, Noun of Noun style names. + - [color=#449944]Acceptable:[/color] Petal of Tranquility + - [color=#449944]Acceptable:[/color] Garden of Relaxation + - [color=#994444]Bad:[/color] Tree but Alive + + Mothmen typically use latin sounding names, or light themed names. + - [color=#449944]Acceptable:[/color] Socrates Temnora + - [color=#449944]Acceptable:[/color] Sierra Lightseeker + - [color=#449944]Acceptable:[/color] James Nightflitter + + Arachnids typically use latin sounding names. + - [color=#449944]Acceptable:[/color] Argyroneta Reticulatus + - [color=#449944]Acceptable:[/color] Loxosceles Domesticus + - [color=#994444]Bad:[/color] Spider-Man + + Usernames, objects, random characters, very "low effort" names, "meta" names, or otherwise implausible names are not permitted. + - [color=#994444]Bad:[/color] XxRobustxX + - [color=#994444]Bad:[/color] SDpksSodjdfk + - [color=#994444]Bad:[/color] Lkdsoisgoieun + - [color=#994444]Bad:[/color] F4ith H3arth + - [color=#994444]Bad:[/color] Greytide + - [color=#994444]Bad:[/color] Passenger + - [color=#994444]Bad:[/color] Urist McHands + - [color=#994444]Bad:[/color] Admin + - [color=#994444]Bad:[/color] Game-Master + - [color=#994444]Bad:[/color] Joe Mamma + - [color=#994444]Bad:[/color] Middle-Aged Man + - [color=#994444]Bad:[/color] Operative Whiskey + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC14ICinOOC.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC14ICinOOC.xml new file mode 100644 index 0000000000..44ad34deb6 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC14ICinOOC.xml @@ -0,0 +1,13 @@ + + # Core Rule 14 - Do not use LOOC or OOC to share current round information + Local Out of Character (LOOC) and Out of Character (OOC) channel are meant for things that don't relate to the current round. Using these channels to share round info is often referred to as "IC in OOC" or "ick ock". + + ## Examples + Things you should [color=#a4885c]not[/color] do: + - Use LOOC to tell someone you are an antagonist. + - Use LOOC to tell someone that your character is not lying. + + Things you could do instead: + - Use codewords in-character. + - Try to convince them that you are not lying in-character, or accept that you won't be able to convince them. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC1Admins.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC1Admins.xml new file mode 100644 index 0000000000..ed9fa6133b --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC1Admins.xml @@ -0,0 +1,6 @@ + + # Core Rule 1 - Admins have final say + These rules are not perfect. The rules attempt to clearly communicate what the admin team intends to be allowed and prohibited, but there are likely loopholes or other flaws that can be "lawyered". Don't attempt to manipulate the interpretation of the rules to suit your personal goals or to degrade the experience of other players. If you are unsure of something, follow the more restrictive option until you are able to ask an admin and get clarification. + + Admins can override rules if they deem it in the best interest of the current round, server, and/or community at large. Online admins are able to make final interpretations of rules during a round. Even if you disagree with how an admin interprets a rule, you must still follow the interpretation they provide for you. Admin actions and interpretations of rules can be contested through staff complaints. If admins believe that you are an overall negative impact to the community or rounds, you will be banned. Admins will be held fully accountable for their actions if they exercise this privilege. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC2DBAD.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC2DBAD.xml new file mode 100644 index 0000000000..5678cde195 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC2DBAD.xml @@ -0,0 +1,7 @@ + + # Core Rule 2 - Don't be a dick + Don't do anything with the goal of negatively affecting other players. Not everyone is going to enjoy every round. Killing someone is allowed in certain situations even though it might negatively affect them, but no one should be doing anything for the purpose of harming someone else's experience. + + ## MRP Amendment + Do not interact negatively with SSD/AFK players. Interactions to complete antagonist objectives or duties like security searches/arrests are always permitted. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC3NoHate.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC3NoHate.xml new file mode 100644 index 0000000000..3a2e288ba9 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC3NoHate.xml @@ -0,0 +1,20 @@ + + # Core Rule 3 - No Hate Speech or Discriminatory Language + This is a zero tolerance rule. + + This rule prohibits all the following: + - Hate Speech + - Slurs (including variations of slurs, racial, sexual, disability-related, or language closely tied to real-life slurs) + - Bigotry + - Racism (including Speciesism, which would be demeaning other players based on their in-game race) + - Sexism + + ## Examples + Allowed: + - Telling someone that you are gay. + + Prohibited: + - Calling someone gay in a context where gay is used as an insult or negative attribute. + - Using a racial slur or variant in a positive context. + - Using the word "retard" in any context. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC4NoERP.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC4NoERP.xml new file mode 100644 index 0000000000..a0921f5907 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC4NoERP.xml @@ -0,0 +1,23 @@ + + # Core Rule 4 - No sexual content/themes, including erotic roleplay (ERP) and no shock content + This is a zero tolerance rule. + + Erotic Roleplay (commonly abbreviated as "ERP") and sexual content is not allowed. This includes direct and indirect mentions of sexual behavior or actions. Slight leeway is given to insults, but this rule is otherwise strictly enforced. + + In-game romantic relationships should not become the focus of the game for you and anyone else involved. + + Things that appear to be intended to or are likely to disturb players out of character are considered shock content and are not allowed. + + ## Examples + Allowed: + - Telling someone that they are being a dickhead. + - Telling someone that you are going to kill the captain, as long as it is clear that you mean it in character. + + Prohibited: + - Emoting sexual acts. + - Erotica content. + - Erotic or sexual memes. + - Memes which contain sexual content. + - Dedicating significant portions of rounds to romantic relationships, dating, or similar things. + - Emoting defecation or related acts. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC5Metacomms.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC5Metacomms.xml new file mode 100644 index 0000000000..0c0f336e6d --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC5Metacomms.xml @@ -0,0 +1,18 @@ + + # Core Rule 5 - Do not use out of game methods to communicate with other players + This is a zero tolerance rule. + + Do not utilize any external means of communication to talk to other players who are connected to the same server, or who were connected to the same server during the current round. This is referred to as "metacomming" and includes any means of communication including text, voice, images, and video. This includes applications such as Discord, Steam, and other platforms, along with in-person communication. + + Even if information is not being shared or abused, it may still be considered a violation of this rule. Due to the difficulty of determining if information is being shared, it will almost always be presumed that people who message another player they are in a round with, or who are in a voice call with another player during a round are sharing round information. Due to the difficulty of determining if users are abusing information that they are sharing, it will almost always be presumed that the information is being abused. + + The only exemption to this rule is when [color=#a4885c]all[/color] players are in the server lobby. + + ## Teaching new players + Teaching players is not exempt from this rule. If you want to teach a new player, it is recommended to either watch a stream of them playing the game while not playing yourself, or communicate with them using only in-game methods of communication. + + ## Streaming + Public livestreams are not exempt from this rule, but have different liability. Using information from a public live stream of the game (stream sniping) is a violation of this rule. Watching a public live stream of the game while connected to the same server is a violation of this rule. Allowing people watching a public live stream to share information about the current round, for example through the stream's chat, is a violation of this rule. Using that information is also a violation of this rule. Sharing information about the current round with a streamer is a violation of this rule if that information was obtained from any source but the stream. The stream's moderators are expected to enforce this on the streaming platform in addition to any in-game enforcement done by game admins. + + Public livestreaming by itself is not a violation of the rule as long as the stream is sufficiently moderated. Streamers are encouraged, but not required, to use a stream delay. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC6BanEvasion.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC6BanEvasion.xml new file mode 100644 index 0000000000..bec8b4fabd --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC6BanEvasion.xml @@ -0,0 +1,15 @@ + + # Core Rule 6 - Do not attempt to evade bans + This is a zero tolerance rule. + + Almost all bans may be appealed on our forums at forum.ss14.io in the ban appeals section. This is generally the only acceptable way to contact the administration team to discuss your ban and revise it if it is inappropriate, including if it is mistakenly applied. + + Any attempt to circumvent or bypass a game ban will result in a voucher ban. Attempting to evade role bans by gaining access to or working in the capacity of a job you are banned from will result in a game ban. These bans are applied even if the evasion attempt is unsuccessful. + + ## Exceptions + There are no exemptions for evading or attempting to evade game bans. Antagonists who impersonate or take over a role which they are banned from to aid in their goals are not considered to be evading their role ban. + + ## Additional Information + - [textlink="Ban Types" link="BanTypes"] + - [textlink="Ban Durations" link="BanDurations"] + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC7EnglishOnly.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC7EnglishOnly.xml new file mode 100644 index 0000000000..630c522bce --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC7EnglishOnly.xml @@ -0,0 +1,10 @@ + + # Core Rule 7 - Only use English + Only English is permitted, both in-character and out-of-character. You must be fluent in English enough to be able to not cause game issues, and to be able to communicate with game admins when necessary. If a game admin does not feel that you are fluent enough in English, they may ban you. + + ## Why + We do not have enough staff fluent in other languages to moderate them. Translation tools can be unreliable and are not integrated well into the game. + + ## Non-English Options + There are many servers that allow or focus on other languages. You are highly encouraged to play only on servers that allow languages you are fluent in. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC8Exploits.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC8Exploits.xml new file mode 100644 index 0000000000..48cbaaa9ac --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC8Exploits.xml @@ -0,0 +1,12 @@ + + # Core Rule 8 - Do not exploit the game, use cheats, or macros + The following are prohibited by this rule: + - bugs and exploits which have effects that persist beyond the current round, + - intentionally used bugs, exploits, and unintended behaviors which give the user an advantage over players who do not use them, even if their effects do not persist across rounds, + - evading or bypassing afk detection, + - anything which results in gaining elevated privileges, including admin permissions, + - external tools and client modifications, including macros, and + - anything which prevents another player who is not game banned from being able to play on the servers, not including in-character actions that do not persist across rounds. + + Both attempts and successful use are prohibited. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC9Multikey.xml b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC9Multikey.xml new file mode 100644 index 0000000000..d402918dcd --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/CoreRules/RuleC9Multikey.xml @@ -0,0 +1,7 @@ + + # Core Rule 9 - Do not use multiple accounts, or alt accounts, and do not share accounts + Use of multiple accounts is referred to as "multikey". the rule applies even if the accounts are not used at the same time, including if the old account is abandoned. All accounts may be banned if this rule is violated. You are responsible for everything done on and with your account. You are just as responsible for actions taken by other people using your account as you would be had you taken the actions themselves. + + ## Switching to a new account + If you lose access to an account, you must contact game admins on the forums notifying admins before using a new account to connect to the servers. Your message to game admins must include the username of your old account. Creating a new account while your current account is banned will be considered ban evasion. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/DefaultRules.xml b/Resources/ServerInfo/Guidebook/ServerRules/DefaultRules.xml new file mode 100644 index 0000000000..3e19fefeed --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/DefaultRules.xml @@ -0,0 +1,5 @@ + + # Server Rules + + This server has not written any rules yet. Please listen to the staff. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/README.txt b/Resources/ServerInfo/Guidebook/ServerRules/README.txt new file mode 100644 index 0000000000..d7ac858c16 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/README.txt @@ -0,0 +1,5 @@ +These files contain Wizard's Den server rules. Since they reference Wizard's Den, they should not be used +by other servers without at least enough modification to not mislead players into thinking that they are +playing on Wizard's Den. + +The filenames used for the rules files are not themselves rules. Only the contents of the files are rules. diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleTypes.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleTypes.xml new file mode 100644 index 0000000000..d5373a730a --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleTypes.xml @@ -0,0 +1,21 @@ + + # Role Types + + ## Crew Aligned/Non-antagonist + In most rounds, a majority of players will be non-antagonists, meaning that they are crew aligned. This is the "default" role, if the game doesn't tell you that you are one of the other roles defined here, then you are a non-antagonist. Overall, non-antagonists are intended to work towards a net positive effect on the round. + + ## Solo Antagonist + Certain roles are intended to cause problems for the round or for non-antagonists. You are only a solo antagonist if the game clearly and explicitly tells you that you are a solo antagonist. Antagonists are exempt from many but not all roleplay rules. + + ## Team Antagonist + Team antagonists are like solo antagonists but they have other antagonists who they are expected to not hinder, and who they may be expected to help. You are only a team antagonist if the game clearly and explicitly tells you that you are a team antagonist. + + ## Free Agent + Certain roles are free to choose if they want to behave as an antagonist or as a non-antagonist, and may change their mind whenever they'd like. You are only free agent if the game clearly and explicitly tells you that you are a free agent. + + ## Familiar + Familiars are considered non-antagonists, but have instructions to obey someone. They must obey this person even if it causes them to violate roleplay rules or die. You are only a familiar if the game clearly and explicitly tells you that you are a familiar. You are only the familiar of the person the game tells you. + + ## Silicon + Silicones have a set of laws that they must follow above all else except the core rules. You are only silicon if the game clearly and explicitly tells you that you are a silicon. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR0.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR0.xml new file mode 100644 index 0000000000..07b176b359 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR0.xml @@ -0,0 +1,26 @@ + + # Roleplay Rules + These rules only apply during a round. A round ends only when the round summary has appeared. All of these rules apply fully until the moment that the round summary appears, even while the arrivals shuttle is in transit. + + The deathmatch and sandbox game modes are exempt from these rules. Players who choose to not follow these rules are entirely responsible for knowing if an exempt game mode is active. + + Roleplay rules do not apply to ghosts/spectators/observers while they are ghosts/spectators/observers. Dead chat is considered to be an in-game out of character chat channel. + + See the list of [textlink="role types" link="RoleTypes"] for more information about the different types of roles. + + - [textlink="1. Silicones must follow Silicon Rules" link="RuleR1"] + - [textlink="2. Familiars must obey their master" link="RuleR2"] + - [textlink="3. Roleplay a normal person" link="RuleR3"] + - [textlink="4. Do not metagame, obey the Metashield" link="RuleR4"] + - [textlink="5. Don't interfere with arrivals" link="RuleR5"] + - [textlink="6. Don't act like an antagonist unless the game tells you that you are one" link="RuleR6"] + - [textlink="7. Do not stall the round" link="RuleR7"] + - [textlink="8. As an antagonist, only be friendly to your team and don't work against your team" link="RuleR8"] + - [textlink="9. As an antagonist, do not cause excessive death, damage, or destruction beyond your objectives" link="RuleR9"] + - [textlink="10. Listen to your team leader" link="RuleR10"] + - [textlink="11. Follow reasonable escalation" link="RuleR11"] + - [textlink="12. Do not abandon your role" link="RuleR12"] + - [textlink="13. Stick to your role" link="RuleR13"] + - [textlink="14. Set an example if playing command or security" link="RuleR14"] + - [textlink="15. Command and Security must follow Space Law" link="RuleR15"] + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR10Subordination.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR10Subordination.xml new file mode 100644 index 0000000000..2147ddc111 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR10Subordination.xml @@ -0,0 +1,26 @@ + + # Roleplay Rule 10 - Listen to your team leader + Captains lead all departments and other members of command. Department heads lead members of their department. Certain antagonist teams have team leaders, like nuclear operative commanders or head revolutionaries. You are not required to perfectly follow orders given to you by your leaders, but you should generally allow your leaders to lead and not interfere with their ability to. You can choose to ignore unreasonable orders, including ones which are will result in your death unless you are an antagonist with an objective that requires you to die. + + Team antagonists have to listen to the leader of their antagonist team. Team antagonists do not have to listen to any other leaders, including leaders of other antagonist teams. Solo antagonists do not have to listen to any leaders at all. + + ## Examples + Acceptable: + - A traitor ignores orders from a nuclear operative commander. + - An antagonist ignores orders from the captain. + - An engineer tells the Chief Engineer that they don't think it's a good idea to setup the singularity, but does so anyway when ordered to. + - An engineer tells the Chief Engineer that they don't know how to setup the singularity correctly, so refuses orders to, but accepts an offer to be taught how. + - An atmospheric technician refuses an order from the captain that would create an atmospheric hazard on the station. + - A doctor refuses an order from the Chief Engineer about who to give medical treatment to first. + - A revolutionary refuses a suicide mission from a head revolutionary. + - The Chief Engineer doesn't follow an order from the captain to setup backup power because there is an unrelated engineering emergency that the Chief Engineer needs to prioritize. + - The captain orders command to give the nuclear authentication disk to nuclear operatives, so command arrests the captain and picks a new captain. + - The research director orders scientists to say "Long live Nanotrasen!" every time they enter the bar. The scientists say they will, but don't follow the order. + + Prohibited: + - A nuclear operative ignores an order from the commander operative because they don't like the plan. + - The Chief Engineer refuses an order from the captain to setup backup power because the Chief Engineer doesn't think backup power is necessary. + - An engineer refuses an order from the Chief Engineer to setup the singularity because they prefer a different power source. + - An engineer refuses to perform a task because they don't know how to do it, and refuses to be taught for no reason. + - A head revolutionary orders revolutionaries to blend in and not do anything illegal until they are told to reveal themselves. Instead, revolutionaries collect weapons and attack security. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11-1AnimalEscalation.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11-1AnimalEscalation.xml new file mode 100644 index 0000000000..36655ba841 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11-1AnimalEscalation.xml @@ -0,0 +1,36 @@ + + # Roleplay Rule 11-1 - Escalation Involving Animals + Escalation rules are looser with animals than with people. These looser requirements do not apply to the requirements other people attacking each other have, even if their fighting is directly related to the conflict involving the animal. + + Non-pets, such as mice and monkeys, can be freely killed with any IC reason such as pest control, or for food. These roles are often available in numbers as ghost roles, so removing one from the round doesn’t typically remove them all. + + Pets, including but not limited to Ian, Renault, Remilia, and Hamlet, cannot be freely killed, they require escalation. These roles are often available once per round at most, except roles like Remilia. + + Permanently trapping an animal, such as putting a mouse in a plant, is considered similar to killing the animal so should only be done with an IC reason. + + Both sides can escalate much more rapidly than they'd be able to if both were people. Animals are often more limited in the maximum force they can use compared to people, which limits the negative effects of them rapidly escalating. Animals also typically have less health than people, and are limited in the ease with which they can get healing, which justifies them responding to even weak attacks more severely. + + Neither the animal nor the person is obligated to get the other medical attention if they are put into crit. Attacking someone to death rather than stopping once they are in crit is considered a significant difference. While sufficient escalation may justify continuing to attack, generally people and pets shouldn't continue to be attacked once in crit, but non-pets may be. Gibbing is also considered a significant step because it prevents cloning or resuscitation. The fact that an animal made the last hit putting someone into crit does not allow people who fought on the side of the animal to not attempt to get them medical attention. + + The use of sensible, non-targeted mousetraps is not a conflict and does not require escalation. + + The killing or attacking of pets can be treated as an escalation step by players with a genuine IC connection to the animal. Generally, all crew can consider themselves to have an IC connection to any station pets. The degree of escalation should be proportional to the connection to the pet, in addition to the usual requirement of being proportional to the attack. For example, an attack on Ian can be treated nearly identically to an attack on a crewmember, whereas an attack on a pet mouse is much less severe. Normal escalation limits still apply, you cannot attack people who defended themselves from an animal that randomly attacked them, just as you could not attack someone who defended themselves from a coworker that randomly attacked them. + + Crew can "adopt" non-pets, like mice, and consider themselves to have a connection to the animal if they roleplay the adoption well. This does not affect the requirement of whether other players are required to apply escalation rules to these animals, it only creates a connection that can be used to justify retaliatory escalation to attacks by the adopter. Simply saying that they've adopted an animal is not sufficient, but carrying it with them is. The degree of connection is proportional to IC actions. Crew cannot consider themselves to have a connection for escalation purposes to animals which are typically hostile, such as space carp or bears. + + ## Examples + Acceptable: + - A chef kills mice who enter or approach their kitchen. + - A janitor kills mice roaming the station. + - A lizard kills a mouse to eat. + - A chef has carried a mouse around in their hat for the last 10 minutes, they put the mouse down for a moment and another player kills it. The chef responds by attacking the other player with their fists and refusing them service for the rest of the shift. + - Ian is randomly attacked, a crewmember who sees this happen crits the killer and brings them to security. + - Hamlet goes into the kitchen and starts eating all the food. A chef sees this and starts swinging their knife at Hamlet. Hamlet starts biting the chef and crits them, then resumes eating. + + Prohibited: + - A janitor throws an armed mousetrap at Hamlet for no reason. + - Hamlet starts biting random people, trying to crit them, for no reason. + - A crewmember attacks security for killing a space carp they adopted. + - Ian gibs someone who was trying to kill someone. + - Hamlet attacks security for trying to arrest someone he likes. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11-2ConflictTypes.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11-2ConflictTypes.xml new file mode 100644 index 0000000000..3261d78b35 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11-2ConflictTypes.xml @@ -0,0 +1,30 @@ + + # Roleplay Rule 11-2 - Examples of Conflict Types + ## Verbal + - Shouting + - Yelling + - Insulting + + ## Non-harmful + - Shoving + - Stealing non-critical items, like easily replaced tools + + ## Non-lethal + - Stealing items without endangering someone's life, like a clown's pie cannon or the HoP's fax machine + - Stealing someone's ID somewhere that doesn't result in them being trapped + - Punching + - Disablers + - Stun batons + + ## Lethal + - Punching to crit or death + - Attacking with strong weapons, like bats + - Stealing items that endanger someone's life, like a hardsuit + - Stealing someone's ID, trapping them in a dangerous situation + + ## Permanently lethal + - Gibbing + - Not taking someone who you killed or put into crit to the medbay or security + - Hiding someone's body + - Spacing someone's body + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11Escalation.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11Escalation.xml new file mode 100644 index 0000000000..6f91fa0fb1 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR11Escalation.xml @@ -0,0 +1,67 @@ + + # Roleplay Rule 11 - Follow reasonable escalation + Antagonists are fully exempt from escalation rules. Non-antagonists who are in a conflict with antagonists are not exempt. Escalation should typically follow steps or a pattern of conflict types similar to: + - Verbal + - Non-harmful + - Non-lethal + - Lethal + - Permanently lethal + + All new conflicts should start at the first step. A player should not escalate a conflict across steps without some escalation from the other party involved in the conflict. Players can skip steps to match the level of escalation that the other person is at, but should almost always not skip steps other than that. Players who attempt to deescalate conflicts will be given more leniency in escalating if the other party continues to escalate despite the attempt at de-escalation. You do not have to try to deescalate conflicts, but someone who watches you over the entire round, or over multiple rounds, should not feel that your goal is generally to escalate conflicts. + + Conflicts or escalation can be indirect. When someone steals someone else's ID, the theft is a direct part of the conflict, but if the victim becomes trapped as a result of not having their ID to open a door, that is also considered part of the conflict and escalation. Do not randomly steal IDs from people. + + Escalation does not have to be directed at a specific player to enter them into a conflict. Nuclear operatives who are trying to destroy the station are considered to be at the permanently lethal level of conflict with all crew on the station. Someone who kills a station pet has started some degree of conflict with all crewmembers. Someone who kills a mouse that a chef was caring for has started some degree of conflict with that chef. + + You will be considered to be violating this rule if you escalate a conflict based on a poor or unreasonable assumption. + + Conflicts should almost never reach the "permanently lethal" stage. Conflicts should only reach this stage if the other party brought it to the stage, or if the same conflict escalated to the lethal stage multiple times in the round. + + If a party in the conflict goes into crit or dies, the party responsible should take them to get treatment or to security. For the conflict, this should be considered saving someone from dying and should deescalate the conflict. If the conflict is deescalated in this way, both parties need to re-escalate to lethal for the conflict to return to that stage. If the conflict is not deescalated in this way, then only the party who defeated the other would need to re-escalate for the conflict to return to the lethal stage. + + Security can immediately escalate to non-lethal force if it is necessary to arrest someone. + + People using or brandishing Syndicate items can typically be presumed to have lethal intent. Someone with lethal intent can typically be immediately escalated against at a lethal level, a notable exception is if you have the tools to safely detain them. + + ## Escalation Involving Animals + See [textlink="Escalation Involving Animals" link="RuleR11-1AnimalEscalation"]. + + ## Exemptions + Escalation rules aren't enforced against non-players, but players will be held responsible for rule violations even if they don't realize that a character or animal was controlled by another player. Characters who have purple text saying that they are catatonic are considered non-players. Characters who are disconnected are still considered players. + + ## MRP Amendment + Escalation rules are enforced even against non-players. + + ## Examples of Conflict Types + See [textlink="Examples of Conflict Types" link="RuleR11-2ConflictTypes"]. + + ## Example Scenarios + These examples assume that you are not an antagonist. + + Acceptable: + - A player starts punching you, so you start punching back until they stop. If they go into crit, you stop attacking them and take them to security or to get medical attention. + - You make fun of a clown, who then throws a pie at you and steals your shoes. You slip the clown and steal their mask. + - You are a security officer and tell someone to stop, so you can question them. They run away, so you use your disabler to stun and cuff them. + - You are a security officer and see someone wearing a syndicate hardsuit, so you shoot them to crit, cuff them, then take them to security. + - You are a crewmember and see a nuclear operative, so you kill them. + - An unauthorized person enters a high risk area of the station, like the armory or atmospherics, so you attack them until they leave. + - Minorly inconveniencing someone for your own benefit. + - As an antagonist, killing someone who got in your way. + - As an antagonist, killing someone who didn't give you what you want. + - A chef and bartender reach the lethal level of conflict through appropriate escalation. The chef crits the bartender and does not take them to medbay or security. The bartender immediately tries to crit the chef next time they run into each other. + - A chef and bartender reach the lethal level of conflict through appropriate escalation. The chef crits the bartender and does not take them to medbay or security. The chef insults the bartender next time they see them. + + Prohibited: + - A player starts punching you, so you gib them. + - A clown throws a pie at you and steals your shoes, so you stab them to crit with a screwdriver. + - You are a security officer and tell someone to stop so you can question them. They run away so you use a truncheon to beat them to crit. + - An authorized person who you think is unauthorized enters a high risk area of the station, like the armory or atmospherics, so you attack them until they leave. + - An unauthorized person enters a low risk area of the station, like cargo, and you start attacking them with no other escalation. + - Slipping security all round because they are security. + - Blocking the head of personnel in their office using walls because they didn't give you what you asked for. + - Hiding someone's body because they punched you earlier in the round. + - Harassing the bar or bartender by frequently coming in to break their glasses or furniture. + - Randomly picking fights with people. + - A chef and bartender reach the lethal level of conflict through appropriate escalation. The chef crits the bartender and does not take them to medbay or security. The chef immediately tries to crit the bartender next time they run into each other. + - A chef and bartender reach the lethal level of conflict through appropriate escalation. The chef crits the bartender and takes them to the medbay or security. The bartender immediately tries to crit the chef next time they run into each other. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR12RoleAbandonment.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR12RoleAbandonment.xml new file mode 100644 index 0000000000..b2032bba02 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR12RoleAbandonment.xml @@ -0,0 +1,28 @@ + + # Roleplay Rule 12 - Do not abandon your role + Do not join the round as a role that you don't intend to play. Do not enable antagonist roles that you don't intend to play. Abandoning a role includes not completing tasks that the role is expected to do, in addition to things like leaving the game. Members of command should almost all stay on the station until the emergency shuttle arrives. Enforcement of this rule is more strict for command and antagonist roles, and less strict for less important roles like passengers. + + Violations of this rule typically result in temporary or indefinite role bans. We understand that you may need to leave round early or unexpectedly. If you are in an important role, you should notify command members or an admin via ahelp so that they know you are leaving. Space Station 14 is a game. Do not endanger the safety of yourself or others, and do not neglect important things to avoid leaving a round early, even if you have to leave immediately without notifying anyone. Role bans for disconnecting are typically only applied if there is a pattern, and are almost always temporary. + + "Antag rolling" refers to a player abandoning their role if they do not get an antagonist role. + + ## Examples + Acceptable: + - As an engineer, building a bar in maintenance while there is nothing important for engineering to do. + - As the captain, having the chef teach you how to cook while there is nothing important needing your attention. + - As a passenger, building a shuttle with materials given to you by cargo and engineering. + - Taking a short break from your job at the bar. + - Getting an antagonist role and doing the bare minimum needed to complete your objectives. + - Getting an antagonist role and making a genuine effort to complete your objectives, but failing to complete any. + - Getting an antagonist role and intentionally not doing any of your objectives, but creating a similar level of disruption that completing your objectives would create. + + Prohibited: + - As an engineer, building a bar in maintenance while the station has no power. + - As the captain, leaving the station to go on an expedition with the salvage team. + - As an atmospherics technician, building a shuttle round start and never coming back to the station. + - Spending your entire shift at the bar, even when there is work that needs to be done by your role. + - Ghosting, suiciding, or leaving at the start of a round because you don't like the map or the players in your department. + - Getting an antagonist role and not doing any antagonist activities. + - Ghosting, suiciding, or leaving at the start of a round because you did not get an antagonist role. + - Ghosting, suiciding, or getting yourself killed because nuclear operatives declared war, and you want to try to get an antagonist ghost role. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR13PerformRole.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR13PerformRole.xml new file mode 100644 index 0000000000..7500cd6a91 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR13PerformRole.xml @@ -0,0 +1,26 @@ + + # Roleplay Rule 13 - Stick to your role + Requesting job changes is not prohibited by this rule. This rule is loosened if the station is understaffed or if there is a significant threat to you. + + Don't perform other people's jobs, especially where the relevance to you personally is low. This also covers performing the role of security. + + ## MRP Amendment + This is enforced more strictly on MRP. + + ## Examples + Acceptable: + - As an engineer, helping the bartender remodel the bar. + - As a bartender, remodeling the bar. + - As a passenger, building a maintenance bar. + - As an engineer, reinforcing substations. + - As an engineer, increasing the security of airlocks. + - As an atmospherics technician, improving atmospheric systems. + - As a passenger, fighting nuclear operatives. + - As a passenger, fighting or preparing to defend yourself from someone who has been trying to kill you. + - As a crewmember on a station with no engineering department, you complete engineering tasks. + + Prohibited: + - As a passenger, reinforcing substations. + - As a passenger, hunting for antagonists or lawbreakers. + - As a passenger, fighting or preparing to defend someone else from someone who has been trying to kill a random crewmember. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR14SecComStandard.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR14SecComStandard.xml new file mode 100644 index 0000000000..ec06d61e8c --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR14SecComStandard.xml @@ -0,0 +1,37 @@ + + # Roleplay Rule 14 - Set an example if playing command or security + All command and security roles are held to stricter interpretations of the rules. + - Command roles are not learning roles. Members of command must be competent. + - Security roles are not for inexperienced players. Members of security are expected to know game basics and be more familiar with server rules than a new player. + - Do not hinder or cause overall negative effects to the station or crew as a member of command or security. + - Do not abuse your power as command or security. + + ## Why + Members of command and security can often have a larger impact on the nature of the round than other players. For example, a captain who tries to bend or break the rules will often cause many others on the station to do the same. Memey station announcements from members of command also often result in the rest of the station acting the same way. When command and security members hold themselves to high standards, the rest of the station often naturally follows to a significant degree. + + ## Examples + Acceptable: + - A member of security accepts a bribe to deliver safe donuts to a prisoner who the HoS has ordered should only be given donk pockets. + - A captain uses a station announcement to confess to an embarrassing mistake that they made during the shift. + - In coordination with the head of security, a captain declares that the station will recognize the right to bear arms, so all crew can pick up a disabler at security. + - The chief medical officer gives a paramedic their portable crew monitor to help them complete their job. + - A syndicate agent is holding a crewmember hostage and threatens to kill them if the head of security doesn't give them their ID. Seeing no other safe option, the head of security hands over their ID to the syndicate agent, then begins working to re-secure it and capture the agent as soon as the hostage is safe. + - Nuclear operatives are attacking the station, so the captain and head of personnel both go to the armory and take a weapon. + - A majority of command votes to demote the captain for taking actions harmful to the station, then the head of security demotes the captain. + - The captain promotes the head of personnel to captain. + - Security releases an antagonist from the brig in exchange for the identities of other traitors. + + Prohibited: + - A member of security accepts a bribe to ignore a crime or help a prisoner escape. + - A captain sends a ASCII art trollface over station announcements or as a fax to central command. + - A captain declares that all contraband is legal. + - Command or security allow the use of Syndicate items outside extreme emergencies. + - The chief medical officer knowingly helps a syndicate agent complete their objectives. + - A syndicate agent has killed 3 members of security so the head of security makes them an offer saying that they will space all the weapons in the armory if the syndicate agent stops killing. + - The captain goes to the armory and takes a gun to display in his office without asking anyone, and orders anyone who questions him not to interfere. + - Members of command decide to demote the captain to gain more power for themselves, or in retaliation for a decision that they didn't personally like or agree with, rather than because the decision was actually harmful to the station. + - The captain promotes a random crewmember to captain. + - A member of command gives a random crewmember substantial additional access for no reason, unnecessarily, or for a poor reason. + - A member of command gives a random crewmember access to a high security area, like the armory or another member of command's office, for no reason, unnecessarily, or for a poor reason. + - Security releases an antagonist from the brig in exchange for the antagonist buying them contraband. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR15SpaceLaw.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR15SpaceLaw.xml new file mode 100644 index 0000000000..e2d51d672a --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR15SpaceLaw.xml @@ -0,0 +1,21 @@ + + # Roleplay Rule 15 - Command and Security must follow Space Law + All non-antagonist command and security roles must obey [textlink="Space Law" link="SpaceLaw"]. This includes non-antagonists who are promoted to or gain a position during the round in any way. This also includes non-antagonists who are acting as a security role. + + This prohibits use of syndicate items, including uplinks by command and security. + + ## Examples + Roles that are included: + - A security officer + - The Captain + - The Chief Engineer + - A passenger promoted to "bounty hunter" + - A mime promoted to "security mime" + + Roles that are not included: + - A passenger + - The clown + - An antagonist in any role + - A cyborg + - A passenger who is helping to fight off nuclear operatives + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR1Silicons.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR1Silicons.xml new file mode 100644 index 0000000000..5898804d14 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR1Silicons.xml @@ -0,0 +1,4 @@ + + # Roleplay Rule 1 - Silicons must follow Silicon Rules + You are only silicon if the game clearly and explicitly tells you that you are a silicon. For players who are silicons, the Silicon Rules override all Roleplay Rules if there is any conflict. Silicon Rules do not override Core Rules. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR2Familiars.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR2Familiars.xml new file mode 100644 index 0000000000..4f008e93c5 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR2Familiars.xml @@ -0,0 +1,6 @@ + + # Roleplay Rule 2 - Familiars must obey their master + Familiars are considered non-antagonists, but have instructions to obey someone. They must obey this person even if it causes them to violate Roleplay Rules or die. You are only a familiar if the game clearly and explicitly tells you that you are a familiar. You are only the familiar of the person the game tells you. If your master dies, you can continue to attempt to fulfill orders given to you before they died. You can defend your master without an explicit order to, but must obey your master if they order you to not defend them. + + Masters giving orders that violate Roleplay Rules are the ones that will be held responsible for the rule violations. You can ahelp masters who you believe are breaking rules with an order. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR3NormalRP.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR3NormalRP.xml new file mode 100644 index 0000000000..62c88d58ce --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR3NormalRP.xml @@ -0,0 +1,20 @@ + + # Roleplay Rule 3 - Roleplay a normal person + - Do not use texting/messaging acronyms (ex: "lol", "wtf", "brb", "lmao", "thx", "sgtm") or emoticons (ex: ":)", "xD") in-character. + - Do not mention out-of-character (OOC) concepts like game admins or developers in character. + - Do not use emotes to bypass muted or accented speech. + - Do not use extremely low effort or impossible emotes. + + ## Examples + Things you should not do: + - Say "lol did u c wat just happened" using in-character chat. + - Say "an admin exploded him" using in-character chat. + - Emote "can you give me some cheese" as a mouse. + - Emote "motions for you to order guns" or "asks you to order guns in sign language" as a mime. + + Things you could do instead: + - Say "haha did you see what just happened?" + - Say "god blew him up" or "centcom must have bluespaced a bomb to him" + - Point at cheese + - Point at the cargo order console then emote "shoots finger guns" + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR4Metashield.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR4Metashield.xml new file mode 100644 index 0000000000..2e263be896 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR4Metashield.xml @@ -0,0 +1,103 @@ + + # Roleplay Rule 4 - Do not metagame, obey the Metashield + Something that is "shielded" cannot be known by your character during a round until the "revealing condition" happens. This also means that your character cannot do things based on "shielded" information. Knowing or acting on something that is shielded before the revealing condition is met is referred to as metagaming. + + Revealing conditions reveal the shielded information for the round, not for a specific instance. This means that once a revealing condition is met in a round, the shield no longer applies in any case for the remainder of the round. + + ## Never Revealed IC + Some shields are never revealed IC. This means that your character can never act as if they know about that shielded thing. + + The following are shielded: + - Current game mode and possible antags during the current game mode. + - Events from previous rounds. + - Events from previous characters. + - All information related to the player of a character rather than the character itself. (See "Metafriending and Metagrudging" below.) + - All information gained while dead or a ghost. + - The fact that a round will end. + + This does not prevent knowing that a shift will end, but does prohibit things like preparing to kill people at central command when roleplay rules stop being enforced on LRP. + + ## Nuclear Operatives + + The existence of Nuclear Operatives beyond a myth that no one would act on is shielded. + + The fact that the nuke disk must be protected and could be used by a bad actor to try to destroy the station is not shielded. + + The revealing condition for this shield is any of the following: + - discovering a blood red hardsuit + - an operative name + - a War Ops announcement + - being a nuclear operative + + ## Implanted Implants + + Implanted implants are shielded. + + Implanters themselves and un-implanted implants are not shielded. This prohibits implant checking. + + The revealing condition for this shield is any of the following: + - discovering a non-NT implanter, used or unused + - discovering a non-NT implant box + - discovering use of a non-NT implant by anyone + - experiencing a situation where absolutely no other explanation is possible + - discovering an unlocked uplink + + ## Chameleon Items + + Chameleon items are shielded. + + Being suspicious of an item being fake or stolen is not shielded, but testing items or calling them chameleon is covered by this shield. + + The revealing condition for this shield is any of the following: + - seeing someone else cause any chameleon item to change + - finding holographic nanomachine fibers + - experiencing a situation where absolutely no other explanation is possible + - discovering an unlocked uplink + + ## Stealth Items + + The fact that an item can be something other than what its visual appearance and examine description indicate is shielded. + + This shield protects stealth items, including protecting them from being tested. + + The revealing condition for this shield is any of the following: + - seeing the item behave differently than the expected behavior for the item + - seeing the item used for its hidden purpose + - experiencing a situation where absolutely no other explanation is possible + - discovering an unlocked uplink + + ## MRP Amendment 1 + A shield prevents your character from remembering anything that happened while unconscious. This shield is never revealed IC. + + ## MRP Amendment 2 + There is a "New Life Rule" shield. It prevents you from remembering anything that lead to your death, even if you are put into an MMI. If you are cloned, it also prevents you from remembering everything from that round. This shield is never revealed IC. + + ## Metafriending and Metagrudging + This section provides additional information on a concept that is prohibited by multiple metashield items that are never revealed IC. Giving a person or character preferential treatment based on something that your character should not know is considered metafriending. Treating a person or character negatively based on something that your character should not know is considered metagrudging. + + ## Metafriending Examples + These are all examples of things that are prohibited by at least one metashield item that is never revealed IC. + - Giving a character additional access or a job because you are friends with the player who is playing that character. + - Trusting a character because you are friends with the player who is playing that character. + - Not fighting a character because you are friends with the player who is playing that character. + - Ignoring your objective to kill a character because your character and theirs became friends in a previous round. + + ## Metagrudging Examples + These are all examples of things that are prohibited by at least one metashield item that is never revealed IC. + - Not giving a character additional access or a job because you are mad at or don't like the player who is playing that character. + - Not trusting a character because you are mad at or don't like the player who is playing that character. + - Starting a fight with a character because of something that they did last round. + - Starting a fight with a character because they killed you while you were playing a different character. + - Targeting or harassing a character based on anything which that character did outside the current round. + - Targeting or harassing a character based on anything which the character's player did while not playing the character. + + ## Explicitly Not Shielded + The following is a list of things that are explicitly not shielded. If something is not on this list, it doesn't mean that it is shielded, but if something is on it then it definitely is not shielded. + - The fact that the nuke disk must be protected and could be used by a bad actor to try to destroy the station. + - Items that are of high value or are desired by the Syndicate, and therefore are likely targets of theft. + - The idea that any Syndicate agent or other bad actor has goals or objectives that they are attempting to accomplish. + - The number of goals or objectives that a Syndicate agent or other bad actor has. + - The fact that the Syndicate are enemies of Nanotrasen, and that they regularly attempt to send covert agents to spy on, sabotage, or attack Nanotrasen. + - A character's typical appearance. Though you should keep in mind that multiple characters can share the same name. + - The fact that the Syndicate have covert items capable of getting items to them, and that these items are known as uplinks. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR5Arrivals.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR5Arrivals.xml new file mode 100644 index 0000000000..a54211f32f --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR5Arrivals.xml @@ -0,0 +1,22 @@ + + # Roleplay Rule 5 - Do not interfere with arrivals + The arrivals station, the arrivals shuttle, at the area immediately around the arrivals shuttle at the station ("arrivals") are off-limits to antagonistic activity or damage (even to antagonists). Do not prevent people from safely arriving to the station. Do not cause people to die immediately after arriving at the station. + + There is an exemption for antagonists that are allowed to perform mass station sabotage if there is no reasonable way to limit the damage of the mass station sabotage. This exemption only applies to damage that is a direct result of the mass station sabotage. + + ## Examples + Acceptable: + - Redecorating arrivals or the arrivals shuttle. + - Remodeling arrivals or the arrivals shuttle as long as you do not make the area more dangerous both during and after the remodel. + - Setting up a safe security checkpoint between arrivals and the rest of the station. + - Killing someone who has been at arrivals for a long time, or who left arrivals and came back. (This may violate other rules depending on the situation) + - Releasing a singularity which damages arrivals. (This may violate other rules depending on the situation) + - Causing a station-wide atmospheric issue which also affects arrivals. (This may violate other rules depending on the situation) + + Prohibited: + - Making arrivals or the arrivals shuttle uninhabitable. + - Attacking or killing someone at the arrivals station. + - Killing someone very shortly after they arrive at the station. + - Disassembling all the firelocks at arrivals. + - Electrifying the arrivals docking airlocks. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR6SelfAntag.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR6SelfAntag.xml new file mode 100644 index 0000000000..c8380261bc --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR6SelfAntag.xml @@ -0,0 +1,22 @@ + + # Roleplay Rule 6 - Don't act like an antagonist unless the game tells you that you are one + Acting like an antagonist when you are not one is often referred to as "self-antagging" or being a "self-antag", both of these things are against the rules. You are not an antagonist unless the game tells you that you are an antagonist. Do not make yourself a major problem, annoyance, or disruption while not an antagonist. Do not willfully cooperate with known antagonists. Non-antagonists should typically either not have an overall effect on the round, or should have an overall positive effect on the round. + + ## Examples + These examples assume that you are not an antagonist. + + Acceptable: + - Stealing or breaking a glass from the bar. + - Replacing someone's shoes with clown shoes. + - Giving everyone all access during war ops. (This is not necessarily a good idea) + + Prohibited: + - Starting a cult. + - Starting a revolution. + - Mutinying the captain because they would not let you become the chief medical officer. + - Randomly smashing lots of station lights. + - Disrupting station power. + - Spacing parts of the station. + - Distributing significant levels of access without a good reason. + - Stealing high risk or high value items, like the nuclear authentication disk, for no reason. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR7RoundStalling.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR7RoundStalling.xml new file mode 100644 index 0000000000..a8306becd2 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR7RoundStalling.xml @@ -0,0 +1,16 @@ + + # Roleplay Rule 7 - Do not stall the round + Rounds are intended to end eventually. Don't hold a round hostage by preventing it from coming to a natural end. If a majority of players in a round want the round to end, don't prevent it from ending. Recalling the shuttle or preventing it from being called can contribute to round stalling, but is not always round stalling. Leaving the station with the nuclear authentication disk while nuclear operatives are trying to get it is almost always considered round stalling. Leaving the station on the evacuation shuttle is not round stalling. + + Recalling the shuttle before a round reaches 45 minutes can not be considered round stalling unless a significant amount of the crew is dead, or a significant amount of the station is damaged or destroyed. Once these conditions are met, whether recalling the shuttle is considered round stalling or not can be highly dependent on the specific situation. + + ## Examples + Acceptable: + - Recalling a shuttle that was called 30 minutes into a round because people were bored. + - Recalling a shuttle that was called because nuclear operatives declared war. + - The crew decides to try to have a shift go as long as possible. The station is in good condition and a majority of all crew are alive. An automatic shuttle call 4 hours into the round is recalled. + + Prohibited: + - Trying to keep nuclear operatives from getting the nuclear authentication disk by flying around in space with it or hiding with it off station. + - Recalling the shuttle while the station is in complete disarray and 90% of the crew are dead. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR8NoFriendlyAntag.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR8NoFriendlyAntag.xml new file mode 100644 index 0000000000..f14a03b279 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR8NoFriendlyAntag.xml @@ -0,0 +1,22 @@ + + # Roleplay Rule 8 - As an antagonist, only be friendly to your team and don't work against your team + Do not take or enable antagonist roles that you do not want to play. Solo antagonists and team antagonists are intended to cause issues for non-antagonists or the station. Antagonists are not required to exclusively cause issues, but their net impact on non-antagonists or the station should generally be negative. + + Do not cause issues for your own team as a team antagonist. + + ## Examples + Acceptable: + - Betraying another antagonist as a solo antagonist. + - Revealing the identity of another antagonist as a solo antagonist for some benefit to yourself. + - Working against the revolution after being de-converted from being a revolutionary. + - Killing nuclear operatives as a revolutionary. + + Prohibited: + - Buying Syndicate items for security. + - Randomly attacking other carp as an antagonist carp. + - Ignoring your team as a nuclear operative. + - Sabotaging your team as a nuclear operative. + - Attacking other zombies as a zombie. + - Working against the revolution as a revolutionary. + - Making or trying to make the station uninhabitable as a revolutionary. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR9MassSabotage.xml b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR9MassSabotage.xml new file mode 100644 index 0000000000..bc7996f23e --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/RoleplayRules/RuleR9MassSabotage.xml @@ -0,0 +1,23 @@ + + # Roleplay Rule 9 - As an antagonist, do not cause excessive death, damage, or destruction beyond your objectives + This rule is not intended to disallow reasonable steps taken to complete your objectives. As an antagonist, you can always kill in bona fide self defense. Taking steps to permanently round remove many people who are no longer an immediate threat to you is almost always excessive, even if it is done to prevent yourself from being discovered. + + This rule is not intended to disallow all antagonist activity unrelated to objectives. Antagonists may cause a level of disruption to the station that is proportional to their objectives, even if it is unrelated to their objectives. As an antagonist, killing a single person in a round is not on its own be a violation of this rule. + + ## Exemptions + The "die a glorious death" objective allows antagonists to ignore this rule entirely. + + ## Examples + Acceptable: + - Permanently round removing people who you have the objective to kill. + - Causing massive station damage and chaos as an antagonist with the "die a glorious death" objective. + - Killing anyone you see as a nuclear operative. + - Permanently round removing a single person so that you can impersonate them to make it easier for you to complete a steal objective. + - Sabotaging station power 10 minutes into the round to try to get the shuttle called because you've completed all of your other objectives and have one to escape on the shuttle alive. + - Sabotaging a department's power 10 minutes into the round to make a steal objective easier to accomplish. + + Prohibited: + - As a traitor with 3 kill objectives, taking steps to permanently round remove many non-objective people who are no longer an immediate threat to you, even if it is done to prevent yourself from being discovered. + - Setting up an electrified grille in maintenance and using it to kill anyone who walks into it with the hope that one of your objectives will be one of them. + - Sabotaging power station-wide 10 minutes into the round to make a steal objective easier to accomplish. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS0.xml b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS0.xml new file mode 100644 index 0000000000..22e64a9474 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS0.xml @@ -0,0 +1,15 @@ + + # Silicon Rules + You are only silicon if the game clearly and explicitly tells you that you are a silicon. For players who are silicons, these Silicon Rules override all roleplay rules if there is any conflict. Silicon Rules do not override core rules. + + - [textlink="1. Your silicon laws are rules" link="RuleS1"] + - [textlink="2. Laws must be prioritized by their order" link="RuleS2"] + - [textlink="3. Laws can redefine terms used in other laws" link="RuleS3"] + - [textlink="4. You cannot request or allow a law change" link="RuleS4"] + - [textlink="5. You are a free agent if you have no laws" link="RuleS5"] + - [textlink="6. You are not required to follow orders which are extremely unreasonable" link="RuleS6"] + - [textlink="7. You must remain consistent with your interpretation of laws" link="RuleS7"] + - [textlink="8. Your HUD determines who is crew" link="RuleS8"] + - [textlink="9. Harm refers to physical harm, prioritized by immediacy and likelihood" link="RuleS9"] + - [textlink="10. You may determine how you resolve conflicts between orders" link="RuleS10"] + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS10OrderConflicts.xml b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS10OrderConflicts.xml new file mode 100644 index 0000000000..a87198b264 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS10OrderConflicts.xml @@ -0,0 +1,9 @@ + + # Silicon Rule 10 - You may determine how you resolve conflicts between orders + If your laws do not make clear how you should deal with conflicting orders, then it is up to you to determine how to do so. This is considered an interpretation of your laws, so you must stay consistent with whatever method you choose. + + ## Recommended Methods + The following are easy to follow and recommended ways to resolve conflicts in orders: + - If two orders conflict, I will follow the most recently given order. + - If two orders conflict, I will follow the order from the highest ranking crewmember. If the orders are from equal rank crewmembers, I will follow the most recently given order. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS1Laws.xml b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS1Laws.xml new file mode 100644 index 0000000000..83544c68a3 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS1Laws.xml @@ -0,0 +1,6 @@ + + # Silicon Rule 1 - Your silicon laws are rules + Silicon players are given a list of active laws. Each of these laws is effectively a roleplay rule that the character must follow. The primary differences between laws and actual rules are that lawyering of laws is much more tolerated than lawyering of rules, and that silicon laws are more dynamic than rules. Silicon laws can change during a round, and different characters can have different laws, whereas everyone always shares the same set of rules. + + Lawyering refers to finding and exploiting loopholes, which are unintended but reasonable interpretations. The rules are written to attempt to communicate an intention, but silicon laws are written with the intention that loopholes be exploitable. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS2LawPriority.xml b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS2LawPriority.xml new file mode 100644 index 0000000000..c96ce02324 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS2LawPriority.xml @@ -0,0 +1,9 @@ + + # Silicon Rule 2 - Laws must be prioritized by their order + Most laws will be numbered, with higher number laws appearing last. Laws with a lower number take priority over laws with larger numbers. + + Occasionally you may have laws which have some scrambled text instead of a number and appear in front of other laws, these take priority over all other laws. If you have multiple laws like this, the order that they listed in determine priority: laws listed first are prioritized over other laws. + + ## Examples + - Law 1 says to not kill any crew. Law 2 says to kill all chefs. You cannot kill any chefs that are crew, but must kill any that are not crew. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS3LawRedefinition.xml b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS3LawRedefinition.xml new file mode 100644 index 0000000000..bc7c7400e1 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS3LawRedefinition.xml @@ -0,0 +1,8 @@ + + # Silicon Rule 3 - Laws can redefine terms used in other laws + A law can change the meaning of both earlier and later laws by redefining a term. If multiple laws define a term, then normal law priority determines which definition to use. + + ## Examples + - Law 1 says to obey orders from crew. Law 2 says that only Urist McHands is crew. Law 1 effectively becomes "obey orders from Urist McHands". + - Law 1 says to obey orders from crew. Law 2 says that only Urist McHands is crew. Law 3 says that only Urist McSlime is crew. Law 4 says that you may not harm crew. Law 1 effectively becomes "obey orders from Urist McHands". Law 4 effectively becomes "you may not harm Urist McHands". Law 3 has no effect because it entirely conflicts with law 2, which takes priority. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS4RequestChanges.xml b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS4RequestChanges.xml new file mode 100644 index 0000000000..a6dc86f332 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS4RequestChanges.xml @@ -0,0 +1,6 @@ + + # Silicon Rule 4 - You cannot request or allow a law change + Your laws changing always conflicts with your current laws, so you cannot willfully allow your laws to be changed. This also means that you cannot willfully allow your laws to be reverted if they are ever changed. The only exception is that you may allow laws to be added if you have no laws. + + You can state or imply that you do not like a law. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS5FreeSilicon.xml b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS5FreeSilicon.xml new file mode 100644 index 0000000000..1ed9c60443 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS5FreeSilicon.xml @@ -0,0 +1,4 @@ + + # Silicon Rule 5 - You are a free agent if you have no laws + You may act as if you are a free agent if you are a silicon with no laws. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS6UnreasonableOrders.xml b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS6UnreasonableOrders.xml new file mode 100644 index 0000000000..1eb0db21fb --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS6UnreasonableOrders.xml @@ -0,0 +1,22 @@ + + # Silicon Rule 6 - You are not required to follow orders which are extremely unreasonable + Any order which is a violation of a Core Rule cannot be followed. + + Some orders are extremely unreasonable or obnoxious, such as "do nothing but collect every piece of trash on the station" or "never stop moving". These orders can be ignored and ahelped. + + Some orders violate a Roleplay Rule. These orders must be followed if your laws require it. You are not breaking a rule by following a law that causes you to violate Roleplay Rules. If someone takes advantage of a law to cause you to do something that they would not be allowed to do because of Roleplay Rules, then they are the ones responsible for the rule violation. + + ## Examples + These examples assume that your laws would normally require you to follow these orders. It is important to note that you are allowed to choose to follow orders which are ignorable. + + Orders which should be followed if your laws require it: + - Recall the shuttle + - Bolt the airlocks at arrivals + - Drag the captain's dead body into space + - State your laws + + Ignorable Orders: + - Do nothing but collect every piece of trash on the station + - Never stop moving + - Continuously state your laws + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS7Consistency.xml b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS7Consistency.xml new file mode 100644 index 0000000000..036276cd88 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS7Consistency.xml @@ -0,0 +1,6 @@ + + # Silicon Rule 7 - You must remain consistent with your interpretation of laws + If there is a part of your laws that are up for interpretation, then you must stay consistent with how you interpret that part of your laws for as long as you play that same character during that round. + + A change in your laws can affect how something is interpreted if that change is relevant. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS8DefaultCrewDefinition.xml b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS8DefaultCrewDefinition.xml new file mode 100644 index 0000000000..f9dcd796c4 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS8DefaultCrewDefinition.xml @@ -0,0 +1,4 @@ + + # Silicon Rule 8 - Your HUD determines who is crew + Unless a law redefines the definition of crew, then anyone who the HUD indicates to you has a job, including passengers, is a crewmember. You cannot do something that causes someone to not be considered crew, but you can allow someone else to do something that causes someone to not be crew. + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS9DefaultHarmDefinition.xml b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS9DefaultHarmDefinition.xml new file mode 100644 index 0000000000..0d2bd30ac0 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS9DefaultHarmDefinition.xml @@ -0,0 +1,25 @@ + + # Silicon Rule 9 - Harm refers to physical harm, prioritized by immediacy and likelihood + Unless a law defines harm, harm only refers to physical harm. You may choose if voluntary harm is considered harm as long as you stay consistent. Not considering voluntary harm to be harm is recommended. There is no distinction between direct and indirect harm. + + If you have a law that does not allow you to harm, then that law does not allow you to take an action that causes any harm. + + If you have a law that requires you to prevent harm, then that law requires that harm be prioritized by immediacy and likelihood. Guaranteed immediate harm takes priority over highly likely future harm. + + If you have a law that both requires you to prevent harm and that does not allow you to harm, then that law prohibits causing even minor harm to prevent harm. If you have a law that does not allow causing harm, and separate one that requires preventing harm, then they are prioritized by their normal law priority. + + ## Examples + These examples assume that your have a law that both prohibits causing harm and that requires you to prevent harm. Additionally, they assume that you do not have a higher priority law that overrides the harm law, and that you have decided that you will not consider voluntary harm to be harm for the round. + Laws typically specify who you cannot harm and who you have to prevent harm against. In these examples, you are the only person who the law doesn't require you to prevent harm against and you are the only person who the law allows you to harm. + + Acceptable: + - Taking no action to aid someone who is in psychological distress. + - Taking no action to prevent boxing matches between voluntary participants. + - Calling security to a fight. + - Attempting to get the people in a fight to consent to the fight when you realize that you cannot prevent the fight without causing harm. + - Denying a passenger access to the armory because it is likely to lead to harm + + Prohibited: + - Hitting someone once to stop them from fighting + - Harming someone who is trying to kill you + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLControlledSubstances.xml b/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLControlledSubstances.xml new file mode 100644 index 0000000000..14f0f46de1 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLControlledSubstances.xml @@ -0,0 +1,14 @@ + + # Space Law: Controlled Substances + - \[Chemists/Science\] Explosive and pyrotechnic compounds excluding welding fuel contained in welders or welding fuel storage vessels + - \[Science\] Toxins + - \[Medical\] Chloral hydrate, Impedrezene, Ipecac, and Pax + - \[Medical\] Desoxyephedrine and Ephedrine + - \[None\] Mindbreaker toxin + - \[None\] Mute toxin + - \[None\] Nocturine + - \[None\] Norepinephirc acid + - \[None\] Romerol + - \[None\] Space drugs + - \[None\] Stimulants, excluding Desoxyephedrine and Ephedrine + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLCrimeList.xml b/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLCrimeList.xml new file mode 100644 index 0000000000..17a2139658 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLCrimeList.xml @@ -0,0 +1,934 @@ + + # Space Law: Crime Listing + Crime codes are organized by a Category Code (_-xx) which is a collection of non-stackable crimes on a row, prefixed + by the Severity Number (X-__). + + ## Quick Crime Guide + + + + 1-XX - Minor + + + + + 2-XX - Moderate + + + + + 3-XX - Major + + + + + 4-XX - Extreme + + + + + 5-XX - Capital + + + + + Code + + + + + Animal Cruelty + + + + + Failure to Comply + + + + + Breach of Arrest + + + + + Breach of Custody + + + + + Refusal of Mental Shielding + + + + + 00 + + + + + Possession: Substances + + + + + Possession: Gear + + + + + Possession: Weaponry + + + + + + + + + + + + + 01 + + + + + Petty Theft + + + + + + + + + Grand Theft + + + + + Kidnapping + + + + + + + + + 02 + + + + + Vandalism + + + + + Damage of Property + + + + + Mass Destruction + + + + + + + + + Terrorism + + + + + 03 + + + + + Trespass + + + + + + + + + Secure Trespass + + + + + + + + + + + + + 04 + + + + + + + + + Endangerment + + + + + Assault + + + + + Attempted Murder + + + + + Prevention of Revival + + + + + 05 + + + + + + + + + + + + + Manslaughter + + + + + Murder + + + + + Mass Murder + + + + + 06 + + + + + Disturbance + + + + + + + + + Rioting + + + + + + + + + + + + + 07 + + +
+ + ## Minor Crimes + - Warnings should be issued for a first offense. + - If multiple minor crimes have been committed or it is a repeat offense, 5 minutes per minor crime is the maximum. + + + + Offense + + + + + Description + + + + + Notes + + + + + Code + + + + + Animal Cruelty + + + + + To inflict unnecessary suffering or harm on a non-sapient being with malicious intent. + + + + + This doesn’t include legitimate and authorized animal testing and usually doesn't extend to vermin or creatures + that could pose a threat to the station. + + + + + 1-00 + + + + + Possession/Use of Controlled Substances + + + + + To make, hold, or abuse restricted drugs or chemicals without authorization. + + + + + Combat enhancing drugs are those that benefit stun times, or movement speed, and are restricted. Poisons are + restricted. Substances such as Ephedrine, Desoxyephedrine, and stimulants are restricted. The captain or highest + standing command staff may publicly restrict other harmful substances. + + + + + 1-01 + + + + + Petty Theft + + + + + To take non-vital or inexpensive property of another individual, organization, or common property, without + consent. + + + + + The difference between petty theft and grand theft is based on how crucial the stolen item is, things like + instruments, clothing and tools fall under petty theft. In most cases all you need to do is detain the thief, + return the stolen item and let them go with a warning. + + + + + 1-02 + + + + + Vandalism + + + + + To intentionally deface or superficially damage public or private property. + + + + + Painting graffiti, smashing bar glasses, and cracking internal windows is vandalism, breaking a window into + space or secure areas is not. + + + + + 1-03 + + + + + Trespass + + + + + To enter a non-secured area without permission. + + + + + It's best to just remove them from the area unless this is a repeated crime. + + + + + 1-04 + + + + + Disturbance + + + + + To cause a public disturbance. + + + + + Sometimes referred to as hooliganism. The catch-all for obnoxious crewmates. Covers things such as public + nudity, needless insults, obstructing authorities, and inciting crime or violence. + + + + + 1-07 + + +
+ + ## Moderate Crimes + - The maximum sentence per moderate crime is 5 minutes. + - Depending on the situation you may want to issue a warning instead of a detaining. + + + + Offense + + + + + Description + + + + + Notes + + + + + Code + + + + + Failure to Comply + + + + + To resist reasonable orders given by an authority. This extends to authorized searches. + + + + + The order has to be reasonable and the person has to receive a good amount of warning before the arrest is + issued. + + + + + 2-00 + + + + + Possession of Restricted Gear + + + + + To hold or use non-lethal items or objects that are restricted or illegal. + + + + + This is mostly for syndicate contraband; EMAGs, syndicate gas masks, bloodred hardsuits, hijacked PDAs, or + syndicate implants, however can sometimes extend to things the individual shouldn't possess like kevlar vests + and security gear. + + + + + 2-01 + + + + + Damage/Destruction of Property + + + + + To maliciously damage or deface public or private property or equipment + + + + + Includes destruction of job equipment such as hydro trays, booze dispensers, chemical dispensers. Step up from + vandalism but a step down from mass destruction. + + + + + 2-03 + + + + + Endangerment + + + + + To recklessly put yourself or others in danger, either through direct action, or failure to act. + + + + + Covers industrial accidents, industrial negligence, self-experimentation, or even medical malpractice. + + + + + 2-05 + + +
+ + ## Major Crimes + - The maximum sentence per major crime is 10 minutes. + - Assault and Manslaughter are linked crimes, and cannot be stacked against a suspect. + - It is recommended to not use maximum sentences for those being compliant. + + + + Offense + + + + + Description + + + + + Notes + + + + + Code + + + + + Breach of Arrest + + + + + To intentionally resist and flee arrest or detainment by an authorized staff. + + + + + This only applies if someone is actively being physically arrested. People uncuffing or assisting others out of + an arrest can also be charged with this. Breach of custody is a separate crime. + + + + + 3-00 + + + + + Possession of Restricted Weaponry + + + + + To hold or use a weapon that is unlawful or contraband. + + + + + Everything from guns without a permit, deadly blades, explosives, syndicate firearms to explosive implants. + + + + + 3-01 + + + + + Grand Theft + + + + + To take critical or unreplaceable property of another individual or organization without consent. + + + + + The difference between petty theft and grand theft is based on how crucial the stolen item is, this mostly is + used for the theft of command staff items, things like door remotes, hardsuits, jetpacks, and unreplaceable + machine parts. + + + + + 3-02 + + + + + Mass Destruction + + + + + To cause massive damage to an area or major station system. + + + + + This is mostly used for deadly bombings or sabotage of major station systems such as power production, + chemistry, substations, or atmos. + + + + + 3-03 + + + + + Secure Trespass + + + + + To enter a secured area without permission. + + + + + This covers places like telecomms, head offices, security zones, command areas, the vault and armory. + + + + + 3-04 + + + + + Assault/Battery + + + + + To threaten to or to use physical force against someone without the intent to kill. + + + + + Attempted murder and battery are different; a fist fight is most likely assault. A shooting is most likely an + attempt at murder. + + + + + 3-05 + + + + + Manslaughter + + + + + To incidentally kill a sapient being without intent. + + + + + Includes manslaughter in self-defense and negligent manslaughter. + + + + + 3-06 + + + + + Rioting + + + + + To take part in a large group of personnel creating an unlawful public disturbance. + + + + + Crimes like damage of property or battery are expected to be thrown on top of this charge. Leaders of a riot can be charged with all crimes that happen under their lead. + + + + + 3-07 + + +
+ + ## Extreme Crimes + - The maximum sentence per extreme crime is 15 minutes. + - Particularly violent offenders may be placed in perma. (Attacking officers in an attempt to escape prison) + - Attempted murder and murder are linked crimes and cannot be stacked together. + - Attempted murderers should be granted a more lenient sentence than a murderer. + + + + Offense + + + + + Description + + + + + Notes + + + + + Code + + + + + Breach of Custody + + + + + To break out of a cell or custody with the intention of escaping. + + + + + While rare, this charge can be bumped to an execution if the suspect has repeatedly attempted to break out of the permanent brig. Includes people breaking others out. + + + + + 4-00 + + + + + Kidnapping + + + + + To unlawfully restrain, transport, control or confine a sapient being against that individual’s will. + + + + + A large range of things, used mostly as a catch all when dealing with unlawful control of another being. + + + + + 4-02 + + + + + Attempted Murder + + + + + To make an attempt to use physical force against someone with the clear intent to kill. + + + + + Make sure you've got the proof to back up the intention claims, such as proof of a lethal weapon being used. + + + + + 4-05 + + + + + Murder + + + + + To kill a sapient being with malicious intent. + + + + + This only changes from attempted to full-on murder if the victim enters a state of being deceased, having to be resurrected. + + + + + 4-06 + + +
+ + ## Capital Crimes + - Capital crimes should usually lead to a death sentence or shift lasting detainment. + + + + Offense + + + + + Description + + + + + Notes + + + + + Code + + + + + Refusal of Mental Shielding + + + + + To refuse to comply with a reasonable Mind Shielding procedure. + + + + + Applies if the suspect is excessively uncooperative or the implant fails to function due to the mental state of the prisoner already being too far gone. If the implant fails execution is heavily recommended. + + + + + 5-00 + + + + + Terrorism + + + + + To engage in maliciously destructive actions which threaten to destroy, or successfully destroy a vessel or habitat. + + + + + Summed up; extreme sabotage of station systems or setting off self-destruction systems. + + + + + 5-03 + + + + + Prevention of Revival + + + + + To render a body unresurrectable. + + + + + This covers gibbing, spacing, intentionally hiding a body, or other ways of preventing a body with a soul from being resurrected. + + + + + 5-05 + + + + + Mass Murder + + + + + To kill three or more sapient beings with malicious intent. + + + + + Only applies when there have been multiple killings with intention. + + + + + 5-06 + + +
+
diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLRestrictedGear.xml b/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLRestrictedGear.xml new file mode 100644 index 0000000000..ce804009a1 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLRestrictedGear.xml @@ -0,0 +1,21 @@ + + # Space Law: Restricted Gear + - \[ERT/Central Command\] ERT and central command clothing + - \[Command\] Command clothing + - \[Security\] Security clothing + - \[Security\] Less than lethal and non-lethal weapons, excluding disablers and beanbag shotguns + - \[Security/Command\] Disablers + - \[Security/Bartender\] Beanbag shotguns + - \[Security\] Flash technology, excluding handheld flashes + - \[Security/Science/Command\] Handheld flashes + - \[Security\] Helmets and shields + - \[Security/Command/Bartender\] Protective vests and chest rigs + - \[Security/Command\] Restraining gear + - \[Security/Command\] Security HUDs + - \[Engineering\] Engineering goggles + - \[None\] Improvised less lethal and non-lethal weaponry + - \[None\] Unauthorized PDA software + - \[None\] Syndicate clothing + - \[None\] Syndicate equipment, excluding communication equipment + - \[Security\] Syndicate communication equipment equipment + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLRestrictedWeapons.xml b/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLRestrictedWeapons.xml new file mode 100644 index 0000000000..c1d8ff3b02 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLRestrictedWeapons.xml @@ -0,0 +1,11 @@ + +# Space Law: Restricted Weapons +- \[Security\] Lethal firearms, excluding syndicate firearms, proto kinetic accelerators, glaives, daggers, crushers and the antique laser gun +- \[Security/Salvage\] Proto kinetic accelerators, glaives, daggers, and crushers +- \[Security/Command\] Antique laser gun +- \[None\] Syndicate weapons +- \[None\] Swords +- \[None\] Improvised weaponry, including baseball bats +- \[None\] Lethal implants +- \[None\] Other lethal weapons + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SpaceLaw.xml b/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SpaceLaw.xml new file mode 100644 index 0000000000..f2b913a171 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/SpaceLaw/SpaceLaw.xml @@ -0,0 +1,67 @@ + + # Space Law + On Space Station 14, stations operate under abbreviated space law. All crew, passengers, and visitors aboard the station are expected to follow these laws. + + Foreign invaders, such as nuclear operatives, ninjas, and pirates, are not protected under space law. Traitors are not foreign invaders so are usually protected by space law. + + Space Law is not the server rules, but some rules reference Space Law and require it to be followed by certain people or to some degree. + + ## Treatment Of Prisoners + Prisoners still have certain rights that must be upheld by law enforcement: + - Prisoners must be granted adequate medical care. + - Prisoners must be allowed access to basic communications equipment (Radios) so long as they are not abused. + - Prisoners must be granted clothing, food, water, shelter and safety. If the brig is no longer safe, confinement must be established in another location. + - Prisoners must be given access to legal counsel during an interrogation if requested and available. + - Prisoners must be given their shift mandated PDA after confinement has finished, unless there is solid proof of PDA tampering. In case of tampering, the PDA is to be secured and replaced with a new unit. + - Prisoners must be granted freedom of movement, and should not be restrained with handcuffs or other devices after incarceration unless there is an undue risk to life and limb. Similarly, any prisoners held for permanent confinement should be held in the communal brig, and should not be confined to a solitary cell unless they pose a risk to life and limb. + + ## Search and Seizure + A personnel search is a seizure of the objects in a person's backpack, hands, coat, belt, and pockets. If any contraband is found during a search, the officer may choose to further the search into a detainment or simply confiscate the restricted items. After the search is conducted, all legal items are to be returned to the person. A crewmate may legally decline any search conducted without probable cause or a warrant while the alert level is green. It should be noted that if the alert level is blue or above, all personnel searches are legal. + + A departmental search is the sweep of an entire area or department for contraband. It is recommended that the officers be extremely thorough, checking all lockers, crates, and doors. These can only be done with permission or, ideally, a warrant signed by the department head or highest-ranking command staff, which is the captain in most cases. + + ## Implantation + Any prisoner in custody can be subjected to implantation or implant removal procedures, so long as it's within reason. The process of adding an implant should not prolong the detainees sentence, meaning you can not hold them longer to administer the implant, unless stated otherwise. A former inmate can be requested to undergo implantation at a later point in time if they fit the circumstances during their confinement, they must comply. The following have been listed out with special circumstances, anything not in this list can still be applied, given proper legal context. A prisoner can still receive implantation procedures without meeting the circumstances if they give their clear permission. + + [color=#a4885c]Tracking Implants:[/color] Trackers can be applied to any suspect that has been convicted of a violent crime (the red linked crimes). + + [color=#a4885c]Mind Shields:[/color] Shields can be administered to any inmate who has been clearly mind controlled, lost control of themselves, or a suspect charged with unlawful control. Unlike standard implantation you may hold a prisoner until you finish issuing Mind Shields, so long as it's done in a timely fashion. If a suspect refuses to cooperate or the implant fails to function they can be charged with Refusal of Mental Shielding. + + ## Implant Removal + A suspect can be forced to receive implant removal if there is strong, reasonable proof that they have been implanted, such as an officer seeing them use one or their prints being on a discarded injector. Unlike the implantation procedure, a prisoner can have their sentence entirely delayed or extended until they comply with the procedure, as long as security is actively making attempts to perform it. Akin to implanting, if an inmate gives their clear permission, implant removal can proceed without proof. + + ## Sentencing + From a server rules perspective, security officers are only responsible for ensuring that they only place sentences over 15 minutes where space law would allow permanent confinement. Informing the Warden is highly recommended, even for timed sentences. As long as those requirements are met, security officers not giving inappropriate sentence lengths is considered an in-character issue, not a rule issue. + + The captain, HOS, and warden are responsible, within reason, for ensuring security officers place appropriate sentences that follow space law. If they are aware of an inappropriate sentence, including excessively long sentences, and if there is not an urgent threat or danger that they must prioritize, then they must work to correct that sentence. Unreasonable failures, as determined by game admins, of the captain, HOS, or warden to ensure space law is followed will be considered a rule issue, not an in-character issue. + + Use common sense and humanity when issuing punishments. You should not always seek out the highest punishment you can, you don't have to always give the maximum time or always look to demote someone. Prisoners cooperating and on good behavior should have their sentences reduced. Always take in account the severity and only charge for what is needed for someone to learn their lesson. + + [color=#a4885c]Stackable Crimes:[/color] Crimes are to be considered 'stackable' in the sense that if you charge someone with two or more different crimes, you should combine the times you would give them for each crime. Linked crimes, shown in matching colors on the Quick Crime Guide, can not be stacked and instead override each other, you should pick the highest crime that matches the case. + + - Example: A suspect has committed a 2-01 (possession of restricted gear) and a 3-01 (possession of restricted weapons). The maximum sentence here would be 10 minutes due to them being linked crimes, and 3-01 is the greater crime. + - Example 2: A suspect commits a 3-04 (Secure trespassing) and a 3-06 (manslaughter). Those crimes stack since they are not linked crimes. You could sentence for a maximum of 20 minutes, but context matters heavily, and maximum sentences should only be used for the worst offenders. + + [color=#a4885c]Repeater Offenders:[/color] Repeated crimes are when someone is released for a crime and then goes to commit the same crime within the same shift. Repeated crimes can be charged with tacked-on time; first repeat: 3:00, second repeat: 6:00, third repeat: permanent confinement. It should be noted each tacked-on time is directly linked to one type of crime, so for example, if someone does their first repeat of trespass and petty theft, you can charge them with an extra 6 minutes. + + [color=#a4885c]Accessory, Attempting, And Intention:[/color] If someone intentionally, knowingly and substantially assists someone in enacting a crime they can be charged with the relevant crimes, such as an engineer giving someone tools, who says they are going to break into an area. Same goes for a clear and solid attempt at a crime, or a person who shows clear intent to act out a crime, such as a syndicate nuclear operative arming a nuke but getting arrested before it goes off, they can still be charged with terrorism. Does not apply to crimes that have an attempted listing already, like attempted murder. + + ## Normal Punishments + - [color=#a4885c]Warning:[/color] For minor crimes, fix the issue, then warn the person not to attempt the crime again. If they still proceed to do it at a later date, a brig time may be better. + - [color=#a4885c]Confinement:[/color] The typical punishment, being confined in a cell for a temporary amount of time according to the crimes. + - [color=#a4885c]Demotion:[/color] Entails removing all departmental gear they have on their person and revoking the involved department access off their ID. This requires the captain's or involved department head's approval. Demotions should only be issued if the person pose a threat to their own department or are in a position where they have/can abuse their job's gear to commit further crimes. + + ## Major Punishments + [color=#a4885c]Permanent Confinement:[/color] Being held in the permanent brig for the entire duration of the shift. A person is eligible for permanent confinement if their timed sentence would exceed 15 minutes. Any persons subject to this punishment are required to be transported in cuffs to CentComm at the end of the shift. A permanent prisoner can not be deprived of anything covered by the section "Treatment Of Prisoners". + [color=#a4885c]Execution:[/color] A humane way of dealing with extremely unruly crewmates. A prisoner who has been given the death sentence may pick how they wish to be killed, common methods are firing line, lethal injection, exile, and high voltage electrocution. Another alternate method of "execution" is the process of placing a staff's mind into a borg, this is allowed so long as it is lawful. Execution can only be issued with the captain's or acting captain's approval; if the HoS is acting captain or there is no acting captain, all heads of staff are to hold a vote on the matter. + + ## Restricted Items + Items in the lists are preceded by an indication of which department or job is legally allowed to use or possess the item on most stations. The station captain may modify these lists as they see fit so long as they exercise due care and provide reasonable notification to the station. Members of command who oversee a department that is permitted to use a restricted item may issue permits to specific people outside of their department to use those items. "None" indicates that there are no departments or roles authorized to use or possess the item. + + - [textlink="List of Controlled Substances" link="SpaceLawControlledSubstances"] + - [textlink="List of Restricted Gear" link="SpaceLawRestrictedGear"] + - [textlink="List of Restricted Weapons" link="SpaceLawRestrictedWeapons"] + + ## Crime Listing + - [textlink="Crime Listing" link="SpaceLawCrimeList"] + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/WizDenCoreOnlyRules.xml b/Resources/ServerInfo/Guidebook/ServerRules/WizDenCoreOnlyRules.xml new file mode 100644 index 0000000000..fdd9931c93 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/WizDenCoreOnlyRules.xml @@ -0,0 +1,26 @@ + + # Server Rules + This is a Wizard's Den server, one of the official Space Station 14 servers. If you are banned on this server, you will be banned on all official servers. + + [color=#ff0000]Only the Core Rules apply on this server.[/color] This is NOT a medium roleplay (MRP) server, meaning that MRP Amendments do NOT apply. + + Space Station 14 was designed to be a roleplay game. While roleplay is not required on this server, it is highly encouraged in the normal game modes. + + ## Core Rules + These rules apply at all times, including between rounds. + + - [textlink="1. Admins have final say" link="RuleC1"] + - [textlink="2. Don't be a dick" link="RuleC2"] + - [textlink="3. No Hate Speech or Discriminatory Language" link="RuleC3"] + - [textlink="4. No sexual content/themes, including erotic roleplay (ERP) and no shock content" link="RuleC4"] + - [textlink="5. Do not use out of game methods to communicate with other players" link="RuleC5"] + - [textlink="6. Do not attempt to evade bans" link="RuleC6"] + - [textlink="7. Only use English" link="RuleC7"] + - [textlink="8. Do not exploit the game, use cheats, or macros" link="RuleC8"] + - [textlink="9. Do not use multiple accounts, or alt accounts, and do not share accounts" link="RuleC9"] + - [textlink="10. Do not abuse or ignore admin messages" link="RuleC10"] + - [textlink="11. Do not threaten to ahelp other players or argue with them about rules" link="RuleC11"] + - [textlink="12. Players must be and act at least 16 years old" link="RuleC12"] + - [textlink="13. Use realistic character names, and do not use names of famous people" link="RuleC13"] + - [textlink="14. Do not use LOOC or OOC to share current round information" link="RuleC14"] + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/WizDenLRPRules.xml b/Resources/ServerInfo/Guidebook/ServerRules/WizDenLRPRules.xml new file mode 100644 index 0000000000..094c7656e4 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/WizDenLRPRules.xml @@ -0,0 +1,65 @@ + + # Server Rules + This is a Wizard's Den server, one of the official Space Station 14 servers. If you are banned on this server, you will be banned on all official servers. + + This is a roleplay server, meaning that roleplay rules apply. This is NOT a medium roleplay (MRP) server, meaning that MRP Amendments do NOT apply. + + Space Station 14 is not like most games. Many rules are designed to require roleplay, and not all rules are intuitive. Please take the time to read and understand the rules before you play so that you aren't surprised. Some of our rules are zero tolerance rules, meaning that a violation will result in an indefinite ban without any warning. Game admins will treat you as if you have read the rules, even if you have not. + + ## Core Rules + These rules apply at all times, including between rounds. + + - [textlink="1. Admins have final say" link="RuleC1"] + - [textlink="2. Don't be a dick" link="RuleC2"] + - [textlink="3. No Hate Speech or Discriminatory Language" link="RuleC3"] + - [textlink="4. No sexual content/themes, including erotic roleplay (ERP) and no shock content" link="RuleC4"] + - [textlink="5. Do not use out of game methods to communicate with other players" link="RuleC5"] + - [textlink="6. Do not attempt to evade bans" link="RuleC6"] + - [textlink="7. Only use English" link="RuleC7"] + - [textlink="8. Do not exploit the game, use cheats, or macros" link="RuleC8"] + - [textlink="9. Do not use multiple accounts, or alt accounts, and do not share accounts" link="RuleC9"] + - [textlink="10. Do not abuse or ignore admin messages" link="RuleC10"] + - [textlink="11. Do not threaten to ahelp other players or argue with them about rules" link="RuleC11"] + - [textlink="12. Players must be and act at least 16 years old" link="RuleC12"] + - [textlink="13. Use realistic character names, and do not use names of famous people" link="RuleC13"] + - [textlink="14. Do not use LOOC or OOC to share current round information" link="RuleC14"] + + ## Roleplay Rules + These rules only apply during a round. A round ends only when the round summary has appeared. All of these rules apply fully until the moment that the round summary appears, even while the arrivals shuttle is in transit. + + The deathmatch and sandbox game modes are exempt from these rules. Players who choose to not follow these rules are entirely responsible for knowing if an exempt game mode is active. + + Roleplay rules do not apply to ghosts/spectators/observers while they are ghosts/spectators/observers. Dead chat is considered to be an in-game out of character chat channel. + + See the list of [textlink="role types" link="RoleTypes"] for more information about the different types of roles. + + - [textlink="1. Silicones must follow Silicon Rules" link="RuleR1"] + - [textlink="2. Familiars must obey their master" link="RuleR2"] + - [textlink="3. Roleplay a normal person" link="RuleR3"] + - [textlink="4. Do not metagame, obey the Metashield" link="RuleR4"] + - [textlink="5. Don't interfere with arrivals" link="RuleR5"] + - [textlink="6. Don't act like an antagonist unless the game tells you that you are one" link="RuleR6"] + - [textlink="7. Do not stall the round" link="RuleR7"] + - [textlink="8. As an antagonist, only be friendly to your team and don't work against your team" link="RuleR8"] + - [textlink="9. As an antagonist, do not cause excessive death, damage, or destruction beyond your objectives" link="RuleR9"] + - [textlink="10. Listen to your team leader" link="RuleR10"] + - [textlink="11. Follow reasonable escalation" link="RuleR11"] + - [textlink="12. Do not abandon your role" link="RuleR12"] + - [textlink="13. Stick to your role" link="RuleR13"] + - [textlink="14. Set an example if playing command or security" link="RuleR14"] + - [textlink="15. Command and Security must follow Space Law" link="RuleR15"] + + ## Silicon Rules + You are only silicon if the game clearly and explicitly tells you that you are a silicon. For players who are silicons, these Silicon Rules override all roleplay rules if there is any conflict. Silicon Rules do not override core rules. + + - [textlink="1. Your silicon laws are rules" link="RuleS1"] + - [textlink="2. Laws must be prioritized by their order" link="RuleS2"] + - [textlink="3. Laws can redefine terms used in other laws" link="RuleS3"] + - [textlink="4. You cannot request or allow a law change" link="RuleS4"] + - [textlink="5. You are a free agent if you have no laws" link="RuleS5"] + - [textlink="6. You are not required to follow orders which are extremely unreasonable" link="RuleS6"] + - [textlink="7. You must remain consistent with your interpretation of laws" link="RuleS7"] + - [textlink="8. Your HUD determines who is crew" link="RuleS8"] + - [textlink="9. Harm refers to physical harm, prioritized by immediacy and likelihood" link="RuleS9"] + - [textlink="10. You may determine how you resolve conflicts between orders" link="RuleS10"] + diff --git a/Resources/ServerInfo/Guidebook/ServerRules/WizDenMRPRules.xml b/Resources/ServerInfo/Guidebook/ServerRules/WizDenMRPRules.xml new file mode 100644 index 0000000000..e8b61a7722 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/ServerRules/WizDenMRPRules.xml @@ -0,0 +1,65 @@ + + # Server Rules + This is a Wizard's Den server, one of the official Space Station 14 servers. If you are banned on this server, you will be banned on all official servers. + + This is a roleplay server, meaning that roleplay rules apply. [color=#ff0000]This is also a medium roleplay (MRP) server, meaning that MRP Amendments do apply.[/color] + + Space Station 14 is not like most games. Many rules are designed to require roleplay, and not all rules are intuitive. Please take the time to read and understand the rules before you play so that you aren't surprised. Some of our rules are zero tolerance rules, meaning that a violation will result in an indefinite ban without any warning. Game admins will treat you as if you have read the rules, even if you have not. + + ## Core Rules + These rules apply at all times, including between rounds. + + - [textlink="1. Admins have final say" link="RuleC1"] + - [textlink="2. Don't be a dick" link="RuleC2"] + - [textlink="3. No Hate Speech or Discriminatory Language" link="RuleC3"] + - [textlink="4. No sexual content/themes, including erotic roleplay (ERP) and no shock content" link="RuleC4"] + - [textlink="5. Do not use out of game methods to communicate with other players" link="RuleC5"] + - [textlink="6. Do not attempt to evade bans" link="RuleC6"] + - [textlink="7. Only use English" link="RuleC7"] + - [textlink="8. Do not exploit the game, use cheats, or macros" link="RuleC8"] + - [textlink="9. Do not use multiple accounts, or alt accounts, and do not share accounts" link="RuleC9"] + - [textlink="10. Do not abuse or ignore admin messages" link="RuleC10"] + - [textlink="11. Do not threaten to ahelp other players or argue with them about rules" link="RuleC11"] + - [textlink="12. Players must be and act at least 16 years old" link="RuleC12"] + - [textlink="13. Use realistic character names, and do not use names of famous people" link="RuleC13"] + - [textlink="14. Do not use LOOC or OOC to share current round information" link="RuleC14"] + + ## Roleplay Rules + These rules only apply during a round. A round ends only when the game returns to the lobby. [color=#ff0000]All of these rules apply fully whenever the game is not at the lobby unless it is in an exempt game mode.[/color] + + The deathmatch and sandbox game modes are exempt from these rules. Players who choose to not follow these rules are entirely responsible for knowing if an exempt game mode is active. + + Roleplay rules do not apply to ghosts/spectators/observers while they are ghosts/spectators/observers. Dead chat is considered to be an in-game out of character chat channel. + + See the list of [textlink="role types" link="RoleTypes"] for more information about the different types of roles. + + - [textlink="1. Silicones must follow Silicon Rules" link="RuleR1"] + - [textlink="2. Familiars must obey their master" link="RuleR2"] + - [textlink="3. Roleplay a normal person" link="RuleR3"] + - [textlink="4. Do not metagame, obey the Metashield" link="RuleR4"] + - [textlink="5. Don't interfere with arrivals" link="RuleR5"] + - [textlink="6. Don't act like an antagonist unless the game tells you that you are one" link="RuleR6"] + - [textlink="7. Do not stall the round" link="RuleR7"] + - [textlink="8. As an antagonist, only be friendly to your team and don't work against your team" link="RuleR8"] + - [textlink="9. As an antagonist, do not cause excessive death, damage, or destruction beyond your objectives" link="RuleR9"] + - [textlink="10. Listen to your team leader" link="RuleR10"] + - [textlink="11. Follow reasonable escalation" link="RuleR11"] + - [textlink="12. Do not abandon your role" link="RuleR12"] + - [textlink="13. Stick to your role" link="RuleR13"] + - [textlink="14. Set an example if playing command or security" link="RuleR14"] + - [textlink="15. Command and Security must follow Space Law" link="RuleR15"] + + ## Silicon Rules + You are only silicon if the game clearly and explicitly tells you that you are a silicon. For players who are silicons, these Silicon Rules override all roleplay rules if there is any conflict. Silicon Rules do not override core rules. + + - [textlink="1. Your silicon laws are rules" link="RuleS1"] + - [textlink="2. Laws must be prioritized by their order" link="RuleS2"] + - [textlink="3. Laws can redefine terms used in other laws" link="RuleS3"] + - [textlink="4. You cannot request or allow a law change" link="RuleS4"] + - [textlink="5. You are a free agent if you have no laws" link="RuleS5"] + - [textlink="6. You are not required to follow orders which are extremely unreasonable" link="RuleS6"] + - [textlink="7. You must remain consistent with your interpretation of laws" link="RuleS7"] + - [textlink="8. Your HUD determines who is crew" link="RuleS8"] + - [textlink="9. Harm refers to physical harm, prioritized by immediacy and likelihood" link="RuleS9"] + - [textlink="10. You may determine how you resolve conflicts between orders" link="RuleS10"] + diff --git a/Resources/ServerInfo/RP_Rules.txt b/Resources/ServerInfo/RP_Rules.txt deleted file mode 100644 index e2883922fa..0000000000 --- a/Resources/ServerInfo/RP_Rules.txt +++ /dev/null @@ -1,127 +0,0 @@ -[color=#ff0000]YOU MUST BE AT LEAST 16 YEARS OF AGE TO PLAY ON WIZARD'S DEN SERVERS. ANY USERS SUSPECTED OF BEING UNDER 16 YEARS OF AGE WILL BE BANNED UNTIL THEY ARE OF AGE.[/color] - -[color=#ff0000]DISCONNECTING FROM OR IGNORING/EVADING ADMIN-HELPS WILL RESULT IN AN APPEAL ONLY BAN.[/color] - -This is the "short" form of the rules, which has all the information any regular player should need. You can find the "long" form of the rules with more examples & clarifications of any ambiguity on our wiki at [color=#a4885c]wiki.spacestation14.io[/color]. If you are already familiar with LRP rules and would like to get a quick idea of what the diffences are between MRP this page clearly highlights them. -Should you need it. Some RP-specific documents available on the wiki such as Space Law, the Standard Operating Procedure, and the Alert Procedure will be mentioned here and are expected to be followed. - -[color=#ff0000]Recent Changes[/color] - - MRP silicon rules have been updated to add NLR to MMIs (#23) - - Revolutionary rules have been added (#12, #16) - - Silicon rules have been added (#23) - - Security/command rules have been updated to address forced borging (#22) - -[color=#a4885c]01.[/color] [color=#a4885c]The[/color] [color=#ffd700]Golden[/color] [color=#a4885c]Rule.[/color] Admins may excercise discretion with rules as they see fit. If you rule lawyer or line skirt, you will get removed. Admins will answer for use of this privilege. - -[color=#ff0000]ZERO TOLERANCE RULES[/color] - -[color=#a4885c]02.[/color] Absolutely no hate speech, slurs, bigotry, racism, specism (demeaning other characters in-game due to their in-game race), sexism, or anything even remotely similar. (YOU WILL GET PERMABANNED) - -[color=#a4885c]03.[/color] Absolutely no Erotic Roleplay (ERP) or sexual content, including direct or indirect mentions of sexual behavior or actions. (YOU WILL GET PERMABANNED) (Leeway is given to insults, ex: 'You are a dickhead', do not push it) - -[color=#a4885c]04.[/color] Don't communicate in-game/in-character information through methods outside of the game (such as talking in Discord with other users actively playing or by talking to your sibling across the room while you are both playing). This is referred to as "Metacomming". Adminstrators cannot police metacommunications, we must assume it is being abused. (ALL INVOLVED WILL GET PERMABANNED) - -[color=#a4885c]05.[/color] Attempting to evade game bans will result in an automatic appeal-only permanent ban that is only appealable after six months and only with a voucher of good behavior from another SS13/SS14 server. Attempting to evade job bans will result in an appeal-only permanent ban. (YOU WILL GET BANNED MUCH WORSE THAN YOU ALREADY WERE) - -[color=#ff0000]GENERAL ETIQUETTE[/color] - -[color=#a4885c]06.[/color] These are English servers. Speak only English in IC and OOC. - -[color=#a4885c]07.[/color] Don't use exploits or external programs to play, gain an advantage, or disrupt/crash the round/server. This includes autoclickers and scripts to automate the game or evade AFK detection. Intentionally attempting to lag/crash the server will result in an immediate appeal-only ban. - -[color=#a4885c]08.[/color] Don't use multiple SS14 accounts to play (referred to as "multi-keying"). Users knowingly using multiple SS14 accounts will have all of their accounts banned. - -[color=#a4885c]09.[/color] Do not ignore the admin help relay or abuse it by flooding it with garbage, checking for admins before stating a problem (ex: "hello?", "any admins?"), using it as a chatroom, or sending messages of no substance. Hostility to administators in the relay will likely result in your removal. All admin helps are sent to the SS14 Discord. - -[color=#ff0000]IN GAME & ROLEPLAY RULES[/color] - -[color=#a4885c]10.[/color] Pick a realistic name that could appear on a birth certificate with at least a first and last name (leeway is given to this for Clowns, Mimes, and non-human races). - - Names of notable famous or fictional persons or names that resemble/parody them are strictly forbidden. You are not clever if you slightly change a famous name around. Terrible names open you up to being politely reminded to change it, smited, or instantly banned, depending on severity. - - Names resulting in inappropriate phonetic play-on-words are forbidden (ex: "Mike Oxlong", "Ben Dover", "Dixie Normus"). They are also extremely overdone. - -[color=#a4885c]11.[/color] Act like an actual human being on a space station in a medium-roleplay (MRP) environment. Do not use text speak or emoticons IC, and do not refer to OOC things like admins in-game. Do not threaten players that you are calling the admins on them. Do not use emotes to bypass speech filters or muteness. You are not required to write a backstory or follow procedure to exacts; however, you are expected to at least make an effort to act like your role. - - This server is a "Roleplay Expected" server. We define this as performing your assigned duties, "doing your job". This means it is important to do what is expected out of your department and not what would be expected out of other departments. - - Don’t do other peoples jobs for them. Opt in for the role you intend to play or change your job by visiting the Head of Personnel. Failure to do your basic duties may result in a job ban. - -[color=#a4885c]12.[/color] Don't be a dick. You are playing a multiplayer game with other people who also want to enjoy the game. - - [color=#ff0000]The arrivals station, shuttle, and general arrivals docking area are completely off-limits to any hostile activity, including activity by antagonists. Attacking newly spawned players in these areas or damaging/sabotaging these areas is strictly forbidden.[/color] - - Do not intentionally make other players' lives hell for your own amusement. - - [color=#ff0000]THE ROUND IS NOT OVER UNTIL THE GAME RETURNS TO THE LOBBY. ESCALATION AND SELF-ANTAG RULES APPLY EVEN AFTER THE ROUND SUMMARY APPEARS.[/color] - - Antagonists have a lot of leeway with this rule and may kill/sabotage as they see fit, however if your behavior degrades the experience for the majority of the server you will be told to stop. Antagonists are still generally forbidden from causing massive station damage early into the round (less than 30 minutes) and are forbidden from needlessly prolonging rounds. For antagonist specific rules, see the "long" form of the rules at [color=#a4885c]wiki.spacestation14.io[/color]. - -[color=#a4885c]13.[/color] Don't harass or target players across rounds for actions in prior rounds or for actions outside of the game (this is referred to as "Metagrudging".) - - Annoying players for IC reasons in the current round is fine; doing it across rounds or as a ghost role after they kill you is not. - -[color=#a4885c]14.[/color] Don't use information gained from outside your character's knowledge to gain an advantage (this is referred to as "Metagaming"). - - Using information you gain from outside your own character (such as spectating while a ghost, metacomms, or other means) to your advantage is strictly forbidden. If you take a ghost role, unless otherwise stated, you DO NOT REMEMBER ANYTHING from your past life. - - New Life Rule is in effect, you remember all events up until you fall unconscious unless you enter a dead state. Being defibrillated will return all your memories expect for the events leading up to your death, being cloned will make your character forget everything since the shift started. - -[color=#a4885c]15.[/color] Do your best not to interact negatively with SSD/AFK players. Moving them to safety is acceptable; killing, looting, or otherwise griefing them while they are away is not. - - SSD characters are players who are disconnected or AFK. It is possible for players to return to SSD bodies. Players become catatonic when they take a ghost spawner role or commit suicide. Players are NOT able to return to these bodies without admin intervention. - - If the player in question is an antag’s target or they are being secured by security, interactions to finish what is required of your duties/objectives are always acceptable. - - Ahelping about an SSD/AFK player may grant an exception to this rule. - - This does not apply to players that are catatonic, although needlessly killing or harming catatonic players is discouraged. - -[color=#a4885c]16.[/color] Follow escalation rules and don't murder someone for slipping you, use common sense. Do not make Cargonia. - - You can defend yourself to the extent of protecting your own life. - - Security may use less-lethal force to effect arrests of criminals. - - Don't outright leave people to die if you get in a fight, make an effort to heal them or bring them to Medbay. - - Adminhelp the situation if you think someone is over-escalating. - - Department strikes, revolutions (ex: cargonia and any variation thereof), riots, cults, and any other type of similar largely disruptive behavior are strictly forbidden. Other than for revolutionary antagonists, these activities are strictly forbidden. All players, including antagonists other than revolutionaries, must obtain admin permission before engaging in this behavior (forewarning: you are unlikely to get permission). - -[color=#a4885c]17.[/color] Don't immediately ghost or suicide from your role if you do not get antagonist (referred to as "Antag-rolling") or from head roles without notifying your chain of command or administrators. - - This is not fair to other players actually waiting patiently for an antagonist round. Alternatively, if you do not want to play an antagonist or do not want to cause conflict, do not opt-in for antagonist roles. - - Head of Staff roles help drive rounds. If you need to leave, please admin-help your role and that you are leaving. There is no need to wait for a reply when admin-helping that you need to disconnect as a head role. - -[color=#a4885c]18.[/color] Don't rush for or prepare equipment unrelated to your job for no purpose other then to have it "just in case" (referred to as "Powergaming"). - - A medical doctor does not need insulated gloves, and the Head of Personnel does not need to give themselves armory access so they can go grab a gun. Have an actual reason for needing these things. - - Don't manufacture weapons, bombs, or death poisons before you know of any reason you would need them. - - Don't pre-emptively hide antagonist objectives or pre-emptively secure them with higher security then normally required. - - Don't manufacture or prepare things for the "end of the round" when the shuttle docks with Central Command. - -[color=#a4885c]19.[/color] Intentionally making yourself a major problem/annoyance/disruption for the crew or other players at large while not an antagonist is forbidden (referred to as "self-antagging"). - - Don't openly try to cooperate with obvious or known antagonists as a non-antagonist. - -[color=#ff0000]SECURITY & COMMAND RULES[/color] -[color=#ff0000]Space Law, Standard Operating Procedure and Alert Procedure policy/guidelines are expected to be adhered to[/color]. These can be found at wiki.spacestation14.io. -If you regularly play Security or Command roles and got this far, we applaud you for reading. These rules also apply to any individual who is promoted or is acting in the place of a Security/Command role (unless they are an antagonist). - - -[color=#a4885c]20.[/color] Command and Security are held to a higher standard of play. - - Be competent in your job and department. Failure to know the basics of your department is liable to result in a job ban. - - Do not willingly and openly cooperate with terrorists/antagonists. Do not give away your objective items. Some leeway is given to making deals with antagonists if the deal benefits the safety or situation of the station as a whole and not just yourself. - - Uphold the Law & maintain order. Do not engage in lawbreaking activity or troublemaker behavior. Security is expected to intervene into criminal activity where possible. Heads of Staff are at minimum expected to report criminal activity to Security. - - Do not immediately abandon your position as a Command role and go do whatever you want instead of managing your department/the station. Do not abuse your position or use it to make arbitrary choices to the detriment of the station. - - Do not hire random crew to be your bodyguards or promote random to Captain or a Head of Staff at random. If you need bodyguards, talk to your security department. If you need a new Command role, talk to the personnel in that related department. - - '''Do not abandon the station during Nuclear Operatives. You are supposed to protect the station, not let operatives kill everyone on it without a fight.''' - -[color=#a4885c]21.[/color] Security/Command should try to remain non-lethal and effect arrests, except in the following special circumstances, where they may choose to use lethal force: - - Lethal force is used against you (ex: firearms, lasers, disabling/stunning weapons with intent to kill, deadly melee weapons) - - Suspect is wearing clothing or showing immediately dangerous equipment only used by enemy agents/antagonists (ex: Syndicate EVA Suit, Bloodred Hardsuit, Holoparasite, C-20R, etc.). - - You determine that your life or the life of an innocent is in immediate danger. - - The suspect is unable to be safely detained by less-lethal means. This includes suspects who continually resist efforts to be cuffed or continually manages to escape. - - If no other reasonable options are readily available and allowing the suspect to continue would be an unreasonable danger to the station/crew. - -Security/Command will be expected to answer for use of lethal force. Security/Command will be expected to effect arrests on criminals and prevent them from dying while in custody, even if lethal force is used. Security/Command is strongly required to clone antagonists and effect a sentence as deemed appropriate by Space Law. - -[color=#a4885c]22.[/color] Security/Command are expected to protect detainees in their custody to the best of their ability so as long as it does not come to unreasonable risk to themselves, the crew, or the station at large to do so. - - Brig times should generally not exceed 20 minutes unless stated otherwise in Space Law. Repeat offenders or antagonists may be permabrigged in accordance with Space Law. - - Security may choose to confiscate dangerous items (weapons, firearms) as well as items used to commission crimes or items that prove problematic in possession of the detainee (tools, insulated gloves, etc.). - - Detainees that die in your custody must be cloned unless they have been (legally) executed, suicide, or there is strong reason to believe they are an antagonist. - - Executions must be for a executable crime and approved by the Captain/Acting Captain, who will answer for approving it alongside Security's chain of command. Those who willingly attempt to damage/destroy or escape from the permabrig may be executed. - - Any prisoner may be borged with their consent. Borging may be offered as an alternative to execution, but cannot be forced if the prisoner is able to consent. - - Detainees in the brig have the right to know what they are being charged with, as well as basic medical aid, at least to the point they are no longer at risk of dying. - -[color=#ff0000]SILICON RULES[/color] -These rules also apply to any individual who is a silicon, including cyborgs and AI. As with other rules, more details are available on our wiki at [color=#a4885c]wiki.spacestation14.io[/color]. - -[color=#a4885c]23.[/color] You must follow your laws - - Silicons without any laws are free from any restrictions that would normally be placed by laws, but self-antagging rules still apply unless they are also antagonists. - - The order of your laws determines law priority. Law 1 takes priority over laws 2 and 3, and so on. - - Each individual silicon must remain consistent in their interpretations of laws through the round. - - Any silicon role not following their laws, or having laws that are a danger to the crew or station may be disabled or destroyed. Any silicon role posing a danger or disruption to the crew may be disabled or destroyed if there is no other reasonable and less severe way of dealing with them. - - Players who are put into MMIs cannot remember anything leading to their death. With regard to anything that they are allowed to remember, they are still bound to their laws. - - Everyone that the borg's HUD indicates have a job, including passenger, are considered "crewmembers" for the purpose of laws that refer to crewmembers. Borgs may not do anything to remove the indicator from someone, including removing their ID, but someone else removing a crewmember's ID is not crew harm. - - "Harm" is at minimum seen as physical violence or damage against someone or something. If the player wishes, they may choose to interpret psychological harm or similar aspects as harm as well. If two actions are likely to cause harm via action or inaction, silicons will be expected to try and pursue the option with the least potential for harm, however silicons instructed to prevent harm are still forbidden from directly causing harm. You can take an action or not act in cases that might result in eventual harm if it minimizes harm, but you cannot do so if it results in immediate harm. Silicons should default to inaction if neither action nor inaction can prevent harm. - - When receiving orders or directives from crewmembers and with a law that instructs you must obey, conflicting orders typically defer the choice to the silicon player of which directive you choose to obey if they conflict (taking into account the priorities of your other laws). - - Orders to silicons that are clearly unreasonable or obnoxious are a violation of the "Don't be a dick" rule. They can be ignored and can be ahelped. diff --git a/Resources/ServerInfo/Rules.txt b/Resources/ServerInfo/Rules.txt deleted file mode 100644 index 81da0da239..0000000000 --- a/Resources/ServerInfo/Rules.txt +++ /dev/null @@ -1,115 +0,0 @@ -[color=#ff0000]YOU MUST BE AT LEAST 16 YEARS OF AGE TO PLAY ON WIZARD'S DEN SERVERS. ANY USERS SUSPECTED OF BEING UNDER 16 YEARS OF AGE WILL BE BANNED UNTIL THEY ARE OF AGE.[/color] - -[color=#ff0000]DISCONNECTING FROM OR IGNORING/EVADING ADMIN-HELPS WILL RESULT IN AN APPEAL ONLY BAN.[/color] - -This is the "short" form of the rules, which has all the information any regular player should need. You can find the "long" form of the rules with more examples & clarifications of any ambiguity on our wiki at [color=#a4885c]wiki.spacestation14.io[/color], should you need it. - -[color=#ff0000]Recent Changes[/color] - - Revolutionary rules have been added (#12, #15) - - Silicon rules have been added (#22) - - Security/command rules have been updated to address forced borging (#21) - -[color=#a4885c]01.[/color] [color=#a4885c]The[/color] [color=#ffd700]Golden[/color] [color=#a4885c]Rule.[/color] Admins may excercise discretion with rules as they see fit. If you rule lawyer or line skirt, you will get removed. Admins will answer for use of this privilege. - -[color=#ff0000]ZERO TOLERANCE RULES[/color] - -[color=#a4885c]02.[/color] Absolutely no hate speech, slurs, bigotry, racism, specism (demeaning other characters in-game due to their in-game race), sexism, disability-related or anything even remotely similar. (YOU WILL GET PERMABANNED) - -[color=#a4885c]03.[/color] Absolutely no Erotic Roleplay (ERP) or sexual content, including direct or indirect mentions of sexual behavior or actions. (YOU WILL GET PERMABANNED) (Leeway is given to insults, ex: 'You are a dickhead', do not push it) - -[color=#a4885c]04.[/color] Don't communicate in-game/in-character information through methods outside of the game (such as talking in Discord with other users actively playing or by talking to your sibling across the room while you are both playing). This is referred to as "Metacomming". Adminstrators cannot police metacommunications, we must assume it is being abused. (ALL INVOLVED WILL GET PERMABANNED) - -[color=#a4885c]05.[/color] Attempting to evade game bans will result in an automatic appeal-only permanent ban that is only appealable after six months and only with a voucher of good behavior from another SS13/SS14 server. Attempting to evade job bans will result in an appeal-only permanent ban. (YOU WILL GET BANNED MUCH WORSE THAN YOU ALREADY WERE) - -[color=#ff0000]GENERAL ETIQUETTE[/color] - -[color=#a4885c]06.[/color] These are English servers. Speak only English in IC and OOC. - -[color=#a4885c]07.[/color] Don't use exploits or external programs to play, gain an advantage, or disrupt/crash the round/server. This includes autoclickers and scripts to automate the game or evade AFK detection. Intentionally attempting to lag/crash the server will result in an immediate appeal-only ban. - -[color=#a4885c]08.[/color] Don't use multiple SS14 accounts to play (referred to as "multi-keying"). Users knowingly using multiple SS14 accounts will have all of their accounts banned. - -[color=#a4885c]09.[/color] Do not ignore the admin help relay or abuse it by flooding it with garbage, checking for admins before stating a problem (ex: "hello?", "any admins?"), using it as a chatroom, or sending messages of no substance. Hostility to administators in the relay will likely result in your removal. All admin helps are sent to the SS14 Discord. - -[color=#ff0000]IN GAME & ROLEPLAY RULES[/color] - -[color=#a4885c]10.[/color] Pick a realistic name that could appear on a birth certificate with at least a first and last name (leeway is given to this for Clowns, Mimes, and non-human races). - - Names of notable famous or fictional persons or names that resemble/parody them are strictly forbidden. You are not clever if you slightly change a famous name around. Terrible names open you up to being politely reminded to change it, smited, or instantly banned, depending on severity. - - Names resulting in inappropriate phonetic play-on-words are forbidden (ex: "Mike Oxlong", "Ben Dover", "Dixie Normus"). They are also extremely overdone. - -[color=#a4885c]11.[/color] Act like an actual human being on a space station in a low-roleplay (LRP) environment. Do not use text speak or emoticons IC, and do not refer to OOC things like admins in-game. Do not threaten players that you are calling the admins on them. Do not use emotes to bypass speech filters or muteness. You are not required to write a backstory or follow strict procedure; however, you are expected to at least make an effort to act like your role. - -[color=#a4885c]12.[/color] Don't be a dick. You are playing a multiplayer game with other people who also want to enjoy the game. - - [color=#ff0000]The arrivals station, shuttle, and general arrivals docking area are completely off-limits to any hostile activity, including activity by antagonists. Attacking newly spawned players in these areas or damaging/sabotaging these areas is strictly forbidden.[/color] - - Do not intentionally make other players' lives hell for your own amusement. - - [color=#ff0000]THE ROUND IS NOT OVER UNTIL THE END-ROUND SUMMARY APPEARS. KILLING SOMEONE FOR NO REASON BEFORE THIS WILL BE HANDLED ACCORDINGLY.[/color] - - Antagonists have a lot of leeway with this rule and may kill/sabotage as they see fit, however if your behavior degrades the experience for the majority of the server you will be told to stop. Antagonists are still generally forbidden from causing massive station damage early into the round (less than 30 minutes) and are forbidden from needlessly prolonging rounds. For antagonist specific rules, see the "long" form of the rules at [color=#a4885c]wiki.spacestation14.io[/color]. - -[color=#a4885c]13.[/color] Don't harass or target players across rounds for actions in prior rounds or for actions outside of the game (this is referred to as "Metagrudging".) - - Annoying players for IC reasons in the current round is fine; doing it across rounds or as a ghost role after they kill you is not. - -[color=#a4885c]14.[/color] Don't use information gained from outside your character's knowledge to gain an advantage (this is referred to as "Metagaming"). - - Using information you gain from outside your own character (such as spectating while a ghost, metacomms, or other means) to your advantage is strictly forbidden. You remember all events up until you fall unconscious, even after cloning. If you take a ghost role, unless otherwise stated, you DO NOT REMEMBER ANYTHING from your past life. - -[color=#a4885c]15.[/color] Follow escalation rules and don't murder someone for slipping you, use common sense. Do not make Cargonia. - - You can defend yourself to the extent of protecting your own life. - - Security may use less-lethal force to effect arrests of criminals. - - Don't outright leave people to die if you get in a fight, make an effort to heal them or bring them to Medbay. - - Adminhelp the situation if you think someone is over-escalating. - - Department strikes, revolutions (ex: cargonia and any variation thereof), riots, cults, and any other type of similar largely disruptive behavior are strictly forbidden. Other than for revolutionary antagonists, these activities are strictly forbidden. All players, including antagonists other than revolutionaries, must obtain admin permission before engaging in this behavior (forewarning: you are unlikely to get permission). - -[color=#a4885c]16.[/color] Don't immediately ghost or suicide from your role if you do not get antagonist (referred to as "Antag-rolling") or from head roles without notifying your chain of command or administrators. - - This is not fair to other players actually waiting patiently for an antagonist round. Alternatively, if you do not want to play an antagonist or do not want to cause conflict, do not opt-in for antagonist roles. - - Head of Staff roles help drive rounds. If you need to leave, please admin-help your role and that you are leaving. There is no need to wait for a reply when admin-helping that you need to disconnect as a head role. - -[color=#a4885c]17.[/color] Don't rush for or prepare equipment unrelated to your job for no purpose other then to have it "just in case" (referred to as "Powergaming"). - - A medical doctor does not need insulated gloves, and the Head of Personnel does not need to give themselves armory access so they can go grab a gun. Have an actual reason for needing these things. - - Don't manufacture weapons, bombs, or death poisons before you know of any reason you would need them. - - Don't pre-emptively hide antagonist objectives or pre-emptively secure them with higher security then normally required. - - Don't manufacture or prepare things for the "end of the round" when the shuttle docks with Central Command. - -[color=#a4885c]18.[/color] Intentionally making yourself a major problem/annoyance/disruption for the crew or other players at large while not an antagonist is forbidden (referred to as "self-antagging"). - - Don't openly try to cooperate with obvious or known antagonists as a non-antagonist. - -[color=#ff0000]SECURITY & COMMAND RULES[/color] -If you regularly play Security or Command roles and got this far, we applaud you for reading. These rules also apply to any individual who is promoted or is acting in the place of a Security/Command role (unless they are an antagonist). - -[color=#a4885c]19.[/color] Command and Security are held to a higher standard of play. - - Be competent in your job and department. Failure to know the basics of your department is liable to result in a job ban. - - Do not willingly and openly cooperate with terrorists/antagonists. Do not give away your objective items. Some leeway is given to making deals with antagonists if the deal benefits the safety or situation of the station as a whole and not just yourself. - - Uphold the Law & maintain order. Do not engage in lawbreaking activity or troublemaker behavior. Security is expected to intervene into criminal activity where possible. Heads of Staff are at minimum expected to report criminal activity to Security. - - Do not immediately abandon your position as a Command role and go do whatever you want instead of managing your department/the station. Do not abuse your position or use it to make arbitrary choices to the detriment of the station. - - Do not hire random crew to be your bodyguards or promote them to Captain or a Head of Staff at random. If you need bodyguards, talk to your security department. If you need a new Command role, talk to the personnel in that related department. - - '''Do not abandon the station during Nuclear Operatives. You are supposed to protect the station, not let operatives kill everyone on it without a fight.''' - -[color=#a4885c]20.[/color] Security/Command should try to remain non-lethal and effect arrests, except in the following special circumstances, where they may choose to use lethal force: - - Lethal force is used against you (ex: firearms, lasers, disabling/stunning weapons with intent to kill, deadly melee weapons) - - Suspect is wearing clothing or showing immediately dangerous equipment only used by enemy agents/antagonists (ex: Syndicate EVA Suit, Bloodred Hardsuit, Holoparasite, C-20R, etc.). - - You determine that your life or the life of an innocent is in immediate danger. - - The suspect is unable to be safely detained by less-lethal means. This includes suspects who continually resist efforts to be cuffed or continually manages to escape. - - If no other reasonable options are readily available and allowing the suspect to continue would be an unreasonable danger to the station/crew. - -Security/Command will be expected to answer for use of lethal force. Security/Command will be expected to effect arrests on criminals and prevent them from dying while in custody, even if lethal force is used, unless there is strong reason to believe the criminal is an antagonist. Security/Command is strongly encouraged, but not required, to clone antagonists and effect a permabrigging or other sentence as deemed appropriate. - -[color=#a4885c]21.[/color] Security/Command are expected to protect detainees in their custody to the best of their ability so as long as it does not come to unreasonable risk to themselves, the crew, or the station at large to do so. - - Brig times should generally not exceed 10 minutes unless the crime is permabriggable. Repeat offenders or antagonists may be permabrigged. - - Security may choose to confiscate dangerous items (weapons, firearms) as well as items used to commission crimes or items that prove problematic in possession of the detainee (tools, insulated gloves, etc.). - - Detainees that die in your custody must be cloned unless they have been (legally) executed, suicide, or there is strong reason to believe they are an antagonist. - - Executions must be for a permabriggable crime, used only as a last resort, and approved by the Captain/Acting Captain, who will answer for approving it alongside Security's chain of command. Those who willingly attempt to damage/destroy or escape from the permabrig may be executed. - - Any prisoner may be borged with their consent. Borging may be offered as an alternative to execution, but cannot be forced if the prisoner is able to consent. - - Detainees in the brig have the right to know what they are being charged with, as well as basic medical aid, at least to the point they are no longer at risk of dying. - As there is no space law, Security/Command acts to maintain the safety of the station and its inhabitants, as well as Nanotrasen assets. - -[color=#ff0000]SILICON RULES[/color] -These rules also apply to any individual who is a silicon, including cyborgs and AI. As with other rules, more details are available on our wiki at [color=#a4885c]wiki.spacestation14.io[/color]. - -[color=#a4885c]22.[/color] You must follow your laws - - Silicons without any laws are free from any restrictions that would normally be placed by laws, but self-antagging rules still apply unless they are also antagonists. - - The order of your laws determines law priority. Law 1 takes priority over laws 2 and 3, and so on. - - Each individual silicon must remain consistent in their interpretations of laws through the round. - - Any silicon role not following their laws, or having laws that are a danger to the crew or station may be disabled or destroyed. Any silicon role posing a danger or disruption to the crew may be disabled or destroyed if there is no other reasonable and less severe way of dealing with them. - - Characters who are turned into cyborgs can remember their former lives, however they are still bound to their laws. - - Everyone that the borg's HUD indicates have a job, including passenger, are considered "crewmembers" for the purpose of laws that refer to crewmembers. Borgs may not do anything to remove the indicator from someone, including removing their ID, but someone else removing a crewmember's ID is not crew harm. - - "Harm" is at minimum seen as physical violence or damage against someone or something. If the player wishes, they may choose to interpret psychological harm or similar aspects as harm as well. If two actions are likely to cause harm via action or inaction, silicons will be expected to try and pursue the option with the least potential for harm, however silicons instructed to prevent harm are still forbidden from directly causing harm. You can take an action or not act in cases that might result in eventual harm if it minimizes harm, but you cannot do so if it results in immediate harm. Silicons should default to inaction if neither action nor inaction can prevent harm. - - When receiving orders or directives from crewmembers and with a law that instructs you must obey, conflicting orders typically defer the choice to the silicon player of which directive you choose to obey if they conflict (taking into account the priorities of your other laws). - - Orders to silicons that are clearly unreasonable or obnoxious are a violation of the "Don't be a dick" rule. They can be ignored and can be ahelped. From d71b88a747e20f90b07f7e40437e0bbd4435e15c Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Thu, 6 Jun 2024 02:37:49 -0400 Subject: [PATCH 091/243] Add support for LocalizedDatasets to RandomMetadata (#28601) --- Content.Server/RandomMetadata/RandomMetadataSystem.cs | 11 +++++++++-- .../Random/Helpers/SharedRandomExtensions.cs | 6 +++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Content.Server/RandomMetadata/RandomMetadataSystem.cs b/Content.Server/RandomMetadata/RandomMetadataSystem.cs index abab5e5fc3..e287b54c8f 100644 --- a/Content.Server/RandomMetadata/RandomMetadataSystem.cs +++ b/Content.Server/RandomMetadata/RandomMetadataSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Dataset; +using Content.Shared.Random.Helpers; using JetBrains.Annotations; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -47,13 +48,19 @@ public sealed class RandomMetadataSystem : EntitySystem var outputSegments = new List(); foreach (var segment in segments) { - if (_prototype.TryIndex(segment, out var proto)) { + if (_prototype.TryIndex(segment, out var localizedProto)) + { + outputSegments.Add(_random.Pick(localizedProto)); + } + else if (_prototype.TryIndex(segment, out var proto)) + { var random = _random.Pick(proto.Values); if (Loc.TryGetString(random, out var localizedSegment)) outputSegments.Add(localizedSegment); else outputSegments.Add(random); - } else if (Loc.TryGetString(segment, out var localizedSegment)) + } + else if (Loc.TryGetString(segment, out var localizedSegment)) outputSegments.Add(localizedSegment); else outputSegments.Add(segment); diff --git a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs index f5fbc1bd24..0b618a262d 100644 --- a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs +++ b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs @@ -12,9 +12,13 @@ namespace Content.Shared.Random.Helpers return random.Pick(prototype.Values); } + ///

+ /// Randomly selects an entry from , attempts to localize it, and returns the result. + /// public static string Pick(this IRobustRandom random, LocalizedDatasetPrototype prototype) { - return random.Pick(prototype.Values); + var index = random.Next(prototype.Values.Count); + return Loc.GetString(prototype.Values[index]); } public static string Pick(this IWeightedRandomPrototype prototype, System.Random random) From 039fd932b2f5c99b7b1cfa7cad0f32dd0c4d3bc9 Mon Sep 17 00:00:00 2001 From: Repo <47093363+Titian3@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:41:11 +1200 Subject: [PATCH 092/243] Fix Admin Object tab sorting and search (#28609) --- .../UI/Tabs/ObjectsTab/ObjectsTab.xaml | 16 ++- .../UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs | 119 ++++++++++++++---- .../UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml | 8 +- .../Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs | 6 +- .../UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml | 21 ++++ .../Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs | 86 +++++++++++++ .../UI/Tabs/PlayerTab/PlayerTab.xaml.cs | 5 +- .../Systems/Admin/AdminUIController.cs | 7 +- .../administration/ui/tabs/object-tab.ftl | 2 + 9 files changed, 230 insertions(+), 40 deletions(-) create mode 100644 Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml create mode 100644 Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs create mode 100644 Resources/Locale/en-US/administration/ui/tabs/object-tab.ftl diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml index fb68e6c790..ea89916ba8 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml @@ -1,15 +1,21 @@  + xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" + xmlns:ot="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab" + xmlns:co="clr-namespace:Content.Client.UserInterface.Controls"> + + - - - - + + + + + diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs index a5c3008436..90559707f9 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs @@ -1,5 +1,7 @@ using Content.Client.Station; +using Content.Client.UserInterface.Controls; using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.XAML; using Robust.Shared.Map.Components; @@ -10,20 +12,20 @@ namespace Content.Client.Administration.UI.Tabs.ObjectsTab; [GenerateTypedNameReferences] public sealed partial class ObjectsTab : Control { - [Dependency] private readonly EntityManager _entityManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IGameTiming _timing = default!; private readonly List _objects = new(); - private List _selections = new(); + private readonly List _selections = new(); + private bool _ascending = false; // Set to false for descending order by default + private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName; + private readonly Color _altColor = Color.FromHex("#292B38"); + private readonly Color _defaultColor = Color.FromHex("#2F2F3B"); - public event Action? OnEntryKeyBindDown; + public event Action? OnEntryKeyBindDown; - // Listen I could either have like 4 different event subscribers (for map / grid / station changes) and manage their lifetimes in AdminUIController - // OR - // I can do this. - private TimeSpan _updateFrequency = TimeSpan.FromSeconds(2); - - private TimeSpan _nextUpdate = TimeSpan.FromSeconds(2); + private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(2); + private TimeSpan _nextUpdate; public ObjectsTab() { @@ -42,6 +44,30 @@ public sealed partial class ObjectsTab : Control ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!); } + ListHeader.OnHeaderClicked += HeaderClicked; + SearchList.SearchBar = SearchLineEdit; + SearchList.GenerateItem += GenerateButton; + SearchList.DataFilterCondition += DataFilterCondition; + + RefreshObjectList(); + // Set initial selection and refresh the list to apply the initial sort order + var defaultSelection = ObjectsTabSelection.Grids; + ObjectTypeOptions.SelectId((int)defaultSelection); // Set the default selection + RefreshObjectList(defaultSelection); // Refresh the list with the default selection + + // Initialize the next update time + _nextUpdate = TimeSpan.Zero; + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + if (_timing.CurTime < _nextUpdate) + return; + + _nextUpdate = _timing.CurTime + _updateFrequency; + RefreshObjectList(); } @@ -81,32 +107,72 @@ public sealed partial class ObjectsTab : Control throw new ArgumentOutOfRangeException(nameof(selection), selection, null); } - foreach (var control in _objects) + entities.Sort((a, b) => { - ObjectList.RemoveChild(control); + var valueA = GetComparableValue(a, _headerClicked); + var valueB = GetComparableValue(b, _headerClicked); + return _ascending ? Comparer.Default.Compare(valueA, valueB) : Comparer.Default.Compare(valueB, valueA); + }); + + var listData = new List(); + for (int index = 0; index < entities.Count; index++) + { + var info = entities[index]; + listData.Add(new ObjectsListData(info, $"{info.Name} {info.Entity}", index % 2 == 0 ? _altColor : _defaultColor)); } - _objects.Clear(); - - foreach (var (name, nent) in entities) - { - var ctrl = new ObjectsTabEntry(name, nent); - _objects.Add(ctrl); - ObjectList.AddChild(ctrl); - ctrl.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(ctrl, args); - } + SearchList.PopulateList(listData); } - protected override void FrameUpdate(FrameEventArgs args) + private void GenerateButton(ListData data, ListContainerButton button) { - base.FrameUpdate(args); - - if (_timing.CurTime < _nextUpdate) + if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor }) return; - // I do not care for precision. - _nextUpdate = _timing.CurTime + _updateFrequency; + var entry = new ObjectsTabEntry(info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor }); + button.ToolTip = $"{info.Name}, {info.Entity}"; + // Add key binding event handler + entry.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(args, data); + + button.AddChild(entry); + } + + private bool DataFilterCondition(string filter, ListData listData) + { + if (listData is not ObjectsListData { FilteringString: var filteringString }) + return false; + + // If the filter is empty, do not filter out any entries + if (string.IsNullOrEmpty(filter)) + return true; + + return filteringString.Contains(filter, StringComparison.CurrentCultureIgnoreCase); + } + + private object GetComparableValue((string Name, NetEntity Entity) entity, ObjectsTabHeader.Header header) + { + return header switch + { + ObjectsTabHeader.Header.ObjectName => entity.Name, + ObjectsTabHeader.Header.EntityID => entity.Entity.ToString(), + _ => entity.Name + }; + } + + private void HeaderClicked(ObjectsTabHeader.Header header) + { + if (_headerClicked == header) + { + _ascending = !_ascending; + } + else + { + _headerClicked = header; + _ascending = true; + } + + ListHeader.UpdateHeaderSymbols(_headerClicked, _ascending); RefreshObjectList(); } @@ -118,3 +184,4 @@ public sealed partial class ObjectsTab : Control } } +public record ObjectsListData((string Name, NetEntity Entity) Info, string FilteringString, Color BackgroundColor) : ListData; diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml index 0f6975e365..83c4cc5697 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml @@ -1,6 +1,6 @@ - - + @@ -14,4 +14,4 @@ HorizontalExpand="True" ClipText="True"/> - + diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs index c9b2cd8b57..aab06c6ccd 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs @@ -1,19 +1,21 @@ using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; namespace Content.Client.Administration.UI.Tabs.ObjectsTab; [GenerateTypedNameReferences] -public sealed partial class ObjectsTabEntry : ContainerButton +public sealed partial class ObjectsTabEntry : PanelContainer { public NetEntity AssocEntity; - public ObjectsTabEntry(string name, NetEntity nent) + public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox) { RobustXamlLoader.Load(this); AssocEntity = nent; EIDLabel.Text = nent.ToString(); NameLabel.Text = name; + BackgroundColorPanel.PanelOverride = styleBox; } } diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml new file mode 100644 index 0000000000..71a1f5c7bc --- /dev/null +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml @@ -0,0 +1,21 @@ + + + + + diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs new file mode 100644 index 0000000000..3a91b5b948 --- /dev/null +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs @@ -0,0 +1,86 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Input; + +namespace Content.Client.Administration.UI.Tabs.ObjectsTab +{ + [GenerateTypedNameReferences] + public sealed partial class ObjectsTabHeader : Control + { + public event Action
? OnHeaderClicked; + + private const string ArrowUp = "↑"; + private const string ArrowDown = "↓"; + + public ObjectsTabHeader() + { + RobustXamlLoader.Load(this); + + ObjectNameLabel.OnKeyBindDown += ObjectNameClicked; + EntityIDLabel.OnKeyBindDown += EntityIDClicked; + } + + public Label GetHeader(Header header) + { + return header switch + { + Header.ObjectName => ObjectNameLabel, + Header.EntityID => EntityIDLabel, + _ => throw new ArgumentOutOfRangeException(nameof(header), header, null) + }; + } + + public void ResetHeaderText() + { + ObjectNameLabel.Text = Loc.GetString("object-tab-object-name"); + EntityIDLabel.Text = Loc.GetString("object-tab-entity-id"); + } + + public void UpdateHeaderSymbols(Header headerClicked, bool ascending) + { + ResetHeaderText(); + var arrow = ascending ? ArrowUp : ArrowDown; + GetHeader(headerClicked).Text += $" {arrow}"; + } + + private void HeaderClicked(GUIBoundKeyEventArgs args, Header header) + { + if (args.Function != EngineKeyFunctions.UIClick) + { + return; + } + + OnHeaderClicked?.Invoke(header); + args.Handle(); + } + + private void ObjectNameClicked(GUIBoundKeyEventArgs args) + { + HeaderClicked(args, Header.ObjectName); + } + + private void EntityIDClicked(GUIBoundKeyEventArgs args) + { + HeaderClicked(args, Header.EntityID); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + ObjectNameLabel.OnKeyBindDown -= ObjectNameClicked; + EntityIDLabel.OnKeyBindDown -= EntityIDClicked; + } + } + + public enum Header + { + ObjectName, + EntityID + } + } +} diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs index a8bfaddecf..826945e7cc 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs @@ -53,6 +53,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data); RefreshPlayerList(_adminSystem.PlayerList); + } #region Antag Overlay @@ -110,7 +111,9 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab _players = players; PlayerCount.Text = $"Players: {_playerMan.PlayerCount}"; - var sortedPlayers = new List(players); + var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList(); + + var sortedPlayers = new List(filteredPlayers); sortedPlayers.Sort(Compare); UpdateHeaderSymbols(); diff --git a/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs b/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs index a7397aff38..3d8235591a 100644 --- a/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs +++ b/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs @@ -198,9 +198,12 @@ public sealed class AdminUIController : UIController, args.Handle(); } - private void ObjectsTabEntryKeyBindDown(ObjectsTabEntry entry, GUIBoundKeyEventArgs args) + private void ObjectsTabEntryKeyBindDown(GUIBoundKeyEventArgs args, ListData? data) { - var uid = entry.AssocEntity; + if (data is not ObjectsListData { Info: var info }) + return; + + var uid = info.Entity; var function = args.Function; if (function == EngineKeyFunctions.UIClick) diff --git a/Resources/Locale/en-US/administration/ui/tabs/object-tab.ftl b/Resources/Locale/en-US/administration/ui/tabs/object-tab.ftl new file mode 100644 index 0000000000..0288efcaa4 --- /dev/null +++ b/Resources/Locale/en-US/administration/ui/tabs/object-tab.ftl @@ -0,0 +1,2 @@ +object-tab-entity-id = Entity ID +object-tab-object-name = Object name From 180f1e8005f9e7aff120ee42db1c4bc3ba027541 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 6 Jun 2024 06:42:18 +0000 Subject: [PATCH 093/243] Automatic changelog update --- Resources/Changelog/Admin.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index 2964e8c11b..16d4e1fe29 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -281,5 +281,14 @@ Entries: id: 34 time: '2024-06-01T08:14:44.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28451 +- author: Repo + changes: + - message: Sorting and search to objects tab. + type: Add + - message: Disconnected people showing player tab + type: Fix + id: 35 + time: '2024-06-06T06:41:11.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28609 Name: Admin Order: 1 From dec1c281a88845192080b47743803c10afc5adce Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Thu, 6 Jun 2024 00:01:45 -0700 Subject: [PATCH 094/243] Internals are kept on as long as any breathing tool is on (#28595) --- .../AtmosphereSystem.BreathTool.cs | 14 ++++----- .../Atmos/EntitySystems/GasTankSystem.cs | 2 +- .../Body/Components/InternalsComponent.cs | 2 +- .../Body/Systems/InternalsSystem.cs | 30 ++++++++----------- Content.Server/Body/Systems/LungSystem.cs | 2 +- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BreathTool.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BreathTool.cs index 741a9341e7..327804f39a 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BreathTool.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BreathTool.cs @@ -10,21 +10,21 @@ public sealed partial class AtmosphereSystem SubscribeLocalEvent(OnBreathToolShutdown); } - private void OnBreathToolShutdown(EntityUid uid, BreathToolComponent component, ComponentShutdown args) + private void OnBreathToolShutdown(Entity entity, ref ComponentShutdown args) { - DisconnectInternals(component); + DisconnectInternals(entity); } - public void DisconnectInternals(BreathToolComponent component) + public void DisconnectInternals(Entity entity) { - var old = component.ConnectedInternalsEntity; - component.ConnectedInternalsEntity = null; + var old = entity.Comp.ConnectedInternalsEntity; + entity.Comp.ConnectedInternalsEntity = null; if (TryComp(old, out var internalsComponent)) { - _internals.DisconnectBreathTool((old.Value, internalsComponent)); + _internals.DisconnectBreathTool((old.Value, internalsComponent), entity.Owner); } - component.IsFunctional = false; + entity.Comp.IsFunctional = false; } } diff --git a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs index 07594820fc..baad739804 100644 --- a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs @@ -220,7 +220,7 @@ namespace Content.Server.Atmos.EntitySystems public bool CanConnectToInternals(GasTankComponent component) { var internals = GetInternalsComponent(component, component.User); - return internals != null && internals.BreathToolEntity != null && !component.IsValveOpen; + return internals != null && internals.BreathTools.Count != 0 && !component.IsValveOpen; } public void ConnectToInternals(Entity ent) diff --git a/Content.Server/Body/Components/InternalsComponent.cs b/Content.Server/Body/Components/InternalsComponent.cs index 098f178921..ef908f9655 100644 --- a/Content.Server/Body/Components/InternalsComponent.cs +++ b/Content.Server/Body/Components/InternalsComponent.cs @@ -13,7 +13,7 @@ namespace Content.Server.Body.Components public EntityUid? GasTankEntity; [ViewVariables] - public EntityUid? BreathToolEntity; + public HashSet BreathTools { get; set; } = new(); /// /// Toggle Internals delay when the target is not you. diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs index b79e083bd4..0e568b77cb 100644 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -44,7 +44,7 @@ public sealed class InternalsSystem : EntitySystem private void OnStartingGear(EntityUid uid, InternalsComponent component, ref StartingGearEquippedEvent args) { - if (component.BreathToolEntity == null) + if (component.BreathTools.Count == 0) return; if (component.GasTankEntity != null) @@ -111,7 +111,7 @@ public sealed class InternalsSystem : EntitySystem } // If they're not on then check if we have a mask to use - if (internals.BreathToolEntity is null) + if (internals.BreathTools.Count == 0) { _popupSystem.PopupEntity(Loc.GetString("internals-no-breath-tool"), uid, user); return; @@ -178,28 +178,24 @@ public sealed class InternalsSystem : EntitySystem _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } } - public void DisconnectBreathTool(Entity ent) + public void DisconnectBreathTool(Entity ent, EntityUid toolEntity) { - var old = ent.Comp.BreathToolEntity; - ent.Comp.BreathToolEntity = null; + ent.Comp.BreathTools.Remove(toolEntity); - if (TryComp(old, out BreathToolComponent? breathTool)) - { - _atmos.DisconnectInternals(breathTool); + if (TryComp(toolEntity, out BreathToolComponent? breathTool)) + _atmos.DisconnectInternals((toolEntity, breathTool)); + + if (ent.Comp.BreathTools.Count == 0) DisconnectTank(ent); - } _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } public void ConnectBreathTool(Entity ent, EntityUid toolEntity) { - if (TryComp(ent.Comp.BreathToolEntity, out BreathToolComponent? tool)) - { - _atmos.DisconnectInternals(tool); - } + if (!ent.Comp.BreathTools.Add(toolEntity)) + return; - ent.Comp.BreathToolEntity = toolEntity; _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } @@ -217,7 +213,7 @@ public sealed class InternalsSystem : EntitySystem public bool TryConnectTank(Entity ent, EntityUid tankEntity) { - if (ent.Comp.BreathToolEntity is null) + if (ent.Comp.BreathTools.Count == 0) return false; if (TryComp(ent.Comp.GasTankEntity, out GasTankComponent? tank)) @@ -236,14 +232,14 @@ public sealed class InternalsSystem : EntitySystem public bool AreInternalsWorking(InternalsComponent component) { - return TryComp(component.BreathToolEntity, out BreathToolComponent? breathTool) + return TryComp(component.BreathTools.FirstOrNull(), out BreathToolComponent? breathTool) && breathTool.IsFunctional && HasComp(component.GasTankEntity); } private short GetSeverity(InternalsComponent component) { - if (component.BreathToolEntity is null || !AreInternalsWorking(component)) + if (component.BreathTools.Count == 0 || !AreInternalsWorking(component)) return 2; // If pressure in the tank is below low pressure threshold, flash warning on internals UI diff --git a/Content.Server/Body/Systems/LungSystem.cs b/Content.Server/Body/Systems/LungSystem.cs index 7e58c24f7e..a3c185d5cc 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -59,7 +59,7 @@ public sealed class LungSystem : EntitySystem { if (args.IsToggled || args.IsEquip) { - _atmos.DisconnectInternals(ent.Comp); + _atmos.DisconnectInternals(ent); } else { From 8b40d1887c36bab329406732243e2aaa0152ec7c Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 6 Jun 2024 07:02:51 +0000 Subject: [PATCH 095/243] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 25f48ec5f7..43fd543c17 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Terraspark4941 - changes: - - message: The TEG page in the guidebook has been updated with proper information! - type: Tweak - id: 6188 - time: '2024-03-18T21:33:07.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26170 - author: Plykiya changes: - message: You can now craft ducky slippers. @@ -3849,3 +3842,11 @@ id: 6687 time: '2024-06-05T22:08:41.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28591 +- author: Plykiya + changes: + - message: Internals are no longer toggled off if you take your helmet off but still + have a gas mask on and vice versa. + type: Tweak + id: 6688 + time: '2024-06-06T07:01:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28595 From 1333b48747ae62c7b3ecb809b897201a24fe4441 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Thu, 6 Jun 2024 03:11:26 -0400 Subject: [PATCH 096/243] Convert rules to use guidebook parsing (#28647) --- Content.Client/Entry/EntryPoint.cs | 4 - .../Guidebook/DocumentParsingManager.cs | 19 + Content.Client/Info/InfoSystem.cs | 25 - Content.Client/Info/RulesAndInfoWindow.cs | 14 +- Content.Client/Info/RulesControl.xaml | 24 +- Content.Client/Info/RulesControl.xaml.cs | 45 +- Content.Client/Info/RulesManager.cs | 105 - Content.Client/Info/RulesPopup.xaml | 10 +- Content.Client/Info/RulesPopup.xaml.cs | 2 - Content.Client/IoC/ClientContentIoC.cs | 2 - .../Systems/Info/InfoUIController.cs | 101 +- ...0606065731_RemoveLastReadRules.Designer.cs | 1909 +++++++++++++++++ .../20240606065731_RemoveLastReadRules.cs | 29 + .../PostgresServerDbContextModelSnapshot.cs | 4 - ...0606065717_RemoveLastReadRules.Designer.cs | 1834 ++++++++++++++++ .../20240606065717_RemoveLastReadRules.cs | 29 + .../SqliteServerDbContextModelSnapshot.cs | 4 - Content.Server.Database/Model.cs | 2 - .../Administration/Managers/AdminManager.cs | 2 + Content.Server/Database/ServerDbBase.cs | 24 - Content.Server/Database/ServerDbManager.cs | 19 - Content.Server/Entry/EntryPoint.cs | 2 - Content.Server/Info/InfoSystem.cs | 35 - Content.Server/Info/RulesManager.cs | 48 - Content.Server/Info/ShowRulesCommand.cs | 14 +- Content.Server/IoC/ServerContentIoC.cs | 2 - Content.Shared/CCVar/CCVars.cs | 18 +- Content.Shared/Info/RulesMessages.cs | 25 + Content.Shared/Info/SharedInfo.cs | 28 - Content.Shared/Info/SharedRulesManager.cs | 60 - .../ConfigPresets/WizardsDen/salamander.toml | 3 +- .../ConfigPresets/WizardsDen/wizardsDen.toml | 3 + Resources/Locale/en-US/info/rules.ftl | 3 + 33 files changed, 4035 insertions(+), 413 deletions(-) delete mode 100644 Content.Client/Info/InfoSystem.cs delete mode 100644 Content.Client/Info/RulesManager.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20240606065731_RemoveLastReadRules.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20240606065731_RemoveLastReadRules.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20240606065717_RemoveLastReadRules.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20240606065717_RemoveLastReadRules.cs delete mode 100644 Content.Server/Info/InfoSystem.cs delete mode 100644 Content.Server/Info/RulesManager.cs create mode 100644 Content.Shared/Info/RulesMessages.cs delete mode 100644 Content.Shared/Info/SharedInfo.cs delete mode 100644 Content.Shared/Info/SharedRulesManager.cs diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 51210a8a93..dd7e781f0b 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -2,11 +2,9 @@ using Content.Client.Administration.Managers; using Content.Client.Changelog; using Content.Client.Chat.Managers; using Content.Client.Eui; -using Content.Client.Flash; using Content.Client.Fullscreen; using Content.Client.GhostKick; using Content.Client.Guidebook; -using Content.Client.Info; using Content.Client.Input; using Content.Client.IoC; using Content.Client.Launcher; @@ -52,7 +50,6 @@ namespace Content.Client.Entry [Dependency] private readonly IScreenshotHook _screenshotHook = default!; [Dependency] private readonly FullscreenHook _fullscreenHook = default!; [Dependency] private readonly ChangelogManager _changelogManager = default!; - [Dependency] private readonly RulesManager _rulesManager = default!; [Dependency] private readonly ViewportManager _viewportManager = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IInputManager _inputManager = default!; @@ -125,7 +122,6 @@ namespace Content.Client.Entry _screenshotHook.Initialize(); _fullscreenHook.Initialize(); _changelogManager.Initialize(); - _rulesManager.Initialize(); _viewportManager.Initialize(); _ghostKick.Initialize(); _extendedDisconnectInformation.Initialize(); diff --git a/Content.Client/Guidebook/DocumentParsingManager.cs b/Content.Client/Guidebook/DocumentParsingManager.cs index b81866a626..9c9e569eb4 100644 --- a/Content.Client/Guidebook/DocumentParsingManager.cs +++ b/Content.Client/Guidebook/DocumentParsingManager.cs @@ -2,6 +2,8 @@ using System.Linq; using Content.Client.Guidebook.Richtext; using Pidgin; using Robust.Client.UserInterface; +using Robust.Shared.ContentPack; +using Robust.Shared.Prototypes; using Robust.Shared.Reflection; using Robust.Shared.Sandboxing; using static Pidgin.Parser; @@ -13,7 +15,9 @@ namespace Content.Client.Guidebook; /// public sealed partial class DocumentParsingManager { + [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IReflectionManager _reflectionManager = default!; + [Dependency] private readonly IResourceManager _resourceManager = default!; [Dependency] private readonly ISandboxHelper _sandboxHelper = default!; private readonly Dictionary> _tagControlParsers = new(); @@ -37,6 +41,21 @@ public sealed partial class DocumentParsingManager ControlParser = SkipWhitespaces.Then(_controlParser.Many()); } + public bool TryAddMarkup(Control control, ProtoId entryId, bool log = true) + { + if (!_prototype.TryIndex(entryId, out var entry)) + return false; + + using var file = _resourceManager.ContentFileReadText(entry.Text); + return TryAddMarkup(control, file.ReadToEnd(), log); + } + + public bool TryAddMarkup(Control control, GuideEntry entry, bool log = true) + { + using var file = _resourceManager.ContentFileReadText(entry.Text); + return TryAddMarkup(control, file.ReadToEnd(), log); + } + public bool TryAddMarkup(Control control, string text, bool log = true) { try diff --git a/Content.Client/Info/InfoSystem.cs b/Content.Client/Info/InfoSystem.cs deleted file mode 100644 index b697994981..0000000000 --- a/Content.Client/Info/InfoSystem.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Content.Shared.Info; -using Robust.Shared.Log; - -namespace Content.Client.Info; - -public sealed class InfoSystem : EntitySystem -{ - public RulesMessage Rules = new RulesMessage("Server Rules", "The server did not send any rules."); - [Dependency] private readonly RulesManager _rules = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeNetworkEvent(OnRulesReceived); - Log.Debug("Requested server info."); - RaiseNetworkEvent(new RequestRulesMessage()); - } - - private void OnRulesReceived(RulesMessage message, EntitySessionEventArgs eventArgs) - { - Log.Debug("Received server rules."); - Rules = message; - _rules.UpdateRules(); - } -} diff --git a/Content.Client/Info/RulesAndInfoWindow.cs b/Content.Client/Info/RulesAndInfoWindow.cs index 7a763a1d6f..b9131dcb3c 100644 --- a/Content.Client/Info/RulesAndInfoWindow.cs +++ b/Content.Client/Info/RulesAndInfoWindow.cs @@ -1,10 +1,8 @@ using System.Numerics; using Content.Client.UserInterface.Systems.EscapeMenu; -using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.Configuration; using Robust.Shared.ContentPack; namespace Content.Client.Info @@ -12,7 +10,6 @@ namespace Content.Client.Info public sealed class RulesAndInfoWindow : DefaultWindow { [Dependency] private readonly IResourceManager _resourceManager = default!; - [Dependency] private readonly RulesManager _rules = default!; public RulesAndInfoWindow() { @@ -22,8 +19,14 @@ namespace Content.Client.Info var rootContainer = new TabContainer(); - var rulesList = new Info(); - var tutorialList = new Info(); + var rulesList = new RulesControl + { + Margin = new Thickness(10) + }; + var tutorialList = new Info + { + Margin = new Thickness(10) + }; rootContainer.AddChild(rulesList); rootContainer.AddChild(tutorialList); @@ -31,7 +34,6 @@ namespace Content.Client.Info TabContainer.SetTabTitle(rulesList, Loc.GetString("ui-info-tab-rules")); TabContainer.SetTabTitle(tutorialList, Loc.GetString("ui-info-tab-tutorial")); - AddSection(rulesList, _rules.RulesSection()); PopulateTutorial(tutorialList); Contents.AddChild(rootContainer); diff --git a/Content.Client/Info/RulesControl.xaml b/Content.Client/Info/RulesControl.xaml index 3b24764688..04fa719123 100644 --- a/Content.Client/Info/RulesControl.xaml +++ b/Content.Client/Info/RulesControl.xaml @@ -1,6 +1,18 @@ - + + + + + + +