Client LuaCsForBarotrauma
AfflictionPrefab.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Reflection;
6 using System.Xml.Linq;
8 using System.Collections.Immutable;
10 using System.Linq;
11 
12 namespace Barotrauma
13 {
15  {
17  public static CPRSettings Active => Prefabs.ActivePrefab;
18 
19  public readonly float ReviveChancePerSkill;
20  public readonly float ReviveChanceExponent;
21  public readonly float ReviveChanceMin;
22  public readonly float ReviveChanceMax;
23  public readonly float StabilizationPerSkill;
24  public readonly float StabilizationMin;
25  public readonly float StabilizationMax;
26  public readonly float DamageSkillThreshold;
27  public readonly float DamageSkillMultiplier;
28 
29  private readonly string insufficientSkillAfflictionIdentifier;
31  {
32  get
33  {
34  return
35  AfflictionPrefab.Prefabs.ContainsKey(insufficientSkillAfflictionIdentifier) ?
36  AfflictionPrefab.Prefabs[insufficientSkillAfflictionIdentifier] :
38  }
39  }
40 
41  public CPRSettings(XElement element, AfflictionsFile file) : base(file, file.Path.Value.ToIdentifier())
42  {
43  ReviveChancePerSkill = Math.Max(element.GetAttributeFloat("revivechanceperskill", 0.01f), 0.0f);
44  ReviveChanceExponent = Math.Max(element.GetAttributeFloat("revivechanceexponent", 2.0f), 0.0f);
45  ReviveChanceMin = MathHelper.Clamp(element.GetAttributeFloat("revivechancemin", 0.05f), 0.0f, 1.0f);
46  ReviveChanceMax = MathHelper.Clamp(element.GetAttributeFloat("revivechancemax", 0.9f), ReviveChanceMin, 1.0f);
47 
48  StabilizationPerSkill = Math.Max(element.GetAttributeFloat("stabilizationperskill", 0.01f), 0.0f);
49  StabilizationMin = MathHelper.Max(element.GetAttributeFloat("stabilizationmin", 0.05f), 0.0f);
50  StabilizationMax = MathHelper.Max(element.GetAttributeFloat("stabilizationmax", 2.0f), StabilizationMin);
51 
52  DamageSkillThreshold = MathHelper.Clamp(element.GetAttributeFloat("damageskillthreshold", 40.0f), 0.0f, 100.0f);
53  DamageSkillMultiplier = MathHelper.Clamp(element.GetAttributeFloat("damageskillmultiplier", 0.1f), 0.0f, 100.0f);
54 
55  insufficientSkillAfflictionIdentifier = element.GetAttributeString("insufficientskillaffliction", "");
56  }
57 
58  public override void Dispose() { }
59  }
60 
65  {
66  // Use any of these to define which limb the appendage is attached to.
67  // If multiple are defined, the order of preference is: id, name, type.
68  public readonly int AttachLimbId;
69  public readonly string AttachLimbName;
70  public readonly LimbType AttachLimbType;
71 
76  public readonly float DormantThreshold;
77 
82  public readonly float ActiveThreshold;
83 
88  public readonly float TransitionThreshold;
89 
94  public readonly float TransformThresholdOnDeath;
95 
100  public readonly Identifier HuskedSpeciesName;
101 
106  public readonly bool TransferBuffs;
107 
112  public readonly bool SendMessages;
113 
118  public readonly bool CauseSpeechImpediment;
119 
124  public readonly bool NeedsAir;
125 
130  public readonly bool ControlHusk;
131 
132  public AfflictionPrefabHusk(ContentXElement element, AfflictionsFile file, Type type = null) : base(element, file, type)
133  {
134  HuskedSpeciesName = element.GetAttributeIdentifier("huskedspeciesname", Identifier.Empty);
135  if (HuskedSpeciesName.IsEmpty)
136  {
137  DebugConsole.NewMessage($"No 'huskedspeciesname' defined for the husk affliction ({Identifier}) in {element}", Color.Orange);
138  HuskedSpeciesName = "husk".ToIdentifier();
139  }
140  // Remove "[speciesname]" for backward support (we don't use it anymore)
141  HuskedSpeciesName = HuskedSpeciesName.Remove("[speciesname]").ToIdentifier();
142  if (TargetSpecies.Length == 0)
143  {
144  DebugConsole.NewMessage($"No 'targets' defined for the husk affliction ({Identifier}) in {element}", Color.Orange);
146  }
147  var attachElement = element.GetChildElement("attachlimb");
148  if (attachElement != null)
149  {
150  AttachLimbId = attachElement.GetAttributeInt("id", -1);
151  AttachLimbName = attachElement.GetAttributeString("name", null);
152  AttachLimbType = attachElement.GetAttributeEnum("type", LimbType.None);
153  }
154  else
155  {
156  AttachLimbId = -1;
157  AttachLimbName = null;
158  AttachLimbType = LimbType.None;
159  }
160 
161  TransferBuffs = element.GetAttributeBool("transferbuffs", true);
162  SendMessages = element.GetAttributeBool("sendmessages", true);
163  CauseSpeechImpediment = element.GetAttributeBool("causespeechimpediment", true);
164  NeedsAir = element.GetAttributeBool("needsair", false);
165  ControlHusk = element.GetAttributeBool("controlhusk", false);
166 
167  DormantThreshold = element.GetAttributeFloat("dormantthreshold", MaxStrength * 0.5f);
168  ActiveThreshold = element.GetAttributeFloat("activethreshold", MaxStrength * 0.75f);
169  TransitionThreshold = element.GetAttributeFloat("transitionthreshold", MaxStrength);
170 
172  {
173  DebugConsole.ThrowError($"Error in \"{Identifier}\": {nameof(DormantThreshold)} is greater than {nameof(ActiveThreshold)} ({DormantThreshold} > {ActiveThreshold})",
174  contentPackage: element.ContentPackage);
175  }
177  {
178  DebugConsole.ThrowError($"Error in \"{Identifier}\": {nameof(ActiveThreshold)} is greater than {nameof(TransitionThreshold)} ({ActiveThreshold} > {TransitionThreshold})",
179  contentPackage: element.ContentPackage);
180  }
181 
182  TransformThresholdOnDeath = element.GetAttributeFloat("transformthresholdondeath", ActiveThreshold);
183  }
184  }
185 
207  {
233  public sealed class Effect
234  {
235  //this effect is applied when the strength is within this range
236  [Serialize(0.0f, IsPropertySaveable.No, description: "Minimum affliction strength required for this effect to be active.")]
237  public float MinStrength { get; private set; }
238 
239  [Serialize(0.0f, IsPropertySaveable.No, description: "Maximum affliction strength for which this effect will be active.")]
240  public float MaxStrength { get; private set; }
241 
242  [Serialize(0.0f, IsPropertySaveable.No, description: "The amount of vitality that is lost at this effect's lowest strength.")]
243  public float MinVitalityDecrease { get; private set; }
244 
245  [Serialize(0.0f, IsPropertySaveable.No, description: "The amount of vitality that is lost at this effect's highest strength.")]
246  public float MaxVitalityDecrease { get; private set; }
247 
248  [Serialize(0.0f, IsPropertySaveable.No, description: "How much the affliction's strength changes every second while this effect is active.")]
249  public float StrengthChange { get; private set; }
250 
251  [Serialize(false, IsPropertySaveable.No, description:
252  "If set to true, MinVitalityDecrease and MaxVitalityDecrease represent a fraction of the affected character's maximum " +
253  "vitality, with 1 meaning 100%, instead of the same amount for all species.")]
254  public bool MultiplyByMaxVitality { get; private set; }
255 
256  [Serialize(0.0f, IsPropertySaveable.No, description: "Blur effect strength at this effect's lowest strength.")]
257  public float MinScreenBlur { get; private set; }
258 
259  [Serialize(0.0f, IsPropertySaveable.No, description: "Blur effect strength at this effect's highest strength.")]
260  public float MaxScreenBlur { get; private set; }
261 
262  [Serialize(0.0f, IsPropertySaveable.No, description: "Generic distortion effect strength at this effect's lowest strength.")]
263  public float MinScreenDistort { get; private set; }
264 
265  [Serialize(0.0f, IsPropertySaveable.No, description: "Generic distortion effect strength at this effect's highest strength.")]
266  public float MaxScreenDistort { get; private set; }
267 
268  [Serialize(0.0f, IsPropertySaveable.No, description: "Radial distortion effect strength at this effect's lowest strength.")]
269  public float MinRadialDistort { get; private set; }
270 
271  [Serialize(0.0f, IsPropertySaveable.No, description: "Radial distortion effect strength at this effect's highest strength.")]
272  public float MaxRadialDistort { get; private set; }
273 
274  [Serialize(0.0f, IsPropertySaveable.No, description: "Chromatic aberration effect strength at this effect's lowest strength.")]
275  public float MinChromaticAberration { get; private set; }
276 
277  [Serialize(0.0f, IsPropertySaveable.No, description: "Chromatic aberration effect strength at this effect's highest strength.")]
278  public float MaxChromaticAberration { get; private set; }
279 
280  [Serialize("255,255,255,255", IsPropertySaveable.No, description: "Radiation grain effect color.")]
281  public Color GrainColor { get; private set; }
282 
283  [Serialize(0.0f, IsPropertySaveable.No, description: "Radiation grain effect strength at this effect's lowest strength.")]
284  public float MinGrainStrength { get; private set; }
285 
286  [Serialize(0.0f, IsPropertySaveable.No, description: "Radiation grain effect strength at this effect's highest strength.")]
287  public float MaxGrainStrength { get; private set; }
288 
289  [Serialize(0.0f, IsPropertySaveable.No, description:
290  "The maximum rate of fluctuation to apply to visual effects caused by this affliction effect. " +
291  "Effective fluctuation is proportional to the affliction's current strength.")]
292  public float ScreenEffectFluctuationFrequency { get; private set; }
293 
294  [Serialize(1.0f, IsPropertySaveable.No, description:
295  "Multiplier for the affliction overlay's opacity at this effect's lowest strength. " +
296  "See the list of elements for more details.")]
297  public float MinAfflictionOverlayAlphaMultiplier { get; private set; }
298 
299  [Serialize(1.0f, IsPropertySaveable.No, description:
300  "Multiplier for the affliction overlay's opacity at this effect's highest strength. " +
301  "See the list of elements for more details.")]
302  public float MaxAfflictionOverlayAlphaMultiplier { get; private set; }
303 
304  [Serialize(1.0f, IsPropertySaveable.No, description:
305  "Multiplier for every buff's decay rate at this effect's lowest strength. " +
306  "Only applies to afflictions of class BuffDurationIncrease.")]
307  public float MinBuffMultiplier { get; private set; }
308 
309  [Serialize(1.0f, IsPropertySaveable.No, description:
310  "Multiplier for every buff's decay rate at this effect's highest strength. " +
311  "Only applies to afflictions of class BuffDurationIncrease.")]
312  public float MaxBuffMultiplier { get; private set; }
313 
314  [Serialize(1.0f, IsPropertySaveable.No, description: "Multiplier to apply to the affected character's speed at this effect's lowest strength.")]
315  public float MinSpeedMultiplier { get; private set; }
316 
317  [Serialize(1.0f, IsPropertySaveable.No, description: "Multiplier to apply to the affected character's speed at this effect's highest strength.")]
318  public float MaxSpeedMultiplier { get; private set; }
319 
320  [Serialize(1.0f, IsPropertySaveable.No, description: "Multiplier to apply to all of the affected character's skill levels at this effect's lowest strength.")]
321  public float MinSkillMultiplier { get; private set; }
322 
323  [Serialize(1.0f, IsPropertySaveable.No, description: "Multiplier to apply to all of the affected character's skill levels at this effect's highest strength.")]
324  public float MaxSkillMultiplier { get; private set; }
325 
330  public readonly ImmutableArray<Identifier> ResistanceFor;
331 
335  public readonly ImmutableArray<LimbType> ResistanceLimbs;
336 
337  [Serialize(0.0f, IsPropertySaveable.No,
338  description: "The amount of resistance to the afflictions specified by ResistanceFor to apply at this effect's lowest strength.")]
339  public float MinResistance { get; private set; }
340 
341  [Serialize(0.0f, IsPropertySaveable.No,
342  description: "The amount of resistance to the afflictions specified by ResistanceFor to apply at this effect's highest strength.")]
343  public float MaxResistance { get; private set; }
344 
345  [Serialize("", IsPropertySaveable.No, description: "Identifier used by AI to determine conversation lines to say when this effect is active.")]
346  public Identifier DialogFlag { get; private set; }
347 
348  [Serialize("", IsPropertySaveable.No, description: "Tag that enemy AI may use to target the affected character when this effect is active.")]
349  public Identifier Tag { get; private set; }
350 
351  [Serialize("0,0,0,0", IsPropertySaveable.No,
352  description: "Color to tint the affected character's face with at this effect's lowest strength. The alpha channel is used to determine how much to tint the character's face.")]
353  public Color MinFaceTint { get; private set; }
354 
355  [Serialize("0,0,0,0", IsPropertySaveable.No,
356  description: "Color to tint the affected character's face with at this effect's highest strength. The alpha channel is used to determine how much to tint the character's face.")]
357  public Color MaxFaceTint { get; private set; }
358 
359  [Serialize("0,0,0,0", IsPropertySaveable.No,
360  description: "Color to tint the affected character's entire body with at this effect's lowest strength. The alpha channel is used to determine how much to tint the character.")]
361  public Color MinBodyTint { get; private set; }
362 
363  [Serialize("0,0,0,0", IsPropertySaveable.No,
364  description: "Color to tint the affected character's entire body with at this effect's highest strength. The alpha channel is used to determine how much to tint the character.")]
365  public Color MaxBodyTint { get; private set; }
366 
367  [Serialize(0.0f, IsPropertySaveable.No,
368  description: "Range of the \"thermal goggles overlay\" enabled by the affliction.")]
369  public float ThermalOverlayRange { get; private set; }
370 
371  [Serialize("255,0,0,255", IsPropertySaveable.No,
372  description: $"Color of the \"thermal goggles overlay\" enabled by the affliction. Only has an effect if {nameof(ThermalOverlayRange)} is larger than 0.")]
373  public Color ThermalOverlayColor { get; private set; }
374 
388  public readonly struct AppliedStatValue
389  {
393  public readonly StatTypes StatType;
394 
398  public readonly float MinValue;
399 
403  public readonly float MaxValue;
404 
408  private readonly float Value;
409 
411  {
412  Value = element.GetAttributeFloat("value", 0.0f);
413  StatType = element.GetAttributeEnum("stattype", StatTypes.None);
414  MinValue = element.GetAttributeFloat("minvalue", Value);
415  MaxValue = element.GetAttributeFloat("maxvalue", Value);
416  }
417  }
418 
422  public readonly ImmutableArray<Identifier> BlockTransformation;
423 
427  public readonly ImmutableDictionary<StatTypes, AppliedStatValue> AfflictionStatValues;
428 
430 
431  //statuseffects applied on the character when the affliction is active
432  public readonly ImmutableArray<StatusEffect> StatusEffects;
433 
434  public Effect(ContentXElement element, string parentDebugName)
435  {
437 
438  ResistanceFor = element.GetAttributeIdentifierArray("resistancefor", Array.Empty<Identifier>())!.ToImmutableArray();
439  ResistanceLimbs = element.GetAttributeEnumArray<LimbType>("resistancelimbs", Array.Empty<LimbType>()).ToImmutableArray();
440 
441  BlockTransformation = element.GetAttributeIdentifierArray("blocktransformation", Array.Empty<Identifier>())!.ToImmutableArray();
442 
443  var afflictionStatValues = new Dictionary<StatTypes, AppliedStatValue>();
444  var statusEffects = new List<StatusEffect>();
445  foreach (var subElement in element.Elements())
446  {
447  switch (subElement.Name.ToString().ToLowerInvariant())
448  {
449  case "statuseffect":
450  statusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
451  break;
452  case "statvalue":
453  var newStatValue = new AppliedStatValue(subElement);
454  if (newStatValue.StatType == StatTypes.None || !afflictionStatValues.TryAdd(newStatValue.StatType, newStatValue))
455  {
456  DebugConsole.ThrowError($"Invalid stat value in the affliction \"{parentDebugName}\".", contentPackage: element.ContentPackage);
457  }
458  break;
459  case "abilityflag":
460  AbilityFlags flagType = subElement.GetAttributeEnum("flagtype", AbilityFlags.None);
461  if (flagType is AbilityFlags.None)
462  {
463  DebugConsole.ThrowError($"Error in affliction \"{parentDebugName}\" - invalid ability flag type \"{subElement.GetAttributeString("flagtype", "")}\".",
464  contentPackage: element.ContentPackage);
465  continue;
466  }
467  AfflictionAbilityFlags |= flagType;
468  break;
469  case "affliction":
470  DebugConsole.AddWarning($"Error in affliction \"{parentDebugName}\" - additional afflictions caused by the affliction should be configured inside status effects.",
471  contentPackage: element.ContentPackage);
472  break;
473  }
474  }
475  AfflictionStatValues = afflictionStatValues.ToImmutableDictionary();
476  StatusEffects = statusEffects.ToImmutableArray();
477  }
478 
483  public float GetStrengthFactor(Affliction affliction) => GetStrengthFactor(affliction.Strength);
484 
489  public float GetStrengthFactor(float strength)
490  => MathUtils.InverseLerp(
491  MinStrength,
492  MaxStrength,
493  strength);
494  }
495 
505  public sealed class Description
506  {
507  public enum TargetType
508  {
512  Any,
516  Self,
520  OtherCharacter
521  }
522 
526  public readonly LocalizedString Text;
527 
531  public readonly Identifier TextTag;
532 
536  public readonly float MinStrength;
537 
541  public readonly float MaxStrength;
542 
546  public readonly TargetType Target;
547 
548  public Description(ContentXElement element, AfflictionPrefab affliction)
549  {
550  TextTag = element.GetAttributeIdentifier("textidentifier", Identifier.Empty);
551  if (!TextTag.IsEmpty)
552  {
553  Text = TextManager.Get(TextTag);
554  }
555  string text = element.GetAttributeString("text", string.Empty);
556  if (!text.IsNullOrEmpty())
557  {
558  Text = Text?.Fallback(text) ?? text;
559  }
560  else if (TextTag.IsEmpty)
561  {
562  DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - no text defined for one of the descriptions.",
563  contentPackage: element.ContentPackage);
564  }
565 
566  MinStrength = element.GetAttributeFloat(nameof(MinStrength), 0.0f);
567  MaxStrength = element.GetAttributeFloat(nameof(MaxStrength), 100.0f);
568  if (MinStrength >= MaxStrength)
569  {
570  DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - max strength is not larger than min.",
571  contentPackage: element.ContentPackage);
572  }
573  Target = element.GetAttributeEnum(nameof(Target), TargetType.Any);
574  }
575  }
576 
593  public sealed class PeriodicEffect
594  {
595  public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
596  public readonly float MinInterval, MaxInterval;
597  public readonly float MinStrength, MaxStrength;
598 
599  public PeriodicEffect(ContentXElement element, string parentDebugName)
600  {
601  foreach (var subElement in element.Elements())
602  {
603  StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
604  }
605 
606  if (element.GetAttribute("interval") != null)
607  {
608  MinInterval = MaxInterval = Math.Max(element.GetAttributeFloat("interval", 1.0f), 1.0f);
609  }
610  else
611  {
612  MinInterval = Math.Max(element.GetAttributeFloat(nameof(MinInterval), 1.0f), 0.1f);
613  MaxInterval = Math.Max(element.GetAttributeFloat(nameof(MaxInterval), 1.0f), MinInterval);
614  MinStrength = Math.Max(element.GetAttributeFloat(nameof(MinStrength), 0f), 0f);
615  MaxStrength = Math.Max(element.GetAttributeFloat(nameof(MaxStrength), MinStrength), MinStrength);
616  }
617  }
618  }
619 
620  public static readonly Identifier DamageType = "damage".ToIdentifier();
621  public static readonly Identifier BurnType = "burn".ToIdentifier();
622  public static readonly Identifier BleedingType = "bleeding".ToIdentifier();
623  public static readonly Identifier ParalysisType = "paralysis".ToIdentifier();
624  public static readonly Identifier PoisonType = "poison".ToIdentifier();
625  public static readonly Identifier StunType = "stun".ToIdentifier();
626  public static readonly Identifier EMPType = "emp".ToIdentifier();
627  public static readonly Identifier SpaceHerpesType = "spaceherpes".ToIdentifier();
628  public static readonly Identifier AlienInfectedType = "alieninfected".ToIdentifier();
629  public static readonly Identifier InvertControlsType = "invertcontrols".ToIdentifier();
630  public static readonly Identifier DisguisedAsHuskType = "disguiseashusk".ToIdentifier();
631 
632  public static AfflictionPrefab InternalDamage => Prefabs["internaldamage"];
633  public static AfflictionPrefab BiteWounds => Prefabs["bitewounds"];
634  public static AfflictionPrefab ImpactDamage => Prefabs["blunttrauma"];
637  public static AfflictionPrefab OxygenLow => Prefabs["oxygenlow"];
638  public static AfflictionPrefab Bloodloss => Prefabs["bloodloss"];
639  public static AfflictionPrefab Pressure => Prefabs["pressure"];
640  public static AfflictionPrefab OrganDamage => Prefabs["organdamage"];
642  public static AfflictionPrefab RadiationSickness => Prefabs["radiationsickness"];
643  public static AfflictionPrefab HuskInfection => Prefabs["huskinfection"];
644 
646 
647  public static IEnumerable<AfflictionPrefab> List => Prefabs;
648 
649  public override void Dispose() { }
650 
651  private readonly ContentXElement configElement;
652 
653  public readonly LocalizedString Name;
654 
655  public readonly LocalizedString CauseOfDeathDescription, SelfCauseOfDeathDescription;
656 
657  private readonly LocalizedString defaultDescription;
658  public readonly ImmutableList<Description> Descriptions;
659 
663  public readonly Identifier AfflictionType;
664 
668  public readonly bool LimbSpecific;
669 
678  public readonly LimbType IndicatorLimb;
679 
685 
691  public readonly bool IsBuff;
692 
697  public readonly bool AffectMachines;
698 
707  public readonly bool HealableInMedicalClinic;
708 
713  public readonly float HealCostMultiplier;
714 
718  public readonly int BaseHealCost;
719 
724  public readonly bool ShowBarInHealthMenu;
725 
729  public readonly bool HideIconAfterDelay;
730 
734  public readonly float ActivationThreshold = 0.0f;
735 
739  public readonly float ShowIconThreshold = 0.05f;
740 
744  public readonly float ShowIconToOthersThreshold = 0.05f;
745 
749  public readonly float MaxStrength = 100.0f;
750 
754  public readonly float GrainBurst;
755 
759  public readonly float ShowInHealthScannerThreshold;
760 
765  public readonly float TreatmentThreshold;
766 
770  public ImmutableHashSet<Identifier> IgnoreTreatmentIfAfflictedBy;
771 
775  public readonly float Duration;
776 
780  public float KarmaChangeOnApplied;
781 
785  public readonly float BurnOverlayAlpha;
786 
790  public readonly float DamageOverlayAlpha;
791 
795 
800 
805  public readonly Color[] IconColors;
806 
811  public readonly bool AfflictionOverlayAlphaIsLinear;
812 
816  public readonly bool ResetBetweenRounds;
817 
822  public readonly bool DamageParticles;
823 
829  public readonly float MedicalSkillGain;
830 
835  public readonly float WeaponsSkillGain;
836 
840  public Identifier[] TargetSpecies { get; protected set; }
841 
846  private readonly List<Effect> effects = new List<Effect>();
847 
851  private readonly List<PeriodicEffect> periodicEffects = new List<PeriodicEffect>();
852 
853  public IEnumerable<Effect> Effects => effects;
854 
855  public IList<PeriodicEffect> PeriodicEffects => periodicEffects;
856 
857  private readonly ConstructorInfo constructor;
858 
862  public readonly Sprite Icon;
863 
868  public readonly Sprite AfflictionOverlay;
869 
870  public ImmutableDictionary<Identifier, float> TreatmentSuitabilities
871  {
872  get;
873  private set;
874  } = new Dictionary<Identifier, float>().ToImmutableDictionary();
875 
879  public bool HasTreatments { get; private set; }
880 
881  public AfflictionPrefab(ContentXElement element, AfflictionsFile file, Type type) : base(file, element.GetAttributeIdentifier("identifier", ""))
882  {
883  configElement = element;
884 
885  AfflictionType = element.GetAttributeIdentifier("type", "");
886  TranslationIdentifier = element.GetAttributeIdentifier("translationoverride", Identifier);
887  Name = TextManager.Get($"AfflictionName.{TranslationIdentifier}");
888  string fallbackName = element.GetAttributeString("name", "");
889  if (!string.IsNullOrEmpty(fallbackName))
890  {
891  Name = Name.Fallback(fallbackName);
892  }
893  defaultDescription = TextManager.Get($"AfflictionDescription.{TranslationIdentifier}");
894  string fallbackDescription = element.GetAttributeString("description", "");
895  if (!string.IsNullOrEmpty(fallbackDescription))
896  {
897  defaultDescription = defaultDescription.Fallback(fallbackDescription);
898  }
899  IsBuff = element.GetAttributeBool(nameof(IsBuff), false);
900  AffectMachines = element.GetAttributeBool(nameof(AffectMachines), true);
901 
902  ShowBarInHealthMenu = element.GetAttributeBool("showbarinhealthmenu", true);
903 
904  HealableInMedicalClinic = element.GetAttributeBool("healableinmedicalclinic",
905  !IsBuff &&
906  AfflictionType != "geneticmaterialbuff" &&
907  AfflictionType != "geneticmaterialdebuff");
909  BaseHealCost = element.GetAttributeInt(nameof(BaseHealCost), 0);
910 
911  IgnoreTreatmentIfAfflictedBy = element.GetAttributeIdentifierArray(nameof(IgnoreTreatmentIfAfflictedBy), Array.Empty<Identifier>()).ToImmutableHashSet();
912 
913  Duration = element.GetAttributeFloat(nameof(Duration), 0.0f);
914 
915  if (element.GetAttribute("nameidentifier") != null)
916  {
917  string nameIdentifier = element.GetAttributeString("nameidentifier", string.Empty);
918  Name = TextManager.Get(nameIdentifier)
919  .Fallback(TextManager.Get($"AfflictionName.{nameIdentifier}"))
920  .Fallback(Name);
921  }
922 
923  LimbSpecific = element.GetAttributeBool("limbspecific", false);
924  if (!LimbSpecific)
925  {
926  string indicatorLimbName = element.GetAttributeString("indicatorlimb", "Torso");
927  if (!Enum.TryParse(indicatorLimbName, out IndicatorLimb))
928  {
929  DebugConsole.ThrowErrorLocalized("Error in affliction prefab " + Name + " - limb type \"" + indicatorLimbName + "\" not found.");
930  }
931  }
932 
933  HideIconAfterDelay = element.GetAttributeBool(nameof(HideIconAfterDelay), false);
934 
936  ShowIconThreshold = element.GetAttributeFloat(nameof(ShowIconThreshold), Math.Max(ActivationThreshold, 0.05f));
938  MaxStrength = element.GetAttributeFloat(nameof(MaxStrength), 100.0f);
939  GrainBurst = element.GetAttributeFloat(nameof(GrainBurst), 0.0f);
940 
942  Math.Max(ActivationThreshold, AfflictionType == "talentbuff" ? float.MaxValue : ShowIconToOthersThreshold));
943  TreatmentThreshold = element.GetAttributeFloat(nameof(TreatmentThreshold), Math.Max(ActivationThreshold, 10.0f));
944 
946  BurnOverlayAlpha = element.GetAttributeFloat(nameof(BurnOverlayAlpha), 0.0f);
947 
949 
951  TextManager.Get($"AfflictionCauseOfDeath.{TranslationIdentifier}")
952  .Fallback(TextManager.Get(element.GetAttributeString("causeofdeathdescription", "")))
953  .Fallback(element.GetAttributeString("causeofdeathdescription", ""));
954  SelfCauseOfDeathDescription =
955  TextManager.Get($"AfflictionCauseOfDeathSelf.{TranslationIdentifier}")
956  .Fallback(TextManager.Get(element.GetAttributeString("selfcauseofdeathdescription", "")))
957  .Fallback(element.GetAttributeString("selfcauseofdeathdescription", ""));
958 
959  IconColors = element.GetAttributeColorArray(nameof(IconColors), null);
963 
964  TargetSpecies = element.GetAttributeIdentifierArray("targets", Array.Empty<Identifier>(), trim: true);
965 
966  ResetBetweenRounds = element.GetAttributeBool("resetbetweenrounds", false);
967 
968  DamageParticles = element.GetAttributeBool(nameof(DamageParticles), true);
969  WeaponsSkillGain = element.GetAttributeFloat(nameof(WeaponsSkillGain), 0.0f);
970  MedicalSkillGain = element.GetAttributeFloat(nameof(MedicalSkillGain), 0.0f);
971 
972  List<Description> descriptions = new List<Description>();
973  foreach (var subElement in element.Elements())
974  {
975  switch (subElement.Name.ToString().ToLowerInvariant())
976  {
977  case "icon":
978  Icon = new Sprite(subElement);
979  break;
980  case "afflictionoverlay":
981  AfflictionOverlay = new Sprite(subElement);
982  break;
983  case "statvalue":
984  DebugConsole.ThrowError($"Error in affliction \"{Identifier}\" - stat values should be configured inside the affliction's effects.",
985  contentPackage: element.ContentPackage);
986  break;
987  case "effect":
988  case "periodiceffect":
989  break;
990  case "description":
991  descriptions.Add(new Description(subElement, this));
992  break;
993  default:
994  DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})",
995  contentPackage: element.ContentPackage);
996  break;
997  }
998  }
999  Descriptions = descriptions.ToImmutableList();
1000 
1001  constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) });
1002  }
1003 
1004  private void RefreshTreatmentSuitabilities()
1005  {
1006  var newTreatmentSuitabilities = new Dictionary<Identifier, float>();
1007 
1008  foreach (var itemPrefab in ItemPrefab.Prefabs)
1009  {
1010  float suitability = itemPrefab.GetTreatmentSuitability(Identifier) + itemPrefab.GetTreatmentSuitability(AfflictionType);
1011  if (!MathUtils.NearlyEqual(suitability, 0.0f))
1012  {
1013  newTreatmentSuitabilities.TryAdd(itemPrefab.Identifier, suitability);
1014  }
1015  }
1016  HasTreatments = newTreatmentSuitabilities.Any(kvp => kvp.Value > 0);
1017  TreatmentSuitabilities = newTreatmentSuitabilities.ToImmutableDictionary();
1018  }
1019 
1020  public LocalizedString GetDescription(float strength, Description.TargetType targetType)
1021  {
1022  foreach (var description in Descriptions)
1023  {
1024  if (strength < description.MinStrength || strength > description.MaxStrength) { continue; }
1025  switch (targetType)
1026  {
1027  case Description.TargetType.Self:
1028  if (description.Target == Description.TargetType.OtherCharacter) { continue; }
1029  break;
1030  case Description.TargetType.OtherCharacter:
1031  if (description.Target == Description.TargetType.Self) { continue; }
1032  break;
1033  }
1034  return description.Text;
1035  }
1036  return defaultDescription;
1037  }
1038 
1043  {
1044  foreach (var prefab in Prefabs)
1045  {
1046  prefab.RefreshTreatmentSuitabilities();
1047  prefab.LoadEffects();
1048  }
1049  }
1050 
1055  public static void ClearAllEffects()
1056  {
1057  Prefabs.ForEach(p => p.ClearEffects());
1058  }
1059 
1060  private void LoadEffects()
1061  {
1062  ClearEffects();
1063  foreach (var subElement in configElement.Elements())
1064  {
1065  switch (subElement.Name.ToString().ToLowerInvariant())
1066  {
1067  case "effect":
1068  effects.Add(new Effect(subElement, Name.Value));
1069  break;
1070  case "periodiceffect":
1071  periodicEffects.Add(new PeriodicEffect(subElement, Name.Value));
1072  break;
1073  }
1074  }
1075  for (int i = 0; i < effects.Count; i++)
1076  {
1077  for (int j = i + 1; j < effects.Count; j++)
1078  {
1079  var a = effects[i];
1080  var b = effects[j];
1081  if (a.MinStrength < b.MaxStrength && b.MinStrength < a.MaxStrength)
1082  {
1083  DebugConsole.AddWarning($"Affliction \"{Identifier}\" contains effects with overlapping strength ranges. Only one effect can be active at a time, meaning one of the effects won't work.",
1084  ContentPackage);
1085  }
1086  }
1087  }
1088  }
1089 
1090  private void ClearEffects()
1091  {
1092  effects.Clear();
1093  periodicEffects.Clear();
1094  }
1095 
1096 #if CLIENT
1097  public void ReloadSoundsIfNeeded()
1098  {
1099  foreach (var effect in effects)
1100  {
1101  foreach (var statusEffect in effect.StatusEffects)
1102  {
1103  foreach (var sound in statusEffect.Sounds)
1104  {
1105  if (sound.Sound == null) { RoundSound.Reload(sound); }
1106  }
1107  }
1108  }
1109  foreach (var periodicEffect in periodicEffects)
1110  {
1111  foreach (var statusEffect in periodicEffect.StatusEffects)
1112  {
1113  foreach (var sound in statusEffect.Sounds)
1114  {
1115  if (sound.Sound == null) { RoundSound.Reload(sound); }
1116  }
1117  }
1118  }
1119  }
1120 #endif
1121 
1122  public override string ToString()
1123  {
1124  return $"AfflictionPrefab ({Name})";
1125  }
1126 
1127  public Affliction Instantiate(float strength, Character source = null)
1128  {
1129  object instance = null;
1130  try
1131  {
1132  instance = constructor.Invoke(new object[] { this, strength });
1133  }
1134  catch (Exception ex)
1135  {
1136  DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString());
1137  }
1138  Affliction affliction = instance as Affliction;
1139  affliction.Source = source;
1140  return affliction;
1141  }
1142 
1143  public Effect GetActiveEffect(float currentStrength)
1144  {
1145  foreach (Effect effect in effects)
1146  {
1147  if (currentStrength > effect.MinStrength && currentStrength <= effect.MaxStrength)
1148  {
1149  return effect;
1150  }
1151  }
1152 
1153  //if above the strength range of all effects, use the highest strength effect
1154  Effect strongestEffect = null;
1155  float largestStrength = currentStrength;
1156  foreach (Effect effect in effects)
1157  {
1158  if (currentStrength > effect.MaxStrength &&
1159  (strongestEffect == null || effect.MaxStrength > largestStrength))
1160  {
1161  strongestEffect = effect;
1162  largestStrength = effect.MaxStrength;
1163  }
1164  }
1165  return strongestEffect;
1166  }
1167 
1168  public float GetTreatmentSuitability(Item item)
1169  {
1170  if (item == null)
1171  {
1172  return 0.0f;
1173  }
1175  }
1176  }
1177 }
Character Source
Which character gave this affliction
Definition: Affliction.cs:88
The description element can be used to define descriptions for the affliction which are shown under s...
readonly float MaxStrength
Maximum strength required for the description to be shown.
Description(ContentXElement element, AfflictionPrefab affliction)
readonly float MinStrength
Minimum strength required for the description to be shown.
readonly TargetType Target
Who can see the description.
readonly Identifier TextTag
Text tag used to set the text from the localization files.
readonly LocalizedString Text
Raw text for the description.
Effects are the primary way to add functionality to afflictions.
readonly ImmutableArray< Identifier > BlockTransformation
Prevents AfflictionHusks with the specified identifier(s) from transforming the character into an AI-...
float GetStrengthFactor(Affliction affliction)
Returns 0 if affliction.Strength is MinStrength, 1 if affliction.Strength is MaxStrength
readonly ImmutableArray< StatusEffect > StatusEffects
readonly AbilityFlags AfflictionAbilityFlags
readonly ImmutableDictionary< StatTypes, AppliedStatValue > AfflictionStatValues
StatType that will be applied to the affected character when the effect is active that is proportiona...
float GetStrengthFactor(float strength)
Returns 0 if affliction.Strength is MinStrength, 1 if affliction.Strength is MaxStrength
Effect(ContentXElement element, string parentDebugName)
readonly ImmutableArray< Identifier > ResistanceFor
A list of identifiers of afflictions that the affected character will be resistant to when this effec...
readonly ImmutableArray< LimbType > ResistanceLimbs
List of limb types that the resistance applies to. If empty, the resistance applies to the whole body...
PeriodicEffect applies StatusEffects to the character periodically.
readonly List< StatusEffect > StatusEffects
PeriodicEffect(ContentXElement element, string parentDebugName)
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
readonly LocalizedString CauseOfDeathDescription
Affliction Instantiate(float strength, Character source=null)
IList< PeriodicEffect > PeriodicEffects
static readonly Identifier InvertControlsType
readonly bool AfflictionOverlayAlphaIsLinear
If set to true and the affliction has an AfflictionOverlay element, the overlay's opacity will be str...
static readonly Identifier BleedingType
static readonly Identifier SpaceHerpesType
readonly float BurnOverlayAlpha
Opacity of the burn effect (darker tint) on limbs affected by this affliction. 1 = full strength.
float KarmaChangeOnApplied
How much karma changes when a player applies this affliction to someone (per strength of the afflicti...
readonly bool ResetBetweenRounds
If set to true, this affliction will not persist between rounds.
static AfflictionPrefab OxygenLow
readonly bool IsBuff
If set to true, the game will recognize this affliction as a buff. This means, among other things,...
readonly Identifier AchievementOnReceived
ImmutableHashSet< Identifier > IgnoreTreatmentIfAfflictedBy
Bots will not try to treat the affliction if the character has any of these afflictions
static void ClearAllEffects()
Removes all the effects of the prefab (including the sounds and other assets defined in them)....
readonly Identifier TranslationIdentifier
Can be set to the identifier of another affliction to make this affliction reuse the same name and de...
readonly int BaseHealCost
The minimum cost of healing this affliction at the medical clinic.
static AfflictionPrefab InternalDamage
readonly Identifier AchievementOnRemoved
Steam achievement given when the affliction is removed from the controlled character.
static readonly Identifier DamageType
static void LoadAllEffectsAndTreatmentSuitabilities()
Should be called before each round: loads all StatusEffects and refreshes treatment suitabilities.
ImmutableDictionary< Identifier, float > TreatmentSuitabilities
static readonly Identifier BurnType
readonly float ShowIconToOthersThreshold
How high the strength has to be for the affliction icon to be shown to others with a health scanner o...
readonly float HealCostMultiplier
How much each unit of this affliction's strength will add to the cost of healing at the medical clini...
readonly bool HideIconAfterDelay
If set to true, this affliction's icon will be hidden from the HUD after 5 seconds.
IEnumerable< Effect > Effects
static IEnumerable< AfflictionPrefab > List
readonly bool AffectMachines
If set to true, this affliction can affect characters that are marked as machines,...
Identifier[] TargetSpecies
A list of species this affliction is allowed to affect.
readonly Color[] IconColors
A gradient that defines which color to render this affliction's icon with, based on the affliction's ...
readonly LimbType IndicatorLimb
If the affliction doesn't affect individual limbs, this attribute determines where the game will rend...
readonly float WeaponsSkillGain
An arbitrary modifier that affects how much weapons skill is increased when you apply the affliction ...
readonly bool ShowBarInHealthMenu
If set to false, the health UI will not show the strength of the affliction as a bar under its indica...
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 AfflictionPrefab ImpactDamage
Effect GetActiveEffect(float currentStrength)
float GetTreatmentSuitability(Item item)
static AfflictionPrefab OrganDamage
bool HasTreatments
Can this affliction be treated with some item?
readonly LocalizedString Name
static readonly Identifier ParalysisType
static readonly Identifier StunType
readonly float Duration
The duration of the affliction, in seconds. If set to 0, the affliction does not expire.
static readonly Identifier AlienInfectedType
readonly bool DamageParticles
Should damage particles be emitted when a character receives this affliction? Only relevant if the af...
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 ShowInHealthScannerThreshold
How high the strength has to be for the affliction icon to be shown with a health scanner
readonly float TreatmentThreshold
How strong the affliction needs to be before bots attempt to treat it. Also effects when the afflicti...
readonly ImmutableList< Description > Descriptions
static AfflictionPrefab RadiationSickness
readonly float DamageOverlayAlpha
Opacity of the bloody damage overlay on limbs affected by this affliction. 1 = full strength.
readonly float ActivationThreshold
How high the strength has to be for the affliction to take effect
AfflictionPrefab(ContentXElement element, AfflictionsFile file, Type type)
readonly float ShowIconThreshold
How high the strength has to be for the affliction icon to be shown in the UI
static AfflictionPrefab Pressure
readonly bool HealableInMedicalClinic
If set to true, this affliction can be healed at the medical clinic.
readonly Sprite Icon
An icon that’s used in the UI to represent this affliction.
static AfflictionPrefab BiteWounds
readonly float GrainBurst
The strength of the radiation grain effect to apply when the strength of this affliction increases.
static AfflictionPrefab Bloodloss
readonly float MedicalSkillGain
An arbitrary modifier that affects how much medical skill is increased when you apply the affliction ...
static readonly Identifier EMPType
static readonly Identifier DisguisedAsHuskType
LocalizedString GetDescription(float strength, Description.TargetType targetType)
static readonly Identifier PoisonType
static AfflictionPrefab HuskInfection
readonly Sprite AfflictionOverlay
A sprite that covers the affected player's entire screen when this affliction is active....
AfflictionPrefabHusk is a special type of affliction that has added functionality for husk infection.
readonly bool CauseSpeechImpediment
If set to true, affected characters will have their speech impeded once the affliction reaches the do...
readonly bool TransferBuffs
If set to true, all buffs are transferred to the converted character after husk transformation is com...
readonly bool NeedsAir
If set to false, affected characters will no longer require air once the affliction reaches the activ...
readonly float TransformThresholdOnDeath
The minimum strength the affliction must have for the affected character to transform into a husk upo...
readonly bool ControlHusk
If set to true, affected players will retain control of their character after transforming into a hus...
readonly bool SendMessages
If set to true, the affected player will see on-screen messages describing husk infection symptoms an...
readonly float DormantThreshold
The minimum strength at which husk infection will be in the dormant stage. It must be less than or eq...
readonly float TransitionThreshold
The minimum strength at which husk infection will be in its final stage. It must be greater than or e...
AfflictionPrefabHusk(ContentXElement element, AfflictionsFile file, Type type=null)
readonly Identifier HuskedSpeciesName
The species of husk to convert the affected character to once husk infection reaches its final stage.
readonly float ActiveThreshold
The minimum strength at which husk infection will be in the active stage. It must be greater than or ...
readonly float StabilizationMax
readonly float DamageSkillMultiplier
readonly float DamageSkillThreshold
readonly float ReviveChanceMax
static readonly PrefabSelector< CPRSettings > Prefabs
static CPRSettings Active
readonly float ReviveChancePerSkill
CPRSettings(XElement element, AfflictionsFile file)
readonly float StabilizationMin
override void Dispose()
readonly float ReviveChanceMin
readonly float StabilizationPerSkill
readonly float ReviveChanceExponent
AfflictionPrefab? InsufficientSkillAffliction
static readonly Identifier HumanSpeciesName
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
float GetAttributeFloat(string key, float def)
ContentPackage? ContentPackage
Color?[] GetAttributeColorArray(string key, Color[]? def)
ContentXElement? GetChildElement(string name)
bool GetAttributeBool(string key, bool def)
int GetAttributeInt(string key, int def)
XAttribute? GetAttribute(string name)
Identifier GetAttributeIdentifier(string key, string def)
static readonly PrefabCollection< ItemPrefab > Prefabs
float GetTreatmentSuitability(Identifier treatmentIdentifier)
LocalizedString Fallback(LocalizedString fallback, bool useDefaultLanguageIfFound=true)
Use this text instead if the original text cannot be found.
ContentPackage? ContentPackage
Definition: Prefab.cs:37
readonly Identifier Identifier
Definition: Prefab.cs:34
Prefab that has a property serves as a deterministic hash of a prefab's identifier....
static void Reload(RoundSound roundSound)
Definition: RoundSound.cs:125
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
static StatusEffect Load(ContentXElement element, string parentDebugName)
AbilityFlags
AbilityFlags are a set of toggleable flags that can be applied to characters.
Definition: Enums.cs:641
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
Definition: Enums.cs:195
StatType that will be applied to the affected character when the effect is active that is proportiona...
readonly float MinValue
Minimum value to apply
readonly StatTypes StatType
Which StatType to apply
readonly float MaxValue
Minimum value to apply