73H+uoLN=6WL*xv%{ZdN04(~ zx990o&(Nk(or(dK%jhMykZQ|C)i>$YI^vwhc_0ck2^C*ti4@iN)^3e!imhmmIntgm z&|*K(LeMOMKGj7}11e+7YyYEd<&^OLck_tpvjZN`mc7Z8-efCpmQ}C79;>l%AAmmf zVA=NwoYx7Rdxamuwk~Du1*!kV$Nmf6I01D#03I|-Pcr2Kv4o1b>j{C)Gc;VtpZ?)lHDS^h;uw8O;2wLgR4rXsm zOW=H+di8jA_~q28$@Hnoy<6VN`6saciW~rgMy!cRDYt;uE6ZB%h1WXxP2?n#diPT< z?6>l3v0{$eUwr7i;gRzt6YoH0W{_$4CDYU~GhUEu_?QcCjLn-!<6UXzg&j}lw7(7* zZP-jrroI(9Xoy(SCAWNvbpMW=N?XZIH_0;JA}dbOd)rH*^UCYX4&AM w zN_5|ZqfJ}sRnhgUy2;`?;z|PD&@!da)5iTo5xtA>=sL^Rbgh5ZUh5AeRKxSNn|<%N z;b7X+1%UvFXnnM9vS7&fLgm^a8vdZPk8YzV9`!{WO2{h_>ssT;Te6x_-wSdn*)RW~ z+&D5%2{!>OeCJn<6q;f@NC0pNECVE4${IqZ3t9cNI}{d!UMCA0qT?2e`)T?Ivxta- z5;TPLQpAIvcVt;3p0)r`&tl-~W$PT}4Tv`2dXm*}v9lk^+Rwy$n8cejVNM=!zUb@{ z7|b3pVNV3JubMI0?bb{aCi8JH+r*n~${s6WvfC1doq1P+**}=EC)#;e&30`pNu65B z ?|sfovWpn9WJ!ah|a^s?+@-HHdk|l+A8u zUwM+iE=j+U!VcfayYh_9G20b3nSJAF`VBJ5d2%8ZQX|y7h~ueKwz1{0an*+#%557O z>Kob<8k&@0)w2!N4gD?A4NZ;xEk}r5VOPoUH^+&gVI zaK5>}<-uI%iq=$%Kz8a6((qdwp&kiawjBz4mfe!D`i%1Te#l_-IeqwZN(bEbZQ{Fd zPW5Q{#}RH4MRYklX@jz&;mBM|;_|M>Ij=x=diVj})Cb|5cM_*Qkc3U{t!}v2X*X~h z3Zh+8eNOq#krIB@6%r1%DCeg~_C3Q7^RKFse_wU$X==?;0={=2#0Cb%hDpCimBsRT zA~y`sx0oRX5$$@YDMZ#isyvvcZy{XI=%Gjh7;zTTaXS??SmN6!D>i1tAktA5wn`De z;8#i2EdH_Daqdr4xfvtYK{^^5SFaimEm#;ybefSz4f)~~ttbJqnyHi MNs(k_n`%J=_nVnDq %kZpa`0d+jA9iP z<{f==1vzVkEGI=0Efr+w7X>L2+LkOd^0G6J9P+e9!~qODB%8?awN}6a*MI`+x%d(3 zCJH=|HjjKp;1Xy#)RbT+yM y-Q^~T7yl|%Q81P>;NQWKK3ld )1puo!o#a1K$Vpadz)006rrSP?Y`MhSQCuY@^s9@viNOa>iWCH(|ZQKdyJ zC=RMrFCakM^@N}VY3O|_ghsqe8WF-u6}pC@?AgR`vQVhGjV_ZA#BI~%d 8)MwISo}oTD^Kh^Wwv1+*LzH=i~mTZ2P-N@0ytOGZl%_>)EJj{-=5pDIY8 z&rr}#CY1bLnEXA${}(0dVV$)w1pcj6 mt2W`}tPQ{&)5MZ=*22 z5<>3n4tSg~z=BuUcSF{>A&chQqdF3eV4>Gl3Qw%RSe%2*%Eiz @%v#X%{naEjJ(RMcfpA4H)J-Qxg5jsZWp1ui)( zxTNPYw~;0vWx@C)M>fI$3gyG2^Bs^jHMgJ*+8<^JHFuATI&?m`o|ptgKF!HF2jt(L zzjR&WYYSLxdugnVUJO{!7nLYlEY>n0s!)trh@l&_bK=6k=7_6iw9tZpn$fP426$ z9pm|!_mC%~GzBS$#h;aN3j#MBjAYQd8l@o&+x@#o1&wWKgYZU4#Q{g6#mY<%42j$a z1&MVLP}zKTgtl2%_qMT<0l}bGEF6v10Yq^hVSnx>g0V?MW;J&e*tGexKCFnacW>*S z`Lj-E^}?^EQpHqVi`*UzsZf+
AIz3wKhsD@4l^Tts$cw4yV1U=xWBA3$pHa zFd)zey&$7v&J@nRB+x&4g>!XXO?RWOP&c|~{Gm6!>lg2J@`Kc{G2NI>30t=L8}haL zDJNY09vpRfeCh`E^IBPG-D%u=UvAz%y5sX=-J4Apz<6fH^Srqi`#;8~N!IMEtG!#o z3YfDCHE{M>zW$qmk79PaJ-v7I>RiPQmACTj7&faeO_5Rf>u*g5X26o|!0E*gv%_=g zKapPcp3FE|7Zy(K6D R54vs!Jn_gO`^=YrreX5j!51DG@#z@q zH>(45sFRR%;!?`#t)K0rn{pDACqMjQ8vB6B&bJ~oCG}CdB5=tPH*7#iAOi`zy`*t* zb5Z6sEPy#qE?u7DaN3f0^4o`f-J9K=zxpu@-sBVRc31y=%E5OhyZzHZg_T0;1%7cP z^zOidkw+`S!P$>q?1}E2rVQPn;DFE7A7-Wlx81!x5c2hxc%sLlKOfzJ$LW)I{dV+$ z7X;1a7o=>%#o*k`??*gh94E<z0&I#y|V+$CIm}KboPRiFmMcl_9vcJtJvZSo|qG z%m3T~{iopuo|zG$2BOdGf5lo3UKKM7 Qx z-?D5)Z65$>_^YKyo2PdSny>r%5?S?(6H}IA`sJ?V>r?pQPx&{O*6j?AI>LEX;pjkr z_0H(7nt0QPozWlOj-6*^n31|e==!5B>k}WtN%cJM)vf-uD#5dtDfMnkzA*jfK>LqD zSCGZU-Gsf{^juB-DqbEgn8z^(_y5=x^F%sby*zWF {CWw`?2pcdsoozFGDGm}}l~reYu@=-juQ zHQrB`{`#1{=XXwgN4r^WMcG2}s1K%-ZT0@_9>nCQI*W?q*sj9#0s5fuFy=;Scy~Or zq!6LTPJBu|{!QJD-rLNn4>JZ{JyEtkihTHU{Li&ReugSF;n< qeZGc3}MzPaa;%uNuExxgpvhFN4A7*xPP- ndE(1HKZfIXjr6h)Pl@ni?25o$&o<932*}jiwsa$Z)*AdDu_XPt literal 0 HcmV?d00001 diff --git a/Resources/Locale/en-US/weapons/grenades/timer-trigger.ftl b/Resources/Locale/en-US/weapons/grenades/timer-trigger.ftl index 76861c8b4fe..77e2dd938d6 100644 --- a/Resources/Locale/en-US/weapons/grenades/timer-trigger.ftl +++ b/Resources/Locale/en-US/weapons/grenades/timer-trigger.ftl @@ -7,6 +7,8 @@ examine-trigger-timer = The timer is set to {$time} seconds. popup-trigger-timer-set = Timer set to {$time} seconds. +verb-start-detonation = Start detonation + verb-toggle-start-on-stick = Toggle auto-activation popup-start-on-stick-off = The device will no longer activate automatically when planted popup-start-on-stick-on = The device will now activate automatically when planted diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml index 13bd6d16650..3ae89e0d1c0 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml @@ -196,33 +196,30 @@ - type: entity name: wet floor sign id: WetFloorSign - parent: BaseItem + parent: ClothingOuterBase description: Caution! Wet Floor! components: - type: Sprite sprite: Objects/Specific/Janitorial/wet_floor_sign.rsi - state: caution - type: Item sprite: Objects/Specific/Janitorial/wet_floor_sign.rsi size: 15 + - type: Armor + modifiers: + coefficients: + Blunt: 0.95 + Slash: 0.95 - type: Tag tags: - WetFloorSign + - WhitelistChameleon - type: entity - name: wet floor sign suffix: Explosive - description: Caution! Wet Floor! - parent: BaseItem + parent: WetFloorSign id: WetFloorSignMineExplosive components: - - type: Sprite - sprite: Objects/Specific/Janitorial/wet_floor_sign.rsi - state: caution - - type: Item - sprite: Objects/Specific/Janitorial/wet_floor_sign.rsi - size: 15 - type: StepTrigger intersectRatio: 0.2 requiredTriggeredSpeed: 0 @@ -254,6 +251,16 @@ totalIntensity: 60 # about a ~3 tile radius canCreateVacuum: false - type: DeleteOnTrigger + - type: OnUseTimerTrigger + useVerbInstead: true + beepInterval: .25 + beepSound: /Audio/Items/Janitor/floor_sign_beep.ogg + params: + volume: 1 + examinable: false + - type: Tag + tags: # ignore "WhitelistChameleon" tag + - WetFloorSign - type: entity name: janitorial trolley diff --git a/Resources/Textures/Objects/Specific/Janitorial/wet_floor_sign.rsi/caution.png b/Resources/Textures/Objects/Specific/Janitorial/wet_floor_sign.rsi/caution.png deleted file mode 100644 index fdd5c1d2cbbbe9684848a0f00a916594b857842e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 234 zcmV crlDmg3= O0Fc9|NiHux`7X3Ce(o18_6JJ;Q^jnE9CjFVPayB|L+D39gkejeJ?AQ&3jGak44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O_~O1^9%xE||`6<^aQ={|sG~3_3bGKw*i@l5`-&RubeF{2v4uHt%1uA1K0E z;1OBOz`%D1gc(IOyc&QOuz0#ShD5l(oz}?LWWeKcoALku)7#dy+%9A7oguN=`%c*V z8lO!fDZc}R4H(x&GcL+noW-`(PAsZx6@TFOMRn{IuUHIz=_Uw9K4EeAGrK(C)Gc<0 zEA9 +|31_AXotw~^Gg^Wl{c)C37E?CX2y;nhR@OsAxjyaF*`h( z@V;38ow9?H0Fe6pMtW8QXMyhlmL2a{BaVMn)LQh)oRMXPdCcp7ng?y)pOtv=ugUbx zn~e!Z*)MiHw%y>fhHdNhbxIAFWskhNAkOepyN}VsChhXS-6h=ZjEpQ(YWFcEJUGs< z)#JSqFT;aZe;e2fcCO#N*n`>gTdD5>d4_Fo8IQPlrSsePS_1=#!PC{xWt~$(69D$i BqW%B? literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/wet_floor_sign.rsi/icon.png b/Resources/Textures/Objects/Specific/Janitorial/wet_floor_sign.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..204e8b8813f1535fdb9e1f7a1a1d380d63c606ed GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCim11AIbU7ffgP^PfRSN9W7|pb&%nA?9u%#aa^N7yKUx7=CZ#3kC{s7I;J! zGcfQS24TkI`72U@f+?OZjv*HQZ!a9=YB1nozHsx~|NW7h!aR 0fs%DY&=d3Z)WEi+?^nJK#)O<=aqfMY6XM+ n$@8D{JbPf4w&BEgg-0wu#raevg-!(m?PKtC^>bP0l+XkK%VbrT literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Janitorial/wet_floor_sign.rsi/inhand-left.png b/Resources/Textures/Objects/Specific/Janitorial/wet_floor_sign.rsi/inhand-left.png index aa3d9fc9c14305c8351f12b5b8e2d8f169c51440..ed1e8946e7c2fee658d9809091e2fc75de989e08 100644 GIT binary patch delta 313 zcmcc3bdqU;WIZzj1H;_yjc NS%G~10G|-o z1=AV+{AbY7(K&MfD8wLth`Ad`v6ck+1^)*EhTq%xf`I~@1s;*b3=DjSL74G){)!Z! z;3iKO$B+p3x6>|i9X8-_oqX*7|7R<|)<1O9l`xZ#p2wd1?yzSDcj1NETNbc7aYrfq zS()HrvE|;K^8ySj6NJuMIv&vO<73&QDju!IIDuP4WCufqK+JUEH;f+?ZkXHHA51c9 z d1`p_b_#UaCyyG88JA_x8Ux92v*sh;Dzw))7;gH=;2 zildFz3#GHa4z(*_vbT !Ax%$oxYs2-ijK6YXGEyu`h5=k$wm=8KdeeUWnAevsF@FQr8~XFJt_f=?8L^5F z0O0=av Xv~$B8H4};002ovPDHLkV1k%!lJ)=q diff --git a/Resources/Textures/Objects/Specific/Janitorial/wet_floor_sign.rsi/inhand-right.png b/Resources/Textures/Objects/Specific/Janitorial/wet_floor_sign.rsi/inhand-right.png index 286dec22cd18ee027898194451cfc531f3449254..355d215fcd7cf13eddc7539386a45d99aea9d687 100644 GIT binary patch delta 313 zcmaFIbdqU;WIZzj1H;_yjc NS%G~10G|-o z1=AV+{AbY7(K&MfD8wLth`Ad`v6ck+1^)*EhTq%xf`I~@1s;*b3=DjSL74G){)!Z! z;3iKO$B+p3x6>|i9X8-_O@92pzS#6w{k09faW@l|R> D(wbPMmPyWM`sgW{zb^7ou)zUV#dl4)-kj%laQi(qeQ))`$w}am z+KXS|RyrMp!Y^j_F>O3@vEL445ZJtG&NqwN+na@1jNS=(Hwm*|V&11>k {hvotazOVpc)I$ztaD0e0s!D+ Bcsc+8 delta 350 zcmV-k0ipiM0`3Bk8Gi-<0063Kaozv`0X0cPK~#90?b h^L_Ra%DgFzX$Y%y}s#bs$ w`^-R2RRPXn$)~Cc5c$l2r}!N}-yJwhTeehXKsXChM*si-07*qoM6N<$f&+J?`2YX_ diff --git a/Resources/Textures/Objects/Specific/Janitorial/wet_floor_sign.rsi/meta.json b/Resources/Textures/Objects/Specific/Janitorial/wet_floor_sign.rsi/meta.json index bc202f443a9..75c381313e4 100644 --- a/Resources/Textures/Objects/Specific/Janitorial/wet_floor_sign.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Janitorial/wet_floor_sign.rsi/meta.json @@ -1,14 +1,18 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/f8f4aeda930fcd0805ca4cc76d9bc9412a5b3428", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/da42afcbaeaa04e5ba288ade027c011efb3ef0ab, modified by Skarlet and Psychpsyo", "size": { "x": 32, "y": 32 }, "states": [ { - "name": "caution" + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 }, { "name": "inhand-left", From e502b6be6165ac69afabb1268625c82353951ff3 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 28 Sep 2023 00:10:52 -0400 Subject: [PATCH 44/61] 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 0ca3c9c8407..f2c87d9c02a 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,13 +1,4 @@ Entries: -- author: metalgearsloth - changes: - - {message: Added xeno spitters to salvage., type: Add} - - {message: Fix dragon carps not looking like their dragon., type: Fix} - - {message: Fix dragon carp NPCs not following the dragon and not idling on spawn., - type: Fix} - - {message: Fix NPCs not shutting down properly upon player takeover., type: Fix} - id: 4408 - time: '2023-08-02T00:48:57.0000000+00:00' - author: Slava0135 changes: - {message: Hitting entities with thrown items that deal damage now provide more @@ -2973,3 +2964,10 @@ Entries: - {message: Bingus is no longer fuzzy., type: Fix} id: 4907 time: '2023-09-28T04:06:13.0000000+00:00' +- author: Psychpsyo + changes: + - {message: The wet floor sign can now be worn as an outer clothing. This now gives + syndicate janitors access to a suicide vest in form the wet floor sign mine., + type: Add} + id: 4908 + time: '2023-09-28T04:09:45.0000000+00:00' From 040538a0b7a1562c3662d83fb47e93a3b246010d Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 28 Sep 2023 20:27:35 +1000 Subject: [PATCH 45/61] Minor slippery stuff (#20535) --- Content.Shared/Slippery/SlipperySystem.cs | 8 -------- Content.Shared/StatusEffect/StatusEffectsSystem.cs | 3 ++- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Content.Shared/Slippery/SlipperySystem.cs b/Content.Shared/Slippery/SlipperySystem.cs index 41bfe03c4c3..a89291aea49 100644 --- a/Content.Shared/Slippery/SlipperySystem.cs +++ b/Content.Shared/Slippery/SlipperySystem.cs @@ -103,14 +103,6 @@ private void TrySlip(EntityUid uid, SlipperyComponent component, EntityUid other _adminLogger.Add(LogType.Slip, LogImpact.Low, $"{ToPrettyString(other):mob} slipped on collision with {ToPrettyString(uid):entity}"); } - - public void CopyConstruct(EntityUid destUid, SlipperyComponent srcSlip) - { - var destEvaporation = EntityManager.EnsureComponent (destUid); - destEvaporation.SlipSound = srcSlip.SlipSound; - destEvaporation.ParalyzeTime = srcSlip.ParalyzeTime; - destEvaporation.LaunchForwardsMultiplier = srcSlip.LaunchForwardsMultiplier; - } } /// diff --git a/Content.Shared/StatusEffect/StatusEffectsSystem.cs b/Content.Shared/StatusEffect/StatusEffectsSystem.cs index 70822a05f44..bedc5a824ce 100644 --- a/Content.Shared/StatusEffect/StatusEffectsSystem.cs +++ b/Content.Shared/StatusEffect/StatusEffectsSystem.cs @@ -61,7 +61,8 @@ private void OnHandleState(EntityUid uid, StatusEffectsComponent component, ref if (args.Current is not StatusEffectsComponentState state) return; - component.AllowedEffects = new(state.AllowedEffects); + component.AllowedEffects.Clear(); + component.AllowedEffects.AddRange(state.AllowedEffects); // Remove non-existent effects. foreach (var effect in component.ActiveEffects.Keys) From 9ad551e3dd59299379de0d7d72167fd54424a134 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 28 Sep 2023 20:29:28 +1000 Subject: [PATCH 46/61] Update submodule to 162.2.0 (#20570) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 8f6b189d293..e75c1659f6d 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 8f6b189d293dc03fc8a33d83340bb2ce66c5c233 +Subproject commit e75c1659f6dbdebfe65bb604bd33ddd7dae9fdd8 From b052e0f2424b0cf73a61a34ce996ea4c8e5c6729 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Thu, 28 Sep 2023 06:48:50 -0400 Subject: [PATCH 47/61] Predicted armor (#20560) --- Content.Client/Armor/ArmorSystem.cs | 9 ++ Content.Server/Armor/ArmorComponent.cs | 11 -- Content.Server/Armor/ArmorSystem.cs | 129 +++--------------- .../EntitySystems/ExplosionSystem.cs | 1 + Content.Shared/Armor/ArmorComponent.cs | 25 ++++ Content.Shared/Armor/SharedArmorSystem.cs | 78 +++++++++++ .../Entities/Objects/Vehicles/buckleable.yml | 6 +- 7 files changed, 135 insertions(+), 124 deletions(-) create mode 100644 Content.Client/Armor/ArmorSystem.cs delete mode 100644 Content.Server/Armor/ArmorComponent.cs create mode 100644 Content.Shared/Armor/ArmorComponent.cs create mode 100644 Content.Shared/Armor/SharedArmorSystem.cs diff --git a/Content.Client/Armor/ArmorSystem.cs b/Content.Client/Armor/ArmorSystem.cs new file mode 100644 index 00000000000..a4116d2f7a3 --- /dev/null +++ b/Content.Client/Armor/ArmorSystem.cs @@ -0,0 +1,9 @@ +using Content.Shared.Armor; + +namespace Content.Client.Armor; + +/// +public sealed class ArmorSystem : SharedArmorSystem +{ + +} diff --git a/Content.Server/Armor/ArmorComponent.cs b/Content.Server/Armor/ArmorComponent.cs deleted file mode 100644 index 09be3bf2401..00000000000 --- a/Content.Server/Armor/ArmorComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Damage; - -namespace Content.Server.Armor -{ - [RegisterComponent] - public sealed partial class ArmorComponent : Component - { - [DataField("modifiers", required: true)] - public DamageModifierSet Modifiers = default!; - } -} diff --git a/Content.Server/Armor/ArmorSystem.cs b/Content.Server/Armor/ArmorSystem.cs index dc02b06667e..dc4b3c7935f 100644 --- a/Content.Server/Armor/ArmorSystem.cs +++ b/Content.Server/Armor/ArmorSystem.cs @@ -1,125 +1,34 @@ -using Content.Shared.Damage; -using Content.Server.Examine; -using Content.Shared.Verbs; -using Robust.Shared.Utility; using Content.Server.Cargo.Systems; +using Content.Shared.Armor; using Robust.Shared.Prototypes; using Content.Shared.Damage.Prototypes; -using Content.Shared.Inventory; -using Content.Shared.Silicons.Borgs; -namespace Content.Server.Armor -{ - public sealed class ArmorSystem : EntitySystem - { - const double CoefDefaultPrice = 2; // default price of 1% protection against any type of damage - const double FlatDefaultPrice = 10; //default price of 1 damage protection against a certain type of damage - - [Dependency] private readonly ExamineSystem _examine = default!; - [Dependency] private readonly IPrototypeManager _protoManager = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent >(OnDamageModify); - SubscribeLocalEvent >(OnBorgDamageModify); - SubscribeLocalEvent >(OnArmorVerbExamine); - SubscribeLocalEvent (GetArmorPrice); - } - - private void GetArmorPrice(EntityUid uid, ArmorComponent component, ref PriceCalculationEvent args) - { - if (component.Modifiers == null) - return; - - double price = 0; - - foreach (var modifier in component.Modifiers.Coefficients) - { - _protoManager.TryIndex(modifier.Key, out DamageTypePrototype? damageType); - - if (damageType != null) - { - price += damageType.ArmorPriceCoefficient * 100 * (1 - modifier.Value); - } - else - { - price += CoefDefaultPrice * 100 * (1 - modifier.Value); - } - } - foreach (var modifier in component.Modifiers.FlatReduction) - { - _protoManager.TryIndex(modifier.Key, out DamageTypePrototype? damageType); +namespace Content.Server.Armor; - if (damageType != null) - { - price += damageType.ArmorPriceFlat * modifier.Value; - } - else - { - price += FlatDefaultPrice * modifier.Value; - } - } - args.Price += price; - } +/// +public sealed class ArmorSystem : SharedArmorSystem +{ + [Dependency] private readonly IPrototypeManager _protoManager = default!; - private void OnDamageModify(EntityUid uid, ArmorComponent component, InventoryRelayedEvent args) - { - args.Args.Damage = DamageSpecifier.ApplyModifierSet(args.Args.Damage, component.Modifiers); - } + public override void Initialize() + { + base.Initialize(); - private void OnBorgDamageModify(EntityUid uid, ArmorComponent component, ref BorgModuleRelayedEvent args) - { - args.Args.Damage = DamageSpecifier.ApplyModifierSet(args.Args.Damage, component.Modifiers); - } + SubscribeLocalEvent (GetArmorPrice); + } - private void OnArmorVerbExamine(EntityUid uid, ArmorComponent component, GetVerbsEvent args) + private void GetArmorPrice(EntityUid uid, ArmorComponent component, ref PriceCalculationEvent args) + { + foreach (var modifier in component.Modifiers.Coefficients) { - if (!args.CanInteract || !args.CanAccess) - return; - - var armorModifiers = component.Modifiers; - - if (armorModifiers == null) - return; - - var examineMarkup = GetArmorExamine(armorModifiers); - - var ev = new ArmorExamineEvent(examineMarkup); - RaiseLocalEvent(uid, ref ev); - - _examine.AddDetailedExamineVerb(args, component, examineMarkup, Loc.GetString("armor-examinable-verb-text"), "/Textures/Interface/VerbIcons/dot.svg.192dpi.png", Loc.GetString("armor-examinable-verb-message")); + var damageType = _protoManager.Index (modifier.Key); + args.Price += damageType.ArmorPriceCoefficient * 100 * (1 - modifier.Value); } - private FormattedMessage GetArmorExamine(DamageModifierSet armorModifiers) + foreach (var modifier in component.Modifiers.FlatReduction) { - var msg = new FormattedMessage(); - - msg.AddMarkup(Loc.GetString("armor-examine")); - - foreach (var coefficientArmor in armorModifiers.Coefficients) - { - msg.PushNewline(); - msg.AddMarkup(Loc.GetString("armor-coefficient-value", - ("type", coefficientArmor.Key), - ("value", MathF.Round((1f - coefficientArmor.Value) * 100,1)) - )); - } - - foreach (var flatArmor in armorModifiers.FlatReduction) - { - msg.PushNewline(); - msg.AddMarkup(Loc.GetString("armor-reduction-value", - ("type", flatArmor.Key), - ("value", flatArmor.Value) - )); - } - - return msg; + var damageType = _protoManager.Index (modifier.Key); + args.Price += damageType.ArmorPriceFlat * modifier.Value; } } } - -[ByRefEvent] -public record struct ArmorExamineEvent(FormattedMessage Msg); diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs index 06c95383fcb..aa007c61c05 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Explosion.Components; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NPC.Pathfinding; +using Content.Shared.Armor; using Content.Shared.Camera; using Content.Shared.CCVar; using Content.Shared.Damage; diff --git a/Content.Shared/Armor/ArmorComponent.cs b/Content.Shared/Armor/ArmorComponent.cs new file mode 100644 index 00000000000..a1bb75923f7 --- /dev/null +++ b/Content.Shared/Armor/ArmorComponent.cs @@ -0,0 +1,25 @@ +using Content.Shared.Damage; +using Robust.Shared.GameStates; +using Robust.Shared.Utility; + +namespace Content.Shared.Armor; + +/// +/// Used for clothing that reduces damage when worn. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedArmorSystem))] +public sealed partial class ArmorComponent : Component +{ + ///+ /// The damage reduction + /// + [DataField(required: true)] + public DamageModifierSet Modifiers = default!; +} + +///+/// Event raised on an armor entity to get additional examine text relating to its armor. +/// +/// +[ByRefEvent] +public record struct ArmorExamineEvent(FormattedMessage Msg); diff --git a/Content.Shared/Armor/SharedArmorSystem.cs b/Content.Shared/Armor/SharedArmorSystem.cs new file mode 100644 index 00000000000..89141fcbd3d --- /dev/null +++ b/Content.Shared/Armor/SharedArmorSystem.cs @@ -0,0 +1,78 @@ +using Content.Shared.Damage; +using Content.Shared.Examine; +using Content.Shared.Inventory; +using Content.Shared.Silicons.Borgs; +using Content.Shared.Verbs; +using Robust.Shared.Utility; + +namespace Content.Shared.Armor; + +///+/// This handles logic relating to +public abstract class SharedArmorSystem : EntitySystem +{ + [Dependency] private readonly ExamineSystemShared _examine = default!; + + ///+/// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent >(OnDamageModify); + SubscribeLocalEvent >(OnBorgDamageModify); + SubscribeLocalEvent >(OnArmorVerbExamine); + } + + private void OnDamageModify(EntityUid uid, ArmorComponent component, InventoryRelayedEvent args) + { + args.Args.Damage = DamageSpecifier.ApplyModifierSet(args.Args.Damage, component.Modifiers); + } + + private void OnBorgDamageModify(EntityUid uid, ArmorComponent component, ref BorgModuleRelayedEvent args) + { + args.Args.Damage = DamageSpecifier.ApplyModifierSet(args.Args.Damage, component.Modifiers); + } + + private void OnArmorVerbExamine(EntityUid uid, ArmorComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + var examineMarkup = GetArmorExamine(component.Modifiers); + + var ev = new ArmorExamineEvent(examineMarkup); + RaiseLocalEvent(uid, ref ev); + + _examine.AddDetailedExamineVerb(args, component, examineMarkup, + Loc.GetString("armor-examinable-verb-text"), "/Textures/Interface/VerbIcons/dot.svg.192dpi.png", + Loc.GetString("armor-examinable-verb-message")); + } + + private FormattedMessage GetArmorExamine(DamageModifierSet armorModifiers) + { + var msg = new FormattedMessage(); + + msg.AddMarkup(Loc.GetString("armor-examine")); + + foreach (var coefficientArmor in armorModifiers.Coefficients) + { + msg.PushNewline(); + msg.AddMarkup(Loc.GetString("armor-coefficient-value", + ("type", coefficientArmor.Key), + ("value", MathF.Round((1f - coefficientArmor.Value) * 100,1)) + )); + } + + foreach (var flatArmor in armorModifiers.FlatReduction) + { + msg.PushNewline(); + msg.AddMarkup(Loc.GetString("armor-reduction-value", + ("type", flatArmor.Key), + ("value", flatArmor.Value) + )); + } + + return msg; + } +} diff --git a/Resources/Prototypes/Entities/Objects/Vehicles/buckleable.yml b/Resources/Prototypes/Entities/Objects/Vehicles/buckleable.yml index d807bcc5a9f..4a8fa0da5cb 100644 --- a/Resources/Prototypes/Entities/Objects/Vehicles/buckleable.yml +++ b/Resources/Prototypes/Entities/Objects/Vehicles/buckleable.yml @@ -39,7 +39,7 @@ sound: path: /Audio/Effects/metalbreak.ogg - !type:ExplodeBehavior - + - type: entity parent: BaseVehicle id: BaseVehicleRideable @@ -198,7 +198,7 @@ baseSprintSpeed: 6 - type: Armor modifiers: - coeffecients: + coefficients: Blunt: 0.8 Slash: 0.6 Piercing: 0.85 @@ -289,7 +289,7 @@ maxBuckleDistance: 1 - type: Armor modifiers: - coeffecients: + coefficients: Blunt: 0.8 Slash: 0.6 Piercing: 0.85 From f19cd906949efde768b4631fa8791c94a2a4f56e Mon Sep 17 00:00:00 2001 From: ravage <142820619+ravage123321@users.noreply.github.com> Date: Thu, 28 Sep 2023 13:49:17 +0300 Subject: [PATCH 48/61] clean up some lines in smile the slime prototype (#20552) --- Resources/Prototypes/Entities/Mobs/NPCs/pets.yml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 7846dd66fae..1efa767af1b 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -619,8 +619,6 @@ description: This masterpiece has gone through thousands of experiments. But it is the sweetest creature in the world. Smile Slime! components: - type: Sprite - drawdepth: Mobs - sprite: Mobs/Aliens/slimes.rsi layers: - map: [ "enum.DamageStateVisualLayers.Base" ] state: rainbow_baby_slime @@ -650,22 +648,14 @@ Dead: Base: rainbow_baby_slime_dead - type: Butcherable - butcheringType: Knife spawned: - id: FoodMeatSlime amount: 1 - id: MaterialSmileExtract amount: 1 - type: Damageable - damageContainer: Biological damageModifierSet: SlimePet - type: Bloodstream - bloodMaxVolume: 150 - bloodReagent: Slime - bloodlossDamage: - types: - Bloodloss: - 1 bloodlossHealDamage: types: Bloodloss: @@ -674,11 +664,6 @@ heatDamageThreshold: 800 coldDamageThreshold: 0 - type: MeleeWeapon - hidden: true - soundHit: - path: /Audio/Weapons/punch3.ogg - angle: 0 - animation: WeaponArcPunch damage: types: Blunt: 1 @@ -688,7 +673,6 @@ - type: MobPrice price: 3000 # it is a truly valuable creature - type: GhostRole - makeSentient: true name: ghost-role-information-smile-name description: ghost-role-information-smile-description - type: Grammar From bc218347a89036045ea0899b114728be7e9ede13 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 28 Sep 2023 12:49:49 +0200 Subject: [PATCH 49/61] Revert "Use full file path for temp replays (#19002)" (#20545) --- Content.Server/GameTicking/GameTicker.Replays.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Content.Server/GameTicking/GameTicker.Replays.cs b/Content.Server/GameTicking/GameTicker.Replays.cs index 42e2de02287..03cf748d09d 100644 --- a/Content.Server/GameTicking/GameTicker.Replays.cs +++ b/Content.Server/GameTicking/GameTicker.Replays.cs @@ -43,7 +43,9 @@ private void ReplayStartRound() { var baseReplayPath = new ResPath(_cfg.GetCVar(CVars.ReplayDirectory)).ToRootedPath(); moveToPath = baseReplayPath / finalPath; - recordPath = new ResPath(tempDir) / finalPath; + + var fileName = finalPath.Filename; + recordPath = new ResPath(tempDir) / fileName; _sawmillReplays.Debug($"Replay will record in temporary position: {recordPath}"); } From 08af7008c83cc7bae40fb061693d9281800b8a38 Mon Sep 17 00:00:00 2001 From: LEVELcat <68501903+LEVELcat@users.noreply.github.com> Date: Thu, 28 Sep 2023 13:52:05 +0300 Subject: [PATCH 50/61] Add EyesGlasses into ClothesMate (#20523) --- .../Catalog/VendingMachines/Inventories/clothesmate.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml index e263903ef50..554989b5cee 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml @@ -58,6 +58,7 @@ ClothingHeadFishCap: 2 ClothingHeadRastaHat: 2 ClothingBeltStorageWaistbag: 3 + ClothingEyesGlasses: 6 contrabandInventory: ClothingUniformJumpsuitTacticool: 1 ClothingUniformJumpskirtTacticool: 1 From c0ddb6f4110f9171ff25c273b6d9ce0a81c32099 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 28 Sep 2023 06:53:09 -0400 Subject: [PATCH 51/61] Automatic changelog update --- Resources/Changelog/Changelog.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f2c87d9c02a..c20993bf998 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Slava0135 - changes: - - {message: Hitting entities with thrown items that deal damage now provide more - feedback (sound and visual effect)., type: Add} - - {message: Thrown items that damage other entities now land after first hit., type: Tweak} - id: 4409 - time: '2023-08-02T09:30:04.0000000+00:00' - author: Slava0135 changes: - {message: added visual effect on collision for damage on high speed entities, @@ -2971,3 +2964,8 @@ Entries: type: Add} id: 4908 time: '2023-09-28T04:09:45.0000000+00:00' +- author: LEVELcat + changes: + - {message: Eyeglass are now available from the ClothesMate., type: Add} + id: 4909 + time: '2023-09-28T10:52:05.0000000+00:00' From 0a8a6f81c51b826a2bc689aa9a8340e859f36102 Mon Sep 17 00:00:00 2001 From: Repo <47093363+Titian3@users.noreply.github.com> Date: Thu, 28 Sep 2023 23:53:53 +1300 Subject: [PATCH 52/61] Fix Punpun crew monitor sensor (#20484) --- .../Access/Components/PresetIdCardComponent.cs | 3 +++ Content.Server/Access/Systems/PresetIdCardSystem.cs | 9 +++++++++ .../Entities/Clothing/Uniforms/jumpsuits.yml | 4 ++++ .../Entities/Objects/Misc/identification_cards.yml | 13 +++++++++++++ .../Prototypes/Roles/Jobs/Fun/misc_startinggear.yml | 2 +- 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Content.Server/Access/Components/PresetIdCardComponent.cs b/Content.Server/Access/Components/PresetIdCardComponent.cs index 89850866d65..a2842d7815f 100644 --- a/Content.Server/Access/Components/PresetIdCardComponent.cs +++ b/Content.Server/Access/Components/PresetIdCardComponent.cs @@ -5,5 +5,8 @@ public sealed partial class PresetIdCardComponent : Component { [DataField("job")] public string? JobName; + + [DataField("name")] + public string? IdName; } } diff --git a/Content.Server/Access/Systems/PresetIdCardSystem.cs b/Content.Server/Access/Systems/PresetIdCardSystem.cs index 271e16cbf93..96a38278b5b 100644 --- a/Content.Server/Access/Systems/PresetIdCardSystem.cs +++ b/Content.Server/Access/Systems/PresetIdCardSystem.cs @@ -37,6 +37,7 @@ private void PlayerJobsAssigned(RulePlayerJobsAssignedEvent ev) return; SetupIdAccess(uid, card, true); + SetupIdName(uid, card); } } @@ -53,6 +54,14 @@ private void OnMapInit(EntityUid uid, PresetIdCardComponent id, MapInitEvent arg extended = Comp (station.Value).ExtendedAccess; SetupIdAccess(uid, id, extended); + SetupIdName(uid, id); + } + + private void SetupIdName(EntityUid uid, PresetIdCardComponent id) + { + if (id.IdName == null) + return; + _cardSystem.TryChangeFullName(uid, id.IdName); } private void SetupIdAccess(EntityUid uid, PresetIdCardComponent id, bool extended) diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml index 4f1917fe182..c9f19e010c9 100644 --- a/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml @@ -48,6 +48,10 @@ sprite: Clothing/Uniforms/Jumpsuit/punpun.rsi - type: Clothing sprite: Clothing/Uniforms/Jumpsuit/punpun.rsi + - type: SuitSensor + controlsLocked: false + randomMode: false + mode: SensorCords - type: entity parent: ClothingUniformBase diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml index cca026f7d8a..24462d51136 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml @@ -293,6 +293,19 @@ - type: PresetIdCard job: Bartender +- type: entity + parent: IDCardStandard + id: PunPunIDCard + name: pun pun ID card + components: + - type: Sprite + layers: + - state: default + - state: idbartender + - type: PresetIdCard + job: Bartender + name: Pun Pun + - type: entity parent: IDCardStandard id: ChefIDCard diff --git a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml index 066ed49adcb..b28546a0c41 100644 --- a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml +++ b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml @@ -282,7 +282,7 @@ head: ClothingHeadHatTophat ears: ClothingHeadsetService jumpsuit: ClothingUniformJumpsuitJacketMonkey - id: BartenderIDCard + id: PunPunIDCard # Passenger but without the ID, bag, or headset From cbeba20ebbbc159383e72c8bad2c3e557fb04eb8 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 28 Sep 2023 06:54:58 -0400 Subject: [PATCH 53/61] Automatic changelog update --- Resources/Changelog/Changelog.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index c20993bf998..ba68c124913 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,10 +1,4 @@ Entries: -- author: Slava0135 - changes: - - {message: added visual effect on collision for damage on high speed entities, - type: Add} - id: 4410 - time: '2023-08-02T09:32:44.0000000+00:00' - author: deltanedas changes: - {message: 'Added the gateway, currently for admins to teleport you to funny places.', @@ -2969,3 +2963,8 @@ Entries: - {message: Eyeglass are now available from the ClothesMate., type: Add} id: 4909 time: '2023-09-28T10:52:05.0000000+00:00' +- author: Repo + changes: + - {message: Pun Pun now showing on crew monitoring., type: Fix} + id: 4910 + time: '2023-09-28T10:53:54.0000000+00:00' From 5ff79120e6939bc6cade91761c8cf9e6bb11fa32 Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Thu, 28 Sep 2023 11:34:21 +0000 Subject: [PATCH 54/61] EasyPry airlocks for arrivals. Now also prying refactor I guess (#19394) Co-authored-by: metalgearsloth --- Content.Client/Doors/AirlockSystem.cs | 8 + Content.Server/Doors/Systems/AirlockSystem.cs | 20 +-- Content.Server/Doors/Systems/DoorSystem.cs | 123 ++++--------- .../Doors/Systems/FirelockSystem.cs | 7 +- .../Systems/NPCSteeringSystem.Obstacles.cs | 2 +- .../NPC/Systems/NPCSteeringSystem.cs | 6 +- .../Zombies/ZombieSystem.Transform.cs | 14 +- .../Doors/Components/DoorComponent.cs | 6 +- Content.Shared/Doors/DoorEvents.cs | 31 ---- .../Doors/Systems/SharedDoorBoltSystem.cs | 11 +- .../Doors/Systems/SharedDoorSystem.cs | 48 ++++-- .../Components/PryUnpoweredComponent.cs | 11 ++ .../Prying/Components/PryingComponent.cs | 82 +++++++++ Content.Shared/Prying/Systems/PryingSystem.cs | 162 ++++++++++++++++++ .../Systems/SharedToolSystem.MultipleTool.cs | 18 +- .../Entities/Debugging/spanisharmyknife.yml | 1 + .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 5 + .../Entities/Objects/Tools/cowtools.yml | 3 +- .../Entities/Objects/Tools/jaws_of_life.yml | 8 +- .../Entities/Objects/Tools/tools.yml | 1 + .../Objects/Weapons/Melee/armblade.yml | 1 + .../Objects/Weapons/Melee/fireaxe.yml | 1 + .../Entities/Objects/Weapons/Melee/mining.yml | 1 + .../Structures/Doors/Airlocks/easy_pry.yml | 63 +++++++ 24 files changed, 463 insertions(+), 170 deletions(-) create mode 100644 Content.Shared/Prying/Components/PryUnpoweredComponent.cs create mode 100644 Content.Shared/Prying/Components/PryingComponent.cs create mode 100644 Content.Shared/Prying/Systems/PryingSystem.cs create mode 100644 Resources/Prototypes/Entities/Structures/Doors/Airlocks/easy_pry.yml diff --git a/Content.Client/Doors/AirlockSystem.cs b/Content.Client/Doors/AirlockSystem.cs index 18b9eae5f34..cc68d090394 100644 --- a/Content.Client/Doors/AirlockSystem.cs +++ b/Content.Client/Doors/AirlockSystem.cs @@ -1,6 +1,7 @@ using Content.Client.Wires.Visualizers; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; +using Content.Shared.Prying.Components; using Robust.Client.Animations; using Robust.Client.GameObjects; @@ -15,6 +16,13 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent (OnComponentStartup); SubscribeLocalEvent (OnAppearanceChange); + SubscribeLocalEvent (OnAirlockPryAttempt); + } + + private void OnAirlockPryAttempt(EntityUid uid, AirlockComponent component, ref BeforePryEvent args) + { + // TODO: Temporary until airlocks predicted. + args.Cancelled = true; } private void OnComponentStartup(EntityUid uid, AirlockComponent comp, ComponentStartup args) diff --git a/Content.Server/Doors/Systems/AirlockSystem.cs b/Content.Server/Doors/Systems/AirlockSystem.cs index bb75fc7d476..0ea2755ab66 100644 --- a/Content.Server/Doors/Systems/AirlockSystem.cs +++ b/Content.Server/Doors/Systems/AirlockSystem.cs @@ -1,7 +1,6 @@ using Content.Server.DeviceLinking.Events; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; -using Content.Shared.Tools.Components; using Content.Server.Wires; using Content.Shared.Doors; using Content.Shared.Doors.Components; @@ -9,6 +8,7 @@ using Content.Shared.Interaction; using Robust.Server.GameObjects; using Content.Shared.Wires; +using Content.Shared.Prying.Components; using Robust.Shared.Prototypes; namespace Content.Server.Doors.Systems; @@ -31,9 +31,9 @@ public override void Initialize() SubscribeLocalEvent (OnStateChanged); SubscribeLocalEvent (OnBeforeDoorOpened); SubscribeLocalEvent (OnBeforeDoorDenied); - SubscribeLocalEvent (OnActivate, before: new [] {typeof(DoorSystem)}); - SubscribeLocalEvent (OnGetPryMod); - SubscribeLocalEvent (OnDoorPry); + SubscribeLocalEvent (OnActivate, before: new[] { typeof(DoorSystem) }); + SubscribeLocalEvent (OnGetPryMod); + SubscribeLocalEvent (OnBeforePry); } @@ -169,20 +169,18 @@ private void OnActivate(EntityUid uid, AirlockComponent component, ActivateInWor } } - private void OnGetPryMod(EntityUid uid, AirlockComponent component, DoorGetPryTimeModifierEvent args) + private void OnGetPryMod(EntityUid uid, AirlockComponent component, ref GetPryTimeModifierEvent args) { if (_power.IsPowered(uid)) args.PryTimeModifier *= component.PoweredPryModifier; } - private void OnDoorPry(EntityUid uid, AirlockComponent component, BeforeDoorPryEvent args) + private void OnBeforePry(EntityUid uid, AirlockComponent component, ref BeforePryEvent args) { - if (this.IsPowered(uid, EntityManager)) + if (this.IsPowered(uid, EntityManager) && !args.PryPowered) { - if (HasComp (args.Tool)) - return; - Popup.PopupEntity(Loc.GetString("airlock-component-cannot-pry-is-powered-message"), uid, args.User); - args.Cancel(); + Popup.PopupClient(Loc.GetString("airlock-component-cannot-pry-is-powered-message"), uid, args.User); + args.Cancelled = true; } } diff --git a/Content.Server/Doors/Systems/DoorSystem.cs b/Content.Server/Doors/Systems/DoorSystem.cs index f9918dfb0a6..aa4de6b86bb 100644 --- a/Content.Server/Doors/Systems/DoorSystem.cs +++ b/Content.Server/Doors/Systems/DoorSystem.cs @@ -1,15 +1,12 @@ using Content.Server.Access; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; -using Content.Server.Construction; using Content.Shared.Database; using Content.Shared.Doors; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; using Content.Shared.Emag.Systems; using Content.Shared.Interaction; -using Content.Shared.Tools.Components; -using Content.Shared.Verbs; using Robust.Shared.Audio; using Content.Server.Administration.Logs; using Content.Server.Power.EntitySystems; @@ -17,6 +14,8 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Content.Shared.DoAfter; +using Content.Shared.Prying.Systems; +using Content.Shared.Prying.Components; using Content.Shared.Tools.Systems; namespace Content.Server.Doors.Systems; @@ -27,20 +26,19 @@ public sealed class DoorSystem : SharedDoorSystem [Dependency] private readonly DoorBoltSystem _bolts = default!; [Dependency] private readonly AirtightSystem _airtightSystem = default!; [Dependency] private readonly SharedToolSystem _toolSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly PryingSystem _pryingSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent (OnInteractUsing, after: new[] { typeof(ConstructionSystem) }); - // Mob prying doors - SubscribeLocalEvent >(OnDoorAltVerb); - - SubscribeLocalEvent (OnPryFinished); + SubscribeLocalEvent (OnBeforeDoorPry); SubscribeLocalEvent (OnWeldAttempt); SubscribeLocalEvent (OnWeldChanged); SubscribeLocalEvent (OnEmagged); + SubscribeLocalEvent (OnAfterPry); } protected override void OnActivate(EntityUid uid, DoorComponent door, ActivateInWorldEvent args) @@ -49,7 +47,9 @@ protected override void OnActivate(EntityUid uid, DoorComponent door, ActivateIn if (args.Handled || !door.ClickOpen) return; - TryToggleDoor(uid, door, args.User); + if (!TryToggleDoor(uid, door, args.User)) + _pryingSystem.TryPry(uid, args.User, out _); + args.Handled = true; } @@ -108,24 +108,7 @@ protected override void PlaySound(EntityUid uid, SoundSpecifier soundSpecifier, Audio.PlayPvs(soundSpecifier, uid, audioParams); } -#region DoAfters - /// - /// Weld or pry open a door. - /// - private void OnInteractUsing(EntityUid uid, DoorComponent door, InteractUsingEvent args) - { - if (args.Handled) - return; - - if (!TryComp(args.Used, out ToolComponent? tool)) - return; - - if (tool.Qualities.Contains(door.PryingQuality)) - { - args.Handled = TryPryDoor(uid, args.Used, args.User, door, out _); - } - } - + #region DoAfters private void OnWeldAttempt(EntityUid uid, DoorComponent component, WeldableAttemptEvent args) { if (component.CurrentlyCrushing.Count > 0) @@ -147,69 +130,12 @@ private void OnWeldChanged(EntityUid uid, DoorComponent component, ref WeldableC SetState(uid, DoorState.Closed, component); } - private void OnDoorAltVerb(EntityUid uid, DoorComponent component, GetVerbsEventargs) + private void OnBeforeDoorPry(EntityUid id, DoorComponent door, ref BeforePryEvent args) { - if (!args.CanInteract || !args.CanAccess) - return; - - if (!TryComp (args.User, out var tool) || !tool.Qualities.Contains(component.PryingQuality)) - return; - - args.Verbs.Add(new AlternativeVerb() - { - Text = Loc.GetString("door-pry"), - Impact = LogImpact.Low, - Act = () => TryPryDoor(uid, args.User, args.User, component, out _, force: true), - }); + if (door.State == DoorState.Welded || !door.CanPry) + args.Cancelled = true; } - - - /// - /// Pry open a door. This does not check if the user is holding the required tool. - /// - public bool TryPryDoor(EntityUid target, EntityUid tool, EntityUid user, DoorComponent door, out DoAfterId? id, bool force = false) - { - id = null; - - if (door.State == DoorState.Welded) - return false; - - if (!force) - { - var canEv = new BeforeDoorPryEvent(user, tool); - RaiseLocalEvent(target, canEv, false); - - if (!door.CanPry || canEv.Cancelled) - // mark handled, as airlock component will cancel after generating a pop-up & you don't want to pry a tile - // under a windoor. - return true; - } - - var modEv = new DoorGetPryTimeModifierEvent(user); - RaiseLocalEvent(target, modEv, false); - - _adminLog.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user)} is using {ToPrettyString(tool)} to pry {ToPrettyString(target)} while it is {door.State}"); // TODO move to generic tool use logging in a way that includes door state - _toolSystem.UseTool(tool, user, target, TimeSpan.FromSeconds(modEv.PryTimeModifier * door.PryTime), new[] {door.PryingQuality}, new DoorPryDoAfterEvent(), out id); - return true; // we might not actually succeeded, but a do-after has started - } - - private void OnPryFinished(EntityUid uid, DoorComponent door, DoAfterEvent args) - { - if (args.Cancelled) - return; - - if (door.State == DoorState.Closed) - { - _adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User)} pried {ToPrettyString(uid)} open"); // TODO move to generic tool use logging in a way that includes door state - StartOpening(uid, door); - } - else if (door.State == DoorState.Open) - { - _adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User)} pried {ToPrettyString(uid)} closed"); // TODO move to generic tool use logging in a way that includes door state - StartClosing(uid, door); - } - } -#endregion + #endregion ///@@ -233,7 +159,7 @@ protected override void HandleCollide(EntityUid uid, DoorComponent door, ref Sta } private void OnEmagged(EntityUid uid, DoorComponent door, ref GotEmaggedEvent args) { - if(TryComp (uid, out var airlockComponent)) + if (TryComp (uid, out var airlockComponent)) { if (_bolts.IsBolted(uid) || !this.IsPowered(uid, EntityManager)) return; @@ -259,10 +185,27 @@ public override void StartOpening(EntityUid uid, DoorComponent? door = null, Ent if (door.OpenSound != null) PlaySound(uid, door.OpenSound, AudioParams.Default.WithVolume(-5), user, predicted); - if(lastState == DoorState.Emagging && TryComp (uid, out var doorBoltComponent)) + if (lastState == DoorState.Emagging && TryComp (uid, out var doorBoltComponent)) _bolts.SetBoltsWithAudio(uid, doorBoltComponent, !doorBoltComponent.BoltsDown); } + /// + /// Open or close a door after it has been successfuly pried. + /// + private void OnAfterPry(EntityUid uid, DoorComponent door, ref PriedEvent args) + { + if (door.State == DoorState.Closed) + { + _adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User)} pried {ToPrettyString(uid)} open"); + StartOpening(uid, door, args.User); + } + else if (door.State == DoorState.Open) + { + _adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User)} pried {ToPrettyString(uid)} closed"); + StartClosing(uid, door, args.User); + } + } + protected override void CheckDoorBump(DoorComponent component, PhysicsComponent body) { var uid = body.Owner; diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs index 7147aa4f24c..e2f25c63ab4 100644 --- a/Content.Server/Doors/Systems/FirelockSystem.cs +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -18,6 +18,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Map.Components; using Robust.Shared.Player; +using Content.Shared.Prying.Components; namespace Content.Server.Doors.Systems { @@ -38,7 +39,7 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnBeforeDoorOpened); - SubscribeLocalEvent (OnDoorGetPryTimeModifier); + SubscribeLocalEvent (OnDoorGetPryTimeModifier); SubscribeLocalEvent (OnUpdateState); SubscribeLocalEvent (OnBeforeDoorAutoclose); @@ -144,7 +145,7 @@ private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, Befo args.Cancel(); } - private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, DoorGetPryTimeModifierEvent args) + private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, ref GetPryTimeModifierEvent args) { var state = CheckPressureAndFire(uid, component); @@ -261,7 +262,7 @@ public bool IsHoldingPressureOrFire(EntityUid uid, FirelockComponent firelock) List directions = new(4); for (var i = 0; i < Atmospherics.Directions; i++) { - var dir = (AtmosDirection) (1 << i); + var dir = (AtmosDirection)(1 << i); if (airtight.AirBlockedDirection.HasFlag(dir)) { directions.Add(dir); diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs index 1d9c19de027..87deec9ea9d 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs @@ -113,7 +113,7 @@ private SteeringObstacleStatus TryHandleFlags(EntityUid uid, NPCSteeringComponen // TODO: Use the verb. if (door.State != DoorState.Opening) - _doors.TryPryDoor(ent, uid, uid, door, out id, force: true); + _pryingSystem.TryPry(ent, uid, out id, uid); component.DoAfterId = id; return SteeringObstacleStatus.Continuing; diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.cs index cbc2ba6d2c4..0fa28f6af79 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.cs @@ -31,6 +31,7 @@ using Robust.Shared.Threading; using Robust.Shared.Timing; using Robust.Shared.Utility; +using Content.Shared.Prying.Systems; namespace Content.Server.NPC.Systems; @@ -63,6 +64,7 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedCombatModeSystem _combat = default!; + [Dependency] private readonly PryingSystem _pryingSystem = default!; private EntityQuery _fixturesQuery; private EntityQuery _modifierQuery; @@ -148,7 +150,7 @@ public override void Shutdown() private void OnDebugRequest(RequestNPCSteeringDebugEvent msg, EntitySessionEventArgs args) { - if (!_admin.IsAdmin((IPlayerSession) args.SenderSession)) + if (!_admin.IsAdmin((IPlayerSession)args.SenderSession)) return; if (msg.Enabled) @@ -440,7 +442,7 @@ private async void RequestPath(EntityUid uid, NPCSteeringComponent steering, Tra if (targetPoly != null && steering.Coordinates.Position.Equals(Vector2.Zero) && TryComp (uid, out var physics) && - _interaction.InRangeUnobstructed(uid, steering.Coordinates.EntityId, range: 30f, (CollisionGroup) physics.CollisionMask)) + _interaction.InRangeUnobstructed(uid, steering.Coordinates.EntityId, range: 30f, (CollisionGroup)physics.CollisionMask)) { steering.CurrentPath.Clear(); steering.CurrentPath.Enqueue(targetPoly); diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index 5da7c6e8cd7..19e80132208 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -32,6 +32,7 @@ using Content.Shared.Weapons.Melee; using Content.Shared.Zombies; using Robust.Shared.Audio; +using Content.Shared.Prying.Components; namespace Content.Server.Zombies { @@ -162,11 +163,12 @@ public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null) melee.Damage = dspec; // humanoid zombies get to pry open doors and shit - var tool = EnsureComp (target); - tool.SpeedModifier = 0.75f; - tool.Qualities = new ("Prying"); - tool.UseSound = new SoundPathSpecifier("/Audio/Items/crowbar.ogg"); - Dirty(tool); + var pryComp = EnsureComp (target); + pryComp.SpeedModifier = 0.75f; + pryComp.PryPowered = true; + pryComp.Force = true; + + Dirty(target, pryComp); } Dirty(melee); @@ -232,7 +234,7 @@ public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null) else { var htn = EnsureComp (target); - htn.RootTask = new HTNCompoundTask() {Task = "SimpleHostileCompound"}; + htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" }; htn.Blackboard.SetValue(NPCBlackboard.Owner, target); _npc.WakeNPC(target, htn); } diff --git a/Content.Shared/Doors/Components/DoorComponent.cs b/Content.Shared/Doors/Components/DoorComponent.cs index 567afa07701..7cfcba8c5b6 100644 --- a/Content.Shared/Doors/Components/DoorComponent.cs +++ b/Content.Shared/Doors/Components/DoorComponent.cs @@ -249,7 +249,7 @@ private float? SecondsUntilStateChange } var curTime = IoCManager.Resolve ().CurTime; - return (float) (NextStateChange.Value - curTime).TotalSeconds; + return (float)(NextStateChange.Value - curTime).TotalSeconds; } set { @@ -299,10 +299,10 @@ private float? SecondsUntilStateChange public bool ClickOpen = true; [DataField("openDrawDepth", customTypeSerializer: typeof(ConstantSerializer ))] - public int OpenDrawDepth = (int) DrawDepth.DrawDepth.Doors; + public int OpenDrawDepth = (int)DrawDepth.DrawDepth.Doors; [DataField("closedDrawDepth", customTypeSerializer: typeof(ConstantSerializer ))] - public int ClosedDrawDepth = (int) DrawDepth.DrawDepth.Doors; + public int ClosedDrawDepth = (int)DrawDepth.DrawDepth.Doors; } [Serializable, NetSerializable] diff --git a/Content.Shared/Doors/DoorEvents.cs b/Content.Shared/Doors/DoorEvents.cs index 5b0ca71ede7..08a2c8b18b1 100644 --- a/Content.Shared/Doors/DoorEvents.cs +++ b/Content.Shared/Doors/DoorEvents.cs @@ -62,35 +62,4 @@ public sealed class BeforeDoorDeniedEvent : CancellableEntityEventArgs public sealed class BeforeDoorAutoCloseEvent : CancellableEntityEventArgs { } - - /// - /// Raised to determine how long the door's pry time should be modified by. - /// Multiply PryTimeModifier by the desired amount. - /// - public sealed class DoorGetPryTimeModifierEvent : EntityEventArgs - { - public readonly EntityUid User; - public float PryTimeModifier = 1.0f; - - public DoorGetPryTimeModifierEvent(EntityUid user) - { - User = user; - } - } - - ///- /// Raised when an attempt to pry open the door is made. - /// Cancel to stop the door from being pried open. - /// - public sealed class BeforeDoorPryEvent : CancellableEntityEventArgs - { - public readonly EntityUid User; - public readonly EntityUid Tool; - - public BeforeDoorPryEvent(EntityUid user, EntityUid tool) - { - User = user; - Tool = tool; - } - } } diff --git a/Content.Shared/Doors/Systems/SharedDoorBoltSystem.cs b/Content.Shared/Doors/Systems/SharedDoorBoltSystem.cs index e8be596b060..1deb6e3f7c0 100644 --- a/Content.Shared/Doors/Systems/SharedDoorBoltSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorBoltSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Doors.Components; using Content.Shared.Popups; +using Content.Shared.Prying.Components; namespace Content.Shared.Doors.Systems; @@ -16,16 +17,16 @@ public override void Initialize() SubscribeLocalEvent(OnBeforeDoorOpened); SubscribeLocalEvent (OnBeforeDoorClosed); SubscribeLocalEvent (OnBeforeDoorDenied); - SubscribeLocalEvent (OnDoorPry); + SubscribeLocalEvent (OnDoorPry); } - private void OnDoorPry(EntityUid uid, DoorBoltComponent component, BeforeDoorPryEvent args) + private void OnDoorPry(EntityUid uid, DoorBoltComponent component, ref BeforePryEvent args) { - if (component.BoltsDown) + if (component.BoltsDown && !args.Force) { - Popup.PopupEntity(Loc.GetString("airlock-component-cannot-pry-is-bolted-message"), uid, args.User); - args.Cancel(); + Popup.PopupClient(Loc.GetString("airlock-component-cannot-pry-is-bolted-message"), uid, args.User); + args.Cancelled = true; } } diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs index 3fc912deba9..e5515171496 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs @@ -16,6 +16,7 @@ using Robust.Shared.Physics.Systems; using Robust.Shared.Serialization; using Robust.Shared.Timing; +using Content.Shared.Prying.Components; namespace Content.Shared.Doors.Systems; @@ -23,14 +24,14 @@ public abstract partial class SharedDoorSystem : EntitySystem { [Dependency] protected readonly IGameTiming GameTiming = default!; [Dependency] protected readonly SharedPhysicsSystem PhysicsSystem = default!; - [Dependency] private readonly DamageableSystem _damageableSystem = default!; - [Dependency] private readonly SharedStunSystem _stunSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly SharedStunSystem _stunSystem = default!; [Dependency] protected readonly TagSystem Tags = default!; [Dependency] protected readonly SharedAudioSystem Audio = default!; - [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!; - [Dependency] private readonly OccluderSystem _occluder = default!; - [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; + [Dependency] private readonly OccluderSystem _occluder = default!; + [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; /// /// A body must have an intersection percentage larger than this in order to be considered as colliding with a @@ -61,6 +62,8 @@ public override void Initialize() SubscribeLocalEvent (HandleCollide); SubscribeLocalEvent (PreventCollision); + SubscribeLocalEvent (OnPryTimeModifier); + } protected virtual void OnComponentInit(EntityUid uid, DoorComponent door, ComponentInit args) @@ -182,6 +185,11 @@ protected virtual void OnActivate(EntityUid uid, DoorComponent door, ActivateInW args.Handled = true; } + private void OnPryTimeModifier(EntityUid uid, DoorComponent door, ref GetPryTimeModifierEvent args) + { + args.BaseTime = door.PryTime; + } + /// /// Update the door state/visuals and play an access denied sound when a user without access interacts with the /// door. @@ -206,6 +214,7 @@ public void Deny(EntityUid uid, DoorComponent? door = null, EntityUid? user = nu PlaySound(uid, door.DenySound, AudioParams.Default.WithVolume(-3), user, predicted); } + public bool TryToggleDoor(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false) { if (!Resolve(uid, ref door)) @@ -246,7 +255,7 @@ public bool CanOpen(EntityUid uid, DoorComponent? door = null, EntityUid? user = if (door.State == DoorState.Welded) return false; - var ev = new BeforeDoorOpenedEvent(){User=user}; + var ev = new BeforeDoorOpenedEvent() { User = user }; RaiseLocalEvent(uid, ev, false); if (ev.Cancelled) return false; @@ -261,6 +270,14 @@ public bool CanOpen(EntityUid uid, DoorComponent? door = null, EntityUid? user = return true; } + /// + /// Immediately start opening a door + /// + /// The uid of the door + /// The doorcomponent of the door + /// The user (if any) opening the door + /// Whether the interaction would have been + /// predicted. See comments in the PlaySound method on the Server system for details public virtual void StartOpening(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false) { if (!Resolve(uid, ref door)) @@ -309,6 +326,14 @@ public bool TryClose(EntityUid uid, DoorComponent? door = null, EntityUid? user return true; } + ///+ /// Immediately start closing a door + /// + /// The uid of the door + /// The doorcomponent of the door + /// The user (if any) opening the door + /// Whether the interaction would have been + /// predicted. See comments in the PlaySound method on the Server system for details public bool CanClose(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool quiet = true) { if (!Resolve(uid, ref door)) @@ -444,11 +469,11 @@ public IEnumerableGetColliding(EntityUid uid, PhysicsComponent? phys //TODO: Make only shutters ignore these objects upon colliding instead of all airlocks // Excludes Glasslayer for windows, GlassAirlockLayer for windoors, TableLayer for tables - if (!otherPhysics.CanCollide || otherPhysics.CollisionLayer == (int) CollisionGroup.GlassLayer || otherPhysics.CollisionLayer == (int) CollisionGroup.GlassAirlockLayer || otherPhysics.CollisionLayer == (int) CollisionGroup.TableLayer) + if (!otherPhysics.CanCollide || otherPhysics.CollisionLayer == (int)CollisionGroup.GlassLayer || otherPhysics.CollisionLayer == (int)CollisionGroup.GlassAirlockLayer || otherPhysics.CollisionLayer == (int)CollisionGroup.TableLayer) continue; //If the colliding entity is a slippable item ignore it by the airlock - if (otherPhysics.CollisionLayer == (int) CollisionGroup.SlipLayer && otherPhysics.CollisionMask == (int) CollisionGroup.ItemMask) + if (otherPhysics.CollisionLayer == (int)CollisionGroup.SlipLayer && otherPhysics.CollisionMask == (int)CollisionGroup.ItemMask) continue; if ((physics.CollisionMask & otherPhysics.CollisionLayer) == 0 && (otherPhysics.CollisionMask & physics.CollisionLayer) == 0) @@ -598,7 +623,7 @@ public override void Update(float frameTime) } } - protected virtual void CheckDoorBump(DoorComponent component, PhysicsComponent body) {} + protected virtual void CheckDoorBump(DoorComponent component, PhysicsComponent body) { } /// /// Makes a door proceed to the next state (if applicable). @@ -659,9 +684,4 @@ private void NextState(DoorComponent door, TimeSpan time) #endregion protected abstract void PlaySound(EntityUid uid, SoundSpecifier soundSpecifier, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted); - - [Serializable, NetSerializable] - protected sealed partial class DoorPryDoAfterEvent : SimpleDoAfterEvent - { - } } diff --git a/Content.Shared/Prying/Components/PryUnpoweredComponent.cs b/Content.Shared/Prying/Components/PryUnpoweredComponent.cs new file mode 100644 index 00000000000..f0e61dc9685 --- /dev/null +++ b/Content.Shared/Prying/Components/PryUnpoweredComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Prying.Components; + +/// +/// Applied to entities that can be pried open without tools while unpowered +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class PryUnpoweredComponent : Component +{ +} diff --git a/Content.Shared/Prying/Components/PryingComponent.cs b/Content.Shared/Prying/Components/PryingComponent.cs new file mode 100644 index 00000000000..4442481dce1 --- /dev/null +++ b/Content.Shared/Prying/Components/PryingComponent.cs @@ -0,0 +1,82 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared.Prying.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class PryingComponent : Component +{ + ///+ /// Whether the entity can pry open powered doors + /// + [DataField("pryPowered")] + public bool PryPowered = false; + + ///+ /// Whether the tool can bypass certain restrictions when prying. + /// For example door bolts. + /// + [DataField("force")] + public bool Force = false; + ///+ /// Modifier on the prying time. + /// Lower values result in more time. + /// + [DataField("speedModifier")] + public float SpeedModifier = 1.0f; + + ///+ /// What sound to play when prying is finished. + /// + [DataField("useSound")] + public SoundSpecifier UseSound = new SoundPathSpecifier("/Audio/Items/crowbar.ogg"); + + ///+ /// Whether the entity can currently pry things. + /// + [DataField("enabled")] + public bool Enabled = true; +} + +///+/// Raised directed on an entity before prying it. +/// Cancel to stop the entity from being pried open. +/// +[ByRefEvent] +public record struct BeforePryEvent(EntityUid User, bool PryPowered, bool Force) +{ + public readonly EntityUid User = User; + + public readonly bool PryPowered = PryPowered; + + public readonly bool Force = Force; + + public bool Cancelled; +} + +///+/// Raised directed on an entity that has been pried. +/// +[ByRefEvent] +public readonly record struct PriedEvent(EntityUid User) +{ + public readonly EntityUid User = User; +} + +///+/// Raised to determine how long the door's pry time should be modified by. +/// Multiply PryTimeModifier by the desired amount. +/// +[ByRefEvent] +public record struct GetPryTimeModifierEvent +{ + public readonly EntityUid User; + public float PryTimeModifier = 1.0f; + public float BaseTime = 5.0f; + + public GetPryTimeModifierEvent(EntityUid user) + { + User = user; + } +} + diff --git a/Content.Shared/Prying/Systems/PryingSystem.cs b/Content.Shared/Prying/Systems/PryingSystem.cs new file mode 100644 index 00000000000..3bb3a9bc9b0 --- /dev/null +++ b/Content.Shared/Prying/Systems/PryingSystem.cs @@ -0,0 +1,162 @@ +using Content.Shared.Prying.Components; +using Content.Shared.Verbs; +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; +using Content.Shared.Administration.Logs; +using Content.Shared.Database; +using Content.Shared.Doors.Components; +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Interaction; +using PryUnpoweredComponent = Content.Shared.Prying.Components.PryUnpoweredComponent; + +namespace Content.Shared.Prying.Systems; + +///+/// Handles prying of entities (e.g. doors) +/// +public sealed class PryingSystem : EntitySystem +{ + [Dependency] private readonly ISharedAdminLogManager _adminLog = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + // Mob prying doors + SubscribeLocalEvent>(OnDoorAltVerb); + SubscribeLocalEvent (OnDoAfter); + SubscribeLocalEvent (TryPryDoor); + } + + private void TryPryDoor(EntityUid uid, DoorComponent comp, InteractUsingEvent args) + { + if (args.Handled) + return; + + args.Handled = TryPry(uid, args.User, out _, args.Used); + } + + private void OnDoorAltVerb(EntityUid uid, DoorComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + if (!TryComp (args.User, out var tool)) + return; + + args.Verbs.Add(new AlternativeVerb() + { + Text = Loc.GetString("door-pry"), + Impact = LogImpact.Low, + Act = () => TryPry(uid, args.User, out _, args.User), + }); + } + + /// + /// Attempt to pry an entity. + /// + public bool TryPry(EntityUid target, EntityUid user, out DoAfterId? id, EntityUid tool) + { + id = null; + + PryingComponent? comp = null; + if (!Resolve(tool, ref comp, false)) + return false; + + if (!comp.Enabled) + return false; + + if (!CanPry(target, user, comp)) + { + // If we have reached this point we want the event that caused this + // to be marked as handled as a popup would be generated on failure. + return true; + } + + StartPry(target, user, tool, comp.SpeedModifier, out id); + + return true; + } + + ///+ /// Try to pry an entity. + /// + public bool TryPry(EntityUid target, EntityUid user, out DoAfterId? id) + { + id = null; + + if (!CanPry(target, user)) + // If we have reached this point we want the event that caused this + // to be marked as handled as a popup would be generated on failure. + return true; + + return StartPry(target, user, null, 1.0f, out id); + } + + private bool CanPry(EntityUid target, EntityUid user, PryingComponent? comp = null) + { + BeforePryEvent canev; + + if (comp != null) + { + canev = new BeforePryEvent(user, comp.PryPowered, comp.Force); + } + else + { + if (!TryComp(target, out _)) + return false; + canev = new BeforePryEvent(user, false, false); + } + + RaiseLocalEvent(target, ref canev); + + if (canev.Cancelled) + return false; + return true; + } + + private bool StartPry(EntityUid target, EntityUid user, EntityUid? tool, float toolModifier, [NotNullWhen(true)] out DoAfterId? id) + { + var modEv = new GetPryTimeModifierEvent(user); + + RaiseLocalEvent(target, ref modEv); + var doAfterArgs = new DoAfterArgs(EntityManager, user, TimeSpan.FromSeconds(modEv.BaseTime * modEv.PryTimeModifier / toolModifier), new DoorPryDoAfterEvent(), target, target, tool) + { + BreakOnDamage = true, + BreakOnUserMove = true, + }; + + if (tool != null) + { + _adminLog.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user)} is using {ToPrettyString(tool.Value)} to pry {ToPrettyString(target)}"); + } + else + { + _adminLog.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user)} is prying {ToPrettyString(target)}"); + } + return _doAfterSystem.TryStartDoAfter(doAfterArgs, out id); + } + + private void OnDoAfter(EntityUid uid, DoorComponent door, DoorPryDoAfterEvent args) + { + if (args.Cancelled) + return; + if (args.Target is null) + return; + + PryingComponent? comp = null; + + if (args.Used != null && Resolve(args.Used.Value, ref comp)) + _audioSystem.PlayPredicted(comp.UseSound, args.Used.Value, args.User); + + var ev = new PriedEvent(args.User); + RaiseLocalEvent(uid, ref ev); + } +} + +[Serializable, NetSerializable] +public sealed partial class DoorPryDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs b/Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs index b198f6d779c..d528c1be7dd 100644 --- a/Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs +++ b/Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs @@ -2,6 +2,7 @@ using Content.Shared.Interaction; using Content.Shared.Tools.Components; using Robust.Shared.GameStates; +using Content.Shared.Prying.Components; namespace Content.Shared.Tools; @@ -27,7 +28,7 @@ private void OnMultipleToolHandleState(EntityUid uid, MultipleToolComponent comp private void OnMultipleToolStartup(EntityUid uid, MultipleToolComponent multiple, ComponentStartup args) { // Only set the multiple tool if we have a tool component. - if(EntityManager.TryGetComponent(uid, out ToolComponent? tool)) + if (EntityManager.TryGetComponent(uid, out ToolComponent? tool)) SetMultipleTool(uid, multiple, tool); } @@ -52,7 +53,7 @@ public bool CycleMultipleTool(EntityUid uid, MultipleToolComponent? multiple = n if (multiple.Entries.Length == 0) return false; - multiple.CurrentEntry = (uint) ((multiple.CurrentEntry + 1) % multiple.Entries.Length); + multiple.CurrentEntry = (uint)((multiple.CurrentEntry + 1) % multiple.Entries.Length); SetMultipleTool(uid, multiple, playSound: true, user: user); return true; @@ -79,6 +80,19 @@ public virtual void SetMultipleTool(EntityUid uid, tool.UseSound = current.Sound; tool.Qualities = current.Behavior; + // TODO: Replace this with a better solution later + if (TryComp (uid, out var pcomp)) + { + if (current.Behavior.Contains("Prying")) + { + pcomp.Enabled = true; + } + else + { + pcomp.Enabled = false; + } + } + if (playSound && current.ChangeSound != null) _audioSystem.PlayPredicted(current.ChangeSound, uid, user); diff --git a/Resources/Prototypes/Entities/Debugging/spanisharmyknife.yml b/Resources/Prototypes/Entities/Debugging/spanisharmyknife.yml index 66e2b35ad81..94464f2535d 100644 --- a/Resources/Prototypes/Entities/Debugging/spanisharmyknife.yml +++ b/Resources/Prototypes/Entities/Debugging/spanisharmyknife.yml @@ -24,6 +24,7 @@ - type: Tool qualities: - Prying + - type: Prying - type: MultipleTool statusShowBehavior: true entries: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 47ea55278fb..9831d20e27a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -25,6 +25,11 @@ speed: 1.5 qualities: - Prying + - type: Prying + pryPowered: !type:Bool + true + force: !type:Bool + true useSound: path: /Audio/Items/crowbar.ogg - type: Reactive diff --git a/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml b/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml index d500ef141c3..24eb0b02b24 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml @@ -1,4 +1,4 @@ -- type: entity +- type: entity name: haycutters parent: BaseItem id: Haycutters @@ -98,6 +98,7 @@ path: /Audio/Items/crowbar.ogg speed: 0.05 - type: TilePrying + - type: Prying - type: entity name: mooltitool diff --git a/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml b/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml index a7f74a542f4..36f96f61af8 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml @@ -24,6 +24,12 @@ - Prying speed: 1.5 useSound: /Audio/Items/jaws_pry.ogg + - type: Prying + pryPowered: !type:Bool + true + force: !type:Bool + true + useSound: /Audio/Items/jaws_pry.ogg - type: ToolForcePowered - type: MultipleTool statusShowBehavior: true @@ -77,4 +83,4 @@ - type: MeleeWeapon damage: types: - Blunt: 14 \ No newline at end of file + Blunt: 14 diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml index 3d58b2b079d..82bef616de4 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml @@ -155,6 +155,7 @@ Steel: 100 - type: StaticPrice price: 22 + - type: Prying - type: entity parent: Crowbar diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml index cb6f8627450..764683183d9 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml @@ -20,3 +20,4 @@ - type: Tool qualities: - Prying + - type: Prying diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml index 9e747328f94..fcc2129a51f 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml @@ -38,6 +38,7 @@ - Prying - type: TilePrying advanced: true + - type: Prying - type: entity id: FireAxeFlaming diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml index d182d9a00e9..3073ed7ab53 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml @@ -61,6 +61,7 @@ - type: Tool qualities: - Prying + - type: Prying - type: entity name: crusher dagger diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/easy_pry.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/easy_pry.yml new file mode 100644 index 00000000000..04a58eebe07 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/easy_pry.yml @@ -0,0 +1,63 @@ +- type: entity + parent: AirlockExternal + id: AirlockExternalEasyPry + suffix: External, EasyPry + description: It opens, it closes, it might crush you, and there might be only space behind it. Has to be manually activated. Has a valve labelled "TURN TO OPEN" + components: + - type: PryUnpowered + +- type: entity + parent: AirlockExternalGlass + id: AirlockExternalGlassEasyPry + suffix: External, Glass, EasyPry + description: It opens, it closes, it might crush you, and there might be only space behind it. Has to be manually activated. Has a valve labelled "TURN TO OPEN" + components: + - type: PryUnpowered + +- type: entity + parent: AirlockGlassShuttle + id: AirlockGlassShuttleEasyPry + suffix: EasyPry, Docking + description: Necessary for connecting two space craft together. Has a valve labelled "TURN TO OPEN" + components: + - type: PryUnpowered + +- type: entity + parent: AirlockShuttle + id: AirlockShuttleEasyPry + suffix: EasyPry, Docking + description: Necessary for connecting two space craft together. Has a valve labelled "TURN TO OPEN" + components: + - type: PryUnpowered + +- type: entity + parent: AirlockExternalLocked + id: AirlockExternalEasyPryLocked + suffix: External, EasyPry, Locked + description: It opens, it closes, it might crush you, and there might be only space behind it. Has to be manually activated. Has a valve labelled "TURN TO OPEN" + components: + - type: PryUnpowered + +- type: entity + parent: AirlockExternalGlassLocked + id: AirlockExternalGlassEasyPryLocked + suffix: External, Glass, EasyPry, Locked + description: It opens, it closes, it might crush you, and there might be only space behind it. Has to be manually activated. Has a valve labelled "TURN TO OPEN" + components: + - type: PryUnpowered + +- type: entity + parent: AirlockExternalGlassShuttleLocked + id: AirlockGlassShuttleEasyPryLocked + suffix: EasyPry, Docking, Locked + description: Necessary for connecting two space craft together. Has a valve labelled "TURN TO OPEN" + components: + - type: PryUnpowered + +- type: entity + parent: AirlockExternalShuttleLocked + id: AirlockShuttleEasyPryLocked + suffix: EasyPry, Docking, Locked + description: Necessary for connecting two space craft together. Has a valve labelled "TURN TO OPEN" + components: + - type: PryUnpowered From 6452482059ceaf45c3fedc27d3a1f2c1ce545dd9 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 28 Sep 2023 07:35:26 -0400 Subject: [PATCH 55/61] Automatic changelog update --- Resources/Changelog/Changelog.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index ba68c124913..c15e4150cb5 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,10 +1,4 @@ Entries: -- author: deltanedas - changes: - - {message: 'Added the gateway, currently for admins to teleport you to funny places.', - type: Add} - id: 4411 - time: '2023-08-02T09:47:18.0000000+00:00' - author: Nimfar11 changes: - {message: Adds a magazine for the C20R to the uplink for 3 TC., type: Add} @@ -2968,3 +2962,9 @@ Entries: - {message: Pun Pun now showing on crew monitoring., type: Fix} id: 4910 time: '2023-09-28T10:53:54.0000000+00:00' +- author: nikthechampiongr + changes: + - {message: Mappers can now place airlocks that can be pried open while unpowered + without tools., type: Add} + id: 4911 + time: '2023-09-28T11:34:22.0000000+00:00' From eb26990782cef0d9733f45f0d37d049e6a36179f Mon Sep 17 00:00:00 2001 From: Doru991 <75124791+Doru991@users.noreply.github.com> Date: Thu, 28 Sep 2023 17:00:18 +0300 Subject: [PATCH 56/61] Make arcades hackable again (#20555) --- .../Recipes/Construction/Graphs/machines/computer.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/machines/computer.yml b/Resources/Prototypes/Recipes/Construction/Graphs/machines/computer.yml index c0736c4973e..6bbbd4f2fff 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/machines/computer.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/machines/computer.yml @@ -124,6 +124,8 @@ entity: !type:BoardNodeEntity { container: board } edges: - to: monitorUnsecured + conditions: + - !type:AllWiresCut {} steps: - tool: Screwing From 4b4520c77116fa3b4b1ca89a565cbcf26c17a089 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 28 Sep 2023 10:01:23 -0400 Subject: [PATCH 57/61] Automatic changelog update --- Resources/Changelog/Changelog.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index c15e4150cb5..ffb5997c8dd 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,9 +1,4 @@ Entries: -- author: Nimfar11 - changes: - - {message: Adds a magazine for the C20R to the uplink for 3 TC., type: Add} - id: 4412 - time: '2023-08-02T10:03:39.0000000+00:00' - author: Nimfar changes: - {message: 'Adds a new theft target for the Syndicate, Emergency Security Orders @@ -2968,3 +2963,8 @@ Entries: without tools., type: Add} id: 4911 time: '2023-09-28T11:34:22.0000000+00:00' +- author: Doru991 + changes: + - {message: Arcade machine maintenance panels can be opened again., type: Fix} + id: 4912 + time: '2023-09-28T14:00:19.0000000+00:00' From 5e25201032502dfba8733039b95b8887cc64cdb2 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Thu, 28 Sep 2023 10:05:42 -0400 Subject: [PATCH 58/61] Health alert tweaks (#20557) --- .../Mobs/Systems/MobThresholdSystem.cs | 94 +++++++++++------- .../Alerts/human_alive.rsi/health0.png | Bin 2153 -> 2177 bytes .../Alerts/human_alive.rsi/health1.png | Bin 2155 -> 2267 bytes .../Alerts/human_alive.rsi/health2.png | Bin 2152 -> 2249 bytes .../Alerts/human_alive.rsi/health3.png | Bin 2207 -> 2331 bytes .../Alerts/human_alive.rsi/health4.png | Bin 2269 -> 2401 bytes 6 files changed, 60 insertions(+), 34 deletions(-) diff --git a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs index 1cb32543ebb..f34240d8fe3 100644 --- a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs +++ b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs @@ -5,7 +5,6 @@ using Content.Shared.FixedPoint; using Content.Shared.Mobs.Components; using Robust.Shared.GameStates; -using Robust.Shared.Utility; namespace Content.Shared.Mobs.Systems; @@ -52,6 +51,38 @@ private void OnHandleState(EntityUid uid, MobThresholdsComponent component, ref #region Public API + /// + /// Gets the next available state for a mob. + /// + /// Target entity + /// Supplied MobState + /// The following MobState. Can be null if there isn't one. + /// Threshold Component Owned by the target + ///True if the next mob state exists + public bool TryGetNextState( + EntityUid target, + MobState mobState, + [NotNullWhen(true)] out MobState? nextState, + MobThresholdsComponent? thresholdsComponent = null) + { + nextState = null; + if (!Resolve(target, ref thresholdsComponent)) + return false; + + MobState? min = null; + foreach (var state in thresholdsComponent.Thresholds.Values) + { + if (state <= mobState) + continue; + + if (min == null || state < min) + min = state; + } + + nextState = min; + return nextState != null; + } + ////// Get the Damage Threshold for the appropriate state if it exists /// @@ -261,7 +292,7 @@ public void SetMobStateThreshold(EntityUid target, FixedPoint2 damage, MobState threshold.Thresholds.Remove(damageThreshold); } threshold.Thresholds[damage] = mobState; - Dirty(threshold); + Dirty(target, threshold); VerifyThresholds(target, threshold); } @@ -291,7 +322,7 @@ public void SetAllowRevives(EntityUid uid, bool val, MobThresholdsComponent? com if (!Resolve(uid, ref component, false)) return; component.AllowRevives = val; - Dirty(component); + Dirty(uid, component); VerifyThresholds(uid, component); } @@ -344,42 +375,37 @@ private void UpdateAlerts(EntityUid target, MobState currentMobState, MobThresho if (!threshold.TriggersAlerts) return; - var dict = threshold.StateAlertDict; - var healthAlert = AlertType.HumanHealth; - var critAlert = AlertType.HumanCrit; - var deadAlert = AlertType.HumanDead; + if (!threshold.StateAlertDict.TryGetValue(currentMobState, out var currentAlert)) + { + Log.Error($"No alert alert for mob state {currentMobState} for entity {ToPrettyString(target)}"); + return; + } - dict.TryGetValue(MobState.Alive, out healthAlert); - dict.TryGetValue(MobState.Critical, out critAlert); - dict.TryGetValue(MobState.Dead, out deadAlert); + if (!_alerts.TryGet(currentAlert, out var alertPrototype)) + { + Log.Error($"Invalid alert type {currentAlert}"); + return; + } - switch (currentMobState) + if (alertPrototype.SupportsSeverity) { - case MobState.Alive: + var severity = _alerts.GetMinSeverity(currentAlert); + if (TryGetNextState(target, currentMobState, out var nextState, threshold) && + TryGetPercentageForState(target, nextState.Value, damageable.TotalDamage, out var percentage)) { - var severity = _alerts.GetMinSeverity(healthAlert); - if (TryGetIncapPercentage(target, damageable.TotalDamage, out var percentage)) - { - severity = (short) MathF.Floor(percentage.Value.Float() * - _alerts.GetSeverityRange(healthAlert)); - severity += _alerts.GetMinSeverity(healthAlert); - } - _alerts.ShowAlert(target, healthAlert, severity); - break; - } - case MobState.Critical: - { - _alerts.ShowAlert(target, critAlert); - break; - } - case MobState.Dead: - { - _alerts.ShowAlert(target, deadAlert); - break; + percentage = FixedPoint2.Clamp(percentage.Value, 0, 1); + + severity = (short) MathF.Round( + MathHelper.Lerp( + _alerts.GetMinSeverity(currentAlert), + _alerts.GetMaxSeverity(currentAlert), + percentage.Value.Float())); } - case MobState.Invalid: - default: - throw new ArgumentOutOfRangeException(nameof(currentMobState), currentMobState, null); + _alerts.ShowAlert(target, currentAlert, severity); + } + else + { + _alerts.ShowAlert(target, currentAlert); } } diff --git a/Resources/Textures/Interface/Alerts/human_alive.rsi/health0.png b/Resources/Textures/Interface/Alerts/human_alive.rsi/health0.png index cce1b027b6101b060904679943dcbe32610f9910..6c9b54e52e29d1909a41d0562da1efb4eba88035 100644 GIT binary patch literal 2177 zcma)7c{J2(A0G`yiXl-(H7;SK*A|gTLKoTn zEKy^d%C0iGGe$$U8N=*zf86)H=e+;D-*cYl`~5!WIp_I)KcDCMJjqU%Y@{R+k`M?) z%Jzbl3+PM0(Ip`c&i73NZ-Y)G%EiV4QZsUh27ySJ+FG4+jV)X*LWPm9?6(QC5j$7b zQ|IUYDX?l+?r>#sNkDmNRrP~=_x3=cdesQmAObF-a&X_tn~fThO6~{K8eFa@jG
(C?1q-*O3C&E(*XKq=9Fc%h0S^OBM@Bk99>=>w*YpUIzXzNZz#aCBI>{qsT#tW? zpHsf#pdO{v_ JBr zJ~~o5>DBVhGsyw?ni6e#RaUko>(!7+Fjwl!VjT@Vg19tl&t;TJ1+{mCkaTO~+vXgo z8J#HMXgJR^DYa?BZxmH#rL8+s?e8lHiMhg@?Dm!$Yt>06VbOypOYBso0!L)?n^8R0 zAOVZ>Mc`u4ka)6I?%;T2i#IWV{&p1TeFR7&a|Y)Zm)7c|O^V8b@VJ1t*?q^FtgSkw z(Szs?yppkiy!C~sySLH;ns8!CJebp7s>R}O= 8aS< z)>N3;o)R}v99GDPv@lctamo(&v|e0axe`nSp9S@^<*7A#W=`HB=gpmd$C&e~aQ_4_ zJ_w8(z6ksIbT*vTv1K`qpJ;Ps3K#i4lMzPk(`|Uf@FY&|$5
cEBxAg(0v?I6*ESk5DNa!t1yv)~7Zj~x=zO`}Gtu?-#*VX212PT8{R^c=a{M(uBM z9VZ`!IU3;$hpO%((wDhX9x?B5vL0vA_OoY^X`IV3ARBCbUh`&wTJR8_Ih$9bdZ>>b zzpwiIrwbOl5ks&;Aw;U4afCZ#?#SgPMT4JxK!}$iV||6vUxOwu*a8prm+;f{Rjb2F zL?8qH5U|GO_?gSoMb_Jvw>a1!);J#mNhe2{9mauccYoec6nA|la&CLWD~=) O&> zp%F=9ePbK|p-S?S8$YH(pXdB53R%F8?iVf(3qy;sY@{#WS$(D$;LaA$z7NpwWZqub z*ZEE`I*hF<+$TQ>OGQR!PFFm^n2=E?xwp#Eg0j_oY&ykf9HpYxTW4i IHtMUzIAN1nhI5@tayk%?CmjK>^$g$6y|WX$Ut_1 zn|o>mIT6aov<51^ ^UJQG=GpS4zo}F&qm3cL z9xPCxs@(9vlO6B2`BD;8m6Qdj&QD73lT8$L-_u|MtYN>0*A&S-;Pt3Lvf^kTUBsUj zed+*>y6RT;=vh_}#?qad4VKU*1f4Y=$!179Lv_WT@c* T#%!VIj4lLaf Z!Q81rGw z>A1o0%?V-YPxLQH8KB~SNnnH?bow0x^aJc&?cxnlQMW+zk2a=AX@CUo11-MS=B`2_ za9Cis6i;xpG;kKh;rTo71C(rSWSuoTniaFno*fg@l|vbwn`CaQ7zO&*ugqK`Y-lj4 zKKeoz$p_EIbg6I+c}Ks8)dCo^Yd&V)yn)y)14$aBEY@_ j%-%=WreisJi*;FW){6XE)(mZ(Nww|I>y7glG#I@~&o1Ink@(2FNI|>FCXHBG z=3eSdmw%R-k@z3A)?>%#v%^{Z&8B%!AHHosA*a)cV8sO3yVW 2+?SPcjW9y4>1WXFKjDvj*r<34XrJ7H z!sM?o%@qo?Kj{lCSZ9u=0F+z)nHO6fMr>jkAI ISoOj+k z-+SNvym$JG$k0_5b`}^6W>t7tNEGTTQ0pM9M8^|b4zp1AUPf3f1B0>Zd}G)mcPo1g zhF~8a61XSp%EHyH^obpq0)Ae>uKZB? #-dE1Ug`Dv6p@uD T1!n)de zTVW6;aAyWaG_?x?;pOEM9~=W6kP1w((u?;a9&5@n%8mzGpo{O}B*h7~%fgo_<|{Tn zPr1Klx64-RF2DkCcMelnRZs?Z2iPlkLvq`WGtvO2U>|3TFoa|Mnl}P815Nb8^ZF}r zTX^ZIX%ZitVbs>-7R~E7X2DF=#R2gqnBSN1L|XU$MNo58)3O 5`tz)eR{j z`Cilbt8?S>(apRINchBG#ydT3q<$^5k#;RdPCr=4xkb0y2y0te6qPq?i}>6i{k!qF zy^O<`g_TCZt8;_ET+ZM7KjYjB8FBr_<`@{q@qCbX!3|qqn}9`f;S**O#;!seqt1Zm z=RHnwLX5Bj?)YjmLKML{><@X)`Iz4bB=}A*^$_tC@W?cwmcKf2lma*(cAJ-r9WZFp zmT)uj1&{&X+Sq6@t145no6L@{)?*bBa6P&=r^6Dy-TyOl{<<-^YtzryNEz(O@E5K4 zDfc7<;+uTvJwyJ?0 s z>GH1^74+%$6RLEg^evZFZVyt}b+%2Vw#*Gbj1s?;wHME3&-_(0)MGzU?Ca18NpFwI zgcDlj5EbkmvHvM06aL=O#P3O*ReX-BC7mnp_C}R$Ge!Y$-f|7+t=Rt~rFbo_QADnr z7uHSHX15>_nZSYdfPjkLR>s42E{paf`8ugz?5MwyApmZel#k$Ag)z{bsz^9+h<|7$ z)$P83I#P6S4|(0gJ&Pq}r>?`QBH$mV3Bo+* MHC~D#B4I2T#R{6~C!DsA;Rs{%BZhgdcf4G} zEuk#OW(?b|+hV=fK#9)-FG4_{t@Vxv6`YPjpUP=ctq3Az+^Hhb6#lN!$%_Rd8il!p zbG=!w){ynGvuQ>wcRdHrD{ApK_%1k2w32$ct4!_9(S&5Adj*`^j^LX`rRvOPIh&HZ z*m6N&ca}T6e14xtXoK(Yvw$-!(kwOSD~+a8w=^^ickciWL@-iSiR=BuKEH?t r3!I^(J12?xgH16|d{*Vhir1J#i@pb#0z*;~yzyrNZiT-}FKB;63gY3p~xj>4@hn z%d4SsPW!k;Zi2Mm>S|~PFrBN)p?R{!K5gYqiB6e%Y~66^Q(~6R%*dz|MN4iPX=^;@ z@Gp*gTRKMq)JiG-V_4;)_3A`^mkd-$&SyVG?2W#Lxi@rCMLGOWD0)Y%H@ydD0n=gn zx!0-iJ9;00^CI+$*TQ4A*3~D=&3V=3R?yQv3qsUeexVH@o99NfM05c0poz1sy!kQ5 z&lsySXmL#SgXq2=8hC#`{NdW@)^tjoBJ~3ilqMIZ-ecd2AuY<4eu#e_l 6F5Fv)>e&NYfX0|=cpB+Nxu-hEO?a}26p?;Ea9vqT 0SshXFcYrv8^5jyXrx7j=8M>V*BQ?Td= z(tID4t6aeQf2AuTuM@ii E@=i3Z ltsr?bz{L#9|MmhQ_%y;TLvM+|3H>`@!na3;GzL+R{|d53A2$F1 diff --git a/Resources/Textures/Interface/Alerts/human_alive.rsi/health1.png b/Resources/Textures/Interface/Alerts/human_alive.rsi/health1.png index 2ec0eb65515ba5b665988c0d2333bb717e256065..186d07386ec845d1b7f0e5174546f06f38cda0e6 100644 GIT binary patch literal 2267 zcmX|?4LH+lAIB}8Mi=HBN#6RGo)UG`$;w-{aws}X7e |Mz|Wuiy7`f4_I;DbxwQO{SYP zG&J-)J=}a%W2Ne8*WRe=Q=G%&RRiRP?+I58YM=S6hK4rD)9sl5Z)JjVWVo5<=5&go zQPhU04IAuj$M-|-e1Hei{V$%{X`sy{S) MwF6W}!8EWng4_rIunh{SG%vVVIM)}pU}=(Ax^0A(Bz^(*bjOkv z*WvdWqdy%|Rwon`ajP;i;8VWjfsxv-X3n#^+y?nAy;~NoQJe>i0yv7B-R70|@w&_= zxI_>lyMp`I3LWeyICr0T^E%es?NS5U2dCgU^@y-TlWfLv?m+1NljK160vQvipj_U1 z>Yb wm6MJCTJvO*-===%M$#jwyYOXY`Hm7(b7`Nnz@G(UBJf32 775}Ir}2A(HLKuaLXG<*tc&9z{eTjEb-(@m^Vk+g1OjpJ;>hqQOc5 U2NxY?8#{W4-EEA2L)#E `lNWppEKLGRhkYt? iFBA@=2O!FE{l#HK*|o(<)dC(h!6u?lffx4{eQ7Jr>Q ~pY-|n@83g78 zD0hv2!!JW`1&miZk94OP7UT%TmwFn!J}H`BmHT&9zRucn25*+rESx(l<;Inn+fPvw z=)woPZ%$!DqMW&L*;MbflTG2Au}lx%J4RjZQoSHvCb#cw{y_X)PCeccj~Ejle$mQB z3^ymz6px(_gJ1uHOz==NhICbN1Y8O)TUqXdlq8AK;67%O4Nvnc?FI7N{J>(JN@W{V zx8$byJ+uROrUsshKPs;!2q8;#Qb(;68~FC8u88e~IOIBw_Q^+y7F!ibpr9^%X*I|m z@cCPbGk>96NU$etd8h(-z{-;{nabz&K)sfAaF~*kjC`;+Z*|E|<%=;r2YPN9k7=~@ zpuNHX =m^K_I~ugSa8P&r z@Z_vN&pP;v)0+Jn=v&1U;2qdn&s$o?B+*N&O;_GZgG&{UgRj8$ IGvwVI#3HYjvv zHnrLJibE2yLEDtWVLGEq+`d!=rs8r*@@Fg8;Ft2RC4X)P_rHIu_xbnS+lcb_&-4cw zefj$Rz)D<%co5XkFtXy|o#ZO0u+OZ6y6d>m|G@sAr_{RAfpvfri=W!`b%`$WS3?j1 ztA@VEGLM5t84>>i_qnaZF5C;8a;Dh!TCFSvGAY+K^ldAb?1+v9l0&ALQ`4Rt8qV^R zF>(QN%xL|H_|0fiJL@y&PJZqRymUR7LF*o^6(_dt@| i?XUKWsG#|I9g -sz3n`oL$42<|4 z-%jVTCx=%X4fJaJlPXVA@$F7i>KAIO$n{PashH9UyZ^Btn!&~iVP=S67``&=ukK z30l4KDQLaz$^D~80Ne8kT+)(n_>B{`Wvwn!D|eD3n3foq%^2b^Xt@VL&$!>-L*cZf zpnbBacvKmn?2(