Deukhoofd defb1349ca
All checks were successful
Build / Build (push) Successful in 47s
Add EventHook parameter to item use scripts
Items can be used on Pokemon outside of battle, and we want the user of the library to be able to check for what changed. This allows for example a heal event to be sent back to the user of the library after using an item.
2025-06-15 11:59:17 +02:00

179 lines
6.4 KiB
C#

using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.BattleFlow;
/// <summary>
/// Helper class for handling the running of a turn in a battle.
/// </summary>
public static class TurnRunner
{
/// <summary>
/// Runs a single turn in a battle to completion.
/// </summary>
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<IEnumerable<ScriptContainer>>(10);
foreach (var side in battle.Sides)
{
foreach (var pokemon in side.Pokemon.WhereNotNull())
{
scripts.Clear();
pokemon.GetOwnScripts(scripts);
scripts.RunScriptHook(x => x.OnEndTurn(pokemon, battle));
}
scripts.Clear();
side.GetOwnScripts(scripts);
scripts.RunScriptHook(x => x.OnEndTurn(side, battle));
}
scripts.Clear();
battle.GetOwnScripts(scripts);
scripts.RunScriptHook(x => x.OnEndTurn(battle, battle));
}
}
/// <summary>
/// Runs a single choice in a battle.
/// </summary>
public 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;
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];
}
itemChoice.Item.RunItemScript(battle.Library.ScriptResolver, target ?? user, battle.EventHook);
}
}