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 
332  [Serialize(0.0f, IsPropertySaveable.No,
333  description: "The amount of resistance to the afflictions specified by ResistanceFor to apply at this effect's lowest strength.")]
334  public float MinResistance { get; private set; }
335 
336  [Serialize(0.0f, IsPropertySaveable.No,
337  description: "The amount of resistance to the afflictions specified by ResistanceFor to apply at this effect's highest strength.")]
338  public float MaxResistance { get; private set; }
339 
340  [Serialize("", IsPropertySaveable.No, description: "Identifier used by AI to determine conversation lines to say when this effect is active.")]
341  public Identifier DialogFlag { get; private set; }
342 
343  [Serialize("", IsPropertySaveable.No, description: "Tag that enemy AI may use to target the affected character when this effect is active.")]
344  public Identifier Tag { get; private set; }
345 
346  [Serialize("0,0,0,0", IsPropertySaveable.No,
347  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.")]
348  public Color MinFaceTint { get; private set; }
349 
350  [Serialize("0,0,0,0", IsPropertySaveable.No,
351  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.")]
352  public Color MaxFaceTint { get; private set; }
353 
354  [Serialize("0,0,0,0", IsPropertySaveable.No,
355  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.")]
356  public Color MinBodyTint { get; private set; }
357 
358  [Serialize("0,0,0,0", IsPropertySaveable.No,
359  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.")]
360  public Color MaxBodyTint { get; private set; }
361 
375  public readonly struct AppliedStatValue
376  {
380  public readonly StatTypes StatType;
381 
385  public readonly float MinValue;
386 
390  public readonly float MaxValue;
391 
395  private readonly float Value;
396 
398  {
399  Value = element.GetAttributeFloat("value", 0.0f);
400  StatType = element.GetAttributeEnum("stattype", StatTypes.None);
401  MinValue = element.GetAttributeFloat("minvalue", Value);
402  MaxValue = element.GetAttributeFloat("maxvalue", Value);
403  }
404  }
405 
409  public readonly ImmutableArray<Identifier> BlockTransformation;
410 
414  public readonly ImmutableDictionary<StatTypes, AppliedStatValue> AfflictionStatValues;
415 
417 
418  //statuseffects applied on the character when the affliction is active
419  public readonly ImmutableArray<StatusEffect> StatusEffects;
420 
421  public Effect(ContentXElement element, string parentDebugName)
422  {
424 
425  ResistanceFor = element.GetAttributeIdentifierArray("resistancefor", Array.Empty<Identifier>())!.ToImmutableArray();
426  BlockTransformation = element.GetAttributeIdentifierArray("blocktransformation", Array.Empty<Identifier>())!.ToImmutableArray();
427 
428  var afflictionStatValues = new Dictionary<StatTypes, AppliedStatValue>();
429  var statusEffects = new List<StatusEffect>();
430  foreach (var subElement in element.Elements())
431  {
432  switch (subElement.Name.ToString().ToLowerInvariant())
433  {
434  case "statuseffect":
435  statusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
436  break;
437  case "statvalue":
438  var newStatValue = new AppliedStatValue(subElement);
439  if (newStatValue.StatType == StatTypes.None || !afflictionStatValues.TryAdd(newStatValue.StatType, newStatValue))
440  {
441  DebugConsole.ThrowError($"Invalid stat value in the affliction \"{parentDebugName}\".", contentPackage: element.ContentPackage);
442  }
443  break;
444  case "abilityflag":
445  AbilityFlags flagType = subElement.GetAttributeEnum("flagtype", AbilityFlags.None);
446  if (flagType is AbilityFlags.None)
447  {
448  DebugConsole.ThrowError($"Error in affliction \"{parentDebugName}\" - invalid ability flag type \"{subElement.GetAttributeString("flagtype", "")}\".",
449  contentPackage: element.ContentPackage);
450  continue;
451  }
452  AfflictionAbilityFlags |= flagType;
453  break;
454  case "affliction":
455  DebugConsole.AddWarning($"Error in affliction \"{parentDebugName}\" - additional afflictions caused by the affliction should be configured inside status effects.",
456  contentPackage: element.ContentPackage);
457  break;
458  }
459  }
460  AfflictionStatValues = afflictionStatValues.ToImmutableDictionary();
461  StatusEffects = statusEffects.ToImmutableArray();
462  }
463 
468  public float GetStrengthFactor(Affliction affliction) => GetStrengthFactor(affliction.Strength);
469 
474  public float GetStrengthFactor(float strength)
475  => MathUtils.InverseLerp(
476  MinStrength,
477  MaxStrength,
478  strength);
479  }
480 
490  public sealed class Description
491  {
492  public enum TargetType
493  {
497  Any,
501  Self,
505  OtherCharacter
506  }
507 
511  public readonly LocalizedString Text;
512 
516  public readonly Identifier TextTag;
517 
521  public readonly float MinStrength;
522 
526  public readonly float MaxStrength;
527 
531  public readonly TargetType Target;
532 
533  public Description(ContentXElement element, AfflictionPrefab affliction)
534  {
535  TextTag = element.GetAttributeIdentifier("textidentifier", Identifier.Empty);
536  if (!TextTag.IsEmpty)
537  {
538  Text = TextManager.Get(TextTag);
539  }
540  string text = element.GetAttributeString("text", string.Empty);
541  if (!text.IsNullOrEmpty())
542  {
543  Text = Text?.Fallback(text) ?? text;
544  }
545  else if (TextTag.IsEmpty)
546  {
547  DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - no text defined for one of the descriptions.",
548  contentPackage: element.ContentPackage);
549  }
550 
551  MinStrength = element.GetAttributeFloat(nameof(MinStrength), 0.0f);
552  MaxStrength = element.GetAttributeFloat(nameof(MaxStrength), 100.0f);
553  if (MinStrength >= MaxStrength)
554  {
555  DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - max strength is not larger than min.",
556  contentPackage: element.ContentPackage);
557  }
558  Target = element.GetAttributeEnum(nameof(Target), TargetType.Any);
559  }
560  }
561 
578  public sealed class PeriodicEffect
579  {
580  public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
581  public readonly float MinInterval, MaxInterval;
582  public readonly float MinStrength, MaxStrength;
583 
584  public PeriodicEffect(ContentXElement element, string parentDebugName)
585  {
586  foreach (var subElement in element.Elements())
587  {
588  StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
589  }
590 
591  if (element.GetAttribute("interval") != null)
592  {
593  MinInterval = MaxInterval = Math.Max(element.GetAttributeFloat("interval", 1.0f), 1.0f);
594  }
595  else
596  {
597  MinInterval = Math.Max(element.GetAttributeFloat(nameof(MinInterval), 1.0f), 1.0f);
598  MaxInterval = Math.Max(element.GetAttributeFloat(nameof(MaxInterval), 1.0f), MinInterval);
599  MinStrength = Math.Max(element.GetAttributeFloat(nameof(MinStrength), 0f), 0f);
600  MaxStrength = Math.Max(element.GetAttributeFloat(nameof(MaxStrength), MinStrength), MinStrength);
601  }
602  }
603  }
604 
605  public static readonly Identifier DamageType = "damage".ToIdentifier();
606  public static readonly Identifier BurnType = "burn".ToIdentifier();
607  public static readonly Identifier BleedingType = "bleeding".ToIdentifier();
608  public static readonly Identifier ParalysisType = "paralysis".ToIdentifier();
609  public static readonly Identifier PoisonType = "poison".ToIdentifier();
610  public static readonly Identifier StunType = "stun".ToIdentifier();
611  public static readonly Identifier EMPType = "emp".ToIdentifier();
612  public static readonly Identifier SpaceHerpesType = "spaceherpes".ToIdentifier();
613  public static readonly Identifier AlienInfectedType = "alieninfected".ToIdentifier();
614  public static readonly Identifier InvertControlsType = "invertcontrols".ToIdentifier();
615 
616  public static AfflictionPrefab InternalDamage => Prefabs["internaldamage"];
617  public static AfflictionPrefab BiteWounds => Prefabs["bitewounds"];
618  public static AfflictionPrefab ImpactDamage => Prefabs["blunttrauma"];
621  public static AfflictionPrefab OxygenLow => Prefabs["oxygenlow"];
622  public static AfflictionPrefab Bloodloss => Prefabs["bloodloss"];
623  public static AfflictionPrefab Pressure => Prefabs["pressure"];
624  public static AfflictionPrefab OrganDamage => Prefabs["organdamage"];
626  public static AfflictionPrefab RadiationSickness => Prefabs["radiationsickness"];
627 
628 
630 
631  public static IEnumerable<AfflictionPrefab> List => Prefabs;
632 
633  public override void Dispose() { }
634 
635  private readonly ContentXElement configElement;
636 
637  public readonly LocalizedString Name;
638 
639  public readonly LocalizedString CauseOfDeathDescription, SelfCauseOfDeathDescription;
640 
641  private readonly LocalizedString defaultDescription;
642  public readonly ImmutableList<Description> Descriptions;
643 
647  public readonly Identifier AfflictionType;
648 
652  public readonly bool LimbSpecific;
653 
662  public readonly LimbType IndicatorLimb;
663 
669 
675  public readonly bool IsBuff;
676 
681  public readonly bool AffectMachines;
682 
691  public readonly bool HealableInMedicalClinic;
692 
697  public readonly float HealCostMultiplier;
698 
702  public readonly int BaseHealCost;
703 
708  public readonly bool ShowBarInHealthMenu;
709 
713  public readonly bool HideIconAfterDelay;
714 
718  public readonly float ActivationThreshold = 0.0f;
719 
723  public readonly float ShowIconThreshold = 0.05f;
724 
728  public readonly float ShowIconToOthersThreshold = 0.05f;
729 
733  public readonly float MaxStrength = 100.0f;
734 
738  public readonly float GrainBurst;
739 
743  public readonly float ShowInHealthScannerThreshold;
744 
749  public readonly float TreatmentThreshold;
750 
754  public ImmutableHashSet<Identifier> IgnoreTreatmentIfAfflictedBy;
755 
759  public readonly float Duration;
760 
764  public float KarmaChangeOnApplied;
765 
769  public readonly float BurnOverlayAlpha;
770 
774  public readonly float DamageOverlayAlpha;
775 
779 
784 
789  public readonly Color[] IconColors;
790 
795  public readonly bool AfflictionOverlayAlphaIsLinear;
796 
800  public readonly bool ResetBetweenRounds;
801 
806  public readonly bool DamageParticles;
807 
813  public readonly float MedicalSkillGain;
814 
819  public readonly float WeaponsSkillGain;
820 
824  public Identifier[] TargetSpecies { get; protected set; }
825 
830  private readonly List<Effect> effects = new List<Effect>();
831 
835  private readonly List<PeriodicEffect> periodicEffects = new List<PeriodicEffect>();
836 
837  public IEnumerable<Effect> Effects => effects;
838 
839  public IList<PeriodicEffect> PeriodicEffects => periodicEffects;
840 
841  private readonly ConstructorInfo constructor;
842 
846  public readonly Sprite Icon;
847 
852  public readonly Sprite AfflictionOverlay;
853 
854  public ImmutableDictionary<Identifier, float> TreatmentSuitabilities
855  {
856  get;
857  private set;
858  } = new Dictionary<Identifier, float>().ToImmutableDictionary();
859 
863  public bool HasTreatments { get; private set; }
864 
865  public AfflictionPrefab(ContentXElement element, AfflictionsFile file, Type type) : base(file, element.GetAttributeIdentifier("identifier", ""))
866  {
867  configElement = element;
868 
869  AfflictionType = element.GetAttributeIdentifier("type", "");
870  TranslationIdentifier = element.GetAttributeIdentifier("translationoverride", Identifier);
871  Name = TextManager.Get($"AfflictionName.{TranslationIdentifier}");
872  string fallbackName = element.GetAttributeString("name", "");
873  if (!string.IsNullOrEmpty(fallbackName))
874  {
875  Name = Name.Fallback(fallbackName);
876  }
877  defaultDescription = TextManager.Get($"AfflictionDescription.{TranslationIdentifier}");
878  string fallbackDescription = element.GetAttributeString("description", "");
879  if (!string.IsNullOrEmpty(fallbackDescription))
880  {
881  defaultDescription = defaultDescription.Fallback(fallbackDescription);
882  }
883  IsBuff = element.GetAttributeBool(nameof(IsBuff), false);
884  AffectMachines = element.GetAttributeBool(nameof(AffectMachines), true);
885 
886  ShowBarInHealthMenu = element.GetAttributeBool("showbarinhealthmenu", true);
887 
888  HealableInMedicalClinic = element.GetAttributeBool("healableinmedicalclinic",
889  !IsBuff &&
890  AfflictionType != "geneticmaterialbuff" &&
891  AfflictionType != "geneticmaterialdebuff");
893  BaseHealCost = element.GetAttributeInt(nameof(BaseHealCost), 0);
894 
895  IgnoreTreatmentIfAfflictedBy = element.GetAttributeIdentifierArray(nameof(IgnoreTreatmentIfAfflictedBy), Array.Empty<Identifier>()).ToImmutableHashSet();
896 
897  Duration = element.GetAttributeFloat(nameof(Duration), 0.0f);
898 
899  if (element.GetAttribute("nameidentifier") != null)
900  {
901  Name = TextManager.Get(element.GetAttributeString("nameidentifier", string.Empty)).Fallback(Name);
902  }
903 
904  LimbSpecific = element.GetAttributeBool("limbspecific", false);
905  if (!LimbSpecific)
906  {
907  string indicatorLimbName = element.GetAttributeString("indicatorlimb", "Torso");
908  if (!Enum.TryParse(indicatorLimbName, out IndicatorLimb))
909  {
910  DebugConsole.ThrowErrorLocalized("Error in affliction prefab " + Name + " - limb type \"" + indicatorLimbName + "\" not found.");
911  }
912  }
913 
914  HideIconAfterDelay = element.GetAttributeBool(nameof(HideIconAfterDelay), false);
915 
917  ShowIconThreshold = element.GetAttributeFloat(nameof(ShowIconThreshold), Math.Max(ActivationThreshold, 0.05f));
919  MaxStrength = element.GetAttributeFloat(nameof(MaxStrength), 100.0f);
920  GrainBurst = element.GetAttributeFloat(nameof(GrainBurst), 0.0f);
921 
923  Math.Max(ActivationThreshold, AfflictionType == "talentbuff" ? float.MaxValue : ShowIconToOthersThreshold));
924  TreatmentThreshold = element.GetAttributeFloat(nameof(TreatmentThreshold), Math.Max(ActivationThreshold, 10.0f));
925 
927  BurnOverlayAlpha = element.GetAttributeFloat(nameof(BurnOverlayAlpha), 0.0f);
928 
930 
932  TextManager.Get($"AfflictionCauseOfDeath.{TranslationIdentifier}")
933  .Fallback(TextManager.Get(element.GetAttributeString("causeofdeathdescription", "")))
934  .Fallback(element.GetAttributeString("causeofdeathdescription", ""));
935  SelfCauseOfDeathDescription =
936  TextManager.Get($"AfflictionCauseOfDeathSelf.{TranslationIdentifier}")
937  .Fallback(TextManager.Get(element.GetAttributeString("selfcauseofdeathdescription", "")))
938  .Fallback(element.GetAttributeString("selfcauseofdeathdescription", ""));
939 
940  IconColors = element.GetAttributeColorArray(nameof(IconColors), null);
944 
945  TargetSpecies = element.GetAttributeIdentifierArray("targets", Array.Empty<Identifier>(), trim: true);
946 
947  ResetBetweenRounds = element.GetAttributeBool("resetbetweenrounds", false);
948 
949  DamageParticles = element.GetAttributeBool(nameof(DamageParticles), true);
950  WeaponsSkillGain = element.GetAttributeFloat(nameof(WeaponsSkillGain), 0.0f);
951  MedicalSkillGain = element.GetAttributeFloat(nameof(MedicalSkillGain), 0.0f);
952 
953  List<Description> descriptions = new List<Description>();
954  foreach (var subElement in element.Elements())
955  {
956  switch (subElement.Name.ToString().ToLowerInvariant())
957  {
958  case "icon":
959  Icon = new Sprite(subElement);
960  break;
961  case "afflictionoverlay":
962  AfflictionOverlay = new Sprite(subElement);
963  break;
964  case "statvalue":
965  DebugConsole.ThrowError($"Error in affliction \"{Identifier}\" - stat values should be configured inside the affliction's effects.",
966  contentPackage: element.ContentPackage);
967  break;
968  case "effect":
969  case "periodiceffect":
970  break;
971  case "description":
972  descriptions.Add(new Description(subElement, this));
973  break;
974  default:
975  DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})",
976  contentPackage: element.ContentPackage);
977  break;
978  }
979  }
980  Descriptions = descriptions.ToImmutableList();
981 
982  constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) });
983  }
984 
985  private void RefreshTreatmentSuitabilities()
986  {
987  var newTreatmentSuitabilities = new Dictionary<Identifier, float>();
988 
989  foreach (var itemPrefab in ItemPrefab.Prefabs)
990  {
991  float suitability = itemPrefab.GetTreatmentSuitability(Identifier) + itemPrefab.GetTreatmentSuitability(AfflictionType);
992  if (!MathUtils.NearlyEqual(suitability, 0.0f))
993  {
994  newTreatmentSuitabilities.TryAdd(itemPrefab.Identifier, suitability);
995  }
996  }
997  HasTreatments = newTreatmentSuitabilities.Any(kvp => kvp.Value > 0);
998  TreatmentSuitabilities = newTreatmentSuitabilities.ToImmutableDictionary();
999  }
1000 
1001  public LocalizedString GetDescription(float strength, Description.TargetType targetType)
1002  {
1003  foreach (var description in Descriptions)
1004  {
1005  if (strength < description.MinStrength || strength > description.MaxStrength) { continue; }
1006  switch (targetType)
1007  {
1008  case Description.TargetType.Self:
1009  if (description.Target == Description.TargetType.OtherCharacter) { continue; }
1010  break;
1011  case Description.TargetType.OtherCharacter:
1012  if (description.Target == Description.TargetType.Self) { continue; }
1013  break;
1014  }
1015  return description.Text;
1016  }
1017  return defaultDescription;
1018  }
1019 
1024  {
1025  foreach (var prefab in Prefabs)
1026  {
1027  prefab.RefreshTreatmentSuitabilities();
1028  prefab.LoadEffects();
1029  }
1030  }
1031 
1036  public static void ClearAllEffects()
1037  {
1038  Prefabs.ForEach(p => p.ClearEffects());
1039  }
1040 
1041  private void LoadEffects()
1042  {
1043  ClearEffects();
1044  foreach (var subElement in configElement.Elements())
1045  {
1046  switch (subElement.Name.ToString().ToLowerInvariant())
1047  {
1048  case "effect":
1049  effects.Add(new Effect(subElement, Name.Value));
1050  break;
1051  case "periodiceffect":
1052  periodicEffects.Add(new PeriodicEffect(subElement, Name.Value));
1053  break;
1054  }
1055  }
1056  for (int i = 0; i < effects.Count; i++)
1057  {
1058  for (int j = i + 1; j < effects.Count; j++)
1059  {
1060  var a = effects[i];
1061  var b = effects[j];
1062  if (a.MinStrength < b.MaxStrength && b.MinStrength < a.MaxStrength)
1063  {
1064  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.",
1065  ContentPackage);
1066  }
1067  }
1068  }
1069  }
1070 
1071  private void ClearEffects()
1072  {
1073  effects.Clear();
1074  periodicEffects.Clear();
1075  }
1076 
1077 #if CLIENT
1078  public void ReloadSoundsIfNeeded()
1079  {
1080  foreach (var effect in effects)
1081  {
1082  foreach (var statusEffect in effect.StatusEffects)
1083  {
1084  foreach (var sound in statusEffect.Sounds)
1085  {
1086  if (sound.Sound == null) { RoundSound.Reload(sound); }
1087  }
1088  }
1089  }
1090  foreach (var periodicEffect in periodicEffects)
1091  {
1092  foreach (var statusEffect in periodicEffect.StatusEffects)
1093  {
1094  foreach (var sound in statusEffect.Sounds)
1095  {
1096  if (sound.Sound == null) { RoundSound.Reload(sound); }
1097  }
1098  }
1099  }
1100  }
1101 #endif
1102 
1103  public override string ToString()
1104  {
1105  return $"AfflictionPrefab ({Name})";
1106  }
1107 
1108  public Affliction Instantiate(float strength, Character source = null)
1109  {
1110  object instance = null;
1111  try
1112  {
1113  instance = constructor.Invoke(new object[] { this, strength });
1114  }
1115  catch (Exception ex)
1116  {
1117  DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString());
1118  }
1119  Affliction affliction = instance as Affliction;
1120  affliction.Source = source;
1121  return affliction;
1122  }
1123 
1124  public Effect GetActiveEffect(float currentStrength)
1125  {
1126  foreach (Effect effect in effects)
1127  {
1128  if (currentStrength > effect.MinStrength && currentStrength <= effect.MaxStrength)
1129  {
1130  return effect;
1131  }
1132  }
1133 
1134  //if above the strength range of all effects, use the highest strength effect
1135  Effect strongestEffect = null;
1136  float largestStrength = currentStrength;
1137  foreach (Effect effect in effects)
1138  {
1139  if (currentStrength > effect.MaxStrength &&
1140  (strongestEffect == null || effect.MaxStrength > largestStrength))
1141  {
1142  strongestEffect = effect;
1143  largestStrength = effect.MaxStrength;
1144  }
1145  }
1146  return strongestEffect;
1147  }
1148 
1149  public float GetTreatmentSuitability(Item item)
1150  {
1151  if (item == null)
1152  {
1153  return 0.0f;
1154  }
1156  }
1157  }
1158 }
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...
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
LocalizedString GetDescription(float strength, Description.TargetType targetType)
static readonly Identifier PoisonType
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:118
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:615
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
Definition: Enums.cs:180
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