Adds disarm action (#2950)

* Adds disarming

* Disarm acts

* yaml

* much better icon for disarm

* Apply Remie's suggestions, improve code!
This commit is contained in:
Vera Aguilera Puerto
2021-01-09 20:31:34 +01:00
committed by GitHub
parent 1fe25049a3
commit d81a5faac4
12 changed files with 247 additions and 2 deletions

View File

@@ -28,6 +28,7 @@ namespace Content.Client.GameObjects.EntitySystems
public override void Initialize() public override void Initialize()
{ {
SubscribeNetworkEvent<PlayMeleeWeaponAnimationMessage>(PlayWeaponArc); SubscribeNetworkEvent<PlayMeleeWeaponAnimationMessage>(PlayWeaponArc);
SubscribeNetworkEvent<PlayLungeAnimationMessage>(PlayLunge);
} }
public override void FrameUpdate(float frameTime) public override void FrameUpdate(float frameTime)
@@ -106,5 +107,13 @@ namespace Content.Client.GameObjects.EntitySystems
}); });
} }
} }
private void PlayLunge(PlayLungeAnimationMessage msg)
{
EntityManager
.GetEntity(msg.Source)
.EnsureComponent<MeleeLungeComponent>()
.SetData(msg.Angle);
}
} }
} }

View File

@@ -0,0 +1,89 @@
#nullable enable
using System;
using System.Linq;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Pulling;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameObjects;
using Content.Server.Utility;
using Content.Shared.Actions;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Pulling;
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Content.Shared.Interfaces;
using Content.Shared.Utility;
using JetBrains.Annotations;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
namespace Content.Server.Actions
{
[UsedImplicitly]
public class DisarmAction : ITargetEntityAction
{
private float _failProb;
private float _pushProb;
private float _cooldown;
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _failProb, "failProb", 0.4f);
serializer.DataField(ref _pushProb, "pushProb", 0.4f);
serializer.DataField(ref _cooldown, "cooldown", 1.5f);
}
public void DoTargetEntityAction(TargetEntityActionEventArgs args)
{
var disarmedActs = args.Target.GetAllComponents<IDisarmedAct>().ToArray();
if (disarmedActs.Length == 0 || !args.Performer.InRangeUnobstructed(args.Target)) return;
if (!args.Performer.TryGetComponent<SharedActionsComponent>(out var actions)) return;
if (args.Target == args.Performer || !args.Performer.CanAttack()) return;
var random = IoCManager.Resolve<IRobustRandom>();
var audio = EntitySystem.Get<AudioSystem>();
var system = EntitySystem.Get<MeleeWeaponSystem>();
var angle = new Angle(args.Target.Transform.MapPosition.Position - args.Performer.Transform.MapPosition.Position);
actions.Cooldown(ActionType.Disarm, Cooldowns.SecondsFromNow(_cooldown));
if (random.Prob(_failProb))
{
audio.PlayFromEntity("/Audio/Weapons/punchmiss.ogg", args.Performer,
AudioHelpers.WithVariation(0.025f));
args.Performer.PopupMessageOtherClients(Loc.GetString("{0} fails to disarm {1}!", args.Performer.Name, args.Target.Name));
args.Performer.PopupMessageCursor(Loc.GetString("You fail to disarm {0}!", args.Target.Name));
system.SendLunge(angle, args.Performer);
return;
}
system.SendAnimation("disarm", angle, args.Performer, args.Performer, new []{ args.Target });
var eventArgs = new DisarmedActEventArgs() {Target = args.Target, Source = args.Performer, PushProbability = _pushProb};
// Sort by priority.
Array.Sort(disarmedActs, (a, b) => a.Priority.CompareTo(b.Priority));
foreach (var disarmedAct in disarmedActs)
{
if (disarmedAct.Disarmed(eventArgs))
return;
}
audio.PlayFromEntity("/Audio/Effects/thudswoosh.ogg", args.Performer,
AudioHelpers.WithVariation(0.025f));
}
}
}

View File

