4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
7 using System.Globalization;
12 using System.Globalization;
13 using MoonSharp.Interpreter;
31 public readonly Dictionary<Identifier, float>
VitalityMultipliers =
new Dictionary<Identifier, float>();
39 if (limbName !=
"generic")
41 Name = TextManager.Get(
"HealthLimbName." + limbName);
43 foreach (var subElement
in element.Elements())
45 switch (subElement.Name.ToString().ToLowerInvariant())
51 case "highlightsprite":
54 case "vitalitymultiplier":
55 if (subElement.GetAttribute(
"name") !=
null)
57 DebugConsole.ThrowError(
"Error in character health config (" + characterHealth.Character.Name +
") - define vitality multipliers using affliction identifiers or types instead of names.",
58 contentPackage: element.ContentPackage);
61 var vitalityMultipliers = subElement.GetAttributeIdentifierArray(
"identifier",
null) ?? subElement.GetAttributeIdentifierArray(
"identifiers",
null);
62 if (vitalityMultipliers !=
null)
64 float multiplier = subElement.GetAttributeFloat(
"multiplier", 1.0f);
65 foreach (var vitalityMultiplier
in vitalityMultipliers)
70 DebugConsole.AddWarning($
"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions with the identifier \"{vitalityMultiplier}\". Did you mean to define the afflictions by type instead?",
71 contentPackage: element.ContentPackage);
75 var vitalityTypeMultipliers = subElement.GetAttributeIdentifierArray(
"type",
null) ?? subElement.GetAttributeIdentifierArray(
"types",
null);
76 if (vitalityTypeMultipliers !=
null)
78 float multiplier = subElement.GetAttributeFloat(
"multiplier", 1.0f);
79 foreach (var vitalityTypeMultiplier
in vitalityTypeMultipliers)
84 DebugConsole.AddWarning($
"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions of the type \"{vitalityTypeMultiplier}\". Did you mean to define the afflictions by identifier instead?",
85 contentPackage: element.ContentPackage);
91 DebugConsole.ThrowError($
"Error in character health config {characterHealth.Character.Name}: affliction identifier(s) or type(s) not defined in the \"VitalityMultiplier\" elements!",
92 contentPackage: element.ContentPackage);
133 private readonly List<LimbHealth> limbHealths =
new List<LimbHealth>();
135 private readonly Dictionary<Affliction, LimbHealth> afflictions =
new Dictionary<Affliction, LimbHealth>();
136 private readonly HashSet<Affliction> irremovableAfflictions =
new HashSet<Affliction>();
150 private float vitality;
184 ? campaign.Settings.CrewVitalityMultiplier
185 : campaign.Settings.NonCrewVitalityMultiplier;
223 return -oxygenLowAffliction.
Strength + 100;
228 oxygenLowAffliction.
Strength = MathHelper.Clamp(-value + 100, 0.0f, 200.0f);
234 get {
return bloodlossAffliction.
Strength; }
240 get {
return stunAffliction.
Strength; }
259 get {
return pressureAffliction; }
266 this.Character = character;
272 InitIrremovableAfflictions();
276 InitProjSpecific(
null, character);
281 this.Character = character;
282 InitIrremovableAfflictions();
289 limbHealthElement ??= element;
290 foreach (var subElement
in limbHealthElement.Elements())
292 if (!subElement.Name.ToString().Equals(
"limb", StringComparison.OrdinalIgnoreCase)) {
continue; }
295 if (limbHealths.Count == 0)
300 InitProjSpecific(element, character);
303 private void InitIrremovableAfflictions()
309 foreach (
Affliction affliction
in irremovableAfflictions)
311 afflictions.Add(affliction,
null);
315 partial
void InitProjSpecific(ContentXElement element,
Character character);
319 return afflictions.Keys;
324 return afflictions.Keys.Where(limbHealthFilter);
327 private float GetTotalDamage(LimbHealth limbHealth)
329 float totalDamage = 0.0f;
330 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
332 if (kvp.Value != limbHealth) {
continue; }
333 var affliction = kvp.Key;
339 private LimbHealth GetMatchingLimbHealth(Limb limb) => limb ==
null ? null : limbHealths[limb.HealthIndex];
343 GetAffliction(identifier.ToIdentifier(), allowLimbAfflictions);
346 =>
GetAffliction(a => a.Prefab.Identifier == identifier, allowLimbAfflictions);
349 =>
GetAffliction(a => a.Prefab.AfflictionType == afflictionType, allowLimbAfflictions);
353 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
355 if (!allowLimbAfflictions && kvp.Value !=
null) {
continue; }
356 if (predicate(kvp.Key)) {
return kvp.Key; }
370 DebugConsole.ThrowError(
"Limb health index out of bounds. Character\"" +
Character.
Name +
371 "\" only has health configured for" + limbHealths.Count +
" limbs but the limb " + limb.
type +
" is targeting index " + limb.
HealthIndex);
374 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
376 if (limbHealths[limb.HealthIndex] == kvp.Value && kvp.Key.Prefab.Identifier == identifier) {
return kvp.Key; }
383 if (affliction !=
null && afflictions.TryGetValue(affliction, out
LimbHealth limbHealth))
385 if (limbHealth ==
null) {
return null; }
386 int limbHealthIndex = limbHealths.IndexOf(limbHealth);
389 if (limb.
HealthIndex == limbHealthIndex) {
return limb; }
404 if (requireLimbSpecific && limbHealths.Count == 1) {
return 0.0f; }
406 float strength = 0.0f;
408 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
410 if (kvp.Value == limbHealth)
425 return GetAfflictionStrength(afflictionType, afflictionidentifier: Identifier.Empty, allowLimbAfflictions);
430 return GetAfflictionStrength(afflictionType: Identifier.Empty, afflictionIdentifier, allowLimbAfflictions);
433 public float GetAfflictionStrength(Identifier afflictionType, Identifier afflictionidentifier,
bool allowLimbAfflictions =
true)
435 float strength = 0.0f;
436 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
438 if (!allowLimbAfflictions && kvp.Value !=
null) {
continue; }
439 var affliction = kvp.Key;
440 if (affliction.Strength < affliction.Prefab.ActivationThreshold) {
continue; }
441 if ((affliction.Prefab.AfflictionType == afflictionType || afflictionType.IsEmpty) &&
442 (affliction.Prefab.Identifier == afflictionidentifier || afflictionidentifier.IsEmpty))
453 if (!ignoreUnkillability)
459 if (targetLimb ==
null)
462 foreach (
LimbHealth limbHealth
in limbHealths)
464 AddLimbAffliction(limbHealth, affliction, allowStacking: allowStacking);
470 AddLimbAffliction(targetLimb, affliction, allowStacking: allowStacking);
475 AddAffliction(affliction, allowStacking: allowStacking);
482 float resistance = 0.0f;
483 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
485 var affliction = kvp.Key;
486 resistance += affliction.
GetResistance(afflictionPrefab.Identifier);
491 return 1 - ((1 - resistance) * abilityResistanceMultiplier);
497 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
499 var affliction = kvp.Key;
507 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
509 var affliction = kvp.Key;
510 if (affliction.HasFlag(flagType)) {
return true; }
515 private readonly List<Affliction> matchingAfflictions =
new List<Affliction>();
519 matchingAfflictions.Clear();
520 matchingAfflictions.AddRange(afflictions.Keys);
522 ReduceMatchingAfflictions(amount, treatmentAction);
527 if (afflictionIdOrType.IsEmpty) {
throw new ArgumentException($
"{nameof(afflictionIdOrType)} is empty"); }
529 matchingAfflictions.Clear();
530 foreach (var affliction
in afflictions)
532 if (affliction.Key.Prefab.Identifier == afflictionIdOrType || affliction.Key.Prefab.AfflictionType == afflictionIdOrType)
534 matchingAfflictions.Add(affliction.Key);
538 ReduceMatchingAfflictions(amount, treatmentAction, attacker);
541 private IEnumerable<Affliction> GetAfflictionsForLimb(
Limb targetLimb)
542 => afflictions.Keys.Where(k => afflictions[k] == limbHealths[targetLimb.HealthIndex]);
546 if (targetLimb is
null) {
throw new ArgumentNullException(nameof(targetLimb)); }
548 matchingAfflictions.Clear();
549 matchingAfflictions.AddRange(GetAfflictionsForLimb(targetLimb));
551 ReduceMatchingAfflictions(amount, treatmentAction);
556 if (afflictionIdOrType.IsEmpty) {
throw new ArgumentException($
"{nameof(afflictionIdOrType)} is empty"); }
557 if (targetLimb is
null) {
throw new ArgumentNullException(nameof(targetLimb)); }
559 matchingAfflictions.Clear();
560 var targetLimbHealth = limbHealths[targetLimb.
HealthIndex];
561 foreach (var affliction
in afflictions)
563 if ((affliction.Key.Prefab.Identifier == afflictionIdOrType || affliction.Key.Prefab.AfflictionType == afflictionIdOrType) &&
564 affliction.Value == targetLimbHealth)
566 matchingAfflictions.Add(affliction.Key);
569 ReduceMatchingAfflictions(amount, treatmentAction, attacker);
572 private void ReduceMatchingAfflictions(
float amount,
ActionType? treatmentAction,
Character attacker =
null)
574 if (matchingAfflictions.Count == 0) {
return; }
576 float reduceAmount = amount / matchingAfflictions.Count;
578 if (reduceAmount > 0f)
580 var abilityReduceAffliction =
new AbilityReduceAffliction(
Character, reduceAmount);
581 attacker?.CheckTalents(
AbilityEffectType.OnReduceAffliction, abilityReduceAffliction);
582 reduceAmount = abilityReduceAffliction.Value;
585 for (
int i = matchingAfflictions.Count - 1; i >= 0; i--)
587 var matchingAffliction = matchingAfflictions[i];
589 if (matchingAffliction.Strength < reduceAmount)
591 float surplus = reduceAmount - matchingAffliction.Strength;
592 amount -= matchingAffliction.Strength;
593 matchingAffliction.Strength = 0.0f;
594 matchingAfflictions.RemoveAt(i);
595 if (i == 0) { i = matchingAfflictions.Count; }
596 if (i > 0) { reduceAmount += surplus / i; }
597 AchievementManager.OnAfflictionRemoved(matchingAffliction,
Character);
601 matchingAffliction.Strength -= reduceAmount;
602 amount -= reduceAmount;
603 if (treatmentAction !=
null)
607 matchingAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
609 else if (treatmentAction.Value ==
ActionType.OnFailure)
611 matchingAffliction.AppliedAsFailedTreatmentTime = Timing.TotalTime;
624 DebugConsole.ThrowError(
"Limb health index out of bounds. Character\"" +
Character.
Name +
625 "\" only has health configured for" + limbHealths.Count +
" limbs but the limb " + hitLimb.
type +
" is targeting index " + hitLimb.
HealthIndex);
629 var should =
GameMain.
LuaCs.
Hook.
Call<
bool?>(
"character.applyDamage",
this, attackResult, hitLimb, allowStacking);
631 if (should !=
null && should.Value)
634 foreach (
Affliction newAffliction
in attackResult.Afflictions)
638 AddLimbAffliction(hitLimb, newAffliction, allowStacking);
642 AddAffliction(newAffliction, allowStacking);
647 private void KillIfOutOfVitality()
656 private readonly
static List<Affliction> afflictionsToRemove =
new List<Affliction>();
657 private readonly
static List<KeyValuePair<Affliction, LimbHealth>> afflictionsToUpdate =
new List<KeyValuePair<Affliction, LimbHealth>>();
658 public void SetAllDamage(
float damageAmount,
float bleedingDamageAmount,
float burnDamageAmount)
662 afflictionsToRemove.Clear();
663 afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
667 foreach (var affliction
in afflictionsToRemove)
669 afflictions.Remove(affliction);
672 foreach (
LimbHealth limbHealth
in limbHealths)
680 KillIfOutOfVitality();
685 float damageStrength;
696 if (afflictionType.IsEmpty)
701 damageStrength = Math.Min(damage + bleeding + burn, max);
707 return damageStrength / max;
713 afflictionsToRemove.Clear();
714 afflictionsToRemove.AddRange(afflictions.Keys.Where(a => !irremovableAfflictions.Contains(a)));
715 foreach (var affliction
in afflictionsToRemove)
717 afflictions.Remove(affliction);
719 foreach (
Affliction affliction
in irremovableAfflictions)
721 affliction.Strength = 0.0f;
728 afflictionsToRemove.Clear();
729 afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
730 !irremovableAfflictions.Contains(a) &&
732 a.Prefab.AfflictionType !=
"geneticmaterialbuff" &&
733 a.Prefab.AfflictionType !=
"geneticmaterialdebuff"));
734 foreach (var affliction
in afflictionsToRemove)
736 afflictions.Remove(affliction);
738 foreach (
Affliction affliction
in irremovableAfflictions)
740 affliction.Strength = 0.0f;
745 private void AddLimbAffliction(
Limb limb,
Affliction newAffliction,
bool allowStacking =
true)
750 DebugConsole.ThrowError(
"Limb health index out of bounds. Character\"" +
Character.
Name +
751 "\" only has health configured for" + limbHealths.Count +
" limbs but the limb " + limb.
type +
" is targeting index " + limb.
HealthIndex);
754 AddLimbAffliction(limbHealths[limb.
HealthIndex], newAffliction, allowStacking);
757 private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction,
bool allowStacking =
true)
760 if (!
DoesBleed && newAffliction is AfflictionBleeding) {
return; }
771 if (newAffliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType || newAffliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)
777 if (newAffliction.Prefab.TargetSpecies.Any() && newAffliction.Prefab.TargetSpecies.None(s => s ==
Character.
SpeciesName)) {
return; }
780 var should = GameMain.LuaCs.Hook.Call<
bool?>(
"character.applyAffliction",
this, limbHealth, newAffliction, allowStacking);
782 if (should !=
null && should.Value)
786 foreach ((Affliction affliction, LimbHealth value) in afflictions)
788 if (value == limbHealth && affliction.Prefab == newAffliction.Prefab)
790 existingAffliction = affliction;
795 if (existingAffliction !=
null)
797 float newStrength = newAffliction.Strength * (100.0f /
MaxVitality) * (1f -
GetResistance(existingAffliction.Prefab));
801 newStrength += existingAffliction.Strength;
803 newStrength = Math.Min(existingAffliction.Prefab.MaxStrength, newStrength);
804 if (existingAffliction == stunAffliction) {
Character.
SetStun(newStrength,
true,
true); }
805 existingAffliction.Strength = newStrength;
806 existingAffliction.Duration = existingAffliction.Prefab.Duration;
807 if (newAffliction.Source !=
null) { existingAffliction.Source = newAffliction.Source; }
809 KillIfOutOfVitality();
815 var copyAffliction = newAffliction.Prefab.Instantiate(
816 Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f /
MaxVitality) * (1f -
GetResistance(newAffliction.Prefab))),
817 newAffliction.Source);
818 afflictions.Add(copyAffliction, limbHealth);
819 AchievementManager.OnAfflictionReceived(copyAffliction,
Character);
820 MedicalClinic.OnAfflictionCountChanged(
Character);
825 KillIfOutOfVitality();
829 selectedLimbIndex = -1;
834 private void AddAffliction(Affliction newAffliction,
bool allowStacking =
true)
836 AddLimbAffliction(limbHealth:
null, newAffliction, allowStacking);
839 partial
void UpdateSkinTint();
841 partial
void UpdateLimbAfflictionOverlays();
847 UpdateOxygen(deltaTime);
853 afflictionsToRemove.Clear();
854 afflictionsToUpdate.Clear();
855 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
857 var affliction = kvp.Key;
858 if (affliction.Strength <= 0.0f)
860 AchievementManager.OnAfflictionRemoved(affliction,
Character);
861 if (!irremovableAfflictions.Contains(affliction)) { afflictionsToRemove.Add(affliction); }
864 if (affliction.Prefab.Duration > 0.0f)
866 affliction.Duration -= deltaTime;
867 if (affliction.Duration <= 0.0f)
869 afflictionsToRemove.Add(affliction);
873 afflictionsToUpdate.Add(kvp);
875 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictionsToUpdate)
877 var affliction = kvp.Key;
878 Limb targetLimb =
null;
879 if (kvp.Value !=
null)
881 int healthIndex = limbHealths.IndexOf(kvp.Value);
886 affliction.
Update(
this, targetLimb, deltaTime);
887 affliction.DamagePerSecondTimer += deltaTime;
890 UpdateBleedingProjSpecific(bleeding, targetLimb, deltaTime);
895 foreach (var affliction
in afflictionsToRemove)
897 afflictions.Remove(affliction);
900 if (afflictionsToRemove.Count is not 0)
902 MedicalClinic.OnAfflictionCountChanged(
Character);
916 UpdateDamageReductions(deltaTime);
923 UpdateLimbAfflictionOverlays();
928 KillIfOutOfVitality();
934 UpdateLimbAfflictionOverlays();
938 private void UpdateDamageReductions(
float deltaTime)
946 if (burnReduction > 0)
951 if (bleedingReduction > 0)
962 private void UpdateOxygen(
float deltaTime)
966 oxygenLowAffliction.
Strength = 0.0f;
975 float decreaseSpeed = Math.Max(0.1f, 1f - oxygenlowResistance);
981 float decreaseSpeed = -5.0f;
982 float increaseSpeed = 10.0f;
983 decreaseSpeed *= (1f - oxygenlowResistance);
984 increaseSpeed *= (1f + oxygenlowResistance);
986 if (holdBreathMultiplier <= -1.0f)
997 UpdateOxygenProjSpecific(prevOxygen, deltaTime);
1000 partial
void UpdateOxygenProjSpecific(
float prevOxygen,
float deltaTime);
1002 partial
void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb,
float deltaTime);
1016 foreach (var (affliction, limbHealth) in afflictions)
1018 float vitalityDecrease = affliction.GetVitalityDecrease(
this);
1019 if (limbHealth !=
null)
1021 vitalityDecrease *= GetVitalityMultiplier(affliction, limbHealth);
1023 vitality -= vitalityDecrease;
1024 affliction.CalculateDamagePerSecond(vitalityDecrease);
1026 if (affliction.Strength >= affliction.Prefab.MaxStrength &&
1035 HintManager.OnCharacterUnconscious(
Character);
1040 private static float GetVitalityMultiplier(
Affliction affliction, LimbHealth limbHealth)
1042 float multiplier = 1.0f;
1043 if (limbHealth.VitalityMultipliers.TryGetValue(affliction.
Prefab.
Identifier, out
float vitalityMultiplier))
1045 multiplier *= vitalityMultiplier;
1047 if (limbHealth.VitalityTypeMultipliers.TryGetValue(affliction.
Prefab.
AfflictionType, out
float vitalityTypeMultiplier))
1049 multiplier *= vitalityTypeMultiplier;
1057 private float GetVitalityDecreaseWithVitalityMultipliers(Affliction affliction)
1059 float vitalityDecrease = affliction.GetVitalityDecrease(
this);
1060 if (afflictions.TryGetValue(affliction, out LimbHealth limbHealth) && limbHealth !=
null)
1062 vitalityDecrease *= GetVitalityMultiplier(affliction, limbHealth);
1064 return vitalityDecrease;
1071 var (
type, affliction) = GetCauseOfDeath();
1072 UpdateLimbAfflictionOverlays();
1078 DisplayVitalityDelay = 0.0f;
1085 private readonly List<Affliction> afflictionsCopy =
new List<Affliction>();
1088 afflictionsCopy.Clear();
1089 afflictionsCopy.AddRange(afflictions.Keys);
1090 foreach (
Affliction affliction
in afflictionsCopy)
1101 float largestStrength = 0.0f;
1102 foreach (
Affliction affliction
in currentAfflictions)
1104 if (strongestAffliction ==
null || affliction.
GetVitalityDecrease(
this) > largestStrength)
1106 strongestAffliction = affliction;
1112 if (strongestAffliction == oxygenLowAffliction)
1117 return (causeOfDeath, strongestAffliction);
1120 private readonly List<Affliction> allAfflictions =
new List<Affliction>();
1121 private List<Affliction>
GetAllAfflictions(
bool mergeSameAfflictions, Func<Affliction, bool> predicate =
null)
1123 allAfflictions.Clear();
1124 if (!mergeSameAfflictions)
1126 allAfflictions.AddRange(predicate ==
null ? afflictions.Keys : afflictions.Keys.Where(predicate));
1130 foreach (Affliction affliction
in afflictions.Keys)
1132 if (predicate !=
null && !predicate(affliction)) {
continue; }
1133 var existingAffliction = allAfflictions.Find(a => a.Prefab == affliction.Prefab);
1134 if (existingAffliction ==
null)
1136 var newAffliction = affliction.Prefab.Instantiate(affliction.Strength);
1137 if (affliction.Source !=
null) { newAffliction.Source = affliction.Source; }
1138 newAffliction.DamagePerSecond = affliction.DamagePerSecond;
1139 newAffliction.DamagePerSecondTimer = affliction.DamagePerSecondTimer;
1140 allAfflictions.Add(newAffliction);
1144 existingAffliction.DamagePerSecond += affliction.DamagePerSecond;
1145 existingAffliction.Strength += affliction.Strength;
1149 return allAfflictions;
1158 public void GetSuitableTreatments(Dictionary<Identifier, float> treatmentSuitability,
Character user,
Limb limb =
null,
bool ignoreHiddenAfflictions =
false,
float predictFutureDuration = 0.0f)
1162 treatmentSuitability.Clear();
1163 float minSuitability = -10, maxSuitability = 10;
1164 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
1166 var affliction = kvp.Key;
1167 var limbHealth = kvp.Value;
1169 affliction.Prefab.LimbSpecific &&
1170 GetMatchingLimbHealth(affliction) != GetMatchingLimbHealth(limb))
1172 if (limbHealth ==
null) {
continue; }
1173 int healthIndex = limbHealths.IndexOf(limbHealth);
1174 if (limb.
HealthIndex != healthIndex) {
continue; }
1177 float strength = affliction.Strength;
1178 if (predictFutureDuration > 0.0f)
1187 if (afflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Key.Identifier))) {
continue; }
1189 if (ignoreHiddenAfflictions)
1193 if (strength < affliction.Prefab.ShowIconThreshold) {
continue; }
1197 if (strength < affliction.Prefab.ShowIconToOthersThreshold) {
continue; }
1201 foreach (KeyValuePair<Identifier, float> treatment
in affliction.Prefab.TreatmentSuitabilities)
1203 float suitability = treatment.Value * strength;
1204 if (suitability > 0)
1209 if (totalAfflictionStrength < affliction.Prefab.TreatmentThreshold) {
continue; }
1211 if (treatment.Value > strength)
1214 float overtreatmentFactor = MathHelper.Clamp(treatment.Value / strength, 1.0f, 10.0f);
1215 suitability /= overtreatmentFactor;
1217 if (!treatmentSuitability.ContainsKey(treatment.Key))
1219 treatmentSuitability[treatment.Key] = suitability;
1223 treatmentSuitability[treatment.Key] += suitability;
1225 minSuitability = Math.Min(treatmentSuitability[treatment.Key], minSuitability);
1226 maxSuitability = Math.Max(treatmentSuitability[treatment.Key], maxSuitability);
1239 float totalAfflictionStrength = includeSameAffliction ? affliction.
Strength : 0;
1242 foreach (
Affliction otherAffliction
in afflictions.Keys)
1244 if (affliction.
Prefab == otherAffliction.
Prefab && affliction != otherAffliction)
1246 totalAfflictionStrength += otherAffliction.
Strength * otherAfflictionMultiplier;
1250 return totalAfflictionStrength;
1253 private readonly HashSet<Identifier> afflictionTags =
new HashSet<Identifier>();
1256 afflictionTags.Clear();
1257 foreach (
Affliction affliction
in afflictions.Keys)
1260 if (currentEffect is { Tag.IsEmpty:
false })
1262 afflictionTags.Add(currentEffect.Tag);
1265 return afflictionTags;
1270 float strength = affliction.
Strength;
1274 float statusEffectDuration = Math.Min(statusEffect.Timer, predictFutureDuration);
1275 foreach (var statusEffectAffliction
in statusEffect.Parent.Afflictions)
1277 if (statusEffectAffliction.Prefab == affliction.
Prefab)
1279 strength += statusEffectAffliction.Strength * statusEffectDuration;
1282 foreach (var statusEffectAffliction
in statusEffect.Parent.ReduceAffliction)
1284 if (statusEffectAffliction.AfflictionIdentifier == affliction.
Identifier ||
1287 strength -= statusEffectAffliction.ReduceAmount * statusEffectDuration;
1294 private readonly List<Affliction> activeAfflictions =
new List<Affliction>();
1295 private readonly List<(LimbHealth limbHealth,
Affliction affliction)> limbAfflictions =
new List<(LimbHealth limbHealth,
Affliction affliction)>();
1298 activeAfflictions.Clear();
1299 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
1301 var affliction = kvp.Key;
1302 var limbHealth = kvp.
Value;
1303 if (limbHealth !=
null) {
continue; }
1304 if (affliction.Strength > 0.0f && affliction.Strength >= affliction.Prefab.ActivationThreshold)
1306 activeAfflictions.Add(affliction);
1309 msg.WriteByte((
byte)activeAfflictions.Count);
1310 foreach (
Affliction affliction
in activeAfflictions)
1313 msg.WriteRangedSingle(
1319 msg.WriteRangedSingle(affliction.
PeriodicEffectTimers[periodicEffect], 0, periodicEffect.MaxInterval, 8);
1323 limbAfflictions.Clear();
1324 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
1326 var limbAffliction = kvp.Key;
1327 var limbHealth = kvp.Value;
1328 if (limbHealth ==
null) {
continue; }
1329 if (limbAffliction.Strength <= 0.0f || limbAffliction.Strength < limbAffliction.Prefab.ActivationThreshold) {
continue; }
1330 limbAfflictions.Add((limbHealth, limbAffliction));
1333 msg.WriteByte((
byte)limbAfflictions.Count);
1334 foreach (var (limbHealth, affliction) in limbAfflictions)
1336 msg.WriteRangedInteger(limbHealths.IndexOf(limbHealth), 0, limbHealths.Count - 1);
1338 msg.WriteRangedSingle(
1344 msg.WriteRangedSingle(affliction.
PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8);
1351 RemoveProjSpecific();
1352 afflictionsToRemove.Clear();
1353 afflictionsToUpdate.Clear();
1356 partial
void RemoveProjSpecific();
1362 afflictions.Where(a => !excludeBuffs || !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength / a.Prefab.MaxStrength);
1364 public void Save(XElement healthElement)
1366 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
1368 var affliction = kvp.Key;
1369 var limbHealth = kvp.
Value;
1370 if (affliction.Strength <= 0.0f || limbHealth !=
null) {
continue; }
1371 if (kvp.Key.Prefab.ResetBetweenRounds) {
continue; }
1372 healthElement.Add(
new XElement(
"Affliction",
1373 new XAttribute(
"identifier", affliction.Identifier),
1374 new XAttribute(
"strength", affliction.Strength.ToString(
"G", CultureInfo.InvariantCulture))));
1377 for (
int i = 0; i < limbHealths.Count; i++)
1379 var limbHealthElement =
new XElement(
"LimbHealth",
new XAttribute(
"i", i));
1380 healthElement.Add(limbHealthElement);
1381 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions.Where(a => a.Value == limbHealths[i]))
1383 var affliction = kvp.Key;
1384 var limbHealth = kvp.
Value;
1385 if (affliction.Strength <= 0.0f) {
continue; }
1386 limbHealthElement.Add(
new XElement(
"Affliction",
1387 new XAttribute(
"identifier", affliction.Identifier),
1388 new XAttribute(
"strength", affliction.Strength.ToString(
"G", CultureInfo.InvariantCulture))));
1393 public void Load(XElement element, Func<AfflictionPrefab, bool> afflictionPredicate =
null)
1395 foreach (var subElement
in element.Elements())
1397 switch (subElement.Name.ToString().ToLowerInvariant())
1400 LoadAffliction(subElement);
1403 int limbHealthIndex = subElement.GetAttributeInt(
"i", -1);
1404 if (limbHealthIndex < 0 || limbHealthIndex >= limbHealths.Count)
1406 DebugConsole.ThrowError($
"Error while loading character health: limb index \"{limbHealthIndex}\" out of range.");
1409 foreach (XElement afflictionElement
in subElement.Elements())
1411 LoadAffliction(afflictionElement, limbHealths[limbHealthIndex]);
1417 void LoadAffliction(XElement afflictionElement,
LimbHealth limbHealth =
null)
1419 string id = afflictionElement.GetAttributeString(
"identifier",
"");
1421 if (afflictionPrefab ==
null)
1423 DebugConsole.ThrowError($
"Error while loading character health: affliction \"{id}\" not found.");
1426 if (afflictionPredicate !=
null && !afflictionPredicate.Invoke(afflictionPrefab)) {
return; }
1427 float strength = afflictionElement.GetAttributeFloat(
"strength", 0.0f);
1428 var irremovableAffliction = irremovableAfflictions.FirstOrDefault(a => a.Prefab == afflictionPrefab);
1429 if (irremovableAffliction !=
null)
1431 irremovableAffliction.Strength = strength;
1435 afflictions.Add(afflictionPrefab.Instantiate(strength), limbHealth);
A special affliction type that increases the character's Bloodloss affliction with a rate relative to...
readonly Dictionary< AfflictionPrefab.PeriodicEffect, float > PeriodicEffectTimers
float GetVitalityDecrease(CharacterHealth characterHealth)
float GetResistance(Identifier afflictionId)
float GetStatValue(StatTypes statType)
AfflictionPrefab.Effect GetActiveEffect()
readonly AfflictionPrefab Prefab
void ApplyStatusEffects(ActionType type, float deltaTime, CharacterHealth characterHealth, Limb targetLimb)
PeriodicEffect applies StatusEffects to the character periodically.
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
Affliction Instantiate(float strength, Character source=null)
IList< PeriodicEffect > PeriodicEffects
static readonly Identifier BleedingType
static AfflictionPrefab OxygenLow
readonly bool IsBuff
If set to true, the game will recognize this affliction as a buff. This means, among other things,...
static AfflictionPrefab InternalDamage
static readonly Identifier DamageType
static readonly Identifier BurnType
readonly LimbType IndicatorLimb
If the affliction doesn't affect individual limbs, this attribute determines where the game will rend...
static AfflictionPrefab Burn
readonly bool LimbSpecific
If set to true, the affliction affects individual limbs. Otherwise, it affects the whole character.
readonly float MaxStrength
The maximum strength this affliction can have.
static AfflictionPrefab Bleeding
static readonly Identifier ParalysisType
readonly Identifier AfflictionType
Arbitrary string that is used to identify the type of the affliction.
static readonly PrefabCollection< AfflictionPrefab > Prefabs
static AfflictionPrefab Stun
readonly float ActivationThreshold
How high the strength has to be for the affliction to take effect
static AfflictionPrefab Pressure
static AfflictionPrefab Bloodloss
readonly LocalizedString Name
readonly Dictionary< Identifier, float > VitalityTypeMultipliers
readonly Dictionary< Identifier, float > VitalityMultipliers
LimbHealth(ContentXElement element, CharacterHealth characterHealth)
void ApplyAffliction(Limb targetLimb, Affliction affliction, bool allowStacking=true, bool ignoreUnkillability=false)
readonly Character Character
void ForceUpdateVisuals()
float VitalityDisregardingDeath
How much vitality the character would have if it was alive? E.g. a character killed by disconnection ...
float GetStatValue(StatTypes statType)
float GetLimbDamage(Limb limb, Identifier afflictionType)
void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
static CharacterHealth?? OpenHealthWindow
float GetAfflictionStrength(Identifier afflictionType, Identifier afflictionidentifier, bool allowLimbAfflictions=true)
Affliction GetAfflictionOfType(Identifier afflictionType, bool allowLimbAfflictions=true)
Affliction PressureAffliction
float GetAfflictionStrength(Identifier afflictionType, Limb limb, bool requireLimbSpecific)
Get the total strength of the afflictions of a specific type attached to a specific limb
void SetVitality(float newVitality)
void RemoveAllAfflictions()
void ReduceAfflictionOnLimb(Limb targetLimb, Identifier afflictionIdOrType, float amount, ActionType? treatmentAction=null, Character attacker=null)
const float LowOxygenThreshold
Affliction BloodlossAffliction
bool HasFlag(AbilityFlags flagType)
static IEnumerable< Affliction > SortAfflictionsBySeverity(IEnumerable< Affliction > afflictions, bool excludeBuffs=true)
Automatically filters out buffs.
void ReduceAllAfflictionsOnLimb(Limb targetLimb, float amount, ActionType? treatmentAction=null)
CharacterHealth(Character character)
Limb GetAfflictionLimb(Affliction affliction)
IEnumerable< Affliction > GetAllAfflictions(Func< Affliction, bool > limbHealthFilter)
void Update(float deltaTime)
const float InsufficientOxygenThreshold
void ServerWrite(IWriteMessage msg)
void Load(XElement element, Func< AfflictionPrefab, bool > afflictionPredicate=null)
IEnumerable< Identifier > GetActiveAfflictionTags()
IReadOnlyCollection< Affliction > GetAllAfflictions()
float GetResistance(AfflictionPrefab afflictionPrefab)
void GetSuitableTreatments(Dictionary< Identifier, float > treatmentSuitability, Character user, Limb limb=null, bool ignoreHiddenAfflictions=false, float predictFutureDuration=0.0f)
Get the identifiers of the items that can be used to treat the character. Takes into account all the ...
static readonly Color DefaultFaceTint
Affliction GetAffliction(Identifier identifier, Limb limb)
float GetTotalAdjustedAfflictionStrength(Affliction affliction, float otherAfflictionMultiplier=0.3f, bool includeSameAffliction=true)
Returns the total strength of instances of the same affliction on all the characters limbs,...
void ApplyDamage(Limb hitLimb, AttackResult attackResult, bool allowStacking=true)
float UnmodifiedMaxVitality
Maximum vitality without talent- or job-based modifiers
float GetAfflictionStrengthByIdentifier(Identifier afflictionIdentifier, bool allowLimbAfflictions=true)
void Save(XElement healthElement)
float OxygenLowResistance
0-1.
CharacterHealth(ContentXElement element, Character character, ContentXElement limbHealthElement=null)
Affliction GetAffliction(Identifier identifier, bool allowLimbAfflictions=true)
float GetPredictedStrength(Affliction affliction, float predictFutureDuration, Limb limb=null)
bool WasInFullHealth
Was the character in full health at the beginning of the frame?
void ReduceAfflictionOnAllLimbs(Identifier afflictionIdOrType, float amount, ActionType? treatmentAction=null, Character attacker=null)
void ReduceAllAfflictionsOnAllLimbs(float amount, ActionType? treatmentAction=null)
void RemoveNegativeAfflictions()
float GetAfflictionStrengthByType(Identifier afflictionType, bool allowLimbAfflictions=true)
T GetAffliction< T >(Identifier identifier, bool allowLimbAfflictions=true)
Affliction GetAffliction(string identifier, bool allowLimbAfflictions=true)
void ApplyAfflictionStatusEffects(ActionType type)
bool GodMode
Godmoded characters cannot receive any afflictions whatsoever
readonly CharacterParams Params
void SetStun(float newStun, bool allowStunDecrease=false, bool isNetworkMessage=false)
float GetStatValue(StatTypes statType, bool includeSaved=true)
void StackSpeedMultiplier(float val)
float HealthMultiplier
Can be used to modify the character's health via StatusEffects
void Kill(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool isNetworkMessage=false, bool log=true)
float GetAbilityResistance(Identifier resistanceId)
float HumanPrefabHealthMultiplier
Health multiplier of the human prefab this character is an instance of (if any)
bool IsVisible
Is the character currently visible on the camera. Refresh the value by calling DoVisibilityCheck.
bool HasAbilityFlag(AbilityFlags abilityFlag)
readonly AnimController AnimController
float? HealthUpdateInterval
IEnumerable< Identifier > ImmunityIdentifiers
float ConstantHealthRegeneration
void Add(ContentXElement elem)
string? GetAttributeString(string key, string? def)
float GetAttributeFloat(string key, float def)
static GameSession?? GameSession
void Update(float deltaTime)
object Call(string name, params object[] args)
readonly Identifier Identifier
Limb GetLimb(LimbType limbType, bool excludeSevered=true)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
static readonly List< DurationListElement > DurationList
AbilityFlags
AbilityFlags are a set of toggleable flags that can be applied to characters.
ActionType
ActionTypes define when a StatusEffect is executed.
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.