mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-10-03 09:37:27 +00:00
* Fix the sensor monitoring console forcing a GC every 3 seconds (#38146) * Optimize sensor monitoring window graph drawing * Add shared static Vector2 pool for all GraphView instances * Address requested changes * remove lock * Update submodule to 264.0.0 (#38629) * Toy/Plushie Inhands and Wearables (#38514) * Fixed dependency injection and some other issues in a few places. * More issue fixes * patchfor broken NetworkConfiguratorLinkMenu (#38632) fix * Fix solutions flickering when transferring contents (#34838) * Use Solution clones when applying SolutionComponent states * Revert "Use Solution clones when applying SolutionComponent states" This reverts commit 013fd111cf92b22562e00f98a7aaa49bc4b4ed62. * Make Solution implement ICloneable and rename Clone method. * Copy CanReact value when cloning a Solution * Convert to IRobustCloneable * Fix typos in guidebook: Buisness → Business (#38636) * Fix typo in `MinorAntagonists.xml` Buisness → Business * Fix typo in `YourFirstCharacter.xml` Buisness → Business * Various Headphones Fixes and Tweaks (#38479) * sprites, wearables * neck * icon-on sprite * Cleanup prototype instantiation in `DamageTest` (#38639) Cleanup prototype instantiation in DamageTest * Cleanup prototype instantiation in `ExplosionSystem` (#38642) Cleanup prototype instantiation in ExplosionSystem * AddBodyPartCommand localization. (#38612) commit * fix ItemSlotsSystem debug assert (#38655) * Allow the Command & Super door remotes to use the access of their user. (Re-creation of PR due to changes to game balance) (#35536) * Added directional beacons (#38284) * Added directional beacons Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Fixed names Signed-off-by: Nox38 <nebulousnox38@gmail.com> --------- Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Power stat and nuke codes commands get some LEC love. (#38585) * commit * requested changes. * Dsay Dirty and Follow commands converted to LEC and localized. (#38666) * commit * whoopwhoopwhoop * Retro laser sprite fix (#38676) * Fixed everything except the icon Signed-off-by: Nox38 <nebulousnox38@gmail.com> * fixed icon Signed-off-by: Nox38 <nebulousnox38@gmail.com> --------- Signed-off-by: Nox38 <nebulousnox38@gmail.com> * fix water coolers (#38681) * Monochromacy typo fix (#38686) * fixes the typo * Fixed cloning looking for the trait, not the component, RE https://github.com/space-wizards/space-station-14/pull/38686#issuecomment-3025093504 * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Validate `CloningSettingsPrototype`s (#38688) * Validate CloningSettingsPrototypes * Update Content.IntegrationTests/Tests/Cloning/CloningSettingsPrototypeTest.cs Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Check EventComponents too --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Add test of objective-related console commands (#36400) * Add test of objective add/list/remove commands * Not sure why we're validating test prototypes, but sure * We don't need a map * Improvements and fixups for New Status Effect API (#38660) * Predict healing and bloodstream (#38690) * initial commit * reapply 38126 * fix rootable * someone missed an important minus sign here * try this * fix * fix * reenable crit hits * cleanup * fix status time dirtying * fix * camelCase * Healing and bloodstream prediction fixes. * Resolving Wizard casting recall on nuke disk making it impossible to disarm (#38661) * Resolving Wizard Recall on Nuke disk making it impossible to disarm - Adding a DisarmBomb case to nuke status update loop - Changing a few methods and parameters to properly follow formatting standards - Updating some names to follow camelCase * Updating missed tag * Reverting DataField change Should prevent this preventative bugfix being a breaking change. * Switch HSV to the default colorspace for character customization (#38434) * Made HSV default for character editor * Adds/fixes comments to HSV defaulting * Added dropbox fix, potentially cursed * Revert "Added dropbox fix, potentially cursed" This reverts commit a709883366fbee813e839742125e70844672af29. --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * Fix: Don't deploy foldables when clicking on items inside containers (#38709) * Fix * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Pressure Relief Valve (#36708) * initial system (this math is probably WRONG) * General code cleanup and OnExamined support (holy moly this code sucks) * UICode and related events foundation TODO: - Actually write the XAML UI and the underlying system - Un-shitcode the entire thing - Actually test everything... * Working UI code TODO: Make predicted, as this certainly isn't predicted. Even though I said it was. It isn't. * Remove one TODO for unshitcoding the examine code * Add reminder yea * Make predicted (defenitely isn't) (also defenitely isn't a copypaste from pressure pump code) * It's predicted! TODO: - Give it snazzy predicted visuals! - Have a different field for pressure entry, lest it gets bulldozed every UI update. * Improve gas pressure relief valve UI TODO: Reminder to reduce amount of dirties using deltafields * Implement DirtyField prediction * Entity<T> cleanup A lot of Entity<T> conversions and lukewarm cleanup. Also got caught copy pasting code in 4K UHD but it's not like you couldn't tell. * More cleanup and comments * Remove TODO comment on bulldozing window title * """refactoring""" - Move appearance out of shared and finally fix it. Pointless to predict appearance in this instance. - More Entity<T> conversions because I like them. - Move UI creation handling over entirely to the ActivatableUI system. - Fix a hardcoded locale string (why????). * Add visuals * Revert debugging variable replacememt yea * Revert skissue * Remove unused using directives and remove TODO * Localize, cleanup, document * Fix adminlogging discrepancy * Add ability to construct, add guidebook entry * Clear up comment * Add guidebook tooltip to valve * Convert GasPressureReliefValveBoundUserInterface declaration into primary constructor * Adds more input handling and adds autofill on open * Un-deepfry input validator shitcode Genuinely what was I smoking * improve visuals logic * Refactor again - Update math to the correct implementation - Moved code that could be re-used in the future into a helper method under AtmosphereSystem.Gases.cs * I'm sorry but I hate warnings * Remove unused using directive in AtmosphereSystem.Gases.cs * Review and cleanup * Lukewarm UI glossup * Maintainer for the upstream project btw * Remove redundant state sets and messy logic * Unduplicate valve updater code * Redo UI (im sorry Slarti) * run tests * Test refactored UI messaging * Second round of UI improvements - God please find a way to improve this system. Feels bad. * Update loop implementation * Further predict UI * Clear up SetToCurrentThreshold * cleanup * Update to master + pipe layers and bug fixes want to run tests * fixes * Deploy rename pipebomb * Documentation and requested changes * Rename the method that wiggled away * Undo rounding changes * Fix comment * Rename and cleanup * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Fix TextLinkTag (#32203) * Adjust uplink buy button to be under item icon (#38596) * Adjusted uplink buy button to be under item icon * Put the discount subtext under the icon * Indent fixes, added margin --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * Scurrets - Audio Improvements (#38482) * Scurret audio tuning * Add new sfx * Update sneezing sfx * YAML support * Rename a folder * make telesci wreck easier (#37569) rel * Validate `ShaderPrototype` IDs (#38728) * Convert all shader prototype string literals to protoids in overlays * Convert more shader prototype literal strings to protoids * Convert ValidatePrototypeId to ProtoId * Later * [BUGFIX] Fixed revenant malfunction ability not working properly only MediBots and Stasis bed (#38664) * fixed * clean up * orks fix smart * review fix 1 * more requested changes * less cursed * more descriptive description * better wording * Tiny Tiny Cleanup of the EyeClosingSystem. (#38734) Update EyeClosingSystem.cs * Fix human skin tone distribution (#38701) * fix: Use PredictedQueueDel for gib spell (#38729) * fix: don't default to uncharged sprite state for cells (#38730) * Fix Hristov description - remove inaccurate technical specs (#38746) - Removes inaccurate 'armor piercing 14.5mm shells' reference - Replaces it with a more funny description, matching the style of the other snipers and guns - Keeps ammunition type '.60 anti-materiel ammo' specification - Fixes issue #38590 Co-authored-by: Arthur Fiorese de Andrade <aandrade@cmcxs.gov.br> * feat: allow mopping evaporating puddles (#38743) * Validate remaining `ProtoId` strings (#38747) Validate remaining ProtoId strings * Validate `ProtoId`s in tests (#38745) * Convert string literals to protoids in Content.Tests * Convert string literals to protoids or consts in Content.IntegrationTests * Fix linter failures Tricksy static using misled me * Cleanup warning in StomachSystem (#38748) you did not see this * Vox scars (#38592) * Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl * Revert "Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl" This reverts commit c73da55ba3b39ddf93b493aecd85604c54dd8a15. * locale key fix * Changed top surgery scar names to be more generalized * Adjusted face scars * Formatting fixes --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * UnlockNode command to LEC. (#38751) * commit * Update UnlockNodeCommand.cs * commit * move command locale to its own file. * Update Content.Server/Xenoarchaeology/Artifact/UnlockNodeCommand.cs --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Typofixes for figurine dialogue (#38737) * Typofixes for figurine dialogue * Forgot two * Janitor Tool: Wire Brush (#38667) * Wow! It's -brush- * spacing. * Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * fixed changes --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Minor escape menu UX improvements (#38650) * fix: spellbooks can have infinite charges (#38376) * fix: spellbooks can have infinite charges * refactor: indicate infinite spellbook charges with null Not sure if I like this much better...
453 lines
16 KiB
C#
453 lines
16 KiB
C#
using System.Linq;
|
|
using Content.Shared.Administration.Logs;
|
|
using Content.Shared.Alert;
|
|
using Content.Shared.CCVar;
|
|
using Content.Shared.CombatMode;
|
|
using Content.Shared.Damage.Components;
|
|
using Content.Shared.Damage.Events;
|
|
using Content.Shared.Database;
|
|
using Content.Shared.Effects;
|
|
using Content.Shared.FixedPoint;
|
|
using Content.Shared.Projectiles;
|
|
using Content.Shared.Rejuvenate;
|
|
using Content.Shared.Rounding;
|
|
using Content.Shared.Stunnable;
|
|
using Content.Shared.Throwing;
|
|
using Content.Shared.Weapons.Melee; // DeltaV
|
|
using Content.Shared.Weapons.Melee.Events;
|
|
using JetBrains.Annotations;
|
|
using Robust.Shared.Audio;
|
|
using Robust.Shared.Audio.Systems;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Serialization;
|
|
using Robust.Shared.Timing;
|
|
|
|
namespace Content.Shared.Damage.Systems;
|
|
|
|
public abstract partial class SharedStaminaSystem : EntitySystem
|
|
{
|
|
[Dependency] protected readonly IGameTiming Timing = default!;
|
|
[Dependency] private readonly INetManager _net = default!;
|
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
|
[Dependency] private readonly AlertsSystem _alerts = default!;
|
|
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
|
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
|
[Dependency] protected readonly SharedStunSystem StunSystem = default!;
|
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
|
[Dependency] private readonly IConfigurationManager _config = default!;
|
|
|
|
/// <summary>
|
|
/// How much of a buffer is there between the stun duration and when stuns can be re-applied.
|
|
/// </summary>
|
|
protected static readonly TimeSpan StamCritBufferTime = TimeSpan.FromSeconds(3f);
|
|
|
|
public float UniversalStaminaDamageModifier { get; private set; } = 1f;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
InitializeModifier();
|
|
InitializeResistance();
|
|
|
|
SubscribeLocalEvent<StaminaComponent, ComponentStartup>(OnStartup);
|
|
SubscribeLocalEvent<StaminaComponent, ComponentShutdown>(OnShutdown);
|
|
SubscribeLocalEvent<StaminaComponent, AfterAutoHandleStateEvent>(OnStamHandleState);
|
|
SubscribeLocalEvent<StaminaComponent, DisarmedEvent>(OnDisarmed);
|
|
SubscribeLocalEvent<StaminaComponent, RejuvenateEvent>(OnRejuvenate);
|
|
|
|
SubscribeLocalEvent<StaminaDamageOnEmbedComponent, EmbedEvent>(OnProjectileEmbed);
|
|
|
|
SubscribeLocalEvent<StaminaDamageOnCollideComponent, ProjectileHitEvent>(OnProjectileHit);
|
|
SubscribeLocalEvent<StaminaDamageOnCollideComponent, ThrowDoHitEvent>(OnThrowHit);
|
|
|
|
SubscribeLocalEvent<StaminaDamageOnHitComponent, MeleeHitEvent>(OnMeleeHit);
|
|
|
|
Subs.CVar(_config, CCVars.PlaytestStaminaDamageModifier, value => UniversalStaminaDamageModifier = value, true);
|
|
}
|
|
|
|
protected virtual void OnStamHandleState(Entity<StaminaComponent> entity, ref AfterAutoHandleStateEvent args)
|
|
{
|
|
if (entity.Comp.Critical)
|
|
EnterStamCrit(entity);
|
|
else
|
|
{
|
|
if (entity.Comp.StaminaDamage > 0f)
|
|
EnsureComp<ActiveStaminaComponent>(entity);
|
|
|
|
ExitStamCrit(entity);
|
|
}
|
|
}
|
|
|
|
protected virtual void OnShutdown(Entity<StaminaComponent> entity, ref ComponentShutdown args)
|
|
{
|
|
if (MetaData(entity).EntityLifeStage < EntityLifeStage.Terminating)
|
|
{
|
|
RemCompDeferred<ActiveStaminaComponent>(entity);
|
|
}
|
|
_alerts.ClearAlert(entity, entity.Comp.StaminaAlert);
|
|
}
|
|
|
|
private void OnStartup(Entity<StaminaComponent> entity, ref ComponentStartup args)
|
|
{
|
|
UpdateStaminaVisuals(entity);
|
|
}
|
|
|
|
[PublicAPI]
|
|
public float GetStaminaDamage(EntityUid uid, StaminaComponent? component = null)
|
|
{
|
|
if (!Resolve(uid, ref component))
|
|
return 0f;
|
|
|
|
var curTime = Timing.CurTime;
|
|
var pauseTime = _metadata.GetPauseTime(uid);
|
|
return MathF.Max(0f, component.StaminaDamage - MathF.Max(0f, (float) (curTime - (component.NextUpdate + pauseTime)).TotalSeconds * component.Decay));
|
|
}
|
|
|
|
private void OnRejuvenate(Entity<StaminaComponent> entity, ref RejuvenateEvent args)
|
|
{
|
|
if (entity.Comp.StaminaDamage >= entity.Comp.CritThreshold)
|
|
{
|
|
ExitStamCrit(entity, entity.Comp);
|
|
}
|
|
|
|
entity.Comp.StaminaDamage = 0;
|
|
AdjustSlowdown(entity.Owner);
|
|
RemComp<ActiveStaminaComponent>(entity);
|
|
UpdateStaminaVisuals(entity);
|
|
Dirty(entity);
|
|
}
|
|
|
|
private void OnDisarmed(EntityUid uid, StaminaComponent component, ref DisarmedEvent args)
|
|
{
|
|
if (args.Handled)
|
|
return;
|
|
|
|
if (component.Critical)
|
|
return;
|
|
|
|
var damage = args.PushProbability * component.CritThreshold;
|
|
TakeStaminaDamage(uid, damage, component, source: args.Source);
|
|
|
|
args.PopupPrefix = "disarm-action-shove-";
|
|
args.IsStunned = component.Critical;
|
|
|
|
args.Handled = true;
|
|
}
|
|
|
|
private void OnMeleeHit(EntityUid uid, StaminaDamageOnHitComponent component, MeleeHitEvent args)
|
|
{
|
|
if (!args.IsHit ||
|
|
!args.HitEntities.Any() ||
|
|
component.Damage <= 0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var ev = new StaminaDamageOnHitAttemptEvent();
|
|
RaiseLocalEvent(uid, ref ev);
|
|
if (ev.Cancelled)
|
|
return;
|
|
|
|
var stamQuery = GetEntityQuery<StaminaComponent>();
|
|
var toHit = new List<(EntityUid Entity, StaminaComponent Component)>();
|
|
|
|
// Split stamina damage between all eligible targets.
|
|
foreach (var ent in args.HitEntities)
|
|
{
|
|
if (!stamQuery.TryGetComponent(ent, out var stam))
|
|
continue;
|
|
|
|
toHit.Add((ent, stam));
|
|
}
|
|
|
|
var hitEvent = new StaminaMeleeHitEvent(toHit);
|
|
RaiseLocalEvent(uid, hitEvent);
|
|
|
|
if (hitEvent.Handled)
|
|
return;
|
|
|
|
// Begin DeltaV additions
|
|
// Allow users to modifier stamina damage as well, this part of the event is not handle-able by listeners.
|
|
RaiseLocalEvent(args.User, hitEvent);
|
|
// End DeltaV additions
|
|
|
|
var damage = component.Damage;
|
|
|
|
damage *= hitEvent.Multiplier;
|
|
|
|
damage += hitEvent.FlatModifier;
|
|
|
|
foreach (var (ent, comp) in toHit)
|
|
{
|
|
TakeStaminaDamage(ent, damage / toHit.Count, comp, source: args.User, with: args.Weapon, sound: component.Sound);
|
|
}
|
|
}
|
|
|
|
private void OnProjectileHit(EntityUid uid, StaminaDamageOnCollideComponent component, ref ProjectileHitEvent args)
|
|
{
|
|
OnCollide(uid, component, args.Target);
|
|
}
|
|
|
|
private void OnProjectileEmbed(EntityUid uid, StaminaDamageOnEmbedComponent component, ref EmbedEvent args)
|
|
{
|
|
if (!TryComp<StaminaComponent>(args.Embedded, out var stamina))
|
|
return;
|
|
|
|
TakeStaminaDamage(args.Embedded, component.Damage, stamina, source: uid);
|
|
}
|
|
|
|
private void OnThrowHit(EntityUid uid, StaminaDamageOnCollideComponent component, ThrowDoHitEvent args)
|
|
{
|
|
OnCollide(uid, component, args.Target);
|
|
}
|
|
|
|
private void OnCollide(EntityUid uid, StaminaDamageOnCollideComponent component, EntityUid target)
|
|
{
|
|
// you can't inflict stamina damage on things with no stamina component
|
|
// this prevents stun batons from using up charges when throwing it at lockers or lights
|
|
if (!HasComp<StaminaComponent>(target))
|
|
return;
|
|
|
|
var ev = new StaminaDamageOnHitAttemptEvent();
|
|
RaiseLocalEvent(uid, ref ev);
|
|
if (ev.Cancelled)
|
|
return;
|
|
|
|
TakeStaminaDamage(target, component.Damage, source: uid, sound: component.Sound);
|
|
}
|
|
|
|
private void UpdateStaminaVisuals(Entity<StaminaComponent> entity)
|
|
{
|
|
SetStaminaAlert(entity, entity.Comp);
|
|
SetStaminaAnimation(entity);
|
|
}
|
|
|
|
// Here so server can properly tell all clients in PVS range to start the animation
|
|
protected virtual void SetStaminaAnimation(Entity<StaminaComponent> entity){}
|
|
|
|
private void SetStaminaAlert(EntityUid uid, StaminaComponent? component = null)
|
|
{
|
|
if (!Resolve(uid, ref component, false) || component.Deleted)
|
|
return;
|
|
|
|
var severity = ContentHelpers.RoundToLevels(MathF.Max(0f, component.CritThreshold - component.StaminaDamage), component.CritThreshold, 7);
|
|
_alerts.ShowAlert(uid, component.StaminaAlert, (short) severity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to take stamina damage without raising the entity over the crit threshold.
|
|
/// </summary>
|
|
public bool TryTakeStamina(EntityUid uid, float value, StaminaComponent? component = null, EntityUid? source = null, EntityUid? with = null)
|
|
{
|
|
// Something that has no Stamina component automatically passes stamina checks
|
|
if (!Resolve(uid, ref component, false))
|
|
return true;
|
|
|
|
var oldStam = component.StaminaDamage;
|
|
|
|
if (oldStam + value > component.CritThreshold || component.Critical)
|
|
return false;
|
|
|
|
TakeStaminaDamage(uid, value, component, source, with, visual: false);
|
|
return true;
|
|
}
|
|
|
|
public void TakeStaminaDamage(EntityUid uid, float value, StaminaComponent? component = null,
|
|
EntityUid? source = null, EntityUid? with = null, bool visual = true, SoundSpecifier? sound = null, bool ignoreResist = false)
|
|
{
|
|
if (!Resolve(uid, ref component, false))
|
|
return;
|
|
|
|
var ev = new BeforeStaminaDamageEvent(value, HasComp<MeleeWeaponComponent>(source)); // DeltaV - check if the source is a melee weapon
|
|
RaiseLocalEvent(uid, ref ev);
|
|
if (ev.Cancelled)
|
|
return;
|
|
|
|
// Allow stamina resistance to be applied.
|
|
if (!ignoreResist)
|
|
{
|
|
value = ev.Value;
|
|
}
|
|
|
|
value = UniversalStaminaDamageModifier * value;
|
|
|
|
// Have we already reached the point of max stamina damage?
|
|
if (component.Critical)
|
|
return;
|
|
|
|
var oldDamage = component.StaminaDamage;
|
|
component.StaminaDamage = MathF.Max(0f, component.StaminaDamage + value);
|
|
|
|
// Reset the decay cooldown upon taking damage.
|
|
if (oldDamage < component.StaminaDamage)
|
|
{
|
|
var nextUpdate = Timing.CurTime + TimeSpan.FromSeconds(component.Cooldown);
|
|
|
|
if (component.NextUpdate < nextUpdate)
|
|
component.NextUpdate = nextUpdate;
|
|
}
|
|
|
|
AdjustSlowdown(uid);
|
|
|
|
UpdateStaminaVisuals((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.StaminaDamage >= component.CritThreshold)
|
|
{
|
|
EnterStamCrit(uid, component);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (component.StaminaDamage < component.CritThreshold)
|
|
{
|
|
ExitStamCrit(uid, component);
|
|
}
|
|
}
|
|
|
|
EnsureComp<ActiveStaminaComponent>(uid);
|
|
Dirty(uid, component);
|
|
|
|
if (value <= 0)
|
|
return;
|
|
if (source != null)
|
|
{
|
|
_adminLogger.Add(LogType.Stamina, $"{ToPrettyString(source.Value):user} caused {value} stamina damage to {ToPrettyString(uid):target}{(with != null ? $" using {ToPrettyString(with.Value):using}" : "")}");
|
|
}
|
|
else
|
|
{
|
|
_adminLogger.Add(LogType.Stamina, $"{ToPrettyString(uid):target} took {value} stamina damage");
|
|
}
|
|
|
|
if (visual)
|
|
{
|
|
_color.RaiseEffect(Color.Aqua, new List<EntityUid>() { uid }, Filter.Pvs(uid, entityManager: EntityManager));
|
|
}
|
|
|
|
if (_net.IsServer)
|
|
{
|
|
_audio.PlayPvs(sound, uid);
|
|
}
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
base.Update(frameTime);
|
|
|
|
var stamQuery = GetEntityQuery<StaminaComponent>();
|
|
var query = EntityQueryEnumerator<ActiveStaminaComponent>();
|
|
var curTime = Timing.CurTime;
|
|
|
|
while (query.MoveNext(out var uid, out _))
|
|
{
|
|
// Just in case we have active but not stamina we'll check and account for it.
|
|
if (!stamQuery.TryGetComponent(uid, out var comp) ||
|
|
comp.StaminaDamage <= 0f && !comp.Critical)
|
|
{
|
|
RemComp<ActiveStaminaComponent>(uid);
|
|
continue;
|
|
}
|
|
|
|
// Shouldn't need to consider paused time as we're only iterating non-paused stamina components.
|
|
var nextUpdate = comp.NextUpdate;
|
|
|
|
if (nextUpdate > curTime)
|
|
continue;
|
|
|
|
// Handle exiting critical condition and restoring stamina damage
|
|
if (comp.Critical)
|
|
ExitStamCrit(uid, comp);
|
|
|
|
comp.NextUpdate += TimeSpan.FromSeconds(1f);
|
|
|
|
TakeStaminaDamage(
|
|
uid,
|
|
comp.AfterCritical ? -comp.Decay * comp.AfterCritDecayMultiplier : -comp.Decay, // Recover faster after crit
|
|
comp);
|
|
|
|
Dirty(uid, comp);
|
|
}
|
|
}
|
|
|
|
private void EnterStamCrit(EntityUid uid, StaminaComponent? component = null)
|
|
{
|
|
if (!Resolve(uid, ref component) ||
|
|
component.Critical)
|
|
{
|
|
return;
|
|
}
|
|
|
|
component.Critical = true;
|
|
component.StaminaDamage = component.CritThreshold;
|
|
|
|
if (StunSystem.TryParalyze(uid, component.StunTime, true))
|
|
StunSystem.TrySeeingStars(uid);
|
|
|
|
// Give them buffer before being able to be re-stunned
|
|
component.NextUpdate = Timing.CurTime + component.StunTime + StamCritBufferTime;
|
|
EnsureComp<ActiveStaminaComponent>(uid);
|
|
Dirty(uid, component);
|
|
_adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} entered stamina crit");
|
|
}
|
|
|
|
private void ExitStamCrit(EntityUid uid, StaminaComponent? component = null)
|
|
{
|
|
if (!Resolve(uid, ref component) ||
|
|
!component.Critical)
|
|
{
|
|
return;
|
|
}
|
|
|
|
component.Critical = false;
|
|
component.AfterCritical = true; // Set to true to indicate that stamina will be restored after exiting stamcrit
|
|
component.NextUpdate = Timing.CurTime;
|
|
|
|
UpdateStaminaVisuals((uid, component));
|
|
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]);
|
|
}
|
|
|
|
[Serializable, NetSerializable]
|
|
public sealed class StaminaAnimationEvent(NetEntity entity) : EntityEventArgs
|
|
{
|
|
public NetEntity Entity = entity;
|
|
}
|
|
}
|