Refactors stunnable to be ECS. (#4819)

Also cleans up StandingStatesystem.
This commit is contained in:
Vera Aguilera Puerto
2021-10-10 12:47:26 +02:00
committed by GitHub
parent 19a588a70a
commit 6eee256b11
34 changed files with 776 additions and 633 deletions

View File

@@ -0,0 +1,9 @@
using Content.Shared.Stunnable;
namespace Content.Client.Stunnable
{
public sealed class StunSystem : SharedStunSystem
{
}
}

View File

@@ -1,33 +0,0 @@
using Content.Shared.Movement.Components;
using Content.Shared.Stunnable;
using Robust.Shared.GameObjects;
namespace Content.Client.Stunnable
{
[RegisterComponent]
[ComponentReference(typeof(SharedStunnableComponent))]
public class StunnableComponent : SharedStunnableComponent
{
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
base.HandleComponentState(curState, nextState);
if (curState is not StunnableComponentState state)
{
return;
}
StunnedTimer = state.StunnedTimer;
KnockdownTimer = state.KnockdownTimer;
SlowdownTimer = state.SlowdownTimer;
WalkModifierOverride = state.WalkModifierOverride;
RunModifierOverride = state.RunModifierOverride;
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? movement))
{
movement.RefreshMovementSpeedModifiers();
}
}
}
}

View File

@@ -1,8 +1,11 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Content.Server.Inventory;
using Content.Server.Inventory.Components;
using Content.Server.Items;
using Content.Server.Stunnable;
using Content.Server.Stunnable.Components;
using Content.Shared.Stunnable;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -73,7 +76,7 @@ namespace Content.IntegrationTests.Tests
Assert.That(inventory.TryGetSlotItem(Slots.INNERCLOTHING, out ItemComponent uniform));
Assert.That(uniform.Owner.Prototype != null && uniform.Owner.Prototype.ID == "InventoryJumpsuitJanitorDummy");
stun.Stun(1f);
EntitySystem.Get<StunSystem>().Stun(human.Uid, TimeSpan.FromSeconds(1f), stun);
// Since the mob is stunned, they can't equip this.
Assert.That(inventory.SpawnItemInSlot(Slots.IDCARD, "InventoryIDCardDummy", true), Is.False);

View File

