Implement highest damage AI, further work on AI runner, random fixes
All checks were successful
Build / Build (push) Successful in 51s

This commit is contained in:
Deukhoofd 2025-07-05 14:56:25 +02:00
parent 32aaa5150a
commit c795f20e54
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
30 changed files with 261 additions and 26 deletions

View File

@ -12,7 +12,7 @@ internal static class Program
private static Task<int> Main(string[] args) private static Task<int> Main(string[] args)
{ {
Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger(); Log.Logger = new LoggerConfiguration().MinimumLevel.Information().WriteTo.Console().CreateLogger();
Log.Information("Starting AI Runner..."); Log.Information("Starting AI Runner...");
_availableAIs = AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()) _availableAIs = AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes())
.Where(type => type.IsSubclassOf(typeof(PokemonAI)) && !type.IsAbstract).Select(Activator.CreateInstance) .Where(type => type.IsSubclassOf(typeof(PokemonAI)) && !type.IsAbstract).Select(Activator.CreateInstance)

View File

@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using PkmnLib.Dynamic.AI; using PkmnLib.Dynamic.AI;
using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models;
@ -12,19 +13,34 @@ public static class TestCommandRunner
{ {
internal static async Task RunTestCommand(PokemonAI ai1, PokemonAI ai2, int battles) internal static async Task RunTestCommand(PokemonAI ai1, PokemonAI ai2, int battles)
{ {
var t1 = DateTime.UtcNow;
var library = DynamicLibraryImpl.Create([ var library = DynamicLibraryImpl.Create([
new Gen7Plugin(), new Gen7Plugin(),
]); ]);
Log.Information("Running {Battles} battles between {AI1} and {AI2}", battles, ai1.Name, ai2.Name); Log.Information("Running {Battles} battles between {AI1} and {AI2}", battles, ai1.Name, ai2.Name);
var averageTimePerTurnPerBattle = new List<double>(battles); var averageTimePerTurnPerBattle = new List<double>(battles);
var results = new List<BattleResult>(battles); var results = new ConcurrentBag<BattleResult>();
var random = new RandomImpl(); var rootRandom = new RandomImpl();
const int maxTasks = 10;
var battleTasks = new Task[maxTasks];
var randoms = new IRandom[maxTasks];
for (var i = 0; i < maxTasks; i++)
{
randoms[i] = new RandomImpl(rootRandom.GetInt());
battleTasks[i] = Task.CompletedTask; // Initialize tasks to avoid null references
}
// Here you would implement the logic to run the AI scripts against each other. // Here you would implement the logic to run the AI scripts against each other.
// This is a placeholder for demonstration purposes. // This is a placeholder for demonstration purposes.
for (var i = 0; i < battles; i++) for (var i = 0; i < battles; i++)
{ {
Log.Information("Battle {BattleNumber}: {AI1} vs {AI2}", i + 1, ai1.Name, ai2.Name); var taskIndex = i % maxTasks;
var index = i;
var battleTask = Task.Run(async () =>
{
Log.Information("Battle {BattleNumber}: {AI1} vs {AI2}", index + 1, ai1.Name, ai2.Name);
var random = randoms[taskIndex];
var battle = GenerateBattle(library, 3, random); var battle = GenerateBattle(library, 3, random);
var timePerTurn = new List<double>(20); var timePerTurn = new List<double>(20);
while (!battle.HasEnded) while (!battle.HasEnded)
@ -33,11 +49,22 @@ public static class TestCommandRunner
timePerTurn.Add(res.MsPerTurn); timePerTurn.Add(res.MsPerTurn);
} }
var result = battle.Result; var result = battle.Result;
Log.Information("Battle {BattleNumber} ended with result: {Result}", i + 1, result); Log.Information("Battle {BattleNumber} ended with result: {Result}", index + 1, result);
averageTimePerTurnPerBattle.Add(timePerTurn.Average()); averageTimePerTurnPerBattle.Add(timePerTurn.Average());
results.Add(result.Value); results.Add(result.Value);
});
battleTasks[taskIndex] = battleTask;
if (i % maxTasks == maxTasks - 1 || i == battles - 1)
{
Log.Debug("Starting {TaskCount} tasks", maxTasks);
await Task.WhenAll(battleTasks);
Log.Debug("Batch of {TaskCount} tasks completed", maxTasks);
battleTasks = new Task[maxTasks]; // Reset tasks for the next batch
} }
Log.Information("All battles completed"); }
var t2 = DateTime.UtcNow;
Log.Information("{Amount} battles completed in {Duration} ms", battles, (t2 - t1).TotalMilliseconds);
var averageTimePerTurn = averageTimePerTurnPerBattle.Average(); var averageTimePerTurn = averageTimePerTurnPerBattle.Average();
Log.Information("Average time per turn: {AverageTimePerTurn} ms", averageTimePerTurn); Log.Information("Average time per turn: {AverageTimePerTurn} ms", averageTimePerTurn);
@ -45,8 +72,11 @@ public static class TestCommandRunner
var winCount2 = results.Count(x => x.WinningSide == 1); var winCount2 = results.Count(x => x.WinningSide == 1);
var drawCount = results.Count(x => x.WinningSide == null); var drawCount = results.Count(x => x.WinningSide == null);
Log.Information("Results: {AI1} wins: {WinCount1}, {AI2} wins: {WinCount2}, Draws: {DrawCount}", ai1.Name, var winRate1 = winCount1 / (double)battles * 100;
winCount1, ai2.Name, winCount2, drawCount); var winRate2 = winCount2 / (double)battles * 100;
Log.Information("AI {AI1} win rate: {WinRate1:F3}% ({WinCount1} wins)", ai1.Name, winRate1, winCount1);
Log.Information("AI {AI2} win rate: {WinRate2:F3}% ({WinCount2} wins)", ai2.Name, winRate2, winCount2);
Log.Information("Draw rate: {DrawRate:F3}% ({DrawCount} draws)", drawCount / (double)battles * 100, drawCount);
} }
private static PokemonPartyImpl GenerateParty(IDynamicLibrary library, int length, IRandom random) private static PokemonPartyImpl GenerateParty(IDynamicLibrary library, int length, IRandom random)
@ -64,7 +94,7 @@ public static class TestCommandRunner
IsHidden = false, IsHidden = false,
Index = abilityIndex, Index = abilityIndex,
}, level, 0, species.GetRandomGender(random), 0, nature.Name); }, level, 0, species.GetRandomGender(random), 0, nature.Name);
var moves = defaultForm.Moves.GetDistinctLevelMoves().OrderBy(x => random.GetInt()).Take(4); var moves = defaultForm.Moves.GetDistinctLevelMoves().OrderBy(_ => random.GetInt()).Take(4);
foreach (var move in moves) foreach (var move in moves)
mon.LearnMove(move, MoveLearnMethod.LevelUp, 255); mon.LearnMove(move, MoveLearnMethod.LevelUp, 255);

