using Content.Shared.Alert;
+using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
[DataField]
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 } };
}
using Content.Shared.Damage.Events;
using Content.Shared.Database;
using Content.Shared.Effects;
-using Content.Shared.IdentityManagement;
-using Content.Shared.Popups;
+using Content.Shared.FixedPoint;
using Content.Shared.Projectiles;
using Content.Shared.Rejuvenate;
using Content.Shared.Rounding;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Player;
-using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Shared.Damage.Systems;
}
component.StaminaDamage = 0;
+ AdjustSlowdown(uid);
RemComp<ActiveStaminaComponent>(uid);
SetStaminaAlert(uid, component);
Dirty(uid, component);
component.NextUpdate = nextUpdate;
}
- var slowdownThreshold = component.CritThreshold / 2f;
+ AdjustSlowdown(uid);
- // If we go above n% then apply slowdown
- if (oldDamage < slowdownThreshold &&
- component.StaminaDamage > slowdownThreshold)
+ 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)
{
- _stunSystem.TrySlowdown(uid, TimeSpan.FromSeconds(3), true, 0.8f, 0.8f);
+ component.AfterCritical = false; // Since the recovery from the crit has been completed, we are no longer 'after crit'
}
- SetStaminaAlert(uid, component);
-
if (!component.Critical)
{
if (component.StaminaDamage >= component.CritThreshold)
{
base.Update(frameTime);
- if (!_timing.IsFirstTimePredicted)
- return;
-
var stamQuery = GetEntityQuery<StaminaComponent>();
var query = EntityQueryEnumerator<ActiveStaminaComponent>();
var curTime = _timing.CurTime;
if (nextUpdate > curTime)
continue;
- // We were in crit so come out of it and continue.
+ // Handle exiting critical condition and restoring stamina damage
if (comp.Critical)
- {
ExitStamCrit(uid, comp);
- continue;
- }
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);
}
}
}
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;
+
SetStaminaAlert(uid, component);
- RemComp<ActiveStaminaComponent>(uid);
Dirty(uid, component);
_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]);
+ }
}
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Bed.Sleep;
+using Content.Shared.Damage.Components;
using Content.Shared.Database;
using Content.Shared.Hands;
using Content.Shared.Mobs;
slowed.SprintSpeedModifier *= runSpeedMultiplier;
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
-
return true;
}
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)
{
if (args.Handled || knocked.HelpTimer > 0f)