From 29aceb4e03825595b006acf177fa798ddc050da4 Mon Sep 17 00:00:00 2001 From: Samuka <47865393+Samuka-C@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:31:15 -0300 Subject: [PATCH] Move logic from EvenHealthChangeEntityEffectSystem to the damage system API (#41684) * add two methods * move stuff to damage system api * use TryIndex * simplify * minor fix * add helper functions * fix * remove random new line * simplify * remove unnecessary lines * rename to GetDamage * Got it working * make more clear * why backwards * value should be the amount to heal * fix * fix all dumb fixedpoint2 edge cases I hope * One more thing * fix * make it more simple * ops it was backwards * valueHeal can't be more than remaining * add all keys beforehand and no need to check and add them inside the loop * break for loop in case remaining is zero * comment was wrong * optimized, works * remove random spaces --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> --- .../Body/Systems/RespiratorSystem.cs | 2 +- .../Damage/Systems/DamageableSystem.API.cs | 181 ++++++++++++++++++ ...stributedHealthChangeEntityEffectSystem.cs | 91 +++++++++ .../EvenHealthChangeEntityEffectSystem.cs | 88 ++------- .../HealthChangeEntityEffectSystem.cs | 6 +- 5 files changed, 293 insertions(+), 75 deletions(-) create mode 100644 Content.Shared/EntityEffects/Effects/Damage/DistributedHealthChangeEntityEffectSystem.cs rename Content.Shared/EntityEffects/Effects/{ => Damage}/EvenHealthChangeEntityEffectSystem.cs (51%) rename Content.Shared/EntityEffects/Effects/{ => Damage}/HealthChangeEntityEffectSystem.cs (95%) diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index deaa5bc834..7fec07107f 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -20,8 +20,8 @@ using Content.Shared.Database; using Content.Shared.EntityConditions; using Content.Shared.EntityConditions.Conditions.Body; using Content.Shared.EntityEffects; -using Content.Shared.EntityEffects.Effects; using Content.Shared.EntityEffects.Effects.Body; +using Content.Shared.EntityEffects.Effects.Damage; using Content.Shared.Mobs.Systems; using JetBrains.Annotations; using Robust.Shared.Prototypes; diff --git a/Content.Shared/Damage/Systems/DamageableSystem.API.cs b/Content.Shared/Damage/Systems/DamageableSystem.API.cs index c39924ae2a..4670886c54 100644 --- a/Content.Shared/Damage/Systems/DamageableSystem.API.cs +++ b/Content.Shared/Damage/Systems/DamageableSystem.API.cs @@ -1,3 +1,5 @@ +using System.Linq; +using System.Net.Sockets; using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; @@ -230,6 +232,185 @@ public sealed partial class DamageableSystem return damageDone; } + /// + /// Will reduce the damage on the entity exactly by as close as equally distributed among all damage types the entity has. + /// If one of the damage types of the entity is too low. it will heal that completly and distribute the excess healing among the other damage types. + /// If the is larger than the total damage of the entity then it just clears all damage. + /// + /// entity to be healed + /// how much to heal. value has to be negative to heal + /// from which group to heal. if null, heal from all groups + /// who did the healing + public DamageSpecifier HealEvenly( + Entity ent, + FixedPoint2 amount, + ProtoId? group = null, + EntityUid? origin = null, + // Begin DeltaV additions - Adapt to shitmed changes + TargetBodyPart? targetPart = null, + bool doPartDamage = true, + bool onlyDamageParts = false // Fix EvenHealing on Limbs && Standardize PartDamage. + // End DeltaV additions - Adapt to shitmed changes + ) + { + var damageChange = new DamageSpecifier(); + + if (!_damageableQuery.Resolve(ent, ref ent.Comp, false) || amount >= 0) + return damageChange; + + // Get our total damage, or heal if we're below a certain amount. + if (!TryGetDamageGreaterThan((ent, ent.Comp), -amount, out var damage, group)) + return ChangeDamage(ent, -damage, true, false, origin); + + // make sure damageChange has the same damage types as damage + damageChange.DamageDict.EnsureCapacity(damage.DamageDict.Count); + foreach (var type in damage.DamageDict.Keys) + { + damageChange.DamageDict.Add(type, FixedPoint2.Zero); + } + + var remaining = -amount; + var keys = damage.DamageDict.Keys.ToList(); + + while (remaining > 0) + { + var count = keys.Count; + // We do this to ensure that we always round up when dividing to avoid excess loops. + // We already have logic to prevent healing more than we have. + var maxHeal = count == 1 ? remaining : (remaining + FixedPoint2.Epsilon * (count - 1)) / count; + + // Iterate backwards since we're removing items. + for (var i = count - 1; i >= 0; i--) + { + var type = keys[i]; + // This is the amount we're trying to heal, capped by maxHeal + var heal = damage.DamageDict[type] + damageChange.DamageDict[type]; + + // Don't go above max, if we don't go above max + if (heal > maxHeal) + heal = maxHeal; + // If we're not above max, we will heal it fully and don't need to enumerate anymore! + else + keys.RemoveAt(i); + + if (heal >= remaining) + { + // Don't remove more than we can remove. Prevents us from healing more than we'd expect... + damageChange.DamageDict[type] -= remaining; + remaining = FixedPoint2.Zero; + break; + } + + remaining -= heal; + damageChange.DamageDict[type] -= heal; + } + } + + return ChangeDamage(ent, damageChange, true, false, origin, targetPart: targetPart, doPartDamage: doPartDamage, onlyDamageParts: onlyDamageParts); // DeltaV - Adapt to shitmed + } + + /// + /// Will reduce the damage on the entity exactly by distributed by weight among all damage types the entity has. + /// (the weight is how much damage of the type there is) + /// If the is larger than the total damage of the entity then it just clears all damage. + /// + /// entity to be healed + /// how much to heal. value has to be negative to heal + /// from which group to heal. if null, heal from all groups + /// who did the healing + public DamageSpecifier HealDistributed( + Entity ent, + FixedPoint2 amount, + ProtoId? group = null, + EntityUid? origin = null) + { + var damageChange = new DamageSpecifier(); + + if (!_damageableQuery.Resolve(ent, ref ent.Comp, false) || amount >= 0) + return damageChange; + + // Get our total damage, or heal if we're below a certain amount. + if (!TryGetDamageGreaterThan((ent, ent.Comp), -amount, out var damage, group)) + return ChangeDamage(ent, -damage, true, false, origin); + + // make sure damageChange has the same damage types as damageEntity + damageChange.DamageDict.EnsureCapacity(damage.DamageDict.Count); + var total = damage.GetTotal(); + + // heal weighted by the damage of that type + foreach (var (type, value) in damage.DamageDict) + { + damageChange.DamageDict.Add(type, value / total * amount); + } + + return ChangeDamage(ent, damageChange, true, false, origin); + } + + /// + /// Tries to get damage from an entity with an optional group specifier. + /// + /// Entity we're checking the damage on + /// Amount we want the damage to be greater than ideally + /// Damage specifier we're returning with + /// An optional group, note that if it fails to index it will just use all damage. + /// True if the total damage is greater than the specified amount + public bool TryGetDamageGreaterThan(Entity ent, + FixedPoint2 amount, + out DamageSpecifier damage, + ProtoId? group = null) + { + // get the damage should be healed (either all or only from one group) + damage = group == null ? GetDamage(ent) : GetDamage(ent, group.Value); + + // If trying to heal more than the total damage of damageEntity just heal everything + return damage.GetTotal() > amount; + } + + /// + /// Returns a with all positive damage of the entity from the group specified + /// + /// entity with damage + /// group of damage to get values from + /// + public DamageSpecifier GetDamage(Entity ent, ProtoId group) + { + // No damage if no group exists... + if (!_prototypeManager.Resolve(group, out var groupProto)) + return new DamageSpecifier(); + + var damage = new DamageSpecifier(); + damage.DamageDict.EnsureCapacity(groupProto.DamageTypes.Count); + + foreach (var damageId in groupProto.DamageTypes) + { + if (!ent.Comp.Damage.DamageDict.TryGetValue(damageId, out var value)) + continue; + if (value > FixedPoint2.Zero) + damage.DamageDict.Add(damageId, value); + } + + return damage; + } + + /// + /// Returns a with all positive damage of the entity + /// + /// entity with damage + /// + public DamageSpecifier GetDamage(Entity ent) + { + var damage = new DamageSpecifier(); + damage.DamageDict.EnsureCapacity(ent.Comp.Damage.DamageDict.Count); + + foreach (var (damageId, value) in ent.Comp.Damage.DamageDict) + { + if (value > FixedPoint2.Zero) + damage.DamageDict.Add(damageId, value); + } + + return damage; + } + /// /// Applies the two universal "All" modifiers, if set. /// Individual damage source modifiers are set in their respective code. diff --git a/Content.Shared/EntityEffects/Effects/Damage/DistributedHealthChangeEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Damage/DistributedHealthChangeEntityEffectSystem.cs new file mode 100644 index 0000000000..66911d7785 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Damage/DistributedHealthChangeEntityEffectSystem.cs @@ -0,0 +1,91 @@ +using Content.Shared.Damage.Components; +using Content.Shared.Damage.Prototypes; +using Content.Shared.Damage.Systems; +using Content.Shared.FixedPoint; +using Content.Shared.Localizations; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Damage; + +/// +/// Heal the damage types in a damage group by up to a specified total on this entity. +/// The amount healed per type is weighted by the amount of damage for that type scaling linearly. +/// Total adjustment is modified by scale. +/// +/// +public sealed partial class DistributedHealthChangeEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + foreach (var (group, amount) in args.Effect.Damage) + { + _damageable.HealDistributed(entity.AsNullable(), amount * args.Scale, group); + } + } +} + +/// +public sealed partial class DistributedHealthChange : EntityEffectBase +{ + /// + /// Damage to heal, collected into entire damage groups. + /// + [DataField(required: true)] + public Dictionary, FixedPoint2> Damage = new(); + + /// + /// Should this effect ignore damage modifiers? + /// + [DataField] + public bool IgnoreResistances = true; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + var damages = new List(); + var heals = false; + var deals = false; + + var damagableSystem = entSys.GetEntitySystem(); + var universalReagentDamageModifier = damagableSystem.UniversalReagentDamageModifier; + var universalReagentHealModifier = damagableSystem.UniversalReagentHealModifier; + + foreach (var (group, amount) in Damage) + { + var groupProto = prototype.Index(group); + + var sign = FixedPoint2.Sign(amount); + float mod; + + switch (sign) + { + case < 0: + heals = true; + mod = universalReagentHealModifier; + break; + case > 0: + deals = true; + mod = universalReagentDamageModifier; + break; + default: + continue; // Don't need to show damage types of 0... + } + + damages.Add( + Loc.GetString("health-change-display", + ("kind", groupProto.LocalizedName), + ("amount", MathF.Abs(amount.Float() * mod)), + ("deltasign", sign) + )); + } + + // We use health change since in practice it's not even and distributed is a mouthful. + // Also because healing groups not using even or distributed healing should be kill. + var healsordeals = heals ? deals ? "both" : "heals" : deals ? "deals" : "none"; + return Loc.GetString("entity-effect-guidebook-health-change", + ("chance", Probability), + ("changes", ContentLocalizationManager.FormatList(damages)), + ("healsordeals", healsordeals)); + } +} diff --git a/Content.Shared/EntityEffects/Effects/EvenHealthChangeEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Damage/EvenHealthChangeEntityEffectSystem.cs similarity index 51% rename from Content.Shared/EntityEffects/Effects/EvenHealthChangeEntityEffectSystem.cs rename to Content.Shared/EntityEffects/Effects/Damage/EvenHealthChangeEntityEffectSystem.cs index 273a4bc961..010193dc22 100644 --- a/Content.Shared/EntityEffects/Effects/EvenHealthChangeEntityEffectSystem.cs +++ b/Content.Shared/EntityEffects/Effects/Damage/EvenHealthChangeEntityEffectSystem.cs @@ -1,102 +1,48 @@ -// DeltaV Start - Fix EvenHealing with Limbs. -using System.Linq; +// DeltaV Start - Fix EvenHealing with Limbs. using Content.Shared._Shitmed.Targeting; using Content.Shared.Body.Systems; // DeltaV End - Fix EvenHealing with Limbs. -using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Systems; using Content.Shared.FixedPoint; using Content.Shared.Localizations; using Robust.Shared.Prototypes; -using Robust.Shared.Utility; -namespace Content.Shared.EntityEffects.Effects; +namespace Content.Shared.EntityEffects.Effects.Damage; /// -/// Evenly adjust the damage types in a damage group by up to a specified total on this entity. +/// Evenly heal the damage types in a damage group by up to a specified total on this entity. /// Total adjustment is modified by scale. /// /// public sealed partial class EvenHealthChangeEntityEffectSystem : EntityEffectSystem { [Dependency] private readonly DamageableSystem _damageable = default!; - [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly SharedBodySystem _body = default!; // DeltaV - [Dependency] private readonly EntityManager _ent = default!; // DeltaV protected override void Effect(Entity entity, ref EntityEffectEvent args) { - // DeltaV - Even Healing with Limbs - var damageSpec = GetDamageSpec(entity.Owner, ref args); // DeltaV - Basically moved this to a private method - - damageSpec *= args.Scale; - - - _damageable.TryChangeDamage( - entity.AsNullable(), - damageSpec, - args.Effect.IgnoreResistances, - interruptsDoAfters: false, - doPartDamage: false); // DeltaV - Even Healing with Limbs - - var bodyParts = SharedTargetingSystem.GetValidParts(); - foreach (var bodyPart in bodyParts) - { - var (targetType, targetSymmetry) = _body.ConvertTargetBodyPart(bodyPart); - if (_body.GetBodyChildrenOfType(entity, targetType, symmetry: targetSymmetry) is { } part) - { - var dspec = GetDamageSpec(part.FirstOrDefault().Id, ref args); - - if (dspec.GetTotal() == 0) - continue; - - _damageable.TryChangeDamage( - entity.AsNullable(), - dspec * args.Scale, - args.Effect.IgnoreResistances, - interruptsDoAfters: false, - targetPart: bodyPart, - onlyDamageParts: true, - canSever: false); - } - } - // END DeltaV - } - - /// - /// DeltaV - Returns a damage spec for a specific entity with DamageableComponent. - /// - /// - /// - /// - private DamageSpecifier GetDamageSpec(Entity entity, ref EntityEffectEvent args) - { - var damageSpec = new DamageSpecifier(); - if (!_ent.TryGetComponent(entity, out var damageable)) - return damageSpec; - foreach (var (group, amount) in args.Effect.Damage) { - var groupProto = _proto.Index(group); - var groupDamage = new Dictionary(); - foreach (var damageId in groupProto.DamageTypes) - { - var damageAmount = damageable.Damage.DamageDict.GetValueOrDefault(damageId); - if (damageAmount != FixedPoint2.Zero) - groupDamage.Add(damageId, damageAmount); - } + // Begin DeltaV Additions - Limb even healing + _damageable.HealEvenly(entity.AsNullable(), amount * args.Scale, group, doPartDamage: false); - var sum = groupDamage.Values.Sum(); - foreach (var (damageId, damageAmount) in groupDamage) + var bodyParts = SharedTargetingSystem.GetValidParts(); + foreach (var bodyPart in bodyParts) { - var existing = damageSpec.DamageDict.GetOrNew(damageId); - damageSpec.DamageDict[damageId] = existing + damageAmount / sum * amount; + var (targetType, targetSymmetry) = _body.ConvertTargetBodyPart(bodyPart); + if (_body.GetBodyChildrenOfType(entity, targetType, symmetry: targetSymmetry) is { } part) + { + _damageable.HealEvenly( + entity.AsNullable(), + amount * args.Scale, + targetPart: bodyPart, + onlyDamageParts: true); + } } + // End DeltaV Additions - Limb even healing } - - return damageSpec; } } diff --git a/Content.Shared/EntityEffects/Effects/HealthChangeEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Damage/HealthChangeEntityEffectSystem.cs similarity index 95% rename from Content.Shared/EntityEffects/Effects/HealthChangeEntityEffectSystem.cs rename to Content.Shared/EntityEffects/Effects/Damage/HealthChangeEntityEffectSystem.cs index 3e6fe8330b..b23ab25f13 100644 --- a/Content.Shared/EntityEffects/Effects/HealthChangeEntityEffectSystem.cs +++ b/Content.Shared/EntityEffects/Effects/Damage/HealthChangeEntityEffectSystem.cs @@ -6,7 +6,7 @@ using Content.Shared.FixedPoint; using Content.Shared.Localizations; using Robust.Shared.Prototypes; -namespace Content.Shared.EntityEffects.Effects; +namespace Content.Shared.EntityEffects.Effects.Damage; /// /// Adjust the damages on this entity by specified amounts. @@ -15,7 +15,7 @@ namespace Content.Shared.EntityEffects.Effects; /// public sealed partial class HealthChangeEntityEffectSystem : EntityEffectSystem { - [Dependency] private readonly Damage.Systems.DamageableSystem _damageable = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; protected override void Effect(Entity entity, ref EntityEffectEvent args) { @@ -94,4 +94,4 @@ public sealed partial class HealthChange : EntityEffectBase ("changes", ContentLocalizationManager.FormatList(damages)), ("healsordeals", healsordeals)); } -} \ No newline at end of file +}