From 649b23a0615ed645ad660a5cb150e43fa58b8462 Mon Sep 17 00:00:00 2001 From: Flareguy Date: Wed, 23 Jul 2025 21:22:02 -0500 Subject: [PATCH 01/40] moth displacement map fixes --- .../Mobs/Species/Moth/displacement.rsi/hand.png | Bin 392 -> 379 bytes .../Moth/displacement.rsi/jumpsuit-female.png | Bin 578 -> 571 bytes .../Moth/displacement.rsi/jumpsuit-male.png | Bin 558 -> 535 bytes .../Moth/displacement.rsi/outerclothing.png | Bin 626 -> 635 bytes .../Species/Moth/displacement.rsi/shoes.png | Bin 385 -> 384 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/hand.png b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/hand.png index f632f80d19e16459c108aaed89d8449e7b4b4c00..5443655da680cb0072c9832311b6f3e0491a0507 100644 GIT binary patch delta 276 zcmV+v0qg#V1N#DyF)lSpL_t(|ob8+23WG2Z1+&r*&zE=mU@xgPxJHvHcm_%-s5=g6 zg^HGQ@^oP)kz^oRQ2<(iCLF+m+x7Z;sn`5lUu{2 delta 324 zcmey()WJN#P~XYZ#WAE}&fD1!`N_H z{7-I*ozXb)okG1xgV+L2_74v}|9ks*Y4ltEr=2eT`<{uB|rC-v6^+zopr031b* AWdHyG diff --git a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/jumpsuit-female.png b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/jumpsuit-female.png index f4cf6def0373e0c10ce4c34bd62fc8b4125a3743..93dada15f713cc56407c0b8d18c3509a336968d9 100644 GIT binary patch delta 533 zcmX@avYTasVf|N67srr_Id7xi_8nH>X?`WQ$KURqzxGzi_=Rfs+;rZW#cGfo&`@H&FpZK%0C(3(-85a=I?z^rSb~G##8T%k*aD!AbVn2W|=ndE~9@eI&b4Zo`xHvwA(!{1Xn=cQ;IR`d{Co-^%{} zx8x4S9L5}EVp@uAx9IER*}=>=c6(d?5J=!oU`{%4CibDs1Z#gS=BOiLYG3mDzcPKY zn4@?9Qq+Pg7X<63GMYx1$#MEO^}qYq;w;E-dO+zwmYTykhJ(runFscD`|Z2a`);Pt zJtf~Q4}6nX+nvpCtDki{K)-oQ)I*7zGA}1-SDTjy^C%w4jnlI*U7~ycz-zTGx8_~@ zlM;^~Gm?EGwf(Gxx<>WBXRG$Kb+yFgtuzgJm75t+r^ph`<*jg@Q|a~OhPk5i<}#Zc z)-2Ci*7Dr>z^~H`2KlB;`+i0kyjh{s!^tb~{?q0IpVzopr06dHKj{pDw delta 540 zcmdnZa)@PuVf`OZ7srr_Id7wHuRE;3)BLKYVt>tD?}-toR2x<7f|4`E*KRRmW6F)N zJ72O?{ILVKgE9|J;(^$nch@<7)K-_t$Pw|NqJA!LIuOg^u0-m$L^vuX?xTSv0S*z@M{<4RLZ+ zpUinRbRAnoj=#QN92%C*`r@R$Nwo5ew+EVmstb0_d+H@{P(O;RpQ-nFeb9|2dD-_5 zIG8UmT|g#mPFi$}zQ3Iv&V1qKQn4hC1*{7eCN-pn?O;87X7Y&!$&KxtcfLr7Yi&RL z^yHfKvK32HTjS?Qy=I&%746D$aP8**hwXXIOjmqmSU=ODn9W18s^Lt-XO-TVxohtl z+1gY|tvu-b_)XQM`u{w>mjmW6S19GNcr`^*TxaK&(rERkQ-bW?BspFzkbTQ_%~Pb4 zt+4v(m2)>L+M2_jdS&@^Eo9vC)P3Eh; diff --git a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/jumpsuit-male.png b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/jumpsuit-male.png index c3be6e6d9d1ac97c288099f9f5c68a5268368753..b78c10e0c1b3383ad6142f4b3b6da838b1b9e3ce 100644 GIT binary patch delta 496 zcmZ3-GM#0DVf{r<7srr_Id7vc_Ax2)w4AN|QMdC>w^{`EmYmxk^Q=1qAL|+OY*}!$ zYTnYR(vKau9h7--5)Ye9G?>xMAyO_6p(|lgWFIzgEr1MtvvWOm1o#Hi5_Z#!X zKQ9ipJVLW-7enj?rt4tGh21{O+A_ zGi=78PjeQeR*R&ix9%-&P@7{Ce{Nm7!V|x|_C)Eq4xu>3;u_p0I_3zLJ-`M^z YeNIbUb7P-T0|O9vy85}Sb4q9e0Ik~NEC2ui delta 520 zcmbQvvW{hfVf_nF7srr_Id7w*`I-%QvM=(TJ9)gW?Mmyupg9#n8(YkBS$F42aW(FF z|I&m1*fFsKZW0G@5eBzE{K&I?@;;4G```7P!!wzq9IpK0t z#Yw(3hi2{1bjYijfACbt<{$F=`F?aVyo@UX4U5lJ! zrLr4irJ`L`4n|l1KRlbw-1LCb1Nli0xRjVodcVi=6m2xGN{QOxGV8@b*T*j^W|g$$m0 z4Rv?1otXH3r`CUGS0~-=U#zsg_LY`$GvQMc!#oLJPU9THuERY`lFm@J;QtfA1S z^0vo|Lpv6-E_lecfDjRA)^OiGPulQpZ^m!KbwcjFu7B)pt)7JSo&2&Xc!kY6@oRr# z-f(=3SFuy7_u@Ds%DA&oM)TD{rGHg?55f(El=LRZ-%)AQQTxxr{o}Ov1CB2u3v0XD zFKo9^jyMuOpYh}L_g;)2clh64%l%`vXTgj4Gj67vJ^OCOpv%zvO~_-yGjYv7XSp{2 ztYyn!e;4R0Xnl5O_Ua_rM$!FQY+uwK-Sj>gHKV|hbC$8MLxyX`T_myXd zxAh8vi_V7Y#HaoFr81*?*6i6~(T#%pUzJ)!x4t`RdT6mf^OXOeZZD8-5Nl|YzF=&z zcJKMNiN6k~Gej{+<$;JS=5urY1btCC)Nkdqv-+CbRV#$e{B{_>x#K*%dE<5><*za#w+|B-T+%{d=y~L8rTOnUm5owmuE;ZCc&GFWu?4UsKu7>kn5> zePBLQBp{YaSN)>3(@XJ%mx~(2R`=d#Wzp$PySj~J~0 delta 588 zcmey(@`+`FVZDy0i(^Q|oVQU2H#HgX_8pamOuY{S=s6Dk$rFXI2`wna@Ww6 z`~1qnl%rna4NxF_`vDy#OWVE6+Ahc3=(UQu@bRne2DdF`e49I#9IbKKE+`u>s66z!kY#Z?_@V0Q zJrlIT*CxGX)Y_NHTH*ibVl<=V{V8nQtKUBTv#n;umHVFc$Cf#!)O_aJk$yK*Y-0Pt zbKEI4zZD}sM@7$G5yH@wUnR3HY+-TX-3JdJJM`B7G?8H1z_~$?siNoft=~B>R@x`A zg84uqQS8B)_IS?ojz2=(SCsMVhOA}|`NpMFklT1IASt`KLT=Nx{d?*gd>ihR?Ngp? zxH>_$R&+{V{Z0wb8`~O;(r+JSDs!8B@OyXnME4X%Ge)yJkq%FOT={;^aPNwIC6 zjjyrnb@%@+cH)y|<6KtlbIUmY?E9W7d`N4hy5{`Sqy$f zz2;XPe+=7Bt|CtHC)E?r`KJM5q4<~Zf1D+sNUsRq^Q%|D9J4o_vh9A{njhBz&-wLV znbNzKmZaCIz-j*JX)u`CT-vc^+k~~v0iCbYAT2EIl8uT>TWr+Fkh(o;wwM3P`*Xy zH@iyDm@LSsS-+TJ3qvo{1tycj>=jq`9Vq|)xazd+_3mq*+wU_TWfU_mu9yG*w01A6 zareFT8S8(#zTTdA`r!Hw#k!?Ymx})-He~!iYX9T?uD6Q**JBra|MN)J;OG-y=c1VF zwwFtJUs&${qdd9#`kiTSO0`~?{QqF`BF*gSyogDU`_kt!Tg5MTtd44MW6WTbVqL(R zA<`hWAdrC=foDMt)AlpXSKRCLgy-t?6Hjg^-TA8iWLmM_+lG)QeRnTZrCsIQ@w;h- h)TQp_UXuU3Kd?qmvFdo?5FW$;1fH&bF6*2UngA>;jVAyA From 23e2f997a96434317d5b008b8f367732c45ea1bc Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 9 Aug 2025 21:53:17 +0000 Subject: [PATCH 02/40] 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 718d0605c9..e032c3290b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,13 +1,4 @@ Entries: -- author: RedBookcase - changes: - - message: Added Advanced Circular Saw as a Medical Doctor specific uplink item. - type: Add - - message: Removed Syndicate Surgery Duffel Bag from uplink. - type: Remove - id: 8328 - time: '2025-04-24T03:01:29.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/35915 - author: AgentSmithRadio, MadeOfHeartAndStone changes: - message: Added cotton grilled cheese sandwich entity and recipe. @@ -3900,3 +3891,11 @@ id: 8840 time: '2025-08-08T19:27:46.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/39481 +- author: Flareguy + changes: + - message: Updated moth displacement maps to fix missing pixels, among other various + issues. + type: Fix + id: 8841 + time: '2025-08-09T21:52:06.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39174 From 4e29107c89e4ba80efa6fa83c415dd75a99fa0bc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 10 Aug 2025 02:45:37 +0200 Subject: [PATCH 03/40] Update Credits (#39512) Co-authored-by: PJBot --- Resources/Credits/GitHub.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index f1a069de14..814930806c 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0leshe, 0tito, 0x6273, 12rabbits, 1337dakota, 13spacemen, 154942, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 3nderall, 4310v343k, 4dplanner, 612git, 778b, 96flo, aaron, abadaba695, Ablankmann, abregado, Absolute-Potato, Absotively, achookh, Acruid, ActiveMammmoth, actually-reb, ada-please, adamsong, Adeinitas, adm2play, Admiral-Obvious-001, adrian, Adrian16199, Ady4ik, Aearo-Deepwater, Aerocrux, Aeshus, Aexolott, Aexxie, africalimedrop, afrokada, AftrLite, AgentSmithRadio, Agoichi, Ahion, aiden, Aidenkrz, Aisu9, ajcm, AJCM-git, AjexRose, Alekshhh, alexkar598, AlexMorgan3817, alexum418, alexumandxgabriel08x, Alice4267, Alithsko, Alkheemist, alliephante, ALMv1, Alpaccalypse, Alpha-Two, AlphaQwerty, Altoids1, amatwiedle, amylizzle, ancientpower, Andre19926, AndrewEyeke, AndreyCamper, Anzarot121, ApolloVector, Appiah, ar4ill, Arcane-Waffle, archee1, ArchPigeon, ArchRBX, areitpog, Arendian, areyouconfused, arimah, Arkanic, ArkiveDev, armoks, Arteben, ArthurMousatov, ArtisticRoomba, artur, ArZarLordOfMango, as334, AsikKEsel, AsnDen, asperger-sind, aspiringLich, astriloqua, august-sun, AutoOtter, AverageNotDoingAnythingEnjoyer, avghdev, Awlod, azzyisnothere, AzzyIsNotHere, B-Kirill, B3CKDOOR, baa14453, BackeTako, Bakke, BananaFlambe, Baptr0b0t, BarryNorfolk, BasedUser, beck-thompson, beesterman, bellwetherlogic, ben, benbryant0, benev0, benjamin-burges, BGare, bhespiritu, bibbly, BigfootBravo, BIGZi0348, bingojohnson, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, blitzthesquishy, bloodrizer, Bloody2372, blueDev2, Boaz1111, BobdaBiscuit, BobTheSleder, boiled-water-tsar, Bokser815, bolantej, Booblesnoot42, Boolean-Buckeye, botanySupremist, brainfood1183, BramvanZijp, Brandon-Huu, BriBrooo, Bright0, brndd, bryce0110, BubblegumBlue, buletsponge, buntobaggins, bvelliquette, BWTCK, byondfuckery, c0rigin, c4llv07e, CaasGit, Caconym27, Calecute, Callmore, Camdot, capnsockless, CaptainMaru, captainsqrbeard, Carbonhell, Carolyn3114, Carou02, carteblanche4me, catdotjs, catlord, Catofquestionableethics, CatTheSystem, Centronias, Chaboricks, chairbender, Chaoticaa, Charlese2, charlie, chartman, ChaseFlorom, chavonadelal, Cheackraze, CheddaCheez, cheesePizza2, CheesePlated, Chief-Engineer, chillyconmor, christhirtle, chromiumboy, Chronophylos, Chubbicous, Chubbygummibear, Ciac32, ciaran, citrea, civilCornball, claustro305, Clement-O, clyf, Clyybber, CMDR-Piboy314, cnv41, coco, cohanna, Cohnway, Cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, Compilatron144, CookieMasterT, coolboy911, coolmankid12345, Coolsurf6, cooperwallace, corentt, CormosLemming, CrafterKolyan, crazybrain23, Crazydave91920, creadth, CrigCrag, CroilBird, Crotalus, CrudeWax, cryals, CrzyPotato, cubixthree, cutemoongod, Cyberboss, d34d10cc, DadeKuma, Daemon, daerSeebaer, dahnte, dakamakat, DamianX, dan, dangerrevolution, daniel-cr, DanSAussieITS, Daracke, Darkenson, DawBla, Daxxi3, dch-GH, de0rix, Deahaka, dean, DEATHB4DEFEAT, Deatherd, deathride58, DebugOk, Decappi, Decortex, Deeeeja, deepdarkdepths, DeepwaterCreations, Deerstop, degradka, Delete69, deltanedas, DenisShvalov, DerbyX, derek, dersheppard, Deserty0, Detintinto, DevilishMilk, devinschubert14, dexlerxd, dffdff2423, DieselMohawk, digitalic, Dimastra, DinnerCalzone, DinoWattz, Disp-Dev, DisposableCrewmember42, dissidentbullet, DjfjdfofdjfjD, doc-michael, docnite, Doctor-Cpu, DogZeroX, dolgovmi, dontbetank, Doomsdrayk, Doru991, DoubleRiceEddiedd, DoutorWhite, DR-DOCTOR-EVIL-EVIL, Dragonjspider, dragonryan06, drakewill-CRL, Drayff, dreamlyjack, DrEnzyme, dribblydrone, DrMelon, drongood12, DrSingh, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, DuckManZach, Duddino, dukevanity, duskyjay, Dutch-VanDerLinde, dvir001, dylanstrategie, dylanwhittingham, Dynexust, Easypoller, echo, eclips_e, eden077, EEASAS, Efruit, efzapa, Ekkosangen, ElectroSR, elsie, elthundercloud, Elysium206, Emisse, emmafornash, EmoGarbage404, Endecc, EnrichedCaramel, Entvari, eoineoineoin, ephememory, eris, erohrs2, ERORR404V1, Errant-4, ertanic, esguard, estacaoespacialpirata, eugene, ewokswagger, exincore, exp111, f0x-n3rd, FacePluslll, Fahasor, FairlySadPanda, farrellka-dev, FATFSAAM2, Feluk6174, ficcialfaint, Fiftyllama, Fildrance, FillerVK, FinnishPaladin, firenamefn, Firewars763, FirinMaLazors, Fishfish458, fl-oz, Flareguy, flashgnash, FlipBrooke, FluffiestFloof, FluffMe, FluidRock, flymo5678, foboscheshir, FoLoKe, fooberticus, ForestNoises, forgotmyotheraccount, forkeyboards, forthbridge, Fortune117, foxhorn, freeman2651, freeze2222, frobnic8, Froffy025, Fromoriss, froozigiusz, FrostMando, FrostRibbon, Funce, FungiFellow, FunTust, Futuristic-OK, GalacticChimp, gamer3107, Gamewar360, gansulalan, GaussiArson, Gaxeer, gbasood, gcoremans, Geekyhobo, genderGeometries, GeneralGaws, Genkail, Gentleman-Bird, geraeumig, Ghagliiarghii, Git-Nivrak, githubuser508, gituhabu, GlassEclipse, GnarpGnarp, GNF54, godisdeadLOL, goet, GoldenCan, Goldminermac, Golinth, golubgik, GoodWheatley, Gorox221, gradientvera, graevy, GraniteSidewalk, GreaseMonk, greenrock64, GreyMario, GrownSamoyedDog, GTRsound, gusxyz, Gyrandola, h3half, hamurlik, Hanzdegloker, HappyRoach, Hardly3D, harikattar, he1acdvv, Hebi, Helix-ctrl, helm4142, Henry, HerCoyote23, HighTechPuddle, Hitlinemoss, hiucko, hivehum, Hmeister-fake, Hmeister-real, Hobbitmax, hobnob, HoidC, Holinka4ever, holyssss, HoofedEar, Hoolny, hord-brayden, Hoshizora, Hreno, Hrosts, htmlsystem, hubismal, Hugal31, Huxellberger, Hyenh, hyperb1, hyperDelegate, hyphenationc, i-justuser-i, iaada, iacore, IamVelcroboy, Ian321, icekot8, icesickleone, iczero, iglov, IgorAnt028, igorsaux, ike709, illersaver, Illiux, Ilushkins33, Ilya246, IlyaElDunaev, imatsoup, IMCB, impubbi, imrenq, imweax, indeano, Injazz, Insineer, insoPL, IntegerTempest, Interrobang01, Intoxicating-Innocence, IProduceWidgets, itsmethom, Itzbenz, iztokbajcar, Jackal298, Jackrost, jacksonzck, Jacktastic09, Jackw2As, jacob, jamessimo, janekvap, Jark255, Jarmer123, Jaskanbe, JasperJRoth, jbox144, JCGWE30, JerryImMouse, jerryimmouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JimGamemaster, jimmy12or, JIPDawg, jjtParadox, jkwookee, jmcb, JohnGinnane, johnku1, Jophire, joshepvodka, JpegOfAFrog, jproads, JrInventor05, Jrpl, jukereise, juliangiebel, JustArt1m, JustCone14, justdie12, justin, justintether, JustinTrotter, JustinWinningham, justtne, K-Dynamic, k3yw, Kadeo64, Kaga-404, kaiserbirch, KaiShibaa, kalane15, kalanosh, KamTheSythe, Kanashi-Panda, katzenminer, kbailey-git, Keelin, Keer-Sar, KEEYNy, keikiru, Kelrak, kerisargit, keronshb, KIBORG04, KieueCaprie, Killerqu00, Kimpes, KingFroozy, kira-er, kiri-yoshikage, Kirillcas, Kirus59, Kistras, Kit0vras, KittenColony, Kittygyat, klaypexx, Kmc2000, Ko4ergaPunk, kognise, kokoc9n, komunre, KonstantinAngelov, kontakt, kosticia, koteq, kotobdev, Kowlin, KrasnoshchekovPavel, Krosus777, Krunklehorn, Kupie, kxvvv, kyupolaris, kzhanik, LaCumbiaDelCoronavirus, lajolico, Lamrr, lanedon, LankLTE, laok233, lapatison, larryrussian, lawdog4817, Lazzi0706, leander-0, leonardo-dabepis, leonidussaks, leonsfriedrich, LeoSantich, LetterN, lettern, Level10Cybermancer, LEVELcat, lever1209, LevitatingTree, Lgibb18, lgruthes, LightVillet, liltenhead, linkbro1, linkuyx, Litraxx, little-meow-meow, LittleBuilderJane, LittleNorthStar, LittleNyanCat, lizelive, ljm862, lmsnoise, localcc, lokachop, Lomcastar, LordCarve, LordEclipse, lucas, LucasTheDrgn, luckyshotpictures, LudwigVonChesterfield, luizwritescode, Lukasz825700516, luminight, lunarcomets, Lusatia, lvvova1, Lyndomen, lyroth001, lzimann, lzk228, M1tht1c, M3739, mac6na6na, MACMAN2003, Macoron, magicalus, magmodius, MagnusCrowe, maland1, malchanceux, MaloTV, ManelNavola, manelnavola, Mangohydra, marboww, Markek1, matt, Matz05, max, MaxNox7, maylokana, MehimoNemo, MeltedPixel, memeproof, MendaxxDev, Menshin, Mephisto72, MerrytheManokit, Mervill, metalgearsloth, MetalSage, MFMessage, mhamsterr, michaelcu, micheel665, mifia, MilenVolf, MilonPL, Minemoder5000, Minty642, minus1over12, Mirino97, mirrorcult, misandrie, MishaUnity, MissKay1994, MisterImp, MisterMecky, Mith-randalf, Mixelz, mjarduk, MjrLandWhale, mkanke-real, MLGTASTICa, mnva0, moderatelyaware, modern-nm, mokiros, momo, Moneyl, monotheonist, Moomoobeef, moony, Morb0, MossyGreySlope, mr-bo-jangles, Mr0maks, MrFippik, mrrobdemo, muburu, MureixloI, murolem, musicmanvr, MWKane, Myakot, Myctai, N3X15, nabegator, nails-n-tape, Nairodian, Naive817, NakataRin, namespace-Memory, Nannek, NazrinNya, neutrino-laser, NickPowers43, nikitosych, nikthechampiongr, Nimfar11, ninruB, Nirnael, NIXC, nkokic, NkoKirkto, nmajask, noctyrnal, noelkathegod, noirogen, nok-ko, NonchalantNoob, NoobyLegion, Nopey, not-gavnaed, notafet, notquitehadouken, NotSoDana, noudoit, noverd, Nox38, NuclearWinter, nukashimika, nuke-haus, NULL882, nullarmo, nyeogmi, Nylux, Nyranu, Nyxilath, och-och, OctoRocket, OldDanceJacket, OliverOtter, onesch, OneZerooo0, OnyxTheBrave, Orange-Winds, OrangeMoronage9622, Orsoniks, osjarw, Ostaf, othymer, OttoMaticode, Owai-Seek, packmore, paige404, paigemaeforrest, pali6, Palladinium, Pangogie, panzer-iv1, partyaddict, patrikturi, PaulRitter, peccneck, Peptide90, peptron1, perryprog, PeterFuto, PetMudstone, pewter-wiz, Pgriha, Phantom-Lily, pheenty, philingham, Phill101, Phooooooooooooooooooooooooooooooosphate, phunnyguy, PicklOH, PilgrimViis, Pill-U, pinkbat5, Piras314, Pireax, Pissachu, pissdemon, PixeltheAertistContrib, PixelTheKermit, PJB3005, Plasmaguy, plinyvic, Plykiya, poeMota, pofitlo, pointer-to-null, pok27, poklj, PolterTzi, PoorMansDreams, PopGamer45, portfiend, potato1234x, PotentiallyTom, PotRoastPiggy, Princess-Cheeseballs, ProfanedBane, PROG-MohamedDwidar, Prole0, ProPandaBear, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykana, psykzz, PuceTint, pumkin69, PuroSlavKing, PursuitInAshes, Putnam3145, py01, Pyrovi, qrtDaniil, qrwas, Quantum-cross, quatre, QueerNB, QuietlyWhisper, qwerltaz, Radezolid, RadioMull, Radosvik, Radrark, Rainbeon, Rainfey, Raitononai, Ramlik, RamZ, randy10122, Rane, Ranger6012, Rapidgame7, ravage123321, rbertoche, RedBookcase, Redfire1331, Redict, RedlineTriad, redmushie, RednoWCirabrab, ReeZer2, RemberBM, RemieRichards, RemTim, rene-descartes2021, Renlou, retequizzle, rhsvenson, rich-dunne, RieBi, riggleprime, RIKELOLDABOSS, rinary1, Rinkashikachi, riolume, rlebell33, RobbyTheFish, robinthedragon, Rockdtben, Rohesie, rok-povsic, rokudara-sen, rolfero, RomanNovo, rosieposieeee, Roudenn, router, ruddygreat, rumaks, RumiTiger, Ruzihm, S1rFl0, S1ss3l, Saakra, Sadie-silly, saga3152, saintmuntzer, Salex08, sam, samgithubaccount, Samuka-C, SaphireLattice, SapphicOverload, sarahon, sativaleanne, SaveliyM360, sBasalto, ScalyChimp, ScarKy0, schrodinger71, scrato, Scribbles0, scrivoy, scruq445, scuffedjays, ScumbagDog, SeamLesss, Segonist, semensponge, sephtasm, Serkket, sewerpig, SG6732, sh18rw, Shaddap1, ShadeAware, ShadowCommander, shadowtheprotogen546, shaeone, shampunj, shariathotpatrol, SharkSnake98, shibechef, Siginanto, SignalWalker, siigiil, silicon14wastaken, Simyon264, sirdragooon, Sirionaut, Sk1tch, SkaldetSkaeg, Skarletto, Skrauz, Skybailey-dev, Skyedra, SlamBamActionman, slarticodefast, Slava0135, sleepyyapril, slimmslamm, Slyfox333, Smugman, snebl, snicket, sniperchance, Snowni, snowsignal, SolidusSnek, solstar2, SonicHDC, SoulFN, SoulSloth, Soundwavesghost, soupkilove, southbridge-fur, sowelipililimute, Soydium, spacelizard, SpaceLizardSky, SpaceManiac, SpaceRox1244, SpaceyLady, Spangs04, spanky-spanky, Sparlight, spartak, SpartanKadence, spderman3333, SpeltIncorrectyl, Spessmann, SphiraI, SplinterGP, spoogemonster, sporekto, sporkyz, ssdaniel24, stalengd, stanberytrask, Stanislav4ix, StanTheCarpenter, starbuckss14, Stealthbomber16, stellar-novas, stewie523, stomf, Stop-Signs, stopbreaking, stopka-html, StrawberryMoses, Stray-Pyramid, strO0pwafel, Strol20, StStevens, Subversionary, sunbear-dev, supergdpwyl, superjj18, Supernorn, SweptWasTaken, SyaoranFox, Sybil, SYNCHRONIC, Szunti, t, Tainakov, takemysoult, tap, TaralGit, Taran, taurie, Tayrtahn, tday93, teamaki, TeenSarlacc, TekuNut, telyonok, TemporalOroboros, tentekal, terezi4real, Terraspark4941, texcruize, Tezzaide, TGODiamond, TGRCdev, tgrkzus, ThatGuyUSA, ThatOneGoblin25, thatrandomcanadianguy, TheArturZh, TheBlueYowie, thecopbennet, TheCze, TheDarkElites, thedraccx, TheEmber, TheFlyingSentry, TheIntoxicatedCat, thekilk, themias, theomund, TheProNoob678, TherapyGoth, ThereDrD0, TheShuEd, thetolbean, thevinter, TheWaffleJesus, thinbug0, ThunderBear2006, timothyteakettle, TimrodDX, timurjavid, tin-man-tim, TiniestShark, Titian3, tk-a369, tkdrg, tmtmtl30, ToastEnjoyer, Toby222, TokenStyle, Tollhouse, Toly65, tom-leys, tomasalves8, Tomeno, Tonydatguy, topy, tornado-technology, TornadoTechnology, tosatur, TotallyLemon, ToxicSonicFan04, Tr1bute, treytipton, trixxedbit, TrixxedHeart, tropicalhibi, truepaintgit, Truoizys, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, tyashley, Tyler-IN, TytosB, Tyzemol, UbaserB, ubis1, UBlueberry, uhbg, UKNOWH, UltimateJester, Unbelievable-Salmon, underscorex5, UnicornOnLSD, Unisol, unusualcrow, Uriende, UristMcDorf, user424242420, Utmanarn, Vaaankas, valentfingerov, valquaint, Varen, Vasilis, VasilisThePikachu, veliebm, Velken, VelonacepsCalyxEggs, veprolet, VerinSenpai, veritable-calamity, Veritius, Vermidia, vero5123, verslebas, vexerot, viceemargo, VigersRay, violet754, Visne, vitusveit, vlad, vlados1408, VMSolidus, vmzd, voidnull000, volotomite, volundr-, Voomra, Vordenburg, vorkathbruh, Vortebo, vulppine, wafehling, walksanatora, Warentan, WarMechanic, Watermelon914, weaversam8, wertanchik, whateverusername0, whatston3, widgetbeck, Will-Oliver-Br, Willhelm53, WilliamECrew, willicassi, Winkarst-cpu, wirdal, wixoaGit, WlarusFromDaSpace, Wolfkey-SomeoneElseTookMyUsername, wrexbe, WTCWR68, xeri7, xkreksx, xprospero, xRiriq, xsainteer, YanehCheck, yathxyz, Ygg01, YotaXP, youarereadingthis, YoungThugSS14, Yousifb26, youtissoum, yunii, YuriyKiss, yuriykiss, zach-hill, Zadeon, Zalycon, zamp, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zero, ZeroDiamond, ZeWaka, zHonys, zionnBE, ZNixian, Zokkie, ZoldorfTheWizard, zonespace27, Zylofan, Zymem, zzylex +0leshe, 0tito, 0x6273, 12rabbits, 1337dakota, 13spacemen, 154942, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 3nderall, 4310v343k, 4dplanner, 612git, 778b, 96flo, aaron, abadaba695, Ablankmann, abregado, Absolute-Potato, Absotively, achookh, Acruid, ActiveMammmoth, actually-reb, ada-please, adamsong, Adeinitas, adm2play, Admiral-Obvious-001, adrian, Adrian16199, Ady4ik, Aearo-Deepwater, Aerocrux, Aeshus, Aexolott, Aexxie, africalimedrop, afrokada, AftrLite, AgentSmithRadio, Agoichi, Ahion, aiden, Aidenkrz, Aisu9, ajcm, AJCM-git, AjexRose, Alekshhh, alexalexmax, alexkar598, AlexMorgan3817, alexum418, alexumandxgabriel08x, Alice4267, Alithsko, Alkheemist, alliephante, ALMv1, Alpaccalypse, Alpha-Two, AlphaQwerty, Altoids1, amatwiedle, amylizzle, ancientpower, Andre19926, AndrewEyeke, AndreyCamper, Anzarot121, ApolloVector, Appiah, ar4ill, Arcane-Waffle, archee1, ArchPigeon, ArchRBX, areitpog, Arendian, areyouconfused, arimah, Arkanic, ArkiveDev, armoks, Arteben, ArthurMousatov, ArtisticRoomba, artur, ArZarLordOfMango, as334, AsikKEsel, AsnDen, asperger-sind, aspiringLich, astriloqua, august-sun, AutoOtter, AverageNotDoingAnythingEnjoyer, avghdev, Awlod, AzzyIsNotHere, azzyisnothere, B-Kirill, B3CKDOOR, baa14453, BackeTako, Bakke, BananaFlambe, Baptr0b0t, BarryNorfolk, BasedUser, beck-thompson, beesterman, bellwetherlogic, ben, benbryant0, benev0, benjamin-burges, BGare, bhespiritu, bibbly, BigfootBravo, BIGZi0348, bingojohnson, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, blitzthesquishy, bloodrizer, Bloody2372, blueDev2, Boaz1111, BobdaBiscuit, BobTheSleder, boiled-water-tsar, Bokser815, bolantej, Booblesnoot42, Boolean-Buckeye, botanySupremist, brainfood1183, BramvanZijp, Brandon-Huu, BriBrooo, Bright0, brndd, bryce0110, BubblegumBlue, buletsponge, buntobaggins, bvelliquette, BWTCK, byondfuckery, c0rigin, c4llv07e, CaasGit, Caconym27, Calecute, Callmore, Camdot, capnsockless, CaptainMaru, captainsqrbeard, Carbonhell, Carolyn3114, Carou02, carteblanche4me, catdotjs, catlord, Catofquestionableethics, CatTheSystem, Centronias, Chaboricks, chairbender, Chaoticaa, Charlese2, charlie, chartman, ChaseFlorom, chavonadelal, Cheackraze, CheddaCheez, cheesePizza2, CheesePlated, Chief-Engineer, chillyconmor, christhirtle, chromiumboy, Chronophylos, Chubbicous, Chubbygummibear, Ciac32, ciaran, citrea, civilCornball, claustro305, Clement-O, clyf, Clyybber, CMDR-Piboy314, cnv41, coco, cohanna, Cohnway, Cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, Compilatron144, CookieMasterT, coolboy911, coolmankid12345, Coolsurf6, cooperwallace, corentt, CormosLemming, CrafterKolyan, crazybrain23, Crazydave91920, creadth, CrigCrag, CroilBird, Crotalus, CrudeWax, cryals, CrzyPotato, cubixthree, cutemoongod, Cyberboss, d34d10cc, DadeKuma, Daemon, daerSeebaer, dahnte, dakamakat, DamianX, dan, dangerrevolution, daniel-cr, DanSAussieITS, Daracke, Darkenson, DawBla, Daxxi3, dch-GH, de0rix, Deahaka, dean, DEATHB4DEFEAT, Deatherd, deathride58, DebugOk, Decappi, Decortex, Deeeeja, deepdarkdepths, DeepwaterCreations, Deerstop, degradka, Delete69, deltanedas, DenisShvalov, DerbyX, derek, dersheppard, Deserty0, Detintinto, DevilishMilk, devinschubert14, dexlerxd, dffdff2423, DieselMohawk, digitalic, Dimastra, DinnerCalzone, DinoWattz, Disp-Dev, DisposableCrewmember42, dissidentbullet, DjfjdfofdjfjD, doc-michael, docnite, Doctor-Cpu, DogZeroX, dolgovmi, dontbetank, Doomsdrayk, Doru991, DoubleRiceEddiedd, DoutorWhite, DR-DOCTOR-EVIL-EVIL, Dragonjspider, dragonryan06, drakewill-CRL, Drayff, dreamlyjack, DrEnzyme, dribblydrone, DrMelon, drongood12, DrSingh, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, DuckManZach, Duddino, dukevanity, duskyjay, Dutch-VanDerLinde, dvir001, dylanstrategie, dylanwhittingham, Dynexust, Easypoller, echo, eclips_e, eden077, EEASAS, Efruit, efzapa, Ekkosangen, ElectroSR, elsie, elthundercloud, Elysium206, Emisse, emmafornash, EmoGarbage404, Endecc, EnrichedCaramel, Entvari, eoineoineoin, ephememory, eris, erohrs2, ERORR404V1, Errant-4, ertanic, esguard, estacaoespacialpirata, eugene, ewokswagger, exincore, exp111, f0x-n3rd, FacePluslll, Fahasor, FairlySadPanda, farrellka-dev, FATFSAAM2, Feluk6174, ficcialfaint, Fiftyllama, Fildrance, FillerVK, FinnishPaladin, firenamefn, Firewars763, FirinMaLazors, Fishfish458, fl-oz, Flareguy, flashgnash, FlipBrooke, FluffiestFloof, FluffMe, FluidRock, flymo5678, foboscheshir, FoLoKe, fooberticus, ForestNoises, forgotmyotheraccount, forkeyboards, forthbridge, Fortune117, foxhorn, freeman2651, freeze2222, frobnic8, Froffy025, Fromoriss, froozigiusz, FrostMando, FrostRibbon, Funce, FungiFellow, FunTust, Futuristic-OK, GalacticChimp, gamer3107, Gamewar360, gansulalan, GaussiArson, Gaxeer, gbasood, gcoremans, Geekyhobo, genderGeometries, GeneralGaws, Genkail, Gentleman-Bird, geraeumig, Ghagliiarghii, Git-Nivrak, githubuser508, gituhabu, GlassEclipse, GnarpGnarp, GNF54, godisdeadLOL, goet, GoldenCan, Goldminermac, Golinth, golubgik, GoodWheatley, Gorox221, gradientvera, graevy, GraniteSidewalk, GreaseMonk, greenrock64, GreyMario, GrownSamoyedDog, GTRsound, gusxyz, Gyrandola, h3half, hamurlik, Hanzdegloker, HappyRoach, Hardly3D, harikattar, he1acdvv, Hebi, Helix-ctrl, helm4142, Henry, HerCoyote23, HighTechPuddle, Hitlinemoss, hiucko, hivehum, Hmeister-fake, Hmeister-real, Hobbitmax, hobnob, HoidC, Holinka4ever, holyssss, HoofedEar, Hoolny, hord-brayden, Hoshizora, Hreno, Hrosts, htmlsystem, hubismal, Hugal31, Huxellberger, Hyenh, hyperb1, hyperDelegate, hyphenationc, i-justuser-i, iaada, iacore, IamVelcroboy, Ian321, icekot8, icesickleone, iczero, iglov, IgorAnt028, igorsaux, ike709, illersaver, Illiux, Ilushkins33, Ilya246, IlyaElDunaev, imatsoup, IMCB, impubbi, imrenq, imweax, indeano, Injazz, Insineer, insoPL, IntegerTempest, Interrobang01, Intoxicating-Innocence, IProduceWidgets, itsmethom, Itzbenz, iztokbajcar, Jackal298, Jackrost, jacksonzck, Jacktastic09, Jackw2As, jacob, jamessimo, janekvap, Jark255, Jarmer123, Jaskanbe, JasperJRoth, jbox144, JCGWE30, jerryimmouse, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JimGamemaster, jimmy12or, JIPDawg, jjtParadox, jkwookee, jmcb, JohnGinnane, johnku1, Jophire, joshepvodka, JpegOfAFrog, jproads, JrInventor05, Jrpl, jukereise, juliangiebel, JustArt1m, JustCone14, justdie12, justin, justintether, JustinTrotter, JustinWinningham, justtne, K-Dynamic, k3yw, Kadeo64, Kaga-404, kaiserbirch, KaiShibaa, kalane15, kalanosh, KamTheSythe, Kanashi-Panda, katzenminer, kbailey-git, Keelin, Keer-Sar, KEEYNy, keikiru, Kelrak, kerisargit, keronshb, KIBORG04, KieueCaprie, Killerqu00, Kimpes, KingFroozy, kira-er, kiri-yoshikage, Kirillcas, Kirus59, Kistras, Kit0vras, KittenColony, Kittygyat, klaypexx, Kmc2000, Ko4ergaPunk, kognise, kokoc9n, komunre, KonstantinAngelov, kontakt, kosticia, koteq, kotobdev, Kowlin, KrasnoshchekovPavel, Krosus777, Krunklehorn, Kupie, kxvvv, kyupolaris, kzhanik, LaCumbiaDelCoronavirus, lajolico, Lamrr, lanedon, LankLTE, laok233, lapatison, larryrussian, lawdog4817, Lazzi0706, leander-0, leonardo-dabepis, leonidussaks, leonsfriedrich, LeoSantich, lettern, LetterN, Level10Cybermancer, LEVELcat, lever1209, LevitatingTree, Lgibb18, lgruthes, LightVillet, liltenhead, linkbro1, linkuyx, Litraxx, little-meow-meow, LittleBuilderJane, LittleNorthStar, LittleNyanCat, lizelive, ljm862, lmsnoise, localcc, lokachop, lolman360, Lomcastar, LordCarve, LordEclipse, lucas, LucasTheDrgn, luckyshotpictures, LudwigVonChesterfield, luizwritescode, Lukasz825700516, luminight, lunarcomets, Lusatia, Luxeator, lvvova1, Lyndomen, lyroth001, lzimann, lzk228, M1tht1c, M3739, M87S, mac6na6na, MACMAN2003, Macoron, magicalus, magmodius, MagnusCrowe, maland1, malchanceux, MaloTV, manelnavola, ManelNavola, Mangohydra, marboww, Markek1, marlyn, matt, Matz05, max, MaxNox7, maylokana, MehimoNemo, MeltedPixel, memeproof, MendaxxDev, Menshin, Mephisto72, MerrytheManokit, Mervill, metalgearsloth, MetalSage, MFMessage, mhamsterr, michaelcu, micheel665, mifia, MilenVolf, MilonPL, Minemoder5000, Minty642, minus1over12, Mirino97, mirrorcult, misandrie, MishaUnity, MissKay1994, MisterImp, MisterMecky, Mith-randalf, Mixelz, mjarduk, MjrLandWhale, mkanke-real, MLGTASTICa, mnva0, moderatelyaware, modern-nm, mokiros, momo, Moneyl, monotheonist, Moomoobeef, moony, Morb0, MossyGreySlope, mr-bo-jangles, Mr0maks, MrFippik, mrrobdemo, muburu, MureixloI, murolem, musicmanvr, MWKane, Myakot, Myctai, N3X15, nabegator, nails-n-tape, Nairodian, Naive817, NakataRin, namespace-Memory, Nannek, NazrinNya, neutrino-laser, NickPowers43, nikitosych, nikthechampiongr, Nimfar11, ninruB, Nirnael, NIXC, nkokic, NkoKirkto, nmajask, noctyrnal, noelkathegod, noirogen, nok-ko, NonchalantNoob, NoobyLegion, Nopey, not-gavnaed, notafet, notquitehadouken, NotSoDana, noudoit, noverd, Nox38, NuclearWinter, nukashimika, nuke-haus, NULL882, nullarmo, nyeogmi, Nylux, Nyranu, Nyxilath, och-och, OctoRocket, OldDanceJacket, OliverOtter, onesch, OneZerooo0, OnyxTheBrave, Orange-Winds, OrangeMoronage9622, Orsoniks, osjarw, Ostaf, othymer, OttoMaticode, Owai-Seek, packmore, paige404, paigemaeforrest, pali6, Palladinium, Pangogie, panzer-iv1, partyaddict, patrikturi, PaulRitter, peccneck, Peptide90, peptron1, perryprog, PeterFuto, PetMudstone, pewter-wiz, pgraycs, Pgriha, Phantom-Lily, pheenty, philingham, Phill101, Phooooooooooooooooooooooooooooooosphate, phunnyguy, PicklOH, PilgrimViis, Pill-U, pinkbat5, Piras314, Pireax, Pissachu, pissdemon, PixeltheAertistContrib, PixelTheKermit, PJB3005, Plasmaguy, plinyvic, Plykiya, poeMota, pofitlo, pointer-to-null, pok27, poklj, PolterTzi, PoorMansDreams, PopGamer45, portfiend, potato1234x, PotentiallyTom, PotRoastPiggy, Princess-Cheeseballs, ProfanedBane, PROG-MohamedDwidar, Prole0, ProPandaBear, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykana, psykzz, PuceTint, pumkin69, PuroSlavKing, PursuitInAshes, Putnam3145, py01, Pyrovi, qrtDaniil, qrwas, Quantum-cross, quatre, QueerNB, QuietlyWhisper, qwerltaz, Radezolid, RadioMull, Radosvik, Radrark, Rainbeon, Rainfey, Raitononai, Ramlik, RamZ, randy10122, Rane, Ranger6012, Rapidgame7, ravage123321, rbertoche, RedBookcase, Redfire1331, Redict, RedlineTriad, redmushie, RednoWCirabrab, ReeZer2, RemberBM, RemieRichards, RemTim, rene-descartes2021, Renlou, retequizzle, rhsvenson, rich-dunne, RieBi, riggleprime, RIKELOLDABOSS, rinary1, Rinkashikachi, riolume, rlebell33, RobbyTheFish, robinthedragon, Rockdtben, Rohesie, rok-povsic, rokudara-sen, rolfero, RomanNovo, rosieposieeee, Roudenn, router, ruddygreat, rumaks, RumiTiger, Ruzihm, S1rFl0, S1ss3l, Saakra, Sadie-silly, saga3152, saintmuntzer, Salex08, sam, samgithubaccount, Samuka-C, SaphireLattice, SapphicOverload, sarahon, sativaleanne, SaveliyM360, sBasalto, ScalyChimp, ScarKy0, schrodinger71, scrato, Scribbles0, scrivoy, scruq445, scuffedjays, ScumbagDog, SeamLesss, Segonist, semensponge, sephtasm, ser1-1y, Serkket, sewerpig, SG6732, sh18rw, Shaddap1, ShadeAware, ShadowCommander, shadowtheprotogen546, shaeone, shampunj, shariathotpatrol, SharkSnake98, shibechef, Siginanto, SignalWalker, siigiil, silicon14wastaken, Simyon264, sirdragooon, Sirionaut, Sk1tch, SkaldetSkaeg, Skarletto, Skrauz, Skybailey-dev, Skyedra, SlamBamActionman, slarticodefast, Slava0135, sleepyyapril, slimmslamm, Slyfox333, Smugman, snebl, snicket, sniperchance, Snowni, snowsignal, SolidusSnek, solstar2, SonicHDC, SoulFN, SoulSloth, Soundwavesghost, soupkilove, southbridge-fur, sowelipililimute, Soydium, spacelizard, SpaceLizardSky, SpaceManiac, SpaceRox1244, SpaceyLady, Spangs04, spanky-spanky, Sparlight, spartak, SpartanKadence, spderman3333, SpeltIncorrectyl, Spessmann, SphiraI, SplinterGP, spoogemonster, sporekto, sporkyz, ssdaniel24, stalengd, stanberytrask, Stanislav4ix, StanTheCarpenter, starbuckss14, Stealthbomber16, stellar-novas, stewie523, stomf, Stop-Signs, stopbreaking, stopka-html, StrawberryMoses, Stray-Pyramid, strO0pwafel, Strol20, StStevens, Subversionary, sunbear-dev, supergdpwyl, superjj18, Supernorn, SweptWasTaken, SyaoranFox, Sybil, SYNCHRONIC, Szunti, t, Tainakov, takemysoult, taonewt, tap, TaralGit, Taran, taurie, Tayrtahn, tday93, teamaki, TeenSarlacc, TekuNut, telyonok, TemporalOroboros, tentekal, terezi4real, Terraspark4941, texcruize, Tezzaide, TGODiamond, TGRCdev, tgrkzus, ThatGuyUSA, ThatOneGoblin25, thatrandomcanadianguy, TheArturZh, TheBlueYowie, thecopbennet, TheCze, TheDarkElites, thedraccx, TheEmber, TheFlyingSentry, TheIntoxicatedCat, thekilk, themias, theomund, TheProNoob678, TherapyGoth, ThereDrD0, TheShuEd, thetolbean, thevinter, TheWaffleJesus, thinbug0, ThunderBear2006, timothyteakettle, TimrodDX, timurjavid, tin-man-tim, TiniestShark, Titian3, tk-a369, tkdrg, tmtmtl30, ToastEnjoyer, Toby222, TokenStyle, Tollhouse, Toly65, tom-leys, tomasalves8, Tomeno, Tonydatguy, topy, tornado-technology, TornadoTechnology, tosatur, TotallyLemon, ToxicSonicFan04, Tr1bute, treytipton, trixxedbit, TrixxedHeart, tropicalhibi, truepaintgit, Truoizys, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, tyashley, Tyler-IN, TytosB, Tyzemol, UbaserB, ubis1, UBlueberry, uhbg, UKNOWH, UltimateJester, Unbelievable-Salmon, underscorex5, UnicornOnLSD, Unisol, unusualcrow, Uriende, UristMcDorf, user424242420, Utmanarn, Vaaankas, valentfingerov, valquaint, Varen, Vasilis, VasilisThePikachu, veliebm, Velken, VelonacepsCalyxEggs, veprolet, VerinSenpai, veritable-calamity, Veritius, Vermidia, vero5123, verslebas, vexerot, viceemargo, VigersRay, violet754, Visne, vitusveit, vlad, vlados1408, VMSolidus, vmzd, voidnull000, volotomite, volundr-, Voomra, Vordenburg, vorkathbruh, Vortebo, vulppine, wafehling, walksanatora, Warentan, WarMechanic, Watermelon914, weaversam8, wertanchik, whateverusername0, whatston3, widgetbeck, Will-Oliver-Br, Willhelm53, WilliamECrew, willicassi, Winkarst-cpu, wirdal, wixoaGit, WlarusFromDaSpace, Wolfkey-SomeoneElseTookMyUsername, wrexbe, WTCWR68, xeri7, xkreksx, xprospero, xRiriq, xsainteer, YanehCheck, yathxyz, Ygg01, YotaXP, youarereadingthis, YoungThugSS14, Yousifb26, youtissoum, yunii, yuriykiss, YuriyKiss, zach-hill, Zadeon, Zalycon, zamp, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zero, ZeroDiamond, ZeWaka, zHonys, zionnBE, ZNixian, Zokkie, ZoldorfTheWizard, zonespace27, Zylofan, Zymem, zzylex From 55335cce0f3bcd166ee3ac095967b635ed5ea3b7 Mon Sep 17 00:00:00 2001 From: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Date: Sun, 10 Aug 2025 10:49:29 -0700 Subject: [PATCH 04/40] Crawling Fixes 1: Dragons and Borgs can't do the worm. (#39084) * Init Commit * Remove unused code, fix stun visuals bug * Update Content.Shared/Stunnable/SharedStunSystem.cs * Some initial changes * first batch of changes * Commit * One line cleanup * KnockdownStatusEffect ain't worth it. * Fix 2 bugs * Fixes * Remove that actually, * Commit * Better solution * Alright final commit I think * Add better remarks * How the fuck did this not get pushed??? * Wait no why was my ryder trying to push that??? I didn't make that change! DON'T DO THAT!!! * Review * Don't log that --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> --- .../Shuttles/Systems/ShuttleSystem.Impact.cs | 2 +- .../Components/StunOnCollideComponent.cs | 100 ++++++----- .../Stunnable/Systems/StunOnCollideSystem.cs | 75 +++++---- Content.Shared/Slippery/SlipperySystem.cs | 3 +- .../Standing/StandingStateComponent.cs | 17 +- .../Standing/StandingStateSystem.cs | 13 +- Content.Shared/Stunnable/CrawlerComponent.cs | 40 +++++ .../KnockdownStatusEffectComponent.cs | 20 ++- .../Stunnable/SharedStunSystem.Knockdown.cs | 148 +++++++++++------ Content.Shared/Stunnable/SharedStunSystem.cs | 157 +++++++++++++----- Content.Shared/Stunnable/StunnableEvents.cs | 4 +- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 1 + .../Prototypes/Entities/Mobs/Species/base.yml | 1 + .../Entities/StatusEffects/movement.yml | 7 - 14 files changed, 376 insertions(+), 212 deletions(-) create mode 100644 Content.Shared/Stunnable/CrawlerComponent.cs diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs index e78a17e180..b5adeb04db 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs @@ -249,7 +249,7 @@ public sealed partial class ShuttleSystem if (direction.LengthSquared() > minsq) { - _stuns.TryUpdateKnockdownDuration(uid, knockdownTime); + _stuns.TryCrawling(uid, knockdownTime); _throwing.TryThrow(uid, direction, physics, Transform(uid), _projQuery, direction.Length(), playSound: false); } else diff --git a/Content.Server/Stunnable/Components/StunOnCollideComponent.cs b/Content.Server/Stunnable/Components/StunOnCollideComponent.cs index 363fb78b75..cbf6b17af8 100644 --- a/Content.Server/Stunnable/Components/StunOnCollideComponent.cs +++ b/Content.Server/Stunnable/Components/StunOnCollideComponent.cs @@ -1,58 +1,66 @@ -namespace Content.Server.Stunnable.Components +using Content.Server.Stunnable.Systems; + +namespace Content.Server.Stunnable.Components; + +/// +/// Adds stun when it collides with an entity +/// +[RegisterComponent, Access(typeof(StunOnCollideSystem))] +public sealed partial class StunOnCollideComponent : Component { + // TODO: Can probably predict this. + /// - /// Adds stun when it collides with an entity + /// How long we are stunned for /// - [RegisterComponent, Access(typeof(StunOnCollideSystem))] - public sealed partial class StunOnCollideComponent : Component - { - // TODO: Can probably predict this. + [DataField] + public TimeSpan StunAmount; - /// - /// How long we are stunned for - /// - [DataField] - public TimeSpan StunAmount; + /// + /// How long we are knocked down for + /// + [DataField] + public TimeSpan KnockdownAmount; - /// - /// How long we are knocked down for - /// - [DataField] - public TimeSpan KnockdownAmount; + /// + /// How long we are slowed down for + /// + [DataField] + public TimeSpan SlowdownAmount; - /// - /// How long we are slowed down for - /// - [DataField] - public TimeSpan SlowdownAmount; + /// + /// Multiplier for a mob's walking speed + /// + [DataField] + public float WalkSpeedModifier = 1f; - /// - /// Multiplier for a mob's walking speed - /// - [DataField] - public float WalkSpeedModifier = 1f; + /// + /// Multiplier for a mob's sprinting speed + /// + [DataField] + public float SprintSpeedModifier = 1f; - /// - /// Multiplier for a mob's sprinting speed - /// - [DataField] - public float SprintSpeedModifier = 1f; + /// + /// Refresh Stun or Slowdown on hit + /// + [DataField] + public bool Refresh = true; - /// - /// Refresh Stun or Slowdown on hit - /// - [DataField] - public bool Refresh = true; + /// + /// Should the entity try and stand automatically after being knocked down? + /// + [DataField] + public bool AutoStand = true; - /// - /// Should the entity try and stand automatically after being knocked down? - /// - [DataField] - public bool AutoStand = true; + /// + /// Should the entity drop their items upon first being knocked down? + /// + [DataField] + public bool Drop = true; - /// - /// Fixture we track for the collision. - /// - [DataField("fixture")] public string FixtureID = "projectile"; - } + /// + /// Fixture we track for the collision. + /// + [DataField("fixture")] public string FixtureID = "projectile"; } + diff --git a/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs b/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs index 18c386d4ac..2257812da1 100644 --- a/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs +++ b/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs @@ -4,47 +4,60 @@ using JetBrains.Annotations; using Content.Shared.Throwing; using Robust.Shared.Physics.Events; -namespace Content.Server.Stunnable +namespace Content.Server.Stunnable.Systems; + +[UsedImplicitly] +internal sealed class StunOnCollideSystem : EntitySystem { - [UsedImplicitly] - internal sealed class StunOnCollideSystem : EntitySystem + [Dependency] private readonly StunSystem _stunSystem = default!; + [Dependency] private readonly MovementModStatusSystem _movementMod = default!; + + public override void Initialize() { - [Dependency] private readonly StunSystem _stunSystem = default!; - [Dependency] private readonly MovementModStatusSystem _movementMod = default!; + base.Initialize(); - public override void Initialize() + SubscribeLocalEvent(HandleCollide); + SubscribeLocalEvent(HandleThrow); + } + + private void TryDoCollideStun(Entity ent, EntityUid target) + { + _stunSystem.TryKnockdown(target, ent.Comp.KnockdownAmount, ent.Comp.Refresh, ent.Comp.AutoStand, ent.Comp.Drop); + + if (ent.Comp.Refresh) { - base.Initialize(); - SubscribeLocalEvent(HandleCollide); - SubscribeLocalEvent(HandleThrow); - } - - private void TryDoCollideStun(EntityUid uid, StunOnCollideComponent component, EntityUid target) - { - _stunSystem.TryUpdateStunDuration(target, component.StunAmount); - - _stunSystem.TryKnockdown(target, component.KnockdownAmount, component.Refresh, component.AutoStand, force: true); - + _stunSystem.TryUpdateStunDuration(target, ent.Comp.StunAmount); _movementMod.TryUpdateMovementSpeedModDuration( target, MovementModStatusSystem.TaserSlowdown, - component.SlowdownAmount, - component.WalkSpeedModifier, - component.SprintSpeedModifier + ent.Comp.SlowdownAmount, + ent.Comp.WalkSpeedModifier, + ent.Comp.SprintSpeedModifier ); } - - private void HandleCollide(EntityUid uid, StunOnCollideComponent component, ref StartCollideEvent args) + else { - if (args.OurFixtureId != component.FixtureID) - return; - - TryDoCollideStun(uid, component, args.OtherEntity); - } - - private void HandleThrow(EntityUid uid, StunOnCollideComponent component, ThrowDoHitEvent args) - { - TryDoCollideStun(uid, component, args.Target); + _stunSystem.TryAddStunDuration(target, ent.Comp.StunAmount); + _movementMod.TryAddMovementSpeedModDuration( + target, + MovementModStatusSystem.TaserSlowdown, + ent.Comp.SlowdownAmount, + ent.Comp.WalkSpeedModifier, + ent.Comp.SprintSpeedModifier + ); } } + + private void HandleCollide(Entity ent, ref StartCollideEvent args) + { + if (args.OurFixtureId != ent.Comp.FixtureID) + return; + + TryDoCollideStun(ent, args.OtherEntity); + } + + private void HandleThrow(Entity ent, ref ThrowDoHitEvent args) + { + TryDoCollideStun(ent, args.Target); + } } diff --git a/Content.Shared/Slippery/SlipperySystem.cs b/Content.Shared/Slippery/SlipperySystem.cs index 6a0e96888a..51bbd2bea0 100644 --- a/Content.Shared/Slippery/SlipperySystem.cs +++ b/Content.Shared/Slippery/SlipperySystem.cs @@ -149,7 +149,8 @@ public sealed class SlipperySystem : EntitySystem _audio.PlayPredicted(component.SlipSound, other, other); } - _stun.TryKnockdown(other, component.SlipData.KnockdownTime, true, force: true); + // Slippery is so tied to knockdown that we really just need to force it here. + _stun.TryKnockdown(other, component.SlipData.KnockdownTime, force: true); _adminLogger.Add(LogType.Slip, LogImpact.Low, $"{ToPrettyString(other):mob} slipped on collision with {ToPrettyString(uid):entity}"); } diff --git a/Content.Shared/Standing/StandingStateComponent.cs b/Content.Shared/Standing/StandingStateComponent.cs index c6252d969a..8ed00ce23e 100644 --- a/Content.Shared/Standing/StandingStateComponent.cs +++ b/Content.Shared/Standing/StandingStateComponent.cs @@ -15,23 +15,10 @@ namespace Content.Shared.Standing public bool Standing { get; set; } = true; /// - /// Time it takes us to stand up + /// Friction modifier applied to an entity in the downed state. /// [DataField, AutoNetworkedField] - public TimeSpan StandTime = TimeSpan.FromSeconds(2); - - /// - /// Default Friction modifier for knocked down players. - /// Makes them accelerate and deccelerate slower. - /// - [DataField, AutoNetworkedField] - public float FrictionModifier = 0.4f; - - /// - /// Base modifier to the maximum movement speed of a knocked down mover. - /// - [DataField, AutoNetworkedField] - public float SpeedModifier = 0.3f; + public float DownFrictionMod = 0.4f; /// /// List of fixtures that had their collision mask changed when the entity was downed. diff --git a/Content.Shared/Standing/StandingStateSystem.cs b/Content.Shared/Standing/StandingStateSystem.cs index 7177568163..f965e0ae7c 100644 --- a/Content.Shared/Standing/StandingStateSystem.cs +++ b/Content.Shared/Standing/StandingStateSystem.cs @@ -23,7 +23,6 @@ public sealed class StandingStateSystem : EntitySystem base.Initialize(); SubscribeLocalEvent(OnMobCollide); SubscribeLocalEvent(OnMobTargetCollide); - SubscribeLocalEvent(OnRefreshMovementSpeedModifiers); SubscribeLocalEvent(OnRefreshFrictionModifiers); SubscribeLocalEvent(OnTileFriction); } @@ -44,25 +43,19 @@ public sealed class StandingStateSystem : EntitySystem } } - private void OnRefreshMovementSpeedModifiers(Entity entity, ref RefreshMovementSpeedModifiersEvent args) - { - if (!entity.Comp.Standing) - args.ModifySpeed(entity.Comp.FrictionModifier); - } - private void OnRefreshFrictionModifiers(Entity entity, ref RefreshFrictionModifiersEvent args) { if (entity.Comp.Standing) return; - args.ModifyFriction(entity.Comp.FrictionModifier); - args.ModifyAcceleration(entity.Comp.FrictionModifier); + args.ModifyFriction(entity.Comp.DownFrictionMod); + args.ModifyAcceleration(entity.Comp.DownFrictionMod); } private void OnTileFriction(Entity entity, ref TileFrictionEvent args) { if (!entity.Comp.Standing) - args.Modifier *= entity.Comp.FrictionModifier; + args.Modifier *= entity.Comp.DownFrictionMod; } public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) diff --git a/Content.Shared/Stunnable/CrawlerComponent.cs b/Content.Shared/Stunnable/CrawlerComponent.cs new file mode 100644 index 0000000000..d969117344 --- /dev/null +++ b/Content.Shared/Stunnable/CrawlerComponent.cs @@ -0,0 +1,40 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Stunnable; + +/// +/// This is used to denote that an entity can crawl. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStunSystem))] +public sealed partial class CrawlerComponent : Component +{ + /// + /// Default time we will be knocked down for. + /// + [DataField, AutoNetworkedField] + public TimeSpan DefaultKnockedDuration { get; set; } = TimeSpan.FromSeconds(0.5); + + /// + /// Minimum damage taken to extend our knockdown timer by the default time. + /// + [DataField, AutoNetworkedField] + public float KnockdownDamageThreshold = 5f; + + /// + /// Time it takes us to stand up + /// + [DataField, AutoNetworkedField] + public TimeSpan StandTime = TimeSpan.FromSeconds(2); + + /// + /// Base modifier to the maximum movement speed of a knocked down mover. + /// + [DataField, AutoNetworkedField] + public float SpeedModifier = 0.4f; + + /// + /// Friction modifier applied to an entity in the downed state. + /// + [DataField, AutoNetworkedField] + public float FrictionModifier = 1f; +} diff --git a/Content.Shared/Stunnable/KnockdownStatusEffectComponent.cs b/Content.Shared/Stunnable/KnockdownStatusEffectComponent.cs index 79b2fb695b..b4805511b2 100644 --- a/Content.Shared/Stunnable/KnockdownStatusEffectComponent.cs +++ b/Content.Shared/Stunnable/KnockdownStatusEffectComponent.cs @@ -6,4 +6,22 @@ namespace Content.Shared.Stunnable; /// Knockdown as a status effect. /// [RegisterComponent, NetworkedComponent, Access(typeof(SharedStunSystem))] -public sealed partial class KnockdownStatusEffectComponent : Component; +public sealed partial class KnockdownStatusEffectComponent : Component +{ + /// + /// Should this knockdown only affect crawlers? + /// + /// + /// If your status effect doesn't come paired with + /// Or if your status effect doesn't whitelist itself to only those with + /// Then you need to set this to true. + /// + [DataField] + public bool Crawl; + + /// + /// Should we drop items when we fall? + /// + [DataField] + public bool Drop = true; +} diff --git a/Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs b/Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs index 26e8e47f03..7917f10bd5 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs @@ -25,13 +25,7 @@ namespace Content.Shared.Stunnable; /// public abstract partial class SharedStunSystem { - // TODO: Both of these constants need to be moved to a component somewhere, and need to be tweaked for balance... - // We don't always have standing state available when these are called so it can't go there - // Maybe I can pass the values to KnockedDownComponent from Standing state on Component init? - // Default knockdown timer - public static readonly TimeSpan DefaultKnockedDuration = TimeSpan.FromSeconds(0.5f); - // Minimum damage taken to refresh our knockdown timer to the default duration - public static readonly float KnockdownDamageThreshold = 5f; + private EntityQuery _crawlerQuery; [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; @@ -43,6 +37,8 @@ public abstract partial class SharedStunSystem private void InitializeKnockdown() { + _crawlerQuery = GetEntityQuery(); + SubscribeLocalEvent(OnRejuvenate); // Startup and Shutdown @@ -61,8 +57,9 @@ public abstract partial class SharedStunSystem // DoAfter event subscriptions SubscribeLocalEvent(OnStandDoAfter); - // Knockdown Extenders - SubscribeLocalEvent(OnDamaged); + // Crawling + SubscribeLocalEvent(OnKnockdownRefresh); + SubscribeLocalEvent(OnDamaged); // Handling Alternative Inputs SubscribeAllEvent(OnForceStandup); @@ -81,6 +78,7 @@ public abstract partial class SharedStunSystem while (query.MoveNext(out var uid, out var knockedDown)) { + // If it's null then we don't want to stand up if (!knockedDown.AutoStand || knockedDown.DoAfterId.HasValue || knockedDown.NextUpdate > GameTiming.CurTime) continue; @@ -157,7 +155,7 @@ public abstract partial class SharedStunSystem /// Entity who's knockdown time we're updating. /// The time we're updating with. /// Whether we're resetting the timer or adding to the current timer. - public void UpdateKnockdownTime(Entity entity, TimeSpan time, bool refresh = true) + public void UpdateKnockdownTime(Entity entity, TimeSpan time, bool refresh = true) { if (refresh) RefreshKnockdownTime(entity, time); @@ -174,6 +172,7 @@ public abstract partial class SharedStunSystem { entity.Comp.NextUpdate = time; DirtyField(entity, entity.Comp, nameof(KnockedDownComponent.NextUpdate)); + Alerts.ShowAlert(entity, KnockdownAlert, null, (GameTiming.CurTime, entity.Comp.NextUpdate)); } /// @@ -182,11 +181,14 @@ public abstract partial class SharedStunSystem /// /// Entity whose timer we're updating /// The time we want them to be knocked down for. - public void RefreshKnockdownTime(Entity entity, TimeSpan time) + public void RefreshKnockdownTime(Entity entity, TimeSpan time) { + if (!Resolve(entity, ref entity.Comp, false)) + return; + var knockedTime = GameTiming.CurTime + time; if (entity.Comp.NextUpdate < knockedTime) - SetKnockdownTime(entity, knockedTime); + SetKnockdownTime((entity, entity.Comp), knockedTime); } /// @@ -194,35 +196,20 @@ public abstract partial class SharedStunSystem /// /// Entity whose timer we're updating /// The time we want to add to their knocked down timer. - public void AddKnockdownTime(Entity entity, TimeSpan time) + public void AddKnockdownTime(Entity entity, TimeSpan time) { + if (!Resolve(entity, ref entity.Comp, false)) + return; + if (entity.Comp.NextUpdate < GameTiming.CurTime) { - SetKnockdownTime(entity, GameTiming.CurTime + time); + SetKnockdownTime((entity, entity.Comp), GameTiming.CurTime + time); return; } entity.Comp.NextUpdate += time; DirtyField(entity, entity.Comp, nameof(KnockedDownComponent.NextUpdate)); - } - - /// - /// Checks if an entity is able to stand, returns true if it can, returns false if it cannot. - /// - /// Entity we're checking - /// Returns whether the entity is able to stand - public bool CanStand(Entity entity) - { - if (entity.Comp.NextUpdate > GameTiming.CurTime) - return false; - - if (!Blocker.CanMove(entity)) - return false; - - var ev = new StandUpAttemptEvent(); - RaiseLocalEvent(entity, ref ev); - - return !ev.Cancelled; + Alerts.ShowAlert(entity, KnockdownAlert, null, (GameTiming.CurTime, entity.Comp.NextUpdate)); } #endregion @@ -237,29 +224,55 @@ public abstract partial class SharedStunSystem if (playerSession.AttachedEntity is not { Valid: true } playerEnt || !Exists(playerEnt)) return; - if (!TryComp(playerEnt, out var component)) + ToggleKnockdown(playerEnt); + } + + /// + /// Handles an entity trying to make itself fall down. + /// + /// Entity who is trying to fall down + private void ToggleKnockdown(Entity entity) + { + // We resolve here instead of using TryCrawling to be extra sure someone without crawler can't stand up early. + if (!Resolve(entity, ref entity.Comp1, false)) + return; + + if (!Resolve(entity, ref entity.Comp2, false)) { - TryKnockdown(playerEnt, DefaultKnockedDuration, true, false, false); // TODO: Unhardcode these numbers + TryKnockdown(entity.Owner, entity.Comp1.DefaultKnockedDuration, true, false, false); return; } - var stand = !component.DoAfterId.HasValue; - SetAutoStand(playerEnt, stand); + var stand = !entity.Comp2.DoAfterId.HasValue; + SetAutoStand((entity, entity.Comp2), stand); - if (!stand || !TryStanding(playerEnt)) - CancelKnockdownDoAfter((playerEnt, component)); + if (!stand || !TryStanding((entity, entity.Comp2))) + CancelKnockdownDoAfter((entity, entity.Comp2)); } - public bool TryStanding(Entity entity) + public bool TryStanding(Entity entity) { // If we aren't knocked down or can't be knocked down, then we did technically succeed in standing up - if (!Resolve(entity, ref entity.Comp1, ref entity.Comp2, false)) + if (!Resolve(entity, ref entity.Comp, false)) return true; - if (!TryStand((entity.Owner, entity.Comp1))) + if (!KnockdownOver((entity, entity.Comp))) return false; - var ev = new GetStandUpTimeEvent(entity.Comp2.StandTime); + if (!_crawlerQuery.TryComp(entity, out var crawler)) + { + // If we can't crawl then just have us sit back up... + // In case you're wondering, the KnockdownOverCheck, returns if we're able to move, so if next update is null. + // An entity that can't crawl will stand up the next time they can move, which should prevent moving while knocked down. + RemComp(entity); + _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(entity):user} has stood up from knockdown."); + return true; + } + + if (!TryStand((entity, entity.Comp))) + return false; + + var ev = new GetStandUpTimeEvent(crawler.StandTime); RaiseLocalEvent(entity, ref ev); var doAfterArgs = new DoAfterArgs(EntityManager, entity, ev.DoAfterTime, new TryStandDoAfterEvent(), entity, entity) @@ -275,11 +288,19 @@ public abstract partial class SharedStunSystem if (!DoAfter.TryStartDoAfter(doAfterArgs, out var doAfterId)) return false; - entity.Comp1.DoAfterId = doAfterId.Value.Index; - DirtyField(entity, entity.Comp1, nameof(KnockedDownComponent.DoAfterId)); + entity.Comp.DoAfterId = doAfterId.Value.Index; + DirtyField(entity, entity.Comp, nameof(KnockedDownComponent.DoAfterId)); return true; } + public bool KnockdownOver(Entity entity) + { + if (entity.Comp.NextUpdate > GameTiming.CurTime) + return false; + + return Blocker.CanMove(entity); + } + /// /// A variant of used when we're actually trying to stand. /// Main difference is this one affects autostand datafields and also displays popups. @@ -288,10 +309,7 @@ public abstract partial class SharedStunSystem /// Returns whether the entity is able to stand public bool TryStand(Entity entity) { - if (entity.Comp.NextUpdate > GameTiming.CurTime) - return false; - - if (!Blocker.CanMove(entity)) + if (!KnockdownOver(entity)) return false; var ev = new StandUpAttemptEvent(entity.Comp.AutoStand); @@ -308,6 +326,22 @@ public abstract partial class SharedStunSystem return !ev.Cancelled; } + /// + /// Checks if an entity is able to stand, returns true if it can, returns false if it cannot. + /// + /// Entity we're checking + /// Returns whether the entity is able to stand + public bool CanStand(Entity entity) + { + if (!KnockdownOver(entity)) + return false; + + var ev = new StandUpAttemptEvent(); + RaiseLocalEvent(entity, ref ev); + + return !ev.Cancelled; + } + private bool StandingBlocked(Entity entity) { if (!TryStand(entity)) @@ -338,7 +372,7 @@ public abstract partial class SharedStunSystem // That way if we fail to stand, the game will try to stand for us when we are able to SetAutoStand(entity, true); - if (!HasComp(entity) || StandingBlocked((entity, entity.Comp))) + if (StandingBlocked((entity, entity.Comp))) return; if (!_hands.TryGetEmptyHand(entity.Owner, out _)) @@ -436,16 +470,22 @@ public abstract partial class SharedStunSystem #endregion - #region Knockdown Extenders + #region Crawling - private void OnDamaged(Entity entity, ref DamageChangedEvent args) + private void OnDamaged(Entity entity, ref DamageChangedEvent args) { // We only want to extend our knockdown timer if it would've prevented us from standing up if (!args.InterruptsDoAfters || !args.DamageIncreased || args.DamageDelta == null || GameTiming.ApplyingState) return; - if (args.DamageDelta.GetTotal() >= KnockdownDamageThreshold) // TODO: Unhardcode this - SetKnockdownTime(entity, GameTiming.CurTime + DefaultKnockedDuration); + if (args.DamageDelta.GetTotal() >= entity.Comp.KnockdownDamageThreshold) + RefreshKnockdownTime(entity.Owner, entity.Comp.DefaultKnockedDuration); + } + + private void OnKnockdownRefresh(Entity entity, ref KnockedDownRefreshEvent args) + { + args.FrictionModifier *= entity.Comp.FrictionModifier; + args.SpeedModifier *= entity.Comp.SpeedModifier; } #endregion diff --git a/Content.Shared/Stunnable/SharedStunSystem.cs b/Content.Shared/Stunnable/SharedStunSystem.cs index 199afcabf2..dcf9ee4f60 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -26,7 +26,6 @@ namespace Content.Shared.Stunnable; public abstract partial class SharedStunSystem : EntitySystem { public static readonly EntProtoId StunId = "StatusEffectStunned"; - public static readonly EntProtoId KnockdownId = "StatusEffectKnockdown"; [Dependency] protected readonly IGameTiming GameTiming = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; @@ -61,10 +60,11 @@ public abstract partial class SharedStunSystem : EntitySystem SubscribeLocalEvent(OnMobStateChanged); // New Status Effect subscriptions - SubscribeLocalEvent(OnStunEffectApplied); + SubscribeLocalEvent(OnStunStatusApplied); SubscribeLocalEvent(OnStunStatusRemoved); SubscribeLocalEvent>(OnStunEndAttempt); + SubscribeLocalEvent(OnKnockdownStatusApplied); SubscribeLocalEvent>(OnStandUpAttempt); // Stun Appearance Data @@ -123,7 +123,7 @@ public abstract partial class SharedStunSystem : EntitySystem return; TryUpdateStunDuration(args.OtherEntity, ent.Comp.Duration); - TryKnockdown(args.OtherEntity, ent.Comp.Duration, true, force: true); + TryKnockdown(args.OtherEntity, ent.Comp.Duration, force: true); } // TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...) @@ -156,29 +156,54 @@ public abstract partial class SharedStunSystem : EntitySystem _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} stunned for {timeForLogs} seconds"); } - public bool TryAddKnockdownDuration(EntityUid uid, TimeSpan duration) + /// + /// Tries to knock an entity to the ground, but will fail if they aren't able to crawl. + /// Useful if you don't want to paralyze an entity that can't crawl, but still want to knockdown + /// entities that can. + /// + /// Entity we're trying to knockdown. + /// Time of the knockdown. + /// Do we refresh their timer, or add to it if one exists? + /// Whether we should automatically stand when knockdown ends. + /// Should we drop what we're holding? + /// Should we force crawling? Even if something tried to block it? + /// Returns true if the entity is able to crawl, and was able to be knocked down. + public bool TryCrawling(Entity entity, + TimeSpan? time, + bool refresh = true, + bool autoStand = true, + bool drop = true, + bool force = false) { - if (!_status.TryAddStatusEffectDuration(uid, KnockdownId, duration)) + if (!Resolve(entity, ref entity.Comp, false)) return false; - TryKnockdown(uid, duration, true, force: true); - - return true; - + return TryKnockdown(entity, time, refresh, autoStand, drop, force); } - public bool TryUpdateKnockdownDuration(EntityUid uid, TimeSpan? duration) + /// + /// An overload of TryCrawling which uses the default crawling time from the CrawlerComponent as its timespan. + public bool TryCrawling(Entity entity, + bool refresh = true, + bool autoStand = true, + bool drop = true, + bool force = false) { - if (!_status.TryUpdateStatusEffectDuration(uid, KnockdownId, duration)) + if (!Resolve(entity, ref entity.Comp, false)) return false; - return TryKnockdown(uid, duration, true, force: true); + return TryKnockdown(entity, entity.Comp.DefaultKnockedDuration, refresh, autoStand, drop, force); } /// - /// Knocks down the entity, making it fall to the ground. + /// Checks if we can knock down an entity to the ground... /// - public bool TryKnockdown(Entity entity, TimeSpan? time, bool refresh, bool autoStand = true, bool drop = true, bool force = false) + /// The entity we're trying to knock down + /// The time of the knockdown + /// Whether we want to automatically stand when knockdown ends. + /// Whether we should drop items. + /// Should we force the status effect? + public bool CanKnockdown(Entity entity, ref TimeSpan? time, ref bool autoStand, ref bool drop, bool force = false) { if (time <= TimeSpan.Zero) return false; @@ -187,30 +212,53 @@ public abstract partial class SharedStunSystem : EntitySystem if (!Resolve(entity, ref entity.Comp, false)) return false; - if (!force) - { - var evAttempt = new KnockDownAttemptEvent(autoStand, drop); - RaiseLocalEvent(entity, ref evAttempt); + var evAttempt = new KnockDownAttemptEvent(autoStand, drop, time); + RaiseLocalEvent(entity, ref evAttempt); - if (evAttempt.Cancelled) - return false; + autoStand = evAttempt.AutoStand; + drop = evAttempt.Drop; - autoStand = evAttempt.AutoStand; - drop = evAttempt.Drop; - } + return force || !evAttempt.Cancelled; + } - Knockdown(entity!, time, refresh, autoStand, drop); + /// + /// Knocks down the entity, making it fall to the ground. + /// + /// The entity we're trying to knock down + /// The time of the knockdown + /// Whether we should refresh a running timer or add to it, if one exists. + /// Whether we want to automatically stand when knockdown ends. + /// Whether we should drop items. + /// Should we force the status effect? + public bool TryKnockdown(Entity entity, TimeSpan? time, bool refresh = true, bool autoStand = true, bool drop = true, bool force = false) + { + if (!CanKnockdown(entity.Owner, ref time, ref autoStand, ref drop, force)) + return false; + // If the entity can't crawl they also need to be stunned, and therefore we should be using paralysis status effect. + // Also time shouldn't be null if we're and trying to add time but, we check just in case anyways. + if (!Resolve(entity, ref entity.Comp, false)) + return refresh || time == null ? TryUpdateParalyzeDuration(entity, time) : TryAddParalyzeDuration(entity, time.Value); + + Knockdown(entity, time, refresh, autoStand, drop); return true; } - private void Knockdown(Entity entity, TimeSpan? time, bool refresh, bool autoStand, bool drop) + private void Crawl(Entity entity, TimeSpan? time, bool refresh, bool autoStand, bool drop) + { + if (!Resolve(entity, ref entity.Comp, false)) + return; + + Knockdown(entity, time, refresh, autoStand, drop); + } + + private void Knockdown(EntityUid uid, TimeSpan? time, bool refresh, bool autoStand, bool drop) { // Initialize our component with the relevant data we need if we don't have it - if (EnsureComp(entity, out var component)) + if (EnsureComp(uid, out var component)) { - RefreshKnockedMovement((entity, component)); - CancelKnockdownDoAfter((entity, component)); + RefreshKnockedMovement((uid, component)); + CancelKnockdownDoAfter((uid, component)); } else { @@ -218,41 +266,50 @@ public abstract partial class SharedStunSystem : EntitySystem if (drop) { var ev = new DropHandItemsEvent(); - RaiseLocalEvent(entity, ref ev); + RaiseLocalEvent(uid, ref ev); } // Only update Autostand value if it's our first time being knocked down... - SetAutoStand((entity, component), autoStand); + SetAutoStand((uid, component), autoStand); } - var knockedEv = new KnockedDownEvent(time); - RaiseLocalEvent(entity, ref knockedEv); + var knockedEv = new KnockedDownEvent(); + RaiseLocalEvent(uid, ref knockedEv); if (time != null) { - UpdateKnockdownTime((entity, component), time.Value, refresh); - _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(entity):user} knocked down for {time.Value.Seconds} seconds"); + UpdateKnockdownTime((uid, component), time.Value, refresh); + _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} was knocked down for {time.Value.Seconds} seconds"); } else - _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(entity):user} knocked down for an indefinite amount of time"); - - Alerts.ShowAlert(entity, KnockdownAlert, null, (GameTiming.CurTime, component.NextUpdate)); + { + Alerts.ShowAlert(uid, KnockdownAlert); + _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} was knocked down"); + } } public bool TryAddParalyzeDuration(EntityUid uid, TimeSpan duration) { - var knockdown = TryAddKnockdownDuration(uid, duration); - var stunned = TryAddStunDuration(uid, duration); + if (!_status.TryAddStatusEffectDuration(uid, StunId, duration)) + return false; - return knockdown || stunned; + // We can't exit knockdown when we're stunned, so this prevents knockdown lasting longer than the stun. + Knockdown(uid, null, false, true, true); + OnStunnedSuccessfully(uid, duration); + + return true; } public bool TryUpdateParalyzeDuration(EntityUid uid, TimeSpan? duration) { - var knockdown = TryUpdateKnockdownDuration(uid, duration); - var stunned = TryUpdateStunDuration(uid, duration); + if (!_status.TryUpdateStatusEffectDuration(uid, StunId, duration)) + return false; - return knockdown || stunned; + // We can't exit knockdown when we're stunned, so this prevents knockdown lasting longer than the stun. + Knockdown(uid, null, false, true, true); + OnStunnedSuccessfully(uid, duration); + + return true; } public bool TryUnstun(Entity entity) @@ -266,7 +323,7 @@ public abstract partial class SharedStunSystem : EntitySystem return !ev.Cancelled && RemComp(entity); } - private void OnStunEffectApplied(Entity entity, ref StatusEffectAppliedEvent args) + private void OnStunStatusApplied(Entity entity, ref StatusEffectAppliedEvent args) { if (GameTiming.ApplyingState) return; @@ -289,6 +346,18 @@ public abstract partial class SharedStunSystem : EntitySystem args.Args = ev; } + private void OnKnockdownStatusApplied(Entity entity, ref StatusEffectAppliedEvent args) + { + if (GameTiming.ApplyingState) + return; + + // If you make something that shouldn't crawl, crawl, that's your own fault. + if (entity.Comp.Crawl) + Crawl(args.Target, null, true, true, drop: entity.Comp.Drop); + else + Knockdown(args.Target, null, true, true, drop: entity.Comp.Drop); + } + private void OnStandUpAttempt(Entity entity, ref StatusEffectRelayedEvent args) { if (args.Args.Cancelled) diff --git a/Content.Shared/Stunnable/StunnableEvents.cs b/Content.Shared/Stunnable/StunnableEvents.cs index f4a0191c92..f0c08f6136 100644 --- a/Content.Shared/Stunnable/StunnableEvents.cs +++ b/Content.Shared/Stunnable/StunnableEvents.cs @@ -26,7 +26,7 @@ public record struct StunEndAttemptEvent(bool Cancelled); /// knocked down arguments. /// [ByRefEvent] -public record struct KnockDownAttemptEvent(bool AutoStand, bool Drop) +public record struct KnockDownAttemptEvent(bool AutoStand, bool Drop, TimeSpan? Time) { public bool Cancelled; } @@ -35,7 +35,7 @@ public record struct KnockDownAttemptEvent(bool AutoStand, bool Drop) /// Raised directed on an entity when it is knocked down. /// [ByRefEvent] -public record struct KnockedDownEvent(TimeSpan? Time); +public record struct KnockedDownEvent; /// /// Raised on an entity that needs to refresh its knockdown modifiers diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index da68ee109b..d9b2b83993 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1433,6 +1433,7 @@ methods: [ Touch ] effects: - !type:WashCreamPieReaction + - type: Crawler diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 8fcaef42b9..10c39ed7a0 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -163,6 +163,7 @@ - type: SleepEmitSound - type: SSDIndicator - type: StandingState + - type: Crawler - type: Dna - type: MindContainer showExamineInfo: true diff --git a/Resources/Prototypes/Entities/StatusEffects/movement.yml b/Resources/Prototypes/Entities/StatusEffects/movement.yml index a6cbd20724..71142af434 100644 --- a/Resources/Prototypes/Entities/StatusEffects/movement.yml +++ b/Resources/Prototypes/Entities/StatusEffects/movement.yml @@ -58,10 +58,3 @@ - type: StatusEffectAlert alert: Stun - type: StunnedStatusEffect - -- type: entity - parent: MobStandStatusEffectBase - id: StatusEffectKnockdown - name: knocked down - components: - - type: KnockdownStatusEffect From 80299e863a8baf0266eb6e85cc270ab293e49d37 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 10 Aug 2025 17:50:37 +0000 Subject: [PATCH 05/40] 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 e032c3290b..1470928b69 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: AgentSmithRadio, MadeOfHeartAndStone - changes: - - message: Added cotton grilled cheese sandwich entity and recipe. - type: Add - id: 8329 - time: '2025-04-24T03:24:37.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/36135 - author: SG6732 changes: - message: Added meat patties. Get the charcoal and let's start grillin. @@ -3899,3 +3892,10 @@ id: 8841 time: '2025-08-09T21:52:06.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/39174 +- author: Princess-Cheeseballs + changes: + - message: Mobs such as borgs, dragons, etc can no longer crawl. + type: Tweak + id: 8842 + time: '2025-08-10T17:49:29.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39084 From 80375370f8572aee61e1db536f5e5adb4ddbe93c Mon Sep 17 00:00:00 2001 From: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Date: Sun, 10 Aug 2025 11:10:13 -0700 Subject: [PATCH 06/40] Add voice locks to various hidden syndicate items (#39310) --- .../Access/Systems/AgentIDCardSystem.cs | 5 +- Content.Server/VoiceMask/VoiceMaskSystem.cs | 16 +++++ .../SharedChameleonClothingSystem.cs | 4 +- .../Item/ItemToggle/ItemToggleSystem.cs | 4 +- .../Lock/ItemToggleRequiresLockComponent.cs | 10 ++- Content.Shared/Lock/LockComponent.cs | 16 ++++- Content.Shared/Lock/LockSystem.cs | 70 +++++++++++++------ ...omponent.cs => UIRequiresLockComponent.cs} | 12 +++- .../SharedVoiceTriggerLockSystem.cs | 30 ++++++++ .../SecretLocks/VoiceTriggerLockComponent.cs | 10 +++ .../Effects/LockOnTriggerComponent.cs | 19 +++++ .../Triggers/TriggerOnVoiceComponent.cs | 48 +++++++++++++ .../Trigger/Systems/LockOnTriggerSystem.cs | 35 ++++++++++ .../Trigger/Systems/TriggerSystem.Voice.cs | 22 +++--- .../UserInterface/ActivatableUISystem.cs | 19 +++-- .../components/agent-id-card-component.ftl | 2 + .../Locale/en-US/locks/voice-trigger-lock.ftl | 5 ++ .../Entities/Clothing/Back/specific.yml | 2 +- .../Entities/Clothing/Ears/specific.yml | 6 +- .../Entities/Clothing/Eyes/specific.yml | 9 +-- .../Entities/Clothing/Hands/specific.yml | 6 +- .../Entities/Clothing/Head/specific.yml | 6 +- .../Entities/Clothing/Masks/specific.yml | 12 ++-- .../Entities/Clothing/Neck/specific.yml | 6 +- .../Clothing/OuterClothing/specific.yml | 6 +- .../Entities/Clothing/Shoes/specific.yml | 6 +- .../Entities/Clothing/Uniforms/specific.yml | 6 +- .../Mobs/Cyborgs/base_borg_chassis.yml | 4 +- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 1 + .../Entities/Objects/Devices/pda.yml | 2 +- .../Objects/Misc/identification_cards.yml | 4 +- .../Entities/Objects/Specific/locks.yml | 24 +++++++ .../Entities/Objects/Weapons/Melee/cane.yml | 5 +- .../Objects/Weapons/Melee/e_sword.yml | 12 +++- .../Storage/Canisters/gas_canisters.yml | 4 +- Resources/Prototypes/chameleon.yml | 15 ++++ 36 files changed, 366 insertions(+), 97 deletions(-) rename Content.Shared/Lock/{ActivatableUIRequiresLockComponent.cs => UIRequiresLockComponent.cs} (67%) create mode 100644 Content.Shared/SecretLocks/SharedVoiceTriggerLockSystem.cs create mode 100644 Content.Shared/SecretLocks/VoiceTriggerLockComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/LockOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Systems/LockOnTriggerSystem.cs create mode 100644 Resources/Locale/en-US/locks/voice-trigger-lock.ftl create mode 100644 Resources/Prototypes/Entities/Objects/Specific/locks.yml create mode 100644 Resources/Prototypes/chameleon.yml diff --git a/Content.Server/Access/Systems/AgentIDCardSystem.cs b/Content.Server/Access/Systems/AgentIDCardSystem.cs index 6385274336..0df760baef 100644 --- a/Content.Server/Access/Systems/AgentIDCardSystem.cs +++ b/Content.Server/Access/Systems/AgentIDCardSystem.cs @@ -13,6 +13,7 @@ using Content.Server.Clothing.Systems; using Content.Server.Implants; using Content.Shared.Implants; using Content.Shared.Inventory; +using Content.Shared.Lock; using Content.Shared.PDA; namespace Content.Server.Access.Systems @@ -25,6 +26,7 @@ namespace Content.Server.Access.Systems [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly ChameleonClothingSystem _chameleon = default!; [Dependency] private readonly ChameleonControllerSystem _chamController = default!; + [Dependency] private readonly LockSystem _lock = default!; public override void Initialize() { @@ -79,7 +81,8 @@ namespace Content.Server.Access.Systems private void OnAfterInteract(EntityUid uid, AgentIDCardComponent component, AfterInteractEvent args) { - if (args.Target == null || !args.CanReach || !TryComp(args.Target, out var targetAccess) || !HasComp(args.Target)) + if (args.Target == null || !args.CanReach || _lock.IsLocked(uid) || + !TryComp(args.Target, out var targetAccess) || !HasComp(args.Target)) return; if (!TryComp(uid, out var access) || !HasComp(uid)) diff --git a/Content.Server/VoiceMask/VoiceMaskSystem.cs b/Content.Server/VoiceMask/VoiceMaskSystem.cs index cd85ff2428..528acd58b0 100644 --- a/Content.Server/VoiceMask/VoiceMaskSystem.cs +++ b/Content.Server/VoiceMask/VoiceMaskSystem.cs @@ -5,11 +5,13 @@ using Content.Shared.Chat; using Content.Shared.Clothing; using Content.Shared.Database; using Content.Shared.Inventory; +using Content.Shared.Lock; using Content.Shared.Popups; using Content.Shared.Preferences; using Content.Shared.Speech; using Content.Shared.VoiceMask; using Robust.Shared.Configuration; +using Robust.Shared.Containers; using Robust.Shared.Prototypes; namespace Content.Server.VoiceMask; @@ -22,6 +24,8 @@ public sealed partial class VoiceMaskSystem : EntitySystem [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly LockSystem _lock = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; // CCVar. private int _maxNameLength; @@ -30,6 +34,7 @@ public sealed partial class VoiceMaskSystem : EntitySystem { base.Initialize(); SubscribeLocalEvent>(OnTransformSpeakerName); + SubscribeLocalEvent(OnLockToggled); SubscribeLocalEvent(OnChangeName); SubscribeLocalEvent(OnChangeVerb); SubscribeLocalEvent(OnEquip); @@ -44,6 +49,14 @@ public sealed partial class VoiceMaskSystem : EntitySystem args.Args.SpeechVerb = entity.Comp.VoiceMaskSpeechVerb ?? args.Args.SpeechVerb; } + private void OnLockToggled(Entity ent, ref LockToggledEvent args) + { + if (args.Locked) + _actions.RemoveAction(ent.Comp.ActionEntity); + else if (_container.TryGetContainingContainer(ent.Owner, out var container)) + _actions.AddAction(container.Owner, ref ent.Comp.ActionEntity, ent.Comp.Action, ent); + } + #region User inputs from UI private void OnChangeVerb(Entity entity, ref VoiceMaskChangeVerbMessage msg) { @@ -78,6 +91,9 @@ public sealed partial class VoiceMaskSystem : EntitySystem #region UI private void OnEquip(EntityUid uid, VoiceMaskComponent component, ClothingGotEquippedEvent args) { + if (_lock.IsLocked(uid)) + return; + _actions.AddAction(args.Wearer, ref component.ActionEntity, component.Action, uid); } diff --git a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs index 233a71acee..4b38d926f6 100644 --- a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Contraband; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Item; +using Content.Shared.Lock; using Content.Shared.Tag; using Content.Shared.Verbs; using Robust.Shared.Prototypes; @@ -23,6 +24,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly TagSystem _tag = default!; [Dependency] protected readonly IGameTiming _timing = default!; + [Dependency] private readonly LockSystem _lock = default!; private static readonly SlotFlags[] IgnoredSlots = { @@ -122,7 +124,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem private void OnVerb(Entity ent, ref GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract || ent.Comp.User != args.User) + if (!args.CanAccess || !args.CanInteract || _lock.IsLocked(ent.Owner)) return; // Can't pass args from a ref event inside of lambdas diff --git a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs index ff31faaaa1..367b078f23 100644 --- a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs +++ b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs @@ -78,7 +78,7 @@ public sealed class ItemToggleSystem : EntitySystem if (ent.Comp.Activated) { - var ev = new ItemToggleActivateAttemptEvent(args.User); + var ev = new ItemToggleDeactivateAttemptEvent(args.User); RaiseLocalEvent(ent.Owner, ref ev); if (ev.Cancelled) @@ -86,7 +86,7 @@ public sealed class ItemToggleSystem : EntitySystem } else { - var ev = new ItemToggleDeactivateAttemptEvent(args.User); + var ev = new ItemToggleActivateAttemptEvent(args.User); RaiseLocalEvent(ent.Owner, ref ev); if (ev.Cancelled) diff --git a/Content.Shared/Lock/ItemToggleRequiresLockComponent.cs b/Content.Shared/Lock/ItemToggleRequiresLockComponent.cs index 94b8729476..1f257a90c8 100644 --- a/Content.Shared/Lock/ItemToggleRequiresLockComponent.cs +++ b/Content.Shared/Lock/ItemToggleRequiresLockComponent.cs @@ -5,13 +5,19 @@ namespace Content.Shared.Lock; /// /// This is used for toggleable items that require the entity to have a lock in a certain state. /// -[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(LockSystem))] public sealed partial class ItemToggleRequiresLockComponent : Component { /// /// TRUE: the lock must be locked to toggle the item. /// FALSE: the lock must be unlocked to toggle the item. /// - [DataField] + [DataField, AutoNetworkedField] public bool RequireLocked; + + /// + /// Popup text for when someone tries to toggle the item, but it's locked. If null, no popup will be shown. + /// + [DataField] + public LocId? LockedPopup = "lock-comp-generic-fail"; } diff --git a/Content.Shared/Lock/LockComponent.cs b/Content.Shared/Lock/LockComponent.cs index 0fdee2477f..1e5f0fdd50 100644 --- a/Content.Shared/Lock/LockComponent.cs +++ b/Content.Shared/Lock/LockComponent.cs @@ -21,6 +21,18 @@ public sealed partial class LockComponent : Component [AutoNetworkedField] public bool Locked = true; + /// + /// If true, will show verbs to lock and unlock the item. Otherwise, it will not. + /// + [DataField, AutoNetworkedField] + public bool ShowLockVerbs = true; + + /// + /// If true will show examine text. + /// + [DataField, AutoNetworkedField] + public bool ShowExamine = true; + /// /// Whether or not the lock is locked by simply clicking. /// @@ -44,7 +56,7 @@ public sealed partial class LockComponent : Component /// The sound played when unlocked. /// [DataField("unlockingSound"), ViewVariables(VVAccess.ReadWrite)] - public SoundSpecifier UnlockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg") + public SoundSpecifier? UnlockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg") { Params = AudioParams.Default.WithVolume(-5f), }; @@ -53,7 +65,7 @@ public sealed partial class LockComponent : Component /// The sound played when locked. /// [DataField("lockingSound"), ViewVariables(VVAccess.ReadWrite)] - public SoundSpecifier LockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_on.ogg") + public SoundSpecifier? LockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_on.ogg") { Params = AudioParams.Default.WithVolume(-5f) }; diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index 397a3636bb..6ca546f581 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -28,12 +28,12 @@ public sealed class LockSystem : EntitySystem { [Dependency] private readonly AccessReaderSystem _accessReader = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; - [Dependency] private readonly ActivatableUISystem _activatableUI = default!; [Dependency] private readonly EmagSystem _emag = default!; [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; /// public override void Initialize() @@ -54,8 +54,8 @@ public sealed class LockSystem : EntitySystem SubscribeLocalEvent(OnAttemptChangePanel); SubscribeLocalEvent(OnUnanchorAttempt); - SubscribeLocalEvent(OnUIOpenAttempt); - SubscribeLocalEvent(LockToggled); + SubscribeLocalEvent(OnUIOpenAttempt); + SubscribeLocalEvent(LockToggled); SubscribeLocalEvent(OnActivateAttempt); } @@ -96,6 +96,9 @@ public sealed class LockSystem : EntitySystem private void OnExamined(EntityUid uid, LockComponent lockComp, ExaminedEvent args) { + if (!lockComp.ShowExamine) + return; + args.PushText(Loc.GetString(lockComp.Locked ? "lock-comp-on-examined-is-locked" : "lock-comp-on-examined-is-unlocked", @@ -239,6 +242,20 @@ public sealed class LockSystem : EntitySystem return true; } + /// + /// Toggle the lock to locked if unlocked, and unlocked if locked. + /// + /// Entity to toggle the lock state of. + /// The person trying to toggle the lock + /// Entities lock comp (will be resolved) + public void ToggleLock(EntityUid uid, EntityUid? user, LockComponent? lockComp = null) + { + if (IsLocked((uid, lockComp))) + Unlock(uid, user, lockComp); + else + Lock(uid, user, lockComp); + } + /// /// Returns true if the entity is locked. /// Entities with no lock component are considered unlocked. @@ -287,7 +304,7 @@ public sealed class LockSystem : EntitySystem private void AddToggleLockVerb(EntityUid uid, LockComponent component, GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract || !args.CanComplexInteract) + if (!args.CanAccess || !args.CanInteract || !args.CanComplexInteract || !component.ShowLockVerbs) return; AlternativeVerb verb = new() @@ -394,41 +411,54 @@ public sealed class LockSystem : EntitySystem args.Cancel(); } - private void OnUIOpenAttempt(EntityUid uid, ActivatableUIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args) + private void OnUIOpenAttempt(EntityUid uid, UIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args) { if (args.Cancelled) return; - if (TryComp(uid, out var lockComp) && lockComp.Locked != component.RequireLocked) - { - args.Cancel(); - if (lockComp.Locked) - { - _sharedPopupSystem.PopupClient(Loc.GetString("entity-storage-component-locked-message"), uid, args.User); - } + if (!TryComp(uid, out var lockComp) || lockComp.Locked == component.RequireLocked) + return; - _audio.PlayPredicted(component.AccessDeniedSound, uid, args.User); + args.Cancel(); + if (lockComp.Locked && component.Popup != null) + { + _sharedPopupSystem.PopupClient(Loc.GetString(component.Popup), uid, args.User); } + + _audio.PlayPredicted(component.AccessDeniedSound, uid, args.User); } - private void LockToggled(EntityUid uid, ActivatableUIRequiresLockComponent component, LockToggledEvent args) + private void LockToggled(EntityUid uid, UIRequiresLockComponent component, LockToggledEvent args) { if (!TryComp(uid, out var lockComp) || lockComp.Locked == component.RequireLocked) return; - _activatableUI.CloseAll(uid); + if (component.UserInterfaceKeys == null) + { + _ui.CloseUis(uid); + return; + } + + foreach (var key in component.UserInterfaceKeys) + { + _ui.CloseUi(uid, key); + } } + private void OnActivateAttempt(EntityUid uid, ItemToggleRequiresLockComponent component, ref ItemToggleActivateAttemptEvent args) { if (args.Cancelled) return; - if (TryComp(uid, out var lockComp) && lockComp.Locked != component.RequireLocked) + if (!TryComp(uid, out var lockComp) || lockComp.Locked == component.RequireLocked) + return; + + args.Cancelled = true; + + if (lockComp.Locked && component.LockedPopup != null) { - args.Cancelled = true; - if (lockComp.Locked) - _sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-generic-fail", - ("target", Identity.Entity(uid, EntityManager))), + _sharedPopupSystem.PopupClient(Loc.GetString(component.LockedPopup, + ("target", Identity.Entity(uid, EntityManager))), uid, args.User); } diff --git a/Content.Shared/Lock/ActivatableUIRequiresLockComponent.cs b/Content.Shared/Lock/UIRequiresLockComponent.cs similarity index 67% rename from Content.Shared/Lock/ActivatableUIRequiresLockComponent.cs rename to Content.Shared/Lock/UIRequiresLockComponent.cs index a2a9d8c556..ab10526103 100644 --- a/Content.Shared/Lock/ActivatableUIRequiresLockComponent.cs +++ b/Content.Shared/Lock/UIRequiresLockComponent.cs @@ -7,8 +7,15 @@ namespace Content.Shared.Lock; /// This is used for activatable UIs that require the entity to have a lock in a certain state. /// [RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))] -public sealed partial class ActivatableUIRequiresLockComponent : Component +public sealed partial class UIRequiresLockComponent : Component { + /// + /// UIs that are locked behind this component. + /// If null, will close all UIs. + /// + [DataField] + public List? UserInterfaceKeys; + /// /// TRUE: the lock must be locked to access the UI. /// FALSE: the lock must be unlocked to access the UI. @@ -21,4 +28,7 @@ public sealed partial class ActivatableUIRequiresLockComponent : Component /// [DataField] public SoundSpecifier? AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg"); + + [DataField] + public LocId? Popup = "entity-storage-component-locked-message"; } diff --git a/Content.Shared/SecretLocks/SharedVoiceTriggerLockSystem.cs b/Content.Shared/SecretLocks/SharedVoiceTriggerLockSystem.cs new file mode 100644 index 0000000000..483b3ec251 --- /dev/null +++ b/Content.Shared/SecretLocks/SharedVoiceTriggerLockSystem.cs @@ -0,0 +1,30 @@ +using Content.Shared.Item.ItemToggle; +using Content.Shared.Lock; +using Content.Shared.Trigger.Components.Triggers; + +namespace Content.Shared.SecretLocks; + +public sealed partial class SharedVoiceTriggerLockSystem : EntitySystem +{ + [Dependency] private readonly ItemToggleSystem _toggle = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnLockToggled); + } + + private void OnLockToggled(Entity ent, ref LockToggledEvent args) + { + if (!TryComp(ent.Owner, out var triggerComp)) + return; + + triggerComp.ShowVerbs = !args.Locked; + triggerComp.ShowExamine = !args.Locked; + + _toggle.TryDeactivate(ent.Owner, null, true, false); + + Dirty(ent.Owner, triggerComp); + } +} diff --git a/Content.Shared/SecretLocks/VoiceTriggerLockComponent.cs b/Content.Shared/SecretLocks/VoiceTriggerLockComponent.cs new file mode 100644 index 0000000000..345f76226f --- /dev/null +++ b/Content.Shared/SecretLocks/VoiceTriggerLockComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.SecretLocks; + +/// +/// "Locks" items (Doesn't actually lock them but just switches various settings) so its not possible to tell +/// the item is triggered by a voice activation. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class VoiceTriggerLockComponent : Component; diff --git a/Content.Shared/Trigger/Components/Effects/LockOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/LockOnTriggerComponent.cs new file mode 100644 index 0000000000..38eea0a461 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/LockOnTriggerComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Trigger.Components.Effects; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class LockOnTriggerComponent : BaseXOnTriggerComponent +{ + [DataField, AutoNetworkedField] + public LockAction LockOnTrigger = LockAction.Toggle; +} + +[Serializable, NetSerializable] +public enum LockAction +{ + Lock = 0, + Unlock = 1, + Toggle = 2, +} diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs index a36992d7da..1fc3c1b966 100644 --- a/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs @@ -44,4 +44,52 @@ public sealed partial class TriggerOnVoiceComponent : BaseTriggerOnXComponent /// [DataField, AutoNetworkedField] public int MaxLength = 50; + + /// + /// When examining the item, should it show information about what word is recorded? + /// + [DataField, AutoNetworkedField] + public bool ShowExamine = true; + + /// + /// Should there be verbs that allow re-recording of the trigger word? + /// + [DataField, AutoNetworkedField] + public bool ShowVerbs = true; + + /// + /// The verb text that is shown when you can start recording a message. + /// + [DataField] + public LocId StartRecordingVerb = "trigger-on-voice-record"; + + /// + /// The verb text that is shown when you can stop recording a message. + /// + [DataField] + public LocId StopRecordingVerb = "trigger-on-voice-stop"; + + /// + /// Tooltip that appears when hovering over the stop or start recording verbs. + /// + [DataField] + public LocId? RecordingVerbMessage; + + /// + /// The verb text that is shown when you can clear a recording. + /// + [DataField] + public LocId ClearRecordingVerb = "trigger-on-voice-clear"; + + /// + /// The loc string that is shown when inspecting an uninitialized voice trigger. + /// + [DataField] + public LocId? InspectUninitializedLoc = "trigger-on-voice-uninitialized"; + + /// + /// The loc string to use when inspecting voice trigger. Will also include the triggering phrase + /// + [DataField] + public LocId? InspectInitializedLoc = "trigger-on-voice-examine"; } diff --git a/Content.Shared/Trigger/Systems/LockOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/LockOnTriggerSystem.cs new file mode 100644 index 0000000000..8726ede899 --- /dev/null +++ b/Content.Shared/Trigger/Systems/LockOnTriggerSystem.cs @@ -0,0 +1,35 @@ +using Content.Shared.Lock; +using Content.Shared.Trigger.Components.Effects; + +namespace Content.Shared.Trigger.Systems; + +public sealed class LockOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly LockSystem _lock = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + switch (ent.Comp.LockOnTrigger) + { + case LockAction.Lock: + _lock.Lock(ent.Owner, args.User); + break; + case LockAction.Unlock: + _lock.Unlock(ent, args.User); + break; + case LockAction.Toggle: + _lock.ToggleLock(ent, args.User); + break; + } + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs index ac67cb7ed2..c374369a7f 100644 --- a/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs @@ -25,15 +25,21 @@ public sealed partial class TriggerSystem RemCompDeferred(ent); } - private void OnVoiceExamine(Entity ent, ref ExaminedEvent args) + private void OnVoiceExamine(EntityUid uid, TriggerOnVoiceComponent component, ExaminedEvent args) { - if (args.IsInDetailsRange) + if (!args.IsInDetailsRange || !component.ShowExamine) + return; + + if (component.InspectUninitializedLoc != null && string.IsNullOrWhiteSpace(component.KeyPhrase)) { - args.PushText(string.IsNullOrWhiteSpace(ent.Comp.KeyPhrase) - ? Loc.GetString("trigger-on-voice-uninitialized") - : Loc.GetString("trigger-on-voice-examine", ("keyphrase", ent.Comp.KeyPhrase))); + args.PushText(Loc.GetString(component.InspectUninitializedLoc)); + } + else if (component.InspectInitializedLoc != null && !string.IsNullOrWhiteSpace(component.KeyPhrase)) + { + args.PushText(Loc.GetString(component.InspectInitializedLoc.Value, ("keyphrase", component.KeyPhrase))); } } + private void OnListen(Entity ent, ref ListenEvent args) { var component = ent.Comp; @@ -71,13 +77,13 @@ public sealed partial class TriggerSystem private void OnVoiceGetAltVerbs(Entity ent, ref GetVerbsEvent args) { - if (!args.CanInteract || !args.CanAccess) + if (!args.CanInteract || !args.CanAccess || !ent.Comp.ShowVerbs) return; var user = args.User; args.Verbs.Add(new AlternativeVerb { - Text = Loc.GetString(ent.Comp.IsRecording ? "trigger-on-voice-stop" : "trigger-on-voice-record"), + Text = Loc.GetString(ent.Comp.IsRecording ? ent.Comp.StopRecordingVerb : ent.Comp.StartRecordingVerb), Act = () => { if (ent.Comp.IsRecording) @@ -93,7 +99,7 @@ public sealed partial class TriggerSystem args.Verbs.Add(new AlternativeVerb { - Text = Loc.GetString("trigger-on-voice-clear"), + Text = Loc.GetString(ent.Comp.ClearRecordingVerb), Act = () => { ClearRecording(ent); diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs index a3f9a033df..d1df375ccb 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -116,7 +116,7 @@ public sealed partial class ActivatableUISystem : EntitySystem } } - return args.CanInteract || HasComp(args.User) && !component.BlockSpectators; + return (args.CanInteract || HasComp(args.User) && !component.BlockSpectators) && !RaiseCanOpenEventChecks(args.User, uid); } private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args) @@ -225,11 +225,7 @@ public sealed partial class ActivatableUISystem : EntitySystem // If we've gotten this far, fire a cancellable event that indicates someone is about to activate this. // This is so that stuff can require further conditions (like power). - var oae = new ActivatableUIOpenAttemptEvent(user); - var uae = new UserOpenActivatableUIAttemptEvent(user, uiEntity); - RaiseLocalEvent(user, uae); - RaiseLocalEvent(uiEntity, oae); - if (oae.Cancelled || uae.Cancelled) + if (RaiseCanOpenEventChecks(user, uiEntity)) return false; // Give the UI an opportunity to prepare itself if it needs to do anything @@ -286,4 +282,15 @@ public sealed partial class ActivatableUISystem : EntitySystem if (ent.Comp.InHandsOnly) CloseAll(ent, ent); } + + private bool RaiseCanOpenEventChecks(EntityUid user, EntityUid uiEntity) + { + // If we've gotten this far, fire a cancellable event that indicates someone is about to activate this. + // This is so that stuff can require further conditions (like power). + var oae = new ActivatableUIOpenAttemptEvent(user); + var uae = new UserOpenActivatableUIAttemptEvent(user, uiEntity); + RaiseLocalEvent(user, uae); + RaiseLocalEvent(uiEntity, oae); + return oae.Cancelled || uae.Cancelled; + } } diff --git a/Resources/Locale/en-US/access/components/agent-id-card-component.ftl b/Resources/Locale/en-US/access/components/agent-id-card-component.ftl index 5e1e3cd7cf..c645967d98 100644 --- a/Resources/Locale/en-US/access/components/agent-id-card-component.ftl +++ b/Resources/Locale/en-US/access/components/agent-id-card-component.ftl @@ -8,3 +8,5 @@ agent-id-card-current-name = Name: agent-id-card-current-job = Job: agent-id-card-job-icon-label = Job icon: agent-id-menu-title = Agent ID Card + +agent-id-open-ui-verb = Change settings diff --git a/Resources/Locale/en-US/locks/voice-trigger-lock.ftl b/Resources/Locale/en-US/locks/voice-trigger-lock.ftl new file mode 100644 index 0000000000..fd2dc38d23 --- /dev/null +++ b/Resources/Locale/en-US/locks/voice-trigger-lock.ftl @@ -0,0 +1,5 @@ +voice-trigger-lock-verb-record = Record lock phrase +voice-trigger-lock-verb-message = Locking the item will disable features that reveal its true nature! + +voice-trigger-lock-on-uninitialized = The display is blank +voice-trigger-lock-on-examine = The display shows the passphrase: "{$keyphrase}" diff --git a/Resources/Prototypes/Entities/Clothing/Back/specific.yml b/Resources/Prototypes/Entities/Clothing/Back/specific.yml index 005dfedba5..b80e7709d5 100644 --- a/Resources/Prototypes/Entities/Clothing/Back/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Back/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingBackpack + parent: [ClothingBackpack, BaseChameleon] id: ClothingBackpackChameleon name: backpack description: You wear this on your back and put items into it. diff --git a/Resources/Prototypes/Entities/Clothing/Ears/specific.yml b/Resources/Prototypes/Entities/Clothing/Ears/specific.yml index 83612ac4b2..987c3f7340 100644 --- a/Resources/Prototypes/Entities/Clothing/Ears/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Ears/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingHeadsetGrey + parent: [ClothingHeadsetGrey, BaseChameleon] id: ClothingHeadsetChameleon name: passenger headset description: An updated, modular intercom that fits over the head. Takes encryption keys. @@ -14,7 +14,3 @@ - type: ChameleonClothing slot: [ears] default: ClothingHeadsetGrey - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml b/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml index 936856fdf7..b62773fe50 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingEyesBase + parent: [ClothingEyesBase, BaseChameleon] id: ClothingEyesChameleon # no flash immunity, sorry name: sun glasses description: Useful both for security and cargonia. @@ -7,7 +7,7 @@ components: - type: Tag tags: # intentionally no WhitelistChameleon tag - - PetWearable + - PetWearable - type: Sprite sprite: Clothing/Eyes/Glasses/sunglasses.rsi - type: Clothing @@ -15,8 +15,3 @@ - type: ChameleonClothing slot: [eyes] default: ClothingEyesGlassesSunglasses - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface - diff --git a/Resources/Prototypes/Entities/Clothing/Hands/specific.yml b/Resources/Prototypes/Entities/Clothing/Hands/specific.yml index 6140bcd8ed..1a2ed4301a 100644 --- a/Resources/Prototypes/Entities/Clothing/Hands/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Hands/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingHandsButcherable + parent: [ClothingHandsButcherable, BaseChameleon] id: ClothingHandsChameleon # doesn't protect from electricity or heat name: black gloves description: Regular black gloves that do not keep you from frying. @@ -17,10 +17,6 @@ - type: Fiber fiberMaterial: fibers-chameleon - type: FingerprintMask - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface - type: entity parent: ClothingHandsChameleon diff --git a/Resources/Prototypes/Entities/Clothing/Head/specific.yml b/Resources/Prototypes/Entities/Clothing/Head/specific.yml index 15f89da106..4539e2fdf3 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingHeadBase + parent: [ClothingHeadBase, BaseChameleon] id: ClothingHeadHatChameleon name: beret description: A beret, an artists favorite headwear. @@ -14,10 +14,6 @@ - type: ChameleonClothing slot: [HEAD] default: ClothingHeadHatBeret - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface - type: entity parent: ClothingHeadHatFedoraBrown diff --git a/Resources/Prototypes/Entities/Clothing/Masks/specific.yml b/Resources/Prototypes/Entities/Clothing/Masks/specific.yml index 286dfd6cf1..33f0ec3ad6 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingMaskBase + parent: [ClothingMaskBase, BaseChameleon] id: ClothingMaskGasChameleon name: gas mask description: A face-covering mask that can be connected to an air supply. @@ -16,10 +16,6 @@ default: ClothingMaskGas - type: BreathMask - type: IdentityBlocker # need that for default ClothingMaskGas - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface - type: HideLayerClothing slots: - Snout @@ -30,6 +26,12 @@ suffix: Voice Mask, Chameleon components: - type: VoiceMask + - type: UIRequiresLock + userInterfaceKeys: + - enum.ChameleonUiKey.Key + - enum.VoiceMaskUIKey.Key + accessDeniedSound: null + popup: null - type: HideLayerClothing slots: - Snout diff --git a/Resources/Prototypes/Entities/Clothing/Neck/specific.yml b/Resources/Prototypes/Entities/Clothing/Neck/specific.yml index b98cdd02e0..a32d9b5eea 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingNeckBase + parent: [ClothingNeckBase, BaseChameleon] id: ClothingNeckChameleon name: striped red scarf description: A stylish striped red scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks. @@ -14,7 +14,3 @@ - type: ChameleonClothing slot: [NECK] default: ClothingNeckScarfStripedRed - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml index aec34e80ca..8b2116f0ca 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingOuterBase + parent: [ClothingOuterBase, BaseChameleon] id: ClothingOuterChameleon name: vest description: A thick vest with a rubbery, water-resistant shell. @@ -14,10 +14,6 @@ - type: ChameleonClothing slot: [outerClothing] default: ClothingOuterVest - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface - type: TemperatureProtection # Same as a basic winter coat. heatingCoefficient: 1.1 coolingCoefficient: 0.1 diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml index ebf304557e..cfdc967a7e 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml @@ -163,7 +163,7 @@ sprite: Clothing/Shoes/Specific/wizard.rsi - type: entity - parent: ClothingShoesBase + parent: [ClothingShoesBase, BaseChameleon] id: ClothingShoesChameleon name: black shoes suffix: Chameleon @@ -197,10 +197,6 @@ - type: ChameleonClothing slot: [FEET] default: ClothingShoesColorBlack - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface - type: entity parent: ClothingShoesChameleon diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml index a2c903bac3..f7ce37fe19 100644 --- a/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingUniformBase + parent: [ClothingUniformBase, BaseChameleon] id: ClothingUniformJumpsuitChameleon name: black jumpsuit description: A generic black jumpsuit with no rank markings. @@ -36,7 +36,3 @@ - type: ChameleonClothing slot: [innerclothing] default: ClothingUniformJumpsuitColorBlack - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index cb95c231e9..34fdbbd74b 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -167,7 +167,9 @@ - type: Lock locked: true unlockOnClick: false - - type: ActivatableUIRequiresLock + - type: UIRequiresLock + userInterfaceKeys: + - enum.BorgUiKey.Key - type: LockedWiresPanel - type: Damageable damageContainer: Silicon diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index d9b2b83993..4c6facdd4f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -2320,6 +2320,7 @@ types: Heat : 0.2 #per second, scales with temperature & other constants +# TODO: Make grenade penguin voice activated like the rest of the stealth items. - type: entity name: grenade penguin parent: [ MobPenguin, MobCombat, BaseSyndicateContraband ] diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index 2082d9a152..92bd4297ad 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -1467,7 +1467,7 @@ - MedTekCartridge - type: entity - parent: BasePDA + parent: [BasePDA, VoiceLock] id: ChameleonPDA name: passenger PDA description: Why isn't it gray? diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml index c4c5cc0aaf..d236383226 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml @@ -580,7 +580,7 @@ - type: entity name: passenger ID card - parent: IDCardStandard + parent: [IDCardStandard, BaseChameleon] id: AgentIDCard suffix: Agent components: @@ -595,9 +595,11 @@ - state: default - state: idpassenger - type: AgentIDCard + - type: UIRequiresLock - type: ActivatableUI key: enum.AgentIDCardUiKey.Key inHandsOnly: true + verbText: agent-id-open-ui-verb - type: Tag tags: - DoorBumpOpener diff --git a/Resources/Prototypes/Entities/Objects/Specific/locks.yml b/Resources/Prototypes/Entities/Objects/Specific/locks.yml new file mode 100644 index 0000000000..296b9fd7d9 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/locks.yml @@ -0,0 +1,24 @@ +- type: entity + id: VoiceLock + abstract: true + components: + - type: Lock + locked: false + showLockVerbs: false + showExamine: false + lockOnClick: false + unlockOnClick: false + useAccess: false + unlockingSound: null # TODO: Maybe add sounds but just to the user? + lockingSound: null + lockTime: 0 + unlockTime: 0 + - type: TriggerOnVoice + listenRange: 2 # more fun + startRecordingVerb: voice-trigger-lock-verb-record + recordingVerbMessage: voice-trigger-lock-verb-message + inspectUninitializedLoc: voice-trigger-lock-on-uninitialized + inspectInitializedLoc: voice-trigger-lock-on-examine + - type: LockOnTrigger + - type: ActiveListener + - type: VoiceTriggerLock diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/cane.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/cane.yml index cbf437d0b2..2847b723d8 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/cane.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/cane.yml @@ -53,7 +53,7 @@ - type: DisarmMalus - type: entity - parent: Cane + parent: [Cane, VoiceLock] id: CaneSheath suffix: Empty components: @@ -69,6 +69,9 @@ interfaces: enum.StorageUiKey.Key: type: StorageBoundUserInterface + - type: ItemSlotsLock + slots: + - item - type: ItemSlots slots: item: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml index f879e2891e..f634425006 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml @@ -159,7 +159,7 @@ - type: entity name: pen - parent: BaseMeleeWeaponEnergy + parent: [BaseMeleeWeaponEnergy, VoiceLock] id: EnergyDagger suffix: E-Dagger description: 'A dark ink pen.' @@ -222,6 +222,16 @@ damage: types: Blunt: 1 + - type: EmitSoundOnUse + sound: + path: /Audio/Items/pen_click.ogg + params: + volume: -4 + maxDistance: 2 + - type: UseDelay + delay: 1.5 + - type: ItemToggleRequiresLock # TODO: FIX THIS VERB IS COOKED + lockedPopup: null - type: Tag tags: - Write diff --git a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml index 0662094143..2ab12c1705 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml @@ -37,7 +37,9 @@ 3: { state: can-o3, shader: "unshaded" } - type: ActivatableUI key: enum.GasCanisterUiKey.Key - - type: ActivatableUIRequiresLock + - type: UIRequiresLock + userInterfaceKeys: + - enum.GasCanisterUiKey.Key - type: UserInterface interfaces: enum.GasCanisterUiKey.Key: diff --git a/Resources/Prototypes/chameleon.yml b/Resources/Prototypes/chameleon.yml new file mode 100644 index 0000000000..6969380621 --- /dev/null +++ b/Resources/Prototypes/chameleon.yml @@ -0,0 +1,15 @@ +# for clothing that can be toggled, like magboots +- type: entity + parent: VoiceLock + abstract: true + id: BaseChameleon + components: + - type: UIRequiresLock + userInterfaceKeys: + - enum.ChameleonUiKey.Key + accessDeniedSound: null + popup: null + - type: UserInterface + interfaces: + enum.ChameleonUiKey.Key: + type: ChameleonBoundUserInterface From 2aca7f62dc611e6d4b0e6f2a310118644043e3cc Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 10 Aug 2025 18:11:20 +0000 Subject: [PATCH 07/40] Automatic changelog update --- Resources/Changelog/Changelog.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 1470928b69..00dfe85b31 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: SG6732 - changes: - - message: Added meat patties. Get the charcoal and let's start grillin. - type: Add - id: 8330 - time: '2025-04-24T03:45:27.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/34896 - author: DrMelon, EmoGarbage404 changes: - message: Added turnstiles, gates able to limit the flow of foot traffic. The station's @@ -3899,3 +3892,16 @@ id: 8842 time: '2025-08-10T17:49:29.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/39084 +- author: beck-thompson + changes: + - message: Various syndicate items can now be "locked" with your voice to hide their + true identity. The items are, all chameleon clothing, voice masks, no slips, + agent IDs, cane swords, and e daggers. + type: Add + - message: e dagger now has a slightly longer activation cool down (1s -> 1.5s). + type: Tweak + - message: Voice triggered items now properly display the trigger text when examined. + type: Fix + id: 8843 + time: '2025-08-10T18:10:13.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39310 From 7825d30562bb20a589849f1309713a39d8796718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=81da?= Date: Sun, 10 Aug 2025 14:59:32 -0500 Subject: [PATCH 08/40] Fire stacks trigger (#39530) * Simple as * whoops * not gonna work * chopped * missed something * Better name * formatting --------- Co-authored-by: iaada Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../Systems/FlameStackOnTriggerSystem.cs | 37 +++++++++++++++++++ .../Trigger/Systems/IgniteOnTriggerSystem.cs | 1 + .../Effects/FlameStackOnTriggerComponent.cs | 26 +++++++++++++ .../Effects/IgniteOnTriggerComponent.cs | 1 + 4 files changed, 65 insertions(+) create mode 100644 Content.Server/Trigger/Systems/FlameStackOnTriggerSystem.cs create mode 100644 Content.Shared/Trigger/Components/Effects/FlameStackOnTriggerComponent.cs diff --git a/Content.Server/Trigger/Systems/FlameStackOnTriggerSystem.cs b/Content.Server/Trigger/Systems/FlameStackOnTriggerSystem.cs new file mode 100644 index 0000000000..879d57b845 --- /dev/null +++ b/Content.Server/Trigger/Systems/FlameStackOnTriggerSystem.cs @@ -0,0 +1,37 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Trigger; +using Content.Shared.Trigger.Components.Effects; + +namespace Content.Server.Trigger.Systems; + +/// +/// Trigger system for setting something on fire. +/// +/// +public sealed class FlameStackOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly FlammableSystem _flame = default!; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + _flame.AdjustFireStacks(target.Value, ent.Comp.FireStacks, ignite: ent.Comp.DoIgnite); + + args.Handled = true; + } +} diff --git a/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs b/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs index f4d88b774a..b19f4738ca 100644 --- a/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs +++ b/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs @@ -8,6 +8,7 @@ namespace Content.Server.Trigger.Systems; /// /// Handles igniting when triggered and stopping ignition after the delay. /// +/// public sealed class IgniteOnTriggerSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; diff --git a/Content.Shared/Trigger/Components/Effects/FlameStackOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/FlameStackOnTriggerComponent.cs new file mode 100644 index 0000000000..f7186ae003 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/FlameStackOnTriggerComponent.cs @@ -0,0 +1,26 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Adjusts fire stacks on trigger, optionally setting them on fire as well. +/// Requires to ignite the target. +/// If TargetUser is true they will have their firestacks adjusted instead. +/// +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class FlameStackOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// How many fire stacks to add or remove. + /// + [DataField, AutoNetworkedField] + public float FireStacks; + + /// + /// If true, the target will be set on fire if it isn't already. + /// If false does nothing. + /// + [DataField, AutoNetworkedField] + public bool DoIgnite = true; +} diff --git a/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs index 36273ef1b2..e9f51cf4e7 100644 --- a/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs +++ b/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs @@ -8,6 +8,7 @@ namespace Content.Shared.Trigger.Components.Effects; /// Requires along with triggering components. /// The if TargetUser is true they will be ignited instead (they need IgnitionSourceComponent as well). /// +/// [RegisterComponent, NetworkedComponent] [AutoGenerateComponentState, AutoGenerateComponentPause] public sealed partial class IgniteOnTriggerComponent : BaseXOnTriggerComponent From 458e2d222c4c9cb133a46f59df0346b49c038b5a Mon Sep 17 00:00:00 2001 From: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Date: Sun, 10 Aug 2025 13:55:13 -0700 Subject: [PATCH 09/40] Status Effect Alerts and Time Bugfixes (#39529) * Bugefix * Clean up --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> --- .../StatusEffectNew/StatusEffectSystem.API.cs | 4 +- .../StatusEffectNew/StatusEffectsSystem.cs | 75 ++++++++----------- 2 files changed, 32 insertions(+), 47 deletions(-) diff --git a/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs b/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs index 2144b5a0c1..56636c9601 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs @@ -69,7 +69,7 @@ public sealed partial class StatusEffectsSystem if (!TryGetStatusEffect(target, effectProto, out statusEffect)) return TryAddStatusEffect(target, effectProto, out statusEffect, duration); - SetStatusEffectTime(statusEffect.Value, duration); + SetStatusEffectEndTime(statusEffect.Value, duration); return true; } @@ -291,7 +291,7 @@ public sealed partial class StatusEffectsSystem var meta = MetaData(effect); if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto) { - SetStatusEffectTime(effect, time); + SetStatusEffectEndTime(effect, time); return true; } } diff --git a/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs b/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs index 1ffb74570a..b385a12fb8 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs @@ -125,47 +125,6 @@ public sealed partial class StatusEffectsSystem : EntitySystem PredictedQueueDel(ent.Owner); } - private void SetStatusEffectTime(EntityUid effect, TimeSpan? duration) - { - if (!_effectQuery.TryComp(effect, out var effectComp)) - return; - - if (duration is null) - { - if(effectComp.EndEffectTime is null) - return; - - effectComp.EndEffectTime = null; - } - else - effectComp.EndEffectTime = _timing.CurTime + duration; - - Dirty(effect, effectComp); - } - - private void UpdateStatusEffectTime(EntityUid effect, TimeSpan? duration) - { - if (!_effectQuery.TryComp(effect, out var effectComp)) - return; - - // It's already infinitely long - if (effectComp.EndEffectTime is null) - return; - - if (duration is null) - effectComp.EndEffectTime = null; - else - { - var newEndTime = _timing.CurTime + duration; - if (effectComp.EndEffectTime >= newEndTime) - return; - - effectComp.EndEffectTime = newEndTime; - } - - Dirty(effect, effectComp); - } - public bool CanAddStatusEffect(EntityUid uid, EntProtoId effectProto) { if (!_proto.TryIndex(effectProto, out var effectProtoData)) @@ -227,13 +186,39 @@ public sealed partial class StatusEffectsSystem : EntitySystem return true; } - private void AddStatusEffectTime(EntityUid effect, TimeSpan delta) + private void UpdateStatusEffectTime(Entity effect, TimeSpan? duration) { - if (!_effectQuery.TryComp(effect, out var effectComp)) + if (!_effectQuery.Resolve(effect, ref effect.Comp)) return; - // If we don't have an end time set, we want to just make the status effect end in delta time from now. - SetStatusEffectEndTime((effect, effectComp), (effectComp.EndEffectTime ?? _timing.CurTime) + delta); + // It's already infinitely long + if (effect.Comp.EndEffectTime is null) + return; + + TimeSpan? newEndTime = null; + + if (duration is not null) + { + // Don't update time to a smaller timespan... + newEndTime = _timing.CurTime + duration; + if (effect.Comp.EndEffectTime >= newEndTime) + return; + } + + SetStatusEffectEndTime(effect, newEndTime); + } + + private void AddStatusEffectTime(Entity effect, TimeSpan delta) + { + if (!_effectQuery.Resolve(effect, ref effect.Comp)) + return; + + // It's already infinitely long can't add or subtract from infinity... + if (effect.Comp.EndEffectTime is null) + return; + + // Add to the current end effect time, if we're here we should have one set already, and if it's null it's probably infinite. + SetStatusEffectEndTime((effect, effect.Comp), effect.Comp.EndEffectTime.Value + delta); } private void SetStatusEffectEndTime(Entity ent, TimeSpan? endTime) From 46a0cd9057810e0eefdb617285eabeb880fa5ba3 Mon Sep 17 00:00:00 2001 From: Jessica M Date: Sun, 10 Aug 2025 15:20:26 -0700 Subject: [PATCH 10/40] Adds rare Hamlet variant: Fragile Hamlet (#39531) * slippery gib hamlet * add suffix for easier admeme * address review * increase odds * parent MobHamsterHamlet, predicted emitsound --------- Co-authored-by: Jessica M --- .../Prototypes/Catalog/Fills/Crates/npc.yml | 5 +++++ Resources/Prototypes/Entities/Mobs/NPCs/pets.yml | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Resources/Prototypes/Catalog/Fills/Crates/npc.yml b/Resources/Prototypes/Catalog/Fills/Crates/npc.yml index 10c715bb99..403eb1a01d 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/npc.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/npc.yml @@ -233,6 +233,11 @@ - type: StorageFill contents: - id: MobHamsterHamlet + prob: 1 + orGroup: MobHamsterHamlet + - id: MobHamsterHamletSlippery + prob: 0.001 + orGroup: MobHamsterHamlet - type: entity id: CrateNPCLizard diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index dcfd6b41fc..caeb4f4446 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -662,6 +662,22 @@ - meaty - sadness +- type: entity + parent: MobHamsterHamlet + id: MobHamsterHamletSlippery + suffix: Slippery + components: + - type: Slippery + - type: StepTrigger + requiredTriggeredSpeed: 1 + - type: TriggerOnStepTrigger + - type: GibOnTrigger + - type: EmitSoundOnTrigger + sound: + path: "/Audio/Animals/mouse_squeak.ogg" + positional: true + predicted: true + - type: entity name: Shiva parent: MobGiantSpider From 936831bfe0456d249a2146f316582d83872cd9b9 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 10 Aug 2025 22:21:36 +0000 Subject: [PATCH 11/40] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 00dfe85b31..25dc9e9f98 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: DrMelon, EmoGarbage404 - changes: - - message: Added turnstiles, gates able to limit the flow of foot traffic. The station's - flow is at your fingertips. - type: Add - id: 8331 - time: '2025-04-24T11:39:40.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/36313 - author: EmoGarbage404 changes: - message: Added prisoner closets. These create automatic ID's which allow you to @@ -3905,3 +3897,10 @@ id: 8843 time: '2025-08-10T18:10:13.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/39310 +- author: jessicamaybe + changes: + - message: 'Added a rare Hamlet variant: Fragile Hamlet' + type: Add + id: 8844 + time: '2025-08-10T22:20:26.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39531 From ad5fe5678c5415e5d3fbbce9e56871324058d578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=81da?= Date: Mon, 11 Aug 2025 02:44:36 -0500 Subject: [PATCH 12/40] Trigger on round end (#39545) * works if it works * small rewording --------- Co-authored-by: iaada Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../Triggers/TriggerOnRoundEndComponent.cs | 9 ++++++ .../Systems/TriggerOnRoundEndSystem.cs | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnRoundEndComponent.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerOnRoundEndSystem.cs diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnRoundEndComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnRoundEndComponent.cs new file mode 100644 index 0000000000..29a9643ca7 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnRoundEndComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers the entity when the round ends, i.e. the scoreboard appears and post-round begins. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnRoundEndComponent : BaseTriggerOnXComponent; diff --git a/Content.Shared/Trigger/Systems/TriggerOnRoundEndSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnRoundEndSystem.cs new file mode 100644 index 0000000000..c18fb08f3f --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerOnRoundEndSystem.cs @@ -0,0 +1,31 @@ +using Content.Shared.GameTicking; +using Content.Shared.Trigger.Components.Triggers; + +namespace Content.Shared.Trigger.Systems; + +/// +/// System for creating a trigger when the round ends. +/// +public sealed class TriggerOnRoundEndSystem : EntitySystem +{ + [Dependency] private readonly TriggerSystem _trigger = default!; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRoundEnd); + } + + private void OnRoundEnd(RoundEndMessageEvent args) + { + var triggerQuery = EntityQueryEnumerator(); + + // trigger everything with the component + while (triggerQuery.MoveNext(out var uid, out var comp)) + { + _trigger.Trigger(uid, null, comp.KeyOut); + } + } +} From 3654fcf5ddb194aa749dd6ab9b324a8934e0f70f Mon Sep 17 00:00:00 2001 From: Perry Fraser Date: Mon, 11 Aug 2025 07:00:12 -0400 Subject: [PATCH 13/40] fix: reform dionas via SpawnNextToOrDrop (#39505) --- Content.Shared/Species/Systems/ReformSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Shared/Species/Systems/ReformSystem.cs b/Content.Shared/Species/Systems/ReformSystem.cs index 943432522d..285d36840f 100644 --- a/Content.Shared/Species/Systems/ReformSystem.cs +++ b/Content.Shared/Species/Systems/ReformSystem.cs @@ -90,7 +90,7 @@ public sealed partial class ReformSystem : EntitySystem // Spawn a new entity // This is, to an extent, taken from polymorph. I don't use polymorph for various reasons- most notably that this is permanent. - var child = Spawn(comp.ReformPrototype, Transform(uid).Coordinates); + var child = SpawnNextToOrDrop(comp.ReformPrototype, uid); // This transfers the mind to the new entity if (_mindSystem.TryGetMind(uid, out var mindId, out var mind)) @@ -106,7 +106,7 @@ public sealed partial class ReformSystem : EntitySystem } public sealed partial class ReformEvent : InstantActionEvent { } - + [Serializable, NetSerializable] public sealed partial class ReformDoAfterEvent : SimpleDoAfterEvent { } } From 8d0a174b43d5efc0962c929aeeb835272671c556 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 11 Aug 2025 11:01:20 +0000 Subject: [PATCH 14/40] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 25dc9e9f98..cc30aa756d 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,13 +1,4 @@ Entries: -- author: EmoGarbage404 - changes: - - message: Added prisoner closets. These create automatic ID's which allow you to - quickly handle the transfer of prisoners in and out of genpop cells while keeping - their belongings secure. Imprison people today! - type: Add - id: 8332 - time: '2025-04-24T14:32:11.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/36392 - author: Southbridge changes: - message: Colorful Light Crates have been added and can be ordered. @@ -3904,3 +3895,10 @@ id: 8844 time: '2025-08-10T22:20:26.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/39531 +- author: perryprog + changes: + - message: "Diona\_nymphs that reform inside of a locker will no longer be invisible." + type: Fix + id: 8845 + time: '2025-08-11T11:00:12.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39505 From d7295d1ae4644fb129ac704dcec9c9cdbad0aa83 Mon Sep 17 00:00:00 2001 From: Red <96445749+TheShuEd@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:52:20 +0300 Subject: [PATCH 15/40] Actions examine (#39558) --- .../Actions/UI/ActionAlertTooltip.cs | 13 +------- .../Systems/Actions/Controls/ActionButton.cs | 30 ++++++++----------- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/Content.Client/Actions/UI/ActionAlertTooltip.cs b/Content.Client/Actions/UI/ActionAlertTooltip.cs index 2425cdefb9..664a67b406 100644 --- a/Content.Client/Actions/UI/ActionAlertTooltip.cs +++ b/Content.Client/Actions/UI/ActionAlertTooltip.cs @@ -21,7 +21,7 @@ namespace Content.Client.Actions.UI /// public (TimeSpan Start, TimeSpan End)? Cooldown { get; set; } - public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null, FormattedMessage? charges = null) + public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null) { _gameTiming = IoCManager.Resolve(); @@ -52,17 +52,6 @@ namespace Content.Client.Actions.UI vbox.AddChild(description); } - if (charges != null && !string.IsNullOrWhiteSpace(charges.ToString())) - { - var chargesLabel = new RichTextLabel - { - MaxWidth = TooltipTextMaxWidth, - StyleClasses = { StyleNano.StyleClassTooltipActionCharges } - }; - chargesLabel.SetMessage(charges); - vbox.AddChild(chargesLabel); - } - vbox.AddChild(_cooldownLabel = new RichTextLabel { MaxWidth = TooltipTextMaxWidth, diff --git a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs index cad9045fa8..be3af28b15 100644 --- a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs +++ b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs @@ -3,12 +3,12 @@ using Content.Client.Actions; using Content.Client.Actions.UI; using Content.Client.Cooldown; using Content.Client.Stylesheets; -using Content.Shared.Actions; using Content.Shared.Actions.Components; -using Content.Shared.Charges.Components; using Content.Shared.Charges.Systems; +using Content.Shared.Examine; using Robust.Client.GameObjects; using Robust.Client.Graphics; +using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; @@ -23,9 +23,9 @@ namespace Content.Client.UserInterface.Systems.Actions.Controls; public sealed class ActionButton : Control, IEntityControl { private IEntityManager _entities; + private IPlayerManager _player; private SpriteSystem? _spriteSys; private ActionUIController? _controller; - private SharedChargesSystem _sharedChargesSys; private bool _beingHovered; private bool _depressed; private bool _toggled; @@ -67,8 +67,8 @@ public sealed class ActionButton : Control, IEntityControl // TODO why is this constructor so slooooow. The rest of the code is fine _entities = entities; + _player = IoCManager.Resolve(); _spriteSys = spriteSys; - _sharedChargesSys = _entities.System(); _controller = controller; MouseFilter = MouseFilterMode.Pass; @@ -197,23 +197,17 @@ public sealed class ActionButton : Control, IEntityControl return null; var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName)); - var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription)); - FormattedMessage? chargesText = null; + var desc = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription)); - // TODO: Don't touch this use an event make callers able to add their own shit for actions or I kill you. - if (_entities.TryGetComponent(Action, out LimitedChargesComponent? actionCharges)) - { - var charges = _sharedChargesSys.GetCurrentCharges((Action.Value, actionCharges, null)); - chargesText = FormattedMessage.FromMarkupPermissive(Loc.GetString($"Charges: {charges.ToString()}/{actionCharges.MaxCharges}")); + if (_player.LocalEntity is null) + return null; - if (_entities.TryGetComponent(Action, out AutoRechargeComponent? autoRecharge)) - { - var chargeTimeRemaining = _sharedChargesSys.GetNextRechargeTime((Action.Value, actionCharges, autoRecharge)); - chargesText.AddText(Loc.GetString($"{Environment.NewLine}Time Til Recharge: {chargeTimeRemaining}")); - } - } + var ev = new ExaminedEvent(desc, Action.Value, _player.LocalEntity.Value, true, !desc.IsEmpty); + _entities.EventBus.RaiseLocalEvent(Action.Value.Owner, ev); - return new ActionAlertTooltip(name, decr, charges: chargesText); + var newDesc = ev.GetTotalMessage(); + + return new ActionAlertTooltip(name, newDesc); } protected override void ControlFocusExited() From 9ecb8333f27acf05d1221d303532b83ca7816e0e Mon Sep 17 00:00:00 2001 From: kosticia Date: Mon, 11 Aug 2025 18:35:11 +0300 Subject: [PATCH 16/40] Predict suitsensor system (#39325) * adwadsdwasadwas * dev * fix * review * some more cleanup --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../Medical/SuitSensors/SuitSensorSystem.cs | 5 + .../Medical/SuitSensors/SuitSensorSystem.cs | 451 +---------------- .../SharedSuitSensor.cs | 0 .../SuitSensors/SharedSuitSensorSystem.cs | 468 ++++++++++++++++++ .../SuitSensors/SuitSensorComponent.cs | 16 +- 5 files changed, 498 insertions(+), 442 deletions(-) create mode 100644 Content.Client/Medical/SuitSensors/SuitSensorSystem.cs rename Content.Shared/Medical/{SuitSensor => SuitSensors}/SharedSuitSensor.cs (100%) create mode 100644 Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs rename {Content.Server => Content.Shared}/Medical/SuitSensors/SuitSensorComponent.cs (89%) diff --git a/Content.Client/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Client/Medical/SuitSensors/SuitSensorSystem.cs new file mode 100644 index 0000000000..75868e08d9 --- /dev/null +++ b/Content.Client/Medical/SuitSensors/SuitSensorSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Medical.SuitSensors; + +namespace Content.Client.Medical.SuitSensors; + +public sealed class SuitSensorSystem : SharedSuitSensorSystem; diff --git a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs index cb0fbd736c..7af093b178 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs +++ b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs @@ -1,65 +1,25 @@ -using System.Numerics; -using Content.Server.Access.Systems; using Content.Server.DeviceNetwork.Systems; using Content.Server.Emp; using Content.Server.Medical.CrewMonitoring; -using Content.Server.Popups; -using Content.Server.Station.Systems; -using Content.Shared.ActionBlocker; -using Content.Shared.Clothing; -using Content.Shared.Damage; -using Content.Shared.DeviceNetwork; -using Content.Shared.DoAfter; -using Content.Shared.Examine; -using Content.Shared.GameTicking; -using Content.Shared.Interaction; -using Content.Shared.Inventory; -using Content.Shared.Medical.SuitSensor; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; -using Content.Shared.Mobs.Systems; -using Content.Shared.Verbs; -using Robust.Shared.Containers; -using Robust.Shared.Map; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Timing; using Content.Shared.DeviceNetwork.Components; +using Content.Shared.Medical.SuitSensor; +using Content.Shared.Medical.SuitSensors; +using Robust.Shared.Timing; namespace Content.Server.Medical.SuitSensors; -public sealed class SuitSensorSystem : EntitySystem +public sealed class SuitSensorSystem : SharedSuitSensorSystem { [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!; - [Dependency] private readonly IdCardSystem _idCardSystem = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly StationSystem _stationSystem = default!; [Dependency] private readonly SingletonDeviceNetServerSystem _singletonServerSystem = default!; - [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; - [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; - [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly InventorySystem _inventory = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnPlayerSpawn); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnEquipped); - SubscribeLocalEvent(OnUnequipped); - SubscribeLocalEvent(OnExamine); - SubscribeLocalEvent>(OnVerb); - SubscribeLocalEvent(OnInsert); - SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnEmpPulse); SubscribeLocalEvent(OnEmpFinished); - SubscribeLocalEvent(OnSuitSensorDoAfter); } public override void Update(float frameTime) @@ -78,14 +38,13 @@ public sealed class SuitSensorSystem : EntitySystem if (curTime < sensor.NextUpdate) continue; - if (!CheckSensorAssignedStation(uid, sensor)) + if (!CheckSensorAssignedStation((uid, sensor))) continue; - // TODO: This would cause imprecision at different tick rates. - sensor.NextUpdate = curTime + sensor.UpdateRate; + sensor.NextUpdate += sensor.UpdateRate; // get sensor status - var status = GetSensorState(uid, sensor); + var status = GetSensorState((uid, sensor)); if (status == null) continue; @@ -112,399 +71,21 @@ public sealed class SuitSensorSystem : EntitySystem } } - /// - /// Checks whether the sensor is assigned to a station or not - /// and tries to assign an unassigned sensor to a station if it's currently on a grid - /// - /// True if the sensor is assigned to a station or assigning it was successful. False otherwise. - private bool CheckSensorAssignedStation(EntityUid uid, SuitSensorComponent sensor) - { - if (!sensor.StationId.HasValue && Transform(uid).GridUid == null) - return false; - - sensor.StationId = _stationSystem.GetOwningStation(uid); - return sensor.StationId.HasValue; - } - - private void OnPlayerSpawn(PlayerSpawnCompleteEvent ev) - { - // If the player spawns in arrivals then the grid underneath them may not be appropriate. - // in which case we'll just use the station spawn code told us they are attached to and set all of their - // sensors. - var sensorQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - RecursiveSensor(ev.Mob, ev.Station, sensorQuery, xformQuery); - } - - private void RecursiveSensor(EntityUid uid, EntityUid stationUid, EntityQuery sensorQuery, EntityQuery xformQuery) - { - var xform = xformQuery.GetComponent(uid); - var enumerator = xform.ChildEnumerator; - - while (enumerator.MoveNext(out var child)) - { - if (sensorQuery.TryGetComponent(child, out var sensor)) - { - sensor.StationId = stationUid; - } - - RecursiveSensor(child, stationUid, sensorQuery, xformQuery); - } - } - - private void OnMapInit(EntityUid uid, SuitSensorComponent component, MapInitEvent args) - { - // Fallback - component.StationId ??= _stationSystem.GetOwningStation(uid); - - // generate random mode - if (component.RandomMode) - { - //make the sensor mode favor higher levels, except coords. - var modesDist = new[] - { - SuitSensorMode.SensorOff, - SuitSensorMode.SensorBinary, SuitSensorMode.SensorBinary, - SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, - SuitSensorMode.SensorCords, SuitSensorMode.SensorCords - }; - component.Mode = _random.Pick(modesDist); - } - } - - private void OnEquipped(EntityUid uid, SuitSensorComponent component, ref ClothingGotEquippedEvent args) - { - component.User = args.Wearer; - } - - private void OnUnequipped(EntityUid uid, SuitSensorComponent component, ref ClothingGotUnequippedEvent args) - { - component.User = null; - } - - private void OnExamine(EntityUid uid, SuitSensorComponent component, ExaminedEvent args) - { - if (!args.IsInDetailsRange) - return; - - string msg; - switch (component.Mode) - { - case SuitSensorMode.SensorOff: - msg = "suit-sensor-examine-off"; - break; - case SuitSensorMode.SensorBinary: - msg = "suit-sensor-examine-binary"; - break; - case SuitSensorMode.SensorVitals: - msg = "suit-sensor-examine-vitals"; - break; - case SuitSensorMode.SensorCords: - msg = "suit-sensor-examine-cords"; - break; - default: - return; - } - - args.PushMarkup(Loc.GetString(msg)); - } - - private void OnVerb(EntityUid uid, SuitSensorComponent component, GetVerbsEvent args) - { - // check if user can change sensor - if (component.ControlsLocked) - return; - - // standard interaction checks - if (!args.CanInteract || args.Hands == null) - return; - - if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target)) - return; - - // check if target is incapacitated (cuffed, dead, etc) - if (component.User != null && args.User != component.User && _actionBlocker.CanInteract(component.User.Value, null)) - return; - - args.Verbs.UnionWith(new[] - { - CreateVerb(uid, component, args.User, SuitSensorMode.SensorOff), - CreateVerb(uid, component, args.User, SuitSensorMode.SensorBinary), - CreateVerb(uid, component, args.User, SuitSensorMode.SensorVitals), - CreateVerb(uid, component, args.User, SuitSensorMode.SensorCords) - }); - } - - private void OnInsert(EntityUid uid, SuitSensorComponent component, EntGotInsertedIntoContainerMessage args) - { - if (args.Container.ID != component.ActivationContainer) - return; - - component.User = args.Container.Owner; - } - - private void OnRemove(EntityUid uid, SuitSensorComponent component, EntGotRemovedFromContainerMessage args) - { - if (args.Container.ID != component.ActivationContainer) - return; - - component.User = null; - } - - private void OnEmpPulse(EntityUid uid, SuitSensorComponent component, ref EmpPulseEvent args) + private void OnEmpPulse(Entity ent, ref EmpPulseEvent args) { args.Affected = true; args.Disabled = true; - component.PreviousMode = component.Mode; - SetSensor((uid, component), SuitSensorMode.SensorOff, null); + ent.Comp.PreviousMode = ent.Comp.Mode; + SetSensor(ent.AsNullable(), SuitSensorMode.SensorOff, null); - component.PreviousControlsLocked = component.ControlsLocked; - component.ControlsLocked = true; + ent.Comp.PreviousControlsLocked = ent.Comp.ControlsLocked; + ent.Comp.ControlsLocked = true; } - private void OnEmpFinished(EntityUid uid, SuitSensorComponent component, ref EmpDisabledRemoved args) + private void OnEmpFinished(Entity ent, ref EmpDisabledRemoved args) { - SetSensor((uid, component), component.PreviousMode, null); - component.ControlsLocked = component.PreviousControlsLocked; - } - - private Verb CreateVerb(EntityUid uid, SuitSensorComponent component, EntityUid userUid, SuitSensorMode mode) - { - return new Verb() - { - Text = GetModeName(mode), - Disabled = component.Mode == mode, - Priority = -(int) mode, // sort them in descending order - Category = VerbCategory.SetSensor, - Act = () => TrySetSensor((uid, component), mode, userUid) - }; - } - - private string GetModeName(SuitSensorMode mode) - { - string name; - switch (mode) - { - case SuitSensorMode.SensorOff: - name = "suit-sensor-mode-off"; - break; - case SuitSensorMode.SensorBinary: - name = "suit-sensor-mode-binary"; - break; - case SuitSensorMode.SensorVitals: - name = "suit-sensor-mode-vitals"; - break; - case SuitSensorMode.SensorCords: - name = "suit-sensor-mode-cords"; - break; - default: - return ""; - } - - return Loc.GetString(name); - } - - public void TrySetSensor(Entity sensors, SuitSensorMode mode, EntityUid userUid) - { - var comp = sensors.Comp; - - if (!Resolve(sensors, ref comp)) - return; - - if (comp.User == null || userUid == comp.User) - SetSensor(sensors, mode, userUid); - else - { - var doAfterEvent = new SuitSensorChangeDoAfterEvent(mode); - var doAfterArgs = new DoAfterArgs(EntityManager, userUid, comp.SensorsTime, doAfterEvent, sensors) - { - BreakOnMove = true, - BreakOnDamage = true - }; - - _doAfterSystem.TryStartDoAfter(doAfterArgs); - } - } - - private void OnSuitSensorDoAfter(Entity sensors, ref SuitSensorChangeDoAfterEvent args) - { - if (args.Handled || args.Cancelled) - return; - - SetSensor(sensors, args.Mode, args.User); - } - - public void SetSensor(Entity sensors, SuitSensorMode mode, EntityUid? userUid = null) - { - var comp = sensors.Comp; - - comp.Mode = mode; - - if (userUid != null) - { - var msg = Loc.GetString("suit-sensor-mode-state", ("mode", GetModeName(mode))); - _popupSystem.PopupEntity(msg, sensors, userUid.Value); - } - } - - /// - /// Set all suit sensors on the equipment someone is wearing to the specified mode. - /// - public void SetAllSensors(EntityUid target, SuitSensorMode mode, SlotFlags slots = SlotFlags.All ) - { - // iterate over all inventory slots - var slotEnumerator = _inventory.GetSlotEnumerator(target, slots); - while (slotEnumerator.NextItem(out var item, out _)) - { - if (TryComp(item, out var sensorComp)) - SetSensor((item, sensorComp), mode); - } - } - - public SuitSensorStatus? GetSensorState(EntityUid uid, SuitSensorComponent? sensor = null, TransformComponent? transform = null) - { - if (!Resolve(uid, ref sensor, ref transform)) - return null; - - // check if sensor is enabled and worn by user - if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || !HasComp(sensor.User) || transform.GridUid == null) - return null; - - // try to get mobs id from ID slot - var userName = Loc.GetString("suit-sensor-component-unknown-name"); - var userJob = Loc.GetString("suit-sensor-component-unknown-job"); - var userJobIcon = "JobIconNoId"; - var userJobDepartments = new List(); - - if (_idCardSystem.TryFindIdCard(sensor.User.Value, out var card)) - { - if (card.Comp.FullName != null) - userName = card.Comp.FullName; - if (card.Comp.LocalizedJobTitle != null) - userJob = card.Comp.LocalizedJobTitle; - userJobIcon = card.Comp.JobIcon; - - foreach (var department in card.Comp.JobDepartments) - userJobDepartments.Add(Loc.GetString(_proto.Index(department).Name)); - } - - // get health mob state - var isAlive = false; - if (TryComp(sensor.User.Value, out MobStateComponent? mobState)) - isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState); - - // get mob total damage - var totalDamage = 0; - if (TryComp(sensor.User.Value, out var damageable)) - totalDamage = damageable.TotalDamage.Int(); - - // Get mob total damage crit threshold - int? totalDamageThreshold = null; - if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold)) - totalDamageThreshold = critThreshold.Value.Int(); - - // finally, form suit sensor status - var status = new SuitSensorStatus(GetNetEntity(sensor.User.Value), GetNetEntity(uid), userName, userJob, userJobIcon, userJobDepartments); - switch (sensor.Mode) - { - case SuitSensorMode.SensorBinary: - status.IsAlive = isAlive; - break; - case SuitSensorMode.SensorVitals: - status.IsAlive = isAlive; - status.TotalDamage = totalDamage; - status.TotalDamageThreshold = totalDamageThreshold; - break; - case SuitSensorMode.SensorCords: - status.IsAlive = isAlive; - status.TotalDamage = totalDamage; - status.TotalDamageThreshold = totalDamageThreshold; - EntityCoordinates coordinates; - var xformQuery = GetEntityQuery(); - - if (transform.GridUid != null) - { - coordinates = new EntityCoordinates(transform.GridUid.Value, - Vector2.Transform(_transform.GetWorldPosition(transform, xformQuery), - _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery))); - } - else if (transform.MapUid != null) - { - coordinates = new EntityCoordinates(transform.MapUid.Value, - _transform.GetWorldPosition(transform, xformQuery)); - } - else - { - coordinates = EntityCoordinates.Invalid; - } - - status.Coordinates = GetNetCoordinates(coordinates); - break; - } - - return status; - } - - /// - /// Serialize create a device network package from the suit sensors status. - /// - public NetworkPayload SuitSensorToPacket(SuitSensorStatus status) - { - var payload = new NetworkPayload() - { - [DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState, - [SuitSensorConstants.NET_NAME] = status.Name, - [SuitSensorConstants.NET_JOB] = status.Job, - [SuitSensorConstants.NET_JOB_ICON] = status.JobIcon, - [SuitSensorConstants.NET_JOB_DEPARTMENTS] = status.JobDepartments, - [SuitSensorConstants.NET_IS_ALIVE] = status.IsAlive, - [SuitSensorConstants.NET_SUIT_SENSOR_UID] = status.SuitSensorUid, - [SuitSensorConstants.NET_OWNER_UID] = status.OwnerUid, - }; - - if (status.TotalDamage != null) - payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage); - if (status.TotalDamageThreshold != null) - payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, status.TotalDamageThreshold); - if (status.Coordinates != null) - payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates); - - return payload; - } - - /// - /// Try to create the suit sensors status from the device network message - /// - public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload) - { - // check command - if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command)) - return null; - if (command != DeviceNetworkConstants.CmdUpdatedState) - return null; - - // check name, job and alive - if (!payload.TryGetValue(SuitSensorConstants.NET_NAME, out string? name)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_JOB, out string? job)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_ICON, out string? jobIcon)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_DEPARTMENTS, out List? jobDepartments)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_IS_ALIVE, out bool? isAlive)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_SUIT_SENSOR_UID, out NetEntity suitSensorUid)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_OWNER_UID, out NetEntity ownerUid)) return null; - - // try get total damage and cords (optionals) - payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage); - payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, out int? totalDamageThreshold); - payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords); - - var status = new SuitSensorStatus(ownerUid, suitSensorUid, name, job, jobIcon, jobDepartments) - { - IsAlive = isAlive.Value, - TotalDamage = totalDamage, - TotalDamageThreshold = totalDamageThreshold, - Coordinates = coords, - }; - return status; + SetSensor(ent.AsNullable(), ent.Comp.PreviousMode, null); + ent.Comp.ControlsLocked = ent.Comp.PreviousControlsLocked; } } diff --git a/Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs b/Content.Shared/Medical/SuitSensors/SharedSuitSensor.cs similarity index 100% rename from Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs rename to Content.Shared/Medical/SuitSensors/SharedSuitSensor.cs diff --git a/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs b/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs new file mode 100644 index 0000000000..2ed1089ba8 --- /dev/null +++ b/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs @@ -0,0 +1,468 @@ +using System.Numerics; +using Content.Shared.Access.Systems; +using Content.Shared.ActionBlocker; +using Content.Shared.Clothing; +using Content.Shared.Damage; +using Content.Shared.DeviceNetwork; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.GameTicking; +using Content.Shared.Interaction; +using Content.Shared.Inventory; +using Content.Shared.Medical.SuitSensor; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Popups; +using Content.Shared.Station; +using Content.Shared.Verbs; +using Robust.Shared.Containers; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Shared.Medical.SuitSensors; + +public abstract class SharedSuitSensorSystem : EntitySystem +{ + [Dependency] private readonly SharedStationSystem _stationSystem = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly SharedIdCardSystem _idCardSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + private EntityQuery _sensorQuery; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnPlayerSpawn); + SubscribeLocalEvent(OnEquipped); + SubscribeLocalEvent(OnUnequipped); + SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent>(OnVerb); + SubscribeLocalEvent(OnInsert); + SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnSuitSensorDoAfter); + + _sensorQuery = GetEntityQuery(); + } + + /// + /// Checks whether the sensor is assigned to a station or not + /// and tries to assign an unassigned sensor to a station if it's currently on a grid. + /// + /// True if the sensor is assigned to a station or assigning it was successful. False otherwise. + public bool CheckSensorAssignedStation(Entity sensor) + { + if (!sensor.Comp.StationId.HasValue && Transform(sensor.Owner).GridUid == null) + return false; + + sensor.Comp.StationId = _stationSystem.GetOwningStation(sensor.Owner); + Dirty(sensor); + return sensor.Comp.StationId.HasValue; + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + // Fallback + ent.Comp.StationId ??= _stationSystem.GetOwningStation(ent.Owner); + + // generate random mode + if (ent.Comp.RandomMode) + { + //make the sensor mode favor higher levels, except coords. + var modesDist = new[] + { + SuitSensorMode.SensorOff, + SuitSensorMode.SensorBinary, SuitSensorMode.SensorBinary, + SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, + SuitSensorMode.SensorCords, SuitSensorMode.SensorCords + }; + ent.Comp.Mode = _random.Pick(modesDist); + } + + ent.Comp.NextUpdate = _timing.CurTime; + Dirty(ent); + } + + private void OnPlayerSpawn(PlayerSpawnCompleteEvent ev) + { + // If the player spawns in arrivals then the grid underneath them may not be appropriate. + // in which case we'll just use the station spawn code told us they are attached to and set all of their + // sensors. + RecursiveSensor(ev.Mob, ev.Station); + } + + private void RecursiveSensor(EntityUid uid, EntityUid stationUid) + { + var xform = Transform(uid); + var enumerator = xform.ChildEnumerator; + + while (enumerator.MoveNext(out var child)) + { + if (_sensorQuery.TryComp(child, out var sensor)) + { + sensor.StationId = stationUid; + Dirty(child, sensor); + } + + RecursiveSensor(child, stationUid); + } + } + + private void OnEquipped(Entity ent, ref ClothingGotEquippedEvent args) + { + ent.Comp.User = args.Wearer; + Dirty(ent); + } + + private void OnUnequipped(Entity ent, ref ClothingGotUnequippedEvent args) + { + ent.Comp.User = null; + Dirty(ent); + } + + private void OnExamine(Entity ent, ref ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + string msg; + switch (ent.Comp.Mode) + { + case SuitSensorMode.SensorOff: + msg = "suit-sensor-examine-off"; + break; + case SuitSensorMode.SensorBinary: + msg = "suit-sensor-examine-binary"; + break; + case SuitSensorMode.SensorVitals: + msg = "suit-sensor-examine-vitals"; + break; + case SuitSensorMode.SensorCords: + msg = "suit-sensor-examine-cords"; + break; + default: + return; + } + + args.PushMarkup(Loc.GetString(msg)); + } + + private void OnVerb(Entity ent, ref GetVerbsEvent args) + { + // check if user can change sensor + if (ent.Comp.ControlsLocked) + return; + + // standard interaction checks + if (!args.CanInteract || args.Hands == null) + return; + + if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target)) + return; + + // check if target is incapacitated (cuffed, dead, etc) + if (ent.Comp.User != null && args.User != ent.Comp.User && _actionBlocker.CanInteract(ent.Comp.User.Value, null)) + return; + + args.Verbs.UnionWith(new[] + { + CreateVerb(ent, args.User, SuitSensorMode.SensorOff), + CreateVerb(ent, args.User, SuitSensorMode.SensorBinary), + CreateVerb(ent, args.User, SuitSensorMode.SensorVitals), + CreateVerb(ent, args.User, SuitSensorMode.SensorCords) + }); + } + + private void OnInsert(Entity ent, ref EntGotInsertedIntoContainerMessage args) + { + if (args.Container.ID != ent.Comp.ActivationContainer) + return; + + ent.Comp.User = args.Container.Owner; + Dirty(ent); + } + + private void OnRemove(Entity ent, ref EntGotRemovedFromContainerMessage args) + { + if (args.Container.ID != ent.Comp.ActivationContainer) + return; + + ent.Comp.User = null; + Dirty(ent); + } + + private Verb CreateVerb(Entity ent, EntityUid userUid, SuitSensorMode mode) + { + return new Verb() + { + Text = GetModeName(mode), + Disabled = ent.Comp.Mode == mode, + Priority = -(int)mode, // sort them in descending order + Category = VerbCategory.SetSensor, + Act = () => TrySetSensor(ent.AsNullable(), mode, userUid) + }; + } + + public string GetModeName(SuitSensorMode mode) + { + string name; + switch (mode) + { + case SuitSensorMode.SensorOff: + name = "suit-sensor-mode-off"; + break; + case SuitSensorMode.SensorBinary: + name = "suit-sensor-mode-binary"; + break; + case SuitSensorMode.SensorVitals: + name = "suit-sensor-mode-vitals"; + break; + case SuitSensorMode.SensorCords: + name = "suit-sensor-mode-cords"; + break; + default: + return ""; + } + + return Loc.GetString(name); + } + + /// + /// Attempts to set mode of the entity to the selected in params. + /// Works instantly if the user is the player wearing the sensors and will start a DoAfter otherwise. + /// + /// Entity and its component that should be changed. + /// Selected mode + /// userUid, when not equal to the , creates doafter + public bool TrySetSensor(Entity sensors, SuitSensorMode mode, EntityUid userUid) + { + if (!Resolve(sensors, ref sensors.Comp, false)) + return false; + + if (sensors.Comp.User == null || userUid == sensors.Comp.User) + SetSensor(sensors, mode, userUid); + else + { + var doAfterEvent = new SuitSensorChangeDoAfterEvent(mode); + var doAfterArgs = new DoAfterArgs(EntityManager, userUid, sensors.Comp.SensorsTime, doAfterEvent, sensors) + { + BreakOnMove = true, + BreakOnDamage = true + }; + + _doAfterSystem.TryStartDoAfter(doAfterArgs); + } + return true; + } + + private void OnSuitSensorDoAfter(Entity sensors, ref SuitSensorChangeDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + SetSensor(sensors.AsNullable(), args.Mode, args.User); + } + + /// + /// Sets mode of the of the chosen entity. + /// Makes popup when not null + /// + /// Entity and it's component that should be changed + /// Selected mode + /// uid, required for the popup + public void SetSensor(Entity sensors, SuitSensorMode mode, EntityUid? userUid = null) + { + if (!Resolve(sensors, ref sensors.Comp, false)) + return; + + sensors.Comp.Mode = mode; + Dirty(sensors); + + if (userUid != null) + { + var msg = Loc.GetString("suit-sensor-mode-state", ("mode", GetModeName(mode))); + _popupSystem.PopupClient(msg, sensors, userUid.Value); + } + } + + /// + /// Set all suit sensors on the equipment someone is wearing to the specified mode. + /// + public void SetAllSensors(EntityUid target, SuitSensorMode mode, SlotFlags slots = SlotFlags.All) + { + // iterate over all inventory slots + var slotEnumerator = _inventory.GetSlotEnumerator(target, slots); + while (slotEnumerator.NextItem(out var item, out _)) + { + if (TryComp(item, out var sensorComp)) + SetSensor((item, sensorComp), mode); + } + } + + /// + /// Attempts to get full from the + /// + /// Entity to get status + /// Full of the chosen uid + public SuitSensorStatus? GetSensorState(Entity ent) + { + if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2, false)) + return null; + + var sensor = ent.Comp1; + var transform = ent.Comp2; + + // check if sensor is enabled and worn by user + if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || !HasComp(sensor.User) || transform.GridUid == null) + return null; + + // try to get mobs id from ID slot + var userName = Loc.GetString("suit-sensor-component-unknown-name"); + var userJob = Loc.GetString("suit-sensor-component-unknown-job"); + var userJobIcon = "JobIconNoId"; + var userJobDepartments = new List(); + + if (_idCardSystem.TryFindIdCard(sensor.User.Value, out var card)) + { + if (card.Comp.FullName != null) + userName = card.Comp.FullName; + if (card.Comp.LocalizedJobTitle != null) + userJob = card.Comp.LocalizedJobTitle; + userJobIcon = card.Comp.JobIcon; + + foreach (var department in card.Comp.JobDepartments) + userJobDepartments.Add(Loc.GetString(_proto.Index(department).Name)); + } + + // get health mob state + var isAlive = false; + if (TryComp(sensor.User.Value, out MobStateComponent? mobState)) + isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState); + + // get mob total damage + var totalDamage = 0; + if (TryComp(sensor.User.Value, out var damageable)) + totalDamage = damageable.TotalDamage.Int(); + + // Get mob total damage crit threshold + int? totalDamageThreshold = null; + if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold)) + totalDamageThreshold = critThreshold.Value.Int(); + + // finally, form suit sensor status + var status = new SuitSensorStatus(GetNetEntity(sensor.User.Value), GetNetEntity(ent.Owner), userName, userJob, userJobIcon, userJobDepartments); + switch (sensor.Mode) + { + case SuitSensorMode.SensorBinary: + status.IsAlive = isAlive; + break; + case SuitSensorMode.SensorVitals: + status.IsAlive = isAlive; + status.TotalDamage = totalDamage; + status.TotalDamageThreshold = totalDamageThreshold; + break; + case SuitSensorMode.SensorCords: + status.IsAlive = isAlive; + status.TotalDamage = totalDamage; + status.TotalDamageThreshold = totalDamageThreshold; + EntityCoordinates coordinates; + var xformQuery = GetEntityQuery(); + + if (transform.GridUid != null) + { + coordinates = new EntityCoordinates(transform.GridUid.Value, + Vector2.Transform(_transform.GetWorldPosition(transform, xformQuery), + _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery))); + } + else if (transform.MapUid != null) + { + coordinates = new EntityCoordinates(transform.MapUid.Value, + _transform.GetWorldPosition(transform, xformQuery)); + } + else + { + coordinates = EntityCoordinates.Invalid; + } + + status.Coordinates = GetNetCoordinates(coordinates); + break; + } + + return status; + } + + /// + /// Create a device network package from the suit sensors status. + /// + public NetworkPayload SuitSensorToPacket(SuitSensorStatus status) + { + var payload = new NetworkPayload() + { + [DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState, + [SuitSensorConstants.NET_NAME] = status.Name, + [SuitSensorConstants.NET_JOB] = status.Job, + [SuitSensorConstants.NET_JOB_ICON] = status.JobIcon, + [SuitSensorConstants.NET_JOB_DEPARTMENTS] = status.JobDepartments, + [SuitSensorConstants.NET_IS_ALIVE] = status.IsAlive, + [SuitSensorConstants.NET_SUIT_SENSOR_UID] = status.SuitSensorUid, + [SuitSensorConstants.NET_OWNER_UID] = status.OwnerUid, + }; + + if (status.TotalDamage != null) + payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage); + if (status.TotalDamageThreshold != null) + payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, status.TotalDamageThreshold); + if (status.Coordinates != null) + payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates); + + return payload; + } + + /// + /// Try to create the suit sensors status from the device network message. + /// + public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload) + { + // check command + if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command)) + return null; + if (command != DeviceNetworkConstants.CmdUpdatedState) + return null; + + // check name, job and alive + if (!payload.TryGetValue(SuitSensorConstants.NET_NAME, out string? name)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_JOB, out string? job)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_ICON, out string? jobIcon)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_DEPARTMENTS, out List? jobDepartments)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_IS_ALIVE, out bool? isAlive)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_SUIT_SENSOR_UID, out NetEntity suitSensorUid)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_OWNER_UID, out NetEntity ownerUid)) return null; + + // try get total damage and cords (optionals) + payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage); + payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, out int? totalDamageThreshold); + payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords); + + var status = new SuitSensorStatus(ownerUid, suitSensorUid, name, job, jobIcon, jobDepartments) + { + IsAlive = isAlive.Value, + TotalDamage = totalDamage, + TotalDamageThreshold = totalDamageThreshold, + Coordinates = coords, + }; + return status; + } +} diff --git a/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs b/Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs similarity index 89% rename from Content.Server/Medical/SuitSensors/SuitSensorComponent.cs rename to Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs index 91039712e5..b20b7af2c9 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs +++ b/Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs @@ -1,14 +1,16 @@ using Content.Shared.Medical.SuitSensor; +using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.Medical.SuitSensors; +namespace Content.Shared.Medical.SuitSensors; /// /// Tracking device, embedded in almost all uniforms and jumpsuits. /// If enabled, will report to crew monitoring console owners position and status. /// -[RegisterComponent, AutoGenerateComponentPause] -[Access(typeof(SuitSensorSystem))] +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedSuitSensorSystem))] +[AutoGenerateComponentState, AutoGenerateComponentPause] public sealed partial class SuitSensorComponent : Component { /// @@ -20,7 +22,7 @@ public sealed partial class SuitSensorComponent : Component /// /// If true user can't change suit sensor mode /// - [DataField] + [DataField, AutoNetworkedField] public bool ControlsLocked = false; /// @@ -32,7 +34,7 @@ public sealed partial class SuitSensorComponent : Component /// /// Current sensor mode. Can be switched by user verbs. /// - [DataField] + [DataField, AutoNetworkedField] public SuitSensorMode Mode = SuitSensorMode.SensorOff; /// @@ -56,7 +58,7 @@ public sealed partial class SuitSensorComponent : Component /// /// Current user that wears suit sensor. Null if nobody wearing it. /// - [ViewVariables] + [DataField, AutoNetworkedField] public EntityUid? User = null; /// @@ -69,7 +71,7 @@ public sealed partial class SuitSensorComponent : Component /// /// The station this suit sensor belongs to. If it's null the suit didn't spawn on a station and the sensor doesn't work. /// - [DataField("station")] + [DataField("station"), AutoNetworkedField] public EntityUid? StationId = null; /// From 2a46fb474f65c0f159af350004020e7aebf1c741 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 11 Aug 2025 11:43:47 -0400 Subject: [PATCH 17/40] Oasis: Add atmos network monitor (#39331) --- Resources/Maps/oasis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Resources/Maps/oasis.yml b/Resources/Maps/oasis.yml index be47821c55..057dd96e4b 100644 --- a/Resources/Maps/oasis.yml +++ b/Resources/Maps/oasis.yml @@ -81884,6 +81884,14 @@ entities: - type: Transform pos: 10.5,-33.5 parent: 2 +- proto: ComputerAtmosMonitoring + entities: + - uid: 8792 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -40.5,3.5 + parent: 2 - proto: computerBodyScanner entities: - uid: 1069 From 6ae19340aba19f482228df7e2a8851a46ae3ff3e Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 11 Aug 2025 15:44:56 +0000 Subject: [PATCH 18/40] Automatic changelog update --- Resources/Changelog/Maps.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Resources/Changelog/Maps.yml b/Resources/Changelog/Maps.yml index b3e0833f7e..195d7b0e1a 100644 --- a/Resources/Changelog/Maps.yml +++ b/Resources/Changelog/Maps.yml @@ -503,4 +503,11 @@ id: 62 time: '2025-07-31T20:28:39.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/39295 +- author: F1restar4 + changes: + - message: On Oasis, added an atmospherics network monitor to atmos' front desk + type: Add + id: 63 + time: '2025-08-11T15:43:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39331 Order: 1 From 47d7db0665f31ac55f9b8eb6d7227fecbf495f9a Mon Sep 17 00:00:00 2001 From: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:13:27 +0200 Subject: [PATCH 19/40] Base changeling objective(s) (#39562) Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../Locale/en-US/changeling/changeling.ftl | 3 --- .../game-presets/preset-changeling.ftl | 13 +++++++++++ Resources/Prototypes/GameRules/roundstart.yml | 5 ++++ .../Prototypes/Objectives/changeling.yml | 23 +++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 Resources/Locale/en-US/game-ticking/game-presets/preset-changeling.ftl create mode 100644 Resources/Prototypes/Objectives/changeling.yml diff --git a/Resources/Locale/en-US/changeling/changeling.ftl b/Resources/Locale/en-US/changeling/changeling.ftl index 423cb0811e..d304385848 100644 --- a/Resources/Locale/en-US/changeling/changeling.ftl +++ b/Resources/Locale/en-US/changeling/changeling.ftl @@ -1,9 +1,6 @@ roles-antag-changeling-name = Changeling roles-antag-changeling-objective = A intelligent predator that assumes the identities of its victims. -changeling-role-greeting = You are a Changeling, a highly intelligent predator. Your only goal is to escape the station alive via assuming the identities of the denizens of this station. You are hungry and will not make it long without sustenance... kill, consume, hide, survive. -changeling-briefing = You are a changeling, your goal is to survive. Consume humanoids to gain biomass and utilize it to evade termination. You are able to utilize and assume the identities of those you consume to evade a grim fate. - changeling-devour-attempt-failed-rotting = This corpse has only rotted biomass. changeling-devour-attempt-failed-protected = This victim's biomass is protected. diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-changeling.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-changeling.ftl new file mode 100644 index 0000000000..32e79679cc --- /dev/null +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-changeling.ftl @@ -0,0 +1,13 @@ +changeling-role-greeting = + You are a Changeling, a highly intelligent predator. + Your primary goal is to escape the station alive via assuming the identities of the denizens of this station. + You are hungry and will not make it long without sustenance... + Kill, consume, hide, survive. + +changeling-briefing = + You are a changeling. + You are able to utilize and assume the identities of those you consume to evade a grim fate. + +objective-issuer-changeling = [color=#FA2A55]The Hivemind[/color] + +changeling-round-end-agent-name = changeling diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index 1748a4c5a3..a7c7af7f37 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -232,6 +232,8 @@ - type: GameRule minPlayers: 25 - type: AntagSelection + agentName: changeling-round-end-agent-name + selectionTime: IntraPlayerSpawn definitions: - prefRoles: [ Changeling ] max: 3 @@ -249,6 +251,9 @@ - ActionRetractableItemArmBlade # Temporary addition, will inevitably be a purchasable in the bio-store mindRoles: - MindRoleChangeling + - type: AntagObjectives + objectives: + - ChangelingSurviveObjective - type: entity id: Revolutionary diff --git a/Resources/Prototypes/Objectives/changeling.yml b/Resources/Prototypes/Objectives/changeling.yml new file mode 100644 index 0000000000..a1a4612217 --- /dev/null +++ b/Resources/Prototypes/Objectives/changeling.yml @@ -0,0 +1,23 @@ +- type: entity + abstract: true + parent: BaseObjective + id: BaseChangelingObjective + components: + - type: Objective + issuer: objective-issuer-changeling + difficulty: 1 + - type: RoleRequirement + roles: + - ChangelingRole + +- type: entity + parent: [BaseChangelingObjective, BaseSurviveObjective] + id: ChangelingSurviveObjective + name: Survive. + description: We must stay alive at all cost. + components: + - type: Objective + difficulty: 1 + icon: + sprite: Mobs/Species/Human/organs.rsi + state: heart-on From ed6f906e6f5d917061dd69f88dfd4731d2b2b5dc Mon Sep 17 00:00:00 2001 From: Samuka-C <47865393+Samuka-C@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:57:39 -0300 Subject: [PATCH 20/40] Better robotics console (#38023) --- .../Robotics/UI/RoboticsConsoleWindow.xaml.cs | 29 +++++++--- .../Robotics/Systems/RoboticsConsoleSystem.cs | 8 ++- .../Silicons/Borgs/BorgSystem.Transponder.cs | 54 +++++++++++++++++-- .../Components/RoboticsConsoleComponent.cs | 6 +++ Content.Shared/Robotics/RoboticsConsoleUi.cs | 19 +++++-- .../research/components/robotics-console.ftl | 1 + 6 files changed, 102 insertions(+), 15 deletions(-) diff --git a/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs index 06e5674d9c..24583d2776 100644 --- a/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs +++ b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs @@ -28,6 +28,8 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow public EntityUid Entity; + private bool _allowBorgControl = true; + public RoboticsConsoleWindow() { RobustXamlLoader.Load(this); @@ -72,6 +74,7 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow public void UpdateState(RoboticsConsoleState state) { _cyborgs = state.Cyborgs; + _allowBorgControl = state.AllowBorgControl; // clear invalid selection if (_selected is {} selected && !_cyborgs.ContainsKey(selected)) @@ -85,8 +88,8 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow PopulateData(); var locked = _lock.IsLocked(Entity); - DangerZone.Visible = !locked; - LockedMessage.Visible = locked; + DangerZone.Visible = !locked && _allowBorgControl; + LockedMessage.Visible = locked && _allowBorgControl; // Only show if locked AND control is allowed } private void PopulateCyborgs() @@ -120,11 +123,19 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow BorgSprite.Texture = _sprite.Frame0(data.ChassisSprite!); var batteryColor = data.Charge switch { - < 0.2f => "red", - < 0.4f => "orange", - < 0.6f => "yellow", - < 0.8f => "green", - _ => "blue" + < 0.2f => "#FF6C7F", // red + < 0.4f => "#EF973C", // orange + < 0.6f => "#E8CB2D", // yellow + < 0.8f => "#30CC19", // green + _ => "#00D3B8" // cyan + }; + + var hpPercentColor = data.HpPercent switch { + < 0.2f => "#FF6C7F", // red + < 0.4f => "#EF973C", // orange + < 0.6f => "#E8CB2D", // yellow + < 0.8f => "#30CC19", // green + _ => "#00D3B8" // cyan }; var text = new FormattedMessage(); @@ -132,12 +143,14 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow text.AddMarkupOrThrow(Loc.GetString("robotics-console-designation")); text.AddText($" {data.Name}\n"); // prevent players trolling by naming borg [color=red]satan[/color] text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-battery", ("charge", (int)(data.Charge * 100f)), ("color", batteryColor))}\n"); + text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-hp", ("hp", (int)(data.HpPercent * 100f)), ("color", hpPercentColor))}\n"); text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-brain", ("brain", data.HasBrain))}\n"); text.AddMarkupOrThrow(Loc.GetString("robotics-console-modules", ("count", data.ModuleCount))); BorgInfo.SetMessage(text); // how the turntables - DisableButton.Disabled = !(data.HasBrain && data.CanDisable); + DisableButton.Disabled = !_allowBorgControl || !(data.HasBrain && data.CanDisable); + DestroyButton.Disabled = !_allowBorgControl; } protected override void FrameUpdate(FrameEventArgs args) diff --git a/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs b/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs index c4554d65d6..560d8174aa 100644 --- a/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs +++ b/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs @@ -95,6 +95,9 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem private void OnDisable(Entity ent, ref RoboticsConsoleDisableMessage args) { + if (!ent.Comp.AllowBorgControl) + return; + if (_lock.IsLocked(ent.Owner)) return; @@ -112,6 +115,9 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem private void OnDestroy(Entity ent, ref RoboticsConsoleDestroyMessage args) { + if (!ent.Comp.AllowBorgControl) + return; + if (_lock.IsLocked(ent.Owner)) return; @@ -139,7 +145,7 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem private void UpdateUserInterface(Entity ent) { - var state = new RoboticsConsoleState(ent.Comp.Cyborgs); + var state = new RoboticsConsoleState(ent.Comp.Cyborgs, ent.Comp.AllowBorgControl); _ui.SetUiState(ent.Owner, RoboticsConsoleUiKey.Key, state); } } diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs b/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs index debed525f6..c14e81ce8a 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs @@ -1,4 +1,9 @@ +using Content.Shared.Containers.ItemSlots; using Content.Shared.DeviceNetwork; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Components; using Content.Shared.Popups; using Content.Shared.Robotics; @@ -14,6 +19,9 @@ namespace Content.Server.Silicons.Borgs; public sealed partial class BorgSystem { [Dependency] private readonly EmagSystem _emag = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; + [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; private void InitializeTransponder() { @@ -28,7 +36,7 @@ public sealed partial class BorgSystem var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp, out var chassis, out var device, out var meta)) { - if (comp.NextDisable is {} nextDisable && now >= nextDisable) + if (comp.NextDisable is { } nextDisable && now >= nextDisable) DoDisable((uid, comp, chassis, meta)); if (now < comp.NextBroadcast) @@ -38,13 +46,17 @@ public sealed partial class BorgSystem if (_powerCell.TryGetBatteryFromSlot(uid, out var battery)) charge = battery.CurrentCharge / battery.MaxCharge; - var hasBrain = chassis.BrainEntity != null && !comp.FakeDisabled; + var hpPercent = CalcHP(uid); + + // checks if it has a brain and if the brain is not a empty MMI (gives false anyway if the fake disable is true) + var hasBrain = CheckBrain(chassis.BrainEntity) && !comp.FakeDisabled; var canDisable = comp.NextDisable == null && !comp.FakeDisabling; var data = new CyborgControlData( comp.Sprite, comp.Name, meta.EntityName, charge, + hpPercent, chassis.ModuleCount, hasBrain, canDisable); @@ -70,7 +82,7 @@ public sealed partial class BorgSystem return; } - if (ent.Comp2.BrainEntity is not {} brain) + if (ent.Comp2.BrainEntity is not { } brain) return; var message = Loc.GetString(ent.Comp1.DisabledPopup, ("name", Name(ent, ent.Comp3))); @@ -151,4 +163,40 @@ public sealed partial class BorgSystem { ent.Comp.Name = name; } + + /// + /// Returns a ratio between 0 and 1, 1 when they have no damage and 0 whenever they are crit (or more damaged) + /// + private float CalcHP(EntityUid uid) + { + if (!TryComp(uid, out var damageable)) + return 1; + + if (!_mobState.IsAlive(uid)) + return 0; + + if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold)) + { + Log.Error($"Borg({ToPrettyString(uid)}), doesn't have critical threshold."); + return 1; + } + + return 1 - ((FixedPoint2)(damageable.TotalDamage / threshold)).Float(); + } + + /// + /// Returns true if the borg has a brain + /// + private bool CheckBrain(EntityUid? brainEntity) + { + if (brainEntity == null) + return false; + + // if the brainEntity.Value has the component MMIComponent then it is a MMI, + // in that case it trys to get the "brain" of the MMI, if it is null the MMI is empty and so it returns false + if (TryComp(brainEntity.Value, out var mmi) && _itemSlotsSystem.GetItemOrNull(brainEntity.Value, mmi.BrainSlotId) == null) + return false; + + return true; + } } diff --git a/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs b/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs index 9e4b51866f..eef2007f31 100644 --- a/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs +++ b/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs @@ -50,4 +50,10 @@ public sealed partial class RoboticsConsoleComponent : Component [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [AutoNetworkedField, AutoPausedField] public TimeSpan NextDestroy = TimeSpan.Zero; + + /// + /// Controls if the console can disable or destroy any borg. + /// + [DataField] + public bool AllowBorgControl = true; } diff --git a/Content.Shared/Robotics/RoboticsConsoleUi.cs b/Content.Shared/Robotics/RoboticsConsoleUi.cs index 7a6974fb51..01ecb3f598 100644 --- a/Content.Shared/Robotics/RoboticsConsoleUi.cs +++ b/Content.Shared/Robotics/RoboticsConsoleUi.cs @@ -1,4 +1,4 @@ -using Robust.Shared.Prototypes; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Utility; @@ -19,9 +19,15 @@ public sealed class RoboticsConsoleState : BoundUserInterfaceState /// public Dictionary Cyborgs; - public RoboticsConsoleState(Dictionary cyborgs) + /// + /// If the UI will have the buttons to disable and destroy. + /// + public bool AllowBorgControl; + + public RoboticsConsoleState(Dictionary cyborgs, bool allowBorgControl) { Cyborgs = cyborgs; + AllowBorgControl = allowBorgControl; } } @@ -84,6 +90,12 @@ public partial record struct CyborgControlData [DataField] public float Charge; + /// + /// HP level from 0 to 1. + /// + [DataField] + public float HpPercent; // 0.0 to 1.0 + /// /// How many modules this borg has, just useful information for roboticists. /// Lets them keep track of the latejoin borgs that need new modules and stuff. @@ -111,12 +123,13 @@ public partial record struct CyborgControlData [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan Timeout = TimeSpan.Zero; - public CyborgControlData(SpriteSpecifier? chassisSprite, string chassisName, string name, float charge, int moduleCount, bool hasBrain, bool canDisable) + public CyborgControlData(SpriteSpecifier? chassisSprite, string chassisName, string name, float charge, float hpPercent, int moduleCount, bool hasBrain, bool canDisable) { ChassisSprite = chassisSprite; ChassisName = chassisName; Name = name; Charge = charge; + HpPercent = hpPercent; ModuleCount = moduleCount; HasBrain = hasBrain; CanDisable = canDisable; diff --git a/Resources/Locale/en-US/research/components/robotics-console.ftl b/Resources/Locale/en-US/research/components/robotics-console.ftl index a4c82bd032..d3d2bcea48 100644 --- a/Resources/Locale/en-US/research/components/robotics-console.ftl +++ b/Resources/Locale/en-US/research/components/robotics-console.ftl @@ -6,6 +6,7 @@ robotics-console-model = [color=gray]Model:[/color] {$name} # name is not formatted to prevent players trolling robotics-console-designation = [color=gray]Designation:[/color] robotics-console-battery = [color=gray]Battery charge:[/color] [color={$color}]{$charge}[/color]% +robotics-console-hp = [color=gray]Integrity:[/color] [color={$color}]{$hp}[/color]% robotics-console-modules = [color=gray]Modules installed:[/color] {$count} robotics-console-brain = [color=gray]Brain installed:[/color] [color={$brain -> [true] green]Yes From 85708cad7fc5fe00aa320108ada6705f77a28ce7 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 11 Aug 2025 18:58:47 +0000 Subject: [PATCH 21/40] Automatic changelog update --- Resources/Changelog/Changelog.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index cc30aa756d..19a68e9fdf 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Southbridge - changes: - - message: Colorful Light Crates have been added and can be ordered. - type: Add - id: 8333 - time: '2025-04-24T17:41:55.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/36750 - author: ScarKy0, Jbsundown changes: - message: Letters and Packages can now be cut open with sharp objects. Be wary, @@ -3902,3 +3895,16 @@ id: 8845 time: '2025-08-11T11:00:12.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/39505 +- author: Samuka + changes: + - message: The Robotics Console now correctly displays the brain installed information + if the borg has a empty MMI + type: Fix + - message: The Robotics Console now shows the borg physical integrity + type: Tweak + - message: Changed the colors for the battery display in the robotics console so + it easier to read the percentage + type: Tweak + id: 8846 + time: '2025-08-11T18:57:39.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/38023 From a5351b8c770435dc502cd3b69aec05e174bd9812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=81da?= Date: Mon, 11 Aug 2025 14:21:11 -0500 Subject: [PATCH 22/40] ExtinguishOnTrigger and TriggerOnInteractHand (#39537) * simplely one commit * simplelly two commit * requested changes --------- Co-authored-by: iaada --- .../Systems/FireStackOnTriggerSystem.cs | 53 +++++++++++++++++++ .../Systems/FlameStackOnTriggerSystem.cs | 37 ------------- .../Trigger/Systems/IgniteOnTriggerSystem.cs | 2 +- .../Effects/ExtinguishOnTriggerComponent.cs | 10 ++++ ...nent.cs => FireStackOnTriggerComponent.cs} | 2 +- .../Effects/IgniteOnTriggerComponent.cs | 2 +- .../TriggerOnInteractHandComponent.cs | 11 ++++ .../Systems/TriggerSystem.Interaction.cs | 10 ++++ 8 files changed, 87 insertions(+), 40 deletions(-) create mode 100644 Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs delete mode 100644 Content.Server/Trigger/Systems/FlameStackOnTriggerSystem.cs create mode 100644 Content.Shared/Trigger/Components/Effects/ExtinguishOnTriggerComponent.cs rename Content.Shared/Trigger/Components/Effects/{FlameStackOnTriggerComponent.cs => FireStackOnTriggerComponent.cs} (90%) create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnInteractHandComponent.cs diff --git a/Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs b/Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs new file mode 100644 index 0000000000..af3298b865 --- /dev/null +++ b/Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs @@ -0,0 +1,53 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Trigger; +using Content.Shared.Trigger.Components.Effects; + +namespace Content.Server.Trigger.Systems; + +/// +/// Trigger system for adding or removing fire stacks from an entity with . +/// +/// +public sealed class FireStackOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly FlammableSystem _flame = default!; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTriggerFlame); + SubscribeLocalEvent(OnTriggerExtinguish); + } + + private void OnTriggerFlame(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + _flame.AdjustFireStacks(target.Value, ent.Comp.FireStacks, ignite: ent.Comp.DoIgnite); + + args.Handled = true; + } + + private void OnTriggerExtinguish(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + _flame.Extinguish(target.Value); + + args.Handled = true; + } +} diff --git a/Content.Server/Trigger/Systems/FlameStackOnTriggerSystem.cs b/Content.Server/Trigger/Systems/FlameStackOnTriggerSystem.cs deleted file mode 100644 index 879d57b845..0000000000 --- a/Content.Server/Trigger/Systems/FlameStackOnTriggerSystem.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Trigger; -using Content.Shared.Trigger.Components.Effects; - -namespace Content.Server.Trigger.Systems; - -/// -/// Trigger system for setting something on fire. -/// -/// -public sealed class FlameStackOnTriggerSystem : EntitySystem -{ - [Dependency] private readonly FlammableSystem _flame = default!; - - /// - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnTrigger); - } - - private void OnTrigger(Entity ent, ref TriggerEvent args) - { - if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) - return; - - var target = ent.Comp.TargetUser ? args.User : ent.Owner; - - if (target == null) - return; - - _flame.AdjustFireStacks(target.Value, ent.Comp.FireStacks, ignite: ent.Comp.DoIgnite); - - args.Handled = true; - } -} diff --git a/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs b/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs index b19f4738ca..c6ae16ec78 100644 --- a/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs +++ b/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs @@ -8,7 +8,7 @@ namespace Content.Server.Trigger.Systems; /// /// Handles igniting when triggered and stopping ignition after the delay. /// -/// +/// public sealed class IgniteOnTriggerSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; diff --git a/Content.Shared/Trigger/Components/Effects/ExtinguishOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/ExtinguishOnTriggerComponent.cs new file mode 100644 index 0000000000..43208a9971 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/ExtinguishOnTriggerComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// This trigger removes all the fire stacks on a target with . +/// If TargetUser is true, the entity that caused this trigger will be extinguished instead. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ExtinguishOnTriggerComponent : BaseXOnTriggerComponent; diff --git a/Content.Shared/Trigger/Components/Effects/FlameStackOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/FireStackOnTriggerComponent.cs similarity index 90% rename from Content.Shared/Trigger/Components/Effects/FlameStackOnTriggerComponent.cs rename to Content.Shared/Trigger/Components/Effects/FireStackOnTriggerComponent.cs index f7186ae003..cde5075e9b 100644 --- a/Content.Shared/Trigger/Components/Effects/FlameStackOnTriggerComponent.cs +++ b/Content.Shared/Trigger/Components/Effects/FireStackOnTriggerComponent.cs @@ -9,7 +9,7 @@ namespace Content.Shared.Trigger.Components.Effects; /// /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class FlameStackOnTriggerComponent : BaseXOnTriggerComponent +public sealed partial class FireStackOnTriggerComponent : BaseXOnTriggerComponent { /// /// How many fire stacks to add or remove. diff --git a/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs index e9f51cf4e7..3e3db526e4 100644 --- a/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs +++ b/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs @@ -8,7 +8,7 @@ namespace Content.Shared.Trigger.Components.Effects; /// Requires along with triggering components. /// The if TargetUser is true they will be ignited instead (they need IgnitionSourceComponent as well). /// -/// +/// [RegisterComponent, NetworkedComponent] [AutoGenerateComponentState, AutoGenerateComponentPause] public sealed partial class IgniteOnTriggerComponent : BaseXOnTriggerComponent diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnInteractHandComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnInteractHandComponent.cs new file mode 100644 index 0000000000..ca7e96be74 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnInteractHandComponent.cs @@ -0,0 +1,11 @@ +using Content.Shared.Interaction; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Trigger on , aka clicking on an entity with an empty hand. +/// User is the player with the hand doing the clicking. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnInteractHandComponent : BaseTriggerOnXComponent; diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs index f506909760..035ef4ec91 100644 --- a/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs @@ -12,6 +12,7 @@ public sealed partial class TriggerSystem { SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnUse); + SubscribeLocalEvent(OnInteractHand); SubscribeLocalEvent(HandleItemToggleOnTrigger); SubscribeLocalEvent(HandleAnchorOnTrigger); @@ -39,6 +40,15 @@ public sealed partial class TriggerSystem args.Handled = true; } + private void OnInteractHand(Entity ent, ref InteractHandEvent args) + { + if (args.Handled) + return; + + Trigger(ent.Owner, args.User, ent.Comp.KeyOut); + args.Handled = true; + } + private void HandleItemToggleOnTrigger(Entity ent, ref TriggerEvent args) { if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) From 661fc00a1fa574abeb4348c7ee4cae8877de49ac Mon Sep 17 00:00:00 2001 From: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:41:13 -0700 Subject: [PATCH 23/40] [HOTFIX] Stop players from clipping through Windoors (#39564) * Don't have standing state edit soft fixtures? * Bugfix * Cherry pick acquired --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> --- .../Standing/StandingStateSystem.cs | 67 +++++++++++++------ 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/Content.Shared/Standing/StandingStateSystem.cs b/Content.Shared/Standing/StandingStateSystem.cs index f965e0ae7c..7f3b8c9aac 100644 --- a/Content.Shared/Standing/StandingStateSystem.cs +++ b/Content.Shared/Standing/StandingStateSystem.cs @@ -1,3 +1,4 @@ +using Content.Shared.Climbing.Events; using Content.Shared.Hands.Components; using Content.Shared.Movement.Events; using Content.Shared.Movement.Systems; @@ -25,6 +26,7 @@ public sealed class StandingStateSystem : EntitySystem SubscribeLocalEvent(OnMobTargetCollide); SubscribeLocalEvent(OnRefreshFrictionModifiers); SubscribeLocalEvent(OnTileFriction); + SubscribeLocalEvent(OnEndClimb); } private void OnMobTargetCollide(Entity ent, ref AttemptMobTargetCollideEvent args) @@ -58,6 +60,15 @@ public sealed class StandingStateSystem : EntitySystem args.Modifier *= entity.Comp.DownFrictionMod; } + private void OnEndClimb(Entity entity, ref EndClimbEvent args) + { + if (entity.Comp.Standing) + return; + + // Currently only Climbing also edits fixtures layers like this so this is fine for now. + ChangeLayers(entity); + } + public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) { if (!Resolve(uid, ref standingState, false)) @@ -111,17 +122,7 @@ public sealed class StandingStateSystem : EntitySystem _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Horizontal, appearance); // Change collision masks to allow going under certain entities like flaps and tables - if (TryComp(uid, out FixturesComponent? fixtureComponent)) - { - foreach (var (key, fixture) in fixtureComponent.Fixtures) - { - if ((fixture.CollisionMask & StandingCollisionLayer) == 0) - continue; - - standingState.ChangedFixtures.Add(key); - _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask & ~StandingCollisionLayer, manager: fixtureComponent); - } - } + ChangeLayers((uid, standingState)); // check if component was just added or streamed to client // if true, no need to play sound - mob was down before player could seen that @@ -166,18 +167,44 @@ public sealed class StandingStateSystem : EntitySystem _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Vertical, appearance); - if (TryComp(uid, out FixturesComponent? fixtureComponent)) - { - foreach (var key in standingState.ChangedFixtures) - { - if (fixtureComponent.Fixtures.TryGetValue(key, out var fixture)) - _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask | StandingCollisionLayer, fixtureComponent); - } - } - standingState.ChangedFixtures.Clear(); + RevertLayers((uid, standingState)); return true; } + + // TODO: This should be moved to a PhysicsModifierSystem which raises events so multiple systems can modify fixtures at once + private void ChangeLayers(Entity entity) + { + if (!Resolve(entity, ref entity.Comp2, false)) + return; + + foreach (var (key, fixture) in entity.Comp2.Fixtures) + { + if ((fixture.CollisionMask & StandingCollisionLayer) == 0 || !fixture.Hard) + continue; + + entity.Comp1.ChangedFixtures.Add(key); + _physics.SetCollisionMask(entity, key, fixture, fixture.CollisionMask & ~StandingCollisionLayer, manager: entity.Comp2); + } + } + + // TODO: This should be moved to a PhysicsModifierSystem which raises events so multiple systems can modify fixtures at once + private void RevertLayers(Entity entity) + { + if (!Resolve(entity, ref entity.Comp2, false)) + { + entity.Comp1.ChangedFixtures.Clear(); + return; + } + + foreach (var key in entity.Comp1.ChangedFixtures) + { + if (entity.Comp2.Fixtures.TryGetValue(key, out var fixture) && fixture.Hard) + _physics.SetCollisionMask(entity, key, fixture, fixture.CollisionMask | StandingCollisionLayer, entity.Comp2); + } + + entity.Comp1.ChangedFixtures.Clear(); + } } [ByRefEvent] From f161b7d386320cbb2a6ef10e3fc7b105e09e24fb Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:41:53 +0200 Subject: [PATCH 24/40] Reduce LevelOfParallelism to 2 for integration tests (#39566) less paralelliism --- Content.IntegrationTests/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.IntegrationTests/AssemblyInfo.cs b/Content.IntegrationTests/AssemblyInfo.cs index 76fc42f3a9..b8a88e2623 100644 --- a/Content.IntegrationTests/AssemblyInfo.cs +++ b/Content.IntegrationTests/AssemblyInfo.cs @@ -5,4 +5,4 @@ // https://github.com/dotnet/runtime/issues/107197 // So we can't really parallelize integration tests harder either until the runtime fixes that, // *or* we fix serv3 to not spam expression trees. -[assembly: LevelOfParallelism(3)] +[assembly: LevelOfParallelism(2)] From fd52f698c743d240573e37d34219bc109736362c Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 12 Aug 2025 07:06:28 +1000 Subject: [PATCH 25/40] Predict PoweredLights (#36541) * Move PoweredLight to shared * Predict the rest of the owl * reacher * compinit & anim * Fix names * Revert this? * Fix these * chicken drummies * deita * Fix * review * fix * fixes * fix PVS weirdness --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../Light/EntitySystems/LightBulbSystem.cs | 28 +- .../Light/EntitySystems/PoweredLightSystem.cs | 5 + .../Components/ApcPowerReceiverComponent.cs | 1 + .../Electrocution/ElectrocutionSystem.cs | 2 +- .../Light/EntitySystems/LightBulbSystem.cs | 88 +--- .../Light/EntitySystems/PoweredLightSystem.cs | 464 ++---------------- .../Components/ApcPowerReceiverComponent.cs | 7 +- .../EntitySystems/RevenantSystem.Abilities.cs | 1 + .../StationEvents/Events/SolarFlareRule.cs | 1 + .../Artifact/XAE/XAELightFlickerSystem.cs | 2 +- .../Light/Components/LightBulbComponent.cs | 31 +- .../Components/LightReplacerComponent.cs | 1 + .../Light/Components/PoweredLightComponent.cs | 61 ++- .../EntitySystems/SharedLightBulbSystem.cs | 84 ++++ .../EntitySystems/SharedPoweredLightSystem.cs | 417 ++++++++++++++++ .../SharedApcPowerReceiverComponent.cs | 3 + 16 files changed, 630 insertions(+), 566 deletions(-) create mode 100644 Content.Client/Light/EntitySystems/PoweredLightSystem.cs rename {Content.Server => Content.Shared}/Light/Components/PoweredLightComponent.cs (54%) create mode 100644 Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs create mode 100644 Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs diff --git a/Content.Client/Light/EntitySystems/LightBulbSystem.cs b/Content.Client/Light/EntitySystems/LightBulbSystem.cs index c028cc64c6..a3698fc199 100644 --- a/Content.Client/Light/EntitySystems/LightBulbSystem.cs +++ b/Content.Client/Light/EntitySystems/LightBulbSystem.cs @@ -1,36 +1,46 @@ using Content.Shared.Light.Components; +using Content.Shared.Light.EntitySystems; using Robust.Client.GameObjects; -namespace Content.Client.Light.Visualizers; +namespace Content.Client.Light.EntitySystems; -public sealed class LightBulbSystem : VisualizerSystem +public sealed class LightBulbSystem : SharedLightBulbSystem { - protected override void OnAppearanceChange(EntityUid uid, LightBulbComponent comp, ref AppearanceChangeEvent args) + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly SpriteSystem _sprite = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnAppearanceChange); + } + + private void OnAppearanceChange(EntityUid uid, LightBulbComponent comp, ref AppearanceChangeEvent args) { if (args.Sprite == null) return; // update sprite state - if (AppearanceSystem.TryGetData(uid, LightBulbVisuals.State, out var state, args.Component)) + if (_appearance.TryGetData(uid, LightBulbVisuals.State, out var state, args.Component)) { switch (state) { case LightBulbState.Normal: - SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.NormalSpriteState); + _sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.NormalSpriteState); break; case LightBulbState.Broken: - SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BrokenSpriteState); + _sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BrokenSpriteState); break; case LightBulbState.Burned: - SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BurnedSpriteState); + _sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BurnedSpriteState); break; } } // also update sprites color - if (AppearanceSystem.TryGetData(uid, LightBulbVisuals.Color, out var color, args.Component)) + if (_appearance.TryGetData(uid, LightBulbVisuals.Color, out var color, args.Component)) { - SpriteSystem.SetColor((uid, args.Sprite), color); + _sprite.SetColor((uid, args.Sprite), color); } } } diff --git a/Content.Client/Light/EntitySystems/PoweredLightSystem.cs b/Content.Client/Light/EntitySystems/PoweredLightSystem.cs new file mode 100644 index 0000000000..b8a6b16da4 --- /dev/null +++ b/Content.Client/Light/EntitySystems/PoweredLightSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Light.EntitySystems; + +namespace Content.Client.Light.EntitySystems; + +public sealed class PoweredLightSystem : SharedPoweredLightSystem; diff --git a/Content.Client/Power/Components/ApcPowerReceiverComponent.cs b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs index fbebcb7cf8..ead686189e 100644 --- a/Content.Client/Power/Components/ApcPowerReceiverComponent.cs +++ b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs @@ -5,4 +5,5 @@ namespace Content.Client.Power.Components; [RegisterComponent] public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent { + public override float Load { get; set; } } diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 05dfffa4a9..1f04421c0f 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -1,5 +1,4 @@ using Content.Server.Administration.Logs; -using Content.Server.Light.Components; using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.NodeGroups; @@ -16,6 +15,7 @@ using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Jittering; +using Content.Shared.Light.Components; using Content.Shared.Maps; using Content.Shared.NodeContainer; using Content.Shared.NodeContainer.NodeGroups; diff --git a/Content.Server/Light/EntitySystems/LightBulbSystem.cs b/Content.Server/Light/EntitySystems/LightBulbSystem.cs index 5714bde3e5..ba5b795e2d 100644 --- a/Content.Server/Light/EntitySystems/LightBulbSystem.cs +++ b/Content.Server/Light/EntitySystems/LightBulbSystem.cs @@ -1,87 +1,5 @@ -using Content.Server.Light.Components; -using Content.Shared.Destructible; -using Content.Shared.Light.Components; -using Content.Shared.Throwing; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Player; +using Content.Shared.Light.EntitySystems; -namespace Content.Server.Light.EntitySystems -{ - public sealed class LightBulbSystem : EntitySystem - { - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; +namespace Content.Server.Light.EntitySystems; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(HandleLand); - SubscribeLocalEvent(OnBreak); - } - - private void OnInit(EntityUid uid, LightBulbComponent bulb, ComponentInit args) - { - // update default state of bulbs - SetColor(uid, bulb.Color, bulb); - SetState(uid, bulb.State, bulb); - } - - private void HandleLand(EntityUid uid, LightBulbComponent bulb, ref LandEvent args) - { - PlayBreakSound(uid, bulb); - SetState(uid, LightBulbState.Broken, bulb); - } - - private void OnBreak(EntityUid uid, LightBulbComponent component, BreakageEventArgs args) - { - SetState(uid, LightBulbState.Broken, component); - } - - /// - /// Set a new color for a light bulb and raise event about change - /// - public void SetColor(EntityUid uid, Color color, LightBulbComponent? bulb = null) - { - if (!Resolve(uid, ref bulb)) - return; - - bulb.Color = color; - UpdateAppearance(uid, bulb); - } - - /// - /// Set a new state for a light bulb (broken, burned) and raise event about change - /// - public void SetState(EntityUid uid, LightBulbState state, LightBulbComponent? bulb = null) - { - if (!Resolve(uid, ref bulb)) - return; - - bulb.State = state; - UpdateAppearance(uid, bulb); - } - - public void PlayBreakSound(EntityUid uid, LightBulbComponent? bulb = null) - { - if (!Resolve(uid, ref bulb)) - return; - - _audio.PlayPvs(bulb.BreakSound, uid); - } - - private void UpdateAppearance(EntityUid uid, LightBulbComponent? bulb = null, - AppearanceComponent? appearance = null) - { - if (!Resolve(uid, ref bulb, ref appearance, logMissing: false)) - return; - - // try to update appearance and color - _appearance.SetData(uid, LightBulbVisuals.State, bulb.State, appearance); - _appearance.SetData(uid, LightBulbVisuals.Color, bulb.Color, appearance); - } - } -} +public sealed class LightBulbSystem : SharedLightBulbSystem; diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index b5b9f20432..948c44cd75 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -1,440 +1,64 @@ -using Content.Server.DeviceLinking.Systems; -using Content.Server.DeviceNetwork; -using Content.Server.DeviceNetwork.Systems; using Content.Server.Emp; using Content.Server.Ghost; -using Content.Server.Light.Components; -using Content.Server.Power.Components; -using Content.Shared.Audio; -using Content.Shared.Damage; -using Content.Shared.DeviceLinking.Events; -using Content.Shared.DoAfter; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Interaction; -using Content.Shared.Light; using Content.Shared.Light.Components; -using Robust.Server.GameObjects; -using Robust.Shared.Containers; -using Robust.Shared.Timing; -using Robust.Shared.Audio.Systems; -using Content.Shared.Damage.Systems; -using Content.Shared.Damage.Components; -using Content.Shared.DeviceNetwork; -using Content.Shared.DeviceNetwork.Events; -using Content.Shared.Power; +using Content.Shared.Light.EntitySystems; -namespace Content.Server.Light.EntitySystems +namespace Content.Server.Light.EntitySystems; + +/// +/// System for the PoweredLightComponents +/// +public sealed class PoweredLightSystem : SharedPoweredLightSystem { - /// - /// System for the PoweredLightComponents - /// - public sealed class PoweredLightSystem : EntitySystem + public override void Initialize() { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedAmbientSoundSystem _ambientSystem = default!; - [Dependency] private readonly LightBulbSystem _bulbSystem = default!; - [Dependency] private readonly SharedHandsSystem _handsSystem = default!; - [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly PointLightSystem _pointLight = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly DamageOnInteractSystem _damageOnInteractSystem = default!; + base.Initialize(); + SubscribeLocalEvent(OnMapInit); - private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2); - public const string LightBulbContainer = "light_bulb"; + SubscribeLocalEvent(OnGhostBoo); - public override void Initialize() + SubscribeLocalEvent(OnEmpPulse); + } + + private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args) + { + if (light.IgnoreGhostsBoo) + return; + + // check cooldown first to prevent abuse + var time = GameTiming.CurTime; + if (light.LastGhostBlink != null) { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnInteractHand); - - SubscribeLocalEvent(OnGhostBoo); - SubscribeLocalEvent(HandleLightDamaged); - - SubscribeLocalEvent(OnSignalReceived); - SubscribeLocalEvent(OnPacketReceived); - - SubscribeLocalEvent(OnPowerChanged); - - SubscribeLocalEvent(OnDoAfter); - SubscribeLocalEvent(OnEmpPulse); - } - - private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args) - { - light.LightBulbContainer = _containerSystem.EnsureContainer(uid, LightBulbContainer); - _signalSystem.EnsureSinkPorts(uid, light.OnPort, light.OffPort, light.TogglePort); - } - - private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args) - { - // TODO: Use ContainerFill dog - if (light.HasLampOnSpawn != null) - { - var entity = Spawn(light.HasLampOnSpawn, Comp(uid).Coordinates); - _containerSystem.Insert(entity, light.LightBulbContainer); - } - // need this to update visualizers - UpdateLight(uid, light); - } - - private void OnInteractUsing(EntityUid uid, PoweredLightComponent component, InteractUsingEvent args) - { - if (args.Handled) + if (time <= light.LastGhostBlink + light.GhostBlinkingCooldown) return; - - args.Handled = InsertBulb(uid, args.Used, component); } - private void OnInteractHand(EntityUid uid, PoweredLightComponent light, InteractHandEvent args) + light.LastGhostBlink = time; + + ToggleBlinkingLight(uid, light, true); + uid.SpawnTimer(light.GhostBlinkingTime, () => { - if (args.Handled) - return; + ToggleBlinkingLight(uid, light, false); + }); - // check if light has bulb to eject - var bulbUid = GetBulb(uid, light); - if (bulbUid == null) - return; + args.Handled = true; + } - var userUid = args.User; - //removing a broken/burned bulb, so allow instant removal - if(TryComp(bulbUid.Value, out var bulb) && bulb.State != LightBulbState.Normal) - { - args.Handled = EjectBulb(uid, userUid, light) != null; - return; - } - - // removing a working bulb, so require a delay - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, userUid, light.EjectBulbDelay, new PoweredLightDoAfterEvent(), uid, target: uid) - { - BreakOnMove = true, - BreakOnDamage = true, - }); - - args.Handled = true; - } - - #region Bulb Logic API - /// - /// Inserts the bulb if possible. - /// - /// True if it could insert it, false if it couldn't. - public bool InsertBulb(EntityUid uid, EntityUid bulbUid, PoweredLightComponent? light = null) + private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args) + { + // TODO: Use ContainerFill dog + if (light.HasLampOnSpawn != null) { - if (!Resolve(uid, ref light)) - return false; - - // check if light already has bulb - if (GetBulb(uid, light) != null) - return false; - - // check if bulb fits - if (!TryComp(bulbUid, out LightBulbComponent? lightBulb)) - return false; - if (lightBulb.Type != light.BulbType) - return false; - - // try to insert bulb in container - if (!_containerSystem.Insert(bulbUid, light.LightBulbContainer)) - return false; - - UpdateLight(uid, light); - return true; + var entity = EntityManager.SpawnEntity(light.HasLampOnSpawn, EntityManager.GetComponent(uid).Coordinates); + ContainerSystem.Insert(entity, light.LightBulbContainer); } + // need this to update visualizers + UpdateLight(uid, light); + } - /// - /// Ejects the bulb to a mob's hand if possible. - /// - /// Bulb uid if it was successfully ejected, null otherwise - public EntityUid? EjectBulb(EntityUid uid, EntityUid? userUid = null, PoweredLightComponent? light = null) - { - if (!Resolve(uid, ref light)) - return null; - - // check if light has bulb - if (GetBulb(uid, light) is not { Valid: true } bulb) - return null; - - // try to remove bulb from container - if (!_containerSystem.Remove(bulb, light.LightBulbContainer)) - return null; - - // try to place bulb in hands - _handsSystem.PickupOrDrop(userUid, bulb); - - UpdateLight(uid, light); - return bulb; - } - - /// - /// Replaces the spawned prototype of a pre-mapinit powered light with a different variant. - /// - public bool ReplaceSpawnedPrototype(Entity light, string bulb) - { - if (light.Comp.LightBulbContainer.ContainedEntity != null) - return false; - - if (LifeStage(light.Owner) >= EntityLifeStage.MapInitialized) - return false; - - light.Comp.HasLampOnSpawn = bulb; - return true; - } - - /// - /// Try to replace current bulb with a new one - /// If succeed old bulb just drops on floor - /// - public bool ReplaceBulb(EntityUid uid, EntityUid bulb, PoweredLightComponent? light = null) - { - EjectBulb(uid, null, light); - return InsertBulb(uid, bulb, light); - } - - /// - /// Try to get light bulb inserted in powered light - /// - /// Bulb uid if it exist, null otherwise - public EntityUid? GetBulb(EntityUid uid, PoweredLightComponent? light = null) - { - if (!Resolve(uid, ref light)) - return null; - - return light.LightBulbContainer.ContainedEntity; - } - - /// - /// Try to break bulb inside light fixture - /// - public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null) - { - if (!Resolve(uid, ref light, false)) - return false; - - // if we aren't mapinited, - // just null the spawned bulb - if (LifeStage(uid) < EntityLifeStage.MapInitialized) - { - light.HasLampOnSpawn = null; - return true; - } - - // check bulb state - var bulbUid = GetBulb(uid, light); - if (bulbUid == null || !TryComp(bulbUid.Value, out LightBulbComponent? lightBulb)) - return false; - if (lightBulb.State == LightBulbState.Broken) - return false; - - // break it - _bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb); - _bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb); - UpdateLight(uid, light); - return true; - } - #endregion - - private void UpdateLight(EntityUid uid, - PoweredLightComponent? light = null, - ApcPowerReceiverComponent? powerReceiver = null, - AppearanceComponent? appearance = null) - { - if (!Resolve(uid, ref light, ref powerReceiver, false)) - return; - - // Optional component. - Resolve(uid, ref appearance, false); - - // check if light has bulb - var bulbUid = GetBulb(uid, light); - if (bulbUid == null || !TryComp(bulbUid.Value, out LightBulbComponent? lightBulb)) - { - SetLight(uid, false, light: light); - powerReceiver.Load = 0; - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Empty, appearance); - return; - } - - switch (lightBulb.State) - { - case LightBulbState.Normal: - if (powerReceiver.Powered && light.On) - { - SetLight(uid, true, lightBulb.Color, light, lightBulb.LightRadius, lightBulb.LightEnergy, lightBulb.LightSoftness); - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.On, appearance); - var time = _gameTiming.CurTime; - if (time > light.LastThunk + ThunkDelay) - { - light.LastThunk = time; - _audio.PlayPvs(light.TurnOnSound, uid, light.TurnOnSound.Params.AddVolume(-10f)); - } - } - else - { - SetLight(uid, false, light: light); - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Off, appearance); - } - break; - case LightBulbState.Broken: - SetLight(uid, false, light: light); - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Broken, appearance); - break; - case LightBulbState.Burned: - SetLight(uid, false, light: light); - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Burned, appearance); - break; - } - - powerReceiver.Load = (light.On && lightBulb.State == LightBulbState.Normal) ? lightBulb.PowerUse : 0; - } - - /// - /// Destroy the light bulb if the light took any damage. - /// - public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args) - { - // Was it being repaired, or did it take damage? - if (args.DamageIncreased) - { - // Eventually, this logic should all be done by this (or some other) system, not a component. - TryDestroyBulb(uid, component); - } - } - - private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args) - { - if (light.IgnoreGhostsBoo) - return; - - // check cooldown first to prevent abuse - var time = _gameTiming.CurTime; - if (light.LastGhostBlink != null) - { - if (time <= light.LastGhostBlink + light.GhostBlinkingCooldown) - return; - } - - light.LastGhostBlink = time; - - ToggleBlinkingLight(uid, light, true); - uid.SpawnTimer(light.GhostBlinkingTime, () => - { - ToggleBlinkingLight(uid, light, false); - }); - - args.Handled = true; - } - - private void OnPowerChanged(EntityUid uid, PoweredLightComponent component, ref PowerChangedEvent args) - { - // TODO: Power moment - var metadata = MetaData(uid); - - if (metadata.EntityPaused || TerminatingOrDeleted(uid, metadata)) - return; - - UpdateLight(uid, component); - } - - public void ToggleBlinkingLight(EntityUid uid, PoweredLightComponent light, bool isNowBlinking) - { - if (light.IsBlinking == isNowBlinking) - return; - - light.IsBlinking = isNowBlinking; - - if (!TryComp(uid, out AppearanceComponent? appearance)) - return; - - _appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance); - } - - private void OnSignalReceived(EntityUid uid, PoweredLightComponent component, ref SignalReceivedEvent args) - { - if (args.Port == component.OffPort) - SetState(uid, false, component); - else if (args.Port == component.OnPort) - SetState(uid, true, component); - else if (args.Port == component.TogglePort) - ToggleLight(uid, component); - } - - /// - /// Turns the light on or of when receiving a command. - /// The light is turned on or of according to the value - /// - private void OnPacketReceived(EntityUid uid, PoweredLightComponent component, DeviceNetworkPacketEvent args) - { - if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || command != DeviceNetworkConstants.CmdSetState) return; - if (!args.Data.TryGetValue(DeviceNetworkConstants.StateEnabled, out bool enabled)) return; - - SetState(uid, enabled, component); - } - - private void SetLight(EntityUid uid, bool value, Color? color = null, PoweredLightComponent? light = null, float? radius = null, float? energy = null, float? softness = null) - { - if (!Resolve(uid, ref light)) - return; - - light.CurrentLit = value; - _ambientSystem.SetAmbience(uid, value); - - if (TryComp(uid, out PointLightComponent? pointLight)) - { - _pointLight.SetEnabled(uid, value, pointLight); - - if (color != null) - _pointLight.SetColor(uid, color.Value, pointLight); - if (radius != null) - _pointLight.SetRadius(uid, (float) radius, pointLight); - if (energy != null) - _pointLight.SetEnergy(uid, (float) energy, pointLight); - if (softness != null) - _pointLight.SetSoftness(uid, (float) softness, pointLight); - } - - // light bulbs burn your hands! - if (TryComp(uid, out var damageOnInteractComp)) - _damageOnInteractSystem.SetIsDamageActiveTo((uid, damageOnInteractComp), value); - } - - public void ToggleLight(EntityUid uid, PoweredLightComponent? light = null) - { - if (!Resolve(uid, ref light)) - return; - - light.On = !light.On; - UpdateLight(uid, light); - } - - public void SetState(EntityUid uid, bool state, PoweredLightComponent? light = null) - { - if (!Resolve(uid, ref light)) - return; - - light.On = state; - UpdateLight(uid, light); - } - - private void OnDoAfter(EntityUid uid, PoweredLightComponent component, DoAfterEvent args) - { - if (args.Handled || args.Cancelled || args.Args.Target == null) - return; - - EjectBulb(args.Args.Target.Value, args.Args.User, component); - - args.Handled = true; - } - - private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args) - { - if (TryDestroyBulb(uid, component)) - args.Affected = true; - } + private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args) + { + if (TryDestroyBulb(uid, component)) + args.Affected = true; } } diff --git a/Content.Server/Power/Components/ApcPowerReceiverComponent.cs b/Content.Server/Power/Components/ApcPowerReceiverComponent.cs index aa8feff3e0..bfd096a253 100644 --- a/Content.Server/Power/Components/ApcPowerReceiverComponent.cs +++ b/Content.Server/Power/Components/ApcPowerReceiverComponent.cs @@ -14,9 +14,12 @@ namespace Content.Server.Power.Components /// /// Amount of charge this needs from an APC per second to function. /// - [ViewVariables(VVAccess.ReadWrite)] [DataField("powerLoad")] - public float Load { get => NetworkLoad.DesiredPower; set => NetworkLoad.DesiredPower = value; } + public override float Load + { + get => NetworkLoad.DesiredPower; + set => NetworkLoad.DesiredPower = value; + } public ApcPowerProviderComponent? Provider = null; diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 0ef7b8f533..3f91b0fa71 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -20,6 +20,7 @@ using Content.Shared.DoAfter; using Content.Shared.Emag.Systems; using Content.Shared.FixedPoint; using Content.Shared.Humanoid; +using Content.Shared.Light.Components; using Content.Shared.Maps; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; diff --git a/Content.Server/StationEvents/Events/SolarFlareRule.cs b/Content.Server/StationEvents/Events/SolarFlareRule.cs index 19f6e393d2..b6530d867f 100644 --- a/Content.Server/StationEvents/Events/SolarFlareRule.cs +++ b/Content.Server/StationEvents/Events/SolarFlareRule.cs @@ -8,6 +8,7 @@ using Content.Shared.Radio.Components; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; using Content.Shared.GameTicking.Components; +using Content.Shared.Light.Components; namespace Content.Server.StationEvents.Events; diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs index 4c4073b123..6e7c4fc4ad 100644 --- a/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs +++ b/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs @@ -1,6 +1,6 @@ using Content.Server.Ghost; -using Content.Server.Light.Components; using Content.Server.Xenoarchaeology.Artifact.XAE.Components; +using Content.Shared.Light.Components; using Content.Shared.Xenoarchaeology.Artifact; using Content.Shared.Xenoarchaeology.Artifact.XAE; using Robust.Shared.Random; diff --git a/Content.Shared/Light/Components/LightBulbComponent.cs b/Content.Shared/Light/Components/LightBulbComponent.cs index 35b04be897..fcd3840a0c 100644 --- a/Content.Shared/Light/Components/LightBulbComponent.cs +++ b/Content.Shared/Light/Components/LightBulbComponent.cs @@ -8,69 +8,61 @@ namespace Content.Shared.Light.Components; /// Component that represents a light bulb. Can be broken, or burned, which turns them mostly useless. /// TODO: Breaking and burning should probably be moved to another component eventually. /// -[RegisterComponent, NetworkedComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class LightBulbComponent : Component { /// /// The color of the lightbulb and the light it produces. /// - [DataField("color")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public Color Color = Color.White; /// /// The type of lightbulb. Tube/bulb/etc... /// [DataField("bulb")] - [ViewVariables(VVAccess.ReadWrite)] public LightBulbType Type = LightBulbType.Tube; /// /// The initial state of the lightbulb. /// - [DataField("startingState")] + [DataField("startingState"), AutoNetworkedField] public LightBulbState State = LightBulbState.Normal; /// /// The temperature the air around the lightbulb is exposed to when the lightbulb burns out. /// [DataField("BurningTemperature")] - [ViewVariables(VVAccess.ReadWrite)] public int BurningTemperature = 1400; /// /// Relates to how bright the light produced by the lightbulb is. /// - [DataField("lightEnergy")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public float LightEnergy = 0.8f; /// /// The maximum radius of the point light source this light produces. /// - [DataField("lightRadius")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public float LightRadius = 10; /// /// Relates to the falloff constant of the light produced by the lightbulb. /// - [DataField("lightSoftness")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public float LightSoftness = 1; /// /// The amount of power used by the lightbulb when it's active. /// [DataField("PowerUse")] - [ViewVariables(VVAccess.ReadWrite)] public int PowerUse = 60; /// /// The sound produced when the lightbulb breaks. /// - [DataField("breakSound")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public SoundSpecifier BreakSound = new SoundCollectionSpecifier("GlassBreak", AudioParams.Default.WithVolume(-6f)); #region Appearance @@ -78,22 +70,19 @@ public sealed partial class LightBulbComponent : Component /// /// The sprite state used when the lightbulb is intact. /// - [DataField("normalSpriteState")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public string NormalSpriteState = "normal"; /// /// The sprite state used when the lightbulb is broken. /// - [DataField("brokenSpriteState")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public string BrokenSpriteState = "broken"; /// /// The sprite state used when the lightbulb is burned. /// - [DataField("burnedSpriteState")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public string BurnedSpriteState = "burned"; #endregion Appearance diff --git a/Content.Shared/Light/Components/LightReplacerComponent.cs b/Content.Shared/Light/Components/LightReplacerComponent.cs index 3ce9647c88..1276ff9edc 100644 --- a/Content.Shared/Light/Components/LightReplacerComponent.cs +++ b/Content.Shared/Light/Components/LightReplacerComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Light.Components; using Content.Shared.Light.EntitySystems; using Content.Shared.Storage; using Robust.Shared.Audio; diff --git a/Content.Server/Light/Components/PoweredLightComponent.cs b/Content.Shared/Light/Components/PoweredLightComponent.cs similarity index 54% rename from Content.Server/Light/Components/PoweredLightComponent.cs rename to Content.Shared/Light/Components/PoweredLightComponent.cs index 1a6f610516..ad9a9d401c 100644 --- a/Content.Server/Light/Components/PoweredLightComponent.cs +++ b/Content.Shared/Light/Components/PoweredLightComponent.cs @@ -1,80 +1,87 @@ -using Content.Server.Light.EntitySystems; -using Content.Shared.Damage; using Content.Shared.DeviceLinking; -using Content.Shared.Light.Components; +using Content.Shared.Light.EntitySystems; using Robust.Shared.Audio; using Robust.Shared.Containers; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Server.Light.Components +namespace Content.Shared.Light.Components { /// /// Component that represents a wall light. It has a light bulb that can be replaced when broken. /// - [RegisterComponent, Access(typeof(PoweredLightSystem))] + [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause, Access(typeof(SharedPoweredLightSystem))] public sealed partial class PoweredLightComponent : Component { - [DataField("burnHandSound")] + /* + * Stop adding more fields, use components or I will shed you. + */ + + [DataField] public SoundSpecifier BurnHandSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg"); - [DataField("turnOnSound")] + [DataField] public SoundSpecifier TurnOnSound = new SoundPathSpecifier("/Audio/Machines/light_tube_on.ogg"); - [DataField("hasLampOnSpawn", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? HasLampOnSpawn = null; + // Should be using containerfill? + [DataField] + public EntProtoId? HasLampOnSpawn = null; [DataField("bulb")] public LightBulbType BulbType; - [DataField("on")] + [DataField, AutoNetworkedField] public bool On = true; - [DataField("ignoreGhostsBoo")] + [DataField] public bool IgnoreGhostsBoo; - [DataField("ghostBlinkingTime")] + [DataField] public TimeSpan GhostBlinkingTime = TimeSpan.FromSeconds(10); - [DataField("ghostBlinkingCooldown")] + [DataField] public TimeSpan GhostBlinkingCooldown = TimeSpan.FromSeconds(60); [ViewVariables] public ContainerSlot LightBulbContainer = default!; - [ViewVariables] + + [AutoNetworkedField] public bool CurrentLit; - [ViewVariables] + + [DataField, AutoNetworkedField] public bool IsBlinking; - [ViewVariables] + + [DataField, AutoNetworkedField, AutoPausedField] public TimeSpan LastThunk; - [ViewVariables] + + [DataField, AutoPausedField] public TimeSpan? LastGhostBlink; - [DataField("onPort", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string OnPort = "On"; + [DataField] + public ProtoId OnPort = "On"; - [DataField("offPort", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string OffPort = "Off"; + [DataField] + public ProtoId OffPort = "Off"; - [DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string TogglePort = "Toggle"; + [DataField] + public ProtoId TogglePort = "Toggle"; /// /// How long it takes to eject a bulb from this /// - [DataField("ejectBulbDelay")] + [DataField] public float EjectBulbDelay = 2; /// /// Shock damage done to a mob that hits the light with an unarmed attack /// - [DataField("unarmedHitShock")] + [DataField] public int UnarmedHitShock = 20; /// /// Stun duration applied to a mob that hits the light with an unarmed attack /// - [DataField("unarmedHitStun")] + [DataField] public TimeSpan UnarmedHitStun = TimeSpan.FromSeconds(5); } } diff --git a/Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs b/Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs new file mode 100644 index 0000000000..34c986b4de --- /dev/null +++ b/Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs @@ -0,0 +1,84 @@ +using Content.Shared.Destructible; +using Content.Shared.Light.Components; +using Content.Shared.Throwing; +using Robust.Shared.Audio.Systems; + +namespace Content.Shared.Light.EntitySystems; + +public abstract class SharedLightBulbSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(HandleLand); + SubscribeLocalEvent(OnBreak); + } + + private void OnInit(EntityUid uid, LightBulbComponent bulb, ComponentInit args) + { + // update default state of bulbs + SetColor(uid, bulb.Color, bulb); + SetState(uid, bulb.State, bulb); + } + + private void HandleLand(EntityUid uid, LightBulbComponent bulb, ref LandEvent args) + { + PlayBreakSound(uid, bulb); + SetState(uid, LightBulbState.Broken, bulb); + } + + private void OnBreak(EntityUid uid, LightBulbComponent component, BreakageEventArgs args) + { + SetState(uid, LightBulbState.Broken, component); + } + + /// + /// Set a new color for a light bulb and raise event about change + /// + public void SetColor(EntityUid uid, Color color, LightBulbComponent? bulb = null) + { + if (!Resolve(uid, ref bulb) || bulb.Color.Equals(color)) + return; + + bulb.Color = color; + Dirty(uid, bulb); + UpdateAppearance(uid, bulb); + } + + /// + /// Set a new state for a light bulb (broken, burned) and raise event about change + /// + public void SetState(EntityUid uid, LightBulbState state, LightBulbComponent? bulb = null) + { + if (!Resolve(uid, ref bulb) || bulb.State == state) + return; + + bulb.State = state; + Dirty(uid, bulb); + UpdateAppearance(uid, bulb); + } + + public void PlayBreakSound(EntityUid uid, LightBulbComponent? bulb = null, EntityUid? user = null) + { + if (!Resolve(uid, ref bulb)) + return; + + _audio.PlayPredicted(bulb.BreakSound, uid, user: user); + } + + private void UpdateAppearance(EntityUid uid, LightBulbComponent? bulb = null, + AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref bulb, ref appearance, logMissing: false)) + return; + + // try to update appearance and color + _appearance.SetData(uid, LightBulbVisuals.State, bulb.State, appearance); + _appearance.SetData(uid, LightBulbVisuals.Color, bulb.Color, appearance); + } +} diff --git a/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs b/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs new file mode 100644 index 0000000000..e1dc4be5d0 --- /dev/null +++ b/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs @@ -0,0 +1,417 @@ +using Content.Shared.Audio; +using Content.Shared.Damage; +using Content.Shared.Damage.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.DeviceLinking; +using Content.Shared.DeviceLinking.Events; +using Content.Shared.DeviceNetwork; +using Content.Shared.DeviceNetwork.Events; +using Content.Shared.DoAfter; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Content.Shared.Light.Components; +using Content.Shared.Power; +using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; +using Content.Shared.Storage.EntitySystems; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Timing; + +namespace Content.Shared.Light.EntitySystems; + +public abstract class SharedPoweredLightSystem : EntitySystem +{ + [Dependency] protected readonly IGameTiming GameTiming = default!; + [Dependency] private readonly DamageOnInteractSystem _damageOnInteractSystem = default!; + [Dependency] private readonly SharedAmbientSoundSystem _ambientSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedLightBulbSystem _bulbSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!; + [Dependency] private readonly SharedPointLightSystem _pointLight = default!; + [Dependency] private readonly SharedStorageSystem _storage = default!; + [Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!; + + private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2); + public const string LightBulbContainer = "light_bulb"; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemoved); + SubscribeLocalEvent(OnInserted); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnSignalReceived); + SubscribeLocalEvent(OnPacketReceived); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(HandleLightDamaged); + } + + private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args) + { + light.LightBulbContainer = ContainerSystem.EnsureContainer(uid, LightBulbContainer); + _deviceLink.EnsureSinkPorts(uid, light.OnPort, light.OffPort, light.TogglePort); + } + + private void OnRemoved(Entity light, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID != LightBulbContainer) + return; + + UpdateLight(light, light); + } + + private void OnInserted(Entity light, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID != LightBulbContainer) + return; + + UpdateLight(light, light); + } + + private void OnInteractUsing(EntityUid uid, PoweredLightComponent component, InteractUsingEvent args) + { + if (args.Handled) + return; + + args.Handled = InsertBulb(uid, args.Used, component, user: args.User, playAnimation: true); + } + + private void OnInteractHand(EntityUid uid, PoweredLightComponent light, InteractHandEvent args) + { + if (args.Handled) + return; + + // check if light has bulb to eject + var bulbUid = GetBulb(uid, light); + if (bulbUid == null) + return; + + var userUid = args.User; + //removing a broken/burned bulb, so allow instant removal + if (TryComp(bulbUid.Value, out var bulb) && bulb.State != LightBulbState.Normal) + { + args.Handled = EjectBulb(uid, userUid, light) != null; + return; + } + + // removing a working bulb, so require a delay + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, userUid, light.EjectBulbDelay, new PoweredLightDoAfterEvent(), uid, target: uid) + { + BreakOnMove = true, + BreakOnDamage = true, + }); + + args.Handled = true; + } + + private void OnSignalReceived(Entity ent, ref SignalReceivedEvent args) + { + if (args.Port == ent.Comp.OffPort) + SetState(ent, false, ent.Comp); + else if (args.Port == ent.Comp.OnPort) + SetState(ent, true, ent.Comp); + else if (args.Port == ent.Comp.TogglePort) + ToggleLight(ent, ent.Comp); + } + + /// + /// Turns the light on or of when receiving a command. + /// The light is turned on or of according to the value + /// + private void OnPacketReceived(EntityUid uid, PoweredLightComponent component, DeviceNetworkPacketEvent args) + { + if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || command != DeviceNetworkConstants.CmdSetState) return; + if (!args.Data.TryGetValue(DeviceNetworkConstants.StateEnabled, out bool enabled)) return; + + SetState(uid, enabled, component); + } + + /// + /// Inserts the bulb if possible. + /// + /// True if it could insert it, false if it couldn't. + public bool InsertBulb(EntityUid uid, EntityUid bulbUid, PoweredLightComponent? light = null, EntityUid? user = null, bool playAnimation = false) + { + if (!Resolve(uid, ref light)) + return false; + + // check if light already has bulb + if (GetBulb(uid, light) != null) + return false; + + // check if bulb fits + if (!TryComp(bulbUid, out var lightBulb)) + return false; + + if (lightBulb.Type != light.BulbType) + return false; + + // try to insert bulb in container + if (!ContainerSystem.Insert(bulbUid, light.LightBulbContainer)) + return false; + + if (playAnimation && TryComp(user, out TransformComponent? xform)) + { + var itemXform = Transform(uid); + _storage.PlayPickupAnimation(bulbUid, xform.Coordinates, itemXform.Coordinates, itemXform.LocalRotation, user: user); + } + + return true; + } + + /// + /// Ejects the bulb to a mob's hand if possible. + /// + /// Bulb uid if it was successfully ejected, null otherwise + public EntityUid? EjectBulb(EntityUid uid, EntityUid? userUid = null, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light)) + return null; + + // check if light has bulb + if (GetBulb(uid, light) is not { Valid: true } bulb) + return null; + + // try to remove bulb from container + if (!ContainerSystem.Remove(bulb, light.LightBulbContainer)) + return null; + + // try to place bulb in hands + _handsSystem.PickupOrDrop(userUid, bulb); + + return bulb; + } + + /// + /// Replaces the spawned prototype of a pre-mapinit powered light with a different variant. + /// + public bool ReplaceSpawnedPrototype(Entity light, string bulb) + { + if (light.Comp.LightBulbContainer.ContainedEntity != null) + return false; + + if (LifeStage(light.Owner) >= EntityLifeStage.MapInitialized) + return false; + + light.Comp.HasLampOnSpawn = bulb; + return true; + } + + /// + /// Try to replace current bulb with a new one + /// If succeed old bulb just drops on floor + /// + public bool ReplaceBulb(EntityUid uid, EntityUid bulb, PoweredLightComponent? light = null) + { + EjectBulb(uid, null, light); + return InsertBulb(uid, bulb, light); + } + + /// + /// Try to get light bulb inserted in powered light + /// + /// Bulb uid if it exist, null otherwise + public EntityUid? GetBulb(EntityUid uid, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light)) + return null; + + return light.LightBulbContainer?.ContainedEntity; + } + + /// + /// Try to break bulb inside light fixture + /// + public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light, false)) + return false; + + // if we aren't mapinited, + // just null the spawned bulb + if (LifeStage(uid) < EntityLifeStage.MapInitialized) + { + light.HasLampOnSpawn = null; + return true; + } + + // check bulb state + var bulbUid = GetBulb(uid, light); + if (bulbUid == null || !EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb)) + return false; + if (lightBulb.State == LightBulbState.Broken) + return false; + + // break it + _bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb); + _bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb); + UpdateLight(uid, light); + return true; + } + + protected void UpdateLight(EntityUid uid, + PoweredLightComponent? light = null, + SharedApcPowerReceiverComponent? powerReceiver = null, + AppearanceComponent? appearance = null, + EntityUid? user = null) + { + if (!Resolve(uid, ref light, false)) + return; + + if (!_receiver.ResolveApc(uid, ref powerReceiver)) + return; + + // Optional component. + Resolve(uid, ref appearance, false); + + // check if light has bulb + var bulbUid = GetBulb(uid, light); + if (bulbUid == null || !TryComp(bulbUid.Value, out var lightBulb)) + { + SetLight(uid, false, light: light); + powerReceiver.Load = 0; + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Empty, appearance); + return; + } + + switch (lightBulb.State) + { + case LightBulbState.Normal: + if (powerReceiver.Powered && light.On) + { + SetLight(uid, true, lightBulb.Color, light, lightBulb.LightRadius, lightBulb.LightEnergy, lightBulb.LightSoftness); + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.On, appearance); + var time = GameTiming.CurTime; + if (time > light.LastThunk + ThunkDelay) + { + light.LastThunk = time; + Dirty(uid, light); + _audio.PlayPredicted(light.TurnOnSound, uid, user: user, light.TurnOnSound.Params.AddVolume(-10f)); + } + } + else + { + SetLight(uid, false, light: light); + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Off, appearance); + } + break; + case LightBulbState.Broken: + SetLight(uid, false, light: light); + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Broken, appearance); + break; + case LightBulbState.Burned: + SetLight(uid, false, light: light); + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Burned, appearance); + break; + } + + powerReceiver.Load = (light.On && lightBulb.State == LightBulbState.Normal) ? lightBulb.PowerUse : 0; + } + + /// + /// Destroy the light bulb if the light took any damage. + /// + public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args) + { + // Was it being repaired, or did it take damage? + if (args.DamageIncreased) + { + // Eventually, this logic should all be done by this (or some other) system, not a component. + TryDestroyBulb(uid, component); + } + } + + private void OnPowerChanged(EntityUid uid, PoweredLightComponent component, ref PowerChangedEvent args) + { + // TODO: Power moment + var metadata = MetaData(uid); + + if (metadata.EntityPaused || TerminatingOrDeleted(uid, metadata)) + return; + + UpdateLight(uid, component); + } + + public void ToggleBlinkingLight(EntityUid uid, PoweredLightComponent light, bool isNowBlinking) + { + if (light.IsBlinking == isNowBlinking) + return; + + light.IsBlinking = isNowBlinking; + Dirty(uid, light); + + if (!TryComp(uid, out var appearance)) + return; + + _appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance); + } + + private void SetLight(EntityUid uid, bool value, Color? color = null, PoweredLightComponent? light = null, float? radius = null, float? energy = null, float? softness = null) + { + if (!Resolve(uid, ref light)) + return; + + if (light.CurrentLit != value) + { + light.CurrentLit = value; + Dirty(uid, light); + } + + _ambientSystem.SetAmbience(uid, value); + + if (_pointLight.TryGetLight(uid, out var pointLight)) + { + _pointLight.SetEnabled(uid, value, pointLight); + + if (color != null) + _pointLight.SetColor(uid, color.Value, pointLight); + if (radius != null) + _pointLight.SetRadius(uid, (float)radius, pointLight); + if (energy != null) + _pointLight.SetEnergy(uid, (float)energy, pointLight); + if (softness != null) + _pointLight.SetSoftness(uid, (float)softness, pointLight); + } + + // light bulbs burn your hands! + if (TryComp(uid, out var damageOnInteractComp)) + _damageOnInteractSystem.SetIsDamageActiveTo((uid, damageOnInteractComp), value); + } + + public void ToggleLight(EntityUid uid, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light)) + return; + + light.On = !light.On; + UpdateLight(uid, light); + } + + public void SetState(EntityUid uid, bool state, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light)) + return; + + light.On = state; + Dirty(uid, light); + UpdateLight(uid, light); + } + + private void OnDoAfter(EntityUid uid, PoweredLightComponent component, DoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Args.Target == null) + return; + + EjectBulb(args.Args.Target.Value, args.Args.User, component); + + args.Handled = true; + } +} diff --git a/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs b/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs index 80bacd24bd..96189a92ae 100644 --- a/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs +++ b/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs @@ -19,4 +19,7 @@ public abstract partial class SharedApcPowerReceiverComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] public virtual bool PowerDisabled { get; set; } + + // Doesn't actually do anything on the client just here for shared code. + public abstract float Load { get; set; } } From 291a6c98086adf964510bb169181dd4a4381c786 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 11 Aug 2025 21:07:36 +0000 Subject: [PATCH 26/40] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 19a68e9fdf..eac1ec5354 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: ScarKy0, Jbsundown - changes: - - message: Letters and Packages can now be cut open with sharp objects. Be wary, - this makes cargo lose money! - type: Add - id: 8334 - time: '2025-04-24T19:47:46.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/36815 - author: EmoGarbage404 changes: - message: Fixed situations where using the QSI would send the player into an esoteric @@ -3908,3 +3900,10 @@ id: 8846 time: '2025-08-11T18:57:39.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/38023 +- author: metalgearsloth + changes: + - message: Inserting lights now plays an insert animation. + type: Add + id: 8847 + time: '2025-08-11T21:06:28.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/36541 From de7486b8dba0481c1abc676f05e32beeaa67ea6a Mon Sep 17 00:00:00 2001 From: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:38:01 -0700 Subject: [PATCH 27/40] Fix ReagentQuantity Equality check (#39574) Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> --- Content.Shared/Chemistry/Reagent/ReagentQuantity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Chemistry/Reagent/ReagentQuantity.cs b/Content.Shared/Chemistry/Reagent/ReagentQuantity.cs index 4cc87f5bd7..cc5b52fbe2 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentQuantity.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentQuantity.cs @@ -52,7 +52,7 @@ public partial struct ReagentQuantity : IEquatable public bool Equals(ReagentQuantity other) { - return Quantity != other.Quantity && Reagent.Equals(other.Reagent); + return Quantity == other.Quantity && Reagent.Equals(other.Reagent); } public override bool Equals(object? obj) From 915d8152542f45bd197965147fff65807393e7f7 Mon Sep 17 00:00:00 2001 From: Perry Fraser Date: Mon, 11 Aug 2025 18:44:10 -0400 Subject: [PATCH 28/40] feat: make ReagentId hash by value (#39494) --- Content.Shared/Chemistry/Reagent/ReagentId.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Content.Shared/Chemistry/Reagent/ReagentId.cs b/Content.Shared/Chemistry/Reagent/ReagentId.cs index 798dd28db4..88c0abff2a 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentId.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentId.cs @@ -75,7 +75,21 @@ public partial struct ReagentId : IEquatable public override int GetHashCode() { - return HashCode.Combine(Prototype, Data); + // We need to make sure we take the hash code of Data by value in order + // for hashed key lookups to work properly + var hash = 17; + unchecked + { + if (Data?.Count != 0) + { + foreach (var data in Data ?? []) + { + hash = hash * 23 + data.GetHashCode(); + } + } + } + + return HashCode.Combine(Prototype, hash); } public string ToString(FixedPoint2 quantity) From c76d21e9735b9fff29ae30b5cf6f8c43407bbabe Mon Sep 17 00:00:00 2001 From: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Date: Tue, 12 Aug 2025 20:53:04 +0200 Subject: [PATCH 29/40] Fix antag objective assignment (again) (#39565) init --- Resources/Prototypes/Objectives/traitor.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/Prototypes/Objectives/traitor.yml b/Resources/Prototypes/Objectives/traitor.yml index 411a457961..b4a1baa85f 100644 --- a/Resources/Prototypes/Objectives/traitor.yml +++ b/Resources/Prototypes/Objectives/traitor.yml @@ -150,8 +150,8 @@ - !type:TargetObjectiveMindFilter blacklist: components: - - RandomTraitorAlive - - RandomTraitorProgress + - HelpProgressCondition + - KeepAliveCondition - type: entity parent: [BaseTraitorSocialObjective, BaseHelpProgressObjective] @@ -179,8 +179,8 @@ - !type:TargetObjectiveMindFilter blacklist: components: - - RandomTraitorAlive - - RandomTraitorProgress + - HelpProgressCondition + - KeepAliveCondition # steal From 9b6cb79fa2e2d554480c1f617529ad3a3d7b4484 Mon Sep 17 00:00:00 2001 From: UpAndLeaves <92269094+Alpha-Two@users.noreply.github.com> Date: Tue, 12 Aug 2025 21:09:00 +0100 Subject: [PATCH 30/40] Fix dock radar colours (#38942) * Fix docking colours * Add comments and fallback * Better comments! --- Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs | 8 +++++--- Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs | 4 ++-- .../Shuttles/Systems/ShuttleConsoleSystem.cs | 2 ++ Content.Shared/Shuttles/BUIStates/DockingPortState.cs | 10 ++++++++++ 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs index 2b575b4805..449323c746 100644 --- a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs @@ -40,6 +40,8 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl private readonly HashSet _drawnDocks = new(); private readonly Dictionary _dockButtons = new(); + private readonly Color _fallbackHighlightedColor = Color.Magenta; + /// /// Store buttons for every other dock /// @@ -213,11 +215,11 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl if (HighlightedDock == dock.Entity) { - otherDockColor = Color.ToSrgb(Color.Magenta); + otherDockColor = Color.ToSrgb(dock.HighlightedColor); } else { - otherDockColor = Color.ToSrgb(Color.Purple); + otherDockColor = Color.ToSrgb(dock.Color); } /* @@ -311,7 +313,7 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl ScalePosition(Vector2.Transform(new Vector2(-0.5f, 0.5f), rotation)), ScalePosition(Vector2.Transform(new Vector2(0.5f, -0.5f), rotation))); - var dockColor = Color.Magenta; + var dockColor = _viewedState?.HighlightedColor ?? _fallbackHighlightedColor; var connectionColor = Color.Pink; handle.DrawRect(ourDockConnection, connectionColor.WithAlpha(0.2f)); diff --git a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs index 2dcec6b44a..7899a5ef3e 100644 --- a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs @@ -308,7 +308,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl -dockRadius * UIScale, (Size.X + dockRadius) * UIScale, (Size.Y + dockRadius) * UIScale); - + if (_docks.TryGetValue(nent, out var docks)) { foreach (var state in docks) @@ -321,7 +321,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl continue; } - var color = Color.ToSrgb(Color.Magenta); + var color = Color.ToSrgb(state.HighlightedColor); var verts = new[] { diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs index 049e149135..cbd6abe9f5 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs @@ -235,6 +235,8 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem _xformQuery.TryGetComponent(comp.DockedWith, out var otherDockXform) ? GetNetEntity(otherDockXform.GridUid) : null, + Color = comp.RadarColor, + HighlightedColor = comp.HighlightedRadarColor }; gridDocks.Add(state); diff --git a/Content.Shared/Shuttles/BUIStates/DockingPortState.cs b/Content.Shared/Shuttles/BUIStates/DockingPortState.cs index a605c2ea77..a058831545 100644 --- a/Content.Shared/Shuttles/BUIStates/DockingPortState.cs +++ b/Content.Shared/Shuttles/BUIStates/DockingPortState.cs @@ -17,4 +17,14 @@ public sealed class DockingPortState public bool Connected => GridDockedWith != null; public NetEntity? GridDockedWith; + + /// + /// The default colour used to shade a dock on a radar screen + /// + public Color Color; + + /// + /// The colour used to shade a dock on a radar screen if it is highlighted (hovered over/selected on docking screen/shown in the main ship radar) + /// + public Color HighlightedColor; } From 8b76ace136a985a99d22f3c768aeaf0274facfa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=81da?= Date: Tue, 12 Aug 2025 17:17:03 -0500 Subject: [PATCH 31/40] Slightly shorten examine text for splashing a solution with a melee attack (#39428) smaller text. Co-authored-by: iaada --- .../Locale/en-US/fluids/components/spillable-component.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Locale/en-US/fluids/components/spillable-component.ftl b/Resources/Locale/en-US/fluids/components/spillable-component.ftl index cfcdf86a90..6dc677d0bc 100644 --- a/Resources/Locale/en-US/fluids/components/spillable-component.ftl +++ b/Resources/Locale/en-US/fluids/components/spillable-component.ftl @@ -10,4 +10,4 @@ spill-melee-hit-others = {CAPITALIZE(THE($attacker))} spills some of {THE($spill spill-land-spilled-on-other = {CAPITALIZE(THE($spillable))} spills some of its solution onto {THE($target)}! spill-examine-is-spillable = This container looks spillable. -spill-examine-spillable-weapon = You could splash this onto someone with a melee attack. +spill-examine-spillable-weapon = You could splash this onto someone with an attack. From 39cb27fe2171b55cf236d10bfc801350f7bdd38d Mon Sep 17 00:00:00 2001 From: pathetic meowmeow Date: Tue, 12 Aug 2025 18:21:42 -0400 Subject: [PATCH 32/40] Borg hands & hand whitelisting (#38668) * Borg hands & hand whitelisting * yaml linted * yaml linted (x2) * yaml linted (x3) * my storage tests so pass * no need for SetCount * ok new stuff you can get fixed too * oops * staque * what if we addressed feedback * my place so holder * what if we addresesd feedback * what if i did it correctly * terminating or deleted --- Content.Client/Stack/StackSystem.cs | 16 +- .../UserInterface/Controls/SlotControl.cs | 33 + .../Systems/Hands/HandsUIController.cs | 69 +- .../Systems/Hotbar/Widgets/HotbarGui.xaml.cs | 8 +- .../Controls/ItemStatusPanel.xaml.cs | 43 +- .../ConstructionSystem.Interactions.cs | 4 +- .../Silicons/Borgs/BorgSystem.Modules.cs | 94 +-- Content.Server/Stack/StackSystem.cs | 2 +- .../Components/MachineBoardComponent.cs | 6 + .../Hands/Components/HandsComponent.cs | 35 +- .../EntitySystems/SharedHandsSystem.Pickup.cs | 3 + .../SharedHandsSystem.Whitelist.cs | 15 + .../Hands/EntitySystems/SharedHandsSystem.cs | 7 +- .../Components/ItemBorgModuleComponent.cs | 58 +- Content.Shared/Stacks/SharedStackSystem.cs | 9 +- Content.Shared/Stacks/StackComponent.cs | 12 +- .../Locale/en-US/robotics/borg_modules.ftl | 12 + .../Machine/base_machineboard.yml | 1 + .../Devices/Circuitboards/computer.yml | 1 + .../Devices/Electronics/base_electronics.yml | 1 + .../Objects/Materials/Sheets/glass.yml | 28 +- .../Objects/Materials/Sheets/metal.yml | 19 +- .../Objects/Materials/Sheets/other.yml | 13 +- .../Entities/Objects/Materials/ingots.yml | 1 + .../Entities/Objects/Materials/materials.yml | 3 + .../Entities/Objects/Materials/parts.yml | 41 +- .../Entities/Objects/Misc/machine_parts.yml | 3 + .../Objects/Specific/Janitorial/soap.yml | 22 - .../Objects/Specific/Medical/healing.yml | 54 -- .../Specific/Robotics/borg_modules.yml | 639 ++++++++++++------ .../Objects/Specific/chemistry-vials.yml | 58 -- .../Entities/Objects/Specific/chemistry.yml | 59 -- .../Entities/Objects/Tools/cable_coils.yml | 27 - Resources/Prototypes/tags.yml | 3 + 34 files changed, 732 insertions(+), 667 deletions(-) create mode 100644 Content.Shared/Hands/EntitySystems/SharedHandsSystem.Whitelist.cs create mode 100644 Resources/Locale/en-US/robotics/borg_modules.ftl diff --git a/Content.Client/Stack/StackSystem.cs b/Content.Client/Stack/StackSystem.cs index d12e9900a6..d7d1c34ae2 100644 --- a/Content.Client/Stack/StackSystem.cs +++ b/Content.Client/Stack/StackSystem.cs @@ -28,22 +28,8 @@ namespace Content.Client.Stack base.SetCount(uid, amount, component); - if (component.Lingering && - TryComp(uid, out var sprite)) - { - // tint the stack gray and make it transparent if it's lingering. - var color = component.Count == 0 && component.Lingering - ? Color.DarkGray.WithAlpha(0.65f) - : Color.White; - - for (var i = 0; i < sprite.AllLayers.Count(); i++) - { - _sprite.LayerSetColor((uid, sprite), i, color); - } - } - // TODO PREDICT ENTITY DELETION: This should really just be a normal entity deletion call. - if (component.Count <= 0 && !component.Lingering) + if (component.Count <= 0) { Xform.DetachEntity(uid, Transform(uid)); return; diff --git a/Content.Client/UserInterface/Controls/SlotControl.cs b/Content.Client/UserInterface/Controls/SlotControl.cs index a684bb05ef..2b43f2397d 100644 --- a/Content.Client/UserInterface/Controls/SlotControl.cs +++ b/Content.Client/UserInterface/Controls/SlotControl.cs @@ -1,9 +1,11 @@ using System.Numerics; using Content.Client.Cooldown; using Content.Client.UserInterface.Systems.Inventory.Controls; +using Robust.Client.GameObjects; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; +using Robust.Shared.Prototypes; namespace Content.Client.UserInterface.Controls { @@ -20,6 +22,7 @@ namespace Content.Client.UserInterface.Controls public CooldownGraphic CooldownDisplay { get; } private SpriteView SpriteView { get; } + private EntityPrototypeView ProtoView { get; } public EntityUid? Entity => SpriteView.Entity; @@ -141,6 +144,13 @@ namespace Content.Client.UserInterface.Controls SetSize = new Vector2(DefaultButtonSize, DefaultButtonSize), OverrideDirection = Direction.South }); + AddChild(ProtoView = new EntityPrototypeView + { + Visible = false, + Scale = new Vector2(2, 2), + SetSize = new Vector2(DefaultButtonSize, DefaultButtonSize), + OverrideDirection = Direction.South + }); AddChild(HoverSpriteView = new SpriteView { @@ -209,12 +219,35 @@ namespace Content.Client.UserInterface.Controls HoverSpriteView.SetEntity(null); } + /// + /// Causes the control to display a placeholder prototype, optionally faded + /// public void SetEntity(EntityUid? ent) { SpriteView.SetEntity(ent); + SpriteView.Visible = true; + ProtoView.Visible = false; UpdateButtonTexture(); } + /// + /// Causes the control to display a placeholder prototype, optionally faded + /// + public void SetPrototype(EntProtoId? proto, bool fade) + { + ProtoView.SetPrototype(proto); + SpriteView.Visible = false; + ProtoView.Visible = true; + + UpdateButtonTexture(); + + if (ProtoView.Entity is not { } ent || !fade) + return; + + var sprites = IoCManager.Resolve().GetEntitySystem(); + sprites.SetColor((ent.Owner, ent.Comp1), Color.DarkGray.WithAlpha(0.65f)); + } + private void UpdateButtonTexture() { var fullTexture = Theme.ResolveTextureOrNull(_fullButtonTexturePath); diff --git a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs index ecaddd8f98..a20f4b35f8 100644 --- a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs +++ b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs @@ -12,6 +12,7 @@ using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; using Robust.Shared.Input; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -73,7 +74,8 @@ public sealed class HandsUIController : UIController, IOnStateEntered entity, string name) @@ -139,7 +141,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered(_entity, out var meta) || meta.Deleted) { - Update(null); + Update(null, hand); return; } diff --git a/Content.Server/Construction/ConstructionSystem.Interactions.cs b/Content.Server/Construction/ConstructionSystem.Interactions.cs index 74c856a6f1..dd69fe4e13 100644 --- a/Content.Server/Construction/ConstructionSystem.Interactions.cs +++ b/Content.Server/Construction/ConstructionSystem.Interactions.cs @@ -276,8 +276,8 @@ namespace Content.Server.Construction if(!insertStep.EntityValid(insert, EntityManager, Factory)) return HandleResult.False; - // Unremovable items can't be inserted, unless they are a lingering stack - if(HasComp(insert) && (!TryComp(insert, out var comp) || !comp.Lingering)) + // Unremovable items can't be inserted + if(HasComp(insert)) return HandleResult.False; // If we're only testing whether this step would be handled by the given event, then we're done. diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs index 07fb1cb30b..bbd62d7a01 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs @@ -52,7 +52,7 @@ public sealed partial class BorgSystem private void OnProvideItemStartup(EntityUid uid, ItemBorgModuleComponent component, ComponentStartup args) { - component.ProvidedContainer = Container.EnsureContainer(uid, component.ProvidedContainerId); + Container.EnsureContainer(uid, component.HoldingContainer); } private void OnSelectableInstalled(EntityUid uid, SelectableBorgModuleComponent component, ref BorgModuleInstalledEvent args) @@ -187,43 +187,43 @@ public sealed partial class BorgSystem if (!TryComp(chassis, out var hands)) return; - var xform = Transform(chassis); - foreach (var itemProto in component.Items) - { - EntityUid item; + if (!_container.TryGetContainer(uid, component.HoldingContainer, out var container)) + return; - if (!component.ItemsCreated) + var xform = Transform(chassis); + + for (var i = 0; i < component.Hands.Count; i++) + { + var hand = component.Hands[i]; + var handId = $"{uid}-hand-{i}"; + + _hands.AddHand((chassis, hands), handId, hand.Hand); + EntityUid? item = null; + + if (component.StoredItems is not null) + { + if (component.StoredItems.TryGetValue(handId, out var storedItem)) + { + item = storedItem; + _container.Remove(storedItem, container, force: true); + } + } + else if (hand.Item is { } itemProto) { item = Spawn(itemProto, xform.Coordinates); } - else + + if (item is { } pickUp) { - item = component.ProvidedContainer.ContainedEntities - .FirstOrDefault(ent => Prototype(ent)?.ID == itemProto.Id); - if (!item.IsValid()) + _hands.DoPickup(chassis, handId, pickUp, hands); + if (!hand.ForceRemovable && hand.Hand.Whitelist == null && hand.Hand.Blacklist == null) { - Log.Debug($"no items found: {component.ProvidedContainer.ContainedEntities.Count}"); - continue; + EnsureComp(pickUp); } - - _container.Remove(item, component.ProvidedContainer, force: true); } - - if (!item.IsValid()) - { - Log.Debug("no valid item"); - continue; - } - - var handId = $"{uid}-item{component.HandCounter}"; - component.HandCounter++; - _hands.AddHand((chassis, hands), handId, HandLocation.Middle); - _hands.DoPickup(chassis, handId, item, hands); - EnsureComp(item); - component.ProvidedItems.Add(handId, item); } - component.ItemsCreated = true; + Dirty(uid, component); } private void RemoveProvidedItems(EntityUid chassis, EntityUid uid, BorgChassisComponent? chassisComponent = null, ItemBorgModuleComponent? component = null) @@ -234,27 +234,33 @@ public sealed partial class BorgSystem if (!TryComp(chassis, out var hands)) return; - if (TerminatingOrDeleted(uid)) - { - foreach (var (hand, item) in component.ProvidedItems) - { - QueueDel(item); - _hands.RemoveHand(chassis, hand); - } - component.ProvidedItems.Clear(); + if (!_container.TryGetContainer(uid, component.HoldingContainer, out var container)) return; - } - foreach (var (handId, item) in component.ProvidedItems) + if (TerminatingOrDeleted(uid)) + return; + + component.StoredItems ??= new(); + + for (var i = 0; i < component.Hands.Count; i++) { - if (LifeStage(item) <= EntityLifeStage.MapInitialized) + var handId = $"{uid}-hand-{i}"; + + if (_hands.TryGetHeldItem(chassis, handId, out var held)) { - RemComp(item); - _container.Insert(item, component.ProvidedContainer); + RemComp(held.Value); + _container.Insert(held.Value, container); + component.StoredItems[handId] = held.Value; } + else + { + component.StoredItems.Remove(handId); + } + _hands.RemoveHand(chassis, handId); } - component.ProvidedItems.Clear(); + + Dirty(uid, component); } /// @@ -286,8 +292,8 @@ public sealed partial class BorgSystem if (!TryComp(containedModuleUid, out var containedItemModuleComp)) continue; - if (containedItemModuleComp.Items.Count == itemModuleComp.Items.Count && - containedItemModuleComp.Items.All(itemModuleComp.Items.Contains)) + if (containedItemModuleComp.Hands.Count == itemModuleComp.Hands.Count && + containedItemModuleComp.Hands.All(itemModuleComp.Hands.Contains)) { if (user != null) Popup.PopupEntity(Loc.GetString("borg-module-duplicate"), uid, user.Value); diff --git a/Content.Server/Stack/StackSystem.cs b/Content.Server/Stack/StackSystem.cs index 61153be401..a24cb2df42 100644 --- a/Content.Server/Stack/StackSystem.cs +++ b/Content.Server/Stack/StackSystem.cs @@ -33,7 +33,7 @@ namespace Content.Server.Stack base.SetCount(uid, amount, component); // Queue delete stack if count reaches zero. - if (component.Count <= 0 && !component.Lingering) + if (component.Count <= 0) QueueDel(uid); } diff --git a/Content.Shared/Construction/Components/MachineBoardComponent.cs b/Content.Shared/Construction/Components/MachineBoardComponent.cs index 0469b59fd1..292b17dcf1 100644 --- a/Content.Shared/Construction/Components/MachineBoardComponent.cs +++ b/Content.Shared/Construction/Components/MachineBoardComponent.cs @@ -33,6 +33,12 @@ public sealed partial class MachineBoardComponent : Component public EntProtoId Prototype; } +/// +/// Marker component for any item that's machine board-like without necessarily being a MachineBoardComponent +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class CircuitboardComponent : Component; + [DataDefinition, Serializable] public partial struct GenericPartInfo { diff --git a/Content.Shared/Hands/Components/HandsComponent.cs b/Content.Shared/Hands/Components/HandsComponent.cs index 2aa97e7841..30422210d4 100644 --- a/Content.Shared/Hands/Components/HandsComponent.cs +++ b/Content.Shared/Hands/Components/HandsComponent.cs @@ -1,6 +1,8 @@ using Content.Shared.DisplacementMap; using Content.Shared.Hands.EntitySystems; +using Content.Shared.Whitelist; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Hands.Components; @@ -106,16 +108,45 @@ public sealed partial class HandsComponent : Component public partial record struct Hand { [DataField] - public HandLocation Location = HandLocation.Right; + public HandLocation Location = HandLocation.Middle; + + /// + /// The label to be displayed for this hand when it does not contain an entity + /// + [DataField] + public LocId? EmptyLabel; + + /// + /// The prototype ID of a "representative" entity prototype for what this hand could hold, used in the UI. + /// It is not map-initted. + /// + [DataField] + public EntProtoId? EmptyRepresentative; + + /// + /// What this hand is allowed to hold + /// + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// What this hand is not allowed to hold + /// + [DataField] + public EntityWhitelist? Blacklist; public Hand() { } - public Hand(HandLocation location) + public Hand(HandLocation location, LocId? emptyLabel = null, EntProtoId? emptyRepresentative = null, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null) { Location = location; + EmptyLabel = emptyLabel; + EmptyRepresentative = emptyRepresentative; + Whitelist = whitelist; + Blacklist = blacklist; } } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs index 4558f6c528..ea13004313 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs @@ -185,6 +185,9 @@ public abstract partial class SharedHandsSystem if (checkActionBlocker && !_actionBlocker.CanPickup(uid, entity)) return false; + if (!CheckWhitelists((uid, handsComp), handId, entity)) + return false; + if (ContainerSystem.TryGetContainingContainer((entity, null, null), out var container)) { if (!ContainerSystem.CanRemove(entity, container)) diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Whitelist.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Whitelist.cs new file mode 100644 index 0000000000..dbb1978ce2 --- /dev/null +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Whitelist.cs @@ -0,0 +1,15 @@ +using Content.Shared.Hands.Components; +using Content.Shared.Whitelist; + +namespace Content.Shared.Hands.EntitySystems; + +public abstract partial class SharedHandsSystem +{ + private bool CheckWhitelists(Entity ent, string handId, EntityUid toTest) + { + if (!TryGetHand(ent, handId, out var hand)) + return false; + + return _entityWhitelist.CheckBoth(toTest, hand.Value.Blacklist, hand.Value.Whitelist); + } +} diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs index af5e82f417..8431e27658 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs @@ -7,8 +7,10 @@ using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Inventory.VirtualItem; using Content.Shared.Storage.EntitySystems; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Input.Binding; +using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Shared.Hands.EntitySystems; @@ -23,6 +25,7 @@ public abstract partial class SharedHandsSystem [Dependency] private readonly SharedStorageSystem _storage = default!; [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] private readonly SharedVirtualItemSystem _virtualSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!; public event Action, string, HandLocation>? OnPlayerAddHand; public event Action, string>? OnPlayerRemoveHand; @@ -66,9 +69,9 @@ public abstract partial class SharedHandsSystem /// /// Adds a hand with the given container id and supplied location to the specified entity. /// - public void AddHand(Entity ent, string handName, HandLocation handLocation) + public void AddHand(Entity ent, string handName, HandLocation handLocation, LocId? emptyLabel = null, EntProtoId? emptyRepresentative = null, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null) { - AddHand(ent, handName, new Hand(handLocation)); + AddHand(ent, handName, new Hand(handLocation, emptyLabel, emptyRepresentative, whitelist, blacklist)); } /// diff --git a/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs b/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs index e86edb3476..3680a4cbde 100644 --- a/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs +++ b/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs @@ -1,6 +1,8 @@ -using Robust.Shared.Containers; +using Content.Shared.Hands.Components; +using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; namespace Content.Shared.Silicons.Borgs.Components; @@ -11,40 +13,40 @@ namespace Content.Shared.Silicons.Borgs.Components; public sealed partial class ItemBorgModuleComponent : Component { /// - /// The items that are provided. + /// The hands that are provided. /// [DataField(required: true)] - public List Items = new(); + public List Hands = new(); /// - /// The entities from that were spawned. + /// The items stored within the hands. Null until the first time items are stored. /// - [DataField("providedItems")] - public SortedDictionary ProvidedItems = new(); + [DataField] + public Dictionary? StoredItems; /// - /// A counter that ensures a unique + /// An ID for the container where items are stored when not in use. /// - [DataField("handCounter")] - public int HandCounter; - - /// - /// Whether or not the items have been created and stored in - /// - [DataField("itemsCrated")] - public bool ItemsCreated; - - /// - /// A container where provided items are stored when not being used. - /// This is helpful as it means that items retain state. - /// - [ViewVariables] - public Container ProvidedContainer = default!; - - /// - /// An ID for the container where provided items are stored when not used. - /// - [DataField("providedContainerId")] - public string ProvidedContainerId = "provided_container"; + [DataField] + public string HoldingContainer = "holding_container"; } +[DataDefinition, Serializable, NetSerializable] +public partial record struct BorgHand +{ + [DataField] + public EntProtoId? Item; + + [DataField] + public Hand Hand = new(); + + [DataField] + public bool ForceRemovable = false; + + public BorgHand(EntProtoId? item, Hand hand, bool forceRemovable = false) + { + Item = item; + Hand = hand; + ForceRemovable = forceRemovable; + } +} diff --git a/Content.Shared/Stacks/SharedStackSystem.cs b/Content.Shared/Stacks/SharedStackSystem.cs index 912089379a..60b93a8da8 100644 --- a/Content.Shared/Stacks/SharedStackSystem.cs +++ b/Content.Shared/Stacks/SharedStackSystem.cs @@ -352,10 +352,6 @@ namespace Content.Shared.Stacks private void OnStackStarted(EntityUid uid, StackComponent component, ComponentStartup args) { - // on client, lingering stacks that start at 0 need to be darkened - // on server this does nothing - SetCount(uid, component.Count, component); - if (!TryComp(uid, out AppearanceComponent? appearance)) return; @@ -366,7 +362,7 @@ namespace Content.Shared.Stacks private void OnStackGetState(EntityUid uid, StackComponent component, ref ComponentGetState args) { - args.State = new StackComponentState(component.Count, component.MaxCountOverride, component.Lingering); + args.State = new StackComponentState(component.Count, component.MaxCountOverride); } private void OnStackHandleState(EntityUid uid, StackComponent component, ref ComponentHandleState args) @@ -375,7 +371,6 @@ namespace Content.Shared.Stacks return; component.MaxCountOverride = cast.MaxCount; - component.Lingering = cast.Lingering; // This will change the count and call events. SetCount(uid, cast.Count, component); } @@ -428,7 +423,7 @@ namespace Content.Shared.Stacks return; // We haven't eaten the whole stack yet or are unable to eat it completely. - if (eaten.Comp.Count > 0 || eaten.Comp.Lingering) + if (eaten.Comp.Count > 0) { args.Refresh = true; return; diff --git a/Content.Shared/Stacks/StackComponent.cs b/Content.Shared/Stacks/StackComponent.cs index 356b888606..453c8a737d 100644 --- a/Content.Shared/Stacks/StackComponent.cs +++ b/Content.Shared/Stacks/StackComponent.cs @@ -34,13 +34,6 @@ namespace Content.Shared.Stacks [ViewVariables(VVAccess.ReadOnly)] public bool Unlimited { get; set; } - /// - /// Lingering stacks will remain present even when there are no items. - /// Instead, they will become transparent. - /// - [DataField("lingering"), ViewVariables(VVAccess.ReadWrite)] - public bool Lingering; - [DataField("throwIndividually"), ViewVariables(VVAccess.ReadWrite)] public bool ThrowIndividually { get; set; } = false; @@ -93,13 +86,10 @@ namespace Content.Shared.Stacks public int Count { get; } public int? MaxCount { get; } - public bool Lingering; - - public StackComponentState(int count, int? maxCount, bool lingering) + public StackComponentState(int count, int? maxCount) { Count = count; MaxCount = maxCount; - Lingering = lingering; } } diff --git a/Resources/Locale/en-US/robotics/borg_modules.ftl b/Resources/Locale/en-US/robotics/borg_modules.ftl new file mode 100644 index 0000000000..b6c55447e7 --- /dev/null +++ b/Resources/Locale/en-US/robotics/borg_modules.ftl @@ -0,0 +1,12 @@ +borg-slot-cables-empty = Cables +borg-slot-construction-empty = Construction materials +borg-slot-circuitboards-empty = Circuitboards +borg-slot-flatpacks-empty = Flatpacks +borg-slot-tiles-empty = Floor tiles +borg-slot-topicals-empty = Topicals +borg-slot-small-containers-empty = Small containers +borg-slot-chemical-containers-empty = Chemical containers +borg-slot-documents-empty = Books and papers +borg-slot-soap-empty = Soap +borg-slot-instruments-empty = Instruments +borg-slot-beakers-empty = Beakers diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml index 360fc5ffaa..eb202e6345 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml @@ -18,4 +18,5 @@ Glass: 230 chemicalComposition: Silicon: 20 + - type: Circuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml index 8f2c7f0103..82a149bf27 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml @@ -17,6 +17,7 @@ Glass: 230 chemicalComposition: Silicon: 20 + - type: Circuitboard - type: entity parent: BaseComputerCircuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml index 7848d987e5..7d2daaec0d 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml @@ -17,3 +17,4 @@ Glass: 200 chemicalComposition: Silicon: 20 + - type: Circuitboard diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml index 5676c49324..532d0b9cca 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml @@ -15,6 +15,7 @@ - type: Tag tags: - Sheet + - ConstructionMaterial - type: Material - type: Damageable damageContainer: Inorganic @@ -108,15 +109,6 @@ stackType: Glass count: 1 -- type: entity - parent: SheetGlass - id: SheetGlassLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - - type: entity parent: SheetGlassBase id: SheetRGlass @@ -197,15 +189,6 @@ Quantity: 0.5 canReact: false -- type: entity - parent: SheetRGlass - id: SheetRGlassLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - - type: entity parent: SheetGlassBase id: SheetPGlass @@ -336,15 +319,6 @@ stackType: ReinforcedPlasmaGlass count: 1 -- type: entity - parent: SheetRPGlass - id: SheetRPGlassLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - - type: entity parent: SheetGlassBase id: SheetUGlass diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml index bd22eee691..1cc4c3a5f5 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml @@ -15,6 +15,7 @@ tags: - Sheet - Metal + - ConstructionMaterial - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic @@ -94,15 +95,6 @@ stackType: Steel count: 1 -- type: entity - parent: SheetSteel - id: SheetSteelLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - - type: entity parent: SheetMetalBase id: SheetBrass @@ -229,12 +221,3 @@ - type: Stack stackType: Plasteel count: 1 - -- type: entity - parent: SheetPlasteel - id: SheetPlasteelLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml index 4a37e18ccf..b10e071fd9 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml @@ -12,6 +12,7 @@ - type: Tag tags: - Sheet + - ConstructionMaterial - type: Damageable damageContainer: Inorganic - type: Destructible @@ -110,6 +111,7 @@ - type: Tag tags: - Sheet + - ConstructionMaterial - type: entity parent: SheetPlasma @@ -132,16 +134,6 @@ - type: Stack count: 1 -- type: entity - parent: SheetPlasma - id: SheetPlasmaLingering0 - name: plasma - suffix: 0, Lingering - components: - - type: Stack - lingering: true - count: 0 - - type: entity parent: SheetOtherBase id: SheetPlastic @@ -152,6 +144,7 @@ tags: - Plastic - Sheet + - ConstructionMaterial - type: Material - type: PhysicalComposition materialComposition: diff --git a/Resources/Prototypes/Entities/Objects/Materials/ingots.yml b/Resources/Prototypes/Entities/Objects/Materials/ingots.yml index 7e8e6a5b74..8d9b8259da 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/ingots.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/ingots.yml @@ -14,6 +14,7 @@ - type: Tag tags: - Ingot + - ConstructionMaterial - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic diff --git a/Resources/Prototypes/Entities/Objects/Materials/materials.yml b/Resources/Prototypes/Entities/Objects/Materials/materials.yml index 969dc5997a..9eeed7af80 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/materials.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/materials.yml @@ -147,6 +147,7 @@ - ClothMade - Gauze - RawMaterial + - ConstructionMaterial - type: Construction graph: WebObjects # not sure if I should either keep this here or just make another prototype. Will keep it here just in case. node: cloth @@ -212,6 +213,7 @@ tags: - ClothMade - RawMaterial + - ConstructionMaterial - type: Item heldPrefix: durathread @@ -261,6 +263,7 @@ tags: - Wooden - RawMaterial + - ConstructionMaterial - type: Extractable grindableSolutionName: wood - type: SolutionContainerManager diff --git a/Resources/Prototypes/Entities/Objects/Materials/parts.yml b/Resources/Prototypes/Entities/Objects/Materials/parts.yml index 44d72cda12..71e2704fce 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/parts.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/parts.yml @@ -19,6 +19,9 @@ behaviors: - !type:DoActsBehavior acts: [ "Destruction" ] + - type: Tag + tags: + - ConstructionMaterial - type: entity parent: PartBase @@ -81,6 +84,7 @@ - type: Tag tags: - RodMetal1 + - ConstructionMaterial - type: Sprite state: rods - type: Stack @@ -95,43 +99,8 @@ - type: Tag tags: - RodMetal1 + - ConstructionMaterial - type: Sprite state: rods - type: Stack count: 1 - -- type: entity - parent: PartRodMetal - id: PartRodMetalLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - -- type: entity - parent: FloorTileItemSteel - id: FloorTileItemSteelLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - -- type: entity - parent: FloorTileItemWhite - id: FloorTileItemWhiteLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - -- type: entity - parent: FloorTileItemDark - id: FloorTileItemDarkLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 diff --git a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml index 7d944602c5..83bc2d1cec 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml @@ -11,6 +11,9 @@ size: Tiny - type: Stack count: 1 + - type: Tag + tags: + - ConstructionMaterial - type: entity id: MicroManipulatorStockPart diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml index 9f33e3c239..318c8d7150 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml @@ -239,25 +239,3 @@ - type: Residue residueAdjective: residue-slippery residueColor: residue-blue - -- type: entity - name: soap - id: SoapBorg # Intended for borg internals, not slippery or food, not a container for soap reagent - parent: BaseItem - description: A Nanotrasen brand bar of soap. Smells of plasma and machines. - components: - - type: Sprite - sprite: Objects/Specific/Janitorial/soap.rsi - layers: - - state: nt-4 - - type: Appearance - - type: Item - sprite: Objects/Specific/Janitorial/soap.rsi - storedRotation: -90 - - type: CleansForensics - - type: Residue - residueAdjective: residue-slippery - residueColor: residue-grey - - type: Tag - tags: - - Soap diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml index 4a05817646..0f0d8ae0c2 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml @@ -61,15 +61,6 @@ stackType: Ointment count: 1 -- type: entity - id: Ointment10Lingering - parent: Ointment - suffix: 10, Lingering - components: - - type: Stack - lingering: true - count: 10 - - type: entity name: regenerative mesh description: Used to treat even the nastiest burns. Also effective against caustic burns. @@ -118,15 +109,6 @@ stackType: RegenerativeMesh count: 1 -- type: entity - parent: RegenerativeMesh - id: RegenerativeMeshLingering0 - suffix: 0, Lingering - components: - - type: Stack - lingering: true - count: 0 - - type: entity name: bruise pack description: A therapeutic gel pack and bandages designed to treat blunt-force trauma. @@ -172,15 +154,6 @@ stackType: Brutepack count: 1 -- type: entity - id: Brutepack10Lingering - parent: Brutepack - suffix: 10, Lingering - components: - - type: Stack - lingering: true - count: 10 - - type: entity name: medicated suture description: A suture soaked in medicine, treats blunt-force trauma effectively and closes wounds. @@ -228,15 +201,6 @@ stackType: MedicatedSuture count: 1 -- type: entity - parent: MedicatedSuture - id: MedicatedSutureLingering0 - suffix: 0, Lingering - components: - - type: Stack - lingering: true - count: 0 - - type: entity name: blood pack description: Contains a groundbreaking universal blood replacement created by Nanotrasen's advanced medical science. @@ -283,15 +247,6 @@ stackType: Bloodpack count: 1 -- type: entity - parent: Bloodpack - id: Bloodpack10Lingering - suffix: 10, Lingering - components: - - type: Stack - lingering: true - count: 10 - - type: entity parent: BaseHealingItem id: Tourniquet @@ -375,15 +330,6 @@ - type: Stack count: 1 -- type: entity - id: Gauze10Lingering - parent: Gauze - suffix: 10, Lingering - components: - - type: Stack - lingering: true - count: 10 - - type: entity name: aloe cream description: A topical cream for burns. diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml index dee6033a5b..02b1afdbc5 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -46,7 +46,7 @@ - type: SelectableBorgModule - type: ContainerContainer containers: - provided_container: !type:Container { } + holding_container: !type:Container { } - type: entity parent: BaseAction @@ -422,13 +422,31 @@ - state: generic - state: icon-cables - type: ItemBorgModule - items: - - CableApcStackLingering10 - - CableMVStackLingering10 - - CableHVStackLingering10 - - Wirecutter - - Crowbar - - trayScanner + hands: + - item: CableApcStack10 + hand: + emptyRepresentative: CableApcStack10 + emptyLabel: borg-slot-cables-empty + whitelist: + tags: + - CableCoil + - item: CableMVStack10 + hand: + emptyRepresentative: CableMVStack10 + emptyLabel: borg-slot-cables-empty + whitelist: + tags: + - CableCoil + - item: CableHVStack10 + hand: + emptyRepresentative: CableHVStack10 + emptyLabel: borg-slot-cables-empty + whitelist: + tags: + - CableCoil + - item: Wirecutter + - item: Crowbar + - item: trayScanner - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: wire-module } @@ -443,11 +461,11 @@ - state: generic - state: icon-fire-extinguisher - type: ItemBorgModule - items: - - BorgFireExtinguisher - - BorgHandheldGPSBasic - - HandheldStationMapUnpowered - - HandHeldMassScannerBorg + hands: + - item: BorgFireExtinguisher + - item: BorgHandheldGPSBasic + - item: HandheldStationMapUnpowered + - item: HandHeldMassScannerBorg - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: extinguisher-module } @@ -461,13 +479,13 @@ - state: generic - state: icon-tools - type: ItemBorgModule - items: - - Crowbar - - Wrench - - Screwdriver - - Wirecutter - - WelderIndustrial - - Multitool + hands: + - item: Crowbar + - item: Wrench + - item: Screwdriver + - item: Wirecutter + - item: WelderIndustrial + - item: Multitool - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: tool-module } @@ -482,13 +500,13 @@ - state: cargo - state: icon-appraisal - type: ItemBorgModule - items: - - AppraisalTool - - Pen - - HandLabeler - - RubberStampApproved - - RubberStampDenied - - RadioHandheld + hands: + - item: AppraisalTool + - item: Pen + - item: HandLabeler + - item: RubberStampApproved + - item: RubberStampDenied + - item: RadioHandheld - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: appraisal-module } @@ -502,11 +520,11 @@ - state: cargo - state: icon-mining - type: ItemBorgModule - items: - - MiningDrill - - Shovel - - MineralScannerUnpowered - - BorgOreBag + hands: + - item: MiningDrill + - item: Shovel + - item: MineralScannerUnpowered + - item: BorgOreBag - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: mining-module } @@ -521,11 +539,11 @@ - state: cargo - state: icon-mining-adv - type: ItemBorgModule - items: - - MiningDrillDiamond - - Shovel - - AdvancedMineralScannerUnpowered - - OreBagOfHolding + hands: + - item: MiningDrillDiamond + - item: Shovel + - item: AdvancedMineralScannerUnpowered + - item: OreBagOfHolding - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-mining-module } @@ -539,11 +557,11 @@ - state: cargo - state: icon-grappling-gun - type: ItemBorgModule - items: - - WeaponGrapplingGun - - BorgFireExtinguisher - - BorgHandheldGPSBasic - - HandHeldMassScannerBorg + hands: + - item: WeaponGrapplingGun + - item: BorgFireExtinguisher + - item: BorgHandheldGPSBasic + - item: HandHeldMassScannerBorg - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: grappling-module } @@ -559,15 +577,23 @@ - state: engineering - state: icon-tools-adv - type: ItemBorgModule - items: - - JawsOfLife - - PowerDrill - - WelderExperimental - - Multitool - - RemoteSignallerAdvanced + hands: + - item: JawsOfLife + - item: PowerDrill + - item: WelderExperimental + - item: Multitool + - item: RemoteSignallerAdvanced - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-tools-module } +- type: entity + id: BorgModuleConstructionMaterialPlaceholder + parent: BaseItem + components: + - type: Sprite + sprite: Objects/Materials/Sheets/other.rsi + state: generic_materials + - type: entity id: BorgModuleConstruction parent: [ BaseBorgModuleEngineering, BaseProviderBorgModule ] @@ -578,16 +604,61 @@ - state: engineering - state: icon-construction - type: ItemBorgModule - items: - - SheetSteelLingering0 - - SheetGlassLingering0 - - SheetRGlassLingering0 - - SheetRPGlassLingering0 - - SheetPlasteelLingering0 - - PartRodMetalLingering0 - - FloorTileItemSteelLingering0 - - FloorTileItemWhiteLingering0 - - FloorTileItemDarkLingering0 + hands: + - hand: + emptyRepresentative: BorgModuleConstructionMaterialPlaceholder + emptyLabel: borg-slot-construction-empty + whitelist: + tags: + - ConstructionMaterial + - hand: + emptyRepresentative: BorgModuleConstructionMaterialPlaceholder + emptyLabel: borg-slot-construction-empty + whitelist: + tags: + - ConstructionMaterial + - hand: + emptyRepresentative: BorgModuleConstructionMaterialPlaceholder + emptyLabel: borg-slot-construction-empty + whitelist: + tags: + - ConstructionMaterial + - hand: + emptyRepresentative: DoorElectronics + emptyLabel: borg-slot-circuitboards-empty + whitelist: + components: + - Circuitboard + - hand: + emptyRepresentative: MicroManipulatorStockPart + emptyLabel: borg-slot-construction-empty + whitelist: + tags: + - ConstructionMaterial + - hand: + emptyRepresentative: SpaceHeaterFlatpack + emptyLabel: borg-slot-flatpacks-empty + whitelist: + components: + - Flatpack + - hand: + emptyRepresentative: FloorTileItemSteel + emptyLabel: borg-slot-tiles-empty + whitelist: + components: + - FloorTile + - hand: + emptyRepresentative: FloorTileItemWhite + emptyLabel: borg-slot-tiles-empty + whitelist: + components: + - FloorTile + - hand: + emptyRepresentative: FloorTileItemDark + emptyLabel: borg-slot-tiles-empty + whitelist: + components: + - FloorTile - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: construction-module } @@ -601,13 +672,13 @@ - state: engineering - state: icon-rcd - type: ItemBorgModule - items: - - RCDRecharging - - BorgFireExtinguisher - - BorgHandheldGPSBasic - - GasAnalyzer - - HolofanProjectorBorg - - GeigerCounter + hands: + - item: RCDRecharging + - item: BorgFireExtinguisher + - item: BorgHandheldGPSBasic + - item: GasAnalyzer + - item: HolofanProjectorBorg + - item: GeigerCounter - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: rcd-module } @@ -622,11 +693,17 @@ - state: janitor - state: icon-light-replacer - type: ItemBorgModule - items: - - LightReplacer - - BorgTrashBag - - Plunger - - SoapBorg + hands: + - item: LightReplacer + - item: BorgTrashBag + - item: Plunger + - item: SoapNT + hand: + emptyLabel: borg-slot-soap-empty + emptyRepresentative: SoapNT + whitelist: + tags: + - Soap - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: light-replacer-module } @@ -640,11 +717,11 @@ - state: janitor - state: icon-mop - type: ItemBorgModule - items: - - MopItem - - BorgBucket - - BorgSprayBottle - - HoloprojectorBorg + hands: + - item: MopItem + - item: BorgBucket + - item: BorgSprayBottle + - item: HoloprojectorBorg - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: cleaning-module } @@ -659,12 +736,18 @@ - state: janitor - state: icon-mop-adv - type: ItemBorgModule - items: - - AdvMopItem - - BorgMegaSprayBottle - - HoloprojectorBorg - - BorgDropper - - BorgBeaker + hands: + - item: AdvMopItem + - item: BorgMegaSprayBottle + - item: HoloprojectorBorg + - item: BorgDropper + - item: Beaker + hand: + emptyLabel: borg-slot-beakers-empty + emptyRepresentative: Beaker + whitelist: + tags: + - GlassBeaker - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-cleaning-module } @@ -679,9 +762,9 @@ - state: medical - state: icon-diagnosis - type: ItemBorgModule - items: - - HandheldHealthAnalyzerUnpowered - - ClothingNeckStethoscope + hands: + - item: HandheldHealthAnalyzerUnpowered + - item: ClothingNeckStethoscope - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: diagnosis-module } @@ -695,14 +778,36 @@ - state: medical - state: icon-treatment - type: ItemBorgModule - items: - - HandheldHealthAnalyzerUnpowered - - Gauze10Lingering - - Brutepack10Lingering - - Ointment10Lingering - - Bloodpack10Lingering - - RegenerativeMeshLingering0 - - MedicatedSutureLingering0 + hands: + - item: HandheldHealthAnalyzerUnpowered + - item: Gauze + hand: + emptyLabel: borg-slot-topicals-empty + emptyRepresentative: Gauze + whitelist: + components: + - Healing + - item: Brutepack + hand: + emptyLabel: borg-slot-topicals-empty + emptyRepresentative: Brutepack + whitelist: + components: + - Healing + - item: Ointment + hand: + emptyLabel: borg-slot-topicals-empty + emptyRepresentative: Ointment + whitelist: + components: + - Healing + - item: Bloodpack + hand: + emptyLabel: borg-slot-topicals-empty + emptyRepresentative: Bloodpack + whitelist: + components: + - Healing - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: treatment-module } @@ -716,12 +821,12 @@ - state: medical - state: icon-defib - type: ItemBorgModule - items: - - HandheldHealthAnalyzerUnpowered - - DefibrillatorOneHandedUnpowered - - BorgFireExtinguisher - - BorgHandheldGPSBasic - - HandLabeler + hands: + - item: HandheldHealthAnalyzerUnpowered + - item: DefibrillatorOneHandedUnpowered + - item: BorgFireExtinguisher + - item: BorgHandheldGPSBasic + - item: HandLabeler - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: defib-module } @@ -735,13 +840,31 @@ - state: medical - state: icon-chem - type: ItemBorgModule - items: - - HandheldHealthAnalyzerUnpowered - - Syringe - - BorgDropper - - BorgVial - - BorgVial - - BorgVial + hands: + - item: HandheldHealthAnalyzerUnpowered + - item: Syringe + - item: BorgDropper + - item: BaseChemistryEmptyVial + hand: + emptyLabel: borg-slot-small-containers-empty + emptyRepresentative: BaseChemistryEmptyVial + whitelist: + components: + - FitsInDispenser + - item: BaseChemistryEmptyVial + hand: + emptyLabel: borg-slot-small-containers-empty + emptyRepresentative: BaseChemistryEmptyVial + whitelist: + components: + - FitsInDispenser + - item: BaseChemistryEmptyVial + hand: + emptyLabel: borg-slot-small-containers-empty + emptyRepresentative: BaseChemistryEmptyVial + whitelist: + components: + - FitsInDispenser - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: chem-module } @@ -756,13 +879,38 @@ - state: medical - state: icon-chemist - type: ItemBorgModule - items: - - HandheldHealthAnalyzerUnpowered - - BorgHypo - - Syringe - - BorgDropper - - BorgBeaker - - BorgBeaker + hands: + - item: HandheldHealthAnalyzerUnpowered + - item: BorgHypo + - item: Syringe + - item: BorgDropper + - item: Beaker + hand: + emptyLabel: borg-slot-chemical-containers-empty + emptyRepresentative: Beaker + whitelist: + components: + - FitsInDispenser + tags: + - ChemDispensable + - item: Beaker + hand: + emptyLabel: borg-slot-chemical-containers-empty + emptyRepresentative: Beaker + whitelist: + components: + - FitsInDispenser + tags: + - ChemDispensable + - item: Beaker + hand: + emptyLabel: borg-slot-chemical-containers-empty + emptyRepresentative: Beaker + whitelist: + components: + - FitsInDispenser + tags: + - ChemDispensable - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-chem-module } @@ -778,13 +926,19 @@ - state: science - state: icon-artifacts - type: ItemBorgModule - items: - - NodeScanner - - SprayBottle - - GasAnalyzer - - BorgDropper - - BorgVial - - GeigerCounter + hands: + - item: NodeScanner + - item: SprayBottle + - item: GasAnalyzer + - item: BorgDropper + - item: BaseChemistryEmptyVial + hand: + emptyLabel: borg-slot-small-containers-empty + emptyRepresentative: BaseChemistryEmptyVial + whitelist: + components: + - FitsInDispenser + - item: GeigerCounter - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: node-scanner-module } @@ -798,13 +952,23 @@ - state: science - state: icon-anomalies - type: ItemBorgModule - items: - - AnomalyScanner - - AnomalyLocatorUnpowered - - AnomalyLocatorWideUnpowered - - HandLabeler - - SheetRGlassLingering0 - - SheetRPGlassLingering0 + hands: + - item: AnomalyScanner + - item: AnomalyLocatorUnpowered + - item: AnomalyLocatorWideUnpowered + - item: HandLabeler + - hand: + emptyRepresentative: SheetRGlass + emptyLabel: borg-slot-construction-empty + whitelist: + tags: + - ConstructionMaterial + - hand: + emptyRepresentative: SheetRPGlass + emptyLabel: borg-slot-construction-empty + whitelist: + tags: + - ConstructionMaterial - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: anomaly-module } @@ -819,14 +983,43 @@ - state: service - state: icon-pen - type: ItemBorgModule - items: - - Pen - #- BooksBag (Add back when hand whitelisting exists, at the moment they can only use it like an orebag.) - - HandLabeler - - RubberStampApproved - - RubberStampDenied - - BorgDropper - - BorgVial + hands: + - item: Pen + - item: BooksBag + - hand: + emptyLabel: borg-slot-documents-empty + emptyRepresentative: BookBase + whitelist: + tags: + - Book + - Dice + - Document + - Figurine + - TabletopBoard + - Write + - hand: + emptyLabel: borg-slot-documents-empty + emptyRepresentative: Paper + whitelist: + tags: + - Book + - Dice + - Document + - Figurine + - TabletopBoard + - Write + - item: HandLabeler + - item: RubberStampApproved + - item: RubberStampDenied + - item: BorgDropper + - item: DrinkShaker + hand: + emptyLabel: borg-slot-small-containers-empty + emptyRepresentative: DrinkShaker + whitelist: + components: + - FitsInDispenser + - item: BarSpoon - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: service-module } @@ -840,10 +1033,28 @@ - state: service - state: icon-musique - type: ItemBorgModule - items: - - SynthesizerInstrument - - ElectricGuitarInstrument - - SaxophoneInstrument + hands: + - item: SynthesizerInstrument + hand: + emptyLabel: borg-slot-instruments-empty + emptyRepresentative: SynthesizerInstrument + whitelist: + components: + - Instrument + - item: ElectricGuitarInstrument + hand: + emptyLabel: borg-slot-instruments-empty + emptyRepresentative: ElectricGuitarInstrument + whitelist: + components: + - Instrument + - item: SaxophoneInstrument + hand: + emptyLabel: borg-slot-instruments-empty + emptyRepresentative: SaxophoneInstrument + whitelist: + components: + - Instrument - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: musical-module } @@ -857,11 +1068,11 @@ - state: service - state: icon-gardening - type: ItemBorgModule - items: - - HydroponicsToolMiniHoe - - HydroponicsToolSpade - - HydroponicsToolClippers - - Bucket + hands: + - item: HydroponicsToolMiniHoe + - item: HydroponicsToolSpade + - item: HydroponicsToolClippers + - item: Bucket - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: gardening-module } @@ -875,10 +1086,10 @@ - state: service - state: icon-harvesting - type: ItemBorgModule - items: - - HydroponicsToolScythe - - HydroponicsToolHatchet - - PlantBag + hands: + - item: HydroponicsToolScythe + - item: HydroponicsToolHatchet + - item: PlantBag - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: harvesting-module } @@ -892,10 +1103,10 @@ - state: service - state: icon-clown - type: ItemBorgModule - items: - - BikeHorn - - ClownRecorder - - BikeHornInstrument + hands: + - item: BikeHorn + - item: ClownRecorder + - item: BikeHornInstrument - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: clowning-module } @@ -910,12 +1121,12 @@ - state: service - state: icon-clown-adv - type: ItemBorgModule - items: - - HoloprojectorClownBorg - - BorgLauncherCreamPie - - ClownRecorder - - PushHorn - - BikeHornInstrument + hands: + - item: HoloprojectorClownBorg + - item: BorgLauncherCreamPie + - item: ClownRecorder + - item: PushHorn + - item: BikeHornInstrument - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-clowning-module } @@ -930,9 +1141,9 @@ - state: syndicate - state: icon-syndicate - type: ItemBorgModule - items: - - WeaponPistolEchis - - EnergyDaggerLoud + hands: + - item: WeaponPistolEchis + - item: EnergyDaggerLoud - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-weapon-module } - type: Item @@ -967,11 +1178,11 @@ - state: syndicate - state: icon-syndicate - type: ItemBorgModule - items: - - Crowbar - - Emag - - AccessBreaker - - PinpointerSyndicateNuclear + hands: + - item: Crowbar + - item: Emag + - item: AccessBreaker + - item: PinpointerSyndicateNuclear - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-operative-module } @@ -986,9 +1197,9 @@ - state: syndicate - state: icon-syndicate - type: ItemBorgModule - items: - - CyborgEnergySwordDouble - - PinpointerSyndicateNuclear + hands: + - item: CyborgEnergySwordDouble + - item: PinpointerSyndicateNuclear - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-esword-module } @@ -1003,9 +1214,9 @@ - state: syndicate - state: icon-syndicate - type: ItemBorgModule - items: - - WeaponLightMachineGunL6C - - PinpointerSyndicateNuclear + hands: + - item: WeaponLightMachineGunL6C + - item: PinpointerSyndicateNuclear - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-l6c-module } @@ -1020,8 +1231,8 @@ - state: syndicateborgbomb - state: icon-bomb - type: ItemBorgModule - items: - - SelfDestructSeq + hands: + - item: SelfDestructSeq - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-martyr-module } - type: Item @@ -1057,10 +1268,10 @@ - state: xenoborg_generic - state: icon-xenoborg-basic - type: ItemBorgModule - items: - - MaterialBag - - PinpointerMothership - - HandheldGPSBasic + hands: + - item: MaterialBag + - item: PinpointerMothership + - item: HandheldGPSBasic - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-basic-module } @@ -1075,13 +1286,13 @@ - state: xenoborg_generic - state: icon-xenoborg-tools - type: ItemBorgModule - items: - - Crowbar - - Wrench - - Screwdriver - - Wirecutter - - Multitool - - RefuelingWelder + hands: + - item: Crowbar + - item: Wrench + - item: Screwdriver + - item: Wirecutter + - item: Multitool + - item: RefuelingWelder - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-tool-module } @@ -1096,8 +1307,8 @@ - state: xenoborg_engi - state: icon-xenoborg-access-breaker - type: ItemBorgModule - items: - - AccessBreaker + hands: + - item: AccessBreaker - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-access-breaker-module } @@ -1112,8 +1323,8 @@ - state: xenoborg_engi - state: icon-xenoborg-fire-extinguisher - type: ItemBorgModule - items: - - SelfRechargingFireExtinguisher + hands: + - item: SelfRechargingFireExtinguisher - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-extinguisher-module } @@ -1128,8 +1339,8 @@ - state: xenoborg_heavy - state: icon-xenoborg-jammer - type: ItemBorgModule - items: - - XenoborgRadioJammer + hands: + - item: XenoborgRadioJammer - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-jammer-module } @@ -1144,8 +1355,8 @@ - state: xenoborg_heavy - state: icon-xenoborg-laser - type: ItemBorgModule - items: - - XenoborgLaserGun + hands: + - item: XenoborgLaserGun - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-laser-module } @@ -1160,8 +1371,8 @@ - state: xenoborg_heavy - state: icon-xenoborg-laser2 - type: ItemBorgModule - items: - - XenoborgHeavyLaserGun + hands: + - item: XenoborgHeavyLaserGun - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-laser2-module } @@ -1176,12 +1387,12 @@ - state: xenoborg_scout - state: icon-xenoborg-space-movement - type: ItemBorgModule - items: - - HandheldGPSBasic - - HandHeldMassScannerBorg - - HandheldStationMapUnpowered - - WeaponGrapplingGun - - JetpackXenoborg + hands: + - item: HandheldGPSBasic + - item: HandHeldMassScannerBorg + - item: HandheldStationMapUnpowered + - item: WeaponGrapplingGun + - item: JetpackXenoborg - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-space-movement-module } @@ -1196,9 +1407,9 @@ - state: xenoborg_scout - state: icon-xenoborg-sword - type: ItemBorgModule - items: - - KukriKnife - - JetpackXenoborg + hands: + - item: KukriKnife + - item: JetpackXenoborg - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-sword-module } @@ -1213,9 +1424,9 @@ - state: xenoborg_scout - state: icon-xenoborg-sword2 - type: ItemBorgModule - items: - - EnergyDaggerLoudBlue - - JetpackXenoborg + hands: + - item: EnergyDaggerLoudBlue + - item: JetpackXenoborg - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-sword2-module } @@ -1230,8 +1441,8 @@ - state: xenoborg_stealth - state: icon-xenoborg-hypo - type: ItemBorgModule - items: - - NocturineHypo + hands: + - item: NocturineHypo - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-hypo-module } @@ -1246,8 +1457,8 @@ - state: xenoborg_stealth - state: icon-xenoborg-projector - type: ItemBorgModule - items: - - ChameleonProjector + hands: + - item: ChameleonProjector - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-projector-module } @@ -1262,8 +1473,8 @@ - state: xenoborg_stealth - state: icon-xenoborg-cloak - type: ItemBorgModule - items: - - CloakingDevice + hands: + - item: CloakingDevice - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-eye-module } @@ -1278,7 +1489,7 @@ - state: xenoborg_stealth - state: icon-xenoborg-cloak2 - type: ItemBorgModule - items: - - SuperCloakingDevice + hands: + - item: SuperCloakingDevice - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-eye2-module } diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml index bc8207b673..21b3742d02 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml @@ -104,64 +104,6 @@ solution: beaker - type: DnaSubstanceTrace -- type: entity - parent: BaseItem - id: BorgVial - name: integrated vial - description: An internal compartment installed into a cyborg. Rated for 30 units of any liquid. - components: - # All this shit is here to avoid inheriting breakable, since borgs can't replace broken vials. - - type: Sprite - sprite: Objects/Specific/Chemistry/vial.rsi - layers: - - state: vial-1 - - state: vial-1-1 - map: ["enum.SolutionContainerLayers.Fill"] - visible: false - - type: Appearance - - type: SolutionContainerVisuals - maxFillLevels: 6 - fillBaseName: vial-1- - inHandsMaxFillLevels: 4 - inHandsFillBaseName: -fill- - - type: Drink - solution: beaker - - type: SolutionContainerManager - solutions: - beaker: - maxVol: 30 - - type: MixableSolution - solution: beaker - - type: RefillableSolution - solution: beaker - - type: DrainableSolution - solution: beaker - - type: ExaminableSolution - solution: beaker - exactVolume: true - - type: DrawableSolution - solution: beaker - - type: SolutionTransfer - maxTransferAmount: 30 - canChangeTransferAmount: true - - type: SolutionItemStatus - solution: beaker - - type: UserInterface - interfaces: - enum.TransferAmountUiKey.Key: - type: TransferAmountBoundUserInterface - - type: Item - size: Tiny - sprite: Objects/Specific/Chemistry/vial.rsi - shape: - - 0,0,0,0 - - type: MeleeWeapon - soundNoDamage: - path: "/Audio/Effects/Fluids/splat.ogg" - damage: - types: - Blunt: 0 - - type: entity id: VestineChemistryVial parent: BaseChemistryEmptyVial diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index 70ea8080ae..62c533bff1 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -95,65 +95,6 @@ price: 30 - type: DnaSubstanceTrace -- type: entity - parent: BaseItem - id: BorgBeaker - name: integrated beaker - description: An internal compartment installed into a cyborg. Rated for 50 units of any liquid. - components: - # 3 morbillion components are to avoid inheriting breakable since borgs can't replace beakers. - - type: Tag - tags: - - GlassBeaker - - type: Sprite - sprite: Objects/Specific/Chemistry/beaker.rsi - layers: - - state: beaker - - state: beaker1 - map: ["enum.SolutionContainerLayers.Fill"] - visible: false - - type: Item - sprite: Objects/Specific/Chemistry/beaker.rsi - - type: MeleeWeapon - soundNoDamage: - path: "/Audio/Effects/Fluids/splat.ogg" - damage: - types: - Blunt: 0 - - type: SolutionContainerManager - solutions: - beaker: - maxVol: 50 - - type: MixableSolution - solution: beaker - - type: FitsInDispenser - solution: beaker - - type: RefillableSolution - solution: beaker - - type: DrainableSolution - solution: beaker - - type: ExaminableSolution - solution: beaker - exactVolume: true - - type: DrawableSolution - solution: beaker - - type: InjectableSolution - solution: beaker - - type: SolutionTransfer - canChangeTransferAmount: true - - type: SolutionItemStatus - solution: beaker - - type: UserInterface - interfaces: - enum.TransferAmountUiKey.Key: - type: TransferAmountBoundUserInterface - - type: Drink - solution: beaker - - type: Appearance - - type: SolutionContainerVisuals - maxFillLevels: 6 - fillBaseName: beaker - - type: entity parent: BaseItem id: BaseBeakerMetallic diff --git a/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml b/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml index 4c24117caa..d8b05addca 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml @@ -82,15 +82,6 @@ - type: Stack count: 10 -- type: entity - parent: CableHVStack10 - id: CableHVStackLingering10 - suffix: Lingering, 10 - components: - - type: Stack - lingering: true - count: 10 - - type: entity parent: CableHVStack id: CableHVStack1 @@ -147,15 +138,6 @@ - type: Stack count: 10 -- type: entity - parent: CableMVStack10 - id: CableMVStackLingering10 - suffix: Lingering, 10 - components: - - type: Stack - lingering: true - count: 10 - - type: entity parent: CableMVStack id: CableMVStack1 @@ -211,15 +193,6 @@ - type: Stack count: 10 -- type: entity - parent: CableApcStack10 - id: CableApcStackLingering10 - suffix: Lingering, 10 - components: - - type: Stack - lingering: true - count: 10 - - type: entity parent: CableApcStack id: CableApcStack1 diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 5d34f0f30c..95b261466f 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -347,6 +347,9 @@ - type: Tag id: ComputerTelevisionCircuitboard +- type: Tag + id: ConstructionMaterial + - type: Tag id: ConveyorAssembly From 6f2699f52108ba9b5b7a485d1abe01bdc8e48fff Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 12 Aug 2025 22:22:52 +0000 Subject: [PATCH 33/40] Automatic changelog update --- Resources/Changelog/Changelog.yml | 54 ++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index eac1ec5354..8e80f48616 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: EmoGarbage404 - changes: - - message: Fixed situations where using the QSI would send the player into an esoteric - space realm. - type: Fix - id: 8335 - time: '2025-04-24T21:18:26.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/36834 - author: thetolbean changes: - message: Specific outer clothing now correctly blocks your identity. @@ -3907,3 +3899,49 @@ id: 8847 time: '2025-08-11T21:06:28.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/36541 +- author: sowelipililimute + changes: + - message: Borg modules can now have hands! + type: Add + - message: The generic cables module can now throw and mix and match cables in its + slots + type: Tweak + - message: The engineering construction module can now hold any material in its + materials slots, any floor tile in its floor tile slots, and now has slots for + holding any circuitboard and flatpack + type: Tweak + - message: The janitor custodial module can now throw its soap and pick up new and + better varieties of soap + type: Tweak + - message: The janitor advanced cleaning module can now throw and pick up beakers + in its slot + type: Tweak + - message: The medical topicals module can now hold any type of topicals in its + topicals slots + type: Tweak + - message: The medical chemical module can now drop and pick up its vials, and hold + other small containers + type: Tweak + - message: The medical advanced chemical module can now drop and pick up its beakers, + and hold other larger containers + type: Tweak + - message: The science artifact module can drop and pick up its vial, and hold other + small containers + type: Tweak + - message: The service service module can now drop and pick up its shaker, and pick + up other drink containers. It also has a spoon now.l containers + type: Tweak + - message: The science anomaly module can now throw its reinforced and reinforced + plasma glass + type: Tweak + - message: The service service module now has a book bag, and two slots for books + and paper + type: Tweak + - message: The service service module can now drop and pick up its shaker, and pick + up other drink containers. It also has a spoon now. + type: Tweak + - message: The service musique module can now drop and pick up instruments. + type: Tweak + id: 8848 + time: '2025-08-12T22:21:42.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/38668 From 1901fafc501ff734617f42f3b71e16044334a599 Mon Sep 17 00:00:00 2001 From: Perry Fraser Date: Tue, 12 Aug 2025 19:00:36 -0400 Subject: [PATCH 34/40] fix: fix lights not always enabling correctly (#39585) --- .../Light/EntitySystems/SharedPoweredLightSystem.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs b/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs index e1dc4be5d0..65097f0d06 100644 --- a/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs +++ b/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs @@ -263,6 +263,14 @@ public abstract class SharedPoweredLightSystem : EntitySystem AppearanceComponent? appearance = null, EntityUid? user = null) { + // We don't do anything during state application on the client as if + // it's due to an entity spawn, we'd have to wait for component init to + // be able to do anything, despite the server having already sent us the + // state that we need. On the other hand, we still want this to run in + // prediction so we can, well, predict lights turning on. + if (GameTiming.ApplyingState) + return; + if (!Resolve(uid, ref light, false)) return; From dcfdd8914a43a13c4d396f61d9a2b0f63acc5edf Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 12 Aug 2025 23:01:43 +0000 Subject: [PATCH 35/40] 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 8e80f48616..a8f2351ee5 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: thetolbean - changes: - - message: Specific outer clothing now correctly blocks your identity. - type: Fix - id: 8336 - time: '2025-04-24T23:42:27.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/33085 - author: SlamBamActionman changes: - message: Changed 2 posters to be rules-compliant. @@ -3945,3 +3938,11 @@ id: 8848 time: '2025-08-12T22:21:42.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/38668 +- author: perryprog + changes: + - message: Fixed lights sometimes not emitting light when spawning them or when + they enter PVS. + type: Fix + id: 8849 + time: '2025-08-12T23:00:36.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39585 From 024301e69846711c031033f4e4486140350b4a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=81da?= Date: Tue, 12 Aug 2025 18:04:29 -0500 Subject: [PATCH 36/40] RandomChance trigger condition (#39543) * branch names don't matter anyway * commits are a window to the soul * requested change * also requested * ship it * remove key --------- Co-authored-by: iaada --- .../RandomChanceTriggerConditionComponent.cs | 16 ++++++++++++ .../Systems/TriggerSystem.Condition.cs | 25 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 Content.Shared/Trigger/Components/Conditions/RandomChanceTriggerConditionComponent.cs diff --git a/Content.Shared/Trigger/Components/Conditions/RandomChanceTriggerConditionComponent.cs b/Content.Shared/Trigger/Components/Conditions/RandomChanceTriggerConditionComponent.cs new file mode 100644 index 0000000000..2612f16007 --- /dev/null +++ b/Content.Shared/Trigger/Components/Conditions/RandomChanceTriggerConditionComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Conditions; + +/// +/// This condition will cancel triggers based on random chance. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class RandomChanceTriggerConditionComponent : BaseTriggerConditionComponent +{ + /// + /// Chance for the trigger to succeed. + /// + [DataField, AutoNetworkedField] + public float SuccessChance = .9f; +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs index a917f1ad48..2d0756556a 100644 --- a/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs @@ -1,5 +1,7 @@ -using Content.Shared.Trigger.Components.Conditions; +using Content.Shared.Random.Helpers; +using Content.Shared.Trigger.Components.Conditions; using Content.Shared.Verbs; +using Robust.Shared.Random; namespace Content.Shared.Trigger.Systems; @@ -13,6 +15,8 @@ public sealed partial class TriggerSystem SubscribeLocalEvent(OnToggleTriggerAttempt); SubscribeLocalEvent>(OnToggleGetAltVerbs); + + SubscribeLocalEvent(OnRandomChanceTriggerAttempt); } private void OnWhitelistTriggerAttempt(Entity ent, ref AttemptTriggerEvent args) @@ -54,4 +58,23 @@ public sealed partial class TriggerSystem ent.Comp.Enabled = !ent.Comp.Enabled; Dirty(ent); } + + private void OnRandomChanceTriggerAttempt(Entity ent, + ref AttemptTriggerEvent args) + { + if (args.Key == null || ent.Comp.Keys.Contains(args.Key)) + { + // TODO: Replace with RandomPredicted once the engine PR is merged + var hash = new List + { + (int)_timing.CurTick.Value, + GetNetEntity(ent).Id, + args.User == null ? 0 : GetNetEntity(args.User.Value).Id, + }; + var seed = SharedRandomExtensions.HashCodeCombine(hash); + var rand = new System.Random(seed); + + args.Cancelled |= !rand.Prob(ent.Comp.SuccessChance); // When not successful, Cancelled = true + } + } } From cea8dea005e18be2656632bc2036550e7d0d2245 Mon Sep 17 00:00:00 2001 From: Winkarst-cpu <74284083+Winkarst-cpu@users.noreply.github.com> Date: Wed, 13 Aug 2025 09:35:35 +0300 Subject: [PATCH 37/40] Fix: Break do_after if target/tool becomes inaccessible (#35079) --- Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs index 31ff034809..97ff74f64a 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs @@ -184,22 +184,22 @@ public abstract partial class SharedDoAfterSystem : EntitySystem } } - // Whether the user and the target are too far apart. + // Whether the user and the target are too far apart or they are inaccessible. if (args.Target != null) { if (args.DistanceThreshold != null) { - if (!_interaction.InRangeUnobstructed(args.User, args.Target.Value, args.DistanceThreshold.Value)) + if (!_interaction.InRangeAndAccessible(args.User, args.Target.Value, args.DistanceThreshold.Value)) return true; } } - // Whether the distance between the tool and the user has grown too much. + // Whether the distance between the tool and the user has grown too much or they became inaccessible. if (args.Used != null) { if (args.DistanceThreshold != null) { - if (!_interaction.InRangeUnobstructed(args.User, + if (!_interaction.InRangeAndAccessible(args.User, args.Used.Value, args.DistanceThreshold.Value)) return true; From 1f4dfcdcf94dc327b5de2d016dba2fe18c3acbe2 Mon Sep 17 00:00:00 2001 From: Winkarst-cpu <74284083+Winkarst-cpu@users.noreply.github.com> Date: Wed, 13 Aug 2025 10:54:02 +0300 Subject: [PATCH 38/40] Predict GetVerbsEvent in PowerSwitchableSystem (#39589) * Prediction * Update --- .../Power/Generator/GeneratorSystem.cs | 1 + .../Generator/PortableGeneratorSystem.cs | 8 --- .../Power/Generator/PowerSwitchableSystem.cs | 56 ++----------------- .../SharedPortableGeneratorSystem.cs | 12 ++++ .../Generator/SharedPowerSwitchableSystem.cs | 45 +++++++++++++++ 5 files changed, 62 insertions(+), 60 deletions(-) diff --git a/Content.Server/Power/Generator/GeneratorSystem.cs b/Content.Server/Power/Generator/GeneratorSystem.cs index 72b99d59af..97491ddbf4 100644 --- a/Content.Server/Power/Generator/GeneratorSystem.cs +++ b/Content.Server/Power/Generator/GeneratorSystem.cs @@ -198,6 +198,7 @@ public sealed class GeneratorSystem : SharedGeneratorSystem generator.On = on; UpdateState(uid, generator); + Dirty(uid, generator); } public override void Update(float frameTime) diff --git a/Content.Server/Power/Generator/PortableGeneratorSystem.cs b/Content.Server/Power/Generator/PortableGeneratorSystem.cs index a2d506f6c2..d8fca4e48a 100644 --- a/Content.Server/Power/Generator/PortableGeneratorSystem.cs +++ b/Content.Server/Power/Generator/PortableGeneratorSystem.cs @@ -42,8 +42,6 @@ public sealed class PortableGeneratorSystem : SharedPortableGeneratorSystem SubscribeLocalEvent(GeneratorStartMessage); SubscribeLocalEvent(GeneratorStopMessage); SubscribeLocalEvent(GeneratorSwitchOutputMessage); - - SubscribeLocalEvent(OnSwitchPowerCheck); } private void GeneratorSwitchOutputMessage(EntityUid uid, PortableGeneratorComponent component, PortableGeneratorSwitchOutputMessage args) @@ -195,12 +193,6 @@ public sealed class PortableGeneratorSystem : SharedPortableGeneratorSystem } } - private void OnSwitchPowerCheck(EntityUid uid, FuelGeneratorComponent comp, ref SwitchPowerCheckEvent args) - { - if (comp.On) - args.DisableMessage = Loc.GetString("fuel-generator-verb-disable-on"); - } - public override void Update(float frameTime) { var query = EntityQueryEnumerator(); diff --git a/Content.Server/Power/Generator/PowerSwitchableSystem.cs b/Content.Server/Power/Generator/PowerSwitchableSystem.cs index 25de3bd293..1a89f20627 100644 --- a/Content.Server/Power/Generator/PowerSwitchableSystem.cs +++ b/Content.Server/Power/Generator/PowerSwitchableSystem.cs @@ -1,5 +1,4 @@ -using Content.Server.NodeContainer; -using Content.Server.NodeContainer.EntitySystems; +using Content.Server.NodeContainer.EntitySystems; using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Power.Nodes; @@ -7,9 +6,7 @@ using Content.Shared.NodeContainer; using Content.Shared.Power; using Content.Shared.Power.Generator; using Content.Shared.Timing; -using Content.Shared.Verbs; using Robust.Shared.Audio.Systems; -using Robust.Shared.Utility; namespace Content.Server.Power.Generator; @@ -26,47 +23,9 @@ public sealed class PowerSwitchableSystem : SharedPowerSwitchableSystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly UseDelaySystem _useDelay = default!; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent>(GetVerbs); - } - - private void GetVerbs(EntityUid uid, PowerSwitchableComponent comp, GetVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract) - return; - - var voltage = VoltageColor(GetNextVoltage(uid, comp)); - var msg = Loc.GetString("power-switchable-switch-voltage", ("voltage", voltage)); - - InteractionVerb verb = new() - { - Act = () => - { - // don't need to check it again since if its disabled server wont let the verb act - Cycle(uid, args.User, comp); - }, - Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/zap.svg.192dpi.png")), - Text = msg - }; - - var ev = new SwitchPowerCheckEvent(); - RaiseLocalEvent(uid, ref ev); - if (ev.DisableMessage != null) - { - verb.Message = ev.DisableMessage; - verb.Disabled = true; - } - - args.Verbs.Add(verb); - } - - /// - /// Cycles voltage then updates nodes and optionally power supplier to match it. - /// - public void Cycle(EntityUid uid, EntityUid user, PowerSwitchableComponent? comp = null) + // TODO: Prediction + /// + public override void Cycle(EntityUid uid, EntityUid user, PowerSwitchableComponent? comp = null) { if (!Resolve(uid, ref comp)) return; @@ -115,10 +74,3 @@ public sealed class PowerSwitchableSystem : SharedPowerSwitchableSystem _useDelay.TryResetDelay((uid, useDelay)); } } - -/// -/// Raised on a to see if its verb should work. -/// If is non-null, the verb is disabled with that as the message. -/// -[ByRefEvent] -public record struct SwitchPowerCheckEvent(string? DisableMessage = null); diff --git a/Content.Shared/Power/Generator/SharedPortableGeneratorSystem.cs b/Content.Shared/Power/Generator/SharedPortableGeneratorSystem.cs index e8ccc85166..4ed911c296 100644 --- a/Content.Shared/Power/Generator/SharedPortableGeneratorSystem.cs +++ b/Content.Shared/Power/Generator/SharedPortableGeneratorSystem.cs @@ -9,6 +9,18 @@ namespace Content.Shared.Power.Generator; /// public abstract class SharedPortableGeneratorSystem : EntitySystem { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSwitchPowerCheck); + } + + private void OnSwitchPowerCheck(EntityUid uid, FuelGeneratorComponent comp, ref SwitchPowerCheckEvent args) + { + if (comp.On) + args.DisableMessage = Loc.GetString("fuel-generator-verb-disable-on"); + } } /// diff --git a/Content.Shared/Power/Generator/SharedPowerSwitchableSystem.cs b/Content.Shared/Power/Generator/SharedPowerSwitchableSystem.cs index c1787b6078..00d2b1d7a0 100644 --- a/Content.Shared/Power/Generator/SharedPowerSwitchableSystem.cs +++ b/Content.Shared/Power/Generator/SharedPowerSwitchableSystem.cs @@ -1,4 +1,6 @@ using Content.Shared.Examine; +using Content.Shared.Verbs; +using Robust.Shared.Utility; namespace Content.Shared.Power.Generator; @@ -11,6 +13,7 @@ public abstract class SharedPowerSwitchableSystem : EntitySystem public override void Initialize() { SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent>(GetVerbs); } private void OnExamined(EntityUid uid, PowerSwitchableComponent comp, ExaminedEvent args) @@ -20,6 +23,41 @@ public abstract class SharedPowerSwitchableSystem : EntitySystem args.PushMarkup(Loc.GetString(comp.ExamineText, ("voltage", voltage))); } + private void GetVerbs(EntityUid uid, PowerSwitchableComponent comp, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + var voltage = VoltageColor(GetNextVoltage(uid, comp)); + var msg = Loc.GetString("power-switchable-switch-voltage", ("voltage", voltage)); + + InteractionVerb verb = new() + { + Act = () => + { + // don't need to check it again since if its disabled server wont let the verb act + Cycle(uid, args.User, comp); + }, + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/zap.svg.192dpi.png")), + Text = msg + }; + + var ev = new SwitchPowerCheckEvent(); + RaiseLocalEvent(uid, ref ev); + if (ev.DisableMessage != null) + { + verb.Message = ev.DisableMessage; + verb.Disabled = true; + } + + args.Verbs.Add(verb); + } + + /// + /// Cycles voltage then updates nodes and optionally power supplier to match it. + /// + public virtual void Cycle(EntityUid uid, EntityUid user, PowerSwitchableComponent? comp = null) { } + /// /// Helper to get the colored markup string for a voltage type. /// @@ -70,3 +108,10 @@ public abstract class SharedPowerSwitchableSystem : EntitySystem return comp.Cables[NextIndex(uid, comp)].Voltage; } } + +/// +/// Raised on a to see if its verb should work. +/// If is non-null, the verb is disabled with that as the message. +/// +[ByRefEvent] +public record struct SwitchPowerCheckEvent(string? DisableMessage = null); From 2743dcf67f027a9f452d16b4a63dc0be7edba49b Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Wed, 13 Aug 2025 12:51:46 +0200 Subject: [PATCH 39/40] Move mind role components to shared (#39606) --- .../Tests/GameRules/NukeOpsTest.cs | 2 +- .../Tests/Minds/MindTests.cs | 3 +- .../Tests/Minds/RoleTests.cs | 4 +-- .../Administration/Systems/AdminSystem.cs | 2 +- .../GameTicking/GameTicker.RoundFlow.cs | 3 +- .../GameTicking/Rules/ChangelingRuleSystem.cs | 23 ------------- .../GameTicking/Rules/DragonRuleSystem.cs | 1 + .../GameTicking/Rules/NukeopsRuleSystem.cs | 1 + .../Rules/RevolutionaryRuleSystem.cs | 4 +++ .../GameTicking/Rules/SurvivorRuleSystem.cs | 1 + .../GameTicking/Rules/ThiefRuleSystem.cs | 1 + .../GameTicking/Rules/TraitorRuleSystem.cs | 5 +-- .../GameTicking/Rules/ZombieRuleSystem.cs | 1 + Content.Server/Ghost/ObserverRoleComponent.cs | 12 ------- .../Roles/GhostRoleMarkerRoleComponent.cs | 15 -------- Content.Server/Ghost/Roles/GhostRoleSystem.cs | 3 -- Content.Server/Mindshield/MindShieldSystem.cs | 1 + .../Ninja/Systems/SpiderChargeSystem.cs | 2 +- .../Systems/NinjaConditionsSystem.cs | 2 +- Content.Server/Roles/DragonRoleComponent.cs | 12 ------- .../Roles/InitialInfectedRoleComponent.cs | 12 ------- Content.Server/Roles/NinjaRoleComponent.cs | 11 ------ Content.Server/Roles/NukeopsRoleComponent.cs | 11 ------ .../Roles/ParadoxCloneRoleComponent.cs | 17 ---------- .../Roles/ParadoxCloneRoleSystem.cs | 2 +- Content.Server/Roles/RemoveRoleCommand.cs | 1 + .../Roles/RevolutionaryRoleComponent.cs | 16 --------- Content.Server/Roles/RoleBriefingComponent.cs | 13 ------- Content.Server/Roles/RoleBriefingSystem.cs | 2 ++ .../Roles/SubvertedSiliconRoleComponent.cs | 11 ------ Content.Server/Roles/SurvivorRoleComponent.cs | 9 ----- Content.Server/Roles/ThiefRoleComponent.cs | 11 ------ Content.Server/Roles/TraitorRoleComponent.cs | 11 ------ Content.Server/Roles/WizardRoleComponent.cs | 7 ---- Content.Server/Roles/ZombieRoleComponent.cs | 11 ------ .../Silicons/Borgs/BorgSystem.MMI.cs | 4 +-- .../Silicons/Laws/SiliconLawSystem.cs | 3 +- .../Thief/Systems/ThiefBeaconSystem.cs | 2 +- Content.Server/Zombies/ZombieSystem.cs | 2 +- .../Changeling/ChangelingRoleComponent.cs | 9 ----- .../EffectConditions/JobCondition.cs | 2 +- .../Components/ChangelingRoleComponent.cs | 9 +++++ .../Roles/Components/DragonRoleComponent.cs | 9 +++++ .../GhostRoleMarkerRoleComponent.cs | 9 +++++ .../InitialInfectedRoleComponent.cs | 9 +++++ .../{Jobs => Components}/JobRoleComponent.cs | 9 ++--- .../{ => Components}/MindRoleComponent.cs | 34 +++++++++---------- .../Roles/Components/NinjaRoleComponent.cs | 9 +++++ .../Roles/Components/NukeopsRoleComponent.cs | 9 +++++ .../Roles/Components/ObserverRoleComponent.cs | 9 +++++ .../Components/ParadoxCloneRoleComponent.cs | 17 ++++++++++ .../Components/RevolutionaryRoleComponent.cs | 16 +++++++++ .../Roles/Components/RoleBriefingComponent.cs | 13 +++++++ .../Components/SiliconBrainRoleComponent.cs | 9 +++++ .../StartingMindRoleComponent.cs | 7 ++-- .../SubvertedSiliconRoleComponent.cs | 9 +++++ .../Roles/Components/SurvivorRoleComponent.cs | 9 +++++ .../Roles/Components/ThiefRoleComponent.cs | 9 +++++ .../Roles/Components/TraitorRoleComponent.cs | 9 +++++ .../Roles/Components/WizardRoleComponent.cs | 9 +++++ .../Roles/Components/ZombieRoleComponent.cs | 9 +++++ Content.Shared/Roles/Jobs/SharedJobSystem.cs | 1 + Content.Shared/Roles/SharedRoleSystem.cs | 2 +- .../Roles/SiliconBrainRoleComponent.cs | 9 ----- .../Locale/en-US/ghost/observer-role.ftl | 2 -- .../Prototypes/Roles/MindRoles/mind_roles.yml | 2 ++ 66 files changed, 227 insertions(+), 276 deletions(-) delete mode 100644 Content.Server/GameTicking/Rules/ChangelingRuleSystem.cs delete mode 100644 Content.Server/Ghost/ObserverRoleComponent.cs delete mode 100644 Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs delete mode 100644 Content.Server/Roles/DragonRoleComponent.cs delete mode 100644 Content.Server/Roles/InitialInfectedRoleComponent.cs delete mode 100644 Content.Server/Roles/NinjaRoleComponent.cs delete mode 100644 Content.Server/Roles/NukeopsRoleComponent.cs delete mode 100644 Content.Server/Roles/ParadoxCloneRoleComponent.cs delete mode 100644 Content.Server/Roles/RevolutionaryRoleComponent.cs delete mode 100644 Content.Server/Roles/RoleBriefingComponent.cs delete mode 100644 Content.Server/Roles/SubvertedSiliconRoleComponent.cs delete mode 100644 Content.Server/Roles/SurvivorRoleComponent.cs delete mode 100644 Content.Server/Roles/ThiefRoleComponent.cs delete mode 100644 Content.Server/Roles/TraitorRoleComponent.cs delete mode 100644 Content.Server/Roles/WizardRoleComponent.cs delete mode 100644 Content.Server/Roles/ZombieRoleComponent.cs delete mode 100644 Content.Shared/Changeling/ChangelingRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/ChangelingRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/DragonRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/GhostRoleMarkerRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/InitialInfectedRoleComponent.cs rename Content.Shared/Roles/{Jobs => Components}/JobRoleComponent.cs (59%) rename Content.Shared/Roles/{ => Components}/MindRoleComponent.cs (52%) create mode 100644 Content.Shared/Roles/Components/NinjaRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/NukeopsRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/ObserverRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/ParadoxCloneRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/RevolutionaryRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/RoleBriefingComponent.cs create mode 100644 Content.Shared/Roles/Components/SiliconBrainRoleComponent.cs rename Content.Shared/Roles/{ => Components}/StartingMindRoleComponent.cs (84%) create mode 100644 Content.Shared/Roles/Components/SubvertedSiliconRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/SurvivorRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/ThiefRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/TraitorRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/WizardRoleComponent.cs create mode 100644 Content.Shared/Roles/Components/ZombieRoleComponent.cs delete mode 100644 Content.Shared/Roles/SiliconBrainRoleComponent.cs delete mode 100644 Resources/Locale/en-US/ghost/observer-role.ftl diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index cf16617479..4f92fd4e55 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -9,7 +9,6 @@ using Content.Server.Mind; using Content.Server.Roles; using Content.Server.RoundEnd; using Content.Server.Shuttles.Components; -using Content.Server.Station.Components; using Content.Shared.CCVar; using Content.Shared.Damage; using Content.Shared.FixedPoint; @@ -20,6 +19,7 @@ using Content.Shared.NPC.Prototypes; using Content.Shared.NPC.Systems; using Content.Shared.NukeOps; using Content.Shared.Pinpointer; +using Content.Shared.Roles.Components; using Content.Shared.Station.Components; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.cs b/Content.IntegrationTests/Tests/Minds/MindTests.cs index 48e11e4648..2f77519829 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTests.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTests.cs @@ -3,7 +3,6 @@ using System.Linq; using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles.Components; using Content.Server.Mind; -using Content.Server.Roles; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; @@ -11,7 +10,7 @@ using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Players; using Content.Shared.Roles; -using Content.Shared.Roles.Jobs; +using Content.Shared.Roles.Components; using Robust.Server.Console; using Robust.Server.GameObjects; using Robust.Server.Player; diff --git a/Content.IntegrationTests/Tests/Minds/RoleTests.cs b/Content.IntegrationTests/Tests/Minds/RoleTests.cs index 8acfff3fb9..f0a7268a3d 100644 --- a/Content.IntegrationTests/Tests/Minds/RoleTests.cs +++ b/Content.IntegrationTests/Tests/Minds/RoleTests.cs @@ -1,7 +1,5 @@ using System.Linq; -using Content.Server.Roles; -using Content.Shared.Roles; -using Content.Shared.Roles.Jobs; +using Content.Shared.Roles.Components; using Robust.Shared.GameObjects; using Robust.Shared.Reflection; diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index 78433db129..677522a83b 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -1,7 +1,6 @@ using System.Linq; using Content.Server.Administration.Managers; using Content.Server.Chat.Managers; -using Content.Server.Forensics; using Content.Server.GameTicking; using Content.Server.Hands.Systems; using Content.Server.Mind; @@ -21,6 +20,7 @@ using Content.Shared.PDA; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Popups; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Roles.Jobs; using Content.Shared.StationRecords; using Content.Shared.Throwing; diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index a6809e050e..1dadca4c03 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -3,7 +3,6 @@ using System.Numerics; using Content.Server.Announcements; using Content.Server.Discord; using Content.Server.GameTicking.Events; -using Content.Server.Ghost; using Content.Server.Maps; using Content.Server.Roles; using Content.Shared.CCVar; @@ -12,6 +11,7 @@ using Content.Shared.GameTicking; using Content.Shared.Mind; using Content.Shared.Players; using Content.Shared.Preferences; +using Content.Shared.Roles.Components; using JetBrains.Annotations; using Prometheus; using Robust.Shared.Asynchronous; @@ -19,7 +19,6 @@ using Robust.Shared.Audio; using Robust.Shared.EntitySerialization; using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Random; diff --git a/Content.Server/GameTicking/Rules/ChangelingRuleSystem.cs b/Content.Server/GameTicking/Rules/ChangelingRuleSystem.cs deleted file mode 100644 index a64b0e904a..0000000000 --- a/Content.Server/GameTicking/Rules/ChangelingRuleSystem.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Server.GameTicking.Rules.Components; -using Content.Server.Roles; -using Content.Shared.Changeling; - -namespace Content.Server.GameTicking.Rules; - -/// -/// Game rule system for Changelings -/// -public sealed class ChangelingRuleSystem : GameRuleSystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnGetBriefing); - } - - private void OnGetBriefing(Entity ent, ref GetBriefingEvent args) - { - args.Append(Loc.GetString("changeling-briefing")); - } -} diff --git a/Content.Server/GameTicking/Rules/DragonRuleSystem.cs b/Content.Server/GameTicking/Rules/DragonRuleSystem.cs index 964b248beb..53cf3bd4b0 100644 --- a/Content.Server/GameTicking/Rules/DragonRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/DragonRuleSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Mind; using Content.Server.Roles; using Content.Server.Station.Systems; using Content.Shared.Localizations; +using Content.Shared.Roles.Components; using Robust.Server.GameObjects; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 17d9e4c847..9c3562b152 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -17,6 +17,7 @@ using Content.Shared.NPC.Components; using Content.Shared.NPC.Systems; using Content.Shared.Nuke; using Content.Shared.NukeOps; +using Content.Shared.Roles.Components; using Content.Shared.Store; using Content.Shared.Tag; using Content.Shared.Zombies; diff --git a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs index 609039ae4c..ef773f59d1 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -23,6 +23,7 @@ using Content.Shared.Mobs.Systems; using Content.Shared.NPC.Prototypes; using Content.Shared.NPC.Systems; using Content.Shared.Revolutionary.Components; +using Content.Shared.Roles.Components; using Content.Shared.Stunnable; using Content.Shared.Zombies; using Robust.Shared.Prototypes; @@ -161,7 +162,10 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem(revMindId, out var role)) + { role.Value.Comp2.ConvertedCount++; + Dirty(role.Value.Owner, role.Value.Comp2); + } } } diff --git a/Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs b/Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs index 4990b98b91..d673444665 100644 --- a/Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Shuttles.Systems; using Content.Shared.GameTicking.Components; using Content.Shared.Mind; using Content.Shared.Mobs.Systems; +using Content.Shared.Roles.Components; using Content.Shared.Survivor.Components; using Content.Shared.Tag; using Robust.Server.GameObjects; diff --git a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs index b00ed38636..75bdd5387b 100644 --- a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Antag; using Content.Server.GameTicking.Rules.Components; using Content.Server.Roles; using Content.Shared.Humanoid; +using Content.Shared.Roles.Components; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index 0a2882aa3c..e72e9d5f73 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -1,19 +1,16 @@ -using Content.Server.Administration.Logs; using Content.Server.Antag; using Content.Server.GameTicking.Rules.Components; using Content.Server.Mind; using Content.Server.Objectives; using Content.Server.PDA.Ringer; -using Content.Server.Roles; using Content.Server.Traitor.Uplink; -using Content.Shared.Database; using Content.Shared.FixedPoint; -using Content.Shared.GameTicking.Components; using Content.Shared.Mind; using Content.Shared.NPC.Systems; using Content.Shared.PDA; using Content.Shared.Random.Helpers; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Roles.Jobs; using Content.Shared.Roles.RoleCodeword; using Robust.Shared.Prototypes; diff --git a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs index 9fab98446a..c6da622bb4 100644 --- a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs @@ -13,6 +13,7 @@ using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Zombies; using Robust.Shared.Player; using Robust.Shared.Timing; diff --git a/Content.Server/Ghost/ObserverRoleComponent.cs b/Content.Server/Ghost/ObserverRoleComponent.cs deleted file mode 100644 index 8421fb7343..0000000000 --- a/Content.Server/Ghost/ObserverRoleComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Ghost; - -/// -/// This is used to mark Observers properly, as they get Minds -/// -[RegisterComponent] -public sealed partial class ObserverRoleComponent : BaseMindRoleComponent -{ - public string Name => Loc.GetString("observer-role-name"); -} diff --git a/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs b/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs deleted file mode 100644 index da3e89ba2b..0000000000 --- a/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Ghost.Roles; - -/// -/// Added to mind role entities to tag that they are a ghostrole. -/// It also holds the name for the round end display -/// -[RegisterComponent] -public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent -{ - //TODO does anything still use this? It gets populated by GhostRolesystem but I don't see anything ever reading it - [DataField] public string? Name; - -} diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index ec69ebe3c1..98261c0908 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -521,9 +521,6 @@ public sealed class GhostRoleSystem : EntitySystem _mindSystem.TransferTo(newMind, mob); _roleSystem.MindAddRoles(newMind.Owner, role.MindRoles, newMind.Comp); - - if (_roleSystem.MindHasRole(newMind!, out var markerRole)) - markerRole.Value.Comp2.Name = role.RoleName; } /// diff --git a/Content.Server/Mindshield/MindShieldSystem.cs b/Content.Server/Mindshield/MindShieldSystem.cs index 7cc1e8fc75..c04fb12027 100644 --- a/Content.Server/Mindshield/MindShieldSystem.cs +++ b/Content.Server/Mindshield/MindShieldSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Database; using Content.Shared.Implants; using Content.Shared.Mindshield.Components; using Content.Shared.Revolutionary.Components; +using Content.Shared.Roles.Components; using Robust.Shared.Containers; namespace Content.Server.Mindshield; diff --git a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs index c08576a5ce..c2d9fb3f68 100644 --- a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs +++ b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs @@ -1,10 +1,10 @@ using Content.Server.Mind; using Content.Server.Objectives.Components; using Content.Server.Popups; -using Content.Server.Roles; using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Systems; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Sticky; using Content.Shared.Trigger; diff --git a/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs b/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs index c9e9326c1e..db78816503 100644 --- a/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs +++ b/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs @@ -1,8 +1,8 @@ using Content.Server.Objectives.Components; -using Content.Server.Roles; using Content.Shared.Objectives.Components; using Content.Shared.Ninja.Components; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Warps; using Robust.Shared.Random; diff --git a/Content.Server/Roles/DragonRoleComponent.cs b/Content.Server/Roles/DragonRoleComponent.cs deleted file mode 100644 index c47455d8f6..0000000000 --- a/Content.Server/Roles/DragonRoleComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Content.Server.Dragon; -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a space dragon. -/// -[RegisterComponent, Access(typeof(DragonSystem))] -public sealed partial class DragonRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Roles/InitialInfectedRoleComponent.cs b/Content.Server/Roles/InitialInfectedRoleComponent.cs deleted file mode 100644 index 475cd3ba60..0000000000 --- a/Content.Server/Roles/InitialInfectedRoleComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are an initial infected. -/// -[RegisterComponent] -public sealed partial class InitialInfectedRoleComponent : BaseMindRoleComponent -{ - -} diff --git a/Content.Server/Roles/NinjaRoleComponent.cs b/Content.Server/Roles/NinjaRoleComponent.cs deleted file mode 100644 index 7bdffe67a3..0000000000 --- a/Content.Server/Roles/NinjaRoleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a space ninja. -/// -[RegisterComponent] -public sealed partial class NinjaRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Roles/NukeopsRoleComponent.cs b/Content.Server/Roles/NukeopsRoleComponent.cs deleted file mode 100644 index 41561088ea..0000000000 --- a/Content.Server/Roles/NukeopsRoleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a nuke operative. -/// -[RegisterComponent] -public sealed partial class NukeopsRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Roles/ParadoxCloneRoleComponent.cs b/Content.Server/Roles/ParadoxCloneRoleComponent.cs deleted file mode 100644 index 32ebb2fe2d..0000000000 --- a/Content.Server/Roles/ParadoxCloneRoleComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a paradox clone. -/// -[RegisterComponent] -public sealed partial class ParadoxCloneRoleComponent : BaseMindRoleComponent -{ - /// - /// Name modifer applied to the player when they turn into a ghost. - /// Needed to be able to keep the original and the clone apart in dead chat. - /// - [DataField] - public LocId? NameModifier = "paradox-clone-ghost-name-modifier"; -} diff --git a/Content.Server/Roles/ParadoxCloneRoleSystem.cs b/Content.Server/Roles/ParadoxCloneRoleSystem.cs index 83e23fef91..c957692b70 100644 --- a/Content.Server/Roles/ParadoxCloneRoleSystem.cs +++ b/Content.Server/Roles/ParadoxCloneRoleSystem.cs @@ -1,7 +1,7 @@ using Content.Shared.Ghost; using Content.Shared.Mind; using Content.Shared.NameModifier.EntitySystems; -using Content.Shared.Roles; +using Content.Shared.Roles.Components; namespace Content.Server.Roles; diff --git a/Content.Server/Roles/RemoveRoleCommand.cs b/Content.Server/Roles/RemoveRoleCommand.cs index 2d18415067..f3cc4a834d 100644 --- a/Content.Server/Roles/RemoveRoleCommand.cs +++ b/Content.Server/Roles/RemoveRoleCommand.cs @@ -3,6 +3,7 @@ using Content.Shared.Administration; using Content.Shared.Players; using Content.Shared.Roles; using Content.Shared.Roles.Jobs; +using Content.Shared.Roles.Components; using Robust.Server.Player; using Robust.Shared.Console; diff --git a/Content.Server/Roles/RevolutionaryRoleComponent.cs b/Content.Server/Roles/RevolutionaryRoleComponent.cs deleted file mode 100644 index dcdb131b9d..0000000000 --- a/Content.Server/Roles/RevolutionaryRoleComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a Revolutionary. -/// -[RegisterComponent] -public sealed partial class RevolutionaryRoleComponent : BaseMindRoleComponent -{ - /// - /// For headrevs, how many people you have converted. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public uint ConvertedCount = 0; -} diff --git a/Content.Server/Roles/RoleBriefingComponent.cs b/Content.Server/Roles/RoleBriefingComponent.cs deleted file mode 100644 index f4d3fe6353..0000000000 --- a/Content.Server/Roles/RoleBriefingComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Adds a briefing to the character info menu, does nothing else. -/// -[RegisterComponent] -public sealed partial class RoleBriefingComponent : BaseMindRoleComponent -{ - [DataField] - public string Briefing; -} diff --git a/Content.Server/Roles/RoleBriefingSystem.cs b/Content.Server/Roles/RoleBriefingSystem.cs index 17b62a08b3..6825fe8e10 100644 --- a/Content.Server/Roles/RoleBriefingSystem.cs +++ b/Content.Server/Roles/RoleBriefingSystem.cs @@ -1,3 +1,5 @@ +using Content.Shared.Roles.Components; + namespace Content.Server.Roles; public sealed class RoleBriefingSystem : EntitySystem diff --git a/Content.Server/Roles/SubvertedSiliconRoleComponent.cs b/Content.Server/Roles/SubvertedSiliconRoleComponent.cs deleted file mode 100644 index 55727573b9..0000000000 --- a/Content.Server/Roles/SubvertedSiliconRoleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a hacked borg. -/// -[RegisterComponent] -public sealed partial class SubvertedSiliconRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Roles/SurvivorRoleComponent.cs b/Content.Server/Roles/SurvivorRoleComponent.cs deleted file mode 100644 index e5e6dd9f87..0000000000 --- a/Content.Server/Roles/SurvivorRoleComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Adds to a mind role ent to tag they're a Survivor -/// -[RegisterComponent] -public sealed partial class SurvivorRoleComponent : BaseMindRoleComponent; diff --git a/Content.Server/Roles/ThiefRoleComponent.cs b/Content.Server/Roles/ThiefRoleComponent.cs deleted file mode 100644 index c0ddee71a4..0000000000 --- a/Content.Server/Roles/ThiefRoleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a thief. -/// -[RegisterComponent] -public sealed partial class ThiefRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Roles/TraitorRoleComponent.cs b/Content.Server/Roles/TraitorRoleComponent.cs deleted file mode 100644 index a8a11a8f1b..0000000000 --- a/Content.Server/Roles/TraitorRoleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a syndicate traitor. -/// -[RegisterComponent] -public sealed partial class TraitorRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Roles/WizardRoleComponent.cs b/Content.Server/Roles/WizardRoleComponent.cs deleted file mode 100644 index 72a89ee2ca..0000000000 --- a/Content.Server/Roles/WizardRoleComponent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Server.Roles; - -/// -/// Mind role to tag entities that they're a Wizard -/// -[RegisterComponent] -public sealed partial class WizardRoleComponent : Component; diff --git a/Content.Server/Roles/ZombieRoleComponent.cs b/Content.Server/Roles/ZombieRoleComponent.cs deleted file mode 100644 index cff25e53e8..0000000000 --- a/Content.Server/Roles/ZombieRoleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a zombie. -/// -[RegisterComponent] -public sealed partial class ZombieRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Silicons/Borgs/BorgSystem.MMI.cs b/Content.Server/Silicons/Borgs/BorgSystem.MMI.cs index 7435e38f5a..b41f1397ec 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.MMI.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.MMI.cs @@ -1,7 +1,7 @@ -using Content.Server.Roles; -using Content.Shared.Containers.ItemSlots; +using Content.Shared.Containers.ItemSlots; using Content.Shared.Mind.Components; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Silicons.Borgs.Components; using Robust.Shared.Containers; diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index 444732b2b3..e672cb5005 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -2,7 +2,6 @@ using System.Linq; using Content.Server.Administration; using Content.Server.Chat.Managers; using Content.Server.Radio.Components; -using Content.Server.Roles; using Content.Server.Station.Systems; using Content.Shared.Administration; using Content.Shared.Chat; @@ -11,9 +10,9 @@ using Content.Shared.GameTicking; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Silicons.Laws; using Content.Shared.Silicons.Laws.Components; -using Content.Shared.Wires; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; diff --git a/Content.Server/Thief/Systems/ThiefBeaconSystem.cs b/Content.Server/Thief/Systems/ThiefBeaconSystem.cs index 4c65ba5c44..069966a8a4 100644 --- a/Content.Server/Thief/Systems/ThiefBeaconSystem.cs +++ b/Content.Server/Thief/Systems/ThiefBeaconSystem.cs @@ -1,12 +1,12 @@ using Content.Server.Mind; using Content.Server.Objectives.Components; -using Content.Server.Roles; using Content.Server.Thief.Components; using Content.Shared.Examine; using Content.Shared.Foldable; using Content.Shared.Popups; using Content.Shared.Verbs; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Robust.Shared.Audio.Systems; namespace Content.Server.Thief.Systems; diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index 11185da61f..f4ff5dd60c 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -6,7 +6,6 @@ using Content.Server.Chat; using Content.Server.Chat.Systems; using Content.Server.Emoting.Systems; using Content.Server.Speech.EntitySystems; -using Content.Server.Roles; using Content.Shared.Anomaly.Components; using Content.Shared.Armor; using Content.Shared.Bed.Sleep; @@ -21,6 +20,7 @@ using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Weapons.Melee.Events; using Content.Shared.Zombies; using Robust.Shared.Prototypes; diff --git a/Content.Shared/Changeling/ChangelingRoleComponent.cs b/Content.Shared/Changeling/ChangelingRoleComponent.cs deleted file mode 100644 index d2e9c1eccb..0000000000 --- a/Content.Shared/Changeling/ChangelingRoleComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Shared.Changeling; - -/// -/// The Mindrole for Changeling Antags -/// -[RegisterComponent] -public sealed partial class ChangelingRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs b/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs index 7fec087d6b..0b942a3e09 100644 --- a/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs +++ b/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs @@ -3,7 +3,7 @@ using Content.Shared.Localizations; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Roles; -using Content.Shared.Roles.Jobs; +using Content.Shared.Roles.Components; using Robust.Shared.Prototypes; namespace Content.Shared.EntityEffects.EffectConditions; diff --git a/Content.Shared/Roles/Components/ChangelingRoleComponent.cs b/Content.Shared/Roles/Components/ChangelingRoleComponent.cs new file mode 100644 index 0000000000..fb9bc05af3 --- /dev/null +++ b/Content.Shared/Roles/Components/ChangelingRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a changeling. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ChangelingRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/DragonRoleComponent.cs b/Content.Shared/Roles/Components/DragonRoleComponent.cs new file mode 100644 index 0000000000..8f5abecd24 --- /dev/null +++ b/Content.Shared/Roles/Components/DragonRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a space dragon. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class DragonRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/GhostRoleMarkerRoleComponent.cs b/Content.Shared/Roles/Components/GhostRoleMarkerRoleComponent.cs new file mode 100644 index 0000000000..623f298474 --- /dev/null +++ b/Content.Shared/Roles/Components/GhostRoleMarkerRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a ghostrole. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/InitialInfectedRoleComponent.cs b/Content.Shared/Roles/Components/InitialInfectedRoleComponent.cs new file mode 100644 index 0000000000..a96c9a4e1d --- /dev/null +++ b/Content.Shared/Roles/Components/InitialInfectedRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are an initial infected. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class InitialInfectedRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Jobs/JobRoleComponent.cs b/Content.Shared/Roles/Components/JobRoleComponent.cs similarity index 59% rename from Content.Shared/Roles/Jobs/JobRoleComponent.cs rename to Content.Shared/Roles/Components/JobRoleComponent.cs index dbaf12beec..c62c0ea1f4 100644 --- a/Content.Shared/Roles/Jobs/JobRoleComponent.cs +++ b/Content.Shared/Roles/Components/JobRoleComponent.cs @@ -1,12 +1,9 @@ using Robust.Shared.GameStates; -namespace Content.Shared.Roles.Jobs; +namespace Content.Shared.Roles.Components; /// -/// Added to mind role entities to mark them as a job role entity. +/// Added to mind role entities to mark them as a job role entity. /// [RegisterComponent, NetworkedComponent] -public sealed partial class JobRoleComponent : BaseMindRoleComponent -{ - -} +public sealed partial class JobRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/MindRoleComponent.cs b/Content.Shared/Roles/Components/MindRoleComponent.cs similarity index 52% rename from Content.Shared/Roles/MindRoleComponent.cs rename to Content.Shared/Roles/Components/MindRoleComponent.cs index 09593c94cd..45ab808192 100644 --- a/Content.Shared/Roles/MindRoleComponent.cs +++ b/Content.Shared/Roles/Components/MindRoleComponent.cs @@ -2,7 +2,7 @@ using Content.Shared.Mind; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -namespace Content.Shared.Roles; +namespace Content.Shared.Roles.Components; /// /// This holds data for, and indicates, a Mind Role entity @@ -11,49 +11,52 @@ namespace Content.Shared.Roles; public sealed partial class MindRoleComponent : BaseMindRoleComponent { /// - /// Marks this Mind Role as Antagonist - /// A single antag Mind Role is enough to make the owner mind count as Antagonist. + /// Marks this Mind Role as Antagonist. + /// A single antag Mind Role is enough to make the owner mind count as Antagonist. /// [DataField] public bool Antag; /// - /// The mind's current antagonist/special role, or lack thereof; + /// The mind's current antagonist/special role, or lack thereof. /// [DataField] public ProtoId? RoleType; /// - /// The role's subtype, shown only to admins to help with antag categorization + /// The role's subtype, shown only to admins to help with antag categorization. /// [DataField] public LocId? Subtype; /// - /// True if this mindrole is an exclusive antagonist. Antag setting is not checked if this is True. + /// True if this mindrole is an exclusive antagonist. Antag setting is not checked if this is True. /// [DataField] public bool ExclusiveAntag; /// - /// The Mind that this role belongs to + /// The Mind that this role belongs to. /// - public Entity Mind { get; set; } + /// + /// TODO: Make this a datafield. Also components should not store other components. + /// + public Entity Mind; /// - /// The Antagonist prototype of this role + /// The Antagonist prototype of this role. /// [DataField] - public ProtoId? AntagPrototype { get; set; } + public ProtoId? AntagPrototype; /// - /// The Job prototype of this role + /// The Job prototype of this role. /// [DataField] - public ProtoId? JobPrototype { get; set; } + public ProtoId? JobPrototype; /// - /// Used to order the characters on by role/antag status. Highest numbers are shown first. + /// Used to order the characters on by role/antag status. Highest numbers are shown first. /// [DataField] public int SortWeight; @@ -62,7 +65,4 @@ public sealed partial class MindRoleComponent : BaseMindRoleComponent // Why does this base component actually exist? It does make auto-categorization easy, but before that it was useless? // I used it for easy organisation/bookkeeping of what components are for mindroles [EntityCategory("Roles")] -public abstract partial class BaseMindRoleComponent : Component -{ - -} +public abstract partial class BaseMindRoleComponent : Component; diff --git a/Content.Shared/Roles/Components/NinjaRoleComponent.cs b/Content.Shared/Roles/Components/NinjaRoleComponent.cs new file mode 100644 index 0000000000..4aa72e1628 --- /dev/null +++ b/Content.Shared/Roles/Components/NinjaRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a space ninja. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class NinjaRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/NukeopsRoleComponent.cs b/Content.Shared/Roles/Components/NukeopsRoleComponent.cs new file mode 100644 index 0000000000..57b90236e5 --- /dev/null +++ b/Content.Shared/Roles/Components/NukeopsRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a nuke operative. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class NukeopsRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/ObserverRoleComponent.cs b/Content.Shared/Roles/Components/ObserverRoleComponent.cs new file mode 100644 index 0000000000..c7a451eac3 --- /dev/null +++ b/Content.Shared/Roles/Components/ObserverRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// This is used to mark Observers properly, as they get Minds. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ObserverRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/ParadoxCloneRoleComponent.cs b/Content.Shared/Roles/Components/ParadoxCloneRoleComponent.cs new file mode 100644 index 0000000000..40a6e86499 --- /dev/null +++ b/Content.Shared/Roles/Components/ParadoxCloneRoleComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a paradox clone. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ParadoxCloneRoleComponent : BaseMindRoleComponent +{ + /// + /// Name modifer applied to the player when they turn into a ghost. + /// Needed to be able to keep the original and the clone apart in dead chat. + /// + [DataField] + public LocId? NameModifier = "paradox-clone-ghost-name-modifier"; +} diff --git a/Content.Shared/Roles/Components/RevolutionaryRoleComponent.cs b/Content.Shared/Roles/Components/RevolutionaryRoleComponent.cs new file mode 100644 index 0000000000..d4d4660814 --- /dev/null +++ b/Content.Shared/Roles/Components/RevolutionaryRoleComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a Revolutionary. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class RevolutionaryRoleComponent : BaseMindRoleComponent +{ + /// + /// For headrevs, how many people you have converted. + /// + [DataField, AutoNetworkedField] + public uint ConvertedCount = 0; +} diff --git a/Content.Shared/Roles/Components/RoleBriefingComponent.cs b/Content.Shared/Roles/Components/RoleBriefingComponent.cs new file mode 100644 index 0000000000..99eccf8a34 --- /dev/null +++ b/Content.Shared/Roles/Components/RoleBriefingComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Adds a briefing to the character info menu, does nothing else. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class RoleBriefingComponent : BaseMindRoleComponent +{ + [DataField(required: true), AutoNetworkedField] + public LocId Briefing; +} diff --git a/Content.Shared/Roles/Components/SiliconBrainRoleComponent.cs b/Content.Shared/Roles/Components/SiliconBrainRoleComponent.cs new file mode 100644 index 0000000000..d8eaebbe1f --- /dev/null +++ b/Content.Shared/Roles/Components/SiliconBrainRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Used on Silicon's minds to get the appropriate mind role +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class SiliconBrainRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/StartingMindRoleComponent.cs b/Content.Shared/Roles/Components/StartingMindRoleComponent.cs similarity index 84% rename from Content.Shared/Roles/StartingMindRoleComponent.cs rename to Content.Shared/Roles/Components/StartingMindRoleComponent.cs index 768307d391..a37ed29a23 100644 --- a/Content.Shared/Roles/StartingMindRoleComponent.cs +++ b/Content.Shared/Roles/Components/StartingMindRoleComponent.cs @@ -1,26 +1,25 @@ using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -namespace Content.Shared.Roles; +namespace Content.Shared.Roles.Components; /// /// This is most likely not the component you are looking for, almost nothing should be using this. /// Consider using GhostRoleComponent or AntagSelectionComponent instead. /// /// The specified mind role will be added to the mob on spawn. -/// /// [RegisterComponent, NetworkedComponent] public sealed partial class StartingMindRoleComponent : Component { /// - /// The ID of the mind role to add + /// The ID of the mind role to add /// [DataField(required: true)] public EntProtoId MindRole; /// - /// Add the mind role silently + /// Add the mind role silently /// [DataField] public bool Silent = true; diff --git a/Content.Shared/Roles/Components/SubvertedSiliconRoleComponent.cs b/Content.Shared/Roles/Components/SubvertedSiliconRoleComponent.cs new file mode 100644 index 0000000000..6b62f458ce --- /dev/null +++ b/Content.Shared/Roles/Components/SubvertedSiliconRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a hacked borg. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class SubvertedSiliconRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/SurvivorRoleComponent.cs b/Content.Shared/Roles/Components/SurvivorRoleComponent.cs new file mode 100644 index 0000000000..1eee04d9e5 --- /dev/null +++ b/Content.Shared/Roles/Components/SurvivorRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are survivor. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class SurvivorRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/ThiefRoleComponent.cs b/Content.Shared/Roles/Components/ThiefRoleComponent.cs new file mode 100644 index 0000000000..ee30833970 --- /dev/null +++ b/Content.Shared/Roles/Components/ThiefRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a thief. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ThiefRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/TraitorRoleComponent.cs b/Content.Shared/Roles/Components/TraitorRoleComponent.cs new file mode 100644 index 0000000000..7348fe0c57 --- /dev/null +++ b/Content.Shared/Roles/Components/TraitorRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a syndicate traitor. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class TraitorRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/WizardRoleComponent.cs b/Content.Shared/Roles/Components/WizardRoleComponent.cs new file mode 100644 index 0000000000..0d24897216 --- /dev/null +++ b/Content.Shared/Roles/Components/WizardRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a wizard. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class WizardRoleComponent : Component; diff --git a/Content.Shared/Roles/Components/ZombieRoleComponent.cs b/Content.Shared/Roles/Components/ZombieRoleComponent.cs new file mode 100644 index 0000000000..e137fb0b0f --- /dev/null +++ b/Content.Shared/Roles/Components/ZombieRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a zombie. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ZombieRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Jobs/SharedJobSystem.cs b/Content.Shared/Roles/Jobs/SharedJobSystem.cs index 12d673feda..6526a3237a 100644 --- a/Content.Shared/Roles/Jobs/SharedJobSystem.cs +++ b/Content.Shared/Roles/Jobs/SharedJobSystem.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Shared.Players; using Content.Shared.Players.PlayTimeTracking; +using Content.Shared.Roles.Components; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Utility; diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index 4f307d8b31..3269680c32 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -5,7 +5,7 @@ using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.GameTicking; using Content.Shared.Mind; -using Content.Shared.Roles.Jobs; +using Content.Shared.Roles.Components; using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; diff --git a/Content.Shared/Roles/SiliconBrainRoleComponent.cs b/Content.Shared/Roles/SiliconBrainRoleComponent.cs deleted file mode 100644 index 72ad0a86b6..0000000000 --- a/Content.Shared/Roles/SiliconBrainRoleComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Shared.Roles; - -/// -/// Used on Silicon's minds to get the appropriate mind role -/// -[RegisterComponent] -public sealed partial class SiliconBrainRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Resources/Locale/en-US/ghost/observer-role.ftl b/Resources/Locale/en-US/ghost/observer-role.ftl deleted file mode 100644 index acb30b128f..0000000000 --- a/Resources/Locale/en-US/ghost/observer-role.ftl +++ /dev/null @@ -1,2 +0,0 @@ -observer-role-name = Observer - diff --git a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml index 95d49c1b83..d387903ec4 100644 --- a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml +++ b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml @@ -320,3 +320,5 @@ roleType: SoloAntagonist subtype: role-subtype-changeling - type: ChangelingRole + - type: RoleBriefing + briefing: changeling-briefing From 99ad34ed06985e665bb24fcc1fc9d92eece1fa1b Mon Sep 17 00:00:00 2001 From: Winkarst-cpu <74284083+Winkarst-cpu@users.noreply.github.com> Date: Wed, 13 Aug 2025 13:52:08 +0300 Subject: [PATCH 40/40] Disable the lock/unlock verb if we can't do that (#39605) * Fix * Update --- Content.Shared/Lock/LockSystem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index 6ca546f581..65ac04874a 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -309,6 +309,7 @@ public sealed class LockSystem : EntitySystem AlternativeVerb verb = new() { + Disabled = !CanToggleLock(uid, args.User), Act = component.Locked ? () => TryUnlock(uid, args.User, component) : () => TryLock(uid, args.User, component),