Client LuaCsForBarotrauma
CharacterAbilityGroup.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Reflection;
5 
6 namespace Barotrauma.Abilities
7 {
8  abstract class CharacterAbilityGroup
9  {
11  public Character Character { get; }
12 
13  // currently only used to turn off simulation if random conditions are in use
14  public bool IsActive { get; private set; } = true;
15 
17 
18  protected readonly int maxTriggerCount;
19  protected int timesTriggered = 0;
20 
21  // add support for OR conditions?
22  protected readonly List<AbilityCondition> abilityConditions = new List<AbilityCondition>();
23 
28  protected readonly List<CharacterAbility> characterAbilities = new List<CharacterAbility>(),
29  fallbackAbilities = new List<CharacterAbility>();
30 
31  public CharacterAbilityGroup(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, ContentXElement abilityElementGroup)
32  {
33  AbilityEffectType = abilityEffectType;
34  CharacterTalent = characterTalent ?? throw new ArgumentNullException(nameof(characterTalent));
36  maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue);
37  foreach (var subElement in abilityElementGroup.Elements())
38  {
39  switch (subElement.Name.ToString().ToLowerInvariant())
40  {
41  case "abilities":
42  LoadAbilities(subElement);
43  break;
44  case "fallbackabilities":
45  LoadFallbackAbilities(subElement);
46  break;
47  case "condition":
48  case "conditions":
49  LoadConditions(subElement);
50  break;
51  default:
52  DebugConsole.ThrowError($"Error in talent {characterTalent.Prefab.Identifier}: unrecognized xml element \"{subElement.Name}\".");
53  break;
54  }
55  }
56 
57  switch (abilityEffectType)
58  {
59  case AbilityEffectType.OnDieToCharacter:
60  if (characterAbilities.Any(a => a.RequiresAlive))
61  {
62  DebugConsole.AddWarning($"Potential error in talent {characterTalent}: an ability group has the type {AbilityEffectType.OnDieToCharacter}, but includes abilities that require the character to be alive, meaning they will never execute.",
63  characterTalent.Prefab.ContentPackage);
64  }
65  break;
66  }
67  }
68 
69  public void ActivateAbilityGroup(bool addingFirstTime)
70  {
71  if (!CheckActivatingCondition()) { return; }
72 
73  foreach (var characterAbility in characterAbilities)
74  {
75  characterAbility.InitializeAbility(addingFirstTime);
76  }
77 
78  foreach (var characterAbility in fallbackAbilities)
79  {
80  characterAbility.InitializeAbility(addingFirstTime);
81  }
82  }
83 
84  private bool CheckActivatingCondition()
85  {
86  if (AbilityEffectType is not AbilityEffectType.None) { return true; }
87  return !abilityConditions.Any(static abilityCondition => !abilityCondition.MatchesCondition());
88  }
89 
90  public void LoadConditions(ContentXElement conditionElements)
91  {
92  foreach (ContentXElement conditionElement in conditionElements.Elements())
93  {
94  AbilityCondition newCondition = ConstructCondition(CharacterTalent, conditionElement);
95 
96  if (newCondition == null)
97  {
98  DebugConsole.ThrowError($"AbilityCondition was not found in talent {CharacterTalent.DebugIdentifier}!",
99  contentPackage: conditionElement.ContentPackage);
100  return;
101  }
102 
103  if (!newCondition.AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
104  {
105  IsActive = false;
106  }
107 
108  abilityConditions.Add(newCondition);
109  }
110  }
111 
112  public void AddAbility(CharacterAbility characterAbility)
113  {
114  if (characterAbility == null)
115  {
116  DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!",
117  contentPackage: CharacterTalent.Prefab.ContentPackage);
118  return;
119  }
120 
121  characterAbilities.Add(characterAbility);
122  }
123 
124  public void AddFallbackAbility(CharacterAbility characterAbility)
125  {
126  if (characterAbility == null)
127  {
128  DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!",
129  contentPackage: CharacterTalent.Prefab.ContentPackage);
130  return;
131  }
132 
133  fallbackAbilities.Add(characterAbility);
134  }
135 
136  // XML
137  private AbilityCondition ConstructCondition(CharacterTalent characterTalent, ContentXElement conditionElement, bool errorMessages = true)
138  {
139  Type conditionType;
140  string type = conditionElement.Name.ToString().ToLowerInvariant();
141  try
142  {
143  conditionType = ReflectionUtils.GetTypeWithBackwardsCompatibility("Barotrauma.Abilities", type, false, true);
144  if (conditionType == null)
145  {
146  if (errorMessages)
147  {
148  DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")",
149  contentPackage: characterTalent.Prefab.ContentPackage);
150  }
151  return null;
152  }
153  }
154  catch (Exception e)
155  {
156  if (errorMessages)
157  {
158  DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")", e,
159  contentPackage: characterTalent.Prefab.ContentPackage);
160  }
161  return null;
162  }
163 
164  object[] args = { characterTalent, conditionElement };
165 
166  AbilityCondition newCondition;
167  try
168  {
169  newCondition = (AbilityCondition)Activator.CreateInstance(conditionType, args);
170  }
171  catch (TargetInvocationException e)
172  {
173  DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ".", e.InnerException,
174  contentPackage: characterTalent.Prefab.ContentPackage);
175  return null;
176  }
177 
178  if (newCondition == null)
179  {
180  DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ", instance was null",
181  contentPackage: characterTalent.Prefab.ContentPackage);
182  return null;
183  }
184 
185  return newCondition;
186  }
187 
188  private void LoadAbilities(ContentXElement abilityElements)
189  {
190  foreach (var abilityElementGroup in abilityElements.Elements())
191  {
192  AddAbility(ConstructAbility(abilityElementGroup, CharacterTalent));
193  }
194  }
195 
196  private void LoadFallbackAbilities(ContentXElement abilityElements)
197  {
198  foreach (var abilityElementGroup in abilityElements.Elements())
199  {
200  AddFallbackAbility(ConstructAbility(abilityElementGroup, CharacterTalent));
201  }
202  }
203 
204  private CharacterAbility ConstructAbility(ContentXElement abilityElement, CharacterTalent characterTalent)
205  {
206  CharacterAbility newAbility = CharacterAbility.Load(abilityElement, this);
207 
208  if (newAbility == null)
209  {
210  DebugConsole.ThrowError($"Unable to create an ability for {characterTalent.DebugIdentifier}!",
211  contentPackage: characterTalent.Prefab.ContentPackage);
212  return null;
213  }
214 
215  return newAbility;
216  }
217 
218  public static List<StatusEffect> ParseStatusEffects(CharacterTalent characterTalent, ContentXElement statusEffectElements)
219  {
220  if (statusEffectElements == null)
221  {
222  DebugConsole.ThrowError("StatusEffect list was not found in talent " + characterTalent.DebugIdentifier,
223  contentPackage: characterTalent.Prefab.ContentPackage);
224  return null;
225  }
226 
227  List<StatusEffect> statusEffects = new List<StatusEffect>();
228 
229  foreach (var statusEffectElement in statusEffectElements.Elements())
230  {
231  var statusEffect = StatusEffect.Load(statusEffectElement, characterTalent.DebugIdentifier);
232  statusEffects.Add(statusEffect);
233  }
234 
235  return statusEffects;
236  }
237 
238  public static StatTypes ParseStatType(string statTypeString, string debugIdentifier)
239  {
240  //backwards compatibility
241  if (statTypeString.Equals("MedicalItemDurationMultiplier", StringComparison.OrdinalIgnoreCase))
242  {
243  statTypeString = "BuffItemApplyingMultiplier";
244  }
245  if (!Enum.TryParse(statTypeString, true, out StatTypes statType))
246  {
247  DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
248  }
249  return statType;
250  }
251 
252  public static List<Affliction> ParseAfflictions(CharacterTalent characterTalent, ContentXElement afflictionElements)
253  {
254  if (afflictionElements == null)
255  {
256  DebugConsole.ThrowError("Affliction list was not found in talent " + characterTalent.DebugIdentifier,
257  contentPackage: characterTalent.Prefab.ContentPackage);
258  return null;
259  }
260 
261  List<Affliction> afflictions = new List<Affliction>();
262 
263  // similar logic to affliction creation in statuseffects
264  // might be worth unifying
265 
266  foreach (var afflictionElement in afflictionElements.Elements())
267  {
268  Identifier afflictionIdentifier = afflictionElement.GetAttributeIdentifier("identifier", "");
269  AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier);
270  if (afflictionPrefab == null)
271  {
272  DebugConsole.ThrowError("Error in CharacterTalent (" + characterTalent.DebugIdentifier + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found.",
273  contentPackage: characterTalent.Prefab.ContentPackage);
274  continue;
275  }
276 
277  Affliction afflictionInstance = afflictionPrefab.Instantiate(afflictionElement.GetAttributeFloat(1.0f, "amount", "strength"));
278  afflictionInstance.Probability = afflictionElement.GetAttributeFloat(1.0f, "probability");
279  afflictions.Add(afflictionInstance);
280  }
281 
282  return afflictions;
283  }
284 
285  public static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier)
286  {
287  if (!Enum.TryParse(flagTypeString, true, out AbilityFlags flagType))
288  {
289  DebugConsole.ThrowError("Invalid flag type type \"" + flagTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
290  }
291  return flagType;
292  }
293  }
294 }
CharacterAbilityGroup(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, ContentXElement abilityElementGroup)
void LoadConditions(ContentXElement conditionElements)
void AddAbility(CharacterAbility characterAbility)
readonly List< AbilityCondition > abilityConditions
static StatTypes ParseStatType(string statTypeString, string debugIdentifier)
static List< Affliction > ParseAfflictions(CharacterTalent characterTalent, ContentXElement afflictionElements)
readonly List< CharacterAbility > characterAbilities
List of abilities that are triggered by this group. Fallback abilities are triggered if the condition...
static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier)
void AddFallbackAbility(CharacterAbility characterAbility)
static List< StatusEffect > ParseStatusEffects(CharacterTalent characterTalent, ContentXElement statusEffectElements)
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
Affliction Instantiate(float strength, Character source=null)
static IEnumerable< AfflictionPrefab > List
readonly TalentPrefab Prefab
ContentPackage? ContentPackage
IEnumerable< ContentXElement > Elements()
int GetAttributeInt(string key, int def)
static NetworkMember NetworkMember
Definition: GameMain.cs:190
ContentPackage? ContentPackage
Definition: Prefab.cs:37
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
AbilityEffectType
Definition: Enums.cs:125
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
Definition: Enums.cs:180