using System; using System.Reflection; namespace Barotrauma.Abilities { abstract class CharacterAbility { public CharacterAbilityGroup CharacterAbilityGroup { get; } public CharacterTalent CharacterTalent { get; } public Character Character { get; } public bool RequiresAlive { get; } public virtual bool AllowClientSimulation => false; public virtual bool AppliesEffectOnIntervalUpdate => false; private const float DefaultEffectTime = 1.0f; // currently resets if the character dies. would need to be stored in a dictionary of sorts to maintain through death /// /// Used primarily for StatusEffects. Default to constant outside interval abilities. /// protected float EffectDeltaTime => CharacterAbilityGroup is CharacterAbilityGroupInterval abilityGroupInterval ? abilityGroupInterval.TimeSinceLastUpdate : DefaultEffectTime; public CharacterAbility(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) { CharacterAbilityGroup = characterAbilityGroup ?? throw new ArgumentNullException(nameof(characterAbilityGroup)); CharacterTalent = characterAbilityGroup.CharacterTalent; Character = CharacterTalent.Character; RequiresAlive = abilityElement.GetAttributeBool("requiresalive", true); } public bool IsViable() { if (!AllowClientSimulation && GameMain.NetworkMember is { IsClient: true }) { return false; } if (RequiresAlive && Character.IsDead) { return false; } return true; } public virtual void InitializeAbility(bool addingFirstTime) { } public virtual void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate) { // may need a separate Update for changing state on non-interval-based abilities if (AppliesEffectOnIntervalUpdate) { if (conditionsMatched) { ApplyEffect(); } } else { VerifyState(conditionsMatched, timeSinceLastUpdate); } } protected virtual void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) { DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups.", contentPackage: CharacterTalent.Prefab.ContentPackage); } public void ApplyAbilityEffect(AbilityObject abilityObject) { if (abilityObject is null) { ApplyEffect(); } else { ApplyEffect(abilityObject); } } protected virtual void ApplyEffect() { DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect in talent {CharacterTalent.DebugIdentifier}", CharacterTalent.Prefab.ContentPackage); } protected virtual void ApplyEffect(AbilityObject abilityObject) { DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect in talent {CharacterTalent.DebugIdentifier}", CharacterTalent.Prefab.ContentPackage); } protected void LogAbilityObjectMismatch() { DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type in talent {CharacterTalent.DebugIdentifier}", contentPackage: CharacterTalent.Prefab.ContentPackage); } // XML public static CharacterAbility Load(ContentXElement abilityElement, CharacterAbilityGroup characterAbilityGroup, bool errorMessages = true) { Type abilityType; string type = abilityElement.Name.ToString().ToLowerInvariant(); try { abilityType = ReflectionUtils.GetTypeWithBackwardsCompatibility("Barotrauma.Abilities", type, false, true); if (abilityType == null) { if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")", contentPackage: abilityElement.ContentPackage); return null; } } catch (Exception e) { if (errorMessages) { DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")", e, contentPackage: abilityElement.ContentPackage); } return null; } object[] args = { characterAbilityGroup, abilityElement }; CharacterAbility characterAbility; try { characterAbility = (CharacterAbility)Activator.CreateInstance(abilityType, args); } catch (TargetInvocationException e) { DebugConsole.ThrowError("Error while creating an instance of a CharacterAbility of the type " + abilityType + ".", e.InnerException, contentPackage: abilityElement.ContentPackage); return null; } return characterAbility; } } }