using PkmnLib.Dynamic.Events; using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Static; using PkmnLib.Static.Utils; namespace PkmnLib.Dynamic.Models; /// /// A side in a battle. /// public interface IBattleSide : IScriptSource, IDeepCloneable { /// /// The index of the side on the battle. /// byte Index { get; } /// /// The number of Pokémon that can be on the side. /// byte NumberOfPositions { get; } /// /// A list of Pokémon currently on the battlefield. /// IReadOnlyList Pokemon { get; } /// /// The currently set choices for all Pokémon on the battlefield. Cleared when the turn starts. /// IReadOnlyList SetChoices { get; } /// /// Whether every Pokémon on this side has its choices /// bool AllChoicesSet { get; } /// /// The slots on the side that can still be filled. Once all slots are set to false, this side /// has lost the battle. /// IReadOnlyList FillablePositions { get; } /// /// A reference to the battle this side is in. /// IBattle Battle { get; } /// /// Whether this side has fled. /// bool HasFledBattle { get; } /// /// The volatile scripts that are attached to the side. /// IScriptSet VolatileScripts { get; } /// /// Returns true if there are slots that need to be filled with a new pokemon, that have parties /// responsible for them. Returns false if all slots are filled with usable pokemon, or slots are /// empty, but can't be filled by any party anymore. /// bool AllPositionsFilled(); /// /// Sets a choice for a Pokémon on this side. /// void SetChoice(byte position, ITurnChoice choice); /// /// Resets all choices on this side. /// void ResetChoices(); /// /// Forcibly removes a Pokémon from the field. /// /// void ForceClearPokemonFromField(byte index); /// /// Switches out a spot on the field for a different Pokémon. If null is passed, the spot is /// cleared. Returns the Pokémon that was previously in the spot. /// IPokemon? SwapPokemon(byte position, IPokemon? pokemon); /// /// Swaps two Pokémon on the side. /// void SwapPokemon(byte position1, byte position2); /// /// Checks whether a Pokemon is on the field in this side. /// bool IsPokemonOnSide(IPokemon pokemon); /// /// Marks a slot as unfillable. This happens when no parties are able to fill the slot anymore. /// If this happens, the slot can not be used again. /// void MarkPositionAsUnfillable(byte position); /// /// Checks whether a slot is fillable. If it is not, the slot can not be used anymore. /// bool IsPositionFillable(byte position); /// /// Checks whether the side has been defeated. /// bool IsDefeated(); /// /// The number of times this side has attempted to flee. /// uint FleeAttempts { get; } /// /// Registers a flee attempt for this side. /// void RegisterFleeAttempt(); /// /// Mark the side as fled. /// void MarkAsFled(); /// /// Gets a random Pokémon on the given side. /// byte GetRandomPosition(); /// /// Marks an item as consumed for a position. Can be used by moves such as Recycle to get the item back. /// void SetConsumedItem(byte battleDataPosition, IItem heldItem); /// /// Gets the last consumed item for a position. Can be used by moves such as Recycle to get the item back. /// IItem? GetLastConsumedItem(byte battleDataPosition); /// /// Marks a Pokémon as fainted. This is used to track the last turn a Pokémon in a position fainted. /// void MarkFaint(byte position); /// /// Gets the last turn a Pokémon in a specific position fainted. /// uint? GetLastFaintTurn(byte position); /// /// Gets the last turn a Pokémon in any position fainted. /// uint? GetLastFaintTurn(); } /// public class BattleSideImpl : ScriptSource, IBattleSide { /// public BattleSideImpl(byte index, byte numberOfPositions, IBattle battle) { Index = index; NumberOfPositions = numberOfPositions; _pokemon = new IPokemon?[numberOfPositions]; _setChoices = new ITurnChoice?[numberOfPositions]; _fillablePositions = new bool[numberOfPositions]; for (byte i = 0; i < numberOfPositions; i++) { _fillablePositions[i] = true; } Battle = battle; VolatileScripts = new ScriptSet(this); } /// public byte Index { get; } /// public byte NumberOfPositions { get; } private readonly IPokemon?[] _pokemon; /// public IReadOnlyList Pokemon => _pokemon; private readonly ITurnChoice?[] _setChoices; /// public IReadOnlyList SetChoices => _setChoices; /// public bool AllChoicesSet => _setChoices.All(choice => choice is not null); private readonly bool[] _fillablePositions; /// public IReadOnlyList FillablePositions => _fillablePositions; /// public IBattle Battle { get; } /// public bool HasFledBattle { get; private set; } /// public IScriptSet VolatileScripts { get; } /// public bool AllPositionsFilled() { for (byte i = 0; i < NumberOfPositions; i++) { var pokemon = Pokemon[i]; var isPokemonViable = pokemon is not null && pokemon.IsUsable; // If the Pokémon is not valid, but the slot can be filled, return false. if (!isPokemonViable && Battle.CanSlotBeFilled(Index, i)) { return false; } } return true; } /// public void SetChoice(byte position, ITurnChoice choice) { _setChoices[position] = choice; } /// public void ResetChoices() { for (byte i = 0; i < NumberOfPositions; i++) { _setChoices[i] = null; } } /// /// public void ForceClearPokemonFromField(byte index) { var pokemon = _pokemon[index]; if (pokemon is not null) { pokemon.RunScriptHook(script => script.OnRemove()); pokemon.SetOnBattlefield(false); } _pokemon[index] = null; } /// public IPokemon? SwapPokemon(byte position, IPokemon? pokemon) { var oldPokemon = _pokemon[position]; if (oldPokemon is not null) { oldPokemon.RunScriptHook(script => script.OnSwitchOut(oldPokemon, position)); oldPokemon.RunScriptHook(script => script.OnRemove()); oldPokemon.SetOnBattlefield(false); } _pokemon[position] = pokemon; if (pokemon is not null) { pokemon.SetBattleData(Battle, Index); pokemon.SetOnBattlefield(true); pokemon.SetBattleSidePosition(position); Battle.EventHook.Invoke(new SwitchEvent(Index, position, pokemon)); pokemon.RunScriptHook(script => script.OnSwitchIn(pokemon, position)); foreach (var side in Battle.Sides) { if (side == this) continue; var scripts = new List>(10); foreach (var opponent in side.Pokemon.WhereNotNull()) { opponent.MarkOpponentAsSeen(pokemon); pokemon.MarkOpponentAsSeen(opponent); scripts.Clear(); opponent.GetOwnScripts(scripts); opponent.RunScriptHook(script => script.OnOpponentSwitchIn(pokemon, position)); } side.RunScriptHook(script => script.OnOpponentSwitchIn(pokemon, position)); } } else { Battle.EventHook.Invoke(new SwitchEvent(Index, position, null)); } return oldPokemon; } /// public void SwapPokemon(byte position1, byte position2) { throw new NotImplementedException(); } /// public bool IsPokemonOnSide(IPokemon pokemon) => _pokemon.Contains(pokemon); /// public void MarkPositionAsUnfillable(byte position) => _fillablePositions[position] = false; /// public bool IsPositionFillable(byte position) => _fillablePositions[position]; /// public bool IsDefeated() { return _fillablePositions.All(fillable => !fillable); } /// public uint FleeAttempts { get; private set; } /// public void RegisterFleeAttempt() { FleeAttempts++; } /// public void MarkAsFled() => HasFledBattle = true; /// public byte GetRandomPosition() => (byte)Battle.Random.GetInt(0, NumberOfPositions); private Dictionary? _lastConsumedItems; /// public void SetConsumedItem(byte battleDataPosition, IItem heldItem) { _lastConsumedItems ??= new Dictionary(); _lastConsumedItems[battleDataPosition] = heldItem; } /// public IItem? GetLastConsumedItem(byte battleDataPosition) => _lastConsumedItems?.GetValueOrDefault(battleDataPosition); private Dictionary? _lastFaintTurn; /// public void MarkFaint(byte position) { _lastFaintTurn ??= new Dictionary(); _lastFaintTurn[position] = Battle.CurrentTurnNumber; } /// public uint? GetLastFaintTurn(byte position) { if (_lastFaintTurn is null) return null; if (_lastFaintTurn.TryGetValue(position, out var turn)) return turn; return null; } /// public uint? GetLastFaintTurn() => _lastFaintTurn?.Values.Max() ?? null; /// public override int ScriptCount => 1 + Battle.ScriptCount; /// public override void GetOwnScripts(List> scripts) { scripts.Add(VolatileScripts); } /// public override void CollectScripts(List> scripts) { scripts.Add(VolatileScripts); Battle.CollectScripts(scripts); } }