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; }
265 get {
return pressureAffliction; }
272 this.Character = character;
278 InitIrremovableAfflictions();
282 InitProjSpecific(
null, character);
287 this.Character = character;
288 InitIrremovableAfflictions();
295 limbHealthElement ??= element;
296 foreach (var subElement
in limbHealthElement.Elements())
298 if (!subElement.Name.ToString().Equals(
"limb", StringComparison.OrdinalIgnoreCase)) {
continue; }
301 if (limbHealths.Count == 0)
306 InitProjSpecific(element, character);
309 private void InitIrremovableAfflictions()
315 foreach (
Affliction affliction
in irremovableAfflictions)
317 afflictions.Add(affliction,
null);
321 partial
void InitProjSpecific(ContentXElement element,
Character character);
325 return afflictions.Keys;
330 return afflictions.Keys.Where(limbHealthFilter);
333 private float GetTotalDamage(LimbHealth limbHealth)
335 float totalDamage = 0.0f;
336 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
338 if (kvp.Value != limbHealth) {
continue; }
339 var affliction = kvp.Key;
345 private LimbHealth GetMatchingLimbHealth(Limb limb) => limb ==
null ? null : limbHealths[limb.HealthIndex];
349 GetAffliction(identifier.ToIdentifier(), allowLimbAfflictions);
352 =>
GetAffliction(a => a.Prefab.Identifier == identifier, allowLimbAfflictions);
355 =>
GetAffliction(a => a.Prefab.AfflictionType == afflictionType, allowLimbAfflictions);
359 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
361 if (!allowLimbAfflictions && kvp.Value !=
null) {
continue; }
362 if (predicate(kvp.Key)) {
return kvp.Key; }
376 DebugConsole.ThrowError(
"Limb health index out of bounds. Character\"" +
Character.
Name +
377 "\" only has health configured for" + limbHealths.Count +
" limbs but the limb " + limb.
type +
" is targeting index " + limb.
HealthIndex);
380 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
382 if (limbHealths[limb.HealthIndex] == kvp.Value && kvp.Key.Prefab.Identifier == identifier) {
return kvp.Key; }
389 if (affliction !=
null && afflictions.TryGetValue(affliction, out
LimbHealth limbHealth))
391 if (limbHealth ==
null) {
return null; }
392 int limbHealthIndex = limbHealths.IndexOf(limbHealth);
395 if (limb.
HealthIndex == limbHealthIndex) {
return limb; }
410 if (requireLimbSpecific && limbHealths.Count == 1) {
return 0.0f; }
412 float strength = 0.0f;
414 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
416 if (kvp.Value == limbHealth)
431 return GetAfflictionStrength(afflictionType, afflictionidentifier: Identifier.Empty, allowLimbAfflictions);
436 return GetAfflictionStrength(afflictionType: Identifier.Empty, afflictionIdentifier, allowLimbAfflictions);
439 public float GetAfflictionStrength(Identifier afflictionType, Identifier afflictionidentifier,
bool allowLimbAfflictions =
true)
441 float strength = 0.0f;
442 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
444 if (!allowLimbAfflictions && kvp.Value !=
null) {
continue; }
445 var affliction = kvp.Key;
446 if (affliction.Strength < affliction.Prefab.ActivationThreshold) {
continue; }
447 if ((affliction.Prefab.AfflictionType == afflictionType || afflictionType.IsEmpty) &&
448 (affliction.Prefab.Identifier == afflictionidentifier || afflictionidentifier.IsEmpty))
456 public void ApplyAffliction(
Limb targetLimb,
Affliction affliction,
bool allowStacking =
true,
bool ignoreUnkillability =
false,
bool recalculateVitality =
true)
459 if (!ignoreUnkillability)
465 if (targetLimb ==
null)
468 foreach (
LimbHealth limbHealth
in limbHealths)
470 AddLimbAffliction(limbHealth, limb:
null, affliction, allowStacking: allowStacking, recalculateVitality: recalculateVitality);
476 AddLimbAffliction(targetLimb, affliction, allowStacking: allowStacking, recalculateVitality: recalculateVitality);
481 AddAffliction(affliction, allowStacking: allowStacking);
491 float resistance = 0.0f;
492 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
494 var affliction = kvp.Key;
495 resistance += affliction.
GetResistance(afflictionPrefab.Identifier, limbType);
500 return 1 - ((1 - resistance) * abilityResistanceMultiplier);
506 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
508 var affliction = kvp.Key;
516 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
518 var affliction = kvp.Key;
519 if (affliction.HasFlag(flagType)) {
return true; }
524 private readonly List<Affliction> matchingAfflictions =
new List<Affliction>();
528 matchingAfflictions.Clear();
529 matchingAfflictions.AddRange(afflictions.Keys);
531 ReduceMatchingAfflictions(amount, treatmentAction);
536 if (afflictionIdOrType.IsEmpty) {
throw new ArgumentException($
"{nameof(afflictionIdOrType)} is empty"); }
538 matchingAfflictions.Clear();
539 foreach (var affliction
in afflictions)
541 if (affliction.Key.Prefab.Identifier == afflictionIdOrType || affliction.Key.Prefab.AfflictionType == afflictionIdOrType)
543 matchingAfflictions.Add(affliction.Key);
547 ReduceMatchingAfflictions(amount, treatmentAction, attacker);
550 private IEnumerable<Affliction> GetAfflictionsForLimb(
Limb targetLimb)
551 => afflictions.Keys.Where(k => afflictions[k] == limbHealths[targetLimb.HealthIndex]);
555 if (targetLimb is
null) {
throw new ArgumentNullException(nameof(targetLimb)); }
557 matchingAfflictions.Clear();
558 matchingAfflictions.AddRange(GetAfflictionsForLimb(targetLimb));
560 ReduceMatchingAfflictions(amount, treatmentAction);
565 if (afflictionIdOrType.IsEmpty) {
throw new ArgumentException($
"{nameof(afflictionIdOrType)} is empty"); }
566 if (targetLimb is
null) {
throw new ArgumentNullException(nameof(targetLimb)); }
568 matchingAfflictions.Clear();
569 var targetLimbHealth = limbHealths[targetLimb.
HealthIndex];
570 foreach (var affliction
in afflictions)
572 if ((affliction.Key.Prefab.Identifier == afflictionIdOrType || affliction.Key.Prefab.AfflictionType == afflictionIdOrType) &&
573 affliction.Value == targetLimbHealth)
575 matchingAfflictions.Add(affliction.Key);
578 ReduceMatchingAfflictions(amount, treatmentAction, attacker);
581 private void ReduceMatchingAfflictions(
float amount,
ActionType? treatmentAction,
Character attacker =
null)
583 if (matchingAfflictions.Count == 0) {
return; }
585 float reduceAmount = amount / matchingAfflictions.Count;
587 if (reduceAmount > 0f)
589 var abilityReduceAffliction =
new AbilityReduceAffliction(
Character, reduceAmount);
590 attacker?.CheckTalents(
AbilityEffectType.OnReduceAffliction, abilityReduceAffliction);
591 reduceAmount = abilityReduceAffliction.Value;
594 for (
int i = matchingAfflictions.Count - 1; i >= 0; i--)
596 var matchingAffliction = matchingAfflictions[i];
598 if (matchingAffliction.Strength < reduceAmount)
600 float surplus = reduceAmount - matchingAffliction.Strength;
601 amount -= matchingAffliction.Strength;
602 matchingAffliction.Strength = 0.0f;
603 matchingAfflictions.RemoveAt(i);
604 if (i == 0) { i = matchingAfflictions.Count; }
605 if (i > 0) { reduceAmount += surplus / i; }
606 AchievementManager.OnAfflictionRemoved(matchingAffliction,
Character);
610 matchingAffliction.Strength -= reduceAmount;
611 amount -= reduceAmount;
612 if (treatmentAction !=
null)
616 matchingAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
618 else if (treatmentAction.Value ==
ActionType.OnFailure)
620 matchingAffliction.AppliedAsFailedTreatmentTime = Timing.TotalTime;
637 DebugConsole.ThrowError(
"Limb health index out of bounds. Character\"" +
Character.
Name +
638 "\" only has health configured for" + limbHealths.Count +
" limbs but the limb " + hitLimb.
type +
" is targeting index " + hitLimb.
HealthIndex);
642 var should =
GameMain.
LuaCs.
Hook.
Call<
bool?>(
"character.applyDamage",
this, attackResult, hitLimb, allowStacking);
643 if (should !=
null && should.Value) {
return; }
645 foreach (
Affliction newAffliction
in attackResult.Afflictions)
649 AddLimbAffliction(hitLimb, newAffliction, allowStacking, recalculateVitality: recalculateVitality);
654 AddAffliction(newAffliction, allowStacking);
659 private void KillIfOutOfVitality()
668 private readonly
static List<Affliction> afflictionsToRemove =
new List<Affliction>();
669 private readonly
static List<KeyValuePair<Affliction, LimbHealth>> afflictionsToUpdate =
new List<KeyValuePair<Affliction, LimbHealth>>();
670 public void SetAllDamage(
float damageAmount,
float bleedingDamageAmount,
float burnDamageAmount)
674 afflictionsToRemove.Clear();
675 afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
679 foreach (var affliction
in afflictionsToRemove)
681 afflictions.Remove(affliction);
684 foreach (
LimbHealth limbHealth
in limbHealths)
696 float damageStrength;
707 if (afflictionType.IsEmpty)
712 damageStrength = Math.Min(damage + bleeding + burn, max);
718 return damageStrength / max;
724 afflictionsToRemove.Clear();
725 afflictionsToRemove.AddRange(afflictions.Keys.Where(affliction => predicate(affliction)));
726 foreach (var affliction
in afflictionsToRemove)
728 afflictions.Remove(affliction);
735 afflictionsToRemove.Clear();
736 afflictionsToRemove.AddRange(afflictions.Keys.Where(a => !irremovableAfflictions.Contains(a)));
737 foreach (var affliction
in afflictionsToRemove)
739 afflictions.Remove(affliction);
741 foreach (
Affliction affliction
in irremovableAfflictions)
743 affliction.Strength = 0.0f;
750 afflictionsToRemove.Clear();
751 afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
752 !irremovableAfflictions.Contains(a) &&
754 a.Prefab.AfflictionType !=
"geneticmaterialbuff" &&
755 a.Prefab.AfflictionType !=
"geneticmaterialdebuff"));
756 foreach (var affliction
in afflictionsToRemove)
758 afflictions.Remove(affliction);
760 foreach (
Affliction affliction
in irremovableAfflictions)
762 affliction.Strength = 0.0f;
771 private void AddLimbAffliction(
Limb limb,
Affliction newAffliction,
bool allowStacking =
true,
bool recalculateVitality =
true)
776 DebugConsole.ThrowError(
"Limb health index out of bounds. Character\"" +
Character.
Name +
777 "\" only has health configured for" + limbHealths.Count +
" limbs but the limb " + limb.
type +
" is targeting index " + limb.
HealthIndex);
780 AddLimbAffliction(limbHealths[limb.
HealthIndex], limb, newAffliction, allowStacking, recalculateVitality);
787 private void AddLimbAffliction(LimbHealth limbHealth, Limb limb, Affliction newAffliction,
bool allowStacking =
true,
bool recalculateVitality =
true)
791 if (!
DoesBleed && newAffliction is AfflictionBleeding) {
return; }
802 if (newAffliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType || newAffliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)
808 if (newAffliction.Prefab.TargetSpecies.Any() && newAffliction.Prefab.TargetSpecies.None(s => s ==
Character.
SpeciesName)) {
return; }
811 var should = GameMain.LuaCs.Hook.Call<
bool?>(
"character.applyAffliction",
this, limbHealth, newAffliction, allowStacking);
813 if (should !=
null && should.Value)
817 foreach ((Affliction affliction, LimbHealth value) in afflictions)
819 if (value == limbHealth && affliction.Prefab == newAffliction.Prefab)
821 existingAffliction = affliction;
826 if (existingAffliction !=
null)
828 float newStrength = newAffliction.Strength * (100.0f /
MaxVitality) * (1f -
GetResistance(existingAffliction.Prefab, limbType));
832 newStrength += existingAffliction.Strength;
834 newStrength = Math.Min(existingAffliction.Prefab.MaxStrength, newStrength);
835 if (existingAffliction == stunAffliction) {
Character.
SetStun(newStrength,
true,
true); }
836 existingAffliction.Strength = newStrength;
837 existingAffliction.Duration = existingAffliction.Prefab.Duration;
838 if (newAffliction.Source !=
null) { existingAffliction.Source = newAffliction.Source; }
839 if (recalculateVitality)
848 var copyAffliction = newAffliction.Prefab.Instantiate(
849 Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f /
MaxVitality) * (1f -
GetResistance(newAffliction.Prefab, limbType))),
850 newAffliction.Source);
851 afflictions.Add(copyAffliction, limbHealth);
852 AchievementManager.OnAfflictionReceived(copyAffliction,
Character);
853 MedicalClinic.OnAfflictionCountChanged(
Character);
857 if (recalculateVitality)
862 if (OpenHealthWindow !=
this && limbHealth !=
null)
864 selectedLimbIndex = -1;
869 private void AddAffliction(Affliction newAffliction,
bool allowStacking =
true)
871 AddLimbAffliction(limbHealth:
null, limb:
null, newAffliction, allowStacking);
874 partial
void UpdateSkinTint();
876 partial
void UpdateLimbAfflictionOverlays();
882 UpdateOxygen(deltaTime);
888 afflictionsToRemove.Clear();
889 afflictionsToUpdate.Clear();
890 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
892 var affliction = kvp.Key;
893 if (affliction.Strength <= 0.0f)
895 AchievementManager.OnAfflictionRemoved(affliction,
Character);
896 if (!irremovableAfflictions.Contains(affliction)) { afflictionsToRemove.Add(affliction); }
899 if (affliction.Prefab.Duration > 0.0f)
901 affliction.Duration -= deltaTime;
902 if (affliction.Duration <= 0.0f)
904 afflictionsToRemove.Add(affliction);
908 afflictionsToUpdate.Add(kvp);
910 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictionsToUpdate)
912 var affliction = kvp.Key;
913 Limb targetLimb =
null;
914 if (kvp.Value !=
null)
916 int healthIndex = limbHealths.IndexOf(kvp.Value);
921 affliction.
Update(
this, targetLimb, deltaTime);
922 affliction.DamagePerSecondTimer += deltaTime;
925 UpdateBleedingProjSpecific(bleeding, targetLimb, deltaTime);
930 foreach (var affliction
in afflictionsToRemove)
932 afflictions.Remove(affliction);
935 if (afflictionsToRemove.Count is not 0)
937 MedicalClinic.OnAfflictionCountChanged(
Character);
951 UpdateDamageReductions(deltaTime);
956 updateVisualsTimer -= deltaTime;
957 if (
Character.IsVisible && updateVisualsTimer <= 0.0f)
959 UpdateLimbAfflictionOverlays();
961 updateVisualsTimer = UpdateVisualsInterval;
970 UpdateLimbAfflictionOverlays();
974 private void UpdateDamageReductions(
float deltaTime)
982 if (burnReduction > 0)
987 if (bleedingReduction > 0)
998 private void UpdateOxygen(
float deltaTime)
1002 oxygenLowAffliction.
Strength = 0.0f;
1011 float decreaseSpeed = Math.Max(0.1f, 1f - oxygenlowResistance);
1017 float decreaseSpeed = -5.0f;
1018 float increaseSpeed = 10.0f;
1019 decreaseSpeed *= (1f - oxygenlowResistance);
1020 increaseSpeed *= (1f + oxygenlowResistance);
1022 if (holdBreathMultiplier <= -1.0f)
1033 UpdateOxygenProjSpecific(prevOxygen, deltaTime);
1036 partial
void UpdateOxygenProjSpecific(
float prevOxygen,
float deltaTime);
1038 partial
void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb,
float deltaTime);
1043 CalculateVitality();
1046 private void CalculateVitality()
1052 foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
1054 float vitalityDecrease = affliction.GetVitalityDecrease(
this);
1055 if (limbHealth !=
null)
1057 vitalityDecrease *= GetVitalityMultiplier(affliction, limbHealth);
1059 vitality -= vitalityDecrease;
1060 affliction.CalculateDamagePerSecond(vitalityDecrease);
1062 if (affliction.Strength >= affliction.Prefab.MaxStrength &&
1063 affliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)
1071 HintManager.OnCharacterUnconscious(
Character);
1078 CalculateVitality();
1079 KillIfOutOfVitality();
1082 private static float GetVitalityMultiplier(
Affliction affliction, LimbHealth limbHealth)
1084 float multiplier = 1.0f;
1085 if (limbHealth.VitalityMultipliers.TryGetValue(affliction.
Prefab.
Identifier, out
float vitalityMultiplier))
1087 multiplier *= vitalityMultiplier;
1089 if (limbHealth.VitalityTypeMultipliers.TryGetValue(affliction.
Prefab.
AfflictionType, out
float vitalityTypeMultiplier))
1091 multiplier *= vitalityTypeMultiplier;
1099 private float GetVitalityDecreaseWithVitalityMultipliers(Affliction affliction)
1101 float vitalityDecrease = affliction.GetVitalityDecrease(
this);
1102 if (afflictions.TryGetValue(affliction, out LimbHealth limbHealth) && limbHealth !=
null)
1104 vitalityDecrease *= GetVitalityMultiplier(affliction, limbHealth);
1106 return vitalityDecrease;
1113 var (
type, affliction) = GetCauseOfDeath();
1114 UpdateLimbAfflictionOverlays();
1120 DisplayVitalityDelay = 0.0f;
1127 private readonly List<Affliction> afflictionsCopy =
new List<Affliction>();
1130 afflictionsCopy.Clear();
1131 afflictionsCopy.AddRange(afflictions.Keys);
1132 foreach (
Affliction affliction
in afflictionsCopy)
1143 float largestStrength = 0.0f;
1144 foreach (
Affliction affliction
in currentAfflictions)
1146 if (strongestAffliction ==
null || affliction.
GetVitalityDecrease(
this) > largestStrength)
1148 strongestAffliction = affliction;
1154 if (strongestAffliction == oxygenLowAffliction)
1159 return (causeOfDeath, strongestAffliction);
1162 private readonly List<Affliction> allAfflictions =
new List<Affliction>();
1163 private List<Affliction>
GetAllAfflictions(
bool mergeSameAfflictions, Func<Affliction, bool> predicate =
null)
1165 allAfflictions.Clear();
1166 if (!mergeSameAfflictions)
1168 allAfflictions.AddRange(predicate ==
null ? afflictions.Keys : afflictions.Keys.Where(predicate));
1172 foreach (Affliction affliction
in afflictions.Keys)
1174 if (predicate !=
null && !predicate(affliction)) {
continue; }
1175 var existingAffliction = allAfflictions.Find(a => a.Prefab == affliction.Prefab);
1176 if (existingAffliction ==
null)
1178 var newAffliction = affliction.Prefab.Instantiate(affliction.Strength);
1179 if (affliction.Source !=
null) { newAffliction.Source = affliction.Source; }
1180 newAffliction.DamagePerSecond = affliction.DamagePerSecond;
1181 newAffliction.DamagePerSecondTimer = affliction.DamagePerSecondTimer;
1182 allAfflictions.Add(newAffliction);
1186 existingAffliction.DamagePerSecond += affliction.DamagePerSecond;
1187 existingAffliction.Strength += affliction.Strength;
1191 return allAfflictions;
1200 public void GetSuitableTreatments(Dictionary<Identifier, float> treatmentSuitability,
Character user,
Limb limb =
null,
bool ignoreHiddenAfflictions =
false,
float predictFutureDuration = 0.0f)
1204 treatmentSuitability.Clear();
1205 float minSuitability = -10, maxSuitability = 10;
1206 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
1208 var affliction = kvp.Key;
1209 var limbHealth = kvp.Value;
1211 affliction.Prefab.LimbSpecific &&
1212 GetMatchingLimbHealth(affliction) != GetMatchingLimbHealth(limb))
1214 if (limbHealth ==
null) {
continue; }
1215 int healthIndex = limbHealths.IndexOf(limbHealth);
1216 if (limb.HealthIndex != healthIndex) {
continue; }
1219 float strength = affliction.Strength;
1220 if (predictFutureDuration > 0.0f)
1229 if (afflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Key.Identifier))) {
continue; }
1231 if (ignoreHiddenAfflictions)
1235 if (strength < affliction.Prefab.ShowIconThreshold) {
continue; }
1239 if (strength < affliction.Prefab.ShowIconToOthersThreshold) {
continue; }
1243 foreach (KeyValuePair<Identifier, float> treatment
in affliction.Prefab.TreatmentSuitabilities)
1245 float suitability = treatment.Value * strength;
1246 if (suitability > 0)
1251 if (totalAfflictionStrength < affliction.Prefab.TreatmentThreshold) {
continue; }
1253 if (treatment.Value > strength)
1256 float overtreatmentFactor = MathHelper.Clamp(treatment.Value / strength, 1.0f, 10.0f);
1257 suitability /= overtreatmentFactor;
1259 if (!treatmentSuitability.ContainsKey(treatment.Key))
1261 treatmentSuitability[treatment.Key] = suitability;
1265 treatmentSuitability[treatment.Key] += suitability;
1267 minSuitability = Math.Min(treatmentSuitability[treatment.Key], minSuitability);
1268 maxSuitability = Math.Max(treatmentSuitability[treatment.Key], maxSuitability);
1281 float totalAfflictionStrength = includeSameAffliction ? affliction.
Strength : 0;
1284 foreach (
Affliction otherAffliction
in afflictions.Keys)
1286 if (affliction.
Prefab == otherAffliction.
Prefab && affliction != otherAffliction)
1288 totalAfflictionStrength += otherAffliction.
Strength * otherAfflictionMultiplier;
1292 return totalAfflictionStrength;
1295 private readonly HashSet<Identifier> afflictionTags =
new HashSet<Identifier>();
1298 afflictionTags.Clear();
1299 foreach (
Affliction affliction
in afflictions.Keys)
1302 if (currentEffect is { Tag.IsEmpty:
false })
1304 afflictionTags.Add(currentEffect.Tag);
1307 return afflictionTags;
1312 float strength = affliction.
Strength;
1316 float statusEffectDuration = Math.Min(statusEffect.Timer, predictFutureDuration);
1317 foreach (var statusEffectAffliction
in statusEffect.Parent.Afflictions)
1319 if (statusEffectAffliction.Prefab == affliction.
Prefab)
1321 strength += statusEffectAffliction.Strength * statusEffectDuration;
1324 foreach (var statusEffectAffliction
in statusEffect.Parent.ReduceAffliction)
1326 if (statusEffectAffliction.AfflictionIdentifier == affliction.
Identifier ||
1329 strength -= statusEffectAffliction.ReduceAmount * statusEffectDuration;
1336 private readonly List<Affliction> activeAfflictions =
new List<Affliction>();
1337 private readonly List<(LimbHealth limbHealth,
Affliction affliction)> limbAfflictions =
new List<(LimbHealth limbHealth,
Affliction affliction)>();
1340 activeAfflictions.Clear();
1341 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
1343 var affliction = kvp.Key;
1344 var limbHealth = kvp.
Value;
1345 if (limbHealth !=
null) {
continue; }
1346 if (affliction.Strength > 0.0f && affliction.Strength >= affliction.Prefab.ActivationThreshold)
1348 activeAfflictions.Add(affliction);
1351 msg.WriteByte((
byte)activeAfflictions.Count);
1352 foreach (
Affliction affliction
in activeAfflictions)
1355 msg.WriteRangedSingle(
1361 msg.WriteRangedSingle(affliction.
PeriodicEffectTimers[periodicEffect], 0, periodicEffect.MaxInterval, 8);
1365 limbAfflictions.Clear();
1366 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
1368 var limbAffliction = kvp.Key;
1369 var limbHealth = kvp.Value;
1370 if (limbHealth ==
null) {
continue; }
1371 if (limbAffliction.Strength <= 0.0f || limbAffliction.Strength < limbAffliction.Prefab.ActivationThreshold) {
continue; }
1372 limbAfflictions.Add((limbHealth, limbAffliction));
1375 msg.WriteByte((
byte)limbAfflictions.Count);
1376 foreach (var (limbHealth, affliction) in limbAfflictions)
1378 msg.WriteRangedInteger(limbHealths.IndexOf(limbHealth), 0, limbHealths.Count - 1);
1380 msg.WriteRangedSingle(
1386 msg.WriteRangedSingle(affliction.
PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8);
1393 RemoveProjSpecific();
1394 afflictionsToRemove.Clear();
1395 afflictionsToUpdate.Clear();
1398 partial
void RemoveProjSpecific();
1404 afflictions.Where(a => !excludeBuffs || !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength / a.Prefab.MaxStrength);
1406 public void Save(XElement healthElement)
1408 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions)
1410 var affliction = kvp.Key;
1411 var limbHealth = kvp.
Value;
1412 if (affliction.Strength <= 0.0f || limbHealth !=
null) {
continue; }
1413 if (kvp.Key.Prefab.ResetBetweenRounds) {
continue; }
1414 healthElement.Add(
new XElement(
"Affliction",
1415 new XAttribute(
"identifier", affliction.Identifier),
1416 new XAttribute(
"strength", affliction.Strength.ToString(
"G", CultureInfo.InvariantCulture))));
1419 for (
int i = 0; i < limbHealths.Count; i++)
1421 var limbHealthElement =
new XElement(
"LimbHealth",
new XAttribute(
"i", i));
1422 healthElement.Add(limbHealthElement);
1423 foreach (KeyValuePair<Affliction, LimbHealth> kvp
in afflictions.Where(a => a.Value == limbHealths[i]))
1425 var affliction = kvp.Key;
1426 var limbHealth = kvp.
Value;
1427 if (affliction.Strength <= 0.0f) {
continue; }
1428 limbHealthElement.Add(
new XElement(
"Affliction",
1429 new XAttribute(
"identifier", affliction.Identifier),
1430 new XAttribute(
"strength", affliction.Strength.ToString(
"G", CultureInfo.InvariantCulture))));
1435 public void Load(XElement element, Func<AfflictionPrefab, bool> afflictionPredicate =
null)
1437 foreach (var subElement
in element.Elements())
1439 switch (subElement.Name.ToString().ToLowerInvariant())
1442 LoadAffliction(subElement);
1445 int limbHealthIndex = subElement.GetAttributeInt(
"i", -1);
1446 if (limbHealthIndex < 0 || limbHealthIndex >= limbHealths.Count)
1448 DebugConsole.ThrowError($
"Error while loading character health: limb index \"{limbHealthIndex}\" out of range.");
1451 foreach (XElement afflictionElement
in subElement.Elements())
1453 LoadAffliction(afflictionElement, limbHealths[limbHealthIndex]);
1459 void LoadAffliction(XElement afflictionElement,
LimbHealth limbHealth =
null)
1461 string id = afflictionElement.GetAttributeString(
"identifier",
"");
1463 if (afflictionPrefab ==
null)
1465 DebugConsole.ThrowError($
"Error while loading character health: affliction \"{id}\" not found.");
1468 if (afflictionPredicate !=
null && !afflictionPredicate.Invoke(afflictionPrefab)) {
return; }
1469 float strength = afflictionElement.GetAttributeFloat(
"strength", 0.0f);
1470 var irremovableAffliction = irremovableAfflictions.FirstOrDefault(a => a.Prefab == afflictionPrefab);
1471 if (irremovableAffliction !=
null)
1473 irremovableAffliction.Strength = strength;
1477 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 GetStatValue(StatTypes statType)
AfflictionPrefab.Effect GetActiveEffect()
float GetResistance(Identifier afflictionId, LimbType limbType)
How much resistance to the specified affliction does this affliction currently give?
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
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, bool recalculateVitality=true)
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)
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 ApplyDamage(Limb hitLimb, AttackResult attackResult, bool allowStacking=true, bool recalculateVitality=true)
bool ShowDamageOverlay
Show the blood overlay screen space effect when the character takes damage. Enabled normally,...
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
float GetResistance(AfflictionPrefab afflictionPrefab, LimbType limbType)
How much resistance all the afflictions the character has give to the specified affliction?
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()
void RecalculateVitality()
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)
void RemoveAfflictions(Func< Affliction, bool > predicate)
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,...
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 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, bool excludeLimbsWithSecondaryType=false, bool useSecondaryType=false)
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.