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

@@ -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;