Damageable Refactor 3: Revenge of the Instamerge (#4524)

* Add DamageType And DamageGroup Prototypes

* Remove DamageTypePrototype Field "name" as its redundant

* Change I/DamageableComponent to use prototypes

* Update DamageContainer, ReisistanceSet and DamageChangeData

* Change Barotrauma Component to use DamageType from DamageSystem

* Update AsteroidRockComponent

* update some more components

* update some more components

* Fix m o r e c o m p o n e n t s and their damageType

* all thats left is bug/missing node hunting then verification.

* push changes

* update submodule

* Merge fixes

* push DGP for example

* update damagecomponent across shared and server

* fix a few bugs

* Fix Merge issues

* Refactor damageablecomponent update (#4406)

* Fixing merge.

I messed up part of the merge. this should fix it?

* Barotrauma now uses prototypeManager

As System.Runtime.CompilerServices also has a [Dependency], I think I had to use the full path [Robust.Shared.IoC.Dependency]

* FlammableComponent now uses prototypeManager

* SuicideCommands now use prototypeManager

* Changed  many files to use prototypeManager to resolve damaege prototypes

Yeah.... prototype references would be very nice. maybe this was all a waste of time.

* Grouping prototypeManager.Index with datafield definitions

This will make it easier to eventually add prototype references

* removed unused variable

* Moved lines around.

Lines now consistent with other TODO PROTOTYPE blocks

* Grouping more prototypeManager.Index with datafield definitions

* Removed unnecessary code

* Added more prototypeManager indexing

These ones weren't pointed out by DrSmug. But I think this is all of them? That or my regex is shit.

* Remove redundant _damage field

* Remove redundant _currentTemperature

* Moved variables down

* Added prototypeManager indexing to TemperatureComponent

* WeaponComponent/System now use ProtptypeManager

And as far as I can tell damageType is required, and therefore should never have been null anyway?

* Make ranged weapon clumsy fire effects datafields

And yes, the order in which the clumsy effects occur is very important.

* Made damage on vital body part loss a datafield

* Renamed several damageGroup variables to group

* Capitalised DamageListToDamageGroup

* Make radiation and explosion damage types datafields

* Renamed _supportedDamageGroupIDs and _supportedDamageTypeIDs

* Fixed mistakes

Frogot to remove prototypeManager index DamageTypeTrigger, and wrong variable visibility in TemperatureComponent

* Added necessary code

Is something tragically wrong?

* MeleeWeapon damageType is not actually required

* Fixing someone else's mistakes

A search comes up with nothing in the yaml files, and its not a required field. So no one uses it? Hopefully?

* Changed and renamed damageTypeToDamageGroup

Previously would incorrectly return the total container damage for each group, not the total in the group

* renaming varitables

* Renamed variable DamageClasses

* Added dictionary converting functions

* Added ID-keyed dictionaries

* Making MedicalScanner use ID dictionaries, instead of prototype dictionaries

Oh oh no. I've been able to avoid UI & networking up until now. I have no Idea what I am doing.

* Fix Medical Scanner

* Summary (required)

The joke here is that this fixes the empty summary.

* Removed DamageableComponent.GetDamageGroup/Type

* Renamed "damage classes" to groups.

* Update ChangeDamage description

* Replaced Heal() with SettAllDamage()

Heal() was just a confusing name,

* More Class -> Group renaming

* Replace Class with Group in yaml files

DamageClassTrigger does not appear in any yaml? only in testing?
DamageTypeTrigger appears only in human.yaml?
HealthChangeMetabolism is Mostly in medicine.yml and one in soad.yaml

Why the hell is Cola metabolizable by plants? Who is pouring cola on their plants!?!?

* Fix _prototypeManager being null errors.

* Changing comments

Where are the prototype references

* MetabolismComponent doesn't give free heals anymore.

* Changes HungerComponent healing.

Previously I think it would actually damage you.  Only did this as I though it was causing the fast healing. Turns out that was just BREATHING.

* Generalised a function in DamageableComponent and moved it to DamageGroupPrototype

previously DamageTypesDictToDamageGroupDict was private to DamageableComponent, but was also quite general (nearly a static function). As this sort of function may be needed by other components using DamageGroupPrototypes in the future, I moved it there as a static function instead.

* modified DamageableComponent.ChangeDamage()

ignoreResistances was renamed to ignoreDamageResistances to make it clearer that it had no effect on healing.

Now uses default argument for ignoreDamageResistances, so when healing you are not forced to specify an argument that does nothing.

Also made some general changes to ignoreResistances()

* Changed class->group and added missing damage type functionality to DamageContainerPrototypes

* Added Comments to damage.yml

* Misc Changes to DamageableComponent

* Differentiated between group support and group applicability

So far, every damage type is a member of one, and only one, damage group. So this change has no real effect so far.

* Added proposed alternative to ChangeDamage()

* fixed error in DamageGroupPrototype

* Changes to DamageableComponent

Lots of changes to comments.
Some variables renamed in IDamageableComponent and DamageableComponent (also required renaming in other files)

Some minor logic changes, mostly for incorrect descirptions of boolean return values.

Also further differentiating between ApplicableGroups and SupportedGroups... if that will ever even matter

* Generalised MedicalScannerComponent

If needed, can print miscellaneous damage types now

* Fixed HealthChangeMetabolism bug

* Changing Comments around

* More questions

* Made Barotrauma default to blunt

* Fix RejuvenateTest.cs

* Comments

* Coments and variable names

* fix some master-merge issues

* Removed redundant fields

* Misc changes for readbility of PR diff

* Consistent naming

* Fixed atmos damage bug

* Removed Ranting

* Fixed Hunger after I broke it

* Fixing Bugs

* Removed stupid question

* Removed more stupid questions

* Fix potential null errors.

* Made boolean return values consistent

Also renamed several functions, to make it clear they return a bool. Docs were also updated.

* Removed IoCManager.InjectDependencies()

* Removed unnecessary 'suffocation' prefix

* Fixed Spelling

Also removed accidentally left in logger call

* Fixed Medical Scanner

* Apply suggestions from code review

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

* Changing comments and whitespaces

* Made damage thresholds trigger  datafields required

* So many typos

* Changes to DamageableComponents

Changed documentation in IDamageableComponent

Made testing code more readable.

Relabelled groups as 'Applicable' either 'Fully Supported'

* Removed function and degeneralised

* Update DamageableComponent.cs

Removed unused parameters
Fixed Networking

* Added IoCManager.Resolve

* Now using alternative TryChangeDamage()

* Removed function from DamageGroupPrototype

* Removing comments

* Remove bad if statement?

* Fix damageChanged ordering

* Fix hurt server command

* Changed //TODO PROTOTYPE blocks

Now use PrototypeManager differently. Wherever possible, only retrieve the prototype once.

Also added default damage types to some more datafields

* Update Content.Shared/Damage/Container/DamageContainerPrototype.cs

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

* renamed _accumulatedHealth -> _accumulatedDamage and added TODOs

* Another class-> group

* Fix bug in generalisation of damage container prototypes

* Addes Tests to make sure I dont keep adding bugs to my own code.

* Changed Return values when setting

* Removed unused class

* Added more tests, split tests into three files

* Made damage types public and VV read-write-able

* Minor changes to DamageableComponent

Replaced internal use of GetDamagePerType with _damageDict and removed some unnecessary fields

* Fix Suicide, by adding IoC Resolve()

* Fix DamageGroupTrigger bug

* Fix typos in tests

* Change comments./docstrings & spacing

* Merge tests, use test prototypes

Co-authored-by: Leon Friedrich <60421075+leonsfriedrich@users.noreply.github.com>
Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

* Fix merge issues

Co-authored-by: Silver <Silvertorch5@gmail.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
Co-authored-by: Leon Friedrich <60421075+leonsfriedrich@users.noreply.github.com>
This commit is contained in:
Leon Friedrich
2021-08-25 03:06:27 +10:00
committed by GitHub
parent 2a8dc0e9c7
commit 32d99eaba9
71 changed files with 2048 additions and 983 deletions

View File

@@ -18,8 +18,15 @@ namespace Content.Shared.Damage.Components
{
/// <summary>
/// Component that allows attached entities to take damage.
/// This basic version never dies (thus can take an indefinite amount of damage).
/// </summary>
/// <remarks>
/// The supported damage types are specified using a <see cref="DamageContainerPrototype"/>s. DamageContainers
/// are effectively a dictionary of damage types and damage numbers, along with functions to modify them. Damage
/// groups are collections of damage types. A damage group is 'applicable' to a damageable component if it
/// supports at least one damage type in that group. A subset of these groups may be 'fully supported' when every
/// member of the group is supported by the container. This basic version never dies (thus can take an
/// indefinite amount of damage).
/// </remarks>
[RegisterComponent]
[ComponentReference(typeof(IDamageableComponent))]
[NetworkedComponent()]
@@ -27,58 +34,76 @@ namespace Content.Shared.Damage.Components
{
public override string Name => "Damageable";
// TODO define these in yaml?
public const string DefaultResistanceSet = "defaultResistances";
public const string DefaultDamageContainer = "metallicDamageContainer";
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly Dictionary<DamageType, int> _damageList = DamageTypeExtensions.ToNewDictionary();
/// <summary>
/// The main damage dictionary. All the damage information is stored in this dictionary with <see cref="DamageTypePrototype"/> keys.
/// </summary>
private Dictionary<DamageTypePrototype, int> _damageDict = new();
[DataField("resistances")] public string ResistanceSetId = DefaultResistanceSet;
// TODO DAMAGE Use as default values, specify overrides in a separate property through yaml for better (de)serialization
[ViewVariables] [DataField("damageContainer")] public string DamageContainerId { get; set; } = DefaultDamageContainer;
[DataField("resistances")]
public string ResistanceSetId { get; set; } = "defaultResistances";
[ViewVariables] public ResistanceSet Resistances { get; set; } = new();
// TODO DAMAGE Use as default values, specify overrides in a separate property through yaml for better (de)serialization
[ViewVariables]
[DataField("damageContainer")]
public string DamageContainerId { get; set; } = "metallicDamageContainer";
// TODO DAMAGE Cache this
[ViewVariables] public int TotalDamage => _damageList.Values.Sum();
// When moving logic from damageableComponent --> Damage System, make damageSystem update these on damage change.
[ViewVariables] public int TotalDamage => _damageDict.Values.Sum();
[ViewVariables] public IReadOnlyDictionary<DamageTypePrototype, int> GetDamagePerType => _damageDict;
[ViewVariables] public IReadOnlyDictionary<DamageGroupPrototype, int> GetDamagePerApplicableGroup => DamageTypeDictToDamageGroupDict(_damageDict, ApplicableDamageGroups);
[ViewVariables] public IReadOnlyDictionary<DamageGroupPrototype, int> GetDamagePerFullySupportedGroup => DamageTypeDictToDamageGroupDict(_damageDict, FullySupportedDamageGroups);
[ViewVariables] public IReadOnlyDictionary<DamageClass, int> DamageClasses => _damageList.ToClassDictionary();
// Whenever sending over network, also need a <string, int> dictionary
// TODO DAMAGE MAYBE Cache this?
public IReadOnlyDictionary<string, int> GetDamagePerApplicableGroupIDs => ConvertDictKeysToIDs(GetDamagePerApplicableGroup);
public IReadOnlyDictionary<string, int> GetDamagePerFullySupportedGroupIDs => ConvertDictKeysToIDs(GetDamagePerFullySupportedGroup);
public IReadOnlyDictionary<string, int> GetDamagePerTypeIDs => ConvertDictKeysToIDs(_damageDict);
[ViewVariables] public IReadOnlyDictionary<DamageType, int> DamageTypes => _damageList;
// TODO PROTOTYPE Replace these datafield variables with prototype references, once they are supported.
// Also requires appropriate changes in OnExplosion() and RadiationAct()
[ViewVariables]
[DataField("radiationDamageTypes")]
public List<string> RadiationDamageTypeIDs { get; set; } = new() {"Radiation"};
[ViewVariables]
[DataField("explosionDamageTypes")]
public List<string> ExplosionDamageTypeIDs { get; set; } = new() { "Piercing", "Heat" };
[ViewVariables] public HashSet<DamageType> SupportedTypes { get; } = new();
public HashSet<DamageGroupPrototype> ApplicableDamageGroups { get; } = new();
[ViewVariables] public HashSet<DamageClass> SupportedClasses { get; } = new();
public HashSet<DamageGroupPrototype> FullySupportedDamageGroups { get; } = new();
public bool SupportsDamageClass(DamageClass @class)
{
return SupportedClasses.Contains(@class);
}
public bool SupportsDamageType(DamageType type)
{
return SupportedTypes.Contains(type);
}
public HashSet<DamageTypePrototype> SupportedDamageTypes { get; } = new();
protected override void Initialize()
{
base.Initialize();
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
// TODO DAMAGE Serialize damage done and resistance changes
var damagePrototype = prototypeManager.Index<DamageContainerPrototype>(DamageContainerId);
var damageContainerPrototype = _prototypeManager.Index<DamageContainerPrototype>(DamageContainerId);
SupportedClasses.Clear();
SupportedTypes.Clear();
ApplicableDamageGroups.Clear();
FullySupportedDamageGroups.Clear();
SupportedDamageTypes.Clear();
DamageContainerId = damagePrototype.ID;
SupportedClasses.UnionWith(damagePrototype.SupportedClasses);
SupportedTypes.UnionWith(damagePrototype.SupportedTypes);
//Get Damage groups/types from the DamageContainerPrototype.
DamageContainerId = damageContainerPrototype.ID;
ApplicableDamageGroups.UnionWith(damageContainerPrototype.ApplicableDamageGroups);
FullySupportedDamageGroups.UnionWith(damageContainerPrototype.FullySupportedDamageGroups);
SupportedDamageTypes.UnionWith(damageContainerPrototype.SupportedDamageTypes);
var resistancePrototype = prototypeManager.Index<ResistanceSetPrototype>(ResistanceSetId);
Resistances = new ResistanceSet(resistancePrototype);
//initialize damage dictionary 0 damage
_damageDict = new(SupportedDamageTypes.Count);
foreach (var type in SupportedDamageTypes)
{
_damageDict.Add(type, 0);
}
Resistances = new ResistanceSet(_prototypeManager.Index<ResistanceSetPrototype>(ResistanceSetId));
}
protected override void Startup()
@@ -90,7 +115,7 @@ namespace Content.Shared.Damage.Components
public override ComponentState GetComponentState(ICommonSession player)
{
return new DamageableComponentState(_damageList);
return new DamageableComponentState(GetDamagePerTypeIDs);
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
@@ -102,113 +127,101 @@ namespace Content.Shared.Damage.Components
return;
}
_damageList.Clear();
_damageDict.Clear();
foreach (var (type, damage) in state.DamageList)
foreach (var (type, damage) in state.DamageDict)
{
_damageList[type] = damage;
_damageDict[_prototypeManager.Index<DamageTypePrototype>(type)] = damage;
}
}
public int GetDamage(DamageType type)
public int GetDamage(DamageTypePrototype type)
{
return _damageList.GetValueOrDefault(type);
return GetDamagePerType.GetValueOrDefault(type);
}
public bool TryGetDamage(DamageType type, out int damage)
public bool TryGetDamage(DamageTypePrototype type, out int damage)
{
return _damageList.TryGetValue(type, out damage);
return GetDamagePerType.TryGetValue(type, out damage);
}
public int GetDamage(DamageClass @class)
public int GetDamage(DamageGroupPrototype group)
{
if (!SupportsDamageClass(@class))
{
return 0;
}
var damage = 0;
foreach (var type in @class.ToTypes())
{
damage += GetDamage(type);
}
return damage;
return GetDamagePerApplicableGroup.GetValueOrDefault(group);
}
public bool TryGetDamage(DamageClass @class, out int damage)
public bool TryGetDamage(DamageGroupPrototype group, out int damage)
{
if (!SupportsDamageClass(@class))
return GetDamagePerApplicableGroup.TryGetValue(group, out damage);
}
public bool IsApplicableDamageGroup(DamageGroupPrototype group)
{
return ApplicableDamageGroups.Contains(group);
}
public bool IsFullySupportedDamageGroup(DamageGroupPrototype group)
{
return FullySupportedDamageGroups.Contains(group);
}
public bool IsSupportedDamageType(DamageTypePrototype type)
{
return SupportedDamageTypes.Contains(type);
}
public bool TrySetDamage(DamageGroupPrototype group, int newValue)
{
if (!ApplicableDamageGroups.Contains(group))
{
damage = 0;
return false;
}
damage = GetDamage(@class);
if (newValue < 0)
{
// invalid value
return false;
}
foreach (var type in group.DamageTypes)
{
TrySetDamage(type, newValue);
}
return true;
}
/// <summary>
/// Attempts to set the damage value for the given <see cref="DamageType"/>.
/// </summary>
/// <returns>
/// True if successful, false if this container does not support that type.
/// </returns>
public bool TrySetDamage(DamageType type, int newValue)
public bool TrySetAllDamage(int newValue)
{
if (newValue < 0)
{
// invalid value
return false;
}
var damageClass = type.ToClass();
if (SupportedClasses.Contains(damageClass))
foreach (var type in SupportedDamageTypes)
{
var old = _damageList[type] = newValue;
_damageList[type] = newValue;
var delta = newValue - old;
var datum = new DamageChangeData(type, newValue, delta);
var data = new List<DamageChangeData> {datum};
OnHealthChanged(data);
return true;
TrySetDamage(type, newValue);
}
return false;
return true;
}
public void Heal(DamageType type)
public bool TryChangeDamage(DamageTypePrototype type, int amount, bool ignoreDamageResistances = false)
{
SetDamage(type, 0);
}
public void Heal()
{
foreach (var type in SupportedTypes)
{
Heal(type);
}
}
public bool ChangeDamage(
DamageType type,
int amount,
bool ignoreResistances,
IEntity? source = null,
DamageChangeParams? extraParams = null)
{
if (!SupportsDamageType(type))
// Check if damage type is supported, and get the current value if it is.
if (!GetDamagePerType.TryGetValue(type, out var current))
{
return false;
}
if (amount == 0)
{
return false;
}
// Apply resistances (does nothing if amount<0)
var finalDamage = amount;
if (!ignoreResistances)
if (!ignoreDamageResistances)
{
finalDamage = Resistances.CalculateDamage(type, amount);
}
@@ -216,24 +229,23 @@ namespace Content.Shared.Damage.Components
if (finalDamage == 0)
return false;
if (!_damageList.TryGetValue(type, out var current))
{
return false;
}
// Are we healing below zero?
if (current + finalDamage < 0)
{
if (current == 0)
// Damage type is supported, but there is nothing to do
return false;
_damageList[type] = 0;
// Cap healing down to zero
_damageDict[type] = 0;
finalDamage = -current;
}
else
{
_damageList[type] = current + finalDamage;
_damageDict[type] = current + finalDamage;
}
current = _damageList[type];
current = _damageDict[type];
var datum = new DamageChangeData(type, current, finalDamage);
var data = new List<DamageChangeData> {datum};
@@ -243,107 +255,117 @@ namespace Content.Shared.Damage.Components
return true;
}
public bool ChangeDamage(DamageClass @class, int amount, bool ignoreResistances,
IEntity? source = null,
DamageChangeParams? extraParams = null)
public bool TryChangeDamage(DamageGroupPrototype group, int amount, bool ignoreDamageResistances = false)
{
if (!SupportsDamageClass(@class))
{
return false;
}
var types = @class.ToTypes();
var types = group.DamageTypes.ToArray();
if (amount < 0)
{
// Changing multiple types is a bit more complicated. Might be a better way (formula?) to do this,
// but essentially just loops between each damage category until all healing is used up.
var healingLeft = -amount;
var healThisCycle = 1;
// We are Healing. Keep track of how much we can hand out (with a better var name for readability).
var availableHealing = -amount;
// While we have healing left...
while (healingLeft > 0 && healThisCycle != 0)
// Get total group damage.
var damageToHeal = GetDamagePerApplicableGroup[group];
// Is there any damage to even heal?
if (damageToHeal == 0)
return false;
// If total healing is more than there is damage, just set to 0 and return.
if (damageToHeal <= availableHealing)
{
// Infinite loop fallback, if no healing was done in a cycle
// then exit
healThisCycle = 0;
int healPerType;
if (healingLeft < types.Count)
{
// Say we were to distribute 2 healing between 3
// this will distribute 1 to each (and stop after 2 are given)
healPerType = 1;
}
else
{
// Say we were to distribute 62 healing between 3
// this will distribute 20 to each, leaving 2 for next loop
healPerType = healingLeft / types.Count;
}
foreach (var type in types)
{
var damage = GetDamage(type);
var healAmount = Math.Min(healingLeft, damage);
healAmount = Math.Min(healAmount, healPerType);
ChangeDamage(type, -healAmount, true);
healThisCycle += healAmount;
healingLeft -= healAmount;
}
}
return true;
}
var damageLeft = amount;
while (damageLeft > 0)
{
int damagePerType;
if (damageLeft < types.Count)
{
damagePerType = 1;
}
else
{
damagePerType = damageLeft / types.Count;
TrySetDamage(group, 0);
return true;
}
// Partially heal each damage group
int healing, damage;
foreach (var type in types)
{
var damageAmount = Math.Min(damagePerType, damageLeft);
ChangeDamage(type, damageAmount, true);
damageLeft -= damageAmount;
if (!_damageDict.TryGetValue(type, out damage))
{
// Damage Type is not supported. Continue without reducing availableHealing
continue;
}
// Apply healing to the damage type. The healing amount may be zero if either damage==0, or if
// integer rounding made it zero (i.e., damage is small)
healing = (availableHealing * damage) / damageToHeal;
TryChangeDamage(type, -healing, ignoreDamageResistances);
// remove this damage type from the damage we consider for future loops, regardless of how much we
// actually healed this type.
damageToHeal -= damage;
availableHealing -= healing;
// If we now healed all the damage, exit. otherwise 1/0 and universe explodes.
if (damageToHeal == 0)
{
break;
}
}
// Damage type is supported, there was damage to heal, and resistances were ignored
// --> Damage must have changed
return true;
}
else if (amount > 0)
{
// Resistances may result in no actual damage change. We need to keep track if any damage got through.
var damageChanged = false;
// We are adding damage. Keep track of how much we can dish out (with a better var name for readability).
var availableDamage = amount;
// How many damage types do we have to distribute over?.
var numberDamageTypes = types.Length;
// Apply damage to each damage group
int damage;
foreach (var type in types)
{
// Distribute the remaining damage over the remaining damage types.
damage = availableDamage / numberDamageTypes;
// Try apply the damage type. If damage type is not supported, this has no effect.
// We also use the return value to check whether any damage has changed
damageChanged = TryChangeDamage(type, damage, ignoreDamageResistances) || damageChanged;
// regardless of whether we dealt damage, reduce the amount to distribute.
availableDamage -= damage;
numberDamageTypes -= 1;
}
return damageChanged;
}
return true;
// amount==0 no damage change.
return false;
}
public bool SetDamage(DamageType type, int newValue, IEntity? source = null, DamageChangeParams? extraParams = null)
public bool TrySetDamage(DamageTypePrototype type, int newValue)
{
if (newValue >= TotalDamage)
if (!_damageDict.TryGetValue(type, out var oldValue))
{
return false;
}
if (newValue < 0)
{
// invalid value
return false;
}
if (!_damageList.ContainsKey(type))
if (oldValue == newValue)
{
return false;
// No health change.
// But we are trying to set, not trying to change.
return true;
}
var old = _damageList[type];
_damageList[type] = newValue;
_damageDict[type] = newValue;
var delta = newValue - old;
var delta = newValue - oldValue;
var datum = new DamageChangeData(type, 0, delta);
var data = new List<DamageChangeData> {datum};
@@ -356,7 +378,7 @@ namespace Content.Shared.Damage.Components
{
var data = new List<DamageChangeData>();
foreach (var type in SupportedTypes)
foreach (var type in SupportedDamageTypes)
{
var damage = GetDamage(type);
var datum = new DamageChangeData(type, damage, 0);
@@ -382,11 +404,15 @@ namespace Content.Shared.Damage.Components
Dirty();
}
void IRadiationAct.RadiationAct(float frameTime, SharedRadiationPulseComponent radiation)
public void RadiationAct(float frameTime, SharedRadiationPulseComponent radiation)
{
var totalDamage = Math.Max((int)(frameTime * radiation.RadsPerSecond), 1);
ChangeDamage(DamageType.Radiation, totalDamage, false, radiation.Owner);
foreach (var typeID in RadiationDamageTypeIDs)
{
TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>(typeID), totalDamage);
}
}
public void OnExplosion(ExplosionEventArgs eventArgs)
@@ -399,19 +425,75 @@ namespace Content.Shared.Damage.Components
_ => throw new ArgumentOutOfRangeException()
};
ChangeDamage(DamageType.Piercing, damage, false);
ChangeDamage(DamageType.Heat, damage, false);
foreach (var typeID in ExplosionDamageTypeIDs)
{
TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>(typeID), damage);
}
}
/// <summary>
/// Take a dictionary with <see cref="IPrototype"/> keys and return a dictionary using <see cref="IPrototype.ID"/> as keys
/// instead.
/// </summary>
/// <remarks>
/// Useful when sending damage type and group prototypes dictionaries over the network.
/// </remarks>
public static IReadOnlyDictionary<string, int>
ConvertDictKeysToIDs<TPrototype>(IReadOnlyDictionary<TPrototype, int> prototypeDict)
where TPrototype : IPrototype
{
Dictionary<string, int> idDict = new(prototypeDict.Count);
foreach (var entry in prototypeDict)
{
idDict.Add(entry.Key.ID, entry.Value);
}
return idDict;
}
/// <summary>
/// Convert a dictionary with damage type keys to a dictionary of damage groups keys.
/// </summary>
/// <remarks>
/// Takes a dictionary with damage types as keys and integers as values, and an iterable list of damage
/// groups. Returns a dictionary with damage group keys, with values calculated by adding up the values for
/// each damage type in that group. If a damage type is associated with more than one supported damage
/// group, it will contribute to the total of each group. Conversely, some damage types may not contribute
/// to the new dictionary if their associated group(s) are not in given list of groups.
/// </remarks>
public static IReadOnlyDictionary<DamageGroupPrototype, int>
DamageTypeDictToDamageGroupDict(IReadOnlyDictionary<DamageTypePrototype, int> damageTypeDict, IEnumerable<DamageGroupPrototype> groupKeys)
{
var damageGroupDict = new Dictionary<DamageGroupPrototype, int>();
int damageGroupSumDamage, damageTypeDamage;
// iterate over the list of group keys for our new dictionary
foreach (var group in groupKeys)
{
// For each damage type in this group, add up the damage present in the given dictionary
damageGroupSumDamage = 0;
foreach (var type in group.DamageTypes)
{
// if the damage type is in the dictionary, add it's damage to the group total.
if (damageTypeDict.TryGetValue(type, out damageTypeDamage))
{
damageGroupSumDamage += damageTypeDamage;
}
}
damageGroupDict.Add(group, damageGroupSumDamage);
}
return damageGroupDict;
}
}
[Serializable, NetSerializable]
public class DamageableComponentState : ComponentState
{
public readonly Dictionary<DamageType, int> DamageList;
public readonly IReadOnlyDictionary<string, int> DamageDict;
public DamageableComponentState(IReadOnlyDictionary<string, int> damageDict)
public DamageableComponentState(Dictionary<DamageType, int> damageList)
{
DamageList = damageList;
DamageDict = damageDict;
}
}
}