Retractable items system + Arm Blade action (#38150)
This commit is contained in:
@@ -71,7 +71,9 @@ public sealed class GlueSystem : SharedGlueSystem
|
|||||||
private bool TryGlue(Entity<GlueComponent> entity, EntityUid target, EntityUid actor)
|
private bool TryGlue(Entity<GlueComponent> entity, EntityUid target, EntityUid actor)
|
||||||
{
|
{
|
||||||
// if item is glued then don't apply glue again so it can be removed for reasonable time
|
// if item is glued then don't apply glue again so it can be removed for reasonable time
|
||||||
if (HasComp<GluedComponent>(target) || !HasComp<ItemComponent>(target))
|
// If glue is applied to an unremoveable item, the component will disappear after the duration.
|
||||||
|
// This effecitvely means any unremoveable item could be removed with a bottle of glue.
|
||||||
|
if (HasComp<GluedComponent>(target) || !HasComp<ItemComponent>(target) || HasComp<UnremoveableComponent>(target))
|
||||||
{
|
{
|
||||||
_popup.PopupEntity(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
|
_popup.PopupEntity(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -124,6 +124,27 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to pick up an entity into a hand, forcing to drop an item if its not free.
|
||||||
|
/// By default it does check if it's possible to drop items.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryForcePickup(
|
||||||
|
EntityUid uid,
|
||||||
|
EntityUid entity,
|
||||||
|
Hand hand,
|
||||||
|
bool checkActionBlocker = true,
|
||||||
|
bool animate = true,
|
||||||
|
HandsComponent? handsComp = null,
|
||||||
|
ItemComponent? item = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref handsComp, false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
TryDrop(uid, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp);
|
||||||
|
|
||||||
|
return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to pick up an entity into any hand, forcing to drop an item if there are no free hands
|
/// Tries to pick up an entity into any hand, forcing to drop an item if there are no free hands
|
||||||
/// By default it does check if it's possible to drop items
|
/// By default it does check if it's possible to drop items
|
||||||
|
|||||||
@@ -215,6 +215,9 @@ namespace Content.Shared.Interaction
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnRemoveAttempt(EntityUid uid, UnremoveableComponent item, ContainerGettingRemovedAttemptEvent args)
|
private void OnRemoveAttempt(EntityUid uid, UnremoveableComponent item, ContainerGettingRemovedAttemptEvent args)
|
||||||
{
|
{
|
||||||
|
// don't prevent the server state for the container from being applied to the client correctly
|
||||||
|
// otherwise this will cause an error if the client predicts adding UnremoveableComponent
|
||||||
|
if (!_gameTiming.ApplyingState)
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.RetractableItemAction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Component used as a marker for items summoned by the RetractableItemAction system.
|
||||||
|
/// Used for keeping track of items summoned by said action.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(RetractableItemActionSystem))]
|
||||||
|
public sealed partial class ActionRetractableItemComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The action that marked this item.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public EntityUid? SummoningAction;
|
||||||
|
}
|
||||||
9
Content.Shared/RetractableItemAction/ItemActionEvents.cs
Normal file
9
Content.Shared/RetractableItemAction/ItemActionEvents.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Content.Shared.Actions;
|
||||||
|
|
||||||
|
namespace Content.Shared.RetractableItemAction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when using the RetractableItem action.
|
||||||
|
/// </summary>
|
||||||
|
[ByRefEvent]
|
||||||
|
public sealed partial class OnRetractableItemActionEvent : InstantActionEvent;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.RetractableItemAction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for storing an unremovable item within an action and summoning it into your hand on use.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(RetractableItemActionSystem))]
|
||||||
|
public sealed partial class RetractableItemActionComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The item that will appear be spawned by the action.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public EntProtoId SpawnedPrototype;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound collection to play when the item is summoned.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public SoundCollectionSpecifier? SummonSounds;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound collection to play when the summoned item is retracted back into the action.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public SoundCollectionSpecifier? RetractSounds;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The item managed by the action. Will be summoned and hidden as the action is used.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public EntityUid? ActionItemUid;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The container ID used to store the item.
|
||||||
|
/// </summary>
|
||||||
|
public const string ContainerId = "item-action-item-container";
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using Content.Shared.Actions;
|
||||||
|
using Content.Shared.Hands.EntitySystems;
|
||||||
|
using Content.Shared.Interaction.Components;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Robust.Shared.Audio.Systems;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
|
||||||
|
namespace Content.Shared.RetractableItemAction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System for handling retractable items, such as armblades.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RetractableItemActionSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||||
|
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popups = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<RetractableItemActionComponent, MapInitEvent>(OnActionInit);
|
||||||
|
SubscribeLocalEvent<RetractableItemActionComponent, OnRetractableItemActionEvent>(OnRetractableItemAction);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<ActionRetractableItemComponent, ComponentShutdown>(OnActionSummonedShutdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnActionInit(Entity<RetractableItemActionComponent> ent, ref MapInitEvent args)
|
||||||
|
{
|
||||||
|
_containers.EnsureContainer<Container>(ent, RetractableItemActionComponent.ContainerId);
|
||||||
|
|
||||||
|
PopulateActionItem(ent.Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRetractableItemAction(Entity<RetractableItemActionComponent> ent, ref OnRetractableItemActionEvent args)
|
||||||
|
{
|
||||||
|
if (_hands.GetActiveHand(args.Performer) is not { } userHand)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_actions.GetAction(ent.Owner) is not { } action)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (action.Comp.AttachedEntity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ent.Comp.ActionItemUid == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Don't allow to summon an item if holding an unremoveable item unless that item is summoned by the action.
|
||||||
|
if (userHand.HeldEntity != null && !_hands.IsHolding(args.Performer, ent.Comp.ActionItemUid) && !_hands.CanDropHeld(args.Performer, userHand, false))
|
||||||
|
{
|
||||||
|
_popups.PopupClient(Loc.GetString("retractable-item-hand-cannot-drop"), args.Performer, args.Performer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_hands.IsHolding(args.Performer, ent.Comp.ActionItemUid))
|
||||||
|
{
|
||||||
|
RemComp<UnremoveableComponent>(ent.Comp.ActionItemUid.Value);
|
||||||
|
var container = _containers.GetContainer(ent, RetractableItemActionComponent.ContainerId);
|
||||||
|
_containers.Insert(ent.Comp.ActionItemUid.Value, container);
|
||||||
|
_audio.PlayPredicted(ent.Comp.RetractSounds, action.Comp.AttachedEntity.Value, action.Comp.AttachedEntity.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_hands.TryForcePickup(args.Performer, ent.Comp.ActionItemUid.Value, userHand, checkActionBlocker: false);
|
||||||
|
_audio.PlayPredicted(ent.Comp.SummonSounds, action.Comp.AttachedEntity.Value, action.Comp.AttachedEntity.Value);
|
||||||
|
EnsureComp<UnremoveableComponent>(ent.Comp.ActionItemUid.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnActionSummonedShutdown(Entity<ActionRetractableItemComponent> ent, ref ComponentShutdown args)
|
||||||
|
{
|
||||||
|
if (_actions.GetAction(ent.Comp.SummoningAction) is not { } action)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<RetractableItemActionComponent>(action, out var retract) || retract.ActionItemUid != ent.Owner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If the item is somehow destroyed, re-add it to the action.
|
||||||
|
PopulateActionItem(action.Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateActionItem(Entity<RetractableItemActionComponent?> ent)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent.Owner, ref ent.Comp, false) || TerminatingOrDeleted(ent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!PredictedTrySpawnInContainer(ent.Comp.SpawnedPrototype, ent.Owner, RetractableItemActionComponent.ContainerId, out var summoned))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ent.Comp.ActionItemUid = summoned.Value;
|
||||||
|
|
||||||
|
// Mark the unremovable item so it can be added back into the action.
|
||||||
|
var summonedComp = AddComp<ActionRetractableItemComponent>(summoned.Value);
|
||||||
|
summonedComp.SummoningAction = ent.Owner;
|
||||||
|
Dirty(summoned.Value, summonedComp);
|
||||||
|
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
retractable-item-hand-cannot-drop = Your hand is already occupied.
|
||||||
22
Resources/Prototypes/Actions/changeling.yml
Normal file
22
Resources/Prototypes/Actions/changeling.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
- type: entity
|
||||||
|
parent: BaseAction
|
||||||
|
id: ActionRetractableItemArmBlade
|
||||||
|
name: Arm Blade
|
||||||
|
description: Shed your flesh and reform it into a fleshy blade.
|
||||||
|
components:
|
||||||
|
- type: Action
|
||||||
|
useDelay: 2
|
||||||
|
raiseOnAction: true
|
||||||
|
itemIconStyle: BigAction
|
||||||
|
icon:
|
||||||
|
sprite: Interface/Actions/changeling.rsi
|
||||||
|
state: armblade
|
||||||
|
- type: InstantAction
|
||||||
|
event: !type:OnRetractableItemActionEvent
|
||||||
|
- type: RetractableItemAction
|
||||||
|
spawnedPrototype: ArmBlade
|
||||||
|
summonSounds:
|
||||||
|
collection: gib # Placeholder
|
||||||
|
retractSounds:
|
||||||
|
collection: gib # Placeholder
|
||||||
|
|
||||||
@@ -12,12 +12,13 @@
|
|||||||
state: icon
|
state: icon
|
||||||
- type: MeleeWeapon
|
- type: MeleeWeapon
|
||||||
wideAnimationRotation: 90
|
wideAnimationRotation: 90
|
||||||
attackRate: 0.75
|
attackRate: 1
|
||||||
damage:
|
damage:
|
||||||
types:
|
types:
|
||||||
Slash: 25
|
Slash: 10
|
||||||
Piercing: 15
|
Piercing: 10
|
||||||
- type: Item
|
- type: Item
|
||||||
size: Normal
|
size: Normal
|
||||||
sprite: Objects/Weapons/Melee/armblade.rsi
|
sprite: Objects/Weapons/Melee/armblade.rsi
|
||||||
- type: Prying
|
- type: Prying
|
||||||
|
pryPowered: true
|
||||||
|
|||||||
BIN
Resources/Textures/Interface/Actions/changeling.rsi/armblade.png
Normal file
BIN
Resources/Textures/Interface/Actions/changeling.rsi/armblade.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 943 B |
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"copyright": "Created by TiniestShark (github)",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "armblade"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user