});
}
- _sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-lock-success",
+ Lock(uid, user, lockComp);
+ return true;
+ }
+
+ /// <summary>
+ /// Forces a given entity to be locked, does not activate a do-after.
+ /// </summary>
+ 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;
var ev = new LockToggledEvent(true);
RaiseLocalEvent(uid, ref ev, true);
- return true;
}
/// <summary>
--- /dev/null
+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
+{
+ /// <summary>
+ /// Entities that are allowed in the storage on collide
+ /// </summary>
+ [DataField]
+ public EntityWhitelist? Whitelist;
+
+ /// <summary>
+ /// Should this storage lock on collide, provided they have a lock component?
+ /// </summary>
+ [DataField]
+ public bool LockOnCollide;
+
+ /// <summary>
+ /// Should the behavior be disabled when the storage is first opened?
+ /// </summary>
+ [DataField]
+ public bool DisableWhenFirstOpened;
+
+ /// <summary>
+ /// If the behavior is disabled or not
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Disabled;
+}
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))
--- /dev/null
+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<StoreOnCollideComponent, StartCollideEvent>(OnCollide);
+ SubscribeLocalEvent<StoreOnCollideComponent, StorageAfterOpenEvent>(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<StoreOnCollideComponent> ent, ref StartCollideEvent args)
+ {
+ TryStoreTarget(ent, args.OtherEntity);
+
+ TryLockStorage(ent);
+ }
+
+ private void AfterOpen(Entity<StoreOnCollideComponent> ent, ref StorageAfterOpenEvent args)
+ {
+ var comp = ent.Comp;
+
+ if (comp is { DisableWhenFirstOpened: true, Disabled: false })
+ comp.Disabled = true;
+ }
+
+ private void TryStoreTarget(Entity<StoreOnCollideComponent> 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<StoreOnCollideComponent> 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);
+ }
+}
id-card-access-level-syndicate-agent = Syndicate Agent
id-card-access-level-central-command = Central Command
+
+id-card-access-level-wizard = Wizard
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
--- /dev/null
+- type: accessLevel
+ id: Wizard
+ name: id-card-access-level-wizard
- !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
- CentralCommand
- NuclearOperative
- SyndicateAgent
+ - Wizard
- CentralCommand
- NuclearOperative
- SyndicateAgent
+ - Wizard
privilegedIdSlot:
name: id-card-console-privileged-id
ejectSound: /Audio/Machines/id_swipe.ogg
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
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
{
"name": "wand-inhand-right",
"directions": 4
+ },
+ {
+ "name": "locker"
+ },
+ {
+ "name": "locker-effect"
+ },
+ {
+ "name": "locker-inhand-right",
+ "directions": 4
+ },
+ {
+ "name": "locker-inhand-left",
+ "directions": 4
}
]
-}
\ No newline at end of file
+}