DamageableSystem cleanup & performance improvements (#20820)
This commit is contained in:
@@ -133,7 +133,7 @@ namespace Content.Server.Bible
|
|||||||
|
|
||||||
var damage = _damageableSystem.TryChangeDamage(args.Target.Value, component.Damage, true, origin: uid);
|
var damage = _damageableSystem.TryChangeDamage(args.Target.Value, component.Damage, true, origin: uid);
|
||||||
|
|
||||||
if (damage == null || damage.Total == 0)
|
if (damage == null || damage.Empty)
|
||||||
{
|
{
|
||||||
var othersMessage = Loc.GetString(component.LocPrefix + "-heal-success-none-others", ("user", Identity.Entity(args.User, EntityManager)),("target", Identity.Entity(args.Target.Value, EntityManager)),("bible", uid));
|
var othersMessage = Loc.GetString(component.LocPrefix + "-heal-success-none-others", ("user", Identity.Entity(args.User, EntityManager)),("target", Identity.Entity(args.Target.Value, EntityManager)),("bible", uid));
|
||||||
_popupSystem.PopupEntity(othersMessage, args.User, Filter.PvsExcept(args.User), true, PopupType.Medium);
|
_popupSystem.PopupEntity(othersMessage, args.User, Filter.PvsExcept(args.User), true, PopupType.Medium);
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
|||||||
if (!electrified.OnAttacked)
|
if (!electrified.OnAttacked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_meleeWeapon.GetDamage(args.Used, args.User).Total == 0)
|
if (!_meleeWeapon.GetDamage(args.Used, args.User).Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TryDoElectrifiedAct(uid, args.User, 1, electrified);
|
TryDoElectrifiedAct(uid, args.User, 1, electrified);
|
||||||
@@ -192,7 +192,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
|||||||
private void OnLightAttacked(EntityUid uid, PoweredLightComponent component, AttackedEvent args)
|
private void OnLightAttacked(EntityUid uid, PoweredLightComponent component, AttackedEvent args)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (_meleeWeapon.GetDamage(args.Used, args.User).Total == 0)
|
if (!_meleeWeapon.GetDamage(args.Used, args.User).Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (args.Used != args.User)
|
if (args.Used != args.User)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
|||||||
|
|
||||||
if (modifiedDamage is not null && EntityManager.EntityExists(component.Shooter))
|
if (modifiedDamage is not null && EntityManager.EntityExists(component.Shooter))
|
||||||
{
|
{
|
||||||
if (modifiedDamage.Total > FixedPoint2.Zero && !deleted)
|
if (modifiedDamage.Any() && !deleted)
|
||||||
{
|
{
|
||||||
_color.RaiseEffect(Color.Red, new List<EntityUid> { target }, Filter.Pvs(target, entityManager: EntityManager));
|
_color.RaiseEffect(Color.Red, new List<EntityUid> { target }, Filter.Pvs(target, entityManager: EntityManager));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
|||||||
|
|
||||||
var damageSpec = GetDamage(uid, args.User, component);
|
var damageSpec = GetDamage(uid, args.User, component);
|
||||||
|
|
||||||
if (damageSpec.Total == FixedPoint2.Zero)
|
if (damageSpec.Empty)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_damageExamine.AddDamageExamine(args.Message, damageSpec, Loc.GetString("damage-melee"));
|
_damageExamine.AddDamageExamine(args.Message, damageSpec, Loc.GetString("damage-melee"));
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public sealed partial class GunSystem
|
|||||||
{
|
{
|
||||||
var p = (ProjectileComponent) projectile.Component;
|
var p = (ProjectileComponent) projectile.Component;
|
||||||
|
|
||||||
if (p.Damage.Total > FixedPoint2.Zero)
|
if (!p.Damage.Empty)
|
||||||
{
|
{
|
||||||
return p.Damage;
|
return p.Damage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public sealed partial class GunSystem
|
|||||||
{
|
{
|
||||||
var p = (ProjectileComponent) projectile.Component;
|
var p = (ProjectileComponent) projectile.Component;
|
||||||
|
|
||||||
if (p.Damage.Total > FixedPoint2.Zero)
|
if (!p.Damage.Empty)
|
||||||
{
|
{
|
||||||
return p.Damage;
|
return p.Damage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
|||||||
{
|
{
|
||||||
if (!Deleted(hitEntity))
|
if (!Deleted(hitEntity))
|
||||||
{
|
{
|
||||||
if (dmg.Total > FixedPoint2.Zero)
|
if (dmg.Any())
|
||||||
{
|
{
|
||||||
_color.RaiseEffect(Color.Red, new List<EntityUid>() { hitEntity }, Filter.Pvs(hitEntity, entityManager: EntityManager));
|
_color.RaiseEffect(Color.Red, new List<EntityUid>() { hitEntity }, Filter.Pvs(hitEntity, entityManager: EntityManager));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,15 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
|
|||||||
namespace Content.Shared.Damage
|
namespace Content.Shared.Damage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A set of coefficients or flat modifiers to damage types.. Can be applied to <see cref="DamageSpecifier"/> using <see
|
/// A set of coefficients or flat modifiers to damage types. Can be applied to <see cref="DamageSpecifier"/> using <see
|
||||||
/// cref="DamageSpecifier.ApplyModifierSet(DamageSpecifier, DamageModifierSet)"/>. This can be done several times as the
|
/// cref="DamageSpecifier.ApplyModifierSet(DamageSpecifier, DamageModifierSet)"/>. This can be done several times as the
|
||||||
/// <see cref="DamageSpecifier"/> is passed to it's final target. By default the receiving <see cref="DamageableComponent"/>, will
|
/// <see cref="DamageSpecifier"/> is passed to it's final target. By default the receiving <see cref="DamageableComponent"/>, will
|
||||||
/// also apply it's own <see cref="DamageModifierSet"/>.
|
/// also apply it's own <see cref="DamageModifierSet"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The modifier will only ever be applied to damage that is being dealt. Healing is unmodified.
|
||||||
|
/// The modifier can also never convert damage into healing.
|
||||||
|
/// </remarks>
|
||||||
[DataDefinition]
|
[DataDefinition]
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
[Virtual]
|
[Virtual]
|
||||||
|
|||||||
@@ -37,16 +37,42 @@ namespace Content.Shared.Damage
|
|||||||
[IncludeDataField(customTypeSerializer: typeof(DamageSpecifierDictionarySerializer), readOnly: true)]
|
[IncludeDataField(customTypeSerializer: typeof(DamageSpecifierDictionarySerializer), readOnly: true)]
|
||||||
public Dictionary<string, FixedPoint2> DamageDict { get; set; } = new();
|
public Dictionary<string, FixedPoint2> DamageDict { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
[Obsolete("Use GetTotal()")]
|
||||||
|
public FixedPoint2 Total => GetTotal();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sum of the damage values.
|
/// Returns a sum of the damage values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Note that this being zero does not mean this damage has no effect. Healing in one type may cancel damage
|
/// Note that this being zero does not mean this damage has no effect. Healing in one type may cancel damage
|
||||||
/// in another. For this purpose, you should instead use <see cref="TrimZeros()"/> and then check the <see
|
/// in another. Consider using <see cref="Any()"/> or <see cref="Empty"/> instead.
|
||||||
/// cref="Empty"/> property.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[JsonIgnore]
|
public FixedPoint2 GetTotal()
|
||||||
public FixedPoint2 Total => DamageDict.Values.Sum();
|
{
|
||||||
|
var total = FixedPoint2.Zero;
|
||||||
|
foreach (var value in DamageDict.Values)
|
||||||
|
{
|
||||||
|
total += value;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the specifier contains any positive damage values.
|
||||||
|
/// Differs from <see cref="Empty"/> as a damage specifier might contain entries with zeroes.
|
||||||
|
/// This also returns false if the specifier only contains negative values.
|
||||||
|
/// </summary>
|
||||||
|
public bool Any()
|
||||||
|
{
|
||||||
|
foreach (var value in DamageDict.Values)
|
||||||
|
{
|
||||||
|
if (value > FixedPoint2.Zero)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this damage specifier has any entries.
|
/// Whether this damage specifier has any entries.
|
||||||
@@ -100,41 +126,39 @@ namespace Content.Shared.Damage
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Only applies resistance to a damage type if it is dealing damage, not healing.
|
/// Only applies resistance to a damage type if it is dealing damage, not healing.
|
||||||
|
/// This will never convert damage into healing.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static DamageSpecifier ApplyModifierSet(DamageSpecifier damageSpec, DamageModifierSet modifierSet)
|
public static DamageSpecifier ApplyModifierSet(DamageSpecifier damageSpec, DamageModifierSet modifierSet)
|
||||||
{
|
{
|
||||||
// Make a copy of the given data. Don't modify the one passed to this function. I did this before, and weapons became
|
// Make a copy of the given data. Don't modify the one passed to this function. I did this before, and weapons became
|
||||||
// duller as you hit walls. Neat, but not FixedPoint2ended. And confusing, when you realize your fists don't work no
|
// duller as you hit walls. Neat, but not FixedPoint2ended. And confusing, when you realize your fists don't work no
|
||||||
// more cause they're just bloody stumps.
|
// more cause they're just bloody stumps.
|
||||||
DamageSpecifier newDamage = new(damageSpec);
|
DamageSpecifier newDamage = new();
|
||||||
|
newDamage.DamageDict.EnsureCapacity(damageSpec.DamageDict.Count);
|
||||||
|
|
||||||
foreach (var entry in newDamage.DamageDict)
|
foreach (var (key, value) in damageSpec.DamageDict)
|
||||||
{
|
{
|
||||||
if (entry.Value <= 0) continue;
|
if (value == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
float newValue = entry.Value.Float();
|
if (value < 0)
|
||||||
|
|
||||||
if (modifierSet.FlatReduction.TryGetValue(entry.Key, out var reduction))
|
|
||||||
{
|
{
|
||||||
newValue -= reduction;
|
newDamage.DamageDict[key] = value;
|
||||||
if (newValue <= 0)
|
|
||||||
{
|
|
||||||
// flat reductions cannot heal you
|
|
||||||
newDamage.DamageDict[entry.Key] = FixedPoint2.Zero;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float newValue = value.Float();
|
||||||
|
|
||||||
|
if (modifierSet.FlatReduction.TryGetValue(key, out var reduction))
|
||||||
|
newValue -= reduction;
|
||||||
|
|
||||||
|
if (modifierSet.Coefficients.TryGetValue(key, out var coefficient))
|
||||||
|
newValue *= coefficient;
|
||||||
|
|
||||||
|
if (newValue > 0)
|
||||||
|
newDamage.DamageDict[key] = FixedPoint2.New(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modifierSet.Coefficients.TryGetValue(entry.Key, out var coefficient))
|
|
||||||
{
|
|
||||||
// negative coefficients **can** heal you.
|
|
||||||
newValue = newValue * coefficient;
|
|
||||||
}
|
|
||||||
|
|
||||||
newDamage.DamageDict[entry.Key] = FixedPoint2.New(newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
newDamage.TrimZeros();
|
|
||||||
return newDamage;
|
return newDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,13 +170,19 @@ namespace Content.Shared.Damage
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static DamageSpecifier ApplyModifierSets(DamageSpecifier damageSpec, IEnumerable<DamageModifierSet> modifierSets)
|
public static DamageSpecifier ApplyModifierSets(DamageSpecifier damageSpec, IEnumerable<DamageModifierSet> modifierSets)
|
||||||
{
|
{
|
||||||
DamageSpecifier newDamage = new(damageSpec);
|
bool any = false;
|
||||||
|
DamageSpecifier newDamage = damageSpec;
|
||||||
foreach (var set in modifierSets)
|
foreach (var set in modifierSets)
|
||||||
{
|
{
|
||||||
// this is probably really inefficient. just don't call this in a hot path I guess.
|
// This creates a new damageSpec for each modifier when we really onlt need to create one.
|
||||||
|
// This is quite inefficient, but hopefully this shouldn't ever be called frequently.
|
||||||
newDamage = ApplyModifierSet(newDamage, set);
|
newDamage = ApplyModifierSet(newDamage, set);
|
||||||
|
any = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!any)
|
||||||
|
newDamage = new DamageSpecifier(damageSpec);
|
||||||
|
|
||||||
return newDamage;
|
return newDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,9 +254,10 @@ namespace Content.Shared.Damage
|
|||||||
{
|
{
|
||||||
foreach (var (type, value) in other.DamageDict)
|
foreach (var (type, value) in other.DamageDict)
|
||||||
{
|
{
|
||||||
if (DamageDict.ContainsKey(type))
|
// CollectionsMarshal my beloved.
|
||||||
|
if (DamageDict.TryGetValue(type, out var existing))
|
||||||
{
|
{
|
||||||
DamageDict[type] += value;
|
DamageDict[type] = existing + value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,19 +293,23 @@ namespace Content.Shared.Damage
|
|||||||
/// total of each group. If no members of a group are present in this <see cref="DamageSpecifier"/>, the
|
/// total of each group. If no members of a group are present in this <see cref="DamageSpecifier"/>, the
|
||||||
/// group is not included in the resulting dictionary.
|
/// group is not included in the resulting dictionary.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public Dictionary<string, FixedPoint2> GetDamagePerGroup(IPrototypeManager? protoManager = null)
|
public Dictionary<string, FixedPoint2> GetDamagePerGroup(IPrototypeManager protoManager)
|
||||||
{
|
{
|
||||||
IoCManager.Resolve(ref protoManager);
|
var dict = new Dictionary<string, FixedPoint2>();
|
||||||
var damageGroupDict = new Dictionary<string, FixedPoint2>();
|
GetDamagePerGroup(protoManager, dict);
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="GetDamagePerGroup(Robust.Shared.Prototypes.IPrototypeManager)"/>
|
||||||
|
public void GetDamagePerGroup(IPrototypeManager protoManager, Dictionary<string, FixedPoint2> dict)
|
||||||
|
{
|
||||||
|
dict.Clear();
|
||||||
foreach (var group in protoManager.EnumeratePrototypes<DamageGroupPrototype>())
|
foreach (var group in protoManager.EnumeratePrototypes<DamageGroupPrototype>())
|
||||||
{
|
{
|
||||||
if (TryGetDamageInGroup(group, out var value))
|
if (TryGetDamageInGroup(group, out var value))
|
||||||
{
|
dict.Add(group.ID, value);
|
||||||
damageGroupDict.Add(group.ID, value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return damageGroupDict;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Operators
|
#region Operators
|
||||||
public static DamageSpecifier operator *(DamageSpecifier damageSpec, FixedPoint2 factor)
|
public static DamageSpecifier operator *(DamageSpecifier damageSpec, FixedPoint2 factor)
|
||||||
@@ -372,6 +407,8 @@ namespace Content.Shared.Damage
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FixedPoint2 this[string key] => DamageDict[key];
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ namespace Content.Shared.Damage
|
|||||||
[Dependency] private readonly INetManager _netMan = default!;
|
[Dependency] private readonly INetManager _netMan = default!;
|
||||||
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
|
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
|
||||||
|
|
||||||
|
private EntityQuery<AppearanceComponent> _appearanceQuery;
|
||||||
|
private EntityQuery<DamageableComponent> _damageableQuery;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<DamageableComponent, ComponentInit>(DamageableInit);
|
SubscribeLocalEvent<DamageableComponent, ComponentInit>(DamageableInit);
|
||||||
@@ -27,6 +30,9 @@ namespace Content.Shared.Damage
|
|||||||
SubscribeLocalEvent<DamageableComponent, ComponentGetState>(DamageableGetState);
|
SubscribeLocalEvent<DamageableComponent, ComponentGetState>(DamageableGetState);
|
||||||
SubscribeLocalEvent<DamageableComponent, OnIrradiatedEvent>(OnIrradiated);
|
SubscribeLocalEvent<DamageableComponent, OnIrradiatedEvent>(OnIrradiated);
|
||||||
SubscribeLocalEvent<DamageableComponent, RejuvenateEvent>(OnRejuvenate);
|
SubscribeLocalEvent<DamageableComponent, RejuvenateEvent>(OnRejuvenate);
|
||||||
|
|
||||||
|
_appearanceQuery = GetEntityQuery<AppearanceComponent>();
|
||||||
|
_damageableQuery = GetEntityQuery<DamageableComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -45,9 +51,9 @@ namespace Content.Shared.Damage
|
|||||||
component.Damage.DamageDict.TryAdd(type, FixedPoint2.Zero);
|
component.Damage.DamageDict.TryAdd(type, FixedPoint2.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var groupID in damageContainerPrototype.SupportedGroups)
|
foreach (var groupId in damageContainerPrototype.SupportedGroups)
|
||||||
{
|
{
|
||||||
var group = _prototypeManager.Index<DamageGroupPrototype>(groupID);
|
var group = _prototypeManager.Index<DamageGroupPrototype>(groupId);
|
||||||
foreach (var type in group.DamageTypes)
|
foreach (var type in group.DamageTypes)
|
||||||
{
|
{
|
||||||
component.Damage.DamageDict.TryAdd(type, FixedPoint2.Zero);
|
component.Damage.DamageDict.TryAdd(type, FixedPoint2.Zero);
|
||||||
@@ -63,8 +69,8 @@ namespace Content.Shared.Damage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component.DamagePerGroup = component.Damage.GetDamagePerGroup(_prototypeManager);
|
component.Damage.GetDamagePerGroup(_prototypeManager, component.DamagePerGroup);
|
||||||
component.TotalDamage = component.Damage.Total;
|
component.TotalDamage = component.Damage.GetTotal();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -90,11 +96,11 @@ namespace Content.Shared.Damage
|
|||||||
public void DamageChanged(EntityUid uid, DamageableComponent component, DamageSpecifier? damageDelta = null,
|
public void DamageChanged(EntityUid uid, DamageableComponent component, DamageSpecifier? damageDelta = null,
|
||||||
bool interruptsDoAfters = true, EntityUid? origin = null)
|
bool interruptsDoAfters = true, EntityUid? origin = null)
|
||||||
{
|
{
|
||||||
component.DamagePerGroup = component.Damage.GetDamagePerGroup(_prototypeManager);
|
component.Damage.GetDamagePerGroup(_prototypeManager, component.DamagePerGroup);
|
||||||
component.TotalDamage = component.Damage.Total;
|
component.TotalDamage = component.Damage.GetTotal();
|
||||||
Dirty(component);
|
Dirty(uid, component);
|
||||||
|
|
||||||
if (EntityManager.TryGetComponent<AppearanceComponent>(uid, out var appearance) && damageDelta != null)
|
if (_appearanceQuery.TryGetComponent(uid, out var appearance) && damageDelta != null)
|
||||||
{
|
{
|
||||||
var data = new DamageVisualizerGroupData(component.DamagePerGroup.Keys.ToList());
|
var data = new DamageVisualizerGroupData(component.DamagePerGroup.Keys.ToList());
|
||||||
_appearance.SetData(uid, DamageVisualizerKeys.DamageUpdateGroups, data, appearance);
|
_appearance.SetData(uid, DamageVisualizerKeys.DamageUpdateGroups, data, appearance);
|
||||||
@@ -117,7 +123,7 @@ namespace Content.Shared.Damage
|
|||||||
public DamageSpecifier? TryChangeDamage(EntityUid? uid, DamageSpecifier damage, bool ignoreResistances = false,
|
public DamageSpecifier? TryChangeDamage(EntityUid? uid, DamageSpecifier damage, bool ignoreResistances = false,
|
||||||
bool interruptsDoAfters = true, DamageableComponent? damageable = null, EntityUid? origin = null)
|
bool interruptsDoAfters = true, DamageableComponent? damageable = null, EntityUid? origin = null)
|
||||||
{
|
{
|
||||||
if (!uid.HasValue || !Resolve(uid.Value, ref damageable, false))
|
if (!uid.HasValue || !_damageableQuery.Resolve(uid.Value, ref damageable, false))
|
||||||
{
|
{
|
||||||
// TODO BODY SYSTEM pass damage onto body system
|
// TODO BODY SYSTEM pass damage onto body system
|
||||||
return null;
|
return null;
|
||||||
@@ -140,6 +146,8 @@ namespace Content.Shared.Damage
|
|||||||
if (damageable.DamageModifierSetId != null &&
|
if (damageable.DamageModifierSetId != null &&
|
||||||
_prototypeManager.TryIndex<DamageModifierSetPrototype>(damageable.DamageModifierSetId, out var modifierSet))
|
_prototypeManager.TryIndex<DamageModifierSetPrototype>(damageable.DamageModifierSetId, out var modifierSet))
|
||||||
{
|
{
|
||||||
|
// TODO DAMAGE PERFORMANCE
|
||||||
|
// use a local private field instead of creating a new dictionary here..
|
||||||
damage = DamageSpecifier.ApplyModifierSet(damage, modifierSet);
|
damage = DamageSpecifier.ApplyModifierSet(damage, modifierSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,20 +161,30 @@ namespace Content.Shared.Damage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the current damage, for calculating the difference
|
// TODO DAMAGE PERFORMANCE
|
||||||
DamageSpecifier oldDamage = new(damageable.Damage);
|
// Consider using a local private field instead of creating a new dictionary here.
|
||||||
|
// Would need to check that nothing ever tries to cache the delta.
|
||||||
|
var delta = new DamageSpecifier();
|
||||||
|
delta.DamageDict.EnsureCapacity(damage.DamageDict.Count);
|
||||||
|
|
||||||
damageable.Damage.ExclusiveAdd(damage);
|
var dict = damageable.Damage.DamageDict;
|
||||||
damageable.Damage.ClampMin(FixedPoint2.Zero);
|
foreach (var (type, value) in damage.DamageDict)
|
||||||
|
|
||||||
var delta = damageable.Damage - oldDamage;
|
|
||||||
delta.TrimZeros();
|
|
||||||
|
|
||||||
if (!delta.Empty)
|
|
||||||
{
|
{
|
||||||
DamageChanged(uid.Value, damageable, delta, interruptsDoAfters, origin);
|
// 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;
|
||||||
|
delta.DamageDict[type] = newValue - oldValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (delta.DamageDict.Count > 0)
|
||||||
|
DamageChanged(uid.Value, damageable, delta, interruptsDoAfters, origin);
|
||||||
|
|
||||||
return delta;
|
return delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,12 +214,11 @@ namespace Content.Shared.Damage
|
|||||||
|
|
||||||
public void SetDamageModifierSetId(EntityUid uid, string damageModifierSetId, DamageableComponent? comp = null)
|
public void SetDamageModifierSetId(EntityUid uid, string damageModifierSetId, DamageableComponent? comp = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref comp))
|
if (!_damageableQuery.Resolve(uid, ref comp))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
comp.DamageModifierSetId = damageModifierSetId;
|
comp.DamageModifierSetId = damageModifierSetId;
|
||||||
|
Dirty(uid, comp);
|
||||||
Dirty(comp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DamageableGetState(EntityUid uid, DamageableComponent component, ref ComponentGetState args)
|
private void DamageableGetState(EntityUid uid, DamageableComponent component, ref ComponentGetState args)
|
||||||
@@ -265,7 +282,7 @@ namespace Content.Shared.Damage
|
|||||||
/// Raised before damage is done, so stuff can cancel it if necessary.
|
/// Raised before damage is done, so stuff can cancel it if necessary.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public record struct BeforeDamageChangedEvent(DamageSpecifier Delta, EntityUid? Origin = null, bool Cancelled = false);
|
public record struct BeforeDamageChangedEvent(DamageSpecifier Damage, EntityUid? Origin = null, bool Cancelled = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised on an entity when damage is about to be dealt,
|
/// Raised on an entity when damage is about to be dealt,
|
||||||
@@ -312,14 +329,14 @@ namespace Content.Shared.Damage
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Was any of the damage change dealing damage, or was it all healing?
|
/// Was any of the damage change dealing damage, or was it all healing?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly bool DamageIncreased = false;
|
public readonly bool DamageIncreased;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Does this event interrupt DoAfters?
|
/// Does this event interrupt DoAfters?
|
||||||
/// Note: As provided in the constructor, this *does not* account for DamageIncreased.
|
/// Note: As provided in the constructor, this *does not* account for DamageIncreased.
|
||||||
/// As written into the event, this *does* account for DamageIncreased.
|
/// As written into the event, this *does* account for DamageIncreased.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly bool InterruptsDoAfters = false;
|
public readonly bool InterruptsDoAfters;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the entity which caused the change in damage, if any was responsible.
|
/// Contains the entity which caused the change in damage, if any was responsible.
|
||||||
|
|||||||
@@ -495,7 +495,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
|||||||
var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage + attackedEvent.BonusDamage, hitEvent.ModifiersList);
|
var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage + attackedEvent.BonusDamage, hitEvent.ModifiersList);
|
||||||
var damageResult = Damageable.TryChangeDamage(target, modifiedDamage, origin:user);
|
var damageResult = Damageable.TryChangeDamage(target, modifiedDamage, origin:user);
|
||||||
|
|
||||||
if (damageResult != null && damageResult.Total > FixedPoint2.Zero)
|
if (damageResult != null && damageResult.Any())
|
||||||
{
|
{
|
||||||
// If the target has stamina and is taking blunt damage, they should also take stamina damage based on their blunt to stamina factor
|
// If the target has stamina and is taking blunt damage, they should also take stamina damage based on their blunt to stamina factor
|
||||||
if (damageResult.DamageDict.TryGetValue("Blunt", out var bluntDamage))
|
if (damageResult.DamageDict.TryGetValue("Blunt", out var bluntDamage))
|
||||||
@@ -522,7 +522,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
Audio.PlayPredicted(hitEvent.HitSoundOverride, meleeUid, user);
|
Audio.PlayPredicted(hitEvent.HitSoundOverride, meleeUid, user);
|
||||||
}
|
}
|
||||||
else if (GetDamage(meleeUid, user, component).Total.Equals(FixedPoint2.Zero) && component.HitSound != null)
|
else if (!GetDamage(meleeUid, user, component).Any() && component.HitSound != null)
|
||||||
{
|
{
|
||||||
Audio.PlayPredicted(component.HitSound, meleeUid, user);
|
Audio.PlayPredicted(component.HitSound, meleeUid, user);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,15 +17,15 @@ namespace Content.Tests.Shared
|
|||||||
public sealed class DamageTest : ContentUnitTest
|
public sealed class DamageTest : ContentUnitTest
|
||||||
{
|
{
|
||||||
|
|
||||||
static private Dictionary<string, float> _resistanceCoefficientDict = new()
|
private static Dictionary<string, float> _resistanceCoefficientDict = new()
|
||||||
{
|
{
|
||||||
// "missing" blunt entry
|
// "missing" blunt entry
|
||||||
{ "Piercing", -2 },// Turn Piercing into Healing
|
{ "Piercing", -2 }, // negative multipliers just cause the damage to be ignored.
|
||||||
{ "Slash", 3 },
|
{ "Slash", 3 },
|
||||||
{ "Radiation", 1.5f },
|
{ "Radiation", 1.5f },
|
||||||
};
|
};
|
||||||
|
|
||||||
static private Dictionary<string, float> _resistanceReductionDict = new()
|
private static Dictionary<string, float> _resistanceReductionDict = new()
|
||||||
{
|
{
|
||||||
{ "Blunt", - 5 },
|
{ "Blunt", - 5 },
|
||||||
// "missing" piercing entry
|
// "missing" piercing entry
|
||||||
@@ -152,14 +152,14 @@ namespace Content.Tests.Shared
|
|||||||
// Apply once
|
// Apply once
|
||||||
damageSpec = DamageSpecifier.ApplyModifierSet(damageSpec, modifierSet);
|
damageSpec = DamageSpecifier.ApplyModifierSet(damageSpec, modifierSet);
|
||||||
Assert.That(damageSpec.DamageDict["Blunt"], Is.EqualTo(FixedPoint2.New(25)));
|
Assert.That(damageSpec.DamageDict["Blunt"], Is.EqualTo(FixedPoint2.New(25)));
|
||||||
Assert.That(damageSpec.DamageDict["Piercing"], Is.EqualTo(FixedPoint2.New(-40))); // became healing
|
Assert.That(!damageSpec.DamageDict.ContainsKey("Piercing")); // Cannot convert damage into healing.
|
||||||
Assert.That(damageSpec.DamageDict["Slash"], Is.EqualTo(FixedPoint2.New(6)));
|
Assert.That(damageSpec.DamageDict["Slash"], Is.EqualTo(FixedPoint2.New(6)));
|
||||||
Assert.That(damageSpec.DamageDict["Radiation"], Is.EqualTo(FixedPoint2.New(44.25)));
|
Assert.That(damageSpec.DamageDict["Radiation"], Is.EqualTo(FixedPoint2.New(44.25)));
|
||||||
|
|
||||||
// And again, checking for some other behavior
|
// And again, checking for some other behavior
|
||||||
damageSpec = DamageSpecifier.ApplyModifierSet(damageSpec, modifierSet);
|
damageSpec = DamageSpecifier.ApplyModifierSet(damageSpec, modifierSet);
|
||||||
Assert.That(damageSpec.DamageDict["Blunt"], Is.EqualTo(FixedPoint2.New(30)));
|
Assert.That(damageSpec.DamageDict["Blunt"], Is.EqualTo(FixedPoint2.New(30)));
|
||||||
Assert.That(damageSpec.DamageDict["Piercing"], Is.EqualTo(FixedPoint2.New(-40))); // resistances don't apply to healing
|
Assert.That(!damageSpec.DamageDict.ContainsKey("Piercing"));
|
||||||
Assert.That(!damageSpec.DamageDict.ContainsKey("Slash")); // Reduction reduced to 0, and removed from specifier
|
Assert.That(!damageSpec.DamageDict.ContainsKey("Slash")); // Reduction reduced to 0, and removed from specifier
|
||||||
Assert.That(damageSpec.DamageDict["Radiation"], Is.EqualTo(FixedPoint2.New(65.63)));
|
Assert.That(damageSpec.DamageDict["Radiation"], Is.EqualTo(FixedPoint2.New(65.63)));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user