Move all battle state from IPokemon to an ephemeral IBattlePokemon wrapper
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user