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
This commit is contained in:
keronshb
2024-12-04 11:49:54 -05:00
committed by GitHub
parent 152cf3388b
commit 82528dce37
17 changed files with 284 additions and 3 deletions

View File

@@ -131,8 +131,24 @@ public sealed class LockSystem : EntitySystem
}); });
} }
_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); ("entityName", Identity.Name(uid, EntityManager))), uid, user);
}
_audio.PlayPredicted(lockComp.LockSound, uid, user); _audio.PlayPredicted(lockComp.LockSound, uid, user);
lockComp.Locked = true; lockComp.Locked = true;
@@ -141,7 +157,6 @@ public sealed class LockSystem : EntitySystem
var ev = new LockToggledEvent(true); var ev = new LockToggledEvent(true);
RaiseLocalEvent(uid, ref ev, true); RaiseLocalEvent(uid, ref ev, true);
return true;
} }
/// <summary> /// <summary>

View File

@@ -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
{
/// <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;
}

View File

@@ -344,6 +344,14 @@ public abstract class SharedEntityStorageSystem : EntitySystem
return true; 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) public bool CanOpen(EntityUid user, EntityUid target, bool silent = false, SharedEntityStorageComponent? component = null)
{ {
if (!ResolveStorage(target, ref component)) if (!ResolveStorage(target, ref component))

View File

@@ -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<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);
}
}

View File

@@ -42,3 +42,5 @@ id-card-access-level-nuclear-operative = Nuclear Operative
id-card-access-level-syndicate-agent = Syndicate Agent id-card-access-level-syndicate-agent = Syndicate Agent
id-card-access-level-central-command = Central Command id-card-access-level-central-command = Central Command
id-card-access-level-wizard = Wizard

View File

@@ -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-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-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 # Events
spellbook-event-summon-ghosts-name = Summon Ghosts spellbook-event-summon-ghosts-name = Summon Ghosts

View File

@@ -0,0 +1,3 @@
- type: accessLevel
id: Wizard
name: id-card-access-level-wizard

View File

@@ -118,6 +118,19 @@
- !type:ListingLimitedStockCondition - !type:ListingLimitedStockCondition
stock: 1 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 # Event
- type: listing - type: listing
id: SpellbookEventSummonGhosts id: SpellbookEventSummonGhosts

View File

@@ -810,3 +810,4 @@
- CentralCommand - CentralCommand
- NuclearOperative - NuclearOperative
- SyndicateAgent - SyndicateAgent
- Wizard

View File

@@ -119,6 +119,7 @@
- CentralCommand - CentralCommand
- NuclearOperative - NuclearOperative
- SyndicateAgent - SyndicateAgent
- Wizard
privilegedIdSlot: privilegedIdSlot:
name: id-card-console-privileged-id name: id-card-console-privileged-id
ejectSound: /Audio/Machines/id_swipe.ogg ejectSound: /Audio/Machines/id_swipe.ogg

View File

@@ -57,6 +57,42 @@
capacity: 5 capacity: 5
count: 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 - type: entity
name: magical wand of instant death name: magical wand of instant death
parent: WeaponWandBase parent: WeaponWandBase

View File

@@ -79,6 +79,86 @@
totalIntensity: 0.3 totalIntensity: 0.3
maxTileBreak: 0 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 - type: entity
id: ProjectilePolyboltBase id: ProjectilePolyboltBase
parent: BaseBullet parent: BaseBullet

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

View File

@@ -63,6 +63,20 @@
{ {
"name": "wand-inhand-right", "name": "wand-inhand-right",
"directions": 4 "directions": 4
},
{
"name": "locker"
},
{
"name": "locker-effect"
},
{
"name": "locker-inhand-right",
"directions": 4
},
{
"name": "locker-inhand-left",
"directions": 4
} }
] ]
} }