View File

@ -0,0 +1,92 @@
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.AI;
public class HighestDamageAI : PokemonAI
{
/// <inheritdoc />
public HighestDamageAI() : base("highest_damage")
{
}
/// <inheritdoc />
public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon)
{
var opponentSide = pokemon.BattleData!.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();
if (opponent == null)
{
var move = moves.FirstOrDefault();
if (move != null)
{
return new MoveChoice(pokemon, move, opponentSide, 0);
}
return battle.Library.MiscLibrary.ReplacementChoice(pokemon, opponentSide, 0);
}
var movesWithDamage = moves.Select(move =>
{
var hitData = new CustomHitData
{
BasePower = move.MoveData.BasePower,
Effectiveness =
battle.Library.StaticLibrary.Types.GetEffectiveness(move.MoveData.MoveType, opponent.Types),
Type = move.MoveData.MoveType,
};
return new
{
Move = move,
Damage = battle.Library.DamageCalculator.GetDamage(null, move.MoveData.Category, pokemon, opponent, 1,
0, hitData),
};
}).OrderByDescending(x => x.Damage).ToList();
if (movesWithDamage.Count == 0)
{
return battle.Library.MiscLibrary.ReplacementChoice(pokemon, opponentSide, 0);
}
var bestMove = movesWithDamage.First().Move;
return new MoveChoice(pokemon, bestMove, opponentSide, 0);
}
private class CustomHitData : IHitData
{
/// <inheritdoc />
public bool IsCritical => false;
/// <inheritdoc />
public ushort BasePower { get; init; }
/// <inheritdoc />
public float Effectiveness { get; init; }
/// <inheritdoc />
public uint Damage => 0;
/// <inheritdoc />
public TypeIdentifier? Type { get; init; }
/// <inheritdoc />
public bool IsContact => false;
/// <inheritdoc />
public bool HasFailed => false;
/// <inheritdoc />
public void Fail()
{
}
/// <inheritdoc />
public void SetFlag(StringKey flag)
{
}
/// <inheritdoc />
public bool HasFlag(StringKey flag) => false;
}
}

