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);
}
}