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

@@ -8,13 +8,13 @@ namespace PkmnLib.Plugin.Gen7.AI;
public static class AIHelperFunctions
{
public static int GetScoreForTargetStatRaise(int score, AIMoveState move, IPokemon target,
public static int GetScoreForTargetStatRaise(int score, AIMoveState move, IBattlePokemon target,
StatisticSet<sbyte> statChanges, bool fixedChange = false, bool ignoreContrary = false)
{
var wholeEffect = move.Move.Category != MoveCategory.Status;
var desireMult = 1;
if (move.User.BattleData?.SideIndex != target.BattleData?.SideIndex)
if (move.User.SideIndex != target.SideIndex)
desireMult = -1;
if (!ignoreContrary && !fixedChange && target.ActiveAbility?.Name == AbilityNames.Contrary)
@@ -41,8 +41,7 @@ public static class AIHelperFunctions
if (!move.User.HasMoveWithEffect(MoveEffectNames.PowerTrip))
{
var foeIsAware =
target.BattleData?.BattleSide.Pokemon.Any(x => x?.ActiveAbility?.Name == AbilityNames.Unaware) != true;
var foeIsAware = target.BattleSide.Pokemon.Any(x => x?.ActiveAbility?.Name == AbilityNames.Unaware) != true;
if (!foeIsAware)
{
return wholeEffect ? ExplicitAI.MoveUselessScore : score;
@@ -80,13 +79,13 @@ public static class AIHelperFunctions
return score;
}
public static int GetScoreForTargetStatDrop(int score, AIMoveState move, IPokemon target,
public static int GetScoreForTargetStatDrop(int score, AIMoveState move, IBattlePokemon target,
StatisticSet<sbyte> statChanges, bool fixedChange = false, bool ignoreContrary = false)
{
var wholeEffect = move.Move.Category != MoveCategory.Status;
var desireMult = -1;
if (move.User.BattleData?.SideIndex == target.BattleData?.SideIndex)
if (move.User.SideIndex == target.SideIndex)
desireMult = 1;
if (!ignoreContrary && !fixedChange && target.ActiveAbility?.Name == AbilityNames.Contrary)
{
@@ -109,7 +108,7 @@ public static class AIHelperFunctions
return wholeEffect ? ExplicitAI.MoveUselessScore : score;
var foeIsAware = false;
if (target.BattleData?.BattleSide.Pokemon.All(x => x?.ActiveAbility?.Name != AbilityNames.Unaware) == true)
if (target.BattleSide.Pokemon.All(x => x?.ActiveAbility?.Name != AbilityNames.Unaware) == true)
{
foeIsAware = true;
}
@@ -148,7 +147,8 @@ public static class AIHelperFunctions
/// <summary>
/// Checks if a stat raise is worthwhile for the given Pokémon and stat.
/// </summary>
private static bool IsStatRaiseWorthwhile(IPokemon pokemon, Statistic stat, sbyte amount, bool fixedChange = false)
private static bool IsStatRaiseWorthwhile(IBattlePokemon pokemon, Statistic stat, sbyte amount,
bool fixedChange = false)
{
if (!fixedChange && pokemon.StatBoost.GetStatistic(stat) == StatBoostStatisticSet.MaxStatBoost)
return false;
@@ -169,7 +169,7 @@ public static class AIHelperFunctions
}
case Statistic.Defense:
{
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
var opponentSide = pokemon.Battle.Sides.First(x => x != pokemon.BattleSide);
return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y =>
y.MoveData.Category == MoveCategory.Physical ||
y.MoveData.SecondaryEffect?.Name == MoveEffectNames.Psyshock));
@@ -184,7 +184,7 @@ public static class AIHelperFunctions
}
case Statistic.SpecialDefense:
{
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
var opponentSide = pokemon.Battle.Sides.First(x => x != pokemon.BattleSide);
return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y =>
y.MoveData.Category == MoveCategory.Special &&
y.MoveData.SecondaryEffect?.Name != MoveEffectNames.Psyshock));
@@ -194,7 +194,7 @@ public static class AIHelperFunctions
if (!pokemon.HasMoveWithEffect(MoveEffectNames.ElectroBall, MoveEffectNames.PowerTrip))
{
var targetSpeed = pokemon.BoostedStats.Speed;
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
var opponentSide = pokemon.Battle.Sides.First(x => x != pokemon.BattleSide);
var meaningful = opponentSide.Pokemon.WhereNotNull().Select(opponent => opponent.BoostedStats.Speed)
.Any(foeSpeed => targetSpeed < foeSpeed && targetSpeed * 2.5 > foeSpeed);
if (!meaningful)
@@ -208,7 +208,7 @@ public static class AIHelperFunctions
if (minAccuracy >= 90 && pokemon.StatBoost.Accuracy >= 0)
{
var meaningful = false;
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
var opponentSide = pokemon.Battle.Sides.First(x => x != pokemon.BattleSide);
if (opponentSide.Pokemon.WhereNotNull().Any(x => x.StatBoost.Evasion > 0))
{
meaningful = true;
@@ -222,7 +222,7 @@ public static class AIHelperFunctions
return true;
}
private static bool IsStatDropWorthwhile(IPokemon pokemon, Statistic stat, sbyte amount)
private static bool IsStatDropWorthwhile(IBattlePokemon pokemon, Statistic stat, sbyte amount)
{
if (amount == 0)
return false;
@@ -236,7 +236,7 @@ public static class AIHelperFunctions
}
case Statistic.Defense:
{
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
var opponentSide = pokemon.Battle.Sides.First(x => x != pokemon.BattleSide);
return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y =>
y.MoveData.Category == MoveCategory.Physical ||
y.MoveData.SecondaryEffect?.Name == MoveEffectNames.Psyshock));
@@ -247,7 +247,7 @@ public static class AIHelperFunctions
}
case Statistic.SpecialDefense:
{
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
var opponentSide = pokemon.Battle.Sides.First(x => x != pokemon.BattleSide);
return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y =>
y.MoveData.Category == MoveCategory.Special &&
y.MoveData.SecondaryEffect?.Name != MoveEffectNames.Psyshock));
@@ -257,7 +257,7 @@ public static class AIHelperFunctions
if (!pokemon.HasMoveWithEffect(MoveEffectNames.ElectroBall))
{
var targetSpeed = pokemon.BoostedStats.Speed;
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
var opponentSide = pokemon.Battle.Sides.First(x => x != pokemon.BattleSide);
return opponentSide.Pokemon.WhereNotNull().Select(opponent => opponent.BoostedStats.Speed)
.Any(foeSpeed => targetSpeed > foeSpeed && targetSpeed < foeSpeed * 2.5);
}
@@ -272,8 +272,8 @@ public static class AIHelperFunctions
return true;
}
private static void GetTargetStatRaiseScoreOne(ref int score, IPokemon target, Statistic stat, sbyte increment,
AIMoveState move, float desireMult = 1)
private static void GetTargetStatRaiseScoreOne(ref int score, IBattlePokemon target, Statistic stat,
sbyte increment, AIMoveState move, float desireMult = 1)
{
var oldStage = target.StatBoost.GetStatistic(stat);
var newStage = (sbyte)(oldStage + increment);
@@ -282,7 +282,7 @@ public static class AIHelperFunctions
var actualIncrement = incMult;
incMult -= 1;
incMult *= desireMult;
var opponentSide = target.BattleData!.Battle.Sides.First(x => x != target.BattleData.BattleSide);
var opponentSide = target.Battle.Sides.First(x => x != target.BattleSide);
switch (stat)
{
@@ -407,7 +407,7 @@ public static class AIHelperFunctions
}
}
private static void GetTargetStatDropScoreOne(ref int score, IPokemon target, Statistic stat, sbyte decrement,
private static void GetTargetStatDropScoreOne(ref int score, IBattlePokemon target, Statistic stat, sbyte decrement,
AIMoveState move, float desireMult = 1)
{
var oldStage = target.StatBoost.GetStatistic(stat);
@@ -416,7 +416,7 @@ public static class AIHelperFunctions
Gen7BattleStatCalculator.GetStatBoostModifier(Math.Max(newStage, (sbyte)-6));
decMult -= 1;
decMult *= desireMult;
var opponentSide = target.BattleData!.Battle.Sides.First(x => x != target.BattleData.BattleSide);
var opponentSide = target.Battle.Sides.First(x => x != target.BattleSide);
switch (stat)
{
@@ -519,11 +519,11 @@ public static class AIHelperFunctions
/// <summary>
/// Calculates the score for the generic concept of raising a target's stats.
/// </summary>
private static int GetTargetStatRaiseScoreGeneric(int score, IPokemon target, StatisticSet<sbyte> statChanges,
private static int GetTargetStatRaiseScoreGeneric(int score, IBattlePokemon target, StatisticSet<sbyte> statChanges,
AIMoveState move, float desireMult = 1)
{
var totalIncrement = statChanges.Sum(x => x.value);
var turns = target.BattleData!.Battle.CurrentTurnNumber - target.BattleData!.SwitchInTurn;
var turns = target.Battle.CurrentTurnNumber - target.SwitchInTurn;
if (turns < 2 && move.Move.Category == MoveCategory.Status)
score += (int)(totalIncrement * desireMult * 5);
@@ -532,11 +532,11 @@ public static class AIHelperFunctions
return score;
}
private static int GetTargetStatDropScoreGeneric(int score, IPokemon target, StatisticSet<sbyte> statChanges,
private static int GetTargetStatDropScoreGeneric(int score, IBattlePokemon target, StatisticSet<sbyte> statChanges,
AIMoveState move, float desireMult = 1)
{
var totalDecrement = statChanges.Sum(x => x.value);
var turns = target.BattleData!.Battle.CurrentTurnNumber - target.BattleData!.SwitchInTurn;
var turns = target.Battle.CurrentTurnNumber - target.SwitchInTurn;
if (turns < 2 && move.Move.Category == MoveCategory.Status)
score += (int)(totalDecrement * desireMult * 5);
@@ -545,34 +545,34 @@ public static class AIHelperFunctions
return score;
}
private static int GetScoreChangeForAdditionalEffect(this AIMoveState move, IPokemon? target)
private static int GetScoreChangeForAdditionalEffect(this AIMoveState move, IBattlePokemon? target)
{
if (move.Move.SecondaryEffect is null)
return 0;
if (move.User.ActiveAbility?.Name == AbilityNames.SheerForce)
return -999;
if (target is not null && target.BattleData?.Position != move.User.BattleData?.Position &&
if (target is not null && target.Position != move.User.Position &&
target.ActiveAbility?.Name == AbilityNames.ShieldDust)
return -999;
if ((move.Move.SecondaryEffect.Chance < 100 && move.User.ActiveAbility?.Name == AbilityNames.SereneGrace) ||
move.User.BattleData?.BattleSide.VolatileScripts.Contains<RainbowEffect>() == true)
move.User.BattleSide.VolatileScripts.Contains<RainbowEffect>() == true)
{
return 5;
}
return 0;
}
private static bool HasMoveWithEffect(this IPokemon pokemon, params StringKey[] effect)
private static bool HasMoveWithEffect(this IBattlePokemon pokemon, params StringKey[] effect)
{
return pokemon.Moves.WhereNotNull().Any(move => move.MoveData.SecondaryEffect?.Name is not null &&
effect.Contains(move.MoveData.SecondaryEffect.Name));
}
private static bool Opposes(this IPokemon pokemon, IPokemon target) =>
pokemon.BattleData?.BattleSide != target.BattleData?.BattleSide;
private static bool Opposes(this IBattlePokemon pokemon, IBattlePokemon target) =>
pokemon.BattleSide != target.BattleSide;
public static bool WantsStatusProblem(IPokemon pokemon, StringKey? status)
public static bool WantsStatusProblem(IBattlePokemon pokemon, StringKey? status)
{
if (status is null)
return true;

View File

@@ -19,7 +19,8 @@ public static class AISwitchFunctions
/// <summary>
/// Switch out if the Perish Song effect is about to cause the Pokémon to faint.
/// </summary>
private static bool PerishSong(IExplicitAI ai, IPokemon pokemon, IBattle battle, IReadOnlyList<IPokemon> reserves)
private static bool PerishSong(IExplicitAI ai, IBattlePokemon pokemon, IBattle battle,
IReadOnlyList<IBattlePokemon> reserves)
{
if (!pokemon.Volatile.TryGet<PerishSongEffect>(out var effect))
return false;
@@ -29,8 +30,8 @@ public static class AISwitchFunctions
/// <summary>
/// Switch out if the Pokémon is expected to take significant end-of-turn damage.
/// </summary>
private static bool SignificantEndOfTurnDamage(IExplicitAI ai, IPokemon pokemon, IBattle battle,
IReadOnlyList<IPokemon> reserves)
private static bool SignificantEndOfTurnDamage(IExplicitAI ai, IBattlePokemon pokemon, IBattle battle,
IReadOnlyList<IBattlePokemon> reserves)
{
var eorDamage = 0;
pokemon.RunScriptHook<IAIInfoScriptExpectedEndOfTurnDamage>(x =>
@@ -61,21 +62,20 @@ public static class AISwitchFunctions
return false;
}
private static bool HighDamageFromFoe(IExplicitAI ai, IPokemon pokemon, IBattle battle,
IReadOnlyList<IPokemon> reserves)
private static bool HighDamageFromFoe(IExplicitAI ai, IBattlePokemon pokemon, IBattle battle,
IReadOnlyList<IBattlePokemon> reserves)
{
if (!ai.TrainerHighSkill)
return false;
if (pokemon.CurrentHealth >= pokemon.MaxHealth / 2)
return false;
var bigThreat = false;
var opponents = battle.Sides.Where(x => x != pokemon.BattleData?.BattleSide)
.SelectMany(x => x.Pokemon.WhereNotNull());
var opponents = battle.Sides.Where(x => x != pokemon.BattleSide).SelectMany(x => x.Pokemon.WhereNotNull());
foreach (var opponent in opponents)
{
if (Math.Abs(opponent.Level - pokemon.Level) > 5)
continue;
var lastMoveUsed = opponent.BattleData?.LastMoveChoice;
var lastMoveUsed = opponent.LastMoveChoice;
if (lastMoveUsed is null)
continue;
var moveData = lastMoveUsed.ChosenMove.MoveData;
@@ -107,15 +107,15 @@ public static class AISwitchFunctions
/// <summary>
/// Switch out to cure a status problem or heal HP with abilities like Natural Cure or Regenerator.
/// </summary>
private static bool CureStatusProblemBySwitchingOut(IExplicitAI ai, IPokemon pokemon, IBattle battle,
IReadOnlyList<IPokemon> reserves)
private static bool CureStatusProblemBySwitchingOut(IExplicitAI ai, IBattlePokemon pokemon, IBattle battle,
IReadOnlyList<IBattlePokemon> reserves)
{
if (pokemon.ActiveAbility == null)
return false;
// Don't try to cure a status problem/heal a bit of HP if entry hazards will
// KO the battler if it switches back in
var entryHazardDamage = ExplicitAI.CalculateEntryHazardDamage(pokemon, pokemon.BattleData!.BattleSide);
var entryHazardDamage = ExplicitAI.CalculateEntryHazardDamage(pokemon, pokemon.BattleSide);
if (entryHazardDamage >= pokemon.CurrentHealth)
return false;
if (pokemon.StatusScript.Script is null)
@@ -151,7 +151,7 @@ public static class AISwitchFunctions
if (pokemon.StatusScript.Script is Poisoned or BadlyPoisoned &&
!reserves.Any(p => p.Types.Any(t => t.Name == TypeNames.Poison)))
{
if (pokemon.BattleData!.BattleSide.VolatileScripts.TryGet<ToxicSpikesEffect>(out _))
if (pokemon.BattleSide.VolatileScripts.TryGet<ToxicSpikesEffect>(out _))
{
return false;
}
@@ -179,7 +179,7 @@ public static class AISwitchFunctions
var hasDamagingMove = pokemon.Moves.Any(m => m?.MoveData.Category != MoveCategory.Status);
if (hasDamagingMove)
{
var opponents = battle.Sides.Where(x => x != pokemon.BattleData?.BattleSide)
var opponents = battle.Sides.Where(x => x != pokemon.BattleSide)
.SelectMany(x => x.Pokemon.WhereNotNull());
var weakFoe = opponents.Any(opponent => opponent.CurrentHealth < opponent.MaxHealth / 3);
if (weakFoe)