View File

@ -8,7 +8,7 @@ namespace PkmnLib.Dynamic.AI;
/// <summary> /// <summary>
/// The base class for implementing an AI for Pokémon. /// The base class for implementing an AI for Pokémon.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI, UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
public abstract class PokemonAI public abstract class PokemonAI
{ {
/// <summary> /// <summary>

View File

@ -38,6 +38,8 @@ public interface IMoveChoice : ITurnChoice
/// </summary> /// </summary>
Dictionary<StringKey, object?>? AdditionalData { get; } Dictionary<StringKey, object?>? AdditionalData { get; }
void SetAdditionalData(StringKey key, object? value);
/// <summary> /// <summary>
/// Volatile effects that are applied to the move choice. /// Volatile effects that are applied to the move choice.
/// </summary> /// </summary>
@ -84,7 +86,14 @@ public class MoveChoice : TurnChoice, IMoveChoice
public ScriptContainer Script { get; set; } = new(); public ScriptContainer Script { get; set; } = new();
/// <inheritdoc /> /// <inheritdoc />
public Dictionary<StringKey, object?>? AdditionalData { get; } public Dictionary<StringKey, object?>? AdditionalData { get; private set; }
/// <inheritdoc />
public void SetAdditionalData(StringKey key, object? value)
{
AdditionalData ??= new Dictionary<StringKey, object?>();
AdditionalData[key] = value;
}
/// <inheritdoc /> /// <inheritdoc />
public IScriptSet Volatile { get; } public IScriptSet Volatile { get; }

View File

@ -936,6 +936,8 @@ public class PokemonImpl : ScriptSource, IPokemon
AbilityScript.Clear(); AbilityScript.Clear();
} }
private (IAbility, AbilityIndex)? _abilityCache;
/// <inheritdoc /> /// <inheritdoc />
public IAbility? ActiveAbility public IAbility? ActiveAbility
{ {
@ -945,9 +947,12 @@ public class PokemonImpl : ScriptSource, IPokemon
return null; return null;
if (OverrideAbility != null) if (OverrideAbility != null)
return OverrideAbility; return OverrideAbility;
if (_abilityCache is not null && _abilityCache.Value.Item2 == AbilityIndex)
return _abilityCache.Value.Item1;
var ability = Form.GetAbility(AbilityIndex); var ability = Form.GetAbility(AbilityIndex);
if (!Library.StaticLibrary.Abilities.TryGet(ability, out var abilityObj)) if (!Library.StaticLibrary.Abilities.TryGet(ability, out var abilityObj))
throw new KeyNotFoundException($"Ability {ability} not found."); throw new KeyNotFoundException($"Ability {ability} not found.");
_abilityCache = (abilityObj, AbilityIndex);
return abilityObj; return abilityObj;
} }
} }

View File

@ -0,0 +1,21 @@
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
using Assembly = System.Reflection.Assembly;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts;
public class GenericScriptTests
{
[Test]
public async Task EveryScriptHasAttribute()
{
var scripts = typeof(Gen7Plugin).Assembly.GetTypes()
.Where(t => t.IsSubclassOf(typeof(Script)) && !t.IsAbstract);
foreach (var script in scripts)
{
var attributes = script.GetCustomAttributes(typeof(ScriptAttribute), false);
await Assert.That(attributes).IsNotEmpty().Because(
$"Script {script.Name} does not have the Script attribute defined.");
}
}
}

