using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Static.Utils; namespace PkmnLib.Dynamic.Models.BattleFlow; /// /// Helper class for handling the running of a turn in a battle. /// public static class TurnRunner { /// /// Runs a single turn in a battle to completion. /// public static void RunTurn(this IBattle battle) { var queue = battle.ChoiceQueue; if (queue == null) throw new ArgumentNullException(nameof(battle.ChoiceQueue), "The battle's choice queue must be set before running a turn."); // We are now at the very beginning of a turn. We have assigned speeds and priorities to all // choices, and put them in the correct order. // The first thing to do is to run the on_before_turn script hook on every choice. This script hook // is primarily intended to be used to reset variables on a script (for example scripts that need // to check whether a Pokémon was hit this turn. By resetting here, and setting a variable to true // they can then know this later on.) foreach (var choice in queue.GetChoices().WhereNotNull()) { choice.RunScriptHook(script => script.OnBeforeTurnStart(choice)); } // Now we can properly begin executing choices. // One by one dequeue the turns, and run them. If the battle has ended we do not want to // continue running. while (queue.HasNext() && !battle.HasEnded) { var next = queue.Dequeue(); if (next == null) continue; ExecuteChoice(battle, next); } // If the battle is not ended, we have arrived at the normal end of a turn. and thus want // to run the end turn scripts. // As we want all scripts to run exactly once, including those on the sides and battles, // we can't just use the default script hook on each Pokémon. Instead, manually call // the script functions on every script. if (!battle.HasEnded) { var scripts = new List>(10); foreach (var side in battle.Sides) { foreach (var pokemon in side.Pokemon.WhereNotNull()) { scripts.Clear(); pokemon.GetOwnScripts(scripts); scripts.RunScriptHook(x => x.OnEndTurn()); } scripts.Clear(); side.GetOwnScripts(scripts); scripts.RunScriptHook(x => x.OnEndTurn()); } scripts.Clear(); battle.GetOwnScripts(scripts); scripts.RunScriptHook(x => x.OnEndTurn()); } } private static void ExecuteChoice(IBattle battle, ITurnChoice choice) { if (choice is IPassChoice) return; if (battle.HasEnded) return; if (!choice.User.IsUsable) return; if (choice.User.BattleData?.IsOnBattlefield != true) return; switch (choice) { case IMoveChoice moveChoice: MoveTurnExecutor.ExecuteMoveChoice(battle, moveChoice); break; case ISwitchChoice switchChoice: ExecuteSwitchChoice(battle, switchChoice); break; case IFleeChoice fleeChoice: ExecuteFleeChoice(battle, fleeChoice); break; case IItemChoice itemChoice: ExecuteItemChoice(battle, itemChoice); break; } } private static void ExecuteSwitchChoice(IBattle battle, ISwitchChoice fleeChoice) { var user = fleeChoice.User; var battleData = user.BattleData; if (battleData == null) return; var preventSwitch = false; fleeChoice.RunScriptHook(script => script.PreventSelfSwitch(fleeChoice, ref preventSwitch)); if (preventSwitch) return; foreach (var side in battle.Sides) { if (side.Index == battleData.SideIndex) continue; foreach (var pokemon in side.Pokemon.WhereNotNull()) { pokemon.RunScriptHook(script => script.PreventOpponentSwitch(fleeChoice, ref preventSwitch)); if (preventSwitch) return; } } user.Volatile.Clear(); var userSide = battle.Sides[battleData.SideIndex]; userSide.SwapPokemon(battleData.Position, fleeChoice.SwitchTo); } private static void ExecuteFleeChoice(IBattle battle, IFleeChoice fleeChoice) { var user = fleeChoice.User; var battleData = user.BattleData; if (battleData == null) return; if (!battle.CanFlee) return; var preventFlee = false; fleeChoice.RunScriptHook(script => script.PreventSelfRunAway(fleeChoice, ref preventFlee)); if (preventFlee) return; foreach (var side in battle.Sides) { if (side.Index == battleData.SideIndex) continue; foreach (var pokemon in side.Pokemon.WhereNotNull()) { pokemon.RunScriptHook(script => script.PreventOpponentRunAway(fleeChoice, ref preventFlee)); if (preventFlee) return; } } if (!battle.Library.MiscLibrary.CanFlee(battle, fleeChoice)) return; var userSide = battle.Sides[battleData.SideIndex]; userSide.MarkAsFled(); battle.ValidateBattleState(); } private static void ExecuteItemChoice(IBattle battle, IItemChoice itemChoice) { var user = itemChoice.User; var battleData = user.BattleData; if (battleData == null) return; if (!battle.Library.ScriptResolver.TryResolveBattleItemScript(itemChoice.Item, out var itemScript)) { return; } if (!itemScript.IsItemUsable) return; itemScript.OnInitialize(itemChoice.Item.BattleEffect!.Parameters); IPokemon? target = null; if (itemChoice is { TargetSide: not null, TargetPosition: not null }) { var side = battle.Sides[itemChoice.TargetSide.Value]; target = side.Pokemon[itemChoice.TargetPosition.Value]; } var requiresTarget = itemScript.RequiresTarget; if (requiresTarget) { target ??= user; itemScript.OnUseWithTarget(target); } else { itemScript.OnUse(); } } }