From 82528dce377ed76399fedf570a96459288354c95 Mon Sep 17 00:00:00 2001 From: keronshb <54602815+keronshb@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:49:54 -0500 Subject: [PATCH] Adds Store on Collide and Wand of the Locker (#33710) * Adds wand of locker and locker projectile * Adds IsOpen method to check if storage is open * Adds store on collide * Adds Store On Collide to Wizard Locker * Adds Lock API * Adds locking support * Adds resist override and custom visual layers * Fixes decursed states, adds comment for a future visualizer * adds locker wand visuals and descriptions * shrinks locker radius, moves TODO for throw support * Adds whitelist and moves storage and lock logic into their own methods * Adds support to disable store on collide after the first open. Fixes prediction issues with disabling. * Adds wand of locker to the grimoire * Adds wizard access prototype * Adds Wizard to universal access * Moves Lock on collide to on collide method * Comments * Changes layer order * Fixes prediction issues when locking. * Adds Wiz access to universal ID --- Content.Shared/Lock/LockSystem.cs | 19 ++++- .../Components/StoreOnCollideComponent.cs | 34 ++++++++ .../SharedEntityStorageSystem.cs | 8 ++ .../EntitySystems/StoreOnCollideSystem.cs | 71 ++++++++++++++++ .../en-US/prototypes/access/accesses.ftl | 2 + .../Locale/en-US/store/spellbook-catalog.ftl | 3 + Resources/Prototypes/Access/wizard.yml | 3 + .../Prototypes/Catalog/spellbook_catalog.yml | 13 +++ .../Objects/Misc/identification_cards.yml | 1 + .../Objects/Tools/access_configurator.yml | 1 + .../Objects/Weapons/Guns/Basic/wands.yml | 36 ++++++++ .../Weapons/Guns/Projectiles/magic.yml | 80 ++++++++++++++++++ .../Guns/Basic/wands.rsi/locker-effect.png | Bin 0 -> 260 bytes .../Basic/wands.rsi/locker-inhand-left.png | Bin 0 -> 794 bytes .../Basic/wands.rsi/locker-inhand-right.png | Bin 0 -> 803 bytes .../Weapons/Guns/Basic/wands.rsi/locker.png | Bin 0 -> 624 bytes .../Weapons/Guns/Basic/wands.rsi/meta.json | 16 +++- 17 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 Content.Shared/Storage/Components/StoreOnCollideComponent.cs create mode 100644 Content.Shared/Storage/EntitySystems/StoreOnCollideSystem.cs create mode 100644 Resources/Prototypes/Access/wizard.yml create mode 100644 Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-effect.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-inhand-left.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-inhand-right.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker.png diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index 3349034f32..411766d8c1 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -131,8 +131,24 @@ public sealed class LockSystem : EntitySystem }); } - _sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-lock-success", + Lock(uid, user, lockComp); + return true; + } + + /// + /// Forces a given entity to be locked, does not activate a do-after. + /// + public void Lock(EntityUid uid, EntityUid? user, LockComponent? lockComp = null) + { + if (!Resolve(uid, ref lockComp)) + return; + + if (user is { Valid: true }) + { + _sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-lock-success", ("entityName", Identity.Name(uid, EntityManager))), uid, user); + } + _audio.PlayPredicted(lockComp.LockSound, uid, user); lockComp.Locked = true; @@ -141,7 +157,6 @@ public sealed class LockSystem : EntitySystem var ev = new LockToggledEvent(true); RaiseLocalEvent(uid, ref ev, true); - return true; } /// diff --git a/Content.Shared/Storage/Components/StoreOnCollideComponent.cs b/Content.Shared/Storage/Components/StoreOnCollideComponent.cs new file mode 100644 index 0000000000..d8fff31938 --- /dev/null +++ b/Content.Shared/Storage/Components/StoreOnCollideComponent.cs @@ -0,0 +1,34 @@ +using Content.Shared.Storage.EntitySystems; +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.Storage.Components; + +// Use where you want an entity to store other entities on collide +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(StoreOnCollideSystem))] +public sealed partial class StoreOnCollideComponent : Component +{ + /// + /// Entities that are allowed in the storage on collide + /// + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// Should this storage lock on collide, provided they have a lock component? + /// + [DataField] + public bool LockOnCollide; + + /// + /// Should the behavior be disabled when the storage is first opened? + /// + [DataField] + public bool DisableWhenFirstOpened; + + /// + /// If the behavior is disabled or not + /// + [DataField, AutoNetworkedField] + public bool Disabled; +} diff --git a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs index 309ac0a2e0..829f574ad1 100644 --- a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs @@ -344,6 +344,14 @@ public abstract class SharedEntityStorageSystem : EntitySystem return true; } + public bool IsOpen(EntityUid target, SharedEntityStorageComponent? component = null) + { + if (!ResolveStorage(target, ref component)) + return false; + + return component.Open; + } + public bool CanOpen(EntityUid user, EntityUid target, bool silent = false, SharedEntityStorageComponent? component = null) { if (!ResolveStorage(target, ref component)) diff --git a/Content.Shared/Storage/EntitySystems/StoreOnCollideSystem.cs b/Content.Shared/Storage/EntitySystems/StoreOnCollideSystem.cs new file mode 100644 index 0000000000..000e19faf3 --- /dev/null +++ b/Content.Shared/Storage/EntitySystems/StoreOnCollideSystem.cs @@ -0,0 +1,71 @@ +using Content.Shared.Lock; +using Content.Shared.Storage.Components; +using Content.Shared.Whitelist; +using Robust.Shared.Network; +using Robust.Shared.Physics.Events; +using Robust.Shared.Timing; + +namespace Content.Shared.Storage.EntitySystems; + +internal sealed class StoreOnCollideSystem : EntitySystem +{ + [Dependency] private readonly SharedEntityStorageSystem _storage = default!; + [Dependency] private readonly LockSystem _lock = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly INetManager _netMan = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnCollide); + SubscribeLocalEvent(AfterOpen); + // TODO: Add support to stop colliding after throw, wands will need a WandComp + } + + // We use Collide instead of Projectile to support different types of interactions + private void OnCollide(Entity ent, ref StartCollideEvent args) + { + TryStoreTarget(ent, args.OtherEntity); + + TryLockStorage(ent); + } + + private void AfterOpen(Entity ent, ref StorageAfterOpenEvent args) + { + var comp = ent.Comp; + + if (comp is { DisableWhenFirstOpened: true, Disabled: false }) + comp.Disabled = true; + } + + private void TryStoreTarget(Entity ent, EntityUid target) + { + var storageEnt = ent.Owner; + var comp = ent.Comp; + + if (_netMan.IsClient || _gameTiming.ApplyingState) + return; + + if (ent.Comp.Disabled || storageEnt == target || Transform(target).Anchored || _storage.IsOpen(storageEnt) || _whitelist.IsWhitelistFail(comp.Whitelist, target)) + return; + + _storage.Insert(target, storageEnt); + + } + + private void TryLockStorage(Entity ent) + { + var storageEnt = ent.Owner; + var comp = ent.Comp; + + if (_netMan.IsClient || _gameTiming.ApplyingState) + return; + + if (ent.Comp.Disabled) + return; + + if (comp.LockOnCollide && !_lock.IsLocked(storageEnt)) + _lock.Lock(storageEnt, storageEnt); + } +} diff --git a/Resources/Locale/en-US/prototypes/access/accesses.ftl b/Resources/Locale/en-US/prototypes/access/accesses.ftl index 0e8b1d9ac7..5e54fcad22 100644 --- a/Resources/Locale/en-US/prototypes/access/accesses.ftl +++ b/Resources/Locale/en-US/prototypes/access/accesses.ftl @@ -42,3 +42,5 @@ id-card-access-level-nuclear-operative = Nuclear Operative id-card-access-level-syndicate-agent = Syndicate Agent id-card-access-level-central-command = Central Command + +id-card-access-level-wizard = Wizard diff --git a/Resources/Locale/en-US/store/spellbook-catalog.ftl b/Resources/Locale/en-US/store/spellbook-catalog.ftl index ed62c6fa82..dff918b0bc 100644 --- a/Resources/Locale/en-US/store/spellbook-catalog.ftl +++ b/Resources/Locale/en-US/store/spellbook-catalog.ftl @@ -28,6 +28,9 @@ spellbook-wand-polymorph-door-description = For when you need a get-away route. spellbook-wand-polymorph-carp-name = Wand of Carp Polymorph spellbook-wand-polymorph-carp-description = For when you need a carp filet quick and the clown is looking juicy. +spellbook-wand-locker-name = Wand of the Locker +spellbook-wand-locker-description = Shoot cursed lockers at your enemies and lock em away! + # Events spellbook-event-summon-ghosts-name = Summon Ghosts diff --git a/Resources/Prototypes/Access/wizard.yml b/Resources/Prototypes/Access/wizard.yml new file mode 100644 index 0000000000..cacb889936 --- /dev/null +++ b/Resources/Prototypes/Access/wizard.yml @@ -0,0 +1,3 @@ +- type: accessLevel + id: Wizard + name: id-card-access-level-wizard diff --git a/Resources/Prototypes/Catalog/spellbook_catalog.yml b/Resources/Prototypes/Catalog/spellbook_catalog.yml index 6e9bba87a6..57172dc665 100644 --- a/Resources/Prototypes/Catalog/spellbook_catalog.yml +++ b/Resources/Prototypes/Catalog/spellbook_catalog.yml @@ -118,6 +118,19 @@ - !type:ListingLimitedStockCondition stock: 1 +- type: listing + id: SpellbookWandLocker + name: spellbook-wand-locker-name + description: spellbook-wand-locker-description + productEntity: WeaponWandLocker + cost: + WizCoin: 3 + categories: + - SpellbookEquipment + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + # Event - type: listing id: SpellbookEventSummonGhosts diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml index ccb83b6aaa..a0e144d0ac 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml @@ -810,3 +810,4 @@ - CentralCommand - NuclearOperative - SyndicateAgent + - Wizard diff --git a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml index 66f4689099..d55437696a 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml @@ -119,6 +119,7 @@ - CentralCommand - NuclearOperative - SyndicateAgent + - Wizard privilegedIdSlot: name: id-card-console-privileged-id ejectSound: /Audio/Machines/id_swipe.ogg diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml index e723a1db5f..d3745288f6 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml @@ -57,6 +57,42 @@ capacity: 5 count: 5 +- type: entity + name: wand of the locker + description: Stuff nerds at a distance! + parent: WeaponWandBase + id: WeaponWandLocker + components: + - type: Sprite + layers: + - state: locker + map: ["base"] + - state: locker-effect + map: ["effect"] + - type: Gun + soundGunshot: + path: /Audio/Weapons/Guns/Gunshots/Magic/staff_animation.ogg + - type: BasicEntityAmmoProvider + proto: ProjectileLocker + capacity: 5 + count: 5 + - type: Item + size: Normal + inhandVisuals: + left: + - state: locker-inhand-left + right: + - state: locker-inhand-right + - type: GenericVisualizer + visuals: + enum.AmmoVisuals.HasAmmo: + effect: + True: { visible: False } + False: { visible: True } + base: + True: { visible: True } + False: { visible: False } + - type: entity name: magical wand of instant death parent: WeaponWandBase diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml index d11f96d875..cd736a33d0 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml @@ -79,6 +79,86 @@ totalIntensity: 0.3 maxTileBreak: 0 +- type: entity + id: ProjectileLocker + name: cursed locker + description: A cursed magical locker! Can you resist? + parent: ClosetSteelBase + categories: [ HideSpawnMenu ] + components: + - type: ResistLocker + resistTime: 30 + - type: StoreOnCollide + lockOnCollide: true + disableWhenFirstOpened: true + whitelist: + components: + - Body + - type: LockVisuals + stateLocked: cursed_door + stateUnlocked: decursed_door + - type: Lock + breakOnEmag: false + - type: AccessReader + access: [["Wizard"]] + breakOnEmag: false + - type: Projectile + deleteOnCollide: false + onlyCollideWhenShot: true + damage: + types: + Brute: 0 + - type: Sprite + noRot: true + sprite: Structures/Storage/closet.rsi + layers: + - state: cursed + map: [ "enum.StorageVisualLayers.Base" ] + - state: decursed_door + map: [ "enum.StorageVisualLayers.Door" ] + - state: paper + visible: false + sprite: Structures/Storage/closet_labels.rsi + map: [ "enum.PaperLabelVisuals.Layer" ] + - state: cursed_door + map: [ "enum.LockVisualLayers.Lock" ] + - state: welded + visible: false + map: [ "enum.WeldableLayers.BaseWelded" ] + #TODO: Will have to eventually make a custom visualizer for cursed lockers + - type: EntityStorageVisuals + stateBaseClosed: decursed + stateDoorOpen: decursed_open + stateDoorClosed: decursed_door + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.48,0.25,0.48" + density: 75 + mask: + - MachineMask + layer: + - MachineLayer + projectile: + shape: + !type:PhysShapeAabb + bounds: "-0.15,-0.45,0.15,0.15" + hard: false + mask: + - Impassable + - BulletImpassable + fly-by: &flybyfixture + shape: !type:PhysShapeCircle + radius: 0.6 + layer: + - Impassable + - MidImpassable + - HighImpassable + - LowImpassable + hard: false + - type: entity id: ProjectilePolyboltBase parent: BaseBullet diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-effect.png b/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-effect.png new file mode 100644 index 0000000000000000000000000000000000000000..77bcb4b36404837aec35c9e89a2a143875590b28 GIT binary patch literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4jBOuH;Rhv&5D7ekj z#WAE}&fBYoT+Id|?H|=ECcP;;v!Zyf$GU~bPlUXl;rx_qt9pmsL5GXmX3Y}kde5)4 zqh|k$1isGW3~UAsj64Yk>X~mXD-815ajn#i@4$~_i31YM`*k=r9GQQ)^!2U>m)RN5 z^~kf$k+o;(5WLUZA$p&+L-IdYhwOij1s(fY1CE+APWdR_AaLm6-+SK!rmA}0VK!Jd zWtM!#+LMpu64sv8bA70A@Q?a|c&&Y$M^qRetaz;L(A6s&)IRw{&I%Er4;VaM{an^L HB{Ts5nq6hw literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..bcb330b9ed1042b744c5db5101d8524a91b5ec60 GIT binary patch literal 794 zcmV+#1LgdQP)O|5?CrI)^&j|C{0Fvn z+S^+Qf~{bqSZF08B3ASE3}ksMt2ml-dCS}b%gi##9r7iUtFzfZ5_5R~E^pT9bb|4C zoOsv9QqBN$yWQY+yFG0?8jYMlZ~_1U<@0&j@AnTgjB6hs9|Buf5Sg5PeplFQ{D z+QG)9=m0ePo6RQty<9HewZ*l=;UIWaaPFcCoK*ly8`+=F=XY&z4K}cKB{~3o_HjI& zPGM$ev)PZfjYcE1$z(ELK5Wqep!7APtJMm@-!19EV324wn*l(mFI#i~2p)|snM_Kl zRJxme6>Lxi@WDA-bN~pvQmKUJ&ongH^m@HOeb}M{Sg+S|KA&Z|Tt3~+hr?l_Uatq~ zbXwS=0_gYq0h-w>TN!|18^jvu4MGwX0G{RlVU0(k0eC0-D)_RifCj)abIdBB0IWs(=Cj+nD8g%r8X#m6-K{me2pc(y!IleZU+5vtu*XHU+@SKE&D$OaZVz zwK48t3c$Mie;1>&{4O9dmj@s*mj@s*mj@s*mj@s*mj~eDChc}Rm`o;-KF7b}^NQ#I zTCG;_j_-6j{k`)62%uOj%5Jxdlu_7TE*F6>8v?xo5Ih1o9*>dy6dYSL0GfRbJe$ov z?W?w_R4P1YRsp>NP|8pg01&{zH>zJ$(Ew-(uc5&!>y+-zjk4}j&n;{KAtycSbgZs+MOdXZcwLY7mFhm2eSS%us z#(bBHQ5hP5LZJ{IKLNb8PPJMUru=+>kYqXK$($Ia0kF&#)& zLyQ>#Qvl51A#I43YnlPj?7NWVj$j6Woqd&c75JB;RtvfMH#h5h`3jJj%L9;@EA{~X Y0-ctjx)}pDaR2}S07*qoM6N<$g3AP3(*OVf literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..4be7ef205918ca5d5813819d912ae8c6e9c9800c GIT binary patch literal 803 zcmV+;1Kj+HP)Jm>twcn`Dt9k1hdHxxH*k&1NZz zA^f=cI$$suIMeAgRw97Y^Z9&x?hybArPFCDl}fP^0bIc8bUMAc;LQPt!-3)mkO#M7 zvFLg35ujG9(fNEPSOxcSZ~@Q$e!q8r2d`In1X!(Bu0mJ_aRWGk8#ungBS5>|rt9@e z`F!5B43v`&Ui-L$8yv7)E`Q&91Q?IUsZOWkKnQ?sBN^bpj^1v!ARk-?tJSKj6cPsR zr32)_C6mdxd|qcCcoP6gZ#Wz}^?IG+01#&`ehJ<8i_l~;Nwr!nCzs2SbnJFJy4`NH z*=&5PnQ||H0VpA((a1q=Ab~*aNPt15{NrW_`9LakVh!qU?e&u^9;v^=e>}{Ta3jP{LsapoGH;KnaHxfD#TX03{q&0M(0LujkC?^GJX9 z*F_MQKnHZYU1zaah!z1Hg5LWI0LAY2J5?$b(IOyra5x^1N*5>{zyqZN9Khvrx%j^K z6`)WkxYukp>)uOr0na`YXHI{ibN~io8K?$I0XP800$>1s*h134OZZdl%KiW}NuZ?! z%Rse$q70519MB-4IxbT>;PH6;c2-DChFrw}a8W@i0JqBEzS(TjPZA_aK+Jqf9J)b5 za;waGy$)=RVSL;RG#U+(h;q5?UVj7#{G9C%(D{QBJ4SV5))D|S4^>GJbls|b9xxWv zBxMq-(Sb}ri(*CK**Ar;08w>8nAr#Hp=g*vQEZe58l002ovPDHLkV1mA2Sn&V= literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker.png b/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker.png new file mode 100644 index 0000000000000000000000000000000000000000..f638769226a28fdf0546e0428d3f824be5eb4169 GIT binary patch literal 624 zcmV-$0+0QPP)Kp2G&Gc=*H8!9S=%s9iC%ius@Fd;(~3Le3i ziXwgolXq|ms$fb=ULe@qq2kiQ8^ngvV54X?Xh#SMR;(<}41=^FzHk5iwSW@`&btW+ zf*>l#lgY$ks{ny}JRW=$1Z7AxlCWHWz`rGvYh$Z(T?tDC6!^pABUmLY6Hw3GjD#fu zMr9`we!m~bM@NPd+yYv-dgexzP=`xE51vRQ(C&2bH5y%$_2w;eiv^XKOUL8!7~DOuZO!BHIJ(^~d_Er^*Xrsfo*Ul-&3iL#xsJO5@3NV(e?)ScYzkZl*(GG)$rx) z3@(?;@VZ@aCIKz{Wj;r**OQyA)oLMmey)4na5&rvXA)R_2$g8J+X#olXf~S&1OmDn z4a>}#1xPM0E^uEeAsUI$+lj#Y{XQ3*S%4&)&7xANAfl2`!~Z=!VKf?