Overhauled stamina slowdown behavior (#36336)
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
@@ -56,4 +57,22 @@ public sealed partial class StaminaComponent : Component
|
|||||||
|
|
||||||
[DataField]
|
[DataField]
|
||||||
public ProtoId<AlertPrototype> StaminaAlert = "Stamina";
|
public ProtoId<AlertPrototype> StaminaAlert = "Stamina";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This flag indicates whether the value of <see cref="StaminaDamage"/> decreases after the entity exits stamina crit.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool AfterCritical;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This float determines how fast stamina will regenerate after exiting the stamina crit.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public float AfterCritDecayMultiplier = 5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Thresholds that determine an entity's slowdown as a function of stamina damage.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public Dictionary<FixedPoint2, float> StunModifierThresholds = new() { {0, 1f }, { 60, 0.7f }, { 80, 0.5f } };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ using Content.Shared.Damage.Components;
|
|||||||
using Content.Shared.Damage.Events;
|
using Content.Shared.Damage.Events;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Effects;
|
using Content.Shared.Effects;
|
||||||
using Content.Shared.IdentityManagement;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Popups;
|
|
||||||
using Content.Shared.Projectiles;
|
using Content.Shared.Projectiles;
|
||||||
using Content.Shared.Rejuvenate;
|
using Content.Shared.Rejuvenate;
|
||||||
using Content.Shared.Rounding;
|
using Content.Shared.Rounding;
|
||||||
@@ -21,7 +20,6 @@ using Robust.Shared.Audio.Systems;
|
|||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Random;
|
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Shared.Damage.Systems;
|
namespace Content.Shared.Damage.Systems;
|
||||||
@@ -114,6 +112,7 @@ public sealed partial class StaminaSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
component.StaminaDamage = 0;
|
component.StaminaDamage = 0;
|
||||||
|
AdjustSlowdown(uid);
|
||||||
RemComp<ActiveStaminaComponent>(uid);
|
RemComp<ActiveStaminaComponent>(uid);
|
||||||
SetStaminaAlert(uid, component);
|
SetStaminaAlert(uid, component);
|
||||||
Dirty(uid, component);
|
Dirty(uid, component);
|
||||||
@@ -275,17 +274,16 @@ public sealed partial class StaminaSystem : EntitySystem
|
|||||||
component.NextUpdate = nextUpdate;
|
component.NextUpdate = nextUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
var slowdownThreshold = component.CritThreshold / 2f;
|
AdjustSlowdown(uid);
|
||||||
|
|
||||||
// If we go above n% then apply slowdown
|
|
||||||
if (oldDamage < slowdownThreshold &&
|
|
||||||
component.StaminaDamage > slowdownThreshold)
|
|
||||||
{
|
|
||||||
_stunSystem.TrySlowdown(uid, TimeSpan.FromSeconds(3), true, 0.8f, 0.8f);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetStaminaAlert(uid, component);
|
SetStaminaAlert(uid, component);
|
||||||
|
|
||||||
|
// Checking if the stamina damage has decreased to zero after exiting the stamcrit
|
||||||
|
if (component.AfterCritical && oldDamage > component.StaminaDamage && component.StaminaDamage <= 0f)
|
||||||
|
{
|
||||||
|
component.AfterCritical = false; // Since the recovery from the crit has been completed, we are no longer 'after crit'
|
||||||
|
}
|
||||||
|
|
||||||
if (!component.Critical)
|
if (!component.Critical)
|
||||||
{
|
{
|
||||||
if (component.StaminaDamage >= component.CritThreshold)
|
if (component.StaminaDamage >= component.CritThreshold)
|
||||||
@@ -330,9 +328,6 @@ public sealed partial class StaminaSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
|
|
||||||
if (!_timing.IsFirstTimePredicted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var stamQuery = GetEntityQuery<StaminaComponent>();
|
var stamQuery = GetEntityQuery<StaminaComponent>();
|
||||||
var query = EntityQueryEnumerator<ActiveStaminaComponent>();
|
var query = EntityQueryEnumerator<ActiveStaminaComponent>();
|
||||||
var curTime = _timing.CurTime;
|
var curTime = _timing.CurTime;
|
||||||
@@ -353,15 +348,17 @@ public sealed partial class StaminaSystem : EntitySystem
|
|||||||
if (nextUpdate > curTime)
|
if (nextUpdate > curTime)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// We were in crit so come out of it and continue.
|
// Handle exiting critical condition and restoring stamina damage
|
||||||
if (comp.Critical)
|
if (comp.Critical)
|
||||||
{
|
|
||||||
ExitStamCrit(uid, comp);
|
ExitStamCrit(uid, comp);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
comp.NextUpdate += TimeSpan.FromSeconds(1f);
|
comp.NextUpdate += TimeSpan.FromSeconds(1f);
|
||||||
TakeStaminaDamage(uid, -comp.Decay, comp);
|
|
||||||
|
TakeStaminaDamage(
|
||||||
|
uid,
|
||||||
|
comp.AfterCritical ? -comp.Decay * comp.AfterCritDecayMultiplier : -comp.Decay, // Recover faster after crit
|
||||||
|
comp);
|
||||||
|
|
||||||
Dirty(uid, comp);
|
Dirty(uid, comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -398,11 +395,38 @@ public sealed partial class StaminaSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
component.Critical = false;
|
component.Critical = false;
|
||||||
component.StaminaDamage = 0f;
|
component.AfterCritical = true; // Set to true to indicate that stamina will be restored after exiting stamcrit
|
||||||
component.NextUpdate = _timing.CurTime;
|
component.NextUpdate = _timing.CurTime;
|
||||||
|
|
||||||
SetStaminaAlert(uid, component);
|
SetStaminaAlert(uid, component);
|
||||||
RemComp<ActiveStaminaComponent>(uid);
|
|
||||||
Dirty(uid, component);
|
Dirty(uid, component);
|
||||||
_adminLogger.Add(LogType.Stamina, LogImpact.Low, $"{ToPrettyString(uid):user} recovered from stamina crit");
|
_adminLogger.Add(LogType.Stamina, LogImpact.Low, $"{ToPrettyString(uid):user} recovered from stamina crit");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adjusts the movement speed of an entity based on its current <see cref="StaminaComponent.StaminaDamage"/> value.
|
||||||
|
/// If the entity has a <see cref="SlowOnDamageComponent"/>, its custom damage-to-speed thresholds are used,
|
||||||
|
/// otherwise, a default set of thresholds is applied.
|
||||||
|
/// The method determines the closest applicable damage threshold below the crit limit and applies the corresponding
|
||||||
|
/// speed modifier using the stun system. If no threshold is met then the entity's speed is restored to normal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Entity to update</param>
|
||||||
|
private void AdjustSlowdown(Entity<StaminaComponent?> ent)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var closest = FixedPoint2.Zero;
|
||||||
|
|
||||||
|
// Iterate through the dictionary in the similar way as in Damage.SlowOnDamageSystem.OnRefreshMovespeed
|
||||||
|
foreach (var thres in ent.Comp.StunModifierThresholds)
|
||||||
|
{
|
||||||
|
var key = thres.Key.Float();
|
||||||
|
|
||||||
|
if (ent.Comp.StaminaDamage >= key && key > closest && closest < ent.Comp.CritThreshold)
|
||||||
|
closest = thres.Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
_stunSystem.UpdateStunModifiers(ent, ent.Comp.StunModifierThresholds[closest]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Content.Shared.Interaction.Events;
|
|||||||
using Content.Shared.Inventory.Events;
|
using Content.Shared.Inventory.Events;
|
||||||
using Content.Shared.Item;
|
using Content.Shared.Item;
|
||||||
using Content.Shared.Bed.Sleep;
|
using Content.Shared.Bed.Sleep;
|
||||||
|
using Content.Shared.Damage.Components;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Hands;
|
using Content.Shared.Hands;
|
||||||
using Content.Shared.Mobs;
|
using Content.Shared.Mobs;
|
||||||
@@ -248,13 +249,63 @@ public abstract class SharedStunSystem : EntitySystem
|
|||||||
slowed.SprintSpeedModifier *= runSpeedMultiplier;
|
slowed.SprintSpeedModifier *= runSpeedMultiplier;
|
||||||
|
|
||||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the movement speed modifiers of an entity by applying or removing the <see cref="SlowedDownComponent"/>.
|
||||||
|
/// If both walk and run modifiers are approximately 1 (i.e. normal speed) and <see cref="StaminaComponent.StaminaDamage"/> is 0,
|
||||||
|
/// or if the both modifiers are 0, the slowdown component is removed to restore normal movement.
|
||||||
|
/// Otherwise, the slowdown component is created or updated with the provided modifiers,
|
||||||
|
/// and the movement speed is refreshed accordingly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Entity whose movement speed should be updated.</param>
|
||||||
|
/// <param name="walkSpeedModifier">New walk speed modifier. Default is 1f (normal speed).</param>
|
||||||
|
/// <param name="runSpeedModifier">New run (sprint) speed modifier. Default is 1f (normal speed).</param>
|
||||||
|
public void UpdateStunModifiers(Entity<StaminaComponent?> ent,
|
||||||
|
float walkSpeedModifier = 1f,
|
||||||
|
float runSpeedModifier = 1f)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(MathHelper.CloseTo(walkSpeedModifier, 1f) && MathHelper.CloseTo(runSpeedModifier, 1f) && ent.Comp.StaminaDamage == 0f) ||
|
||||||
|
(walkSpeedModifier == 0f && runSpeedModifier == 0f)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RemComp<SlowedDownComponent>(ent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureComp<SlowedDownComponent>(ent, out var comp);
|
||||||
|
|
||||||
|
comp.WalkSpeedModifier = walkSpeedModifier;
|
||||||
|
|
||||||
|
comp.SprintSpeedModifier = runSpeedModifier;
|
||||||
|
|
||||||
|
_movementSpeedModifier.RefreshMovementSpeedModifiers(ent);
|
||||||
|
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A convenience overload of <see cref="UpdateStunModifiers(EntityUid, float, float, StaminaComponent?)"/> that sets both
|
||||||
|
/// walk and run speed modifiers to the same value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">Entity whose movement speed should be updated.</param>
|
||||||
|
/// <param name="speedModifier">New walk and run speed modifier. Default is 1f (normal speed).</param>
|
||||||
|
/// <param name="component">
|
||||||
|
/// Optional <see cref="StaminaComponent"/> of the entity.
|
||||||
|
/// </param>
|
||||||
|
public void UpdateStunModifiers(Entity<StaminaComponent?> ent, float speedModifier = 1f)
|
||||||
|
{
|
||||||
|
UpdateStunModifiers(ent, speedModifier, speedModifier);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnInteractHand(EntityUid uid, KnockedDownComponent knocked, InteractHandEvent args)
|
private void OnInteractHand(EntityUid uid, KnockedDownComponent knocked, InteractHandEvent args)
|
||||||
{
|
{
|
||||||
if (args.Handled || knocked.HelpTimer > 0f)
|
if (args.Handled || knocked.HelpTimer > 0f)
|
||||||
|
|||||||
Reference in New Issue
Block a user