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:
@@ -131,8 +131,24 @@ public sealed class LockSystem : EntitySystem
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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",
|
_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>
|
||||||
|
|||||||
34
Content.Shared/Storage/Components/StoreOnCollideComponent.cs
Normal file
34
Content.Shared/Storage/Components/StoreOnCollideComponent.cs
Normal 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;
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
|||||||
71
Content.Shared/Storage/EntitySystems/StoreOnCollideSystem.cs
Normal file
71
Content.Shared/Storage/EntitySystems/StoreOnCollideSystem.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
3
Resources/Prototypes/Access/wizard.yml
Normal file
3
Resources/Prototypes/Access/wizard.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
- type: accessLevel
|
||||||
|
id: Wizard
|
||||||
|
name: id-card-access-level-wizard
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -810,3 +810,4 @@
|
|||||||
- CentralCommand
|
- CentralCommand
|
||||||
- NuclearOperative
|
- NuclearOperative
|
||||||
- SyndicateAgent
|
- SyndicateAgent
|
||||||
|
- Wizard
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 |
@@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user