@@ -4,22 +4,31 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Pulling;
using Content.Server.GameObjects.EntitySystems.Click; using Content.Server.GameObjects.EntitySystems.Click;
using Content.Server.Interfaces.GameObjects;
using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Body.Part; using Content.Shared.GameObjects.Components.Body.Part;
using Content.Shared.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Items;
using Content.Shared.GameObjects.Components.Pulling;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.EntitySystems.ActionBlocker; using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Content.Shared.Interfaces;
using Content.Shared.Physics.Pull; using Content.Shared.Physics.Pull;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.EntitySystemMessages; using Robust.Server.GameObjects.EntitySystemMessages;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log; using Robust.Shared.Log;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Players; using Robust.Shared.Players;
@@ -32,7 +41,7 @@ namespace Content.Server.GameObjects.Components.GUI
[ComponentReference(typeof(IHandsComponent))] [ComponentReference(typeof(IHandsComponent))]
[ComponentReference(typeof(ISharedHandsComponent))] [ComponentReference(typeof(ISharedHandsComponent))]
[ComponentReference(typeof(SharedHandsComponent))] [ComponentReference(typeof(SharedHandsComponent))]
public class HandsComponent : SharedHandsComponent, IHandsComponent, IBodyPartAdded, IBodyPartRemoved public class HandsComponent : SharedHandsComponent, IHandsComponent, IBodyPartAdded, IBodyPartRemoved, IDisarmedAct
{ {
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
@@ -718,6 +727,43 @@ namespace Content.Server.GameObjects.Components.GUI
RemoveHand(args.Slot); RemoveHand(args.Slot);
} }
bool IDisarmedAct.Disarmed(DisarmedActEventArgs eventArgs)
{
if (BreakPulls())
return false;
var source = eventArgs.Source;
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/thudswoosh.ogg", source,
AudioHelpers.WithVariation(0.025f));
if (ActiveHand != null && Drop(ActiveHand, false))
{
source.PopupMessageOtherClients(Loc.GetString("{0} disarms {1}!", source.Name, eventArgs.Target.Name));
source.PopupMessageCursor(Loc.GetString("You disarm {0}!", eventArgs.Target.Name));
}
else
{
source.PopupMessageOtherClients(Loc.GetString("{0} shoves {1}!", source.Name, eventArgs.Target.Name));
source.PopupMessageCursor(Loc.GetString("You shove {0}!", eventArgs.Target.Name));
}
return true;
}
// We want this to be the last disarm act to run.
int IDisarmedAct.Priority => int.MaxValue;
private bool BreakPulls()
{
// What is this API??
if (!Owner.TryGetComponent(out SharedPullerComponent? puller)
|| puller.Pulling == null || !puller.Pulling.TryGetComponent(out PullableComponent? pullable))
return false;
return pullable.TryStopPull();
}
} }
public class Hand : IDisposable public class Hand : IDisposable

View File

@@ -1,17 +1,23 @@
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameObjects;
using Content.Server.Utility;
using Content.Shared.Alert; using Content.Shared.Alert;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Movement; using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using NFluidsynth; using NFluidsynth;
using Robust.Server.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Timers; using Robust.Shared.GameObjects.Components.Timers;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.Interfaces.Timing; using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Random;
using Robust.Shared.Timers; using Robust.Shared.Timers;
using Logger = Robust.Shared.Log.Logger; using Logger = Robust.Shared.Log.Logger;
@@ -19,7 +25,7 @@ namespace Content.Server.GameObjects.Components.Mobs
{ {
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(SharedStunnableComponent))] [ComponentReference(typeof(SharedStunnableComponent))]
public class StunnableComponent : SharedStunnableComponent public class StunnableComponent : SharedStunnableComponent, IDisarmedAct
{ {
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
@@ -121,5 +127,23 @@ namespace Content.Server.GameObjects.Components.Mobs
return new StunnableComponentState(StunnedTimer, KnockdownTimer, SlowdownTimer, WalkModifierOverride, return new StunnableComponentState(StunnedTimer, KnockdownTimer, SlowdownTimer, WalkModifierOverride,
RunModifierOverride); RunModifierOverride);
} }
bool IDisarmedAct.Disarmed(DisarmedActEventArgs eventArgs)
{
if (!IoCManager.Resolve<IRobustRandom>().Prob(eventArgs.PushProbability))
return false;
Paralyze(4f);
var source = eventArgs.Source;
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/thudswoosh.ogg", source,
AudioHelpers.WithVariation(0.025f));
source.PopupMessageOtherClients(Loc.GetString("{0} pushes {1}!", source, eventArgs.Target.Name));
source.PopupMessageCursor(Loc.GetString("You push {0}!", eventArgs.Target.Name));
return true;
}
} }
} }

