diff --git a/Content.Server/EntityEffects/Effects/HealthChange.cs b/Content.Server/EntityEffects/Effects/HealthChange.cs index fd2a658745b..d09d7ccf2a7 100644 --- a/Content.Server/EntityEffects/Effects/HealthChange.cs +++ b/Content.Server/EntityEffects/Effects/HealthChange.cs @@ -7,6 +7,7 @@ using Robust.Shared.Prototypes; using System.Linq; using System.Text.Json.Serialization; +using Content.Shared.Backmen.Targeting; namespace Content.Server.EntityEffects.Effects { @@ -124,7 +125,13 @@ public override void Effect(EntityEffectBaseArgs args) args.TargetEntity, Damage * scale, IgnoreResistances, - interruptsDoAfters: false); + interruptsDoAfters: false, + // start-backmen: surgery + targetPart: TargetBodyPart.All, + partMultiplier: 0.5f, + canSever: false, + evade: true); + // end-backmen: surgery } } } diff --git a/Content.Server/Medical/HealingSystem.cs b/Content.Server/Medical/HealingSystem.cs index 53b428bd32f..a2c31b37f22 100644 --- a/Content.Server/Medical/HealingSystem.cs +++ b/Content.Server/Medical/HealingSystem.cs @@ -26,6 +26,7 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Random; using System.Linq; +using Content.Shared.Backmen.Targeting; namespace Content.Server.Medical; @@ -86,27 +87,48 @@ entity.Comp.DamageContainerID is not null && if (healing.ModifyBloodLevel != 0) _bloodstreamSystem.TryModifyBloodLevel(entity.Owner, healing.ModifyBloodLevel); - var healed = _damageable.TryChangeDamage(entity.Owner, healing.Damage, true, origin: args.Args.User); + var healed = _damageable.TryChangeDamage(entity.Owner, healing.Damage, true, origin: args.Args.User, partMultiplier: 0f); // No parts healing now! if (healed == null && healing.BloodlossModifier != 0) return; var total = healed?.GetTotal() ?? FixedPoint2.Zero; - /* This is rather shitcodey. Problem is that right now damage is coupled to integrity. - If the body is fully healed, all of the checks on TryChangeDamage stop us from actually healing. - So in this case we add a special check to heal anyway if TryChangeDamage returns null. - */ - if (total == 0) + // start-backmen: surgery + // Try to heal some body part. + if (total == 0 && ArePartsDamaged(entity)) { + var partHealing = healing.Damage; + var parts = _bodySystem.GetBodyChildren(args.Target).ToList(); - // We fetch the most damaged body part - var mostDamaged = parts.MinBy(x => x.Component.TotalDamage); - var targetBodyPart = _bodySystem.GetTargetBodyPart(mostDamaged); + var damagedParts = new List<(Entity, FixedPoint2)>(); + foreach (var part in parts) + { + // Get all body parts that are damaged with *partHealing* types of damage. + var possibleHealing = part.Component.Damage; + possibleHealing.ExclusiveAdd(partHealing); + possibleHealing.ClampMin(part.Component.MinIntegrity); + + // Remove all other types of damage that can't be healed right now, + // so we can prioritize the target part correctly. + foreach (var (damage, value) in possibleHealing.DamageDict) + { + if (!partHealing.DamageDict.ContainsKey(damage)) + possibleHealing.DamageDict.Remove(damage); + } + + if (possibleHealing.GetTotal() == FixedPoint2.Zero) + continue; + + damagedParts.Add((part, possibleHealing.GetTotal())); + } - if (targetBodyPart != null) - _bodySystem.TryChangeIntegrity(mostDamaged, healing.Damage, false, targetBodyPart.Value, out _); + // Fetch the most damaged body part + var mostDamaged = damagedParts.MaxBy(x => x.Item2).Item1; + var targetPart = _bodySystem.GetTargetBodyPart(mostDamaged); + _bodySystem.TryChangeIntegrity(mostDamaged, healing.Damage, false, targetPart, out _); } + // end-backmen: surgery // Re-verify that we can heal the damage. diff --git a/Content.Shared/Backmen/Surgery/Body/SharedBodySystem.Integrity.cs b/Content.Shared/Backmen/Surgery/Body/SharedBodySystem.Integrity.cs index 7d5315d954d..52da98fdbdf 100644 --- a/Content.Shared/Backmen/Surgery/Body/SharedBodySystem.Integrity.cs +++ b/Content.Shared/Backmen/Surgery/Body/SharedBodySystem.Integrity.cs @@ -26,7 +26,7 @@ public partial class SharedBodySystem [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IPrototypeManager _proto = default!; - private readonly string[] _severingDamageTypes = { "Slash", "Pierce", "Blunt" }; + private const double IntegrityJobTime = 0.005; private readonly JobQueue _integrityJobQueue = new(IntegrityJobTime); @@ -117,7 +117,7 @@ public override void Update(float frameTime) /// /// Propagates damage to the specified part of the entity. /// - private void ApplyPartDamage( + public void ApplyPartDamage( Entity partEnt, DamageSpecifier damage, BodyPartType targetType, @@ -126,17 +126,15 @@ private void ApplyPartDamage( bool evade, float partMultiplier) { - if (partEnt.Comp.Body is not {} body) + if (partEnt.Comp.Body == null) return; - _proto.TryIndex("Brute", out var proto); - if (!TryEvadeDamage(partEnt.Comp.Body.Value, GetEvadeChance(targetType)) || evade) { TryChangeIntegrity(partEnt, - damage * partMultiplier, + damage * partMultiplier * GetPartDamageModifier(targetType), // This is true when damage contains at least one of the brute damage types - canSever && damage.TryGetDamageInGroup(proto!, out var dmg) && dmg > FixedPoint2.Zero, + canSever, targetPart, out _); } @@ -162,10 +160,13 @@ public void TryChangeIntegrity( partEnt.Comp.Damage.ExclusiveAdd(damage); partEnt.Comp.Damage.ClampMin(partEnt.Comp.MinIntegrity); // No over-healing! + _proto.TryIndex("Brute", out var proto); + if (canSever && !HasComp(partEnt) && !partEnt.Comp.Enabled - && integrity >= partEnt.Comp.SeverIntegrity + && partEnt.Comp.Damage.TryGetDamageInGroup(proto!, out var dmg) + && dmg > partEnt.Comp.SeverIntegrity && partIdSlot is not null) severed = true; @@ -330,8 +331,8 @@ public static float GetPartDamageModifier(BodyPartType partType) { BodyPartType.Head => 0.5f, // 50% damage, necks are hard to cut BodyPartType.Torso => 1.0f, // 100% damage - BodyPartType.Arm => 0.7f, // 70% damage - BodyPartType.Leg => 0.7f, // 70% damage + BodyPartType.Arm => 0.8f, // 80% damage + BodyPartType.Leg => 0.8f, // 80% damage _ => 0.5f }; } @@ -376,8 +377,6 @@ public static float GetEvadeChance(BodyPartType partType) }; } - - public bool CanEvadeDamage(Entity uid) { if (!Resolve(uid, ref uid.Comp)) diff --git a/Content.Shared/Body/Part/BodyPartComponent.cs b/Content.Shared/Body/Part/BodyPartComponent.cs index b4ca2d8999b..e8c0868590d 100644 --- a/Content.Shared/Body/Part/BodyPartComponent.cs +++ b/Content.Shared/Body/Part/BodyPartComponent.cs @@ -100,7 +100,7 @@ public sealed partial class BodyPartComponent : Component, ISurgeryToolComponent /// to make possible severing it. /// [DataField, AutoNetworkedField] - public float SeverIntegrity = 70; + public float SeverIntegrity = 100; /// /// On what TargetIntegrity we should re-enable the part. @@ -111,12 +111,12 @@ public sealed partial class BodyPartComponent : Component, ISurgeryToolComponent [DataField, AutoNetworkedField] public Dictionary IntegrityThresholds = new() { - { TargetIntegrity.CriticallyWounded, 70 }, - { TargetIntegrity.HeavilyWounded, 56 }, - { TargetIntegrity.ModeratelyWounded, 42 }, - { TargetIntegrity.SomewhatWounded, 28}, + { TargetIntegrity.CriticallyWounded, 80 }, + { TargetIntegrity.HeavilyWounded, 65 }, + { TargetIntegrity.ModeratelyWounded, 48 }, + { TargetIntegrity.SomewhatWounded, 32 }, { TargetIntegrity.LightlyWounded, 17 }, - { TargetIntegrity.Healthy, 7 }, + { TargetIntegrity.Healthy, 6 }, }; /// diff --git a/Content.Shared/Damage/Systems/DamageableSystem.cs b/Content.Shared/Damage/Systems/DamageableSystem.cs index 3c46f91586f..ba08a6233dd 100644 --- a/Content.Shared/Damage/Systems/DamageableSystem.cs +++ b/Content.Shared/Damage/Systems/DamageableSystem.cs @@ -119,7 +119,7 @@ public void DamageChanged(EntityUid uid, DamageableComponent component, DamageSp targetPart = targeter.Target; // ...Or do we? // If the target is Torso then have a 33% chance to hit another part - if (targetPart.Value.HasFlag(TargetBodyPart.Torso)) + if (targetPart.Value == TargetBodyPart.Torso) targetPart = GetRandomPartSpread(_random, 10); } else