using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; using Robust.Shared.Prototypes; namespace Content.Shared.Damage.Systems; public sealed partial class DamageableSystem { /// /// Directly sets the damage specifier of a damageable component. /// /// /// Useful for some unfriendly folk. Also ensures that cached values are updated and that a damage changed /// event is raised. /// public void SetDamage(Entity ent, DamageSpecifier damage) { if (!_damageableQuery.Resolve(ent, ref ent.Comp, false)) return; ent.Comp.Damage = damage; OnEntityDamageChanged((ent, ent.Comp)); } /// /// Applies damage specified via a . /// /// /// 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 . /// /// /// If the attempt was successful or not. /// public bool TryChangeDamage( Entity ent, DamageSpecifier damage, bool ignoreResistances = false, bool interruptsDoAfters = true, EntityUid? origin = null, bool ignoreGlobalModifiers = false, bool forceRefresh = false // Offbrand ) { //! 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, forceRefresh); // Offbrand } /// /// Applies damage specified via a . /// /// /// 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 . /// /// /// If the attempt was successful or not. /// public bool TryChangeDamage( Entity ent, DamageSpecifier damage, out DamageSpecifier newDamage, bool ignoreResistances = false, bool interruptsDoAfters = true, EntityUid? origin = null, bool ignoreGlobalModifiers = false, bool forceRefresh = false // Offbrand ) { //! 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, forceRefresh); // Offbrand return !damage.Empty; } /// /// Applies damage specified via a . /// /// /// 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 . /// /// /// The actual amount of damage taken, as a DamageSpecifier. /// public DamageSpecifier ChangeDamage( Entity ent, DamageSpecifier damage, bool ignoreResistances = false, bool interruptsDoAfters = true, EntityUid? origin = null, bool ignoreGlobalModifiers = false, bool forceRefresh = false // Offbrand ) { var damageDone = new DamageSpecifier(); if (!_damageableQuery.Resolve(ent, ref ent.Comp, false)) return damageDone; if (damage.Empty && !forceRefresh) return damageDone; var before = new BeforeDamageChangedEvent(damage, origin); RaiseLocalEvent(ent, ref before); if (before.Cancelled) return damageDone; // 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); RaiseLocalEvent(ent, ev); damage = ev.Damage; if (damage.Empty && !forceRefresh) // Offbrand return damageDone; } if (!ignoreGlobalModifiers) damage = ApplyUniversalAllModifiers(damage); // Begin Offbrand var beforeCommit = new Content.Shared._Offbrand.Wounds.BeforeDamageCommitEvent(damage, forceRefresh); RaiseLocalEvent(ent.Owner, ref beforeCommit); damage = beforeCommit.Damage; // End Offbrand 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, forceRefresh); // Offbrand return damageDone; } /// /// Applies the two universal "All" modifiers, if set. /// Individual damage source modifiers are set in their respective code. /// /// The damage to be changed. 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 ent) { SetAllDamage(ent, FixedPoint2.Zero); } /// /// Sets all damage types supported by a to the specified value. /// /// /// Does nothing If the given damage value is negative. /// public void SetAllDamage(Entity 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()); } /// /// Set's the damage modifier set prototype for this entity. /// /// The entity we're setting the modifier set of. /// The prototype we're setting. public void SetDamageModifierSetId(Entity ent, ProtoId? damageModifierSetId) { if (!_damageableQuery.Resolve(ent, ref ent.Comp, false)) return; ent.Comp.DamageModifierSetId = damageModifierSetId; Dirty(ent); } }