Delta-v/Content.Shared/Damage/Systems/DamageableSystem.API.cs

290 lines
11 KiB
C#

using Content.Shared.Damage.Components;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
using Content.Shared.Body.Systems; // Shitmed Change
using Content.Shared._Shitmed.Targeting; // Shitmed Change
using Robust.Shared.Random; // Shitmed Change
namespace Content.Shared.Damage.Systems;
public sealed partial class DamageableSystem
{
/// <summary>
/// Directly sets the damage specifier of a damageable component.
/// </summary>
/// <remarks>
/// Useful for some unfriendly folk. Also ensures that cached values are updated and that a damage changed
/// event is raised.
/// </remarks>
public void SetDamage(Entity<DamageableComponent?> ent, DamageSpecifier damage)
{
if (!_damageableQuery.Resolve(ent, ref ent.Comp, false))
return;
ent.Comp.Damage = damage;
OnEntityDamageChanged((ent, ent.Comp));
}
/// <summary>
/// Applies damage specified via a <see cref="DamageSpecifier"/>.
/// </summary>
/// <remarks>
/// <see cref="DamageSpecifier"/> is effectively just a dictionary of damage types and damage values. This
/// function just applies the container's resistances (unless otherwise specified) and then changes the
/// stored damage data. Division of group damage into types is managed by <see cref="DamageSpecifier"/>.
/// </remarks>
/// <returns>
/// If the attempt was successful or not.
/// </returns>
public bool TryChangeDamage(
Entity<DamageableComponent?> ent,
DamageSpecifier damage,
bool ignoreResistances = false,
bool interruptsDoAfters = true,
EntityUid? origin = null,
bool ignoreGlobalModifiers = false,
// Shitmed Changes
bool canSever = true,
bool canEvade = false,
float partMultiplier = 0.5f,
TargetBodyPart? targetPart = null,
bool doPartDamage = true,
bool onlyDamageParts = false
// END Shitmed Changes
)
{
//! Empty just checks if the DamageSpecifier is _literally_ empty, as in, is internal dictionary of damage types is empty.
// If you deal 0.0 of some damage type, Empty will be false!
return !TryChangeDamage(ent, damage, out _, ignoreResistances, interruptsDoAfters, origin, ignoreGlobalModifiers,
canSever: canSever, canEvade: canEvade, partMultiplier: partMultiplier, targetPart: targetPart, doPartDamage: doPartDamage, onlyDamageParts: onlyDamageParts); // Shitmed
}
/// <summary>
/// Applies damage specified via a <see cref="DamageSpecifier"/>.
/// </summary>
/// <remarks>
/// <see cref="DamageSpecifier"/> is effectively just a dictionary of damage types and damage values. This
/// function just applies the container's resistances (unless otherwise specified) and then changes the
/// stored damage data. Division of group damage into types is managed by <see cref="DamageSpecifier"/>.
/// </remarks>
/// <returns>
/// If the attempt was successful or not.
/// </returns>
public bool TryChangeDamage(
Entity<DamageableComponent?> ent,
DamageSpecifier damage,
out DamageSpecifier newDamage,
bool ignoreResistances = false,
bool interruptsDoAfters = true,
EntityUid? origin = null,
bool ignoreGlobalModifiers = false,
// Shitmed Changes
bool canSever = true,
bool canEvade = false,
float partMultiplier = 0.5f,
TargetBodyPart? targetPart = null,
bool doPartDamage = true,
bool onlyDamageParts = false
// END Shitmed Changes
)
{
//! Empty just checks if the DamageSpecifier is _literally_ empty, as in, is internal dictionary of damage types is empty.
// If you deal 0.0 of some damage type, Empty will be false!
newDamage = ChangeDamage(ent, damage, ignoreResistances, interruptsDoAfters, origin, ignoreGlobalModifiers,
canSever: canSever, canEvade: canEvade, partMultiplier: partMultiplier, targetPart: targetPart, doPartDamage: doPartDamage, onlyDamageParts: onlyDamageParts); // Shitmed
return !damage.Empty;
}
/// <summary>
/// Applies damage specified via a <see cref="DamageSpecifier"/>.
/// </summary>
/// <remarks>
/// <see cref="DamageSpecifier"/> is effectively just a dictionary of damage types and damage values. This
/// function just applies the container's resistances (unless otherwise specified) and then changes the
/// stored damage data. Division of group damage into types is managed by <see cref="DamageSpecifier"/>.
/// </remarks>
/// <returns>
/// The actual amount of damage taken, as a DamageSpecifier.
/// </returns>
public DamageSpecifier ChangeDamage(
Entity<DamageableComponent?> ent,
DamageSpecifier damage,
bool ignoreResistances = false,
bool interruptsDoAfters = true,
EntityUid? origin = null,
bool ignoreGlobalModifiers = false,
// Shitmed Changes
bool canSever = true,
bool canEvade = false,
float partMultiplier = 0.5f,
TargetBodyPart? targetPart = null,
bool doPartDamage = true,
bool onlyDamageParts = false // DeltaV - Fix EvenHealing on Limbs && Standardize PartDamage.
// END Shitmed Changes
)
{
var damageDone = new DamageSpecifier();
if (!_damageableQuery.Resolve(ent, ref ent.Comp, false))
return damageDone;
if (damage.Empty)
return damageDone;
damage = ApplyUniversalAllModifiers(damage); // DeltaV - Fix EvenHealing with Limbs
var before = new BeforeDamageChangedEvent(damage, origin);
RaiseLocalEvent(ent, ref before);
if (before.Cancelled)
return damageDone;
// Shitmed - Do Part Damage
if (doPartDamage) // DeltaV - Fix EvenHealing with Limbs.
{
var partDamage = new BeforePartDamageChangedEvent(damage, origin, targetPart, ignoreResistances, canSever, canEvade, partMultiplier); // DeltaV - Standardize PartDamage.
RaiseLocalEvent(ent.Owner, ref partDamage);
if (partDamage.Evaded || partDamage.Cancelled)
return damageDone;
}
// END Shitmed
// Apply resistances
if (!ignoreResistances)
{
if (
ent.Comp.DamageModifierSetId != null &&
_prototypeManager.Resolve(ent.Comp.DamageModifierSetId, out var modifierSet)
)
damage = DamageSpecifier.ApplyModifierSet(damage, modifierSet);
// TODO DAMAGE
// byref struct event.
var ev = new DamageModifyEvent(damage, origin, targetPart: targetPart); // Shitmed - Add TargetPart
RaiseLocalEvent(ent, ev);
damage = ev.Damage;
if (damage.Empty)
return damageDone;
}
if (onlyDamageParts) // DeltaV - Fix EvenHealing with Limbs.
return damageDone;
if (!ignoreGlobalModifiers)
damage = ApplyUniversalAllModifiers(damage);
damageDone.DamageDict.EnsureCapacity(damage.DamageDict.Count);
var dict = ent.Comp.Damage.DamageDict;
foreach (var (type, value) in damage.DamageDict)
{
// CollectionsMarshal my beloved.
if (!dict.TryGetValue(type, out var oldValue))
continue;
var newValue = FixedPoint2.Max(FixedPoint2.Zero, oldValue + value);
if (newValue == oldValue)
continue;
dict[type] = newValue;
damageDone.DamageDict[type] = newValue - oldValue;
}
if (!damageDone.Empty)
OnEntityDamageChanged((ent, ent.Comp), damageDone, interruptsDoAfters, origin, canSever); // Shitmed
return damageDone;
}
/// <summary>
/// Applies the two universal "All" modifiers, if set.
/// Individual damage source modifiers are set in their respective code.
/// </summary>
/// <param name="damage">The damage to be changed.</param>
public DamageSpecifier ApplyUniversalAllModifiers(DamageSpecifier damage)
{
// Checks for changes first since they're unlikely in normal play.
if (
MathHelper.CloseToPercent(UniversalAllDamageModifier, 1f) &&
MathHelper.CloseToPercent(UniversalAllHealModifier, 1f)
)
return damage;
foreach (var (key, value) in damage.DamageDict)
{
if (value == 0)
continue;
if (value > 0)
{
damage.DamageDict[key] *= UniversalAllDamageModifier;
continue;
}
if (value < 0)
damage.DamageDict[key] *= UniversalAllHealModifier;
}
return damage;
}
public void ClearAllDamage(Entity<DamageableComponent?> ent)
{
SetAllDamage(ent, FixedPoint2.Zero);
}
/// <summary>
/// Sets all damage types supported by a <see cref="Components.DamageableComponent"/> to the specified value.
/// </summary>
/// <remarks>
/// Does nothing If the given damage value is negative.
/// </remarks>
public void SetAllDamage(Entity<DamageableComponent?> ent, FixedPoint2 newValue)
{
if (!_damageableQuery.Resolve(ent, ref ent.Comp, false))
return;
if (newValue < 0)
return;
foreach (var type in ent.Comp.Damage.DamageDict.Keys)
{
ent.Comp.Damage.DamageDict[type] = newValue;
}
// Setting damage does not count as 'dealing' damage, even if it is set to a larger value, so we pass an
// empty damage delta.
OnEntityDamageChanged((ent, ent.Comp), new DamageSpecifier());
// Shitmed Change Start
if (HasComp<TargetingComponent>(ent.Owner))
{
foreach (var (part, _) in _body.GetBodyChildren(ent.Owner))
SetAllDamage(part, newValue);
}
// Shitmed Change End
}
/// <summary>
/// Set's the damage modifier set prototype for this entity.
/// </summary>
/// <param name="ent">The entity we're setting the modifier set of.</param>
/// <param name="damageModifierSetId">The prototype we're setting.</param>
public void SetDamageModifierSetId(Entity<DamageableComponent?> ent, ProtoId<DamageModifierSetPrototype>? damageModifierSetId)
{
if (!_damageableQuery.Resolve(ent, ref ent.Comp, false))
return;
ent.Comp.DamageModifierSetId = damageModifierSetId;
Dirty(ent);
}
}