View File

@ -2,6 +2,7 @@ using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Battle; namespace PkmnLib.Plugin.Gen7.Scripts.Battle;
[Script(ScriptCategory.Battle, "wonder_room_effect")]
public class WonderRoomEffect : Script, IScriptChangeDefensiveStatValue, IScriptOnEndTurn public class WonderRoomEffect : Script, IScriptChangeDefensiveStatValue, IScriptOnEndTurn
{ {
public int TurnsLeft { get; private set; } = 5; public int TurnsLeft { get; private set; } = 5;

View File

@ -12,6 +12,7 @@ public class Bounce : Script, IScriptPreventMove, IScriptOnBeforeMove, IScriptOn
return; return;
move.User.Volatile.Add(new ChargeBounceEffect(move.User)); move.User.Volatile.Add(new ChargeBounceEffect(move.User));
move.MoveChoice.SetAdditionalData("bounce_charge", true);
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("bounce_charge", new Dictionary<string, object> move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("bounce_charge", new Dictionary<string, object>
{ {
{ "user", move.User }, { "user", move.User },

View File

@ -12,6 +12,7 @@ public class Dig : Script, IScriptPreventMove, IScriptOnBeforeMove
return; return;
move.User.Volatile.Add(new DigEffect(move.User)); move.User.Volatile.Add(new DigEffect(move.User));
move.MoveChoice.SetAdditionalData("dig_charge", true);
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dig_charge", new Dictionary<string, object> move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dig_charge", new Dictionary<string, object>
{ {
{ "user", move.User }, { "user", move.User },

View File

@ -12,6 +12,7 @@ public class Dive : Script, IScriptPreventMove, IScriptOnSecondaryEffect
return; return;
move.User.Volatile.Add(new DiveEffect(move.User)); move.User.Volatile.Add(new DiveEffect(move.User));
move.MoveChoice.SetAdditionalData("dive_charge", true);
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dive_charge", new Dictionary<string, object> move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dive_charge", new Dictionary<string, object>
{ {
{ "user", move.User }, { "user", move.User },

View File

@ -12,6 +12,7 @@ public class Fly : Script, IScriptPreventMove, IScriptOnBeforeMove
return; return;
move.User.Volatile.Add(new ChargeFlyEffect(move.User)); move.User.Volatile.Add(new ChargeFlyEffect(move.User));
move.MoveChoice.SetAdditionalData("fly_charge", true);
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("fly_charge", new Dictionary<string, object> move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("fly_charge", new Dictionary<string, object>
{ {
{ "user", move.User }, { "user", move.User },

View File

@ -23,6 +23,11 @@ public class MeFirst : Script, IScriptChangeMove
choice.Fail(); choice.Fail();
return; return;
} }
if (battleData.Battle.Library.MiscLibrary.IsReplacementChoice(targetMove))
{
choice.Fail();
return;
}
var targetMoveData = targetMove.ChosenMove.MoveData; var targetMoveData = targetMove.ChosenMove.MoveData;
moveName = targetMoveData.Name; moveName = targetMoveData.Name;
choice.Volatile.Add(new MeFirstPowerBoost()); choice.Volatile.Add(new MeFirstPowerBoost());

View File

@ -16,6 +16,7 @@ public class PhantomForce : Script, IScriptPreventMove, IScriptOnSecondaryEffect
return; return;
move.User.Volatile.Add(new PhantomForceCharge(move.User)); move.User.Volatile.Add(new PhantomForceCharge(move.User));
move.MoveChoice.SetAdditionalData("phantom_force_charge", true);
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("phantom_force_charge", move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("phantom_force_charge",
new Dictionary<string, object> new Dictionary<string, object>
{ {

View File

@ -12,6 +12,7 @@ public class ShadowForce : Script, IScriptPreventMove, IScriptOnSecondaryEffect
return; return;
move.User.Volatile.Add(new ShadowForceCharge(move.User)); move.User.Volatile.Add(new ShadowForceCharge(move.User));
move.MoveChoice.SetAdditionalData("shadow_force_charge", true);
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("shadow_force_charge", move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("shadow_force_charge",
new Dictionary<string, object> new Dictionary<string, object>
{ {

View File

@ -12,6 +12,7 @@ public class SkyDrop : Script, IScriptPreventMove, IScriptOnBeforeMove
return; return;
move.User.Volatile.Add(new ChargeSkyDropEffect(move.User)); move.User.Volatile.Add(new ChargeSkyDropEffect(move.User));
move.MoveChoice.SetAdditionalData("sky_drop_charge", true);
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("sky_drop_charge", new Dictionary<string, object> move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("sky_drop_charge", new Dictionary<string, object>
{ {
{ "user", move.User }, { "user", move.User },

View File

@ -33,4 +33,13 @@ public class ChargeBounceEffect : Script, IScriptForceTurnSelection, IScriptChan
if (!move.UseMove.HasFlag("effective_against_fly")) if (!move.UseMove.HasFlag("effective_against_fly"))
damage *= 2; damage *= 2;
} }
/// <inheritdoc />
public override void OnAfterMoveChoice(IMoveChoice moveChoice)
{
if (moveChoice.AdditionalData?.ContainsKey("bounce_charge") != true)
{
RemoveSelf();
}
}
} }

View File

@ -33,4 +33,13 @@ public class ChargeFlyEffect : Script, IScriptForceTurnSelection, IScriptChangeI
if (!move.UseMove.HasFlag("effective_against_fly")) if (!move.UseMove.HasFlag("effective_against_fly"))
damage *= 2; damage *= 2;
} }
/// <inheritdoc />
public override void OnAfterMoveChoice(IMoveChoice moveChoice)
{
if (moveChoice.AdditionalData?.ContainsKey("fly_charge") != true)
{
RemoveSelf();
}
}
} }

View File

@ -33,4 +33,13 @@ public class ChargeSkyDropEffect : Script, IScriptForceTurnSelection, IScriptCha
if (!move.UseMove.HasFlag("effective_against_fly")) if (!move.UseMove.HasFlag("effective_against_fly"))
damage *= 2; damage *= 2;
} }
/// <inheritdoc />
public override void OnAfterMoveChoice(IMoveChoice moveChoice)
{
if (moveChoice.AdditionalData?.ContainsKey("sky_drop_charge") != true)
{
RemoveSelf();
}
}
} }

View File

@ -32,4 +32,13 @@ public class DigEffect : Script, IScriptForceTurnSelection, IScriptChangeIncomin
if (!move.UseMove.HasFlag("effective_against_underground")) if (!move.UseMove.HasFlag("effective_against_underground"))
damage *= 2; damage *= 2;
} }
/// <inheritdoc />
public override void OnAfterMoveChoice(IMoveChoice moveChoice)
{
if (moveChoice.AdditionalData?.ContainsKey("dig_charge") != true)
{
RemoveSelf();
}
}
} }

View File

@ -32,4 +32,13 @@ public class DiveEffect : Script, IScriptForceTurnSelection, IScriptChangeIncomi
if (!move.UseMove.HasFlag("effective_against_underwater")) if (!move.UseMove.HasFlag("effective_against_underwater"))
damage *= 2; damage *= 2;
} }
/// <inheritdoc />
public override void OnAfterMoveChoice(IMoveChoice moveChoice)
{
if (moveChoice.AdditionalData?.ContainsKey("dive_charge") != true)
{
RemoveSelf();
}
}
} }

View File

@ -1,5 +1,6 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "helping_hand")]
public class HelpingHandEffect : Script, IScriptChangeBasePower, IScriptOnEndTurn public class HelpingHandEffect : Script, IScriptChangeBasePower, IScriptOnEndTurn
{ {
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,5 +1,6 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "lucky_chant")]
public class LuckyChantEffect : Script, IScriptBlockCriticalHit, IScriptOnEndTurn public class LuckyChantEffect : Script, IScriptBlockCriticalHit, IScriptOnEndTurn
{ {
private int _turnsLeft = 5; private int _turnsLeft = 5;

View File

@ -24,4 +24,13 @@ public class PhantomForceCharge : Script, IScriptForceTurnSelection, IScriptBloc
{ {
block = true; block = true;
} }
/// <inheritdoc />
public override void OnAfterMoveChoice(IMoveChoice moveChoice)
{
if (moveChoice.AdditionalData?.ContainsKey("phantom_force_charge") != true)
{
moveChoice.User.Volatile.Remove(ScriptUtils.ResolveName<PhantomForceCharge>());
}
}
} }

View File

@ -24,4 +24,13 @@ public class ShadowForceCharge : Script, IScriptForceTurnSelection, IScriptBlock
{ {
block = true; block = true;
} }
/// <inheritdoc />
public override void OnAfterMoveChoice(IMoveChoice moveChoice)
{
if (moveChoice.AdditionalData?.ContainsKey("shadow_force_charge") != true)
{
RemoveSelf();
}
}
} }

