This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using PkmnLib.Dynamic.BattleFlow;
|
||||
using PkmnLib.Dynamic.Libraries;
|
||||
using PkmnLib.Dynamic.Models;
|
||||
@@ -8,21 +9,27 @@ using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Dynamic.AI.Explicit;
|
||||
|
||||
public interface IExplicitAI
|
||||
{
|
||||
public bool TrainerHighSkill { get; }
|
||||
public bool TrainerMediumSkill { get; }
|
||||
public IRandom Random { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An explicit AI that has explicitly written logic for each Pokémon and move.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is heavily based on the AI used in <a href="https://github.com/Maruno17/pokemon-essentials">Pokémon Essentials</a>
|
||||
/// </remarks>
|
||||
public class ExplicitAI : PokemonAI
|
||||
public partial class ExplicitAI : PokemonAI, IExplicitAI
|
||||
{
|
||||
public const int MoveFailScore = 20;
|
||||
public const int MoveUselessScore = 60;
|
||||
public const int MoveBaseScore = 100;
|
||||
|
||||
private const float TrainerSkill = 100; // TODO: This should be configurable
|
||||
private bool CanPredictMoveFailure => true; // TODO: This should be configurable
|
||||
private bool ScoreMoves => true; // TODO: This should be configurable
|
||||
private SkillFlags _skillFlags = new();
|
||||
|
||||
private float MoveScoreThreshold => (float)(0.6f + 0.35f * Math.Sqrt(Math.Min(TrainerSkill, 100) / 100f));
|
||||
|
||||
@@ -30,17 +37,39 @@ public class ExplicitAI : PokemonAI
|
||||
|
||||
private readonly IRandom _random = new RandomImpl();
|
||||
|
||||
public class SkillFlags
|
||||
{
|
||||
// TODO: Make these configurable
|
||||
public bool CanPredictMoveFailure { get; set; } = true;
|
||||
public bool ScoreMoves { get; set; } = true;
|
||||
public bool ConsiderSwitching { get; set; } = true;
|
||||
public bool ReserveLastPokemon { get; set; } = true;
|
||||
public bool UsePokemonInOrder { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExplicitAI(IDynamicLibrary library) : base("explicit")
|
||||
{
|
||||
_handlers = library.ExplicitAIHandlers;
|
||||
}
|
||||
|
||||
public bool TrainerHighSkill => TrainerSkill >= 45;
|
||||
public bool TrainerMediumSkill => TrainerSkill >= 32;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRandom Random => _random;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon)
|
||||
{
|
||||
if (battle.HasForcedTurn(pokemon, out var choice))
|
||||
return choice;
|
||||
if (TryChooseToSwitchOut(battle, pokemon, false, out var turnChoice))
|
||||
{
|
||||
AILogging.LogInformation($"{pokemon} is switching out.");
|
||||
return turnChoice;
|
||||
}
|
||||
|
||||
var moveChoices = GetMoveScores(pokemon, battle);
|
||||
if (moveChoices.Count == 0)
|
||||
{
|
||||
@@ -48,7 +77,29 @@ public class ExplicitAI : PokemonAI
|
||||
return battle.Library.MiscLibrary.ReplacementChoice(pokemon, opponentSide, pokemon.BattleData.Position);
|
||||
}
|
||||
var maxScore = moveChoices.Max(x => x.score);
|
||||
// TODO: Consider switching to a different pokémon if the score is too low
|
||||
if (TrainerHighSkill && CanSwitch(pokemon))
|
||||
{
|
||||
var badMoves = false;
|
||||
if (maxScore <= MoveUselessScore)
|
||||
{
|
||||
badMoves = CanAttack(pokemon);
|
||||
if (!badMoves && _random.GetInt(100) < 25)
|
||||
badMoves = true;
|
||||
}
|
||||
else if (maxScore < MoveBaseScore * MoveScoreThreshold && pokemon.BattleData?.TurnsOnField > 2 &&
|
||||
_random.GetInt(100) < 80)
|
||||
{
|
||||
badMoves = true;
|
||||
}
|
||||
if (badMoves)
|
||||
{
|
||||
AILogging.LogInformation($"{pokemon} has no good moves, considering switching.");
|
||||
if (TryChooseToSwitchOut(battle, pokemon, badMoves, out var switchChoice))
|
||||
{
|
||||
return switchChoice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var threshold = (float)Math.Floor(maxScore * MoveScoreThreshold);
|
||||
var considerChoices = moveChoices.Select(x => (x, Math.Max(x.score - threshold, 0))).ToArray();
|
||||
@@ -122,7 +173,7 @@ public class ExplicitAI : PokemonAI
|
||||
}
|
||||
}
|
||||
var aiMove = new AIMoveState(user, moveData);
|
||||
if (CanPredictMoveFailure && PredictMoveFailure(user, battle, aiMove))
|
||||
if (_skillFlags.CanPredictMoveFailure && PredictMoveFailure(user, battle, aiMove))
|
||||
{
|
||||
AILogging.LogInformation($"{user} is considering {aiMove.Move.Name} but it will fail.");
|
||||
AddMoveToChoices(index, MoveFailScore);
|
||||
@@ -210,8 +261,8 @@ public class ExplicitAI : PokemonAI
|
||||
return true;
|
||||
|
||||
// Check if the move will fail based on the handlers
|
||||
return aiMove.Move.SecondaryEffect != null &&
|
||||
_handlers.MoveWillFail(aiMove.Move.SecondaryEffect.Name, new MoveOption(aiMove, battle, null));
|
||||
return aiMove.Move.SecondaryEffect != null && _handlers.MoveWillFail(this, aiMove.Move.SecondaryEffect.Name,
|
||||
new MoveOption(aiMove, battle, null));
|
||||
}
|
||||
|
||||
private static readonly StringKey PsychicTerrainName = new("psychic_terrain");
|
||||
@@ -226,8 +277,8 @@ public class ExplicitAI : PokemonAI
|
||||
|
||||
private bool PredictMoveFailureAgainstTarget(IPokemon user, AIMoveState aiMove, IPokemon target, IBattle battle)
|
||||
{
|
||||
if (aiMove.Move.SecondaryEffect != null && _handlers.MoveWillFailAgainstTarget(aiMove.Move.SecondaryEffect.Name,
|
||||
new MoveOption(aiMove, battle, target)))
|
||||
if (aiMove.Move.SecondaryEffect != null && _handlers.MoveWillFailAgainstTarget(this,
|
||||
aiMove.Move.SecondaryEffect.Name, new MoveOption(aiMove, battle, target)))
|
||||
return true;
|
||||
if (aiMove.Move.Priority > 0)
|
||||
{
|
||||
@@ -281,20 +332,20 @@ public class ExplicitAI : PokemonAI
|
||||
score += targetScore;
|
||||
affectedTargets++;
|
||||
}
|
||||
if (affectedTargets == 0 && CanPredictMoveFailure)
|
||||
if (affectedTargets == 0 && _skillFlags.CanPredictMoveFailure)
|
||||
{
|
||||
return MoveFailScore;
|
||||
}
|
||||
if (affectedTargets > 0)
|
||||
score = (int)(score / (float)affectedTargets);
|
||||
}
|
||||
if (ScoreMoves)
|
||||
if (_skillFlags.ScoreMoves)
|
||||
{
|
||||
if (aiMove.Move.SecondaryEffect != null)
|
||||
{
|
||||
_handlers.ApplyMoveEffectScore(aiMove.Move.SecondaryEffect.Name, new MoveOption(aiMove, battle, null),
|
||||
ref score);
|
||||
_handlers.ApplyGenerateMoveScoreModifiers(new MoveOption(aiMove, battle, null), ref score);
|
||||
_handlers.ApplyMoveEffectScore(this, aiMove.Move.SecondaryEffect.Name,
|
||||
new MoveOption(aiMove, battle, null), ref score);
|
||||
_handlers.ApplyGenerateMoveScoreModifiers(this, new MoveOption(aiMove, battle, null), ref score);
|
||||
}
|
||||
}
|
||||
if (score < 0)
|
||||
@@ -304,18 +355,18 @@ public class ExplicitAI : PokemonAI
|
||||
|
||||
private int GetMoveScoreAgainstTarget(IPokemon user, AIMoveState aiMove, IPokemon target, IBattle battle)
|
||||
{
|
||||
if (CanPredictMoveFailure && PredictMoveFailureAgainstTarget(user, aiMove, target, battle))
|
||||
if (_skillFlags.CanPredictMoveFailure && PredictMoveFailureAgainstTarget(user, aiMove, target, battle))
|
||||
{
|
||||
AILogging.LogInformation($"{user} is considering {aiMove.Move.Name} against {target} but it will fail.");
|
||||
return -1;
|
||||
}
|
||||
var score = MoveBaseScore;
|
||||
if (ScoreMoves && aiMove.Move.SecondaryEffect != null)
|
||||
if (_skillFlags.ScoreMoves && aiMove.Move.SecondaryEffect != null)
|
||||
{
|
||||
var estimatedDamage = AIHelpers.CalculateDamageEstimation(aiMove.Move, user, target, battle.Library);
|
||||
var moveOption = new MoveOption(aiMove, battle, target, estimatedDamage);
|
||||
_handlers.ApplyMoveEffectAgainstTargetScore(aiMove.Move.SecondaryEffect.Name, moveOption, ref score);
|
||||
_handlers.ApplyGenerateMoveAgainstTargetScoreModifiers(moveOption, ref score);
|
||||
_handlers.ApplyMoveEffectAgainstTargetScore(this, aiMove.Move.SecondaryEffect.Name, moveOption, ref score);
|
||||
_handlers.ApplyGenerateMoveAgainstTargetScoreModifiers(this, moveOption, ref score);
|
||||
}
|
||||
|
||||
if (aiMove.Move.Target.TargetsFoe() && target.BattleData?.SideIndex == user.BattleData?.SideIndex &&
|
||||
@@ -343,4 +394,17 @@ public class ExplicitAI : PokemonAI
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool CanAttack(IPokemon pokemon)
|
||||
{
|
||||
if (pokemon.Volatile.Contains("requires_recharge"))
|
||||
return false;
|
||||
if (pokemon.HasStatus("frozen") || pokemon.HasStatus("sleep"))
|
||||
return false;
|
||||
if (pokemon.ActiveAbility?.Name == "truant" && pokemon.Volatile.Contains("truant_effect"))
|
||||
return false;
|
||||
if (pokemon.Volatile.Contains("flinch_effect"))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user