using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Static.Moves; using PkmnLib.Static.Utils; namespace PkmnLib.Dynamic.AI; /// /// The base class for implementing an AI for Pokémon. /// [PublicAPI, UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)] public abstract class PokemonAI { /// /// The name of the AI. /// public StringKey Name { get; } /// protected PokemonAI(StringKey name) { Name = name; } /// /// Gets the choice for the Pokémon. /// public abstract ITurnChoice GetChoice(IBattle battle, IPokemon pokemon); /// /// For a given user and move, returns the valid targets for that move. /// public IEnumerable<(byte side, byte position)> GetValidTargetsForMove(IPokemon user, ILearnedMove move) { var userBattleData = user.BattleData!; switch (move.MoveData.Target) { case MoveTarget.Adjacent: yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.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)); 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); 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)); break; case MoveTarget.All: yield return (userBattleData.SideIndex, userBattleData.Position); break; case MoveTarget.AllAdjacent: yield return (userBattleData.SideIndex, userBattleData.Position); break; case MoveTarget.AllAdjacentOpponent: yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position); break; case MoveTarget.AllAlly: yield return (userBattleData.SideIndex, userBattleData.Position); break; case MoveTarget.AllOpponent: yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position); break; case MoveTarget.Any: foreach (var side in userBattleData.Battle.Sides) { foreach (var pokemon in side.Pokemon) { if (pokemon?.BattleData == null) continue; yield return (side.Index, pokemon.BattleData!.Position); } } break; case MoveTarget.RandomOpponent: yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position); break; case MoveTarget.SelfUse: yield return (userBattleData.SideIndex, userBattleData.Position); break; default: throw new ArgumentOutOfRangeException(); } yield break; byte GetOppositeSide(byte side) => side == 0 ? (byte)1 : (byte)0; } public static List InstantiateAis(IDynamicLibrary library) { return AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()) .Where(type => type.IsSubclassOf(typeof(PokemonAI)) && !type.IsAbstract).Select(x => { var ctorWithLibrary = x.GetConstructor([typeof(IDynamicLibrary)]); if (ctorWithLibrary != null) return Activator.CreateInstance(x, library); var defaultCtor = x.GetConstructor(Type.EmptyTypes); if (defaultCtor != null) return Activator.CreateInstance(x); throw new InvalidOperationException($"No suitable constructor found for {x.Name}. " + "Ensure it has a constructor with IDynamicLibrary parameter or a default constructor."); }).Cast().ToList(); } }