View File

@ -2,6 +2,7 @@ using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Side; namespace PkmnLib.Plugin.Gen7.Scripts.Side;
[Script(ScriptCategory.Side, "mat_block")]
public class MatBlockEffect : Script, IScriptOnEndTurn, IScriptBlockIncomingHit public class MatBlockEffect : Script, IScriptOnEndTurn, IScriptBlockIncomingHit
{ {
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,5 +1,6 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Side; namespace PkmnLib.Plugin.Gen7.Scripts.Side;
[Script(ScriptCategory.Side, "quick_guard")]
public class QuickGuardEffect : Script, IScriptIsInvulnerableToMove, IScriptOnEndTurn public class QuickGuardEffect : Script, IScriptIsInvulnerableToMove, IScriptOnEndTurn
{ {
/// <inheritdoc /> /// <inheritdoc />

View File

@ -2,6 +2,7 @@ using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Side; namespace PkmnLib.Plugin.Gen7.Scripts.Side;
[Script(ScriptCategory.Side, "safe_guard")]
public class SafeguardEffect : Script public class SafeguardEffect : Script
{ {
/// <inheritdoc /> /// <inheritdoc />

View File

@ -2,6 +2,7 @@ using PkmnLib.Dynamic.BattleFlow;
namespace PkmnLib.Plugin.Gen7.Scripts.Side; namespace PkmnLib.Plugin.Gen7.Scripts.Side;
[Script(ScriptCategory.Side, "spotlight")]
public class SpotlightEffect : Script, IScriptChangeIncomingTargets, IScriptOnEndTurn public class SpotlightEffect : Script, IScriptChangeIncomingTargets, IScriptOnEndTurn
{ {
private readonly byte _position; private readonly byte _position;

View File

@ -3,15 +3,11 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Utils;
public static class TurnChoiceHelper public static class TurnChoiceHelper
{ {
public static IMoveChoice CreateMoveChoice(IPokemon owner, StringKey moveName, byte targetSide, byte targetPosition) public static IMoveChoice CreateMoveChoice(IPokemon owner, StringKey moveName, byte targetSide, byte targetPosition)
{
var move = owner.Moves.FirstOrDefault(x => x?.MoveData.Name == moveName);
if (move == null)
{ {
if (!owner.Library.StaticLibrary.Moves.TryGet(moveName, out var moveData)) if (!owner.Library.StaticLibrary.Moves.TryGet(moveName, out var moveData))
throw new Exception($"Move '{moveName}' not found in move library."); throw new Exception($"Move '{moveName}' not found in move library.");
move = new LearnedMoveImpl(moveData, MoveLearnMethod.Unknown); var move = new LearnedMoveImpl(moveData, MoveLearnMethod.Unknown);
}
return new MoveChoice(owner, move, targetSide, targetPosition); return new MoveChoice(owner, move, targetSide, targetPosition);
} }
} }