Skip to content

Commit

Permalink
Adds Store on Collide and Wand of the Locker (space-wizards#33710)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
keronshb authored Dec 4, 2024
1 parent 152cf33 commit 82528dc
Show file tree
Hide file tree
Showing 17 changed files with 284 additions and 3 deletions.
19 changes: 17 additions & 2 deletions Content.Shared/Lock/LockSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,24 @@ public bool TryLock(EntityUid uid, EntityUid user, LockComponent? lockComp = nul
});
}

_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;
Expand All @@ -141,7 +157,6 @@ public bool TryLock(EntityUid uid, EntityUid user, LockComponent? lockComp = nul

var ev = new LockToggledEvent(true);
RaiseLocalEvent(uid, ref ev, true);
return true;
}

/// <summary>
Expand Down
34 changes: 34 additions & 0 deletions Content.Shared/Storage/Components/StoreOnCollideComponent.cs
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,14 @@ public bool TryCloseStorage(EntityUid target)
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))
Expand Down
71 changes: 71 additions & 0 deletions Content.Shared/Storage/EntitySystems/StoreOnCollideSystem.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
2 changes: 2 additions & 0 deletions Resources/Locale/en-US/prototypes/access/accesses.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions Resources/Locale/en-US/store/spellbook-catalog.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions Resources/Prototypes/Access/wizard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- type: accessLevel
id: Wizard
name: id-card-access-level-wizard
13 changes: 13 additions & 0 deletions Resources/Prototypes/Catalog/spellbook_catalog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -810,3 +810,4 @@
- CentralCommand
- NuclearOperative
- SyndicateAgent
- Wizard
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
- CentralCommand
- NuclearOperative
- SyndicateAgent
- Wizard
privilegedIdSlot:
name: id-card-console-privileged-id
ejectSound: /Audio/Machines/id_swipe.ogg
Expand Down
36 changes: 36 additions & 0 deletions Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,20 @@
{
"name": "wand-inhand-right",
"directions": 4
},
{
"name": "locker"
},
{
"name": "locker-effect"
},
{
"name": "locker-inhand-right",
"directions": 4
},
{
"name": "locker-inhand-left",
"directions": 4
}
]
}
}

0 comments on commit 82528dc

Please sign in to comment.