using System.Diagnostics.CodeAnalysis;
using PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.Models.Serialized;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static;
using PkmnLib.Static.Species;
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.Models;
///
/// A Pokémon taking part in a battle. This wraps an and holds all state that is only
/// relevant for the duration of a single battle. The wrapper is ephemeral: it is created when the battle is
/// created, and simply dropped when the battle ends, taking all battle-only state with it. Persistent data is
/// proxied to the underlying .
///
public interface IBattlePokemon : IPokemon
{
///
/// The persistent Pokémon this battle Pokémon wraps.
///
IPokemon UnderlyingPokemon { get; }
///
/// The battle the Pokémon is in.
///
IBattle Battle { get; }
///
/// The index of the side of the Pokémon.
///
byte SideIndex { get; }
///
/// The index of the position of the Pokémon on the field.
///
byte Position { get; }
///
/// The side the Pokémon is on.
///
IBattleSide BattleSide { get; }
///
/// Whether the Pokémon is on the battlefield.
///
bool IsOnBattlefield { get; }
///
/// A list of opponents the Pokémon has seen this battle.
///
IReadOnlyList SeenOpponents { get; }
///
/// Adds an opponent to the list of seen opponents.
///
void MarkOpponentAsSeen(IBattlePokemon opponent);
///
/// A list of items the Pokémon has consumed this battle.
///
IReadOnlyList ConsumedItems { get; }
///
/// Marks an item as consumed.
///
void MarkItemAsConsumed(IItem item);
///
/// The turn the Pokémon last switched in.
///
uint SwitchInTurn { get; }
///
/// The number of turns the Pokémon has been on the field.
///
uint TurnsOnField { get; }
///
/// The species of the Pokémon at the time the battle started.
///
ISpecies OriginalSpecies { get; }
///
/// The form of the Pokémon at the time the battle started.
///
IForm OriginalForm { get; }
///
/// The last move choice executed by the Pokémon.
///
IMoveChoice? LastMoveChoice { get; set; }
///
/// The statistics boosts of the Pokemon. Will prevent the value from going above 6, and below -6.
///
StatBoostStatisticSet StatBoost { get; }
///
/// The stats of the Pokemon including the stat boosts.
///
StatisticSet BoostedStats { get; }
///
/// Calculates the boosted stats on the Pokemon, _without_ recalculating the flat stats.
/// This should be called when a stat boost changes.
///
void RecalculateBoostedStats();
///
/// Change a boosted stat by a certain amount.
///
/// The stat to be changed
/// The amount to change the stat by
/// Whether the change was self-inflicted. This can be relevant in scripts.
/// Whether to skip the script hooks that can prevent or change the boost
/// The event batch ID this change is a part of. This is relevant for visual handling
bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted, bool force, EventBatchId batchId = default);
///
/// The volatile status scripts of the Pokemon.
///
IScriptSet Volatile { get; }
///
/// The script for the ability.
///
ScriptContainer AbilityScript { get; }
///
/// The script for the held item.
///
ScriptContainer HeldItemTriggerScript { get; }
///
/// An ability can be overriden to an arbitrary ability. This is for example used for the Mummy
/// ability.
///
IAbility? OverrideAbility { get; }
///
/// Changes the ability of the Pokémon.
///
bool ChangeAbility(IAbility ability);
///
/// Whether the ability of the Pokémon is suppressed.
///
bool AbilitySuppressed { get; }
///
/// Suppresses the ability of the Pokémon.
///
bool SuppressAbility();
///
/// Returns the currently active ability, taking suppression and overrides into account.
///
IAbility? ActiveAbility { get; }
///
/// An optional display species of the Pokemon. If this is set, the client should display this
/// species. An example of usage for this is the Illusion ability.
///
ISpecies? DisplaySpecies { get; }
///
/// An optional display form of the Pokemon. If this is set, the client should display this
/// form. An example of usage for this is the Illusion ability.
///
IForm? DisplayForm { get; }
///
/// Sets the display species and form of the Pokemon. This is used for abilities like Illusion.
///
void SetDisplaySpecies(ISpecies? species, IForm? form);
///
/// The height of the Pokémon in meters. This can be changed during battle by effects such as Autotomize.
///
new float HeightInMeters { get; set; }
///
/// Removes a type from the Pokémon. Returns whether the type was removed.
///
bool RemoveType(TypeIdentifier type);
///
/// Adds a type to the Pokémon. Returns whether the type was added. It will not add the type if
/// the Pokémon already has it.
///
bool AddType(TypeIdentifier type);
///
/// Replace the types of the Pokémon with the provided types.
///
void SetTypes(IReadOnlyList types);
///
/// Whether the Pokémon is levitating. This is used for moves like Magnet Rise, and abilities such as
/// Levitate.
///
bool IsFloating { get; }
///
/// The permanently learned moves of the Pokemon, ignoring any temporary replacements made through
/// . This is of a set length of . Empty move
/// slots are null.
///
IReadOnlyList BaseMoves { get; }
///
/// Temporarily replaces the move in the given slot until the Pokemon leaves the battlefield. The permanently
/// learned move in the slot is left untouched and becomes visible again automatically. Used by effects such as
/// Mimic.
///
/// Thrown when the move is not found in the move library.
void LearnTemporaryMove(StringKey moveName, MoveLearnMethod method, byte index);
///
/// Whether or not this Pokemon was caught this battle.
///
bool IsCaught { get; }
///
/// Marks the Pokemon as caught. This makes it so that the Pokemon is not considered valid in battle anymore.
///
void MarkAsCaught();
///
/// Whether the held item has been removed for the duration of the battle.
///
bool HasItemBeenRemovedForBattle { get; }
///
/// Removes the held item from the Pokemon for the duration of the battle. Returns the previously held item.
///
///
/// This is used for moves that remove a held item, but do not consume it. The held item of the underlying
/// Pokémon is never touched; the removal simply ends with the battle.
///
IItem? RemoveHeldItemForBattle();
///
/// Tries to steal the held item of the Pokémon. If successful, the item is removed from the Pokémon and returned.
/// If the Pokémon does not have a held item, or the item is a form changer, this will return false.
///
bool TryStealHeldItem([NotNullWhen(true)] out IItem? item);
///
/// Restores the held item of a Pokémon if it was temporarily removed.
///
void RestoreRemovedHeldItem();
///
/// Makes the Pokemon uses its held item. Returns whether the item was consumed.
///
bool ConsumeHeldItem();
///
/// Called by the battle when the Pokémon is sent onto the battlefield. This should not be called by
/// user code; use instead.
///
void OnSwitchedIn(byte position);
///
/// Called by the battle when the Pokémon leaves the battlefield. Resets all state that only lasts while
/// the Pokémon is on the field. This should not be called by user code.
///
void OnSwitchedOut();
///
/// Sets the position the Pokémon has within its side, without running switch handling.
///
void SetPosition(byte position);
///
/// Called when the battle has ended. Reverts the few battle effects that write through to the underlying
/// Pokémon, such as battle-only forms.
///
void OnBattleEnd();
}
///
public class BattlePokemonImpl : ScriptSource, IBattlePokemon
{
private readonly IPokemon _pokemon;
///
public BattlePokemonImpl(IPokemon pokemon, IBattle battle, byte sideIndex)
{
_pokemon = pokemon;
Battle = battle;
SideIndex = sideIndex;
OriginalSpecies = pokemon.Species;
OriginalForm = pokemon.Form;
Volatile = new ScriptSet(this);
_types = pokemon.Types.ToList();
HeightInMeters = pokemon.HeightInMeters;
_heldItem = pokemon.HeldItem;
RecalculateBoostedStats();
// A status that was set outside of the battle is parented to the underlying Pokémon. Re-parent it to
// this wrapper, so that it has battle context for the duration of the battle.
pokemon.StatusScript.Script?.OnAddedToParent(this);
}
///
public IPokemon UnderlyingPokemon => _pokemon;
///
public IBattle Battle { get; }
///
public byte SideIndex { get; }
///
public byte Position { get; private set; }
///
public IBattleSide BattleSide => Battle.Sides[SideIndex];
///
public bool IsOnBattlefield { get; private set; }
private readonly List _seenOpponents = [];
///
public IReadOnlyList SeenOpponents => _seenOpponents;
///
public void MarkOpponentAsSeen(IBattlePokemon opponent)
{
if (!_seenOpponents.Contains(opponent))
_seenOpponents.Add(opponent);
}
private readonly List _consumedItems = [];
///
public IReadOnlyList ConsumedItems => _consumedItems;
///
public void MarkItemAsConsumed(IItem item)
{
_consumedItems.Add(item);
BattleSide.SetConsumedItem(Position, item);
}
///
public uint SwitchInTurn { get; private set; }
///
public uint TurnsOnField => Battle.CurrentTurnNumber - SwitchInTurn;
///
public ISpecies OriginalSpecies { get; }
///
public IForm OriginalForm { get; }
///
public IMoveChoice? LastMoveChoice { get; set; }
///
public void OnSwitchedIn(byte position)
{
Position = position;
SwitchInTurn = Battle.CurrentTurnNumber;
IsOnBattlefield = true;
ResolveAbilityScript();
}
///
public void OnSwitchedOut()
{
IsOnBattlefield = false;
Volatile.Clear();
_temporaryMoves = null;
HeightInMeters = _pokemon.Form.Height;
_types = _pokemon.Form.Types.ToList();
OverrideAbility = null;
AbilitySuppressed = false;
StatBoost.Reset();
RecalculateBoostedStats();
}
///
public void SetPosition(byte position) => Position = position;
///
public void OnBattleEnd()
{
if (_pokemon.Form.IsBattleOnlyForm)
{
_pokemon.ChangeForm(OriginalSpecies == _pokemon.Species ? OriginalForm : _pokemon.Species.GetDefaultForm());
}
}
private void ResolveAbilityScript()
{
var ability = ActiveAbility;
if (ability != null && Library.ScriptResolver.TryResolve(ScriptCategory.Ability, ability.Name,
ability.Parameters, out var abilityScript))
{
AbilityScript.Set(abilityScript);
abilityScript.OnAddedToParent(this);
}
else
{
AbilityScript.Clear();
}
}
///
public IDynamicLibrary Library => _pokemon.Library;
///
public ISpecies Species => _pokemon.Species;
///
public IForm Form => _pokemon.Form;
///
public LevelInt Level => _pokemon.Level;
///
public uint Experience => _pokemon.Experience;
///
public uint PersonalityValue => _pokemon.PersonalityValue;
///
public Gender Gender => _pokemon.Gender;
///
public byte Coloring => _pokemon.Coloring;
///
public bool IsShiny => _pokemon.IsShiny;
///
public uint CurrentHealth => _pokemon.CurrentHealth;
///
public byte Happiness
{
get => _pokemon.Happiness;
set => _pokemon.Happiness = value;
}
///
public StatisticSet FlatStats => _pokemon.FlatStats;
///
public IndividualValueStatisticSet IndividualValues => _pokemon.IndividualValues;
///
public EffortValueStatisticSet EffortValues => _pokemon.EffortValues;
///
public INature Nature => _pokemon.Nature;
///
public string? Nickname => _pokemon.Nickname;
///
public AbilityIndex AbilityIndex => _pokemon.AbilityIndex;
///
public IAbility Ability => _pokemon.Ability;
///
public bool AllowedExperience => _pokemon.AllowedExperience;
///
public bool IsEgg => _pokemon.IsEgg;
///
public ScriptContainer StatusScript => _pokemon.StatusScript;
///
public int? GetStatusTurnsLeft => _pokemon.GetStatusTurnsLeft;
///
public bool HasStatus(StringKey status) => _pokemon.HasStatus(status);
///
public bool IsFainted => _pokemon.IsFainted;
///
public SerializedPokemon Serialize() => _pokemon.Serialize();
///
public bool AddExperience(uint experience, EventHook? eventHook = null) =>
_pokemon.AddExperience(experience, eventHook ?? Battle.EventHook);
///
public void RestoreAllPP()
{
foreach (var move in Moves)
{
move?.RestoreAllUses();
}
}
///
public void LearnMove(StringKey moveName, MoveLearnMethod method, byte index) =>
_pokemon.LearnMove(moveName, method, index);
///
public void ChangeLevelBy(int change)
{
_pokemon.ChangeLevelBy(change);
RecalculateBoostedStats();
}
///
public bool EvolveTo(IEvolution evolution, EventHook? eventHook = null) =>
_pokemon.EvolveTo(evolution, eventHook ?? Battle.EventHook);
///
public StatBoostStatisticSet StatBoost { get; } = new();
///
public StatisticSet BoostedStats { get; } = new();
///
public uint MaxHealth => BoostedStats.Hp;
///
public void RecalculateFlatStats()
{
_pokemon.RecalculateFlatStats();
RecalculateBoostedStats();
}
///
public void RecalculateBoostedStats() => Library.StatCalculator.CalculateBoostedStats(this, BoostedStats);
///
public bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted, bool force,
EventBatchId batchId = default)
{
if (!force)
{
var prevented = false;
this.RunScriptHook(script =>
script.PreventStatBoostChange(this, stat, change, selfInflicted, ref prevented));
if (prevented)
return false;
this.RunScriptHook(script =>
script.ChangeStatBoostChange(this, stat, selfInflicted, ref change));
if (change == 0)
return false;
}
var changed = false;
var oldBoost = StatBoost.GetStatistic(stat);
changed = change switch
{
> 0 => StatBoost.IncreaseStatistic(stat, change),
< 0 => StatBoost.DecreaseStatistic(stat, (sbyte)-change),
_ => changed,
};
if (!changed)
return false;
var newBoost = StatBoost.GetStatistic(stat);
Battle.EventHook.Invoke(new StatBoostEvent(this, stat, oldBoost, newBoost)
{
BatchId = batchId,
});
RecalculateBoostedStats();
this.RunScriptHook(script =>
script.OnAfterStatBoostChange(this, stat, selfInflicted, change));
return true;
}
///
public ISpecies? DisplaySpecies { get; private set; }
///
public IForm? DisplayForm { get; private set; }
///
public void SetDisplaySpecies(ISpecies? species, IForm? form)
{
DisplaySpecies = species;
DisplayForm = form;
Battle.EventHook.Invoke(new DisplaySpeciesChangeEvent(this, species, form)
{
BatchId = new EventBatchId(),
});
}
///
public void ChangeSpecies(ISpecies species, IForm form, EventHook? eventHook = null, IRandom? random = null)
{
var oldAbilityName = _pokemon.Form.GetAbility(_pokemon.AbilityIndex);
_pokemon.ChangeSpecies(species, form, eventHook ?? Battle.EventHook, random ?? Battle.Random);
OnAfterFormWriteThrough(oldAbilityName);
}
///
public void ChangeForm(IForm form, EventBatchId batchId = default, EventHook? eventHook = null)
{
if (form == Form)
return;
var oldAbilityName = _pokemon.Form.GetAbility(_pokemon.AbilityIndex);
_pokemon.ChangeForm(form, batchId, eventHook ?? Battle.EventHook);
OnAfterFormWriteThrough(oldAbilityName);
}
///
/// A form or species change writes through to the underlying Pokémon; the battle overlays that derive
/// from the form need to be re-initialized from the new form.
///
private void OnAfterFormWriteThrough(StringKey oldAbilityName)
{
_types = _pokemon.Form.Types.ToList();
HeightInMeters = _pokemon.Form.Height;
var newAbilityName = _pokemon.Form.GetAbility(_pokemon.AbilityIndex);
if (OverrideAbility == null && !AbilitySuppressed && oldAbilityName != newAbilityName)
ResolveAbilityScript();
RecalculateBoostedStats();
}
private List _types;
///
public IReadOnlyList Types => _types;
///
public bool RemoveType(TypeIdentifier type) => _types.Remove(type);
///
public bool AddType(TypeIdentifier type)
{
if (_types.Contains(type))
return false;
_types.Add(type);
return true;
}
///
public void SetTypes(IReadOnlyList types) => _types = types.ToList();
///
public float HeightInMeters { get; set; }
///
public float WeightInKg
{
get
{
var weight = _pokemon.WeightInKg;
// ReSharper disable once AccessToModifiedClosure
this.RunScriptHook(script => script.ModifyWeight(ref weight));
if (weight < 0.1f)
weight = 0.1f;
return weight;
}
}
private static readonly StringKey FlyingTypeName = "flying";
///
public bool IsFloating
{
get
{
var isFloating = Types.Any(x => x.Name == FlyingTypeName);
this.RunScriptHook(x => x.IsFloating(this, ref isFloating));
return isFloating;
}
}
///
public IAbility? OverrideAbility { get; private set; }
///
public bool AbilitySuppressed { get; private set; }
///
public IAbility? ActiveAbility
{
get
{
if (AbilitySuppressed)
return null;
if (OverrideAbility != null)
return OverrideAbility;
return _pokemon.Ability;
}
}
///
public bool SuppressAbility()
{
if (ActiveAbility?.CanBeChanged == false)
return false;
AbilitySuppressed = true;
AbilityScript.Clear();
return true;
}
///
public bool ChangeAbility(IAbility ability)
{
if (!ability.CanBeChanged)
return false;
OverrideAbility = ability;
if (Library.ScriptResolver.TryResolve(ScriptCategory.Ability, ability.Name, ability.Parameters,
out var abilityScript))
{
AbilityScript.Set(abilityScript);
abilityScript.OnAddedToParent(this);
}
else
{
AbilityScript.Clear();
}
return true;
}
///
/// Battle-only per-slot overrides of the underlying moveset. The permanent moveset is never mutated by
/// temporary moves; discarding this array is all that is needed to restore the original moves.
///
private ILearnedMove?[]? _temporaryMoves;
///
public IReadOnlyList Moves =>
_temporaryMoves?.Select((move, index) => move ?? BaseMoves[index]).ToArray() ?? BaseMoves;
///
public IReadOnlyList BaseMoves => _pokemon.Moves;
///
public bool HasMove(StringKey moveName) => Moves.Any(move => move?.MoveData.Name == moveName);
///
public void SwapMoves(byte index1, byte index2)
{
if (index1 >= Const.MovesCount || index2 >= Const.MovesCount)
return;
_pokemon.SwapMoves(index1, index2);
if (_temporaryMoves != null)
(_temporaryMoves[index1], _temporaryMoves[index2]) = (_temporaryMoves[index2], _temporaryMoves[index1]);
}
///
public void LearnTemporaryMove(StringKey moveName, MoveLearnMethod method, byte index)
{
if (index >= Const.MovesCount)
throw new ArgumentOutOfRangeException(nameof(index), $"Move slot {index} is out of range.");
if (!Library.StaticLibrary.Moves.TryGet(moveName, out var move))
throw new KeyNotFoundException($"Move {moveName} not found.");
_temporaryMoves ??= new ILearnedMove?[Const.MovesCount];
_temporaryMoves[index] = new LearnedMoveImpl(move, method);
}
private IItem? _heldItem;
///
public IItem? HeldItem => _heldItem;
///
public bool HasHeldItem(StringKey itemName) => _heldItem?.Name == itemName;
///
public IItem? ForceSetHeldItem(IItem? item)
{
var previous = _heldItem;
_heldItem = item;
this.RunScriptHook(x => x.OnAfterHeldItemChange(this, previous, item));
return previous;
}
///
public IItem? RemoveHeldItem()
{
if (_heldItem is not null && _heldItem.Category == ItemCategory.FormChanger)
return null;
var previous = _heldItem;
_heldItem = null;
this.RunScriptHook(x => x.OnAfterHeldItemChange(this, previous, null));
return previous;
}
private IItem? _removedHeldItem;
///
public bool HasItemBeenRemovedForBattle => _removedHeldItem is not null;
///
public IItem? RemoveHeldItemForBattle() => _removedHeldItem = RemoveHeldItem();
///
public bool TryStealHeldItem([NotNullWhen(true)] out IItem? item)
{
if (_heldItem is null || _heldItem.Category == ItemCategory.FormChanger)
{
item = null;
return false;
}
var prevent = false;
this.RunScriptHook(script =>
script.PreventHeldItemSteal(this, _heldItem, ref prevent));
if (prevent)
{
item = null;
return false;
}
item = RemoveHeldItemForBattle();
return item is not null;
}
///
public void RestoreRemovedHeldItem()
{
_ = ForceSetHeldItem(_removedHeldItem);
_removedHeldItem = null;
}
///
public bool ConsumeHeldItem()
{
if (_heldItem is null)
return false;
if (!Library.ScriptResolver.TryResolveBattleItemScript(_heldItem, out _))
return false;
var prevented = false;
this.RunScriptHook(script =>
script.PreventHeldItemConsume(this, _heldItem, ref prevented));
if (prevented)
return false;
MarkItemAsConsumed(_heldItem);
UseItem(ForceSetHeldItem(null)!);
return true;
}
///
public void UseItem(IItem item)
{
// TODO: actually consume the item
this.RunScriptHook(x => x.OnAfterItemConsume(this, item));
}
///
public bool IsCaught { get; private set; }
///
public void MarkAsCaught()
{
IsCaught = true;
}
///
public bool IsUsable => !IsCaught && _pokemon.IsUsable;
///
public void Damage(uint damage, DamageSource source, EventBatchId batchId = default, bool forceDamage = false)
{
if (IsFainted)
return;
if (!forceDamage)
{
var dmg = damage;
this.RunScriptHook(script =>
script.ChangeIncomingDamage(this, source, ref dmg));
damage = dmg;
}
if (damage == 0)
return;
// If the damage is more than the current health, we cap it at the current health, to prevent
// underflow.
if (damage >= CurrentHealth)
damage = CurrentHealth;
var newHealth = CurrentHealth - damage;
// Trigger an event to the front-end.
Battle.EventHook.Invoke(new DamageEvent(this, CurrentHealth, newHealth, source)
{
BatchId = batchId,
});
// And allow scripts to execute.
this.RunScriptHook(script => script.OnDamage(this, source, CurrentHealth, newHealth));
_pokemon.Damage(damage, source, batchId, true);
// If the Pokémon is now fainted, we also run faint handling.
if (IsFainted)
{
OnFaint(source);
}
}
///
public void Faint(DamageSource source, EventBatchId batchId = default)
{
_pokemon.Faint(source, batchId);
OnFaint(source);
}
private void OnFaint(DamageSource source)
{
// Trigger the faint event to the front-end.
Battle.EventHook.Invoke(new FaintEvent(this));
// Allow scripts to trigger based on the faint.
this.RunScriptHook(script => script.OnFaint(this, source));
foreach (var ally in BattleSide.Pokemon.WhereNotNull().Where(x => x != this))
{
ally.RunScriptHook(script => script.OnAllyFaint(ally, this));
}
// Make sure the OnRemove script is run.
this.RunScriptHook(script => script.OnRemove());
// Mark the position as unfillable if it can't be filled by any party.
if (!Battle.CanSlotBeFilled(SideIndex, Position))
{
BattleSide.MarkPositionAsUnfillable(Position);
}
BattleSide.MarkFaint(Position);
BattleSide.ForceClearPokemonFromField(Position);
foreach (var opponent in SeenOpponents)
{
if (!opponent.IsUsable)
continue;
if (!opponent.AllowedExperience)
continue;
opponent.AddExperience(Library.ExperienceGainCalculator.CalculateExperienceGain(this, opponent));
}
// Validate the battle state to see if the battle is over.
Battle.ValidateBattleState();
}
///
public bool Heal(uint heal, bool allowRevive = false, EventBatchId batchId = default, bool forceHeal = false,
EventHook? customEventHook = null)
{
if (IsFainted && !allowRevive)
return false;
var maxAmount = BoostedStats.Hp - CurrentHealth;
if (heal > maxAmount)
heal = maxAmount;
if (heal == 0)
return false;
if (!forceHeal)
{
var prevented = false;
this.RunScriptHook(x => x.PreventHeal(this, heal, allowRevive, ref prevented));
if (prevented)
return false;
}
customEventHook ??= Battle.EventHook;
return _pokemon.Heal(heal, allowRevive, batchId, true, customEventHook);
}
///
public bool SetStatus(StringKey status, IPokemon? originPokemon, EventBatchId batchId = default)
{
if (!Library.ScriptResolver.TryResolve(ScriptCategory.Status, status, null, out var statusScript))
throw new KeyNotFoundException($"Status script {status} not found");
if (!StatusScript.IsEmpty)
return false;
var oldStatus = StatusScript.Script?.Name;
var selfInflicted = originPokemon == this ||
(originPokemon is IBattlePokemon origin && origin.UnderlyingPokemon == _pokemon);
var preventStatus = false;
this.RunScriptHook(script =>
script.PreventStatusChange(this, status, selfInflicted, ref preventStatus));
if (preventStatus)
return false;
StatusScript.Set(statusScript);
statusScript.OnAddedToParent(this);
Battle.EventHook.Invoke(new StatusChangeEvent(this, oldStatus, status)
{
BatchId = batchId,
});
this.RunScriptHook(script =>
script.OnAfterStatusChange(this, status, originPokemon));
return true;
}
///
public void ClearStatus(EventBatchId batchId = default)
{
var oldStatus = StatusScript.Script?.Name;
_pokemon.ClearStatus(batchId);
Battle.EventHook.Invoke(new StatusChangeEvent(this, oldStatus, null)
{
BatchId = batchId,
});
}
///
public ScriptContainer HeldItemTriggerScript { get; } = new();
///
public ScriptContainer AbilityScript { get; } = new();
///
public IScriptSet Volatile { get; }
///
public override int ScriptCount => 4 + BattleSide.ScriptCount;
///
public override void GetOwnScripts(List> scripts)
{
scripts.Add(HeldItemTriggerScript);
scripts.Add(AbilityScript);
scripts.Add(StatusScript);
scripts.Add(Volatile);
}
///
public override void CollectScripts(List> scripts)
{
GetOwnScripts(scripts);
BattleSide.CollectScripts(scripts);
}
///
public override string ToString() => _pokemon.ToString()!;
}