Move all battle state from IPokemon to an ephemeral IBattlePokemon wrapper

This commit is contained in:
2026-08-28 15:20:26 +02:00
parent 942be8eaeb
commit 8a2733a0a9
859 changed files with 4408 additions and 5331 deletions

View File

@@ -14,7 +14,7 @@ public static class AIHelpers
/// <summary>
/// Estimates the amount of damage that will be done by a move against a target.
/// </summary>
public static uint CalculateDamageEstimation(IMoveData move, IPokemon user, IPokemon target,
public static uint CalculateDamageEstimation(IMoveData move, IBattlePokemon user, IBattlePokemon target,
IDynamicLibrary library)
{
var hitData = new CustomHitData

View File

@@ -9,7 +9,7 @@ namespace PkmnLib.Dynamic.AI.Explicit;
public class AIMoveState
{
/// <inheritdoc cref="AIMoveState" />
public AIMoveState(IPokemon user, IMoveData move)
public AIMoveState(IBattlePokemon user, IMoveData move)
{
User = user;
Move = move;
@@ -18,7 +18,7 @@ public class AIMoveState
/// <summary>
/// The user that's being wrapper
/// </summary>
public IPokemon User { get; }
public IBattlePokemon User { get; }
/// <summary>
/// The move that's being wrapper

View File

@@ -9,7 +9,7 @@ namespace PkmnLib.Dynamic.AI.Explicit;
public partial class ExplicitAI
{
private bool TryChooseToSwitchOut(IBattle battle, IPokemon pokemon, bool terribleMoves,
private bool TryChooseToSwitchOut(IBattle battle, IBattlePokemon pokemon, bool terribleMoves,
[NotNullWhen(true)] out ITurnChoice? choice)
{
choice = null;
@@ -17,13 +17,13 @@ public partial class ExplicitAI
return false;
if (TrainerHighSkill)
{
var opponentSide = battle.Sides.First(x => x != pokemon.BattleData?.BattleSide);
var opponentSide = battle.Sides.First(x => x != pokemon.BattleSide);
var foeCanAct = opponentSide.Pokemon.WhereNotNull().Any(CanAttack);
if (!foeCanAct)
return false;
}
var party = battle.Parties.FirstOrDefault(x => x.IsResponsibleForIndex(
new ResponsibleIndex(pokemon.BattleData!.SideIndex, pokemon.BattleData.Position)));
new ResponsibleIndex(pokemon.SideIndex, pokemon.Position)));
if (party is null)
return false;
var usablePokemon = party.GetUsablePokemonNotInField().ToList();
@@ -44,20 +44,20 @@ public partial class ExplicitAI
if (!shouldSwitch)
return false;
}
var battleSide = pokemon.BattleData!.BattleSide;
var battleSide = pokemon.BattleSide;
var bestReplacement = ChooseBestReplacementPokemon(terribleMoves, usablePokemon, battleSide);
if (bestReplacement is null)
{
AILogging.LogInformation(
$"ExplicitAI: No suitable replacement Pokemon found for {pokemon} at position {pokemon.BattleData.Position}.");
$"ExplicitAI: No suitable replacement Pokemon found for {pokemon} at position {pokemon.Position}.");
return false;
}
choice = new SwitchChoice(pokemon, bestReplacement);
return true;
}
private IPokemon? ChooseBestReplacementPokemon(bool terribleMoves, IReadOnlyList<IPokemon> usablePokemon,
IBattleSide battleSide)
private IBattlePokemon? ChooseBestReplacementPokemon(bool terribleMoves,
IReadOnlyList<IBattlePokemon> usablePokemon, IBattleSide battleSide)
{
var options = usablePokemon.Where((_, index) =>
{
@@ -84,7 +84,7 @@ public partial class ExplicitAI
private static readonly StringKey ToxicSpikesName = "toxic_spikes";
private static readonly StringKey StickyWebName = "sticky_web";
private int RateReplacementPokemon(IPokemon pokemon, IBattleSide battleSide)
private int RateReplacementPokemon(IBattlePokemon pokemon, IBattleSide battleSide)
{
var score = 0;
var types = pokemon.Types;
@@ -107,7 +107,7 @@ public partial class ExplicitAI
var opponentSide = battleSide.Battle.Sides.First(x => x != battleSide);
foreach (var foe in opponentSide.Pokemon.WhereNotNull())
{
var lastMoveUsed = foe.BattleData?.LastMoveChoice;
var lastMoveUsed = foe.LastMoveChoice;
if (lastMoveUsed is null || lastMoveUsed.ChosenMove.MoveData.Category == MoveCategory.Status)
continue;
var moveType = lastMoveUsed.ChosenMove.MoveData.MoveType;
@@ -134,22 +134,19 @@ public partial class ExplicitAI
/// <summary>
/// Calculates the expected entry hazard damage for a given Pokémon on a given battle side.
/// </summary>
public static uint CalculateEntryHazardDamage(IPokemon pokemon, IBattleSide side)
public static uint CalculateEntryHazardDamage(IBattlePokemon pokemon, IBattleSide side)
{
var damage = 0u;
side.RunScriptHook<IAIInfoScriptExpectedEntryDamage>(x => x.ExpectedEntryDamage(pokemon, ref damage));
return damage;
}
private static bool CanSwitch(IPokemon pokemon)
private static bool CanSwitch(IBattlePokemon pokemon)
{
var battleData = pokemon.BattleData;
if (battleData == null)
if (pokemon.Battle.IsWildBattle)
return false;
if (battleData.Battle.IsWildBattle)
return false;
var partyForIndex = battleData.Battle.Parties.FirstOrDefault(x =>
x.IsResponsibleForIndex(new ResponsibleIndex(battleData.SideIndex, battleData.Position)));
var partyForIndex = pokemon.Battle.Parties.FirstOrDefault(x =>
x.IsResponsibleForIndex(new ResponsibleIndex(pokemon.SideIndex, pokemon.Position)));
return partyForIndex != null && partyForIndex.HasUsablePokemonNotInField();
}
}

View File

@@ -37,7 +37,7 @@ public partial class ExplicitAI
private static readonly StringKey KomalaName = "komala";
private static readonly StringKey MiniorName = "minior";
private static bool CanBePoisoned(IPokemon pokemon, IBattle battle)
private static bool CanBePoisoned(IBattlePokemon pokemon, IBattle battle)
{
if (battle.TerrainName == MistyTerrainName)
return false;
@@ -60,7 +60,7 @@ public partial class ExplicitAI
return true;
}
private static bool CanAbsorbMove(IPokemon pokemon, IMoveData move, TypeIdentifier moveType, IBattle battle)
private static bool CanAbsorbMove(IBattlePokemon pokemon, IMoveData move, TypeIdentifier moveType, IBattle battle)
{
if (pokemon.ActiveAbility == null)
return false;

View File

@@ -118,7 +118,7 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
public IRandom Random => _random;
/// <inheritdoc />
public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon)
public override ITurnChoice GetChoice(IBattle battle, IBattlePokemon pokemon)
{
if (battle.HasForcedTurn(pokemon, out var choice))
return choice;
@@ -131,8 +131,8 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
var moveChoices = GetMoveScores(pokemon, battle);
if (moveChoices.Count == 0)
{
var opponentSide = (byte)(pokemon.BattleData!.SideIndex == 0 ? 1 : 0);
return battle.Library.MiscLibrary.ReplacementChoice(pokemon, opponentSide, pokemon.BattleData.Position);
var opponentSide = (byte)(pokemon.SideIndex == 0 ? 1 : 0);
return battle.Library.MiscLibrary.ReplacementChoice(pokemon, opponentSide, pokemon.Position);
}
var maxScore = moveChoices.Max(x => x.score);
if (TrainerHighSkill && CanSwitch(pokemon))
@@ -144,7 +144,7 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
if (!badMoves && _random.GetInt(100) < 25)
badMoves = true;
}
else if (maxScore < MoveBaseScore * MoveScoreThreshold && pokemon.BattleData?.TurnsOnField > 2 &&
else if (maxScore < MoveBaseScore * MoveScoreThreshold && pokemon.TurnsOnField > 2 &&
_random.GetInt(100) < 80)
{
badMoves = true;
@@ -164,8 +164,8 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
var totalScore = considerChoices.Sum(x => x.Item2);
if (totalScore == 0)
{
var opponentSide = (byte)(pokemon.BattleData!.SideIndex == 0 ? 1 : 0);
return battle.Library.MiscLibrary.ReplacementChoice(pokemon, opponentSide, pokemon.BattleData.Position);
var opponentSide = (byte)(pokemon.SideIndex == 0 ? 1 : 0);
return battle.Library.MiscLibrary.ReplacementChoice(pokemon, opponentSide, pokemon.Position);
}
var initialRandomValue = _random.GetFloat(0, totalScore);
var randomValue = initialRandomValue;
@@ -177,15 +177,15 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
var (index, _, targetIndex) = considerChoices[i].x;
var learnedMove = pokemon.Moves[index];
var opponentSide = (byte)(pokemon.BattleData!.SideIndex == 0 ? 1 : 0);
var opponentSide = (byte)(pokemon.SideIndex == 0 ? 1 : 0);
if (targetIndex == -1)
targetIndex = pokemon.BattleData.Position;
targetIndex = pokemon.Position;
return new MoveChoice(pokemon, learnedMove!, opponentSide, (byte)targetIndex);
}
throw new InvalidOperationException("No valid move choice found. This should not happen.");
}
private List<(int index, int score, int targetIndex)> GetMoveScores(IPokemon user, IBattle battle)
private List<(int index, int score, int targetIndex)> GetMoveScores(IBattlePokemon user, IBattle battle)
{
var choices = new List<(int index, int score, int targetIndex)>();
foreach (var (learnedMove, index) in user.Moves.Select((x, i) => (x, i)).Where(x => x.x != null))
@@ -249,30 +249,24 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
// TODO: get redirected target
foreach (var pokemon in battle.Sides.SelectMany(x => x.Pokemon).WhereNotNull())
{
var battleData = pokemon.BattleData;
if (battleData == null)
if (!TargetResolver.IsValidTarget(pokemon.SideIndex, pokemon.Position, target, user))
continue;
if (!TargetResolver.IsValidTarget(battleData.SideIndex, battleData.Position, target, user))
continue;
if (target.TargetsFoe() && battleData.SideIndex == user.BattleData?.SideIndex)
if (target.TargetsFoe() && pokemon.SideIndex == user.SideIndex)
{
continue;
}
var score = GetMoveScoreAgainstTarget(user, aiMove, pokemon, battle);
AddMoveToChoices(index, score, battleData.Position);
AddMoveToChoices(index, score, pokemon.Position);
}
}
else
{
var targets = new List<IPokemon>();
var targets = new List<IBattlePokemon>();
foreach (var pokemon in battle.Sides.SelectMany(x => x.Pokemon).WhereNotNull())
{
var battleData = pokemon.BattleData;
if (battleData == null)
if (!TargetResolver.IsValidTarget(pokemon.SideIndex, pokemon.Position, target, user))
continue;
if (!TargetResolver.IsValidTarget(battleData.SideIndex, battleData.Position, target, user))
continue;
if (target.TargetsFoe() && battleData.SideIndex == user.BattleData?.SideIndex)
if (target.TargetsFoe() && pokemon.SideIndex == user.SideIndex)
{
continue;
}
@@ -295,7 +289,7 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
}
}
private bool PredictMoveFailure(IPokemon user, IBattle battle, AIMoveState aiMove)
private bool PredictMoveFailure(IBattlePokemon user, IBattle battle, AIMoveState aiMove)
{
if (user.HasStatus("sleep"))
{
@@ -333,14 +327,15 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
private static readonly StringKey SubstituteName = new("substitute");
private static readonly StringKey InfiltratorName = new("infiltrator");
private bool PredictMoveFailureAgainstTarget(IPokemon user, AIMoveState aiMove, IPokemon target, IBattle battle)
private bool PredictMoveFailureAgainstTarget(IBattlePokemon user, AIMoveState aiMove, IBattlePokemon target,
IBattle battle)
{
if (aiMove.Move.SecondaryEffect != null && _handlers.MoveWillFailAgainstTarget(this,
aiMove.Move.SecondaryEffect.Name, new MoveOption(aiMove, battle, target)))
return true;
if (aiMove.Move.Priority > 0)
{
if (target.BattleData?.SideIndex != user.BattleData?.SideIndex)
if (target.SideIndex != user.SideIndex)
{
// Psychic Terrain makes all priority moves fail if the target is affected
if (battle.TerrainName == PsychicTerrainName && !target.IsFloating)
@@ -348,7 +343,7 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
return true;
}
// Dazzling and Queenly Majesty prevent priority moves from being used against the Pokémon with those abilities
if (target.BattleData?.BattleSide.Pokemon.WhereNotNull().Any(x =>
if (target.BattleSide.Pokemon.WhereNotNull().Any(x =>
x.ActiveAbility?.Name == DazzlingName || x.ActiveAbility?.Name == QueenlyMajestyName) == true)
{
return true;
@@ -362,7 +357,7 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
if (aiMove.Move.Category != MoveCategory.Status && typeEffectiveness == 0)
return true;
if (user.ActiveAbility?.Name == PranksterName && aiMove.Move.Category == MoveCategory.Status &&
target.Types.Any(x => x.Name == DarkName) && target.BattleData?.SideIndex != user.BattleData?.SideIndex)
target.Types.Any(x => x.Name == DarkName) && target.SideIndex != user.SideIndex)
return true;
if (aiMove.Move.Category != MoveCategory.Status && moveType.Name == GroundName && target.IsFloating)
return true;
@@ -375,7 +370,8 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
return false;
}
private int GetMoveScore(IPokemon user, AIMoveState aiMove, IBattle battle, IReadOnlyList<IPokemon>? targets = null)
private int GetMoveScore(IBattlePokemon user, AIMoveState aiMove, IBattle battle,
IReadOnlyList<IBattlePokemon>? targets = null)
{
var score = MoveBaseScore;
if (targets != null)
@@ -411,7 +407,8 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
return score;
}
private int GetMoveScoreAgainstTarget(IPokemon user, AIMoveState aiMove, IPokemon target, IBattle battle)
private int GetMoveScoreAgainstTarget(IBattlePokemon user, AIMoveState aiMove, IBattlePokemon target,
IBattle battle)
{
if (_skillFlags.CanPredictMoveFailure && PredictMoveFailureAgainstTarget(user, aiMove, target, battle))
{
@@ -427,8 +424,7 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
_handlers.ApplyGenerateMoveAgainstTargetScoreModifiers(this, moveOption, ref score);
}
if (aiMove.Move.Target.TargetsFoe() && target.BattleData?.SideIndex == user.BattleData?.SideIndex &&
target.BattleData?.Position != user.BattleData?.Position)
if (aiMove.Move.Target.TargetsFoe() && target.SideIndex == user.SideIndex && target.Position != user.Position)
{
if (score == MoveUselessScore)
return -1;
@@ -442,7 +438,7 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
private static readonly StringKey OvercoatName = new("overcoat");
private static readonly StringKey SafetyGogglesName = new("safety_goggles");
private static bool AffectedByPowder(IPokemon pokemon)
private static bool AffectedByPowder(IBattlePokemon pokemon)
{
if (pokemon.Types.Any(x => x.Name == GrassName))
return false;
@@ -456,7 +452,7 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
private static readonly StringKey TruantName = "truant";
private static readonly StringKey TruantEffectName = "truant_effect";
private static bool CanAttack(IPokemon pokemon)
private static bool CanAttack(IBattlePokemon pokemon)
{
if (pokemon.Volatile.Contains("requires_recharge"))
return false;

View File

@@ -6,7 +6,7 @@ namespace PkmnLib.Dynamic.AI.Explicit;
/// <summary>
/// An option where a move is used against a target
/// </summary>
public record struct MoveOption(AIMoveState Move, IBattle Battle, IPokemon? Target, uint EstimatedDamage = 0);
public record struct MoveOption(AIMoveState Move, IBattle Battle, IBattlePokemon? Target, uint EstimatedDamage = 0);
/// <summary>
/// A function that takes an explicit AI and a move option and returns a boolean value.
@@ -16,8 +16,8 @@ public delegate bool AIBoolHandler(IExplicitAI ai, MoveOption option);
/// <summary>
/// A function for returning whether a Pokemon should switch.
/// </summary>
public delegate bool AISwitchBoolHandler(IExplicitAI ai, IPokemon pokemon, IBattle battle,
IReadOnlyList<IPokemon> reserves);
public delegate bool AISwitchBoolHandler(IExplicitAI ai, IBattlePokemon pokemon, IBattle battle,
IReadOnlyList<IBattlePokemon> reserves);
/// <summary>
/// A function for returning the base power of a move.
@@ -112,7 +112,7 @@ public interface IReadOnlyExplicitAIHandlers
/// <summary>
/// Indicates whether a Pokemon should switch into another Pokemon
/// </summary>
bool ShouldSwitch(IExplicitAI ai, IPokemon pokemon, IBattle battle, IReadOnlyList<IPokemon> reserves);
bool ShouldSwitch(IExplicitAI ai, IBattlePokemon pokemon, IBattle battle, IReadOnlyList<IBattlePokemon> reserves);
/// <summary>
/// Functions that indicate whether a Pokemon should NOT switch
@@ -122,7 +122,8 @@ public interface IReadOnlyExplicitAIHandlers
/// <summary>
/// Indicates whether a Pokemon should NOT switch into another Pokemon
/// </summary>
bool ShouldNotSwitch(IExplicitAI ai, IPokemon pokemon, IBattle battle, IReadOnlyList<IPokemon> reserves);
bool ShouldNotSwitch(IExplicitAI ai, IBattlePokemon pokemon, IBattle battle,
IReadOnlyList<IBattlePokemon> reserves);
/// <summary>
/// Scores abilities
@@ -246,7 +247,8 @@ public class ExplicitAIHandlers : IReadOnlyExplicitAIHandlers
ShouldSwitchFunctions;
/// <inheritdoc />
public bool ShouldSwitch(IExplicitAI ai, IPokemon pokemon, IBattle battle, IReadOnlyList<IPokemon> reserves)
public bool ShouldSwitch(IExplicitAI ai, IBattlePokemon pokemon, IBattle battle,
IReadOnlyList<IBattlePokemon> reserves)
{
var shouldSwitch = false;
foreach (var (_, handler) in ShouldSwitchFunctions)
@@ -272,7 +274,8 @@ public class ExplicitAIHandlers : IReadOnlyExplicitAIHandlers
public FunctionHandlerDictionary<AIScoreMoveHandler> AbilityRanking = [];
/// <inheritdoc />
public bool ShouldNotSwitch(IExplicitAI ai, IPokemon pokemon, IBattle battle, IReadOnlyList<IPokemon> reserves)
public bool ShouldNotSwitch(IExplicitAI ai, IBattlePokemon pokemon, IBattle battle,
IReadOnlyList<IBattlePokemon> reserves)
{
var shouldNotSwitch = false;
foreach (var (_, handler) in ShouldNotSwitchFunctions)

View File

@@ -15,9 +15,9 @@ public class HighestDamageAI : PokemonAI
}
/// <inheritdoc />
public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon)
public override ITurnChoice GetChoice(IBattle battle, IBattlePokemon pokemon)
{
var opponentSide = pokemon.BattleData!.SideIndex == 0 ? (byte)1 : (byte)0;
var opponentSide = pokemon.SideIndex == 0 ? (byte)1 : (byte)0;
var opponent = battle.Sides[opponentSide].Pokemon.WhereNotNull().FirstOrDefault(x => x.IsUsable);
var moves = pokemon.Moves.WhereNotNull().Where(x => battle.CanUse(new MoveChoice(pokemon, x, opponentSide, 0)))
.ToList();

View File

@@ -14,5 +14,5 @@ public class PassTurnAI : PokemonAI
}
/// <inheritdoc />
public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon) => new PassChoice(pokemon);
public override ITurnChoice GetChoice(IBattle battle, IBattlePokemon pokemon) => new PassChoice(pokemon);
}

View File

@@ -26,70 +26,69 @@ public abstract class PokemonAI
/// <summary>
/// Gets the choice for the Pokémon.
/// </summary>
public abstract ITurnChoice GetChoice(IBattle battle, IPokemon pokemon);
public abstract ITurnChoice GetChoice(IBattle battle, IBattlePokemon pokemon);
/// <summary>
/// For a given user and move, returns the valid targets for that move.
/// </summary>
public IEnumerable<(byte side, byte position)> GetValidTargetsForMove(IPokemon user, ILearnedMove move)
public IEnumerable<(byte side, byte position)> GetValidTargetsForMove(IBattlePokemon user, ILearnedMove move)
{
var userBattleData = user.BattleData!;
switch (move.MoveData.Target)
{
case MoveTarget.Adjacent:
yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
yield return (GetOppositeSide(user.SideIndex), user.Position);
break;
case MoveTarget.AdjacentAlly:
if (userBattleData.Position > 0)
yield return (userBattleData.SideIndex, (byte)(userBattleData.Position - 1));
if (userBattleData.Battle.PositionsPerSide > userBattleData.Position + 1)
yield return (userBattleData.SideIndex, (byte)(userBattleData.Position + 1));
if (user.Position > 0)
yield return (user.SideIndex, (byte)(user.Position - 1));
if (user.Battle.PositionsPerSide > user.Position + 1)
yield return (user.SideIndex, (byte)(user.Position + 1));
break;
case MoveTarget.AdjacentAllySelf:
if (userBattleData.Position > 0)
yield return (userBattleData.SideIndex, (byte)(userBattleData.Position - 1));
if (userBattleData.Battle.PositionsPerSide > userBattleData.Position + 1)
yield return (userBattleData.SideIndex, (byte)(userBattleData.Position + 1));
yield return (userBattleData.SideIndex, userBattleData.Position);
if (user.Position > 0)
yield return (user.SideIndex, (byte)(user.Position - 1));
if (user.Battle.PositionsPerSide > user.Position + 1)
yield return (user.SideIndex, (byte)(user.Position + 1));
yield return (user.SideIndex, user.Position);
break;
case MoveTarget.AdjacentOpponent:
yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
if (userBattleData.Position > 0)
yield return (GetOppositeSide(userBattleData.SideIndex), (byte)(userBattleData.Position - 1));
if (userBattleData.Battle.PositionsPerSide > userBattleData.Position + 1)
yield return (GetOppositeSide(userBattleData.SideIndex), (byte)(userBattleData.Position + 1));
yield return (GetOppositeSide(user.SideIndex), user.Position);
if (user.Position > 0)
yield return (GetOppositeSide(user.SideIndex), (byte)(user.Position - 1));
if (user.Battle.PositionsPerSide > user.Position + 1)
yield return (GetOppositeSide(user.SideIndex), (byte)(user.Position + 1));
break;
case MoveTarget.All:
yield return (userBattleData.SideIndex, userBattleData.Position);
yield return (user.SideIndex, user.Position);
break;
case MoveTarget.AllAdjacent:
yield return (userBattleData.SideIndex, userBattleData.Position);
yield return (user.SideIndex, user.Position);
break;
case MoveTarget.AllAdjacentOpponent:
yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
yield return (GetOppositeSide(user.SideIndex), user.Position);
break;
case MoveTarget.AllAlly:
yield return (userBattleData.SideIndex, userBattleData.Position);
yield return (user.SideIndex, user.Position);
break;
case MoveTarget.AllOpponent:
yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
yield return (GetOppositeSide(user.SideIndex), user.Position);
break;
case MoveTarget.Any:
foreach (var side in userBattleData.Battle.Sides)
foreach (var side in user.Battle.Sides)
{
foreach (var pokemon in side.Pokemon)
{
if (pokemon?.BattleData == null)
if (pokemon == null)
continue;
yield return (side.Index, pokemon.BattleData!.Position);
yield return (side.Index, pokemon.Position);
}
}
break;
case MoveTarget.RandomOpponent:
yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
yield return (GetOppositeSide(user.SideIndex), user.Position);
break;
case MoveTarget.SelfUse:
yield return (userBattleData.SideIndex, userBattleData.Position);
yield return (user.SideIndex, user.Position);
break;
default:
throw new ArgumentOutOfRangeException();

View File

@@ -18,9 +18,9 @@ public class PrescientAI : PokemonAI
}
/// <inheritdoc />
public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon)
public override ITurnChoice GetChoice(IBattle battle, IBattlePokemon pokemon)
{
var opponentSide = pokemon.BattleData!.SideIndex == 0 ? (byte)1 : (byte)0;
var opponentSide = pokemon.SideIndex == 0 ? (byte)1 : (byte)0;
var moves = pokemon.Moves.WhereNotNull().Where(x => battle.CanUse(new MoveChoice(pokemon, x, opponentSide, 0)))
.ToList();
@@ -34,13 +34,13 @@ public class PrescientAI : PokemonAI
}
private static IEnumerable<(ITurnChoice Choice, float Score)> ScoreChoices(IBattle battle,
IReadOnlyList<ILearnedMove> moves, IPokemon pokemon)
IReadOnlyList<ILearnedMove> moves, IBattlePokemon pokemon)
{
var opponentSide = pokemon.BattleData!.SideIndex == 0 ? (byte)1 : (byte)0;
var opponentSide = pokemon.SideIndex == 0 ? (byte)1 : (byte)0;
foreach (var learnedMoveOriginal in moves.WhereNotNull())
{
var battleClone = battle.DeepClone();
var pokemonClone = battleClone.Sides[pokemon.BattleData!.SideIndex].Pokemon[pokemon.BattleData.Position]!;
var pokemonClone = battleClone.Sides[pokemon.SideIndex].Pokemon[pokemon.Position]!;
var learnedMove = pokemonClone.Moves.WhereNotNull()
.First(m => m.MoveData.Name == learnedMoveOriginal.MoveData.Name);
var choice = new MoveChoice(pokemonClone, learnedMove, opponentSide, 0);
@@ -57,17 +57,16 @@ public class PrescientAI : PokemonAI
}
if (battleClone.TrySetChoice(choice))
{
var score = CalculateScore(battleClone.Parties[pokemon.BattleData.SideIndex],
battleClone.Parties[opponentSide]);
var score = CalculateScore(battleClone.Parties[pokemon.SideIndex], battleClone.Parties[opponentSide]);
var realChoice = new MoveChoice(pokemon, learnedMoveOriginal, opponentSide, 0);
yield return (realChoice, score);
}
}
}
private static ITurnChoice GetOpponentChoice(IBattle battle, IPokemon pokemon)
private static ITurnChoice GetOpponentChoice(IBattle battle, IBattlePokemon pokemon)
{
var opponentSide = pokemon.BattleData!.SideIndex == 0 ? (byte)1 : (byte)0;
var opponentSide = pokemon.SideIndex == 0 ? (byte)1 : (byte)0;
var opponent = battle.Sides[opponentSide].Pokemon[0];
if (opponent is null)
{

View File

@@ -19,7 +19,7 @@ public class RandomAI : PokemonAI
}
/// <inheritdoc />
public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon)
public override ITurnChoice GetChoice(IBattle battle, IBattlePokemon pokemon)
{
var moves = pokemon.Moves.WhereNotNull().Where(x => x.CurrentPp > 0).ToList();
while (moves.Count > 0)
@@ -28,7 +28,7 @@ public class RandomAI : PokemonAI
var targets = GetValidTargetsForMove(pokemon, move).ToArray();
if (move.MoveData.Category is MoveCategory.Physical or MoveCategory.Special)
{
targets = targets.Where(x => x.side != pokemon.BattleData!.SideIndex).ToArray();
targets = targets.Where(x => x.side != pokemon.SideIndex).ToArray();
}
if (targets.Length == 0)
{
@@ -43,7 +43,7 @@ public class RandomAI : PokemonAI
}
moves.Remove(move);
}
return battle.Library.MiscLibrary.ReplacementChoice(pokemon,
pokemon.BattleData!.SideIndex == 0 ? (byte)1 : (byte)0, pokemon.BattleData.Position);
return battle.Library.MiscLibrary.ReplacementChoice(pokemon, pokemon.SideIndex == 0 ? (byte)1 : (byte)0,
pokemon.Position);
}
}

View File

@@ -15,7 +15,7 @@ public static class MoveTurnExecutor
{
internal static void ExecuteMoveChoice(IBattle battle, IMoveChoice moveChoice)
{
moveChoice.User.BattleData!.LastMoveChoice = moveChoice;
moveChoice.User.LastMoveChoice = moveChoice;
var chosenMove = moveChoice.ChosenMove;
var useMove = chosenMove.MoveData;
@@ -126,7 +126,7 @@ public static class MoveTurnExecutor
private static readonly ThreadLocal<List<TypeIdentifier>> TypeListCache = new(() => []);
private static void ExecuteMoveChoiceForTarget(IBattle battle, IExecutingMove executingMove, IPokemon target)
private static void ExecuteMoveChoiceForTarget(IBattle battle, IExecutingMove executingMove, IBattlePokemon target)
{
var failed = false;
target.RunScriptHook<IScriptFailIncomingMove>(x => x.FailIncomingMove(executingMove, target, ref failed));

View File

@@ -11,7 +11,8 @@ public static class TargetResolver
/// <summary>
/// Get the targets of a move based on the target type, and the selected side and position to target.
/// </summary>
public static IReadOnlyList<IPokemon?> ResolveTargets(IBattle battle, byte side, byte position, MoveTarget target)
public static IReadOnlyList<IBattlePokemon?> ResolveTargets(IBattle battle, byte side, byte position,
MoveTarget target)
{
return target switch
{
@@ -29,13 +30,10 @@ public static class TargetResolver
/// <summary>
/// Validates whether a given target is valid for a move choice. Returns true if the target is valid.
/// </summary>
public static bool IsValidTarget(byte side, byte position, MoveTarget target, IPokemon user)
public static bool IsValidTarget(byte side, byte position, MoveTarget target, IBattlePokemon user)
{
var userBattleData = user.BattleData;
if (userBattleData == null)
throw new ArgumentNullException(nameof(user.BattleData));
var userSide = userBattleData.SideIndex;
var userPosition = userBattleData.Position;
var userSide = user.SideIndex;
var userPosition = user.Position;
switch (target)
{
@@ -80,7 +78,7 @@ public static class TargetResolver
throw new ArgumentOutOfRangeException(nameof(target), target, null);
}
private static IReadOnlyList<IPokemon?> GetAllTargets(IBattle battle) =>
private static IReadOnlyList<IBattlePokemon?> GetAllTargets(IBattle battle) =>
battle.Sides.SelectMany(x => x.Pokemon).ToList();
private static byte GetOppositeSide(byte side) => side == 0 ? (byte)1 : (byte)0;
@@ -89,7 +87,7 @@ public static class TargetResolver
/// Gets all Pokémon that are adjacent to of directly opposite of a Pokémon. This means the target,
/// the Pokémon left of it, the Pokémon right of it, and the Pokémon opposite of it.
/// </summary>
private static IReadOnlyList<IPokemon?> GetAllAdjacentAndOpponent(IBattle battle, byte side, byte position)
private static IReadOnlyList<IBattlePokemon?> GetAllAdjacentAndOpponent(IBattle battle, byte side, byte position)
{
var left = position - 1;
var right = position + 1;
@@ -123,7 +121,7 @@ public static class TargetResolver
];
}
private static IReadOnlyList<IPokemon?> GetAllAdjacent(IBattle battle, byte side, byte position)
private static IReadOnlyList<IBattlePokemon?> GetAllAdjacent(IBattle battle, byte side, byte position)
{
var left = position - 1;
var right = position + 1;

View File

@@ -85,7 +85,7 @@ public static class TurnRunner
return;
if (!choice.User.IsUsable)
return;
if (choice.User.BattleData?.IsOnBattlefield != true)
if (!choice.User.IsOnBattlefield)
return;
switch (choice)
{
@@ -108,9 +108,6 @@ public static class TurnRunner
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<IScriptPreventSelfSwitch>(script =>
script.PreventSelfSwitch(fleeChoice, ref preventSwitch));
@@ -118,7 +115,7 @@ public static class TurnRunner
return;
foreach (var side in battle.Sides)
{
if (side.Index == battleData.SideIndex)
if (side.Index == user.SideIndex)
continue;
foreach (var pokemon in side.Pokemon.WhereNotNull())
{
@@ -129,16 +126,12 @@ public static class TurnRunner
}
}
user.Volatile.Clear();
var userSide = battle.Sides[battleData.SideIndex];
userSide.SwapPokemon(battleData.Position, fleeChoice.SwitchTo);
user.BattleSide.SwapPokemon(user.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;
@@ -150,7 +143,7 @@ public static class TurnRunner
foreach (var side in battle.Sides)
{
if (side.Index == battleData.SideIndex)
if (side.Index == user.SideIndex)
continue;
foreach (var pokemon in side.Pokemon.WhereNotNull())
{
@@ -167,8 +160,7 @@ public static class TurnRunner
return;
}
var userSide = battle.Sides[battleData.SideIndex];
userSide.MarkAsFled();
user.BattleSide.MarkAsFled();
battle.EventHook.Invoke(new FleeEvent(user, true));
battle.ValidateBattleState();
}
@@ -176,9 +168,6 @@ public static class TurnRunner
private static void ExecuteItemChoice(IBattle battle, IItemChoice itemChoice)
{
var user = itemChoice.User;
var battleData = user.BattleData;
if (battleData == null)
return;
var target = itemChoice.GetTargetPokemon(battle);
battle.EventHook.Invoke(new ItemUseEvent(user, itemChoice.Item));
itemChoice.Item.RunItemScript(battle.Library.ScriptResolver, target ?? user, user, battle, battle.EventHook);

View File

@@ -12,7 +12,7 @@ public record AbilityTriggerEvent : IEventData
/// <summary>
/// The Pokémon whose ability is being triggered.
/// </summary>
public IPokemon Pokemon { get; }
public IBattlePokemon Pokemon { get; }
/// <summary>
/// The ability that is being triggered for the Pokémon.
@@ -22,7 +22,7 @@ public record AbilityTriggerEvent : IEventData
public Dictionary<StringKey, object?>? Metadata { get; init; } = null;
/// <inheritdoc cref="AbilityTriggerEvent"/>
public AbilityTriggerEvent(IPokemon pokemon)
public AbilityTriggerEvent(IBattlePokemon pokemon)
{
Pokemon = pokemon;
Ability = pokemon.ActiveAbility;

View File

@@ -10,7 +10,7 @@ namespace PkmnLib.Dynamic.Events;
public class CaptureAttemptEvent : IEventData
{
/// <inheritdoc cref="CaptureAttemptEvent"/>
public CaptureAttemptEvent(IPokemon target, CaptureResult result, IItem captureItem)
public CaptureAttemptEvent(IBattlePokemon target, CaptureResult result, IItem captureItem)
{
Target = target;
Result = result;
@@ -20,7 +20,7 @@ public class CaptureAttemptEvent : IEventData
/// <summary>
/// The Pokémon that is being captured.
/// </summary>
public IPokemon Target { get; init; }
public IBattlePokemon Target { get; init; }
/// <summary>
/// The result of the capture attempt.

View File

@@ -8,7 +8,7 @@ namespace PkmnLib.Dynamic.Events;
public record DamageEvent : IEventData
{
/// <inheritdoc cref="DamageEvent"/>
public DamageEvent(IPokemon pokemon, uint previousHealth, uint newHealth, DamageSource source)
public DamageEvent(IBattlePokemon pokemon, uint previousHealth, uint newHealth, DamageSource source)
{
Pokemon = pokemon;
PreviousHealth = previousHealth;
@@ -19,7 +19,7 @@ public record DamageEvent : IEventData
/// <summary>
/// The Pokemon that took damage.
/// </summary>
public IPokemon Pokemon { get; init; }
public IBattlePokemon Pokemon { get; init; }
/// <summary>
/// The previous health of the Pokemon.

View File

@@ -5,11 +5,11 @@ namespace PkmnLib.Dynamic.Events;
public class DisplaySpeciesChangeEvent : IEventData
{
public IPokemon Pokemon { get; }
public IBattlePokemon Pokemon { get; }
public ISpecies? Species { get; }
public IForm? Form { get; }
public DisplaySpeciesChangeEvent(IPokemon pokemon, ISpecies? species, IForm? form)
public DisplaySpeciesChangeEvent(IBattlePokemon pokemon, ISpecies? species, IForm? form)
{
Pokemon = pokemon;
Species = species;

View File

@@ -8,7 +8,7 @@ namespace PkmnLib.Dynamic.Events;
public class FaintEvent : IEventData
{
/// <inheritdoc cref="FaintEvent"/>
public FaintEvent(IPokemon pokemon)
public FaintEvent(IBattlePokemon pokemon)
{
Pokemon = pokemon;
}
@@ -16,7 +16,7 @@ public class FaintEvent : IEventData
/// <summary>
/// The Pokemon that fainted.
/// </summary>
public IPokemon Pokemon { get; init; }
public IBattlePokemon Pokemon { get; init; }
/// <inheritdoc />
public EventBatchId BatchId { get; init; } = new();

View File

@@ -8,7 +8,7 @@ namespace PkmnLib.Dynamic.Events;
public class FleeEvent : IEventData
{
/// <inheritdoc cref="FleeEvent"/>
public FleeEvent(IPokemon pokemon, bool success)
public FleeEvent(IBattlePokemon pokemon, bool success)
{
Pokemon = pokemon;
Success = success;
@@ -17,7 +17,7 @@ public class FleeEvent : IEventData
/// <summary>
/// The Pokémon that attempted to flee.
/// </summary>
public IPokemon Pokemon { get; }
public IBattlePokemon Pokemon { get; }
/// <summary>
/// Indicates whether the flee attempt was successful.

View File

@@ -5,13 +5,13 @@ namespace PkmnLib.Dynamic.Events;
public record ItemUseEvent : IEventData
{
public ItemUseEvent(IPokemon pokemon, IItem itemUsed)
public ItemUseEvent(IBattlePokemon pokemon, IItem itemUsed)
{
Pokemon = pokemon;
ItemUsed = itemUsed;
}
public IPokemon Pokemon { get; set; }
public IBattlePokemon Pokemon { get; set; }
public IItem ItemUsed { get; set; }
/// <inheritdoc />

View File

@@ -20,10 +20,10 @@ public class MoveHitEvent : IEventData
/// <summary>
/// The target of the move.
/// </summary>
public IPokemon Target { get; }
public IBattlePokemon Target { get; }
/// <inheritdoc cref="MoveHitEvent"/>
public MoveHitEvent(IExecutingMove executingMove, IHitData hitData, IPokemon target)
public MoveHitEvent(IExecutingMove executingMove, IHitData hitData, IBattlePokemon target)
{
ExecutingMove = executingMove;
HitData = hitData;

View File

@@ -5,9 +5,9 @@ namespace PkmnLib.Dynamic.Events;
public class MoveInvulnerableEvent : IEventData
{
public IExecutingMove ExecutingMove { get; }
public IPokemon Target { get; }
public IBattlePokemon Target { get; }
public MoveInvulnerableEvent(IExecutingMove executingMove, IPokemon target)
public MoveInvulnerableEvent(IExecutingMove executingMove, IBattlePokemon target)
{
ExecutingMove = executingMove;
Target = target;

View File

@@ -9,7 +9,7 @@ namespace PkmnLib.Dynamic.Events;
public class StatBoostEvent : IEventData
{
/// <inheritdoc cref="StatBoostEvent" />
public StatBoostEvent(IPokemon pokemon, Statistic statistic, sbyte oldBoost, sbyte newBoost)
public StatBoostEvent(IBattlePokemon pokemon, Statistic statistic, sbyte oldBoost, sbyte newBoost)
{
Pokemon = pokemon;
Statistic = statistic;
@@ -20,7 +20,7 @@ public class StatBoostEvent : IEventData
/// <summary>
/// The Pokemon that had its stat boosted.
/// </summary>
public IPokemon Pokemon { get; }
public IBattlePokemon Pokemon { get; }
/// <summary>
/// The statistic that was boosted.

View File

@@ -9,7 +9,7 @@ namespace PkmnLib.Dynamic.Events;
public record StatusChangeEvent : IEventData
{
/// <inheritdoc cref="StatusChangeEvent"/>
public StatusChangeEvent(IPokemon pokemon, StringKey? previousStatus, StringKey? newStatus)
public StatusChangeEvent(IBattlePokemon pokemon, StringKey? previousStatus, StringKey? newStatus)
{
Pokemon = pokemon;
PreviousStatus = previousStatus;
@@ -19,7 +19,7 @@ public record StatusChangeEvent : IEventData
/// <summary>
/// The Pokémon whose status has changed.
/// </summary>
public IPokemon Pokemon { get; }
public IBattlePokemon Pokemon { get; }
/// <summary>
/// The new status of the Pokémon after the change.

View File

@@ -8,7 +8,7 @@ namespace PkmnLib.Dynamic.Events;
public class SwitchEvent : IEventData
{
/// <inheritdoc cref="SwitchEvent"/>
public SwitchEvent(byte sideIndex, byte position, IPokemon? pokemon)
public SwitchEvent(byte sideIndex, byte position, IBattlePokemon? pokemon)
{
SideIndex = sideIndex;
Position = position;
@@ -28,7 +28,7 @@ public class SwitchEvent : IEventData
/// <summary>
/// The Pokémon that is switching in. If null, no Pokémon is switching in, and the slot is empty after the switch.
/// </summary>
public IPokemon? Pokemon { get; init; }
public IBattlePokemon? Pokemon { get; init; }
/// <inheritdoc />
public EventBatchId BatchId { get; init; }

View File

@@ -21,15 +21,16 @@ public interface IBattleStatCalculator
/// <summary>
/// Calculate all the boosted stats of a Pokemon, including stat boosts.
/// </summary>
void CalculateBoostedStats(IPokemon pokemon, StatisticSet<uint> stats);
void CalculateBoostedStats(IBattlePokemon pokemon, StatisticSet<uint> stats);
/// <summary>
/// Calculate a single boosted stat of a Pokemon, including stat boosts.
/// </summary>
uint CalculateBoostedStat(IPokemon pokemon, Statistic stat);
uint CalculateBoostedStat(IBattlePokemon pokemon, Statistic stat);
/// <summary>
/// Calculates the accuracy for a move, taking into account any accuracy modifiers.
/// </summary>
byte CalculateModifiedAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, byte moveAccuracy);
byte CalculateModifiedAccuracy(IExecutingMove executingMove, IBattlePokemon target, byte hitIndex,
byte moveAccuracy);
}

View File

@@ -46,5 +46,5 @@ public interface ICaptureLibrary
/// <summary>
/// Attempts to capture a Pokémon using a specified item (e.g., Poké Ball).
/// </summary>
CaptureResult TryCapture(IPokemon target, IItem captureItem, IBattleRandom random);
CaptureResult TryCapture(IBattlePokemon target, IItem captureItem, IBattleRandom random);
}

View File

@@ -11,16 +11,16 @@ public interface IDamageCalculator
/// <summary>
/// Calculate the damage for a given hit on a Pokemon.
/// </summary>
uint GetDamage(IExecutingMove? executingMove, MoveCategory category, IPokemon user, IPokemon target,
uint GetDamage(IExecutingMove? executingMove, MoveCategory category, IBattlePokemon user, IBattlePokemon target,
int targetCount, byte hitNumber, IHitData hitData);
/// <summary>
/// Calculate the base power for a given hit on a Pokemon.
/// </summary>
ushort GetBasePower(IExecutingMove executingMove, IPokemon target, byte hitNumber, IHitData hitData);
ushort GetBasePower(IExecutingMove executingMove, IBattlePokemon target, byte hitNumber, IHitData hitData);
/// <summary>
/// Returns whether a specified hit should be critical or not.
/// </summary>
bool IsCritical(IBattle battle, IExecutingMove executingMove, IPokemon target, byte hitNumber);
bool IsCritical(IBattle battle, IExecutingMove executingMove, IBattlePokemon target, byte hitNumber);
}

View File

@@ -13,7 +13,7 @@ public interface IMiscLibrary
/// Returns the choice that's used when a Pokemon is unable to make the move choice it wants to, or when it has no
/// moves left, yet wants to make a move.
/// </summary>
ITurnChoice ReplacementChoice(IPokemon user, byte targetSide, byte targetPosition);
ITurnChoice ReplacementChoice(IBattlePokemon user, byte targetSide, byte targetPosition);
/// <summary>
/// Returns whether the given choice is the choice that is used when the user is unable to make a move choice.

View File

@@ -92,7 +92,7 @@ public interface IBattle : IScriptSource, IDeepCloneable, IDisposable
/// <summary>
/// Get a Pokemon on the battlefield, on a specific side and an index on that side.
/// </summary>
IPokemon? GetPokemon(byte side, byte position);
IBattlePokemon? GetPokemon(byte side, byte position);
/// <summary>
/// Returns whether a slot on the battlefield can still be filled. If no party is responsible
@@ -116,7 +116,7 @@ public interface IBattle : IScriptSource, IDeepCloneable, IDisposable
/// Checks whether a Pokemon has a forced turn choice. If it does, this returns true and the choice
/// is set in the out parameter. If it does not, this returns false and the out parameter is null.
/// </summary>
bool HasForcedTurn(IPokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice);
bool HasForcedTurn(IBattlePokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice);
/// <summary>
/// Checks whether a choice is actually possible.
@@ -202,6 +202,8 @@ public class BattleImpl : ScriptSource, IBattle
Sides = sides;
Random = randomSeed.HasValue ? new BattleRandomImpl(randomSeed.Value) : new BattleRandomImpl();
EventHook = new EventHook();
foreach (var party in parties)
party.Initialize(this);
}
/// <inheritdoc />
@@ -248,12 +250,22 @@ public class BattleImpl : ScriptSource, IBattle
public BattleChoiceQueue? ChoiceQueue { get; private set; }
/// <inheritdoc />
public IPokemon? GetPokemon(byte side, byte position) => Sides[side].Pokemon[position];
public IBattlePokemon? GetPokemon(byte side, byte position) => Sides[side].Pokemon[position];
/// <inheritdoc />
public bool CanSlotBeFilled(byte side, byte position) => Parties.Any(x =>
x.IsResponsibleForIndex(new ResponsibleIndex(side, position)) && x.HasUsablePokemonNotInField());
private readonly List<IPokemon> _capturedPokemon = [];
/// <summary>
/// Attaches battle-wide result data, such as the captured Pokémon, to a result.
/// </summary>
private BattleResult FinalizeResult(BattleResult result) => result with
{
CapturedPokemon = _capturedPokemon.ToList(),
};
/// <inheritdoc />
public void ValidateBattleState()
{
@@ -265,7 +277,7 @@ public class BattleImpl : ScriptSource, IBattle
{
if (side.HasFledBattle)
{
Result = BattleResult.Inconclusive;
Result = FinalizeResult(BattleResult.Inconclusive);
HasEnded = true;
return;
}
@@ -283,13 +295,13 @@ public class BattleImpl : ScriptSource, IBattle
// If every side is defeated, the battle is a draw
if (!survivingSideExists)
{
Result = BattleResult.Inconclusive;
Result = FinalizeResult(BattleResult.Inconclusive);
HasEnded = true;
return;
}
// If only one side is left, that side has won
Result = BattleResult.Conclusive(survivingSide!.Index);
Result = FinalizeResult(BattleResult.Conclusive(survivingSide!.Index));
HasEnded = true;
}
@@ -297,22 +309,15 @@ public class BattleImpl : ScriptSource, IBattle
public void ForceEndBattle()
{
HasEnded = true;
Result = BattleResult.Inconclusive;
Result = FinalizeResult(BattleResult.Inconclusive);
}
/// <inheritdoc />
public bool HasForcedTurn(IPokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice)
public bool HasForcedTurn(IBattlePokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice)
{
var battleData = pokemon.BattleData;
if (battleData == null)
{
choice = null;
return false;
}
ITurnChoice? forcedChoice = null;
pokemon.RunScriptHook<IScriptForceTurnSelection>(script =>
script.ForceTurnSelection(this, battleData.SideIndex, battleData.Position, ref forcedChoice));
script.ForceTurnSelection(this, pokemon.SideIndex, pokemon.Position, ref forcedChoice));
choice = forcedChoice;
return choice != null;
}
@@ -346,7 +351,7 @@ public class BattleImpl : ScriptSource, IBattle
if (!switchChoice.SwitchTo.IsUsable)
return false;
// Can't switch to a Pokémon already on the field
if (switchChoice.SwitchTo.BattleData is { IsOnBattlefield: true })
if (switchChoice.SwitchTo.IsOnBattlefield)
return false;
if (switchChoice.SwitchTo == switchChoice.User)
return false;
@@ -389,10 +394,10 @@ public class BattleImpl : ScriptSource, IBattle
{
if (!CanUse(choice))
return false;
if (choice.User.BattleData?.IsOnBattlefield != true)
if (!choice.User.IsOnBattlefield)
return false;
var side = Sides[choice.User.BattleData!.SideIndex];
side.SetChoice(choice.User.BattleData!.Position, choice);
var side = Sides[choice.User.SideIndex];
side.SetChoice(choice.User.Position, choice);
CheckChoicesSetAndRun();
return true;
}
@@ -555,8 +560,8 @@ public class BattleImpl : ScriptSource, IBattle
if (attemptCapture.IsCaught)
{
target.MarkAsCaught();
var side = Sides[target.BattleData!.SideIndex];
side.ForceClearPokemonFromField(target.BattleData.Position);
_capturedPokemon.Add(target.UnderlyingPokemon);
target.BattleSide.ForceClearPokemonFromField(target.Position);
}
EventHook.Invoke(new CaptureAttemptEvent(target, attemptCapture, item));
@@ -592,9 +597,9 @@ public class BattleImpl : ScriptSource, IBattle
{
foreach (var party in Parties)
{
foreach (var pokemon in party.Party.WhereNotNull())
foreach (var pokemon in party.BattlePokemon.WhereNotNull())
{
pokemon.ClearBattleData();
pokemon.OnBattleEnd();
}
}
_weatherScript.Clear();

View File

@@ -87,7 +87,7 @@ public class BattleChoiceQueue : IDeepCloneable
/// <returns>
/// Returns true if the Pokémon was found and moved, false otherwise.
/// </returns>
public bool MovePokemonChoiceNext(IPokemon pokemon)
public bool MovePokemonChoiceNext(IBattlePokemon pokemon)
{
var index = Array.FindIndex(_choices, _currentIndex, choice => choice?.User == pokemon);
if (index == -1)
@@ -110,7 +110,7 @@ public class BattleChoiceQueue : IDeepCloneable
/// <returns>
/// Returns true if the Pokémon was found and moved, false otherwise.
/// </returns>
public bool MovePokemonChoiceLast(IPokemon pokemon)
public bool MovePokemonChoiceLast(IBattlePokemon pokemon)
{
var index = Array.FindIndex(_choices, _currentIndex, choice => choice?.User == pokemon);
if (index == -1)

View File

@@ -4,15 +4,35 @@ namespace PkmnLib.Dynamic.Models;
/// <summary>
/// A battle party is a wrapper around a Pokemon party that provides additional functionality for battles.
/// It indicates for which side and position the party is responsible.
/// It indicates for which side and position the party is responsible, and holds the battle-scoped
/// <see cref="IBattlePokemon"/> wrappers for the party's Pokémon.
/// </summary>
public interface IBattleParty : IDeepCloneable
{
/// <summary>
/// The backing Pokemon party.
/// The backing Pokemon party. Battle code should generally use <see cref="BattlePokemon"/> instead, as that
/// view contains the battle-scoped wrappers.
/// </summary>
IPokemonParty Party { get; }
/// <summary>
/// The battle-scoped view of the party. Index-aligned with <see cref="Party"/>. Only available after
/// <see cref="Initialize"/> has been called by the battle.
/// </summary>
IReadOnlyList<IBattlePokemon?> BattlePokemon { get; }
/// <summary>
/// Gets the battle-scoped wrapper for a Pokémon in this party. Returns null if the Pokémon is not in this
/// party. Accepts either the underlying Pokémon or the wrapper itself.
/// </summary>
IBattlePokemon? GetBattlePokemon(IPokemon pokemon);
/// <summary>
/// Initializes the battle-scoped wrappers for this party. This is called by the battle when it is created,
/// and should not be called by user code.
/// </summary>
void Initialize(IBattle battle);
/// <summary>
/// Whether the party is responsible for the specified side and position.
/// </summary>
@@ -26,7 +46,7 @@ public interface IBattleParty : IDeepCloneable
/// <summary>
/// Gets all usable Pokemon that are not currently in the field.
/// </summary>
IEnumerable<IPokemon> GetUsablePokemonNotInField();
IEnumerable<IBattlePokemon> GetUsablePokemonNotInField();
}
/// <summary>
@@ -39,6 +59,8 @@ public record struct ResponsibleIndex(byte Side, byte Position);
public class BattlePartyImpl : IBattleParty
{
private readonly ResponsibleIndex[] _responsibleIndices;
private IBattlePokemon?[] _battlePokemon = [];
private IBattle? _battle;
/// <inheritdoc cref="BattlePartyImpl"/>
public BattlePartyImpl(IPokemonParty party, ResponsibleIndex[] responsibleIndices)
@@ -50,14 +72,53 @@ public class BattlePartyImpl : IBattleParty
/// <inheritdoc />
public IPokemonParty Party { get; }
/// <inheritdoc />
public IReadOnlyList<IBattlePokemon?> BattlePokemon => _battlePokemon;
/// <inheritdoc />
public IBattlePokemon? GetBattlePokemon(IPokemon pokemon) =>
_battlePokemon.FirstOrDefault(x => x != null && (x == pokemon || x.UnderlyingPokemon == pokemon));
/// <inheritdoc />
public void Initialize(IBattle battle)
{
if (_battle != null)
throw new InvalidOperationException("This battle party has already been initialized for a battle.");
if (_responsibleIndices.Length == 0)
throw new InvalidOperationException("A battle party must be responsible for at least one position.");
var sideIndex = _responsibleIndices[0].Side;
if (_responsibleIndices.Any(x => x.Side != sideIndex))
throw new InvalidOperationException("A battle party can only be responsible for a single side.");
_battle = battle;
_battlePokemon = new IBattlePokemon?[Party.Count];
for (var i = 0; i < Party.Count; i++)
{
var pokemon = Party[i];
if (pokemon != null)
_battlePokemon[i] = new BattlePokemonImpl(pokemon, battle, sideIndex);
}
Party.OnSwapInto += (_, args) =>
{
var (pokemon, index) = args;
_battlePokemon[index] = pokemon == null ? null : new BattlePokemonImpl(pokemon, battle, sideIndex);
};
Party.OnSwap += (_, args) =>
{
var (index1, index2) = args;
(_battlePokemon[index1], _battlePokemon[index2]) = (_battlePokemon[index2], _battlePokemon[index1]);
};
}
/// <inheritdoc />
public bool IsResponsibleForIndex(ResponsibleIndex index) => _responsibleIndices.Contains(index);
/// <inheritdoc />
public bool HasUsablePokemonNotInField() =>
Party.WhereNotNull().Any(x => x.IsUsable && x.BattleData?.IsOnBattlefield != true);
_battlePokemon.WhereNotNull().Any(x => x.IsUsable && !x.IsOnBattlefield);
/// <inheritdoc />
public IEnumerable<IPokemon> GetUsablePokemonNotInField() =>
Party.WhereNotNull().Where(x => x.IsUsable && x.BattleData?.IsOnBattlefield != true);
public IEnumerable<IBattlePokemon> GetUsablePokemonNotInField() =>
_battlePokemon.WhereNotNull().Where(x => x.IsUsable && !x.IsOnBattlefield);
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ public interface IBattleRandom : IRandom, IDeepCloneable
/// rolls whether it triggers. As a side effect this run scripts to allow modifying this random
/// chance.
/// </summary>
bool EffectChance(float chance, IExecutingMove executingMove, IPokemon target, byte hitNumber);
bool EffectChance(float chance, IExecutingMove executingMove, IBattlePokemon target, byte hitNumber);
}
/// <inheritdoc cref="IBattleRandom"/>
@@ -36,7 +36,7 @@ public class BattleRandomImpl : RandomImpl, IBattleRandom
}
/// <inheritdoc />
public bool EffectChance(float chance, IExecutingMove executingMove, IPokemon target, byte hitNumber)
public bool EffectChance(float chance, IExecutingMove executingMove, IBattlePokemon target, byte hitNumber)
{
executingMove.RunScriptHook<IScriptChangeEffectChance>(script =>
script.ChangeEffectChance(executingMove, target, hitNumber, ref chance));

View File

@@ -30,4 +30,11 @@ public record struct BattleResult
/// The side that won the battle. If null, no side has won.
/// </summary>
public byte? WinningSide { get; }
/// <summary>
/// The Pokémon that were captured during the battle. These are the underlying Pokémon, so they remain
/// valid after the battle has been disposed. This is set for both conclusive and inconclusive results, as
/// captures complete the moment they happen.
/// </summary>
public IReadOnlyList<IPokemon> CapturedPokemon { get; init; } = [];
}

View File

@@ -24,7 +24,7 @@ public interface IBattleSide : IScriptSource, IDeepCloneable
/// <summary>
/// A list of Pokémon currently on the battlefield.
/// </summary>
IReadOnlyList<IPokemon?> Pokemon { get; }
IReadOnlyList<IBattlePokemon?> Pokemon { get; }
/// <summary>
/// The currently set choices for all Pokémon on the battlefield. Cleared when the turn starts.
@@ -84,7 +84,14 @@ public interface IBattleSide : IScriptSource, IDeepCloneable
/// 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.
/// </summary>
IPokemon? SwapPokemon(byte position, IPokemon? pokemon);
IBattlePokemon? SwapPokemon(byte position, IBattlePokemon? pokemon);
/// <summary>
/// Sends out a Pokémon by its persistent representation. This resolves the battle-scoped wrapper for
/// the Pokémon from the parties in the battle, and swaps it into the given position. This is the
/// convenient entry point for hosts, which generally hold the persistent party Pokémon.
/// </summary>
IBattlePokemon? SendOut(byte position, IPokemon pokemon);
/// <summary>
/// Swaps two Pokémon on the side.
@@ -94,7 +101,7 @@ public interface IBattleSide : IScriptSource, IDeepCloneable
/// <summary>
/// Checks whether a Pokemon is on the field in this side.
/// </summary>
bool IsPokemonOnSide(IPokemon pokemon);
bool IsPokemonOnSide(IBattlePokemon pokemon);
/// <summary>
/// Marks a slot as unfillable. This happens when no parties are able to fill the slot anymore.
@@ -166,7 +173,7 @@ public class BattleSideImpl : ScriptSource, IBattleSide
{
Index = index;
NumberOfPositions = numberOfPositions;
_pokemon = new IPokemon?[numberOfPositions];
_pokemon = new IBattlePokemon?[numberOfPositions];
_setChoices = new ITurnChoice?[numberOfPositions];
_fillablePositions = new bool[numberOfPositions];
for (byte i = 0; i < numberOfPositions; i++)
@@ -183,10 +190,10 @@ public class BattleSideImpl : ScriptSource, IBattleSide
/// <inheritdoc />
public byte NumberOfPositions { get; }
private readonly IPokemon?[] _pokemon;
private readonly IBattlePokemon?[] _pokemon;
/// <inheritdoc />
public IReadOnlyList<IPokemon?> Pokemon => _pokemon;
public IReadOnlyList<IBattlePokemon?> Pokemon => _pokemon;
private readonly ITurnChoice?[] _setChoices;
@@ -249,28 +256,31 @@ public class BattleSideImpl : ScriptSource, IBattleSide
if (pokemon is not null)
{
pokemon.RunScriptHook<IScriptOnRemove>(script => script.OnRemove());
pokemon.SetOnBattlefield(false);
pokemon.OnSwitchedOut();
}
_pokemon[index] = null;
}
/// <inheritdoc />
public IPokemon? SwapPokemon(byte position, IPokemon? pokemon)
public IBattlePokemon? SwapPokemon(byte position, IBattlePokemon? pokemon)
{
var oldPokemon = _pokemon[position];
if (oldPokemon is not null)
{
oldPokemon.RunScriptHook<IScriptOnSwitchOut>(script => script.OnSwitchOut(oldPokemon, position));
oldPokemon.RunScriptHook<IScriptOnRemove>(script => script.OnRemove());
oldPokemon.SetOnBattlefield(false);
oldPokemon.OnSwitchedOut();
}
_pokemon[position] = pokemon;
if (pokemon is not null)
{
pokemon.SetBattleData(Battle, Index);
pokemon.SetOnBattlefield(true);
pokemon.SetBattleSidePosition(position);
if (pokemon.SideIndex != Index)
{
throw new InvalidOperationException(
"A battle Pokémon can only be sent out on the side its party is responsible for.");
}
pokemon.OnSwitchedIn(position);
Battle.EventHook.Invoke(new SwitchEvent(Index, position, pokemon));
pokemon.RunScriptHook<IScriptOnSwitchIn>(script => script.OnSwitchIn(pokemon, position));
@@ -300,6 +310,14 @@ public class BattleSideImpl : ScriptSource, IBattleSide
return oldPokemon;
}
/// <inheritdoc />
public IBattlePokemon? SendOut(byte position, IPokemon pokemon)
{
var wrapper = Battle.Parties.Select(p => p.GetBattlePokemon(pokemon)).FirstOrDefault(x => x != null) ??
throw new InvalidOperationException("The Pokémon does not belong to any party in this battle.");
return SwapPokemon(position, wrapper);
}
/// <inheritdoc />
public void SwapPokemon(byte position1, byte position2)
{
@@ -307,7 +325,7 @@ public class BattleSideImpl : ScriptSource, IBattleSide
}
/// <inheritdoc />
public bool IsPokemonOnSide(IPokemon pokemon) => _pokemon.Contains(pokemon);
public bool IsPokemonOnSide(IBattlePokemon pokemon) => _pokemon.Contains(pokemon);
/// <inheritdoc />
public void MarkPositionAsUnfillable(byte position) => _fillablePositions[position] = false;

View File

@@ -13,7 +13,7 @@ public interface IFleeChoice : ITurnChoice
public class FleeTurnChoice : TurnChoice, IFleeChoice
{
/// <inheritdoc />
public FleeTurnChoice(IPokemon user) : base(user)
public FleeTurnChoice(IBattlePokemon user) : base(user)
{
}

View File

@@ -16,15 +16,15 @@ public interface IItemChoice : ITurnChoice
/// <summary>
/// The target Pokémon of the item, if any.
/// </summary>
IPokemon? GetTargetPokemon(IBattle battle);
IBattlePokemon? GetTargetPokemon(IBattle battle);
}
/// <inheritdoc cref="IItemChoice"/>
public class ItemChoice : TurnChoice, IItemChoice
{
/// <inheritdoc cref="ItemChoice"/>
private ItemChoice(IPokemon user, IItem item, byte? targetSide, byte? targetPosition, IPokemon? targetPokemon) :
base(user)
private ItemChoice(IBattlePokemon user, IItem item, byte? targetSide, byte? targetPosition,
IBattlePokemon? targetPokemon) : base(user)
{
Item = item;
TargetSide = targetSide;
@@ -32,13 +32,13 @@ public class ItemChoice : TurnChoice, IItemChoice
TargetPokemon = targetPokemon;
}
public static ItemChoice CreateWithoutTarget(IPokemon user, IItem item) =>
public static ItemChoice CreateWithoutTarget(IBattlePokemon user, IItem item) =>
new(user, item, null, null, null);
public static ItemChoice CreateForOpponent(IPokemon user, IItem item, byte targetSide, byte targetPosition) =>
public static ItemChoice CreateForOpponent(IBattlePokemon user, IItem item, byte targetSide, byte targetPosition) =>
new(user, item, targetSide, targetPosition, null);
public static ItemChoice CreateForPartyMember(IPokemon user, IItem item, IPokemon targetPokemon) =>
public static ItemChoice CreateForPartyMember(IBattlePokemon user, IItem item, IBattlePokemon targetPokemon) =>
new(user, item, null, null, targetPokemon);
/// <summary>
@@ -47,7 +47,7 @@ public class ItemChoice : TurnChoice, IItemChoice
public IItem Item { get; }
/// <inheritdoc />
public IPokemon? GetTargetPokemon(IBattle battle)
public IBattlePokemon? GetTargetPokemon(IBattle battle)
{
if (TargetPokemon != null)
return TargetPokemon;
@@ -71,7 +71,7 @@ public class ItemChoice : TurnChoice, IItemChoice
/// <summary>
/// The target Pokémon of the item, if any. This is used for party members.
/// </summary>
private IPokemon? TargetPokemon { get; }
private IBattlePokemon? TargetPokemon { get; }
/// <inheritdoc />
public override int ScriptCount => User.ScriptCount;

View File

@@ -51,7 +51,7 @@ public interface IMoveChoice : ITurnChoice
public class MoveChoice : TurnChoice, IMoveChoice
{
/// <inheritdoc cref="MoveChoice"/>
public MoveChoice(IPokemon user, ILearnedMove usedMove, byte targetSide, byte targetPosition) : base(user)
public MoveChoice(IBattlePokemon user, ILearnedMove usedMove, byte targetSide, byte targetPosition) : base(user)
{
ChosenMove = usedMove;
TargetSide = targetSide;

View File

@@ -13,7 +13,7 @@ public interface IPassChoice : ITurnChoice
public class PassChoice : TurnChoice, IPassChoice
{
/// <inheritdoc cref="PassChoice"/>
public PassChoice(IPokemon user) : base(user)
public PassChoice(IBattlePokemon user) : base(user)
{
}

View File

@@ -10,20 +10,20 @@ public interface ISwitchChoice : ITurnChoice
/// <summary>
/// The Pokémon to switch to.
/// </summary>
IPokemon SwitchTo { get; }
IBattlePokemon SwitchTo { get; }
}
/// <inheritdoc cref="ISwitchChoice"/>
public class SwitchChoice : TurnChoice, ISwitchChoice
{
/// <inheritdoc cref="SwitchChoice"/>
public SwitchChoice(IPokemon user, IPokemon switchTo) : base(user)
public SwitchChoice(IBattlePokemon user, IBattlePokemon switchTo) : base(user)
{
SwitchTo = switchTo;
}
/// <inheritdoc />
public IPokemon SwitchTo { get; }
public IBattlePokemon SwitchTo { get; }
/// <inheritdoc />
public override int ScriptCount => User.ScriptCount;

View File

@@ -11,7 +11,7 @@ public interface ITurnChoice : IScriptSource, IDeepCloneable
/// <summary>
/// The user of the turn choice
/// </summary>
IPokemon User { get; }
IBattlePokemon User { get; }
/// <summary>
/// The speed of the user at the beginning of the turn.
@@ -34,7 +34,7 @@ public interface ITurnChoice : IScriptSource, IDeepCloneable
/// Fails the choice. This will prevent it from executing and run a specific fail handling during
/// execution. Note that this can not be undone.
/// </summary>
public void Fail();
void Fail();
}
/// <summary>
@@ -43,7 +43,7 @@ public interface ITurnChoice : IScriptSource, IDeepCloneable
public abstract class TurnChoice : ScriptSource, ITurnChoice
{
/// <inheritdoc cref="TurnChoice"/>
protected TurnChoice(IPokemon user)
protected TurnChoice(IBattlePokemon user)
{
User = user;
}
@@ -51,7 +51,7 @@ public abstract class TurnChoice : ScriptSource, ITurnChoice
/// <summary>
/// The Pokemon for which the choice is made.
/// </summary>
public IPokemon User { get; }
public IBattlePokemon User { get; }
/// <summary>
/// The speed of the user at the beginning of the turn.

View File

@@ -129,7 +129,7 @@ public interface IExecutingMove : IScriptSource
/// <summary>
/// The user of the move.
/// </summary>
IPokemon User { get; }
IBattlePokemon User { get; }
/// <summary>
/// The move the user has actually chosen to do.
@@ -151,17 +151,17 @@ public interface IExecutingMove : IScriptSource
/// <summary>
/// Gets a hit data for a target, with a specific index.
/// </summary>
IHitData GetHitData(IPokemon target, byte hit);
IHitData GetHitData(IBattlePokemon target, byte hit);
/// <summary>
/// Checks whether a Pokémon is a target for this move.
/// </summary>
bool IsPokemonTarget(IPokemon target);
bool IsPokemonTarget(IBattlePokemon target);
/// <summary>
/// Gets the index of the hits in this move where the hits for a specific target start.
/// </summary>
int GetTargetIndex(IPokemon target);
int GetTargetIndex(IBattlePokemon target);
/// <summary>
/// Gets a hit based on its raw index.
@@ -171,7 +171,7 @@ public interface IExecutingMove : IScriptSource
/// <summary>
/// Gets the targets of this move.
/// </summary>
IReadOnlyList<IPokemon?> Targets { get; }
IReadOnlyList<IBattlePokemon?> Targets { get; }
/// <summary>
/// The underlying move choice.
@@ -192,12 +192,12 @@ public interface IExecutingMove : IScriptSource
/// <inheritdoc cref="IExecutingMove"/>
public class ExecutingMoveImpl : ScriptSource, IExecutingMove
{
private readonly IReadOnlyList<IPokemon?> _targets;
private readonly IReadOnlyList<IBattlePokemon?> _targets;
private readonly IHitData[] _hits;
private readonly IBattle _battle;
/// <inheritdoc cref="ExecutingMoveImpl"/>
public ExecutingMoveImpl(IReadOnlyList<IPokemon?> targets, byte numberOfHits, ILearnedMove chosenMove,
public ExecutingMoveImpl(IReadOnlyList<IBattlePokemon?> targets, byte numberOfHits, ILearnedMove chosenMove,
IMoveData useMove, IMoveChoice moveChoice, IBattle battle)
{
_targets = targets;
@@ -222,7 +222,7 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove
public byte NumberOfHits { get; }
/// <inheritdoc />
public IPokemon User => MoveChoice.User;
public IBattlePokemon User => MoveChoice.User;
/// <inheritdoc />
public ILearnedMove ChosenMove { get; }
@@ -239,7 +239,7 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove
public IScriptSet Volatile => MoveChoice.Volatile;
/// <inheritdoc />
public IHitData GetHitData(IPokemon target, byte hit)
public IHitData GetHitData(IBattlePokemon target, byte hit)
{
var targetIndex = _targets.IndexOf(target);
if (targetIndex == -1)
@@ -252,10 +252,10 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove
}
/// <inheritdoc />
public bool IsPokemonTarget(IPokemon target) => _targets.Contains(target);
public bool IsPokemonTarget(IBattlePokemon target) => _targets.Contains(target);
/// <inheritdoc />
public int GetTargetIndex(IPokemon target)
public int GetTargetIndex(IBattlePokemon target)
{
var targetIndex = _targets.IndexOf(target);
if (targetIndex == -1)
@@ -273,7 +273,7 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove
}
/// <inheritdoc />
public IReadOnlyList<IPokemon?> Targets => _targets.ToList();
public IReadOnlyList<IBattlePokemon?> Targets => _targets.ToList();
/// <inheritdoc />
public IMoveChoice MoveChoice { get; }

View File

@@ -35,29 +35,30 @@ public static class ItemTargetTypeHelpers
/// <summary>
/// Determines if the given target is valid based on the ItemTargetType.
/// </summary>
public static bool IsValidTarget(this ItemTargetType targetType, IBattle battle, IPokemon user, IPokemon target)
public static bool IsValidTarget(this ItemTargetType targetType, IBattle battle, IBattlePokemon user,
IBattlePokemon target)
{
if (targetType == ItemTargetType.None)
return true;
if (targetType.HasFlag(ItemTargetType.OwnPokemon))
{
var userParty = battle.Parties.FirstOrDefault(x => x.Party.Contains(user));
var targetParty = battle.Parties.FirstOrDefault(x => x.Party.Contains(target));
var userParty = battle.Parties.FirstOrDefault(x => x.BattlePokemon.Contains(user));
var targetParty = battle.Parties.FirstOrDefault(x => x.BattlePokemon.Contains(target));
if (userParty is not null && targetParty is not null && userParty == targetParty)
return true;
}
if (targetType.HasFlag(ItemTargetType.AllyPokemon))
{
if (user.BattleData?.BattleSide == target.BattleData?.BattleSide)
if (user.BattleSide == target.BattleSide)
return true;
}
if (targetType.HasFlag(ItemTargetType.FoePokemon))
{
var userParty = battle.Parties.FirstOrDefault(x => x.Party.Contains(user));
var targetParty = battle.Parties.FirstOrDefault(x => x.Party.Contains(target));
var userParty = battle.Parties.FirstOrDefault(x => x.BattlePokemon.Contains(user));
var targetParty = battle.Parties.FirstOrDefault(x => x.BattlePokemon.Contains(target));
if (userParty is not null && targetParty is not null && userParty != targetParty)
return true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@ public record SerializedPokemon
Nature = pokemon.Nature.Name;
Nickname = pokemon.Nickname;
Ability = pokemon.Form.GetAbility(pokemon.AbilityIndex);
Moves = pokemon.BaseMoves.Select(move =>
Moves = pokemon.Moves.Select(move =>
{
if (move == null)
return null;

View File

@@ -41,7 +41,7 @@ public abstract class ItemScript : IDeepCloneable
/// <summary>
/// Returns whether the item is usable on the given target.
/// </summary>
public virtual bool IsTargetValid(IPokemon target) => false;
public virtual bool IsTargetValid(IBattlePokemon target) => false;
/// <summary>
/// Returns whether the item can be held by a Pokémon.
@@ -51,7 +51,7 @@ public abstract class ItemScript : IDeepCloneable
/// <summary>
/// Returns whether the item can be held by the given target.
/// </summary>
public virtual bool CanTargetHold(IPokemon pokemon) => true;
public virtual bool CanTargetHold(IBattlePokemon pokemon) => true;
/// <summary>
/// Handles the use of the item.
@@ -63,7 +63,7 @@ public abstract class ItemScript : IDeepCloneable
/// <summary>
/// Handles the use of the item on the given target.
/// </summary>
public virtual void OnUseWithTarget(IPokemon target, EventHook eventHook)
public virtual void OnUseWithTarget(IBattlePokemon target, EventHook eventHook)
{
}
}

View File

@@ -17,9 +17,9 @@ public abstract class PokeballScript : ItemScript
/// <summary>
/// Returns the catch rate of the Pokéball against the given target Pokémon.
/// </summary>
public abstract void ChangeCatchRate(IPokemon target, ref byte catchRate);
public abstract void ChangeCatchRate(IBattlePokemon target, ref byte catchRate);
public virtual void OnAfterSuccessfulCapture(IPokemon target)
public virtual void OnAfterSuccessfulCapture(IBattlePokemon target)
{
// Default implementation does nothing.
// Override this method in derived classes to add custom behavior after a successful capture.
@@ -32,16 +32,13 @@ public abstract class PokeballScript : ItemScript
public override ItemTargetType TargetType => ItemTargetType.FoePokemon;
/// <inheritdoc />
public override bool IsTargetValid(IPokemon target) =>
target.BattleData is not null && target.BattleData.Battle.IsWildBattle;
public override bool IsTargetValid(IBattlePokemon target) => target.Battle.IsWildBattle;
/// <inheritdoc />
public override void OnUseWithTarget(IPokemon target, EventHook eventHook)
public override void OnUseWithTarget(IBattlePokemon target, EventHook eventHook)
{
var battleData = target.BattleData;
var result = battleData?.Battle.AttempCapture(battleData.SideIndex, battleData.Position, Item);
if (result is { IsCaught: true })
var result = target.Battle.AttempCapture(target.SideIndex, target.Position, Item);
if (result.IsCaught)
{
OnAfterSuccessfulCapture(target);
}

View File

@@ -25,7 +25,7 @@ public interface IAIInfoScriptExpectedEndOfTurnDamage
/// This function returns the expected end of turn damage for the script. This is used for scripts that
/// have an end of turn effect, such as Poison or Burn.
/// </summary>
void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage);
void ExpectedEndOfTurnDamage(IBattlePokemon pokemon, ref int damage);
}
/// <summary>
@@ -37,5 +37,5 @@ public interface IAIInfoScriptExpectedEntryDamage
/// This function returns the expected entry damage for the script. This is used for scripts that have
/// an entry hazard effect, such as Spikes or Stealth Rock.
/// </summary>
void ExpectedEntryDamage(IPokemon pokemon, ref uint damage);
void ExpectedEntryDamage(IBattlePokemon pokemon, ref uint damage);
}

View File

@@ -100,8 +100,8 @@ public static class ScriptExecution
/// <summary>
/// Executes a script on an item.
/// </summary>
public static void RunItemScript(this IItem item, ScriptResolver scriptResolver, IPokemon? target, IPokemon user,
IBattle battle, EventHook eventHook)
public static void RunItemScript(this IItem item, ScriptResolver scriptResolver, IBattlePokemon? target,
IBattlePokemon user, IBattle battle, EventHook eventHook)
{
if (!scriptResolver.TryResolveBattleItemScript(item, out var itemScript))
{

View File

@@ -125,7 +125,7 @@ public interface IScriptChangeTargets
/// <summary>
/// Changes the targets of a move choice. This allows for changing the targets of a move before the move starts.
/// </summary>
void ChangeTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets);
void ChangeTargets(IMoveChoice moveChoice, ref IReadOnlyList<IBattlePokemon?> targets);
}
/// <summary>
@@ -136,7 +136,7 @@ public interface IScriptChangeIncomingTargets
/// <summary>
/// This function allows you to change the targets of a move choice before the move starts.
/// </summary>
void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets);
void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList<IBattlePokemon?> targets);
}
/// <summary>
@@ -219,7 +219,7 @@ public interface IScriptChangeMoveType
/// This function allows the script to change the actual type that is used for the move on a target.
/// If this is set to null, the move will be treated as a typeless move.
/// </summary>
void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier);
void ChangeMoveType(IExecutingMove move, IBattlePokemon target, byte hit, ref TypeIdentifier? typeIdentifier);
}
/// <summary>
@@ -230,7 +230,7 @@ public interface IScriptChangeEffectiveness
/// <summary>
/// This function allows the script to change how effective a move is on a target.
/// </summary>
void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness);
void ChangeEffectiveness(IExecutingMove move, IBattlePokemon target, byte hit, ref float effectiveness);
}
/// <summary>
@@ -241,7 +241,7 @@ public interface IScriptChangeIncomingEffectiveness
/// <summary>
/// This function allows the script to override how effective a move is on a target.
/// </summary>
void ChangeIncomingEffectiveness(IExecutingMove executingMove, IPokemon target, byte hitIndex,
void ChangeIncomingEffectiveness(IExecutingMove executingMove, IBattlePokemon target, byte hitIndex,
ref float effectiveness);
}
@@ -253,7 +253,7 @@ public interface IScriptBlockCriticalHit
/// <summary>
/// This function allows a script to block an outgoing move from being critical.
/// </summary>
void BlockCriticalHit(IExecutingMove move, IPokemon target, byte hit, ref bool block);
void BlockCriticalHit(IExecutingMove move, IBattlePokemon target, byte hit, ref bool block);
}
/// <summary>
@@ -264,7 +264,7 @@ public interface IScriptBlockIncomingCriticalHit
/// <summary>
/// This function allows a script to block an incoming move from being critical.
/// </summary>
void BlockIncomingCriticalHit(IExecutingMove move, IPokemon target, byte hit, ref bool block);
void BlockIncomingCriticalHit(IExecutingMove move, IBattlePokemon target, byte hit, ref bool block);
}
/// <summary>
@@ -276,7 +276,7 @@ public interface IScriptOnIncomingHit
/// This function triggers when an incoming hit happens. This triggers after the damage is done,
/// but before the secondary effect of the move happens.
/// </summary>
void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit);
void OnIncomingHit(IExecutingMove move, IBattlePokemon target, byte hit);
}
/// <summary>
@@ -287,7 +287,7 @@ public interface IScriptOnOpponentFaints
/// <summary>
/// This function triggers when an opponent on the f ield faints due to the move that is being executed.
/// </summary>
void OnOpponentFaints(IExecutingMove move, IPokemon target, byte hit);
void OnOpponentFaints(IExecutingMove move, IBattlePokemon target, byte hit);
}
/// <summary>
@@ -300,7 +300,7 @@ public interface IScriptOnSecondaryEffect
/// secondary effects here. Status moves should implement their actual functionality in this
/// function as well, as status moves effects are defined as secondary effects for simplicity.
/// </summary>
void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit);
void OnSecondaryEffect(IExecutingMove move, IBattlePokemon target, byte hit);
}
/// <summary>
@@ -312,7 +312,7 @@ public interface IScriptFailIncomingMove
/// This function allows a script to prevent a move that is targeted at its owner. If set to true
/// the move fails, and fail events get triggered.
/// </summary>
void FailIncomingMove(IExecutingMove move, IPokemon target, ref bool fail);
void FailIncomingMove(IExecutingMove move, IBattlePokemon target, ref bool fail);
}
/// <summary>
@@ -323,7 +323,7 @@ public interface IScriptIsInvulnerableToMove
/// <summary>
/// This function allows a script to make its owner invulnerable to an incoming move.
/// </summary>
void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable);
void IsInvulnerableToMove(IExecutingMove move, IBattlePokemon target, ref bool invulnerable);
}
/// <summary>
@@ -335,7 +335,7 @@ public interface IScriptOnMoveMiss
/// This function allows a script to run when a move misses its target. This is used for moves
/// that have a secondary effect that should run even if the move misses, such as Spore.
/// </summary>
void OnMoveMiss(IExecutingMove move, IPokemon target);
void OnMoveMiss(IExecutingMove move, IBattlePokemon target);
}
/// <summary>
@@ -347,7 +347,7 @@ public interface IScriptChangeAccuracyModifier
/// This function allows a script to modify the accuracy of a move used. This value represents
/// the percentage accuracy, so anything above 100% will make it always hit.
/// </summary>
void ChangeAccuracyModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier);
void ChangeAccuracyModifier(IExecutingMove move, IBattlePokemon target, byte hit, ref float modifier);
}
/// <summary>
@@ -358,7 +358,7 @@ public interface IScriptChangeCriticalStage
/// <summary>
/// This function allows a script to change the critical stage of the move used.
/// </summary>
void ChangeCriticalStage(IExecutingMove move, IPokemon target, byte hit, ref byte stage);
void ChangeCriticalStage(IExecutingMove move, IBattlePokemon target, byte hit, ref byte stage);
}
/// <summary>
@@ -370,7 +370,7 @@ public interface IScriptChangeCriticalModifier
/// This function allows a script to change the damage modifier of a critical hit. This will only
/// run when a hit is critical.
/// </summary>
void ChangeCriticalModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier);
void ChangeCriticalModifier(IExecutingMove move, IBattlePokemon target, byte hit, ref float modifier);
}
/// <summary>
@@ -382,7 +382,7 @@ public interface IScriptChangeStabModifier
/// This function allows a script to change the damage modifier of a Same Type Attack Bonus, which
/// occurs when the user has the move type as one of its own types.
/// </summary>
void ChangeStabModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, bool isStab,
void ChangeStabModifier(IExecutingMove executingMove, IBattlePokemon target, byte hitNumber, bool isStab,
ref float modifier);
}
@@ -394,7 +394,7 @@ public interface IScriptChangeBasePower
/// <summary>
/// This function allows a script to change the effective base power of a move hit.
/// </summary>
void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower);
void ChangeBasePower(IExecutingMove move, IBattlePokemon target, byte hit, ref ushort basePower);
}
/// <summary>
@@ -407,7 +407,7 @@ public interface IScriptBypassDefensiveStatBoosts
/// If this is true, the damage will be calculated as if the target has no positive stat boosts. Negative
/// stat boosts will still be applied.
/// </summary>
void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass);
void BypassDefensiveStatBoosts(IExecutingMove move, IBattlePokemon target, byte hit, ref bool bypass);
}
/// <summary>
@@ -419,7 +419,7 @@ public interface IScriptBypassEvasionStatBoosts
/// This function allows a script to bypass evasion stat boosts for a move hit.
/// If this is true, the move will handle the evasion stat boosts as if the target has no positive stat boosts.
/// </summary>
void BypassEvasionStatBoosts(IExecutingMove move, IPokemon target, byte hitIndex, ref bool bypass);
void BypassEvasionStatBoosts(IExecutingMove move, IBattlePokemon target, byte hitIndex, ref bool bypass);
}
/// <summary>
@@ -432,7 +432,7 @@ public interface IScriptBypassOffensiveStatBoosts
/// If this is true, the damage will be calculated as if the user has no negative offensive stat boosts. Positive
/// stat boosts will still be applied.
/// </summary>
void BypassOffensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass);
void BypassOffensiveStatBoosts(IExecutingMove move, IBattlePokemon target, byte hit, ref bool bypass);
}
/// <summary>
@@ -443,7 +443,7 @@ public interface IScriptChangeOffensiveStatValue
/// <summary>
/// This function allows a script to change the actual offensive stat values used when calculating damage
/// </summary>
void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
void ChangeOffensiveStatValue(IExecutingMove move, IBattlePokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value);
}
@@ -455,7 +455,7 @@ public interface IScriptChangeDefensiveStatValue
/// <summary>
/// This function allows a script to change the actual defensive stat values used when calculating damage.
/// </summary>
void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat,
void ChangeDefensiveStatValue(IExecutingMove move, IBattlePokemon target, byte hit, uint offensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value);
}
@@ -467,7 +467,7 @@ public interface IScriptChangeIncomingMoveOffensiveStatValue
/// <summary>
/// This function allows a script to change the offensive stat value of an incoming move.
/// </summary>
void ChangeIncomingMoveOffensiveStatValue(IExecutingMove executingMove, IPokemon target, byte hitNumber,
void ChangeIncomingMoveOffensiveStatValue(IExecutingMove executingMove, IBattlePokemon target, byte hitNumber,
uint defensiveStat, StatisticSet<uint> targetStats, Statistic offensive, ref uint offensiveStat);
}
@@ -479,7 +479,7 @@ public interface IScriptChangeIncomingMoveDefensiveStatValue
/// <summary>
/// This function allows a script to change the defensive stat value of an incoming move.
/// </summary>
void ChangeIncomingMoveDefensiveStatValue(IExecutingMove executingMove, IPokemon target, byte hitNumber,
void ChangeIncomingMoveDefensiveStatValue(IExecutingMove executingMove, IBattlePokemon target, byte hitNumber,
uint origOffensiveStat, StatisticSet<uint> targetStats, Statistic defensive, ref uint defensiveStat);
}
@@ -492,7 +492,7 @@ public interface IScriptChangeDamageStatModifier
/// This function allows a script to change the raw modifier we retrieved from the stats of the
/// defender and attacker. The default value is the offensive stat divided by the defensive stat.
/// </summary>
void ChangeDamageStatModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier);
void ChangeDamageStatModifier(IExecutingMove move, IBattlePokemon target, byte hit, ref float modifier);
}
/// <summary>
@@ -503,7 +503,7 @@ public interface IScriptChangeDamageModifier
/// <summary>
/// This function allows a script to apply a raw multiplier to the damage done by a move.
/// </summary>
void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier);
void ChangeDamageModifier(IExecutingMove move, IBattlePokemon target, byte hit, ref float modifier);
}
/// <summary>
@@ -514,7 +514,7 @@ public interface IScriptChangeIncomingMoveDamageModifier
/// <summary>
/// This function allows a script to change the damage modifier of an incoming move.
/// </summary>
void ChangeIncomingMoveDamageModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber,
void ChangeIncomingMoveDamageModifier(IExecutingMove executingMove, IBattlePokemon target, byte hitNumber,
ref float modifier);
}
@@ -526,7 +526,7 @@ public interface IScriptChangeMoveDamage
/// <summary>
/// This function allows a script to modify the outgoing damage done by a move.
/// </summary>
void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage);
void ChangeMoveDamage(IExecutingMove move, IBattlePokemon target, byte hit, ref uint damage);
}
/// <summary>
@@ -537,7 +537,7 @@ public interface IScriptChangeIncomingMoveDamage
/// <summary>
/// This function allows a script to modify the incoming damage done by a move.
/// </summary>
void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage);
void ChangeIncomingMoveDamage(IExecutingMove move, IBattlePokemon target, byte hit, ref uint damage);
}
/// <summary>
@@ -549,7 +549,8 @@ public interface IScriptPreventStatBoostChange
/// This function allows a script attached to a Pokemon or its parents to prevent stat boost
/// changes on that Pokemon.
/// </summary>
void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted, ref bool prevent);
void PreventStatBoostChange(IBattlePokemon target, Statistic stat, sbyte amount, bool selfInflicted,
ref bool prevent);
}
/// <summary>
@@ -562,7 +563,7 @@ public interface IScriptChangeStatBoostChange
/// which the stat boost will change. If the stat boost is done by the user itself, self
/// inflicted will be true, otherwise it will be false.
/// </summary>
void ChangeStatBoostChange(IPokemon target, Statistic stat, bool selfInflicted, ref sbyte amount);
void ChangeStatBoostChange(IBattlePokemon target, Statistic stat, bool selfInflicted, ref sbyte amount);
}
/// <summary>
@@ -573,7 +574,7 @@ public interface IScriptOnAfterStatBoostChange
/// <summary>
/// This function allows a script to run after a stat boost change has been applied.
/// </summary>
void OnAfterStatBoostChange(IPokemon pokemon, Statistic stat, bool selfInflicted, sbyte change);
void OnAfterStatBoostChange(IBattlePokemon pokemon, Statistic stat, bool selfInflicted, sbyte change);
}
/// <summary>
@@ -586,7 +587,7 @@ public interface IScriptPreventSecondaryEffect
/// This means the move will still hit and do damage, but not trigger its secondary effect. Note that this
/// function is not called for status moves.
/// </summary>
void PreventSecondaryEffect(IExecutingMove move, IPokemon target, byte hit, ref bool prevent);
void PreventSecondaryEffect(IExecutingMove move, IBattlePokemon target, byte hit, ref bool prevent);
}
/// <summary>
@@ -599,7 +600,7 @@ public interface IScriptPreventIncomingSecondaryEffect
/// secondary effect. This means the move will still hit and do damage, but not trigger its
/// secondary effect. Note that this function is not called for status moves.
/// </summary>
void PreventIncomingSecondaryEffect(IExecutingMove move, IPokemon target, byte hit, ref bool prevent);
void PreventIncomingSecondaryEffect(IExecutingMove move, IBattlePokemon target, byte hit, ref bool prevent);
}
/// <summary>
@@ -613,7 +614,7 @@ public interface IScriptChangeEffectChance
/// changing this to above or equal to 100 will make it always hit, while setting it to equal or
/// below 0 will make it never hit.
/// </summary>
void ChangeEffectChance(IExecutingMove move, IPokemon target, byte hit, ref float chance);
void ChangeEffectChance(IExecutingMove move, IBattlePokemon target, byte hit, ref float chance);
}
/// <summary>
@@ -627,7 +628,7 @@ public interface IScriptChangeIncomingEffectChance
/// so changing this to above or equal to 100 will make it always hit, while setting it to equal
/// or below 0 will make it never hit.
/// </summary>
void ChangeIncomingEffectChance(IExecutingMove move, IPokemon target, byte hit, ref float chance);
void ChangeIncomingEffectChance(IExecutingMove move, IBattlePokemon target, byte hit, ref float chance);
}
/// <summary>
@@ -638,7 +639,7 @@ public interface IScriptOnAfterHits
/// <summary>
/// This function triggers on a move or its parents when all hits on a target are finished.
/// </summary>
void OnAfterHits(IExecutingMove move, IPokemon target);
void OnAfterHits(IExecutingMove move, IBattlePokemon target);
}
/// <summary>
@@ -706,7 +707,7 @@ public interface IScriptOnDamage
/// <summary>
/// This function is triggered on a Pokemon and its parents when the given Pokemon takes damage.
/// </summary>
void OnDamage(IPokemon pokemon, DamageSource source, uint oldHealth, uint newHealth);
void OnDamage(IBattlePokemon pokemon, DamageSource source, uint oldHealth, uint newHealth);
}
/// <summary>
@@ -717,7 +718,7 @@ public interface IScriptOnFaint
/// <summary>
/// This function is triggered on a Pokemon and its parents when the given Pokemon faints.
/// </summary>
void OnFaint(IPokemon pokemon, DamageSource source);
void OnFaint(IBattlePokemon pokemon, DamageSource source);
}
/// <summary>
@@ -728,7 +729,7 @@ public interface IScriptOnAllyFaint
/// <summary>
/// This function is triggered on a Pokemon when an ally Pokemon faints.
/// </summary>
void OnAllyFaint(IPokemon ally, IPokemon faintedPokemon);
void OnAllyFaint(IBattlePokemon ally, IBattlePokemon faintedPokemon);
}
/// <summary>
@@ -740,7 +741,7 @@ public interface IScriptOnSwitchOut
/// This function is triggered on a Pokemon and its parents when the given Pokemon switches out
/// of the battlefield.
/// </summary>
void OnSwitchOut(IPokemon oldPokemon, byte position);
void OnSwitchOut(IBattlePokemon oldPokemon, byte position);
}
/// <summary>
@@ -752,7 +753,7 @@ public interface IScriptOnSwitchIn
/// This function is triggered on a Pokemon and its parents when the given Pokemon is switched into
/// the battlefield.
/// </summary>
void OnSwitchIn(IPokemon pokemon, byte position);
void OnSwitchIn(IBattlePokemon pokemon, byte position);
}
/// <summary>
@@ -763,7 +764,7 @@ public interface IScriptOnOpponentSwitchIn
/// <summary>
/// This function is triggered on a Pokemon and its parents when an opponent switches in.
/// </summary>
void OnOpponentSwitchIn(IPokemon pokemon, byte position);
void OnOpponentSwitchIn(IBattlePokemon pokemon, byte position);
}
/// <summary>
@@ -787,7 +788,7 @@ public interface IScriptOnAfterItemConsume
/// This function is triggered on a Pokemon and its parents when the given Pokemon consumes the
/// held item it had.
/// </summary>
void OnAfterItemConsume(IPokemon pokemon, IItem item);
void OnAfterItemConsume(IBattlePokemon pokemon, IItem item);
}
/// <summary>
@@ -798,7 +799,7 @@ public interface IScriptBlockIncomingHit
/// <summary>
/// This function allows a script to block an incoming hit.
/// </summary>
void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block);
void BlockIncomingHit(IExecutingMove executingMove, IBattlePokemon target, byte hitIndex, ref bool block);
}
/// <summary>
@@ -809,7 +810,7 @@ public interface IScriptBlockOutgoingHit
/// <summary>
/// This function allows a script to block an outgoing hit.
/// </summary>
void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block);
void BlockOutgoingHit(IExecutingMove executingMove, IBattlePokemon target, byte hitIndex, ref bool block);
}
/// <summary>
@@ -820,7 +821,7 @@ public interface IScriptPreventHeldItemConsume
/// <summary>
/// This function allows a script to prevent a held item from being consumed.
/// </summary>
void PreventHeldItemConsume(IPokemon pokemon, IItem heldItem, ref bool prevented);
void PreventHeldItemConsume(IBattlePokemon pokemon, IItem heldItem, ref bool prevented);
}
/// <summary>
@@ -831,7 +832,7 @@ public interface IScriptChangeIncomingDamage
/// <summary>
/// This function allows a script to change any kind of damage that is incoming.
/// </summary>
void ChangeIncomingDamage(IPokemon pokemon, DamageSource source, ref uint damage);
void ChangeIncomingDamage(IBattlePokemon pokemon, DamageSource source, ref uint damage);
}
/// <summary>
@@ -853,7 +854,7 @@ public interface IScriptPreventHeal
/// <summary>
/// This function allows a script to prevent a Pokemon from being healed.
/// </summary>
void PreventHeal(IPokemon pokemon, uint heal, bool allowRevive, ref bool prevented);
void PreventHeal(IBattlePokemon pokemon, uint heal, bool allowRevive, ref bool prevented);
}
/// <summary>
@@ -865,7 +866,8 @@ public interface IScriptChangeTypesForMove
/// This function allows a script to change the types a target has. Multiple types can be set, and will be used
/// for the effectiveness calculation.
/// </summary>
void ChangeTypesForMove(IExecutingMove executingMove, IPokemon target, byte hitIndex, IList<TypeIdentifier> types);
void ChangeTypesForMove(IExecutingMove executingMove, IBattlePokemon target, byte hitIndex,
IList<TypeIdentifier> types);
}
/// <summary>
@@ -877,7 +879,7 @@ public interface IScriptChangeTypesForIncomingMove
/// This function allows a script to change the types a Pokemon has for a move that's incoming. Multiple types can
/// be set, and will be used for the effectiveness calculation.
/// </summary>
void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
void ChangeTypesForIncomingMove(IExecutingMove executingMove, IBattlePokemon target, byte hitIndex,
IList<TypeIdentifier> types);
}
@@ -890,7 +892,7 @@ public interface IScriptChangeCategory
/// This function allows a script to change the handling of the move category. This is used for moves that
/// are sometimes a status move, and sometimes a damaging move, such as pollen puff.
/// </summary>
void ChangeCategory(IExecutingMove move, IPokemon target, byte hitIndex, ref MoveCategory category);
void ChangeCategory(IExecutingMove move, IBattlePokemon target, byte hitIndex, ref MoveCategory category);
}
/// <summary>
@@ -901,7 +903,7 @@ public interface IScriptOnBeforeHit
/// <summary>
/// Triggers first when we're about to hit a target.
/// </summary>
void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex);
void OnBeforeHit(IExecutingMove move, IBattlePokemon target, byte hitIndex);
}
/// <summary>
@@ -912,7 +914,7 @@ public interface IScriptPreventStatusChange
/// <summary>
/// This function allows a script to prevent a Pokemon from being affected by a status condition.
/// </summary>
void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted, ref bool preventStatus);
void PreventStatusChange(IBattlePokemon pokemon, StringKey status, bool selfInflicted, ref bool preventStatus);
}
/// <summary>
@@ -923,7 +925,7 @@ public interface IScriptOnAfterStatusChange
/// <summary>
/// This function triggers after a status condition has been applied to a Pokemon.
/// </summary>
void OnAfterStatusChange(IPokemon pokemon, StringKey status, IPokemon? originPokemon);
void OnAfterStatusChange(IBattlePokemon pokemon, StringKey status, IPokemon? originPokemon);
}
/// <summary>
@@ -946,7 +948,7 @@ public interface IScriptIsFloating
/// This function allows a script to make the Pokémon it is attached to float. This is used for moves
/// such as levitate, and allows for moves such as earthquake to not hit the Pokémon.
/// </summary>
void IsFloating(IPokemon pokemon, ref bool isFloating);
void IsFloating(IBattlePokemon pokemon, ref bool isFloating);
}
/// <summary>
@@ -991,7 +993,7 @@ public interface IScriptModifyIsContact
/// <summary>
/// Modifies whether a move is a contact move or not. This is used for abilities such as Long Reach.
/// </summary>
void ModifyIsContact(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool isContact);
void ModifyIsContact(IExecutingMove executingMove, IBattlePokemon target, byte hitIndex, ref bool isContact);
}
/// <summary>
@@ -1002,7 +1004,7 @@ public interface IScriptPreventHeldItemSteal
/// <summary>
/// This function allows a script to prevent a held item from being stolen by an effect such as Thief or Covet.
/// </summary>
void PreventHeldItemSteal(IPokemon pokemon, IItem heldItem, ref bool prevent);
void PreventHeldItemSteal(IBattlePokemon pokemon, IItem heldItem, ref bool prevent);
}
/// <summary>
@@ -1013,7 +1015,7 @@ public interface IScriptOnAfterHeldItemChange
/// <summary>
/// This function allows a script to run after a held item has changed.
/// </summary>
void OnAfterHeldItemChange(IPokemon pokemon, IItem? previous, IItem? item);
void OnAfterHeldItemChange(IBattlePokemon pokemon, IItem? previous, IItem? item);
}
/// <summary>
@@ -1080,7 +1082,7 @@ public interface IScriptChangeExperienceGained
/// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience,
/// and allows for changing this amount of experience.
/// </summary>
void ChangeExperienceGained(IPokemon faintedPokemon, IPokemon winningPokemon, ref uint amount);
void ChangeExperienceGained(IBattlePokemon faintedPokemon, IBattlePokemon winningPokemon, ref uint amount);
}
/// <summary>
@@ -1093,7 +1095,8 @@ public interface IScriptShareExperience
/// and allows for making the experience be shared across multiple Pokemon.
/// Amount is the modifier for how much experience is shared, with 1 being the default amount.
/// </summary>
void ShareExperience(IPokemon faintedPokemon, IPokemon winningPokemon, ref bool share, ref float amount);
void ShareExperience(IBattlePokemon faintedPokemon, IBattlePokemon winningPokemon, ref bool share,
ref float amount);
}
/// <summary>
@@ -1117,7 +1120,7 @@ public interface IScriptChangeCatchRateBonus
/// rate of this attempt. Pokeball modifier effects should be implemented here, as well as for
/// example status effects that change capture rates.
/// </summary>
void ChangeCatchRateBonus(IPokemon pokemon, IItem pokeball, ref byte modifier);
void ChangeCatchRateBonus(IBattlePokemon pokemon, IItem pokeball, ref byte modifier);
}
/// <summary>
@@ -1142,7 +1145,7 @@ public interface IScriptChangeAccuracy
/// A custom case goes when 255 is returned, in which case the entire accuracy check is skipped, and the move
/// will always hit.
/// </summary>
void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref int modifiedAccuracy);
void ChangeAccuracy(IExecutingMove executingMove, IBattlePokemon target, byte hitIndex, ref int modifiedAccuracy);
}
/// <summary>
@@ -1155,5 +1158,6 @@ public interface IScriptChangeIncomingAccuracy
/// A custom case goes when 255 is returned, in which case the entire accuracy check is skipped, and the move
/// will always hit.
/// </summary>
void ChangeIncomingAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref int modifiedAccuracy);
void ChangeIncomingAccuracy(IExecutingMove executingMove, IBattlePokemon target, byte hitIndex,
ref int modifiedAccuracy);
}