Implement highest damage AI, further work on AI runner, random fixes
All checks were successful
Build / Build (push) Successful in 51s
All checks were successful
Build / Build (push) Successful in 51s
This commit is contained in:
parent
32aaa5150a
commit
c795f20e54
@ -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)
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
92
PkmnLib.Dynamic/AI/HighestDamageAI.cs
Normal file
92
PkmnLib.Dynamic/AI/HighestDamageAI.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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; }
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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());
|
||||||
|
@ -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>
|
||||||
{
|
{
|
||||||
|
@ -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>
|
||||||
{
|
{
|
||||||
|
@ -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 },
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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 />
|
||||||
|
@ -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;
|
||||||
|
@ -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>());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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 />
|
||||||
|
@ -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 />
|
||||||
|
@ -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 />
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user