Generalize ReagentUnit into FixedPoint2 and use it for damage calculations (#5151)

* Damage units

* sum ext method
This commit is contained in:
mirrorcult
2021-11-03 16:48:03 -07:00
committed by GitHub
parent 8165d8f38c
commit 3ab4a30a0f
100 changed files with 730 additions and 601 deletions

View File

@@ -9,12 +9,10 @@ using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.FixedPoint;
namespace Content.Shared.Damage
{
// TODO DAMAGE UNITS Move this whole class away from, using integers. Also get rid of a lot of the rounding. Just
// use DamageUnit math operators.
/// <summary>
/// This class represents a collection of damage types and damage values.
/// </summary>
@@ -25,17 +23,17 @@ namespace Content.Shared.Damage
[DataDefinition]
public class DamageSpecifier
{
[DataField("types", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, DamageTypePrototype>))]
private readonly Dictionary<string,int>? _damageTypeDictionary;
[DataField("types", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, DamageTypePrototype>))]
private readonly Dictionary<string,FixedPoint2>? _damageTypeDictionary;
[DataField("groups", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, DamageGroupPrototype>))]
private readonly Dictionary<string, int>? _damageGroupDictionary;
[DataField("groups", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, DamageGroupPrototype>))]
private readonly Dictionary<string, FixedPoint2>? _damageGroupDictionary;
/// <summary>
/// Main DamageSpecifier dictionary. Most DamageSpecifier functions exist to somehow modifying this.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public Dictionary<string, int> DamageDict
public Dictionary<string, FixedPoint2> DamageDict
{
get
{
@@ -45,7 +43,7 @@ namespace Content.Shared.Damage
}
set => _damageDict = value;
}
private Dictionary<string, int>? _damageDict;
private Dictionary<string, FixedPoint2>? _damageDict;
/// <summary>
/// Sum of the damage values.
@@ -55,7 +53,7 @@ namespace Content.Shared.Damage
/// in another. For this purpose, you should instead use <see cref="TrimZeros()"/> and then check the <see
/// cref="Empty"/> property.
/// </remarks>
public int Total => DamageDict.Values.Sum();
public FixedPoint2 Total => DamageDict.Values.Sum();
/// <summary>
/// Whether this damage specifier has any entries.
@@ -79,7 +77,7 @@ namespace Content.Shared.Damage
/// <summary>
/// Constructor that takes a single damage type prototype and a damage value.
/// </summary>
public DamageSpecifier(DamageTypePrototype type, int value)
public DamageSpecifier(DamageTypePrototype type, FixedPoint2 value)
{
DamageDict = new() { { type.ID, value } };
}
@@ -87,14 +85,14 @@ namespace Content.Shared.Damage
/// <summary>
/// Constructor that takes a single damage group prototype and a damage value. The value is divided between members of the damage group.
/// </summary>
public DamageSpecifier(DamageGroupPrototype group, int value)
public DamageSpecifier(DamageGroupPrototype group, FixedPoint2 value)
{
_damageGroupDictionary = new() { { group.ID, value } };
}
#endregion constructors
/// <summary>
/// Combines the damage group and type datafield dictionaries into a single damage dictionary.
/// Combines the damage group and type datafield dictionaries FixedPoint2o a single damage dictionary.
/// </summary>
public void DeserializeDamage()
{
@@ -128,7 +126,7 @@ namespace Content.Shared.Damage
var remainingDamage = entry.Value;
foreach (var damageType in group.DamageTypes)
{
var damage = remainingDamage / remainingTypes;
var damage = remainingDamage / FixedPoint2.New(remainingTypes);
if (!_damageDict.TryAdd(damageType, damage))
{
// Key already exists, add values
@@ -149,7 +147,7 @@ namespace Content.Shared.Damage
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
// duller as you hit walls. Neat, but not intended. 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.
DamageSpecifier newDamage = new(damageSpec);
@@ -157,7 +155,7 @@ namespace Content.Shared.Damage
{
if (entry.Value <= 0) continue;
float newValue = entry.Value;
float newValue = entry.Value.Float();
if (modifierSet.FlatReduction.TryGetValue(entry.Key, out var reduction))
{
@@ -165,7 +163,7 @@ namespace Content.Shared.Damage
if (newValue <= 0)
{
// flat reductions cannot heal you
newDamage.DamageDict[entry.Key] = 0;
newDamage.DamageDict[entry.Key] = FixedPoint2.Zero;
continue;
}
}
@@ -173,10 +171,10 @@ namespace Content.Shared.Damage
if (modifierSet.Coefficients.TryGetValue(entry.Key, out var coefficient))
{
// negative coefficients **can** heal you.
newValue = MathF.Round(newValue*coefficient, MidpointRounding.AwayFromZero);
newValue = newValue * coefficient;
}
newDamage.DamageDict[entry.Key] = (int) newValue;
newDamage.DamageDict[entry.Key] = FixedPoint2.New(newValue);
}
newDamage.TrimZeros();
@@ -218,7 +216,7 @@ namespace Content.Shared.Damage
/// <summary>
/// Clamps each damage value to be within the given range.
/// </summary>
public void Clamp(int minValue = 0, int maxValue = 0)
public void Clamp(FixedPoint2 minValue, FixedPoint2 maxValue)
{
DebugTools.Assert(minValue < maxValue);
ClampMax(maxValue);
@@ -231,7 +229,7 @@ namespace Content.Shared.Damage
/// <remarks>
/// Note that this only acts on damage types present in the dictionary. It will not add new damage types.
/// </remarks>
public void ClampMin(int minValue = 0)
public void ClampMin(FixedPoint2 minValue)
{
foreach (var (key, value) in DamageDict)
{
@@ -246,7 +244,7 @@ namespace Content.Shared.Damage
/// Sets all damage values to be at most some number. Note that if a damage type is not present in the
/// dictionary, these will not be added.
/// </summary>
public void ClampMax(int maxValue = 0)
public void ClampMax(FixedPoint2 maxValue)
{
foreach (var (key, value) in DamageDict)
{
@@ -282,10 +280,10 @@ namespace Content.Shared.Damage
/// <remarks>
/// If no members of the group are included in this specifier, returns false.
/// </remarks>
public bool TryGetDamageInGroup(DamageGroupPrototype group, out int total)
public bool TryGetDamageInGroup(DamageGroupPrototype group, out FixedPoint2 total)
{
bool containsMemeber = false;
total = 0;
total = FixedPoint2.Zero;
foreach (var type in group.DamageTypes)
{
@@ -307,9 +305,9 @@ namespace Content.Shared.Damage
/// 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.
/// </remarks>
public Dictionary<string, int> GetDamagePerGroup()
public Dictionary<string, FixedPoint2> GetDamagePerGroup()
{
var damageGroupDict = new Dictionary<string, int>();
var damageGroupDict = new Dictionary<string, FixedPoint2>();
foreach (var group in IoCManager.Resolve<IPrototypeManager>().EnumeratePrototypes<DamageGroupPrototype>())
{
if (TryGetDamageInGroup(group, out var value))
@@ -321,7 +319,7 @@ namespace Content.Shared.Damage
}
#region Operators
public static DamageSpecifier operator *(DamageSpecifier damageSpec, int factor)
public static DamageSpecifier operator *(DamageSpecifier damageSpec, FixedPoint2 factor)
{
DamageSpecifier newDamage = new();
foreach (var entry in damageSpec.DamageDict)
@@ -336,17 +334,17 @@ namespace Content.Shared.Damage
DamageSpecifier newDamage = new();
foreach (var entry in damageSpec.DamageDict)
{
newDamage.DamageDict.Add(entry.Key, (int) MathF.Round(entry.Value * factor, MidpointRounding.AwayFromZero));
newDamage.DamageDict.Add(entry.Key, entry.Value * factor);
}
return newDamage;
}
public static DamageSpecifier operator /(DamageSpecifier damageSpec, int factor)
public static DamageSpecifier operator /(DamageSpecifier damageSpec, FixedPoint2 factor)
{
DamageSpecifier newDamage = new();
foreach (var entry in damageSpec.DamageDict)
{
newDamage.DamageDict.Add(entry.Key, (int) MathF.Round(entry.Value / (float) factor, MidpointRounding.AwayFromZero));
newDamage.DamageDict.Add(entry.Key, entry.Value / factor);
}
return newDamage;
}
@@ -357,7 +355,7 @@ namespace Content.Shared.Damage
foreach (var entry in damageSpec.DamageDict)
{
newDamage.DamageDict.Add(entry.Key, (int) MathF.Round(entry.Value / factor, MidpointRounding.AwayFromZero));
newDamage.DamageDict.Add(entry.Key, entry.Value / factor);
}
return newDamage;
}
@@ -387,7 +385,7 @@ namespace Content.Shared.Damage
public static DamageSpecifier operator *(float factor, DamageSpecifier damageSpec) => damageSpec * factor;
public static DamageSpecifier operator *(int factor, DamageSpecifier damageSpec) => damageSpec * factor;
public static DamageSpecifier operator *(FixedPoint2 factor, DamageSpecifier damageSpec) => damageSpec * factor;
}
#endregion
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Content.Shared.Acts;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Radiation;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
@@ -64,12 +65,12 @@ namespace Content.Shared.Damage
/// Groups which have no members that are supported by this component will not be present in this
/// dictionary.
/// </remarks>
[ViewVariables] public Dictionary<string, int> DamagePerGroup = new();
[ViewVariables] public Dictionary<string, FixedPoint2> DamagePerGroup = new();
/// <summary>
/// The sum of all damages in the DamageableComponent.
/// </summary>
[ViewVariables] public int TotalDamage;
[ViewVariables] public FixedPoint2 TotalDamage;
// Really these shouldn't be here. OnExplosion() and RadiationAct() should be handled elsewhere.
[ViewVariables]
@@ -82,7 +83,7 @@ namespace Content.Shared.Damage
// TODO RADIATION Remove this.
void IRadiationAct.RadiationAct(float frameTime, SharedRadiationPulseComponent radiation)
{
var damageValue = Math.Max((int) (frameTime * radiation.RadsPerSecond), 1);
var damageValue = FixedPoint2.New(MathF.Max((frameTime * radiation.RadsPerSecond), 1));
// Radiation should really just be a damage group instead of a list of types.
DamageSpecifier damage = new();
@@ -99,9 +100,9 @@ namespace Content.Shared.Damage
{
var damageValue = eventArgs.Severity switch
{
ExplosionSeverity.Light => 20,
ExplosionSeverity.Heavy => 60,
ExplosionSeverity.Destruction => 250,
ExplosionSeverity.Light => FixedPoint2.New(20),
ExplosionSeverity.Heavy => FixedPoint2.New(60),
ExplosionSeverity.Destruction => FixedPoint2.New(250),
_ => throw new ArgumentOutOfRangeException()
};
@@ -119,11 +120,11 @@ namespace Content.Shared.Damage
[Serializable, NetSerializable]
public class DamageableComponentState : ComponentState
{
public readonly Dictionary<string, int> DamageDict;
public readonly Dictionary<string, FixedPoint2> DamageDict;
public readonly string? ModifierSetId;
public DamageableComponentState(
Dictionary<string, int> damageDict,
Dictionary<string, FixedPoint2> damageDict,
string? modifierSetId)
{
DamageDict = damageDict;

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
@@ -33,7 +34,7 @@ namespace Content.Shared.Damage
// container prototype
foreach (var type in damageContainerPrototype.SupportedTypes)
{
component.Damage.DamageDict.TryAdd(type, 0);
component.Damage.DamageDict.TryAdd(type, FixedPoint2.Zero);
}
foreach (var groupID in damageContainerPrototype.SupportedGroups)
@@ -41,7 +42,7 @@ namespace Content.Shared.Damage
var group = _prototypeManager.Index<DamageGroupPrototype>(groupID);
foreach (var type in group.DamageTypes)
{
component.Damage.DamageDict.TryAdd(type, 0);
component.Damage.DamageDict.TryAdd(type, FixedPoint2.Zero);
}
}
}
@@ -50,7 +51,7 @@ namespace Content.Shared.Damage
// No DamageContainerPrototype was given. So we will allow the container to support all damage types
foreach (var type in _prototypeManager.EnumeratePrototypes<DamageTypePrototype>())
{
component.Damage.DamageDict.TryAdd(type.ID, 0);
component.Damage.DamageDict.TryAdd(type.ID, FixedPoint2.Zero);
}
}
@@ -143,7 +144,7 @@ namespace Content.Shared.Damage
DamageSpecifier oldDamage = new(damageable.Damage);
damageable.Damage.ExclusiveAdd(damage);
damageable.Damage.ClampMin(0);
damageable.Damage.ClampMin(FixedPoint2.Zero);
var delta = damageable.Damage - oldDamage;
delta.TrimZeros();
@@ -162,7 +163,7 @@ namespace Content.Shared.Damage
/// <remakrs>
/// Does nothing If the given damage value is negative.
/// </remakrs>
public void SetAllDamage(DamageableComponent component, int newValue)
public void SetAllDamage(DamageableComponent component, FixedPoint2 newValue)
{
if (newValue < 0)
{