View File

@@ -14,5 +14,10 @@ namespace Content.Server.GameObjects.EntitySystems
RaiseNetworkEvent(new MeleeWeaponSystemMessages.PlayMeleeWeaponAnimationMessage(arc, angle, attacker.Uid, source.Uid, RaiseNetworkEvent(new MeleeWeaponSystemMessages.PlayMeleeWeaponAnimationMessage(arc, angle, attacker.Uid, source.Uid,
hits.Select(e => e.Uid).ToList(), textureEffect, arcFollowAttacker)); hits.Select(e => e.Uid).ToList(), textureEffect, arcFollowAttacker));
} }
public void SendLunge(Angle angle, IEntity source)
{
RaiseNetworkEvent(new MeleeWeaponSystemMessages.PlayLungeAnimationMessage(angle, source.Uid));
}
} }
} }

View File

@@ -0,0 +1,42 @@
using System;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.Interfaces.GameObjects
{
/// <summary>
/// Implements behavior when an entity is disarmed.
/// </summary>
public interface IDisarmedAct
{
/// <summary>
/// Behavior when the entity is disarmed.
/// Return true to prevent the default disarm behavior,
/// or rest of IDisarmAct behaviors that come after this one from happening.
/// </summary>
bool Disarmed(DisarmedActEventArgs eventArgs);
/// <summary>
/// Priority for this disarm act.
/// Used to determine act execution order.
/// </summary>
int Priority => 0;
}
public class DisarmedActEventArgs : EventArgs
{
/// <summary>
/// The entity being disarmed.
/// </summary>
public IEntity Target { get; init; }
/// <summary>
/// The entity performing the disarm.
/// </summary>
public IEntity Source { get; init; }
/// <summary>
/// Probability for push/knockdown.
/// </summary>
public float PushProbability { get; init; }
}
}

View File

@@ -7,6 +7,7 @@
{ {
Error, Error,
HumanScream, HumanScream,
Disarm,
DebugInstant, DebugInstant,
DebugToggle, DebugToggle,
DebugTargetPoint, DebugTargetPoint,

View File

@@ -30,5 +30,18 @@ namespace Content.Shared.GameObjects.EntitySystemMessages
public bool TextureEffect { get; } public bool TextureEffect { get; }
public bool ArcFollowAttacker { get; } public bool ArcFollowAttacker { get; }
} }
[Serializable, NetSerializable]
public sealed class PlayLungeAnimationMessage : EntitySystemMessage
{
public Angle Angle { get; }
public EntityUid Source { get; }
public PlayLungeAnimationMessage(Angle angle, EntityUid source)
{
Angle = angle;
Source = source;
}
}
} }
} }

View File

@@ -22,6 +22,17 @@
- /Audio/Voice/Human/femalescream_5.ogg - /Audio/Voice/Human/femalescream_5.ogg
wilhelm: /Audio/Voice/Human/wilhelm_scream.ogg wilhelm: /Audio/Voice/Human/wilhelm_scream.ogg
- type: action
actionType: Disarm
icon: Interface/Actions/disarm.png
name: "[color=red]Disarm[/color]"
description: "Attempt to [color=red]disarm[/color] someone."
filters:
- human
behaviorType: TargetEntity
repeat: true
behavior: !type:DisarmAction { }
- type: action - type: action
actionType: DebugInstant actionType: DebugInstant
icon: Interface/Alerts/Human/human1.png icon: Interface/Alerts/Human/human1.png

View File

@@ -14,6 +14,7 @@
- type: Actions - type: Actions
innateActions: innateActions:
- HumanScream - HumanScream
- Disarm
- type: OverlayEffectsUI - type: OverlayEffectsUI
- type: Eye - type: Eye
zoom: 0.5, 0.5 zoom: 0.5, 0.5

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -18,6 +18,10 @@
{ {
"name": "scream", "name": "scream",
"directions": 1 "directions": 1
},
{
"name": "disarm",
"directions": 1
} }
] ]
} }