@@ -7,7 +7,7 @@ namespace Content.Server.Act
/// <summary>
/// Implements behavior when an entity is disarmed.
/// </summary>
[RequiresExplicitImplementation]
[RequiresExplicitImplementation, Obsolete("Use the directed event instead.")]
public interface IDisarmedAct
{
/// <summary>
@@ -15,7 +15,7 @@ namespace Content.Server.Act
/// Return true to prevent the default disarm behavior,
/// or rest of IDisarmedAct behaviors that come after this one from happening.
/// </summary>
bool Disarmed(DisarmedActEventArgs eventArgs);
bool Disarmed(DisarmedActEvent @event);
/// <summary>
/// Priority for this disarm act.
@@ -24,7 +24,7 @@ namespace Content.Server.Act
int Priority => 0;
}
public class DisarmedActEventArgs : EventArgs
public class DisarmedActEvent : HandledEntityEventArgs
{
/// <summary>
/// The entity being disarmed.

View File

@@ -91,11 +91,18 @@ namespace Content.Server.Actions.Actions
system.SendAnimation("disarm", angle, args.Performer, args.Performer, new[] { args.Target });
var eventArgs = new DisarmedActEventArgs() { Target = args.Target, Source = args.Performer, PushProbability = _pushProb };
var eventArgs = new DisarmedActEvent() { Target = args.Target, Source = args.Performer, PushProbability = _pushProb };
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(args.Target.Uid, eventArgs);
// Check if the event has been handled, and if so, do nothing else!
if (eventArgs.Handled)
return;
// Sort by priority.
Array.Sort(disarmedActs, (a, b) => a.Priority.CompareTo(b.Priority));
// TODO: Remove this shit.
foreach (var disarmedAct in disarmedActs)
{
if (disarmedAct.Disarmed(eventArgs))

View File

@@ -2,12 +2,14 @@ using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Stunnable;
using Content.Server.Stunnable.Components;
using Content.Shared.Administration;
using Content.Shared.Damage;
using Content.Shared.Jittering;
using Content.Shared.MobState;
using Content.Shared.Nutrition.Components;
using Content.Shared.Stunnable;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
@@ -56,7 +58,11 @@ namespace Content.Server.Administration.Commands
target.GetComponentOrNull<IMobStateComponent>()?.UpdateState(0);
target.GetComponentOrNull<HungerComponent>()?.ResetFood();
target.GetComponentOrNull<ThirstComponent>()?.ResetThirst();
target.GetComponentOrNull<StunnableComponent>()?.ResetStuns();
if (target.TryGetComponent(out StunnableComponent? stunnable))
{
EntitySystem.Get<StunSystem>().Reset(target.Uid, stunnable);
}
if (target.TryGetComponent(out FlammableComponent? flammable))
{

View File

@@ -1,6 +1,7 @@
using System;
using Content.Server.Alert;
using Content.Server.Atmos.Components;
using Content.Server.Stunnable;
using Content.Server.Stunnable.Components;
using Content.Server.Temperature.Components;
using Content.Shared.ActionBlocker;
@@ -9,6 +10,7 @@ using Content.Shared.Atmos;
using Content.Shared.Damage;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Stunnable;
using Content.Shared.Temperature;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
@@ -24,6 +26,7 @@ namespace Content.Server.Atmos.EntitySystems
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly StunSystem _stunSystem = default!;
private const float MinimumFireStacks = -10f;
private const float MaximumFireStacks = 20f;
@@ -148,7 +151,10 @@ namespace Content.Server.Atmos.EntitySystems
UpdateAppearance(uid, flammable);
}
public void Resist(EntityUid uid, FlammableComponent? flammable = null, StunnableComponent? stunnable = null)
public void Resist(EntityUid uid,
FlammableComponent? flammable = null,
StunnableComponent? stunnable = null,
ServerAlertsComponent? alerts = null)
{
if (!Resolve(uid, ref flammable, ref stunnable))
return;
@@ -159,8 +165,9 @@ namespace Content.Server.Atmos.EntitySystems
flammable.Resisting = true;
flammable.Owner.PopupMessage(Loc.GetString("flammable-component-resist-message"));
stunnable.Paralyze(2f);
_stunSystem.Paralyze(uid, TimeSpan.FromSeconds(2f), stunnable, alerts);
// TODO FLAMMABLE: Make this not use TimerComponent...
flammable.Owner.SpawnTimer(2000, () =>
{
flammable.Resisting = false;

View File

@@ -13,6 +13,7 @@ using Content.Shared.Popups;
using Content.Shared.Pulling;
using Content.Shared.Pulling.Components;
using Content.Shared.Standing;
using Content.Shared.Stunnable;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;

View File

@@ -1,13 +1,16 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Content.Server.Items;
using Content.Server.MachineLinking.Events;
using Content.Server.MachineLinking.Models;
using Content.Server.Power.Components;
using Content.Server.Stunnable;
using Content.Server.Stunnable.Components;
using Content.Shared.Conveyor;
using Content.Shared.MachineLinking;
using Content.Shared.Movement.Components;
using Content.Shared.Popups;
using Content.Shared.Stunnable;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
@@ -20,6 +23,7 @@ namespace Content.Server.Conveyor
{
public class ConveyorSystem : EntitySystem
{
[Dependency] private StunSystem _stunSystem = default!;
[Dependency] private IEntityLookup _entityLookup = default!;
public override void Initialize()
@@ -60,7 +64,7 @@ namespace Content.Server.Conveyor
args.Cancel();
if (args.Attemptee.TryGetComponent<StunnableComponent>(out var stunnableComponent))
{
stunnableComponent.Paralyze(2);
_stunSystem.Paralyze(uid, TimeSpan.FromSeconds(2f), stunnableComponent);
component.Owner.PopupMessage(args.Attemptee, Loc.GetString("conveyor-component-failed-link"));
}
}

View File

@@ -9,6 +9,7 @@ using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Popups;
using Content.Shared.Sound;
using Content.Shared.Stunnable;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;

View File

@@ -1,7 +1,10 @@
using System;
using Content.Server.Damage.Components;
using Content.Server.Stunnable;
using Content.Server.Stunnable.Components;
using Content.Shared.Audio;
using Content.Shared.Damage;
using Content.Shared.Stunnable;
using JetBrains.Annotations;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
@@ -19,6 +22,7 @@ namespace Content.Server.Damage.Systems
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly StunSystem _stunSystem = default!;
public override void Initialize()
{
@@ -43,7 +47,7 @@ namespace Content.Server.Damage.Systems
component.LastHit = _gameTiming.CurTime;
if (EntityManager.TryGetComponent(uid, out StunnableComponent? stun) && _robustRandom.Prob(component.StunChance))
stun.Stun(component.StunSeconds);
_stunSystem.Stun(uid, TimeSpan.FromSeconds(component.StunSeconds), stun);
var damageScale = (speed / component.MinimumSpeed) * component.Factor;
_damageableSystem.TryChangeDamage(uid, component.Damage * damageScale);

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Content.Server.Hands.Components;
using Content.Server.Items;
using Content.Server.Stunnable.Components;
using Content.Shared.Stunnable;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;

View File

@@ -8,6 +8,7 @@ using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Construction.Components;
using Content.Server.Hands.Components;
using Content.Server.Stunnable;
using Content.Server.Stunnable.Components;
using Content.Server.Tools;
using Content.Server.Tools.Components;
@@ -15,6 +16,7 @@ using Content.Shared.Damage;
using Content.Shared.Doors;
using Content.Shared.Interaction;
using Content.Shared.Sound;
using Content.Shared.Stunnable;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Robust.Shared.Audio;
@@ -553,12 +555,6 @@ namespace Content.Server.Doors.Components
// Crush
foreach (var e in collidingentities)
{
if (!e.Owner.TryGetComponent(out StunnableComponent? stun)
|| !e.Owner.HasComponent<DamageableComponent>())
{
continue;
}
var percentage = e.GetWorldAABB().IntersectPercentage(doorAABB);
if (percentage < 0.1f)
@@ -567,9 +563,11 @@ namespace Content.Server.Doors.Components
hitsomebody = true;
CurrentlyCrushing.Add(e.Owner.Uid);
EntitySystem.Get<DamageableSystem>().TryChangeDamage(e.Owner.Uid, CrushDamage);
if (e.Owner.HasComponent<DamageableComponent>())
EntitySystem.Get<DamageableSystem>().TryChangeDamage(e.Owner.Uid, CrushDamage);
stun.Paralyze(DoorStunTime);
if(e.Owner.TryGetComponent(out StunnableComponent? stun))
EntitySystem.Get<StunSystem>().Paralyze(e.Owner.Uid, TimeSpan.FromSeconds(DoorStunTime), stun);
}
// If we hit someone, open up after stun (opens right when stun ends)

View File

@@ -1,6 +1,8 @@
using System;
using Content.Server.Flash.Components;
using Content.Server.Inventory.Components;
using Content.Server.Items;
using Content.Server.Stunnable;
using Content.Server.Stunnable.Components;
using Content.Server.Weapon.Melee;
using Content.Shared.Examine;
@@ -11,6 +13,7 @@ using Content.Shared.Inventory;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Sound;
using Content.Shared.Stunnable;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
@@ -25,6 +28,7 @@ namespace Content.Server.Flash
{
[Dependency] private readonly IEntityLookup _entityLookup = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly StunSystem _stunSystem = default!;
public override void Initialize()
{
@@ -127,9 +131,9 @@ namespace Content.Server.Flash
flashable.Dirty();
}
if (EntityManager.TryGetComponent<StunnableComponent>(target, out var stunnableComponent))
if (EntityManager.TryGetComponent<StunnableComponent>(target, out var stunnable))
{
stunnableComponent.Slowdown(flashDuration / 1000f, slowTo, slowTo);
_stunSystem.Slowdown(target, TimeSpan.FromSeconds(flashDuration/1000f), slowTo, slowTo, stunnable);
}
if (displayPopup && user != null && target != user)

View File

@@ -129,13 +129,13 @@ namespace Content.Server.Hands.Components
RemoveHand(args.Slot);
}
bool IDisarmedAct.Disarmed(DisarmedActEventArgs eventArgs)
bool IDisarmedAct.Disarmed(DisarmedActEvent @event)
{
if (BreakPulls())
return false;
var source = eventArgs.Source;
var target = eventArgs.Target;
var source = @event.Source;
var target = @event.Target;
if (source != null)
{

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using Content.Server.Stunnable;
using Content.Server.Stunnable.Components;
using Content.Server.UserInterface;
using Content.Shared.ActionBlocker;
@@ -8,6 +9,7 @@ using Content.Shared.Instruments;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Standing;
using Content.Shared.Stunnable;
using Content.Shared.Throwing;
using Robust.Server.GameObjects;
using Robust.Server.Player;
@@ -364,7 +366,7 @@ namespace Content.Server.Instruments
if (mob.TryGetComponent(out StunnableComponent? stun))
{
stun.Stun(1);
EntitySystem.Get<StunSystem>().Stun(mob.Uid, TimeSpan.FromSeconds(1), stun);
Clean();
}

View File

@@ -1,5 +1,7 @@
using Content.Server.Stunnable.Components;
using Content.Server.Stunnable;
using Content.Server.Stunnable.Components;
using Content.Shared.MobState.State;
using Content.Shared.Stunnable;
using Robust.Shared.GameObjects;
namespace Content.Server.MobState.States
@@ -12,7 +14,7 @@ namespace Content.Server.MobState.States
if (entity.TryGetComponent(out StunnableComponent? stun))
{
stun.CancelAll();
EntitySystem.Get<StunSystem>().Reset(entity.Uid, stun);
}
}
}

View File

@@ -1,8 +1,10 @@
using Content.Server.Alert;
using Content.Server.Stunnable;
using Content.Server.Stunnable.Components;
using Content.Shared.Alert;
using Content.Shared.MobState;
using Content.Shared.MobState.State;
using Content.Shared.Stunnable;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
@@ -21,7 +23,8 @@ namespace Content.Server.MobState.States
if (entity.TryGetComponent(out StunnableComponent? stun))
{
stun.CancelAll();
// TODO: Use resolves to pass ServerAlertsComponent here.
EntitySystem.Get<StunSystem>().Reset(entity.Uid, stun);
}
}
}

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -6,18 +7,26 @@ namespace Content.Server.Stunnable.Components
/// <summary>
/// Adds stun when it collides with an entity
/// </summary>
[RegisterComponent]
internal sealed class StunOnCollideComponent : Component
[RegisterComponent, Friend(typeof(StunOnCollideSystem))]
public sealed class StunOnCollideComponent : Component
{
// TODO: Can probably predict this.
public override string Name => "StunOnCollide";
// See stunnable for what these do
[DataField("stunAmount")]
internal int StunAmount = default;
public int StunAmount;
[DataField("knockdownAmount")]
internal int KnockdownAmount = default;
public int KnockdownAmount;
[DataField("slowdownAmount")]
internal int SlowdownAmount = default;
public int SlowdownAmount;
[DataField("walkSpeedMultiplier")]
public float WalkSpeedMultiplier = 1f;
[DataField("runSpeedMultiplier")]
public float RunSpeedMultiplier = 1f;
}
}

View File

@@ -1,86 +0,0 @@
using Content.Server.Act;
using Content.Server.Popups;
using Content.Shared.Audio;
using Content.Shared.MobState;
using Content.Shared.Popups;
using Content.Shared.Sound;
using Content.Shared.Standing;
using Content.Shared.Stunnable;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Stunnable.Components
{
[RegisterComponent]
[ComponentReference(typeof(SharedStunnableComponent))]
public class StunnableComponent : SharedStunnableComponent, IDisarmedAct
{
[DataField("stunAttemptSound")] private SoundSpecifier _stunAttemptSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
protected override void OnKnockdown()
{
EntitySystem.Get<StandingStateSystem>().Down(Owner);
}
protected override void OnKnockdownEnd()
{
if (Owner.TryGetComponent(out IMobStateComponent? mobState) && !mobState.IsIncapacitated())
EntitySystem.Get<StandingStateSystem>().Stand(Owner);
}
public void CancelAll()
{
KnockdownTimer = null;
StunnedTimer = null;
Dirty();
}
public void ResetStuns()
{
StunnedTimer = null;
SlowdownTimer = null;
if (KnockedDown &&
Owner.TryGetComponent(out IMobStateComponent? mobState) && !mobState.IsIncapacitated())
{
EntitySystem.Get<StandingStateSystem>().Stand(Owner);
}
KnockdownTimer = null;
Dirty();
}
protected override void OnInteractHand()
{
SoundSystem.Play(Filter.Pvs(Owner), _stunAttemptSound.GetSound(), Owner, AudioHelpers.WithVariation(0.05f));
}
bool IDisarmedAct.Disarmed(DisarmedActEventArgs eventArgs)
{
if (!IoCManager.Resolve<IRobustRandom>().Prob(eventArgs.PushProbability))
return false;
Paralyze(4f);
var source = eventArgs.Source;
var target = eventArgs.Target;
if (source != null)
{
SoundSystem.Play(Filter.Pvs(source), _stunAttemptSound.GetSound(), source, AudioHelpers.WithVariation(0.025f));
if (target != null)
{
source.PopupMessageOtherClients(Loc.GetString("stunnable-component-disarm-success-others", ("source", source.Name), ("target", target.Name)));
source.PopupMessageCursor(Loc.GetString("stunnable-component-disarm-success", ("target", target.Name)));
}
}
return true;
}
}
}

View File

@@ -1,6 +1,14 @@
using System;
using Content.Server.Alert;
using Content.Server.Stunnable.Components;
using Content.Shared.Alert;
using Content.Shared.Movement.Components;
using Content.Shared.Standing;
using Content.Shared.Stunnable;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Physics.Dynamics;
namespace Content.Server.Stunnable
@@ -8,6 +16,8 @@ namespace Content.Server.Stunnable
[UsedImplicitly]
internal sealed class StunOnCollideSystem : EntitySystem
{
[Dependency] private readonly StunSystem _stunSystem = default!;
public override void Initialize()
{
base.Initialize();
@@ -16,11 +26,25 @@ namespace Content.Server.Stunnable
private void HandleCollide(EntityUid uid, StunOnCollideComponent component, StartCollideEvent args)
{
if (args.OtherFixture.Body.Owner.TryGetComponent(out StunnableComponent? stunnableComponent))
var otherUid = args.OtherFixture.Body.Owner.Uid;
if (EntityManager.TryGetComponent(otherUid, out StunnableComponent? stunnableComponent))
{
stunnableComponent.Stun(component.StunAmount);
stunnableComponent.Knockdown(component.KnockdownAmount);
stunnableComponent.Slowdown(component.SlowdownAmount);
ServerAlertsComponent? alerts = null;
StandingStateComponent? standingState = null;
AppearanceComponent? appearance = null;
MovementSpeedModifierComponent? speedModifier = null;
// Let the actual methods log errors for these.
Resolve(otherUid, ref alerts, ref standingState, ref appearance, ref speedModifier, false);
_stunSystem.Stun(otherUid, TimeSpan.FromSeconds(component.StunAmount), stunnableComponent, alerts);
_stunSystem.Knockdown(otherUid, TimeSpan.FromSeconds(component.KnockdownAmount), stunnableComponent,
alerts, standingState, appearance);
_stunSystem.Slowdown(otherUid, TimeSpan.FromSeconds(component.SlowdownAmount),
component.WalkSpeedMultiplier, component.RunSpeedMultiplier, stunnableComponent, speedModifier, alerts);
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using Content.Server.Act;
using Content.Server.Popups;
using Content.Shared.Audio;
using Content.Shared.Popups;
using Content.Shared.Stunnable;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Server.Stunnable
{
public sealed class StunSystem : SharedStunSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StunnableComponent, DisarmedActEvent>(OnDisarmed);
}
private void OnDisarmed(EntityUid uid, StunnableComponent stunnable, DisarmedActEvent args)
{
if (args.Handled || !_random.Prob(args.PushProbability))
return;
Paralyze(uid, TimeSpan.FromSeconds(4f), stunnable);
var source = args.Source;
var target = args.Target;
if (source != null)
{
SoundSystem.Play(Filter.Pvs(source), stunnable.StunAttemptSound.GetSound(), source, AudioHelpers.WithVariation(0.025f));
if (target != null)
{
// TODO: Use PopupSystem
source.PopupMessageOtherClients(Loc.GetString("stunnable-component-disarm-success-others", ("source", source.Name), ("target", target.Name)));
source.PopupMessageCursor(Loc.GetString("stunnable-component-disarm-success", ("target", target.Name)));
}
}
args.Handled = true;
}
}
}

View File

@@ -10,6 +10,7 @@ using Content.Shared.Audio;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Stunnable;
using Content.Shared.Throwing;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
@@ -23,6 +24,7 @@ namespace Content.Server.Stunnable
{
public class StunbatonSystem : EntitySystem
{
[Dependency] private readonly StunSystem _stunSystem = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
public override void Initialize()
@@ -119,20 +121,22 @@ namespace Content.Server.Stunnable
{
if (!entity.TryGetComponent(out StunnableComponent? stunnable) || !comp.Activated) return;
// TODO: Make slowdown inflicted customizable.
SoundSystem.Play(Filter.Pvs(comp.Owner), comp.StunSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f));
if (!stunnable.SlowedDown)
{
if (_robustRandom.Prob(comp.ParalyzeChanceNoSlowdown))
stunnable.Paralyze(comp.ParalyzeTime);
_stunSystem.Paralyze(entity.Uid, TimeSpan.FromSeconds(comp.ParalyzeTime), stunnable);
else
stunnable.Slowdown(comp.SlowdownTime);
_stunSystem.Slowdown(entity.Uid, TimeSpan.FromSeconds(comp.SlowdownTime), 0.5f, 0.5f, stunnable);
}
else
{
if (_robustRandom.Prob(comp.ParalyzeChanceWithSlowdown))
stunnable.Paralyze(comp.ParalyzeTime);
_stunSystem.Paralyze(entity.Uid, TimeSpan.FromSeconds(comp.ParalyzeTime), stunnable);
else
stunnable.Slowdown(comp.SlowdownTime);
_stunSystem.Slowdown(entity.Uid, TimeSpan.FromSeconds(comp.SlowdownTime), 0.5f, 0.5f, stunnable);
}

View File

@@ -3,6 +3,7 @@ using Content.Server.Atmos.EntitySystems;
using Content.Server.CombatMode;
using Content.Server.Hands.Components;
using Content.Server.Interaction.Components;
using Content.Server.Stunnable;
using Content.Server.Stunnable.Components;
using Content.Server.Weapon.Ranged.Barrels.Components;
using Content.Shared.ActionBlocker;
@@ -10,6 +11,7 @@ using Content.Shared.Damage;
using Content.Shared.Hands;
using Content.Shared.Popups;
using Content.Shared.Sound;
using Content.Shared.Stunnable;
using Content.Shared.Weapons.Ranged.Components;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
@@ -175,7 +177,7 @@ namespace Content.Server.Weapon.Ranged
// Knock them down
if (user.TryGetComponent(out StunnableComponent? stun))
{
stun.Paralyze(3f);
EntitySystem.Get<StunSystem>().Paralyze(user.Uid, TimeSpan.FromSeconds(3f), stun);
}
// Apply salt to the wound ("Honk!")

View File

@@ -20,8 +20,15 @@ namespace Content.Shared.Hands
public void DropHandItems(IEntity entity, bool doMobChecks = true)
{
if (!entity.TryGetComponent(out SharedHandsComponent? handsComponent)) return;
DropHandItems(handsComponent, doMobChecks);
DropHandItems(entity.Uid, doMobChecks);
}
public void DropHandItems(EntityUid uid, bool doMobChecks = true, SharedHandsComponent? hands = null)
{
if (!Resolve(uid, ref hands))
return;
DropHandItems(hands, doMobChecks);
}
private void DropHandItems(SharedHandsComponent handsComponent, bool doMobChecks = true)

View File

@@ -1,14 +1,18 @@
using System;
using Content.Shared.Nutrition.Components;
using Content.Shared.Stunnable;
using Content.Shared.Throwing;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Shared.Nutrition.EntitySystems
{
[UsedImplicitly]
public abstract class SharedCreamPieSystem : EntitySystem
{
[Dependency] private SharedStunSystem _stunSystem = default!;
public override void Initialize()
{
base.Initialize();
@@ -64,9 +68,9 @@ namespace Content.Shared.Nutrition.EntitySystems
CreamedEntity(uid, creamPied, args);
if (EntityManager.TryGetComponent(uid, out SharedStunnableComponent? stun))
if (EntityManager.TryGetComponent(uid, out StunnableComponent? stun))
{
stun.Paralyze(creamPie.ParalyzeTime);
_stunSystem.Paralyze(uid, TimeSpan.FromSeconds(creamPie.ParalyzeTime), stun);
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.EffectBlocker;
@@ -6,6 +7,7 @@ using Content.Shared.Stunnable;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Dynamics;
@@ -14,6 +16,8 @@ namespace Content.Shared.Slippery
[UsedImplicitly]
public abstract class SharedSlipperySystem : EntitySystem
{
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
private List<SlipperyComponent> _slipped = new();
public override void Initialize()
@@ -45,12 +49,12 @@ namespace Content.Shared.Slippery
}
}
public bool CanSlip(SlipperyComponent component, EntityUid uid, [NotNullWhen(true)] out SharedStunnableComponent? stunnableComponent)
public bool CanSlip(SlipperyComponent component, EntityUid uid, [NotNullWhen(true)] out StunnableComponent? stunnableComponent)
{
if (!component.Slippery
|| component.Owner.IsInContainer()
|| component.Slipped.Contains(uid)
|| !EntityManager.TryGetComponent<SharedStunnableComponent>(uid, out stunnableComponent))
|| !EntityManager.TryGetComponent<StunnableComponent>(uid, out stunnableComponent))
{
stunnableComponent = null;
return false;
@@ -82,7 +86,7 @@ namespace Content.Shared.Slippery
otherBody.LinearVelocity *= component.LaunchForwardsMultiplier;
stun.Paralyze(5);
_stunSystem.Paralyze(component.Owner.Uid, TimeSpan.FromSeconds(5), stun);
component.Slipped.Add(otherBody.Owner.Uid);
component.Dirty();

View File

@@ -1,6 +1,7 @@
using System;
using Content.Shared.EffectBlocker;
using Content.Shared.Sound;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Players;
@@ -10,8 +11,8 @@ using Robust.Shared.ViewVariables;
namespace Content.Shared.Standing
{
[RegisterComponent]
[NetworkedComponent]
[Friend(typeof(StandingStateSystem))]
[RegisterComponent, NetworkedComponent]
public sealed class StandingStateComponent : Component, IEffectBlocker
{
public override string Name => "StandingState";

View File

@@ -1,92 +1,117 @@
using System;
using Content.Shared.Audio;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Rotation;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Player;
namespace Content.Shared.Standing
{
public sealed class StandingStateSystem : EntitySystem
{
[Dependency] private readonly SharedHandsSystem _sharedHandsSystem = default!;
[Obsolete("Use the EntityUid overloads instead.")]
public bool IsDown(IEntity entity)
{
if (entity.TryGetComponent(out StandingStateComponent? standingState) &&
standingState.Standing) return true;
return false;
return IsDown(entity.Uid);
}
public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null)
{
if (!Resolve(uid, ref standingState, false))
return false;
return !standingState.Standing;
}
[Obsolete("Use the EntityUid overloads instead.")]
public void Down(IEntity entity, bool playSound = true, bool dropHeldItems = true)
{
if (!entity.TryGetComponent(out StandingStateComponent? comp)) return;
Down(comp, playSound, dropHeldItems);
Down(entity.Uid, playSound, dropHeldItems);
}
public void Stand(IEntity entity)
public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true,
StandingStateComponent? standingState = null,
SharedAppearanceComponent? appearance = null,
SharedHandsComponent? hands = null)
{
if (!entity.TryGetComponent(out StandingStateComponent? comp)) return;
Stand(comp);
}
// TODO: This should actually log missing comps...
if (!Resolve(uid, ref standingState, false))
return false;
public void Down(StandingStateComponent component, bool playSound = true, bool dropHeldItems = true)
{
if (!component.Standing) return;
// Optional component.
Resolve(uid, ref appearance, ref hands, false);
var entity = component.Owner;
var uid = entity.Uid;
if (!standingState.Standing)
return true;
// This is just to avoid most callers doing this manually saving boilerplate
// 99% of the time you'll want to drop items but in some scenarios (e.g. buckling) you don't want to.
// We do this BEFORE downing because something like buckle may be blocking downing but we want to drop hand items anyway
// and ultimately this is just to avoid boilerplate in Down callers + keep their behavior consistent.
if (dropHeldItems)
if (dropHeldItems && hands != null)
{
Get<SharedHandsSystem>().DropHandItems(entity, false);
_sharedHandsSystem.DropHandItems(uid, false, hands);
}
var msg = new DownAttemptEvent();
EntityManager.EventBus.RaiseLocalEvent(uid, msg);
RaiseLocalEvent(uid, msg, false);
if (msg.Cancelled) return;
if (msg.Cancelled)
return false;
component.Standing = false;
component.Dirty();
EntityManager.EventBus.RaiseLocalEvent(uid, new DownedEvent());
standingState.Standing = false;
standingState.Dirty();
RaiseLocalEvent(uid, new DownedEvent(), false);
// Seemed like the best place to put it
if (entity.TryGetComponent(out SharedAppearanceComponent? appearance))
{
appearance.SetData(RotationVisuals.RotationState, RotationState.Horizontal);
}
appearance?.SetData(RotationVisuals.RotationState, RotationState.Horizontal);
// Currently shit is only downed by server but when it's predicted we can probably only play this on server / client
if (playSound)
{
SoundSystem.Play(Filter.Pvs(entity), component.DownSoundCollection.GetSound(), entity, AudioHelpers.WithVariation(0.25f));
SoundSystem.Play(Filter.Pvs(uid), standingState.DownSoundCollection.GetSound(), uid, AudioHelpers.WithVariation(0.25f));
}
return true;
}
public void Stand(StandingStateComponent component)
[Obsolete("Use the EntityUid overloads instead.")]
public void Stand(IEntity entity)
{
if (component.Standing) return;
Stand(entity.Uid);
}
var entity = component.Owner;
var uid = entity.Uid;
public bool Stand(EntityUid uid,
StandingStateComponent? standingState = null,
SharedAppearanceComponent? appearance = null)
{
// TODO: This should actually log missing comps...
if (!Resolve(uid, ref standingState, false))
return false;
// Optional component.
Resolve(uid, ref appearance, false);
if (standingState.Standing)
return true;
var msg = new StandAttemptEvent();
EntityManager.EventBus.RaiseLocalEvent(uid, msg);
RaiseLocalEvent(uid, msg, false);
if (msg.Cancelled) return;
if (msg.Cancelled)
return false;
component.Standing = true;
component.Dirty();
EntityManager.EventBus.RaiseLocalEvent(uid, new StoodEvent());
standingState.Standing = true;
standingState.Dirty();
RaiseLocalEvent(uid, new StoodEvent(), false);
if (entity.TryGetComponent(out SharedAppearanceComponent? appearance))
{
appearance.SetData(RotationVisuals.RotationState, RotationState.Vertical);
}
appearance?.SetData(RotationVisuals.RotationState, RotationState.Vertical);
return true;
}
}

View File

@@ -0,0 +1,402 @@
using System;
using Content.Shared.Alert;
using Content.Shared.Audio;
using Content.Shared.DragDrop;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Movement;
using Content.Shared.Movement.Components;
using Content.Shared.Speech;
using Content.Shared.Standing;
using Content.Shared.Throwing;
using JetBrains.Annotations;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Content.Shared.Stunnable
{
[UsedImplicitly]
public abstract class SharedStunSystem : EntitySystem
{
[Dependency] private readonly StandingStateSystem _standingStateSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
public override void Initialize()
{
SubscribeLocalEvent<StunnableComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<StunnableComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<StunnableComponent, InteractHandEvent>(OnInteractHand);
// Attempt event subscriptions.
SubscribeLocalEvent<StunnableComponent, MovementAttemptEvent>(OnMoveAttempt);
SubscribeLocalEvent<StunnableComponent, InteractionAttemptEvent>(OnInteractAttempt);
SubscribeLocalEvent<StunnableComponent, UseAttemptEvent>(OnUseAttempt);
SubscribeLocalEvent<StunnableComponent, ThrowAttemptEvent>(OnThrowAttempt);
SubscribeLocalEvent<StunnableComponent, DropAttemptEvent>(OnDropAttempt);
SubscribeLocalEvent<StunnableComponent, PickupAttemptEvent>(OnPickupAttempt);
SubscribeLocalEvent<StunnableComponent, AttackAttemptEvent>(OnAttackAttempt);
SubscribeLocalEvent<StunnableComponent, EquipAttemptEvent>(OnEquipAttempt);
SubscribeLocalEvent<StunnableComponent, UnequipAttemptEvent>(OnUnequipAttempt);
SubscribeLocalEvent<StunnableComponent, StandAttemptEvent>(OnStandAttempt);
}
private void OnGetState(EntityUid uid, StunnableComponent stunnable, ref ComponentGetState args)
{
args.State = new StunnableComponentState(stunnable.StunnedTimer, stunnable.KnockdownTimer, stunnable.SlowdownTimer, stunnable.WalkSpeedMultiplier, stunnable.RunSpeedMultiplier);
}
private void OnHandleState(EntityUid uid, StunnableComponent stunnable, ref ComponentHandleState args)
{
if (args.Current is not StunnableComponentState state)
return;
stunnable.StunnedTimer = state.StunnedTimer;
stunnable.KnockdownTimer = state.KnockdownTimer;
stunnable.SlowdownTimer = state.SlowdownTimer;
stunnable.WalkSpeedMultiplier = state.WalkSpeedMultiplier;
stunnable.RunSpeedMultiplier = state.RunSpeedMultiplier;
if (EntityManager.TryGetComponent(uid, out MovementSpeedModifierComponent? movement))
movement.RefreshMovementSpeedModifiers();
}
private TimeSpan AdjustTime(TimeSpan time, (TimeSpan Start, TimeSpan End)? timer, float cap)
{
if (timer != null)
{
time = timer.Value.End - timer.Value.Start + time;
}
if (time.TotalSeconds > cap)
time = TimeSpan.FromSeconds(cap);
return time;
}
// TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...)
/// <summary>
/// Stuns the entity, disallowing it from doing many interactions temporarily.
/// </summary>
public void Stun(EntityUid uid, TimeSpan time,
StunnableComponent? stunnable = null,
SharedAlertsComponent? alerts = null)
{
if (!Resolve(uid, ref stunnable))
return;
time = AdjustTime(time, stunnable.StunnedTimer, stunnable.StunCap);
if (time <= TimeSpan.Zero)
return;
stunnable.StunnedTimer = (_gameTiming.CurTime, _gameTiming.CurTime + time);
SetAlert(uid, stunnable, alerts);
stunnable.Dirty();
}
/// <summary>
/// Knocks down the entity, making it fall to the ground.
/// </summary>
public void Knockdown(EntityUid uid, TimeSpan time,
StunnableComponent? stunnable = null,
SharedAlertsComponent? alerts = null,
StandingStateComponent? standingState = null,
SharedAppearanceComponent? appearance = null)
{
if (!Resolve(uid, ref stunnable))
return;
time = AdjustTime(time, stunnable.KnockdownTimer, stunnable.KnockdownCap);
if (time <= TimeSpan.Zero)
return;
// Check if we can actually knock down the mob.
if (!_standingStateSystem.Down(uid, standingState:standingState, appearance:appearance))
return;
stunnable.KnockdownTimer = (_gameTiming.CurTime, _gameTiming.CurTime + time);;
SetAlert(uid, stunnable, alerts);
stunnable.Dirty();
}
/// <summary>
/// Applies knockdown and stun to the entity temporarily.
/// </summary>
public void Paralyze(EntityUid uid, TimeSpan time,
StunnableComponent? stunnable = null,
SharedAlertsComponent? alerts = null)
{
if (!Resolve(uid, ref stunnable))
return;
// Optional component.
Resolve(uid, ref alerts, false);
Stun(uid, time, stunnable, alerts);
Knockdown(uid, time, stunnable, alerts);
}
/// <summary>
/// Slows down the mob's walking/running speed temporarily
/// </summary>
public void Slowdown(EntityUid uid, TimeSpan time, float walkSpeedMultiplier = 1f, float runSpeedMultiplier = 1f,
StunnableComponent? stunnable = null,
MovementSpeedModifierComponent? speedModifier = null,
SharedAlertsComponent? alerts = null)
{
if (!Resolve(uid, ref stunnable))
return;
// "Optional" component.
Resolve(uid, ref speedModifier, false);
time = AdjustTime(time, stunnable.SlowdownTimer, stunnable.SlowdownCap);
if (time <= TimeSpan.Zero)
return;
// Doesn't make much sense to have the "Slowdown" method speed up entities now does it?
walkSpeedMultiplier = Math.Clamp(walkSpeedMultiplier, 0f, 1f);
runSpeedMultiplier = Math.Clamp(runSpeedMultiplier, 0f, 1f);
stunnable.WalkSpeedMultiplier *= walkSpeedMultiplier;
stunnable.RunSpeedMultiplier *= runSpeedMultiplier;
stunnable.SlowdownTimer = (_gameTiming.CurTime, _gameTiming.CurTime + time);
speedModifier?.RefreshMovementSpeedModifiers();
SetAlert(uid, stunnable, alerts);
stunnable.Dirty();
}
public void Reset(EntityUid uid,
StunnableComponent? stunnable = null,
MovementSpeedModifierComponent? speedModifier = null,
StandingStateComponent? standingState = null,
SharedAppearanceComponent? appearance = null)
{
if (!Resolve(uid, ref stunnable))
return;
// Optional component.
Resolve(uid, ref speedModifier, false);
stunnable.StunnedTimer = null;
stunnable.SlowdownTimer = null;
stunnable.KnockdownTimer = null;
speedModifier?.RefreshMovementSpeedModifiers();
_standingStateSystem.Stand(uid, standingState, appearance);
stunnable.Dirty();
}
private void SetAlert(EntityUid uid,
StunnableComponent? stunnable = null,
SharedAlertsComponent? alerts = null)
{
// This method is really just optional, doesn't matter if the entity doesn't support alerts.
if (!Resolve(uid, ref stunnable, ref alerts, false))
return;
if (GetTimers(uid, stunnable) is not {} timers)
return;
alerts.ShowAlert(AlertType.Stun, cooldown:timers);
}
private (TimeSpan, TimeSpan)? GetTimers(EntityUid uid, StunnableComponent? stunnable = null)
{
if (!Resolve(uid, ref stunnable))
return null;
// Don't do anything if no stuns are applied.
if (!stunnable.AnyStunActive)
return null;
TimeSpan start = TimeSpan.MaxValue, end = TimeSpan.MinValue;
if (stunnable.StunnedTimer != null)
{
if (stunnable.StunnedTimer.Value.Start < start)
start = stunnable.StunnedTimer.Value.Start;
if (stunnable.StunnedTimer.Value.End > end)
end = stunnable.StunnedTimer.Value.End;
}
if (stunnable.KnockdownTimer != null)
{
if (stunnable.KnockdownTimer.Value.Start < start)
start = stunnable.KnockdownTimer.Value.Start;
if (stunnable.KnockdownTimer.Value.End > end)
end = stunnable.KnockdownTimer.Value.End;
}
if (stunnable.SlowdownTimer != null)
{
if (stunnable.SlowdownTimer.Value.Start < start)
start = stunnable.SlowdownTimer.Value.Start;
if (stunnable.SlowdownTimer.Value.End > end)
end = stunnable.SlowdownTimer.Value.End;
}
return (start, end);
}
private void OnInteractHand(EntityUid uid, StunnableComponent stunnable, InteractHandEvent args)
{
if (args.Handled || stunnable.HelpTimer > 0f || !stunnable.KnockedDown)
return;
// Set it to half the help interval so helping is actually useful...
stunnable.HelpTimer = stunnable.HelpInterval/2f;
stunnable.KnockdownTimer = (stunnable.KnockdownTimer!.Value.Start, stunnable.KnockdownTimer.Value.End - TimeSpan.FromSeconds(stunnable.HelpInterval));
SoundSystem.Play(Filter.Pvs(uid), stunnable.StunAttemptSound.GetSound(), uid, AudioHelpers.WithVariation(0.05f));
SetAlert(uid, stunnable);
stunnable.Dirty();
args.Handled = true;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var curTime = _gameTiming.CurTime;
foreach (var stunnable in EntityManager.EntityQuery<StunnableComponent>())
{
var uid = stunnable.Owner.Uid;
if(stunnable.HelpTimer > 0f)
// If it goes negative, that's okay.
stunnable.HelpTimer -= frameTime;
if (stunnable.StunnedTimer != null)
{
if (stunnable.StunnedTimer.Value.End <= curTime)
{
stunnable.StunnedTimer = null;
stunnable.Dirty();
}
}
if (stunnable.KnockdownTimer != null)
{
if (stunnable.KnockdownTimer.Value.End <= curTime)
{
stunnable.KnockdownTimer = null;
// Try to stand up the mob...
_standingStateSystem.Stand(uid);
stunnable.Dirty();
}
}
if (stunnable.SlowdownTimer != null)
{
if (stunnable.SlowdownTimer.Value.End <= curTime)
{
if (EntityManager.TryGetComponent(uid, out MovementSpeedModifierComponent? movement))
movement.RefreshMovementSpeedModifiers();
stunnable.SlowdownTimer = null;
stunnable.Dirty();
}
}
if (stunnable.AnyStunActive || !EntityManager.TryGetComponent(uid, out SharedAlertsComponent? status)
|| !status.IsShowingAlert(AlertType.Stun))
continue;
status.ClearAlert(AlertType.Stun);
}
}
#region Attempt Event Handling
private void OnMoveAttempt(EntityUid uid, StunnableComponent stunnable, MovementAttemptEvent args)
{
if (stunnable.Stunned)
args.Cancel();
}
private void OnInteractAttempt(EntityUid uid, StunnableComponent stunnable, InteractionAttemptEvent args)
{
if(stunnable.Stunned)
args.Cancel();
}
private void OnUseAttempt(EntityUid uid, StunnableComponent stunnable, UseAttemptEvent args)
{
if(stunnable.Stunned)
args.Cancel();
}
private void OnThrowAttempt(EntityUid uid, StunnableComponent stunnable, ThrowAttemptEvent args)
{
if (stunnable.Stunned)
args.Cancel();
}
private void OnDropAttempt(EntityUid uid, StunnableComponent stunnable, DropAttemptEvent args)
{
if(stunnable.Stunned)
args.Cancel();
}
private void OnPickupAttempt(EntityUid uid, StunnableComponent stunnable, PickupAttemptEvent args)
{
if(stunnable.Stunned)
args.Cancel();
}
private void OnAttackAttempt(EntityUid uid, StunnableComponent stunnable, AttackAttemptEvent args)
{
if(stunnable.Stunned)
args.Cancel();
}
private void OnEquipAttempt(EntityUid uid, StunnableComponent stunnable, EquipAttemptEvent args)
{
if(stunnable.Stunned)
args.Cancel();
}
private void OnUnequipAttempt(EntityUid uid, StunnableComponent stunnable, UnequipAttemptEvent args)
{
if(stunnable.Stunned)
args.Cancel();
}
private void OnStandAttempt(EntityUid uid, StunnableComponent stunnable, StandAttemptEvent args)
{
if(stunnable.KnockedDown)
args.Cancel();
}
#endregion
}
}

View File

@@ -1,388 +0,0 @@
using System;
using System.Threading;
using Content.Shared.ActionBlocker;
using Content.Shared.Alert;
using Content.Shared.Interaction;
using Content.Shared.Movement.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Stunnable
{
[NetworkedComponent()]
public abstract class SharedStunnableComponent : Component, IMoveSpeedModifier, IActionBlocker, IInteractHand
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
public sealed override string Name => "Stunnable";
public (TimeSpan Start, TimeSpan End)? StunnedTimer { get; protected set; }
public (TimeSpan Start, TimeSpan End)? KnockdownTimer { get; protected set; }
public (TimeSpan Start, TimeSpan End)? SlowdownTimer { get; protected set; }
[ViewVariables] public float StunnedSeconds =>
StunnedTimer == null ? 0f : (float)(StunnedTimer.Value.End - StunnedTimer.Value.Start).TotalSeconds;
[ViewVariables] public float KnockdownSeconds =>
KnockdownTimer == null ? 0f : (float)(KnockdownTimer.Value.End - KnockdownTimer.Value.Start).TotalSeconds;
[ViewVariables] public float SlowdownSeconds =>
SlowdownTimer == null ? 0f : (float)(SlowdownTimer.Value.End - SlowdownTimer.Value.Start).TotalSeconds;
[ViewVariables] public bool AnyStunActive => Stunned || KnockedDown || SlowedDown;
[ViewVariables] public bool Stunned => StunnedTimer != null;
[ViewVariables] public bool KnockedDown => KnockdownTimer != null;
[ViewVariables] public bool SlowedDown => SlowdownTimer != null;
[DataField("stunCap")]
protected float _stunCap = 20f;
[DataField("knockdownCap")]
protected float _knockdownCap = 20f;
[DataField("slowdownCap")]
protected float _slowdownCap = 20f;
[DataField("helpInterval")]
private float _helpInterval = 1f;
private bool _canHelp = true;
protected CancellationTokenSource StatusRemoveCancellation = new();
[ViewVariables] protected float WalkModifierOverride = 0f;
[ViewVariables] protected float RunModifierOverride = 0f;
private float StunTimeModifier
{
get
{
var modifier = 1.0f;
var components = Owner.GetAllComponents<IStunModifier>();
foreach (var component in components)
{
modifier *= component.StunTimeModifier;
}
return modifier;
}
}
private float KnockdownTimeModifier
{
get
{
var modifier = 1.0f;
var components = Owner.GetAllComponents<IStunModifier>();
foreach (var component in components)
{
modifier *= component.KnockdownTimeModifier;
}
return modifier;
}
}
private float SlowdownTimeModifier
{
get
{
var modifier = 1.0f;
var components = Owner.GetAllComponents<IStunModifier>();
foreach (var component in components)
{
modifier *= component.SlowdownTimeModifier;
}
return modifier;
}
}
/// <summary>
/// Stuns the entity, disallowing it from doing many interactions temporarily.
/// </summary>
/// <param name="seconds">How many seconds the mob will stay stunned.</param>
/// <returns>Whether or not the owner was stunned.</returns>
public bool Stun(float seconds)
{
seconds = MathF.Min(StunnedSeconds + (seconds * StunTimeModifier), _stunCap);
if (seconds <= 0f)
{
return false;
}
StunnedTimer = (_gameTiming.CurTime, _gameTiming.CurTime.Add(TimeSpan.FromSeconds(seconds)));
SetAlert();
OnStun();
Dirty();
return true;
}
protected virtual void OnStun() { }
/// <summary>
/// Knocks down the mob, making it fall to the ground.
/// </summary>
/// <param name="seconds">How many seconds the mob will stay on the ground.</param>
/// <returns>Whether or not the owner was knocked down.</returns>
public bool Knockdown(float seconds)
{
seconds = MathF.Min(KnockdownSeconds + (seconds * KnockdownTimeModifier), _knockdownCap);
if (seconds <= 0f)
{
return false;
}
KnockdownTimer = (_gameTiming.CurTime, _gameTiming.CurTime.Add(TimeSpan.FromSeconds(seconds)));;
SetAlert();
OnKnockdown();
Dirty();
return true;
}
protected virtual void OnKnockdown() { }
/// <summary>
/// Applies knockdown and stun to the mob temporarily.
/// </summary>
/// <param name="seconds">How many seconds the mob will be paralyzed-</param>
/// <returns>Whether or not the owner of this component was paralyzed-</returns>
public bool Paralyze(float seconds)
{
return Stun(seconds) && Knockdown(seconds);
}
/// <summary>
/// Slows down the mob's walking/running speed temporarily
/// </summary>
/// <param name="seconds">How many seconds the mob will be slowed down</param>
/// <param name="walkModifierOverride">Walk speed modifier. Set to 0 or negative for default value. (0.5f)</param>
/// <param name="runModifierOverride">Run speed modifier. Set to 0 or negative for default value. (0.5f)</param>
public void Slowdown(float seconds, float walkModifierOverride = 0f, float runModifierOverride = 0f)
{
seconds = MathF.Min(SlowdownSeconds + (seconds * SlowdownTimeModifier), _slowdownCap);
if (seconds <= 0f)
return;
WalkModifierOverride = walkModifierOverride;
RunModifierOverride = runModifierOverride;
SlowdownTimer = (_gameTiming.CurTime, _gameTiming.CurTime.Add(TimeSpan.FromSeconds(seconds)));
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? movement))
movement.RefreshMovementSpeedModifiers();
SetAlert();
Dirty();
}
private (TimeSpan, TimeSpan)? GetTimers()
{
// Don't do anything if no stuns are applied.
if (!AnyStunActive)
return null;
TimeSpan start = TimeSpan.MaxValue, end = TimeSpan.MinValue;
if (StunnedTimer != null)
{
if (StunnedTimer.Value.Start < start)
start = StunnedTimer.Value.Start;
if (StunnedTimer.Value.End > end)
end = StunnedTimer.Value.End;
}
if (KnockdownTimer != null)
{
if (KnockdownTimer.Value.Start < start)
start = KnockdownTimer.Value.Start;
if (KnockdownTimer.Value.End > end)
end = KnockdownTimer.Value.End;
}
if (SlowdownTimer != null)
{
if (SlowdownTimer.Value.Start < start)
start = SlowdownTimer.Value.Start;
if (SlowdownTimer.Value.End > end)
end = SlowdownTimer.Value.End;
}
return (start, end);
}
private void SetAlert()
{
if (!Owner.TryGetComponent(out SharedAlertsComponent? status))
{
return;
}
var timers = GetTimers();
if (timers == null)
return;
status.ShowAlert(AlertType.Stun, cooldown:timers);
StatusRemoveCancellation.Cancel();
StatusRemoveCancellation = new CancellationTokenSource();
}
protected virtual void OnInteractHand() { }
bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs)
{
if (!_canHelp || !KnockedDown)
{
return false;
}
_canHelp = false;
Owner.SpawnTimer((int) _helpInterval * 1000, () => _canHelp = true);
KnockdownTimer = (KnockdownTimer!.Value.Start, KnockdownTimer.Value.End.Subtract(TimeSpan.FromSeconds(_helpInterval)));
OnInteractHand();
SetAlert();
Dirty();
return true;
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new StunnableComponentState(StunnedTimer, KnockdownTimer, SlowdownTimer, WalkModifierOverride, RunModifierOverride);
}
protected virtual void OnKnockdownEnd()
{
}
public void Update(float delta)
{
var curTime = _gameTiming.CurTime;
if (StunnedTimer != null)
{
if (StunnedTimer.Value.End <= curTime)
{
StunnedTimer = null;
Dirty();
}
}
if (KnockdownTimer != null)
{
if (KnockdownTimer.Value.End <= curTime)
{
OnKnockdownEnd();
KnockdownTimer = null;
Dirty();
}
}
if (SlowdownTimer != null)
{
if (SlowdownTimer.Value.End <= curTime)
{
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? movement))
{
movement.RefreshMovementSpeedModifiers();
}
SlowdownTimer = null;
Dirty();
}
}
if (AnyStunActive || !Owner.TryGetComponent(out SharedAlertsComponent? status) || !status.IsShowingAlert(AlertType.Stun))
return;
status.ClearAlert(AlertType.Stun);
}
#region ActionBlockers
public bool CanInteract() => (!Stunned);
public bool CanUse() => (!Stunned);
public bool CanThrow() => (!Stunned);
public bool CanSpeak() => true;
public bool CanDrop() => (!Stunned);
public bool CanPickup() => (!Stunned);
public bool CanEmote() => true;
public bool CanAttack() => (!Stunned);
public bool CanEquip() => (!Stunned);
public bool CanUnequip() => (!Stunned);
public bool CanChangeDirection() => true;
public bool CanShiver() => !Stunned;
public bool CanSweat() => true;
#endregion
[ViewVariables]
public float WalkSpeedModifier => (SlowedDown ? (WalkModifierOverride <= 0f ? 0.5f : WalkModifierOverride) : 1f);
[ViewVariables]
public float SprintSpeedModifier => (SlowedDown ? (RunModifierOverride <= 0f ? 0.5f : RunModifierOverride) : 1f);
[Serializable, NetSerializable]
protected sealed class StunnableComponentState : ComponentState
{
public (TimeSpan Start, TimeSpan End)? StunnedTimer { get; }
public (TimeSpan Start, TimeSpan End)? KnockdownTimer { get; }
public (TimeSpan Start, TimeSpan End)? SlowdownTimer { get; }
public float WalkModifierOverride { get; }
public float RunModifierOverride { get; }
public StunnableComponentState(
(TimeSpan Start, TimeSpan End)? stunnedTimer, (TimeSpan Start, TimeSpan End)? knockdownTimer,
(TimeSpan Start, TimeSpan End)? slowdownTimer, float walkModifierOverride, float runModifierOverride)
{
StunnedTimer = stunnedTimer;
KnockdownTimer = knockdownTimer;
SlowdownTimer = slowdownTimer;
WalkModifierOverride = walkModifierOverride;
RunModifierOverride = runModifierOverride;
}
}
}
/// <summary>
/// This interface allows components to multiply the time in seconds of various stuns by a number.
/// </summary>
public interface IStunModifier
{
float StunTimeModifier => 1.0f;
float KnockdownTimeModifier => 1.0f;
float SlowdownTimeModifier => 1.0f;
}
}

View File

@@ -1,32 +0,0 @@
using Content.Shared.Movement;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Shared.Stunnable
{
[UsedImplicitly]
internal sealed class StunSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SharedStunnableComponent, MovementAttemptEvent>(HandleMoveAttempt);
}
private void HandleMoveAttempt(EntityUid uid, SharedStunnableComponent component, MovementAttemptEvent args)
{
if (component.Stunned)
args.Cancel();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var component in EntityManager.EntityQuery<SharedStunnableComponent>(true))
{
component.Update(frameTime);
}
}
}
}

View File

@@ -0,0 +1,96 @@
using System;
using Content.Shared.Movement.Components;
using Content.Shared.Sound;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Stunnable
{
[Friend(typeof(SharedStunSystem))]
[RegisterComponent, NetworkedComponent]
public sealed class StunnableComponent : Component, IMoveSpeedModifier
{
public sealed override string Name => "Stunnable";
public (TimeSpan Start, TimeSpan End)? StunnedTimer { get; set; }
public (TimeSpan Start, TimeSpan End)? KnockdownTimer { get; set; }
public (TimeSpan Start, TimeSpan End)? SlowdownTimer { get; set; }
[ViewVariables]
public float StunnedSeconds =>
StunnedTimer == null ? 0f : (float)(StunnedTimer.Value.End - StunnedTimer.Value.Start).TotalSeconds;
[ViewVariables]
public float KnockdownSeconds =>
KnockdownTimer == null ? 0f : (float)(KnockdownTimer.Value.End - KnockdownTimer.Value.Start).TotalSeconds;
[ViewVariables]
public float SlowdownSeconds =>
SlowdownTimer == null ? 0f : (float)(SlowdownTimer.Value.End - SlowdownTimer.Value.Start).TotalSeconds;
[ViewVariables]
public bool AnyStunActive => Stunned || KnockedDown || SlowedDown;
[ViewVariables]
public bool Stunned => StunnedTimer != null;
[ViewVariables]
public bool KnockedDown => KnockdownTimer != null;
[ViewVariables]
public bool SlowedDown => SlowdownTimer != null;
[DataField("stunCap")]
public float StunCap { get; set; } = 20f;
[DataField("knockdownCap")]
public float KnockdownCap { get; set; } = 20f;
[DataField("slowdownCap")]
public float SlowdownCap { get; set; } = 20f;
[DataField("helpInterval")]
public float HelpInterval { get; set; } = 1f;
[DataField("stunAttemptSound")]
public SoundSpecifier StunAttemptSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
[ViewVariables]
public float HelpTimer { get; set; } = 0f;
[ViewVariables]
public float WalkSpeedMultiplier { get; set; } = 0f;
[ViewVariables]
public float RunSpeedMultiplier { get; set; } = 0f;
[ViewVariables]
public float WalkSpeedModifier => (SlowedDown ? (WalkSpeedMultiplier <= 0f ? 0.5f : WalkSpeedMultiplier) : 1f);
[ViewVariables]
public float SprintSpeedModifier => (SlowedDown ? (RunSpeedMultiplier <= 0f ? 0.5f : RunSpeedMultiplier) : 1f);
}
[Serializable, NetSerializable]
public sealed class StunnableComponentState : ComponentState
{
public (TimeSpan Start, TimeSpan End)? StunnedTimer { get; }
public (TimeSpan Start, TimeSpan End)? KnockdownTimer { get; }
public (TimeSpan Start, TimeSpan End)? SlowdownTimer { get; }
public float WalkSpeedMultiplier { get; }
public float RunSpeedMultiplier { get; }
public StunnableComponentState(
(TimeSpan Start, TimeSpan End)? stunnedTimer, (TimeSpan Start, TimeSpan End)? knockdownTimer,
(TimeSpan Start, TimeSpan End)? slowdownTimer, float walkSpeedMultiplier, float runSpeedMultiplier)
{
StunnedTimer = stunnedTimer;
KnockdownTimer = knockdownTimer;
SlowdownTimer = slowdownTimer;
WalkSpeedMultiplier = walkSpeedMultiplier;
RunSpeedMultiplier = runSpeedMultiplier;
}
}
}

View File

@@ -51,7 +51,7 @@ namespace Content.Shared.Tabletop
protected static bool StunnedOrNoHands(IEntity playerEntity)
{
var stunned = playerEntity.TryGetComponent<SharedStunnableComponent>(out var stun) &&
var stunned = playerEntity.TryGetComponent<StunnableComponent>(out var stun) &&
stun.Stunned;
var hasHand = playerEntity.TryGetComponent<SharedHandsComponent>(out var handsComponent) &&
handsComponent.Hands.Count > 0;