using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Barotrauma.Abilities { abstract class CharacterAbilityGroup { public CharacterTalent CharacterTalent { get; } public Character Character { get; } // currently only used to turn off simulation if random conditions are in use public bool IsActive { get; private set; } = true; public readonly AbilityEffectType AbilityEffectType; protected readonly int maxTriggerCount; protected int timesTriggered = 0; // add support for OR conditions? protected readonly List abilityConditions = new List(); /// /// List of abilities that are triggered by this group. /// Fallback abilities are triggered if the conditional fails /// protected readonly List characterAbilities = new List(), fallbackAbilities = new List(); public CharacterAbilityGroup(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, ContentXElement abilityElementGroup) { AbilityEffectType = abilityEffectType; CharacterTalent = characterTalent ?? throw new ArgumentNullException(nameof(characterTalent)); Character = CharacterTalent.Character; maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue); foreach (var subElement in abilityElementGroup.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "abilities": LoadAbilities(subElement); break; case "fallbackabilities": LoadFallbackAbilities(subElement); break; case "condition": case "conditions": LoadConditions(subElement); break; default: DebugConsole.ThrowError($"Error in talent {characterTalent.Prefab.Identifier}: unrecognized xml element \"{subElement.Name}\"."); break; } } switch (abilityEffectType) { case AbilityEffectType.OnDieToCharacter: if (characterAbilities.Any(a => a.RequiresAlive)) { 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.", characterTalent.Prefab.ContentPackage); } break; } } public void ActivateAbilityGroup(bool addingFirstTime) { if (!CheckActivatingCondition()) { return; } foreach (var characterAbility in characterAbilities) { characterAbility.InitializeAbility(addingFirstTime); } foreach (var characterAbility in fallbackAbilities) { characterAbility.InitializeAbility(addingFirstTime); } } private bool CheckActivatingCondition() { if (AbilityEffectType is not AbilityEffectType.None) { return true; } return !abilityConditions.Any(static abilityCondition => !abilityCondition.MatchesCondition()); } public void LoadConditions(ContentXElement conditionElements) { foreach (ContentXElement conditionElement in conditionElements.Elements()) { AbilityCondition newCondition = ConstructCondition(CharacterTalent, conditionElement); if (newCondition == null) { DebugConsole.ThrowError($"AbilityCondition was not found in talent {CharacterTalent.DebugIdentifier}!", contentPackage: conditionElement.ContentPackage); return; } if (!newCondition.AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { IsActive = false; } abilityConditions.Add(newCondition); } } public void AddAbility(CharacterAbility characterAbility) { if (characterAbility == null) { DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!", contentPackage: CharacterTalent.Prefab.ContentPackage); return; } characterAbilities.Add(characterAbility); } public void AddFallbackAbility(CharacterAbility characterAbility) { if (characterAbility == null) { DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!", contentPackage: CharacterTalent.Prefab.ContentPackage); return; } fallbackAbilities.Add(characterAbility); } // XML private AbilityCondition ConstructCondition(CharacterTalent characterTalent, ContentXElement conditionElement, bool errorMessages = true) { Type conditionType; string type = conditionElement.Name.ToString().ToLowerInvariant(); try { conditionType = ReflectionUtils.GetTypeWithBackwardsCompatibility("Barotrauma.Abilities", type, false, true); if (conditionType == null) { if (errorMessages) { DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")", contentPackage: characterTalent.Prefab.ContentPackage); } return null; } } catch (Exception e) { if (errorMessages) { DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")", e, contentPackage: characterTalent.Prefab.ContentPackage); } return null; } object[] args = { characterTalent, conditionElement }; AbilityCondition newCondition; try { newCondition = (AbilityCondition)Activator.CreateInstance(conditionType, args); } catch (TargetInvocationException e) { DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ".", e.InnerException, contentPackage: characterTalent.Prefab.ContentPackage); return null; } if (newCondition == null) { DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ", instance was null", contentPackage: characterTalent.Prefab.ContentPackage); return null; } return newCondition; } private void LoadAbilities(ContentXElement abilityElements) { foreach (var abilityElementGroup in abilityElements.Elements()) { AddAbility(ConstructAbility(abilityElementGroup, CharacterTalent)); } } private void LoadFallbackAbilities(ContentXElement abilityElements) { foreach (var abilityElementGroup in abilityElements.Elements()) { AddFallbackAbility(ConstructAbility(abilityElementGroup, CharacterTalent)); } } private CharacterAbility ConstructAbility(ContentXElement abilityElement, CharacterTalent characterTalent) { CharacterAbility newAbility = CharacterAbility.Load(abilityElement, this); if (newAbility == null) { DebugConsole.ThrowError($"Unable to create an ability for {characterTalent.DebugIdentifier}!", contentPackage: characterTalent.Prefab.ContentPackage); return null; } return newAbility; } public static List ParseStatusEffects(CharacterTalent characterTalent, ContentXElement statusEffectElements) { if (statusEffectElements == null) { DebugConsole.ThrowError("StatusEffect list was not found in talent " + characterTalent.DebugIdentifier, contentPackage: characterTalent.Prefab.ContentPackage); return null; } List statusEffects = new List(); foreach (var statusEffectElement in statusEffectElements.Elements()) { var statusEffect = StatusEffect.Load(statusEffectElement, characterTalent.DebugIdentifier); statusEffects.Add(statusEffect); } return statusEffects; } public static StatTypes ParseStatType(string statTypeString, string debugIdentifier) { //backwards compatibility if (statTypeString.Equals("MedicalItemDurationMultiplier", StringComparison.OrdinalIgnoreCase)) { statTypeString = "BuffItemApplyingMultiplier"; } if (!Enum.TryParse(statTypeString, true, out StatTypes statType)) { DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in CharacterTalent (" + debugIdentifier + ")"); } return statType; } public static List ParseAfflictions(CharacterTalent characterTalent, ContentXElement afflictionElements) { if (afflictionElements == null) { DebugConsole.ThrowError("Affliction list was not found in talent " + characterTalent.DebugIdentifier, contentPackage: characterTalent.Prefab.ContentPackage); return null; } List afflictions = new List(); // similar logic to affliction creation in statuseffects // might be worth unifying foreach (var afflictionElement in afflictionElements.Elements()) { Identifier afflictionIdentifier = afflictionElement.GetAttributeIdentifier("identifier", ""); AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier); if (afflictionPrefab == null) { DebugConsole.ThrowError("Error in CharacterTalent (" + characterTalent.DebugIdentifier + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found.", contentPackage: characterTalent.Prefab.ContentPackage); continue; } Affliction afflictionInstance = afflictionPrefab.Instantiate(afflictionElement.GetAttributeFloat(1.0f, "amount", "strength")); afflictionInstance.Probability = afflictionElement.GetAttributeFloat(1.0f, "probability"); afflictions.Add(afflictionInstance); } return afflictions; } public static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier) { if (!Enum.TryParse(flagTypeString, true, out AbilityFlags flagType)) { DebugConsole.ThrowError("Invalid flag type type \"" + flagTypeString + "\" in CharacterTalent (" + debugIdentifier + ")"); } return flagType; } } }