add: funny keys that can lock doors (#113)
110
Content.Server/_White/Keyhole/KeyholeSystem.cs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
using Content.Shared._White.Keyhole.Components;
|
||||||
|
using Content.Shared._White.Keyhole;
|
||||||
|
using Content.Shared.DoAfter;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Audio.Systems;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server._White.Keyhole;
|
||||||
|
|
||||||
|
public sealed partial class KeyholeSystem : EntitySystem
|
||||||
|
{
|
||||||
|
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<KeyComponent, ComponentInit>(OnKeyInit);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<KeyComponent, AfterInteractEvent>(OnKeyInsert);
|
||||||
|
SubscribeLocalEvent<KeyholeComponent, KeyInsertDoAfterEvent>(OnDoAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnKeyInit(EntityUid uid, KeyComponent component, ComponentInit ev)
|
||||||
|
{
|
||||||
|
component.FormId = _random.Next(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnKeyInsert(EntityUid uid, KeyComponent component, AfterInteractEvent ev)
|
||||||
|
{
|
||||||
|
if (TryComp<KeyformComponent>(ev.Target, out var keyformComponent))
|
||||||
|
OnKeyInsertForm(uid, component, keyformComponent, ev);
|
||||||
|
|
||||||
|
if (!TryComp<KeyholeComponent>(ev.Target, out var keyholeComponent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
keyholeComponent.FormId ??= component.FormId;
|
||||||
|
|
||||||
|
if (!CanLock(keyholeComponent.Owner, keyholeComponent, component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var doAfterEventArgs =
|
||||||
|
new DoAfterArgs(EntityManager, ev.User, keyholeComponent.Delay, new KeyInsertDoAfterEvent(), ev.Target, ev.Used)
|
||||||
|
{
|
||||||
|
BreakOnTargetMove = true,
|
||||||
|
BreakOnUserMove = true,
|
||||||
|
BreakOnDamage = true
|
||||||
|
};
|
||||||
|
_doAfter.TryStartDoAfter(doAfterEventArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanLock(EntityUid uid, KeyholeComponent keyholeComponent, KeyComponent keyComponent)
|
||||||
|
{
|
||||||
|
var can = TryComp<DoorComponent>(uid, out var doorComponent) &&
|
||||||
|
keyholeComponent.FormId == keyComponent.FormId &&
|
||||||
|
doorComponent.State == DoorState.Closed;
|
||||||
|
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoAfter(EntityUid uid, KeyholeComponent component, KeyInsertDoAfterEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || args.Cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Lock(uid, component, args.User);
|
||||||
|
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Lock(EntityUid uid, KeyholeComponent component, EntityUid user)
|
||||||
|
{
|
||||||
|
var sound = component.Locked ? component.UnlockSound : component.LockSound;
|
||||||
|
var message = Loc.GetString(component.Locked ? "key-unlock-message" : "key-lock-message", ("name", user), ("door", uid));
|
||||||
|
|
||||||
|
var audioParams = new AudioParams().WithVolume(-5f);
|
||||||
|
|
||||||
|
_audio.PlayPvs(sound, user, audioParams);
|
||||||
|
_popupSystem.PopupEntity(message, uid);
|
||||||
|
|
||||||
|
component.Locked = !component.Locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnKeyInsertForm(EntityUid uid, KeyComponent keyComponent, KeyformComponent keyformComponent, AfterInteractEvent args)
|
||||||
|
{
|
||||||
|
if (!keyformComponent.IsUsed)
|
||||||
|
{
|
||||||
|
keyformComponent.FormId ??= keyComponent.FormId;
|
||||||
|
_appearance.SetData(keyformComponent.Owner, KeyformVisuals.IsUsed, true);
|
||||||
|
|
||||||
|
_audio.PlayPvs(keyformComponent.PressSound, uid);
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("key-pressed-in-keyform-message-first", ("user", args.User), ("key", uid)), uid);
|
||||||
|
|
||||||
|
keyformComponent.IsUsed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
keyComponent.FormId = keyformComponent.FormId;
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("key-pressed-in-keyform-message", ("user", args.User), ("key", uid)), uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Shared._White.Keyhole.Components;
|
||||||
using Content.Shared.Access.Components;
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.Access.Systems;
|
using Content.Shared.Access.Systems;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
@@ -6,6 +7,7 @@ using Content.Shared.Doors.Components;
|
|||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Physics;
|
using Content.Shared.Physics;
|
||||||
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Prying.Components;
|
using Content.Shared.Prying.Components;
|
||||||
using Content.Shared.Stunnable;
|
using Content.Shared.Stunnable;
|
||||||
using Content.Shared.Tag;
|
using Content.Shared.Tag;
|
||||||
@@ -30,6 +32,7 @@ public abstract class SharedDoorSystem : EntitySystem
|
|||||||
[Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!;
|
[Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!;
|
||||||
[Dependency] private readonly OccluderSystem _occluder = default!;
|
[Dependency] private readonly OccluderSystem _occluder = default!;
|
||||||
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; //WD edit
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A body must have an intersection percentage larger than this in order to be considered as colliding with a
|
/// A body must have an intersection percentage larger than this in order to be considered as colliding with a
|
||||||
@@ -211,6 +214,19 @@ public abstract class SharedDoorSystem : EntitySystem
|
|||||||
if (!Resolve(uid, ref door))
|
if (!Resolve(uid, ref door))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// WD edit start
|
||||||
|
if (TryComp<KeyholeComponent>(uid, out var keyholeComponent))
|
||||||
|
{
|
||||||
|
if (keyholeComponent.Locked)
|
||||||
|
{
|
||||||
|
PlaySound(uid, keyholeComponent.DoorLockedSound, AudioParams.Default.WithVolume(-3), uid, true);
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("door-locked-via-key", ("door", uid)), uid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// WD edit end
|
||||||
|
|
||||||
if (door.State is DoorState.Closed or DoorState.Denying)
|
if (door.State is DoorState.Closed or DoorState.Denying)
|
||||||
{
|
{
|
||||||
return TryOpen(uid, door, user, predicted, quiet: door.State == DoorState.Denying);
|
return TryOpen(uid, door, user, predicted, quiet: door.State == DoorState.Denying);
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Content.Shared._White.Keyhole.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public partial class KeyBaseComponent : Component
|
||||||
|
{
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField]
|
||||||
|
public int? FormId;
|
||||||
|
}
|
||||||
7
Content.Shared/_White/Keyhole/Components/KeyComponent.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Content.Shared._White.Keyhole.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class KeyComponent : KeyBaseComponent
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
15
Content.Shared/_White/Keyhole/Components/KeyFormComponent.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Robust.Shared.Audio;
|
||||||
|
|
||||||
|
namespace Content.Shared._White.Keyhole.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class KeyformComponent : KeyBaseComponent
|
||||||
|
{
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField]
|
||||||
|
public bool IsUsed;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField]
|
||||||
|
public SoundSpecifier PressSound = new SoundPathSpecifier("/Audio/White/Object/Tools/Form/press.ogg");
|
||||||
|
}
|
||||||
27
Content.Shared/_White/Keyhole/Components/KeyholeComponent.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using Robust.Shared.Audio;
|
||||||
|
|
||||||
|
namespace Content.Shared._White.Keyhole.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class KeyholeComponent: KeyBaseComponent
|
||||||
|
{
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField]
|
||||||
|
public bool Locked = false;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField]
|
||||||
|
public float Delay = 1f;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField]
|
||||||
|
public SoundSpecifier DoorLockedSound = new SoundPathSpecifier("/Audio/White/Object/Tools/Keyhole/locked.ogg");
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField]
|
||||||
|
public SoundSpecifier UnlockSound = new SoundPathSpecifier("/Audio/White/Object/Tools/Keyhole/unlock.ogg");
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField]
|
||||||
|
public SoundSpecifier LockSound = new SoundPathSpecifier("/Audio/White/Object/Tools/Keyhole/lock.ogg");
|
||||||
|
}
|
||||||
8
Content.Shared/_White/Keyhole/KeyInsertEvent.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Content.Shared.DoAfter;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared._White.Keyhole;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed partial class KeyInsertDoAfterEvent : SimpleDoAfterEvent {}
|
||||||
|
|
||||||
9
Content.Shared/_White/Keyhole/KeyformVisuals.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared._White.Keyhole;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum KeyformVisuals : byte
|
||||||
|
{
|
||||||
|
IsUsed
|
||||||
|
}
|
||||||
BIN
Resources/Audio/White/Object/Tools/Form/hit.ogg
Normal file
BIN
Resources/Audio/White/Object/Tools/Form/pickup.ogg
Normal file
BIN
Resources/Audio/White/Object/Tools/Form/press.ogg
Normal file
BIN
Resources/Audio/White/Object/Tools/Key/drop.ogg
Normal file
BIN
Resources/Audio/White/Object/Tools/Key/pickup.ogg
Normal file
BIN
Resources/Audio/White/Object/Tools/Keyhole/lock.ogg
Normal file
BIN
Resources/Audio/White/Object/Tools/Keyhole/locked.ogg
Normal file
BIN
Resources/Audio/White/Object/Tools/Keyhole/unlock.ogg
Normal file
5
Resources/Locale/ru-RU/White/object/tools/key.ftl
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ent-KeyMetal = металлический ключ
|
||||||
|
.desc = заостренный кусок металла
|
||||||
|
|
||||||
|
key-lock-message = {$name} проворачивает ключ в замке {$door} и закрывает {POSS-ADJ($door)}
|
||||||
|
key-unlock-message = {$name} проворачивает ключ в замке {$door} и открывает {POSS-ADJ($door)}
|
||||||
5
Resources/Locale/ru-RU/white/object/tools/keyform.ftl
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ent-KeyForm = пластиковая форма
|
||||||
|
.desc = мягкий кусок пластика
|
||||||
|
|
||||||
|
key-pressed-in-keyform-message-first = {$user} вдавливает {$key} в пластик, создавая в нем форму ключа
|
||||||
|
key-pressed-in-keyform-message = {$user} вставляет {$key} в форму, заставляя его скопировать уникальный узор
|
||||||
1
Resources/Locale/ru-RU/white/object/tools/keyhole.ftl
Normal file
@@ -0,0 +1 @@
|
|||||||
|
door-locked-via-key = {$door} закрыта
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
mode: NoSprite
|
mode: NoSprite
|
||||||
- type: Occluder
|
- type: Occluder
|
||||||
- type: ReflectAspectMark
|
- type: ReflectAspectMark
|
||||||
|
- type: Keyhole
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseMaterialDoor
|
parent: BaseMaterialDoor
|
||||||
|
|||||||
54
Resources/Prototypes/White/Objects/Tools/crafts.yml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
- type: constructionGraph
|
||||||
|
id: KeyFormGraph
|
||||||
|
start: start
|
||||||
|
graph:
|
||||||
|
- node: start
|
||||||
|
edges:
|
||||||
|
- to: KeyFormNode
|
||||||
|
steps:
|
||||||
|
- material: Plastic
|
||||||
|
amount: 1
|
||||||
|
doAfter: 2
|
||||||
|
- node: KeyFormNode
|
||||||
|
entity: KeyForm
|
||||||
|
|
||||||
|
- type: construction
|
||||||
|
name: ent-KeyForm
|
||||||
|
id: KeyForm
|
||||||
|
graph: KeyFormGraph
|
||||||
|
startNode: start
|
||||||
|
targetNode: KeyFormNode
|
||||||
|
category: construction-category-tools
|
||||||
|
objectType: Item
|
||||||
|
description: ent-KeyForm.desc
|
||||||
|
icon:
|
||||||
|
sprite: White/Objects/Tools/form.rsi
|
||||||
|
state: empty
|
||||||
|
|
||||||
|
|
||||||
|
- type: constructionGraph
|
||||||
|
id: KeyGraph
|
||||||
|
start: start
|
||||||
|
graph:
|
||||||
|
- node: start
|
||||||
|
edges:
|
||||||
|
- to: KeyNode
|
||||||
|
steps:
|
||||||
|
- material: Steel
|
||||||
|
amount: 1
|
||||||
|
doAfter: 2
|
||||||
|
- node: KeyNode
|
||||||
|
entity: KeyMetal
|
||||||
|
|
||||||
|
- type: construction
|
||||||
|
name: ent-KeyMetal
|
||||||
|
id: KeyMetal
|
||||||
|
graph: KeyGraph
|
||||||
|
startNode: start
|
||||||
|
targetNode: KeyNode
|
||||||
|
category: construction-category-tools
|
||||||
|
objectType: Item
|
||||||
|
description: ent-KeyMetal.desc
|
||||||
|
icon:
|
||||||
|
sprite: White/Objects/Tools/key.rsi
|
||||||
|
state: icon
|
||||||
34
Resources/Prototypes/White/Objects/Tools/form.yml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
- type: entity
|
||||||
|
name: form
|
||||||
|
parent: BaseItem
|
||||||
|
id: KeyForm
|
||||||
|
description: Makeshift piece of plastic
|
||||||
|
components:
|
||||||
|
- type: EmitSoundOnPickup
|
||||||
|
sound:
|
||||||
|
path: /Audio/White/Object/Tools/Form/pickup.ogg
|
||||||
|
- type: EmitSoundOnLand
|
||||||
|
sound:
|
||||||
|
path: /Audio/White/Object/Tools/Form/hit.ogg
|
||||||
|
- type: Sprite
|
||||||
|
sprite: White/Objects/Tools/form.rsi
|
||||||
|
state: empty
|
||||||
|
- type: Item
|
||||||
|
sprite: White/Objects/Tools/form.rsi
|
||||||
|
storedRotation: -180
|
||||||
|
- type: PhysicalComposition
|
||||||
|
materialComposition:
|
||||||
|
Steel: 100
|
||||||
|
- type: StaticPrice
|
||||||
|
price: 10
|
||||||
|
- type: Keyform
|
||||||
|
- type: GenericVisualizer
|
||||||
|
visuals:
|
||||||
|
enum.KeyformVisuals.IsUsed:
|
||||||
|
base:
|
||||||
|
True: { state: withkey }
|
||||||
|
False: { state: empty }
|
||||||
|
- type: Appearance
|
||||||
|
- type: Construction
|
||||||
|
graph: KeyFormGraph
|
||||||
|
node: KeyFormNode
|
||||||
38
Resources/Prototypes/White/Objects/Tools/key.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
- type: entity
|
||||||
|
name: key
|
||||||
|
parent: BaseItem
|
||||||
|
id: KeyMetal
|
||||||
|
description: Makeshift sharp piece of metal
|
||||||
|
components:
|
||||||
|
- type: EmitSoundOnPickup
|
||||||
|
sound:
|
||||||
|
path: /Audio/White/Object/Tools/Key/pickup.ogg
|
||||||
|
- type: EmitSoundOnDrop
|
||||||
|
sound:
|
||||||
|
path: /Audio/White/Object/Tools/Key/drop.ogg
|
||||||
|
- type: EmitSoundOnLand
|
||||||
|
sound:
|
||||||
|
path: /Audio/White/Object/Tools/Key/drop.ogg
|
||||||
|
- type: Sprite
|
||||||
|
sprite: White/Objects/Tools/key.rsi
|
||||||
|
state: icon
|
||||||
|
- type: Item
|
||||||
|
sprite: White/Objects/Tools/key.rsi
|
||||||
|
storedRotation: -180
|
||||||
|
- type: MeleeWeapon
|
||||||
|
wideAnimationRotation: -180
|
||||||
|
attackRate: 1
|
||||||
|
damage:
|
||||||
|
types:
|
||||||
|
Piercing: 1
|
||||||
|
soundHit:
|
||||||
|
path: "/Audio/Weapons/bladeslice.ogg"
|
||||||
|
- type: PhysicalComposition
|
||||||
|
materialComposition:
|
||||||
|
Steel: 100
|
||||||
|
- type: StaticPrice
|
||||||
|
price: 10
|
||||||
|
- type: Key
|
||||||
|
- type: Construction
|
||||||
|
graph: KeyGraph
|
||||||
|
node: KeyNode
|
||||||
BIN
Resources/Textures/White/Objects/Tools/form.rsi/empty.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
Resources/Textures/White/Objects/Tools/form.rsi/inhand-left.png
Normal file
|
After Width: | Height: | Size: 298 B |
BIN
Resources/Textures/White/Objects/Tools/form.rsi/inhand-right.png
Normal file
|
After Width: | Height: | Size: 302 B |
25
Resources/Textures/White/Objects/Tools/form.rsi/meta.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "https://github.com/frosty-dev/white",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "empty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "withkey"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-left",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-right",
|
||||||
|
"directions": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/White/Objects/Tools/form.rsi/withkey.png
Normal file
|
After Width: | Height: | Size: 541 B |
BIN
Resources/Textures/White/Objects/Tools/key.rsi/icon.png
Normal file
|
After Width: | Height: | Size: 300 B |
BIN
Resources/Textures/White/Objects/Tools/key.rsi/inhand-left.png
Normal file
|
After Width: | Height: | Size: 204 B |
BIN
Resources/Textures/White/Objects/Tools/key.rsi/inhand-right.png
Normal file
|
After Width: | Height: | Size: 198 B |
22
Resources/Textures/White/Objects/Tools/key.rsi/meta.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "https://github.com/frosty-dev/white",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-left",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-right",
|
||||||
|
"directions": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||