From a3a49934075656db6de34efffd0110e83c1e5ca5 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Fri, 11 Jul 2025 17:03:08 +0200 Subject: [PATCH] Getting started with implementing an explicit AI, based on the Essentials one. --- AI/AIRunner/Program.cs | 12 +- AI/AIRunner/TestCommandRunner.cs | 5 +- PkmnLib.Dynamic/AI/AIHelpers.cs | 59 + PkmnLib.Dynamic/AI/AILogging.cs | 8 + PkmnLib.Dynamic/AI/Explicit/AIMoveState.cs | 16 + PkmnLib.Dynamic/AI/Explicit/ExplicitAI.cs | 346 +++++ .../AI/Explicit/ExplicitAIHandlers.cs | 199 +++ .../AI/Explicit/FunctionHandlerDictionary.cs | 117 ++ PkmnLib.Dynamic/AI/HighestDamageAI.cs | 54 +- PkmnLib.Dynamic/AI/PokemonAI.cs | 17 + PkmnLib.Dynamic/Libraries/DynamicLibrary.cs | 17 +- PkmnLib.Dynamic/Libraries/LibraryLoader.cs | 1 + PkmnLib.Dynamic/Models/Pokemon.cs | 8 + .../Registry => Plugins}/Plugin.cs | 4 +- .../Registry => Plugins}/PluginDataMutator.cs | 2 +- .../Registry => Plugins}/ResourceProvider.cs | 2 +- .../ScriptHandling/Registry/ScriptRegistry.cs | 2 + PkmnLib.Dynamic/ScriptHandling/Script.cs | 1152 ---------------- .../ScriptAIInformationInterfaces.cs | 29 + .../ScriptHandling/ScriptInterfaces.cs | 1159 +++++++++++++++++ PkmnLib.Static/Moves/MoveData.cs | 8 + PkmnLib.Static/StatisticSet.cs | 9 +- .../Dataloader/AbilityDataLoaderTests.cs | 1 + .../Dataloader/GrowthRateDataLoaderTests.cs | 1 + .../Dataloader/ItemDataLoaderTests.cs | 1 + .../Dataloader/MoveDataLoaderTests.cs | 1 + .../Dataloader/NatureDataloaderTests.cs | 1 + .../Dataloader/SpeciesDataloaderTests.cs | 1 + .../Dataloader/TypeDataloaderTests.cs | 1 + .../DataTests/MoveDataTests.cs | 1 + .../AI/AIHelperFunctions.cs | 337 +++++ .../AI/AIMoveScoreFunctionAttribute.cs | 8 + .../AI/ExplicitAIFunctions.cs | 70 + Plugins/PkmnLib.Plugin.Gen7/Data/Moves.jsonc | 60 +- Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs | 4 + .../Battling/Gen7BattleStatCalculator.cs | 28 +- .../Scripts/Abilities/DrySkin.cs | 11 +- .../Scripts/Abilities/SolarPower.cs | 23 +- .../Scripts/Moves/ChangeUserStats.cs | 44 + .../Scripts/Moves/MirrorMove.cs | 10 +- .../Scripts/Pokemon/BindEffect.cs | 9 +- .../Scripts/Pokemon/FireSpinEffect.cs | 9 +- .../Scripts/Pokemon/GhostCurseEffect.cs | 8 +- .../Scripts/Pokemon/InfestationEffect.cs | 9 +- .../Scripts/Pokemon/LeechSeedEffect.cs | 10 +- .../Scripts/Pokemon/MagmaStormEffect.cs | 9 +- .../Scripts/Pokemon/NightmareEffect.cs | 8 +- .../Scripts/Pokemon/SubstituteEffect.cs | 6 +- .../Scripts/Pokemon/WhirlpoolEffect.cs | 13 +- .../Scripts/Status/BadlyPoisoned.cs | 2 +- .../Scripts/Status/Burned.cs | 9 +- .../Scripts/Status/Poisoned.cs | 9 +- .../Scripts/Status/Sleep.cs | 5 +- .../Scripts/Terrain/PsychicTerrain.cs | 2 +- .../Scripts/Weather/Hail.cs | 13 +- .../Scripts/Weather/Sandstorm.cs | 11 +- 56 files changed, 2687 insertions(+), 1274 deletions(-) create mode 100644 PkmnLib.Dynamic/AI/AIHelpers.cs create mode 100644 PkmnLib.Dynamic/AI/AILogging.cs create mode 100644 PkmnLib.Dynamic/AI/Explicit/AIMoveState.cs create mode 100644 PkmnLib.Dynamic/AI/Explicit/ExplicitAI.cs create mode 100644 PkmnLib.Dynamic/AI/Explicit/ExplicitAIHandlers.cs create mode 100644 PkmnLib.Dynamic/AI/Explicit/FunctionHandlerDictionary.cs rename PkmnLib.Dynamic/{ScriptHandling/Registry => Plugins}/Plugin.cs (96%) rename PkmnLib.Dynamic/{ScriptHandling/Registry => Plugins}/PluginDataMutator.cs (95%) rename PkmnLib.Dynamic/{ScriptHandling/Registry => Plugins}/ResourceProvider.cs (98%) create mode 100644 PkmnLib.Dynamic/ScriptHandling/ScriptAIInformationInterfaces.cs create mode 100644 PkmnLib.Dynamic/ScriptHandling/ScriptInterfaces.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/AI/AIHelperFunctions.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/AI/AIMoveScoreFunctionAttribute.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/AI/ExplicitAIFunctions.cs diff --git a/AI/AIRunner/Program.cs b/AI/AIRunner/Program.cs index ee9035a..5a7ded5 100644 --- a/AI/AIRunner/Program.cs +++ b/AI/AIRunner/Program.cs @@ -2,6 +2,8 @@ using System.CommandLine.Parsing; using System.Reflection; using PkmnLib.Dynamic.AI; +using PkmnLib.Dynamic.Libraries; +using PkmnLib.Plugin.Gen7; using Serilog; namespace AIRunner; @@ -14,9 +16,11 @@ internal static class Program { Log.Logger = new LoggerConfiguration().MinimumLevel.Information().WriteTo.Console().CreateLogger(); Log.Information("Starting AI Runner..."); - _availableAIs = AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()) - .Where(type => type.IsSubclassOf(typeof(PokemonAI)) && !type.IsAbstract).Select(Activator.CreateInstance) - .Cast().ToList(); + AILogging.LogHandler = Log.Debug; + var library = DynamicLibraryImpl.Create([ + new Gen7Plugin(), + ]); + _availableAIs = PokemonAI.InstantiateAis(library); var testCommand = new Command("test", "Run two AIs against each other") { @@ -58,7 +62,7 @@ internal static class Program var ai2 = _availableAIs!.First(a => string.Equals(a.Name, ai2Name, StringComparison.InvariantCultureIgnoreCase)); - return TestCommandRunner.RunTestCommand(ai1, ai2, result.GetRequiredValue("--battles")); + return TestCommandRunner.RunTestCommand(library, ai1, ai2, result.GetRequiredValue("--battles")); }); var rootCommand = new RootCommand("PkmnLib.NET AI Runner") diff --git a/AI/AIRunner/TestCommandRunner.cs b/AI/AIRunner/TestCommandRunner.cs index 9d2d3d2..c66a01f 100644 --- a/AI/AIRunner/TestCommandRunner.cs +++ b/AI/AIRunner/TestCommandRunner.cs @@ -13,12 +13,9 @@ namespace AIRunner; public static class TestCommandRunner { - internal static async Task RunTestCommand(PokemonAI ai1, PokemonAI ai2, int battles) + internal static async Task RunTestCommand(IDynamicLibrary library, PokemonAI ai1, PokemonAI ai2, int battles) { var t1 = DateTime.UtcNow; - var library = DynamicLibraryImpl.Create([ - new Gen7Plugin(), - ]); const int maxTasks = 10; Log.Information("Running {Battles} battles between {AI1} and {AI2}", battles, ai1.Name, ai2.Name); diff --git a/PkmnLib.Dynamic/AI/AIHelpers.cs b/PkmnLib.Dynamic/AI/AIHelpers.cs new file mode 100644 index 0000000..304c70a --- /dev/null +++ b/PkmnLib.Dynamic/AI/AIHelpers.cs @@ -0,0 +1,59 @@ +using PkmnLib.Dynamic.Libraries; +using PkmnLib.Dynamic.Models; +using PkmnLib.Static; +using PkmnLib.Static.Moves; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Dynamic.AI; + +public static class AIHelpers +{ + public static uint CalculateDamageEstimation(IMoveData move, IPokemon user, IPokemon target, + IDynamicLibrary library) + { + var hitData = new CustomHitData + { + BasePower = move.BasePower, + Effectiveness = library.StaticLibrary.Types.GetEffectiveness(move.MoveType, target.Types), + Type = move.MoveType, + }; + return library.DamageCalculator.GetDamage(null, move.Category, user, target, 1, 0, hitData); + } + + private class CustomHitData : IHitData + { + /// + public bool IsCritical => false; + + /// + public ushort BasePower { get; init; } + + /// + public float Effectiveness { get; init; } + + /// + public uint Damage => 0; + + /// + public TypeIdentifier? Type { get; init; } + + /// + public bool IsContact => false; + + /// + public bool HasFailed => false; + + /// + public void Fail() + { + } + + /// + public void SetFlag(StringKey flag) + { + } + + /// + public bool HasFlag(StringKey flag) => false; + } +} \ No newline at end of file diff --git a/PkmnLib.Dynamic/AI/AILogging.cs b/PkmnLib.Dynamic/AI/AILogging.cs new file mode 100644 index 0000000..78ff71b --- /dev/null +++ b/PkmnLib.Dynamic/AI/AILogging.cs @@ -0,0 +1,8 @@ +namespace PkmnLib.Dynamic.AI; + +public static class AILogging +{ + public static Action LogHandler { get; set; } = message => { }; + + public static void LogInformation(string message) => LogHandler(message); +} \ No newline at end of file diff --git a/PkmnLib.Dynamic/AI/Explicit/AIMoveState.cs b/PkmnLib.Dynamic/AI/Explicit/AIMoveState.cs new file mode 100644 index 0000000..a7c3914 --- /dev/null +++ b/PkmnLib.Dynamic/AI/Explicit/AIMoveState.cs @@ -0,0 +1,16 @@ +using PkmnLib.Dynamic.Models; +using PkmnLib.Static.Moves; + +namespace PkmnLib.Dynamic.AI.Explicit; + +public class AIMoveState +{ + public AIMoveState(IPokemon user, IMoveData move) + { + User = user; + Move = move; + } + + public IPokemon User { get; } + public IMoveData Move { get; } +} \ No newline at end of file diff --git a/PkmnLib.Dynamic/AI/Explicit/ExplicitAI.cs b/PkmnLib.Dynamic/AI/Explicit/ExplicitAI.cs new file mode 100644 index 0000000..65fcf2e --- /dev/null +++ b/PkmnLib.Dynamic/AI/Explicit/ExplicitAI.cs @@ -0,0 +1,346 @@ +using PkmnLib.Dynamic.BattleFlow; +using PkmnLib.Dynamic.Libraries; +using PkmnLib.Dynamic.Models; +using PkmnLib.Dynamic.Models.Choices; +using PkmnLib.Dynamic.ScriptHandling; +using PkmnLib.Static.Moves; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Dynamic.AI.Explicit; + +/// +/// An explicit AI that has explicitly written logic for each Pokémon and move. +/// +/// +/// This is heavily based on the AI used in Pokémon Essentials +/// +public class ExplicitAI : PokemonAI +{ + 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 float MoveScoreThreshold => (float)(0.6f + 0.35f * Math.Sqrt(Math.Min(TrainerSkill, 100) / 100f)); + + private readonly IReadOnlyExplicitAIHandlers _handlers; + + private readonly IRandom _random = new RandomImpl(); + + /// + public ExplicitAI(IDynamicLibrary library) : base("explicit") + { + _handlers = library.ExplicitAIHandlers; + } + + /// + public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon) + { + if (battle.HasForcedTurn(pokemon, out var choice)) + return choice; + 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 maxScore = moveChoices.Max(x => x.score); + // TODO: Consider switching to a different pokémon if the score is too low + + var threshold = (float)Math.Floor(maxScore * MoveScoreThreshold); + var considerChoices = moveChoices.Select(x => (x, Math.Max(x.score - threshold, 0))).ToArray(); + 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 initialRandomValue = _random.GetFloat(0, totalScore); + var randomValue = initialRandomValue; + for (var i = 0; i < considerChoices.Length; i++) + { + randomValue -= considerChoices[i].Item2; + if (randomValue >= 0) + continue; + + var (index, _, targetIndex) = considerChoices[i].x; + var learnedMove = pokemon.Moves[index]; + var opponentSide = (byte)(pokemon.BattleData!.SideIndex == 0 ? 1 : 0); + if (targetIndex == -1) + targetIndex = pokemon.BattleData.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) + { + 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)) + { + var moveChoice = new MoveChoice(user, learnedMove!, 0, 0); + if (!battle.CanUse(moveChoice)) + { + if (learnedMove!.CurrentPp == 0 && learnedMove.MaxPp > 0) + { + AILogging.LogInformation($"{user} cannot use {learnedMove} because it has no PP left."); + } + else + { + AILogging.LogInformation($"{user} cannot use {learnedMove} because it is not a valid choice."); + } + continue; + } + var moveData = learnedMove!.MoveData; + var moveName = moveData.Name; + moveChoice.RunScriptHook(x => x.ChangeMove(moveChoice, ref moveName)); + if (moveName != learnedMove.MoveData.Name) + { + AILogging.LogInformation($"{user} changed {learnedMove} to {moveName}."); + if (!battle.Library.StaticLibrary.Moves.TryGet(moveName, out moveData)) + throw new InvalidOperationException($"Move {moveName} not found in the move library."); + var secondaryEffect = moveData.SecondaryEffect; + if (secondaryEffect != null) + { + if (moveChoice.User.Library.ScriptResolver.TryResolve(ScriptCategory.Move, secondaryEffect.Name, + secondaryEffect.Parameters, out var script)) + { + moveChoice.Script.Set(script); + script.OnAddedToParent(moveChoice); + } + else + { + moveChoice.Script.Clear(); + } + } + else + { + moveChoice.Script.Clear(); + } + } + var aiMove = new AIMoveState(user, moveData); + if (CanPredictMoveFailure && PredictMoveFailure(user, battle, aiMove)) + { + AILogging.LogInformation($"{user} is considering {aiMove.Move.Name} but it will fail."); + AddMoveToChoices(index, MoveFailScore); + } + + var target = moveData.Target; + if (target is MoveTarget.All or MoveTarget.AllAlly or MoveTarget.AllOpponent or MoveTarget.SelfUse) + { + var score = GetMoveScore(user, aiMove, battle); + AddMoveToChoices(index, score); + } + else if (target is MoveTarget.AdjacentOpponent or MoveTarget.Any or MoveTarget.Adjacent + or MoveTarget.AdjacentAllySelf) + { + // TODO: get redirected target + foreach (var pokemon in battle.Sides.SelectMany(x => x.Pokemon).WhereNotNull()) + { + var battleData = pokemon.BattleData; + if (battleData == null) + continue; + if (!TargetResolver.IsValidTarget(battleData.SideIndex, battleData.Position, target, user)) + continue; + if (target.TargetsFoe() && battleData.SideIndex == user.BattleData?.SideIndex) + { + continue; + } + var score = GetMoveScoreAgainstTarget(user, aiMove, pokemon, battle); + AddMoveToChoices(index, score, battleData.Position); + } + } + else + { + var targets = new List(); + foreach (var pokemon in battle.Sides.SelectMany(x => x.Pokemon).WhereNotNull()) + { + var battleData = pokemon.BattleData; + if (battleData == null) + continue; + if (!TargetResolver.IsValidTarget(battleData.SideIndex, battleData.Position, target, user)) + continue; + if (target.TargetsFoe() && battleData.SideIndex == user.BattleData?.SideIndex) + { + continue; + } + targets.Add(pokemon); + } + var score = GetMoveScore(user, aiMove, battle, targets); + AddMoveToChoices(index, score); + } + } + return choices; + + void AddMoveToChoices(int index, int score, int targetIndex = -1) + { + choices.Add((index, score, targetIndex)); + // If the user is a wild Pokémon, doubly prefer a random move. + if (battle.IsWildBattle && user.PersonalityValue % user.Moves.Count == index) + { + choices.Add((index, score, targetIndex)); + } + } + } + + private bool PredictMoveFailure(IPokemon user, IBattle battle, AIMoveState aiMove) + { + if (user.HasStatus("sleep")) + { + // User is asleep, and will not wake up, and the move is not usable while asleep. + if (user.GetStatusTurnsLeft != 0 && !aiMove.Move.HasFlag("usable_while_asleep")) + { + AILogging.LogInformation($"{user} is asleep and cannot use {aiMove.Move.Name}."); + return true; + } + } + // The move is only usable while asleep, but the user is not asleep + else if (aiMove.Move.HasFlag("usable_while_asleep")) + { + return true; + } + + // Primal weather + if (battle.WeatherName == "primordial_sea" && aiMove.Move.MoveType.Name == "fire") + return true; + if (battle.WeatherName == "desolate_lands" && aiMove.Move.MoveType.Name == "water") + 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)); + } + + private static readonly StringKey PsychicTerrainName = new("psychic_terrain"); + private static readonly StringKey DazzlingName = new("dazzling"); + private static readonly StringKey QueenlyMajestyName = new("queenly_majesty"); + private static readonly StringKey PranksterName = new("prankster"); + private static readonly StringKey DarkName = new("dark"); + private static readonly StringKey GroundName = new("ground"); + private static readonly StringKey PowderName = new("powder"); + 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) + { + if (aiMove.Move.SecondaryEffect != null && _handlers.MoveWillFailAgainstTarget(aiMove.Move.SecondaryEffect.Name, + new MoveOption(aiMove, battle, target))) + return true; + if (aiMove.Move.Priority > 0) + { + if (target.BattleData?.SideIndex != user.BattleData?.SideIndex) + { + // Psychic Terrain makes all priority moves fail if the target is affected + if (battle.TerrainName == PsychicTerrainName && !target.IsFloating) + { + 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 => + x.ActiveAbility?.Name == DazzlingName || x.ActiveAbility?.Name == QueenlyMajestyName) == true) + { + return true; + } + } + } + // TODO: Check immunity because of ability + + var moveType = aiMove.Move.MoveType; + var typeEffectiveness = battle.Library.StaticLibrary.Types.GetEffectiveness(moveType, target.Types); + 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) + return true; + if (aiMove.Move.Category != MoveCategory.Status && moveType.Name == GroundName && target.IsFloating) + return true; + if (aiMove.Move.HasFlag(PowderName) && !AffectedByPowder(target)) + return true; + if (target.Volatile.Contains(SubstituteName) && aiMove.Move.Category == MoveCategory.Status && + user.ActiveAbility?.Name != InfiltratorName) + return true; + + return false; + } + + private int GetMoveScore(IPokemon user, AIMoveState aiMove, IBattle battle, IReadOnlyList? targets = null) + { + var score = MoveBaseScore; + if (targets != null) + { + score = 0; + var affectedTargets = 0; + foreach (var target in targets) + { + var targetScore = GetMoveScoreAgainstTarget(user, aiMove, target, battle); + if (targetScore < 0) + continue; + score += targetScore; + affectedTargets++; + } + if (affectedTargets == 0 && CanPredictMoveFailure) + { + return MoveFailScore; + } + if (affectedTargets > 0) + score = (int)(score / (float)affectedTargets); + } + if (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); + } + } + if (score < 0) + score = 0; + return score; + } + + private int GetMoveScoreAgainstTarget(IPokemon user, AIMoveState aiMove, IPokemon target, IBattle battle) + { + if (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) + { + 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); + } + + if (aiMove.Move.Target.TargetsFoe() && target.BattleData?.SideIndex == user.BattleData?.SideIndex && + target.BattleData?.Position != user.BattleData?.Position) + { + if (score == MoveUselessScore) + return -1; + score = (int)(1.85f * MoveBaseScore - score); + } + + return score; + } + + private static readonly StringKey GrassName = new("grass"); + private static readonly StringKey OvercoatName = new("overcoat"); + private static readonly StringKey SafetyGogglesName = new("safety_goggles"); + + private static bool AffectedByPowder(IPokemon pokemon) + { + if (pokemon.Types.Any(x => x.Name == GrassName)) + return false; + if (pokemon.ActiveAbility?.Name == OvercoatName) + return false; + if (pokemon.HasHeldItem(SafetyGogglesName)) + return false; + return true; + } +} \ No newline at end of file diff --git a/PkmnLib.Dynamic/AI/Explicit/ExplicitAIHandlers.cs b/PkmnLib.Dynamic/AI/Explicit/ExplicitAIHandlers.cs new file mode 100644 index 0000000..1e134f2 --- /dev/null +++ b/PkmnLib.Dynamic/AI/Explicit/ExplicitAIHandlers.cs @@ -0,0 +1,199 @@ +using PkmnLib.Dynamic.Models; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Dynamic.AI.Explicit; + +public record struct MoveOption(AIMoveState Move, IBattle Battle, IPokemon? Target, uint EstimatedDamage = 0); + +public delegate bool AIBoolHandler(MoveOption option); + +public delegate void AIMoveBasePowerHandler(MoveOption option, ref int score); + +public delegate void AIScoreMoveHandler(MoveOption option, ref int score); + +public interface IReadOnlyExplicitAIHandlers +{ + /// + /// A list of checks to determine if a move will fail. + /// + IReadOnlyDictionary MoveFailureCheck { get; } + + /// + /// Checks if a move will fail based on the provided function code and options. + /// + bool MoveWillFail(StringKey functionCode, MoveOption option); + + /// + /// A list of checks to determine if a move will fail against a target. + /// + IReadOnlyDictionary MoveFailureAgainstTargetCheck { get; } + + /// + /// Checks if a move will fail against a target based on the provided function code and options. + /// + bool MoveWillFailAgainstTarget(StringKey functionCode, MoveOption option); + + /// + /// A list of handlers to apply scores for move effects. + /// + IReadOnlyDictionary MoveEffectScore { get; } + + /// + /// Applies the score for a move effect based on the provided name and options. + /// + void ApplyMoveEffectScore(StringKey name, MoveOption option, ref int score); + + /// + /// A list of handlers to apply scores for move effects against a target. + /// + IReadOnlyDictionary MoveEffectAgainstTargetScore { get; } + + /// + /// Applies the score for a move effect against a target based on the provided name and options. + /// + void ApplyMoveEffectAgainstTargetScore(StringKey name, MoveOption option, ref int score); + + /// + /// A list of handlers to determine the base power of a move. + /// + IReadOnlyDictionary MoveBasePower { get; } + + /// + /// Applies the base power for a move based on the provided name and options. + /// + void GetBasePower(StringKey name, MoveOption option, ref int power); + + /// + /// A list of handlers to apply scores for general move effectiveness. + /// + IReadOnlyDictionary GeneralMoveScore { get; } + + /// + /// Applies the score for a general move based on the provided option. + /// + /// + /// + /// + void ApplyGenerateMoveScoreModifiers(MoveOption option, ref int score); + + /// + /// A list of handlers to apply scores for general move effectiveness against a target. + /// + IReadOnlyDictionary GeneralMoveAgainstTargetScore { get; } + + /// + /// Applies the score for a general move against a target based on the provided option. + /// + void ApplyGenerateMoveAgainstTargetScoreModifiers(MoveOption option, ref int score); + + IReadOnlyDictionary ShouldSwitch { get; } + IReadOnlyDictionary ShouldNotSwitch { get; } + IReadOnlyDictionary AbilityRanking { get; } +} + +public class ExplicitAIHandlers : IReadOnlyExplicitAIHandlers +{ + /// + IReadOnlyDictionary IReadOnlyExplicitAIHandlers.MoveFailureCheck => MoveFailureCheck; + + public FunctionHandlerDictionary MoveFailureCheck { get; } = new(); + + /// + public bool MoveWillFail(StringKey functionCode, MoveOption option) => + MoveFailureCheck.TryGetValue(functionCode, out var handler) && handler(option); + + public FunctionHandlerDictionary MoveFailureAgainstTargetCheck { get; } = new(); + + /// + IReadOnlyDictionary IReadOnlyExplicitAIHandlers.MoveFailureAgainstTargetCheck => + MoveFailureAgainstTargetCheck; + + /// + public bool MoveWillFailAgainstTarget(StringKey functionCode, MoveOption option) => + MoveFailureAgainstTargetCheck.TryGetValue(functionCode, out var handler) && handler(option); + + public FunctionHandlerDictionary MoveEffectScore { get; } = []; + + /// + IReadOnlyDictionary IReadOnlyExplicitAIHandlers.MoveEffectScore => + MoveEffectScore; + + public FunctionHandlerDictionary MoveEffectAgainstTargetScore = []; + + /// + public void ApplyMoveEffectScore(StringKey name, MoveOption option, ref int score) + { + if (MoveEffectScore.TryGetValue(name, out var handler)) + handler(option, ref score); + } + + /// + IReadOnlyDictionary IReadOnlyExplicitAIHandlers.MoveEffectAgainstTargetScore => + MoveEffectAgainstTargetScore; + + public FunctionHandlerDictionary MoveBasePower = []; + + /// + public void ApplyMoveEffectAgainstTargetScore(StringKey name, MoveOption option, ref int score) + { + if (MoveEffectAgainstTargetScore.TryGetValue(name, out var handler)) + handler(option, ref score); + } + + /// + IReadOnlyDictionary IReadOnlyExplicitAIHandlers.MoveBasePower => + MoveBasePower; + + public FunctionHandlerDictionary GeneralMoveScore = []; + + /// + public void GetBasePower(StringKey name, MoveOption option, ref int power) + { + if (MoveBasePower.TryGetValue(name, out var handler)) + handler(option, ref power); + } + + /// + IReadOnlyDictionary IReadOnlyExplicitAIHandlers.GeneralMoveScore => + GeneralMoveScore; + + public FunctionHandlerDictionary GeneralMoveAgainstTargetScore = []; + + /// + public void ApplyGenerateMoveScoreModifiers(MoveOption option, ref int score) + { + foreach (var (_, handler) in GeneralMoveScore) + { + handler(option, ref score); + } + } + + /// + IReadOnlyDictionary IReadOnlyExplicitAIHandlers.GeneralMoveAgainstTargetScore => + GeneralMoveAgainstTargetScore; + + public FunctionHandlerDictionary ShouldSwitch = []; + + /// + public void ApplyGenerateMoveAgainstTargetScoreModifiers(MoveOption option, ref int score) + { + foreach (var (_, handler) in GeneralMoveAgainstTargetScore) + { + handler(option, ref score); + } + } + + /// + IReadOnlyDictionary IReadOnlyExplicitAIHandlers.ShouldSwitch => ShouldSwitch; + + public FunctionHandlerDictionary ShouldNotSwitch = []; + + /// + IReadOnlyDictionary IReadOnlyExplicitAIHandlers.ShouldNotSwitch => ShouldNotSwitch; + + public FunctionHandlerDictionary AbilityRanking = []; + + /// + IReadOnlyDictionary IReadOnlyExplicitAIHandlers.AbilityRanking => + AbilityRanking; +} \ No newline at end of file diff --git a/PkmnLib.Dynamic/AI/Explicit/FunctionHandlerDictionary.cs b/PkmnLib.Dynamic/AI/Explicit/FunctionHandlerDictionary.cs new file mode 100644 index 0000000..a7b4f4b --- /dev/null +++ b/PkmnLib.Dynamic/AI/Explicit/FunctionHandlerDictionary.cs @@ -0,0 +1,117 @@ +using System.Collections; +using System.Collections.Specialized; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Dynamic.AI.Explicit; + +public class FunctionHandlerDictionary : IDictionary, IReadOnlyDictionary + where TValue : class +{ + private readonly OrderedDictionary _backingDictionary = new(); + + /// + public IEnumerator> GetEnumerator() => _backingDictionary.Cast() + .Select(entry => new KeyValuePair((StringKey)entry.Key, (TValue)entry.Value)) + .GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public void Add(KeyValuePair item) => _backingDictionary.Add(item.Key, item.Value); + + /// + public void Clear() => _backingDictionary.Clear(); + + /// + public bool Contains(KeyValuePair item) + { + if (_backingDictionary.Contains(item.Key)) + { + var value = _backingDictionary[item.Key]; + return EqualityComparer.Default.Equals((TValue)value, item.Value); + } + return false; + } + + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (arrayIndex < 0 || arrayIndex + Count > array.Length) + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + + foreach (var item in this) + { + array[arrayIndex++] = item; + } + } + + /// + public bool Remove(KeyValuePair item) + { + if (_backingDictionary.Contains(item.Key)) + { + var value = _backingDictionary[item.Key]; + if (EqualityComparer.Default.Equals((TValue)value, item.Value)) + { + _backingDictionary.Remove(item.Key); + return true; + } + } + return false; + } + + /// + public int Count => _backingDictionary.Count; + + /// + public bool IsReadOnly => false; + + /// + public void Add(StringKey key, TValue value) => _backingDictionary.Add(key, value); + + /// + public bool ContainsKey(StringKey key) => _backingDictionary.Contains(key); + + /// + public bool Remove(StringKey key) + { + if (!_backingDictionary.Contains(key)) + return false; + _backingDictionary.Remove(key); + return true; + } + + /// + public bool TryGetValue(StringKey key, out TValue value) + { + if (_backingDictionary.Contains(key)) + { + value = (TValue)_backingDictionary[key]; + return true; + } + value = null!; + return false; + } + + /// + public TValue this[StringKey key] + { + get => (TValue)_backingDictionary[key]; + set => _backingDictionary[key] = value; + } + + /// + IEnumerable IReadOnlyDictionary.Keys => Keys; + + /// + IEnumerable IReadOnlyDictionary.Values => Values; + + /// + public ICollection Keys => _backingDictionary.Keys.Cast().ToList(); + + /// + public ICollection Values => _backingDictionary.Values.Cast().ToList(); +} \ No newline at end of file diff --git a/PkmnLib.Dynamic/AI/HighestDamageAI.cs b/PkmnLib.Dynamic/AI/HighestDamageAI.cs index e0dba8c..793dd06 100644 --- a/PkmnLib.Dynamic/AI/HighestDamageAI.cs +++ b/PkmnLib.Dynamic/AI/HighestDamageAI.cs @@ -30,21 +30,10 @@ public class HighestDamageAI : PokemonAI : battle.Library.MiscLibrary.ReplacementChoice(pokemon, opponentSide, 0); } - var movesWithDamage = moves.Select(move => + var movesWithDamage = moves.Select(move => new { - 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), - }; + Move = move, + Damage = AIHelpers.CalculateDamageEstimation(move.MoveData, pokemon, opponent, battle.Library), }).OrderByDescending(x => x.Damage).FirstOrDefault(); if (movesWithDamage is null) { @@ -53,41 +42,4 @@ public class HighestDamageAI : PokemonAI var bestMove = movesWithDamage.Move; return new MoveChoice(pokemon, bestMove, opponentSide, 0); } - - private class CustomHitData : IHitData - { - /// - public bool IsCritical => false; - - /// - public ushort BasePower { get; init; } - - /// - public float Effectiveness { get; init; } - - /// - public uint Damage => 0; - - /// - public TypeIdentifier? Type { get; init; } - - /// - public bool IsContact => false; - - /// - public bool HasFailed => false; - - /// - public void Fail() - { - } - - /// - public void SetFlag(StringKey flag) - { - } - - /// - public bool HasFlag(StringKey flag) => false; - } } \ No newline at end of file diff --git a/PkmnLib.Dynamic/AI/PokemonAI.cs b/PkmnLib.Dynamic/AI/PokemonAI.cs index 7404cda..73e6cbb 100644 --- a/PkmnLib.Dynamic/AI/PokemonAI.cs +++ b/PkmnLib.Dynamic/AI/PokemonAI.cs @@ -1,3 +1,4 @@ +using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Static.Moves; @@ -96,4 +97,20 @@ public abstract class PokemonAI 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(); + } } \ No newline at end of file diff --git a/PkmnLib.Dynamic/Libraries/DynamicLibrary.cs b/PkmnLib.Dynamic/Libraries/DynamicLibrary.cs index 85ec479..9fb0cc7 100644 --- a/PkmnLib.Dynamic/Libraries/DynamicLibrary.cs +++ b/PkmnLib.Dynamic/Libraries/DynamicLibrary.cs @@ -1,3 +1,5 @@ +using PkmnLib.Dynamic.AI.Explicit; +using PkmnLib.Dynamic.Plugins; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Static.Libraries; @@ -41,6 +43,12 @@ public interface IDynamicLibrary /// A holder of the script types that can be resolved by this library. /// ScriptResolver ScriptResolver { get; } + + /// + /// The deterministic AI handlers provide a way to access the various handlers used by the + /// to make decisions. + /// + IReadOnlyExplicitAIHandlers ExplicitAIHandlers { get; } } /// @@ -55,18 +63,20 @@ public class DynamicLibraryImpl : IDynamicLibrary var load = LibraryLoader.LoadPlugins(plugins); return new DynamicLibraryImpl(load.StaticLibrary, load.Registry.BattleStatCalculator!, - load.Registry.DamageCalculator!, load.Registry.MiscLibrary!, load.Registry.CaptureLibrary!, load.Resolver); + load.Registry.DamageCalculator!, load.Registry.MiscLibrary!, load.Registry.CaptureLibrary!, load.Resolver, + load.Registry.ExplicitAIHandlers); } private DynamicLibraryImpl(IStaticLibrary staticLibrary, IBattleStatCalculator statCalculator, IDamageCalculator damageCalculator, IMiscLibrary miscLibrary, ICaptureLibrary captureLibrary, - ScriptResolver scriptResolver) + ScriptResolver scriptResolver, IReadOnlyExplicitAIHandlers explicitAIHandlers) { StaticLibrary = staticLibrary; StatCalculator = statCalculator; DamageCalculator = damageCalculator; MiscLibrary = miscLibrary; ScriptResolver = scriptResolver; + ExplicitAIHandlers = explicitAIHandlers; CaptureLibrary = captureLibrary; } @@ -87,4 +97,7 @@ public class DynamicLibraryImpl : IDynamicLibrary /// public ScriptResolver ScriptResolver { get; } + + /// + public IReadOnlyExplicitAIHandlers ExplicitAIHandlers { get; } } \ No newline at end of file diff --git a/PkmnLib.Dynamic/Libraries/LibraryLoader.cs b/PkmnLib.Dynamic/Libraries/LibraryLoader.cs index 98ce651..26ef872 100644 --- a/PkmnLib.Dynamic/Libraries/LibraryLoader.cs +++ b/PkmnLib.Dynamic/Libraries/LibraryLoader.cs @@ -1,4 +1,5 @@ using PkmnLib.Dynamic.Libraries.DataLoaders; +using PkmnLib.Dynamic.Plugins; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Static.Libraries; diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs index 15005dd..fbebf54 100644 --- a/PkmnLib.Dynamic/Models/Pokemon.cs +++ b/PkmnLib.Dynamic/Models/Pokemon.cs @@ -220,6 +220,11 @@ public interface IPokemon : IScriptSource, IDeepCloneable /// ScriptContainer StatusScript { get; } + /// + /// The number of turns left for the current non-volatile status. + /// + int? GetStatusTurnsLeft { get; } + /// /// The volatile status scripts of the Pokemon. /// @@ -782,6 +787,9 @@ public class PokemonImpl : ScriptSource, IPokemon /// public ScriptContainer StatusScript { get; } = new(); + /// + public int? GetStatusTurnsLeft => (StatusScript.Script as IAIInfoScriptNumberTurnsLeft)?.TurnsLeft(); + /// public IScriptSet Volatile { get; } diff --git a/PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs b/PkmnLib.Dynamic/Plugins/Plugin.cs similarity index 96% rename from PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs rename to PkmnLib.Dynamic/Plugins/Plugin.cs index 2a9c42d..74086c9 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs +++ b/PkmnLib.Dynamic/Plugins/Plugin.cs @@ -1,4 +1,6 @@ -namespace PkmnLib.Dynamic.ScriptHandling.Registry; +using PkmnLib.Dynamic.ScriptHandling.Registry; + +namespace PkmnLib.Dynamic.Plugins; /// /// A plugin is a way to register scripts and other components to the script registry. diff --git a/PkmnLib.Dynamic/ScriptHandling/Registry/PluginDataMutator.cs b/PkmnLib.Dynamic/Plugins/PluginDataMutator.cs similarity index 95% rename from PkmnLib.Dynamic/ScriptHandling/Registry/PluginDataMutator.cs rename to PkmnLib.Dynamic/Plugins/PluginDataMutator.cs index 4f62e9f..22092b2 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Registry/PluginDataMutator.cs +++ b/PkmnLib.Dynamic/Plugins/PluginDataMutator.cs @@ -1,7 +1,7 @@ using PkmnLib.Dynamic.Libraries.DataLoaders.Models; using PkmnLib.Static; -namespace PkmnLib.Dynamic.ScriptHandling.Registry; +namespace PkmnLib.Dynamic.Plugins; /// /// Interface for plugins that can mutate data. diff --git a/PkmnLib.Dynamic/ScriptHandling/Registry/ResourceProvider.cs b/PkmnLib.Dynamic/Plugins/ResourceProvider.cs similarity index 98% rename from PkmnLib.Dynamic/ScriptHandling/Registry/ResourceProvider.cs rename to PkmnLib.Dynamic/Plugins/ResourceProvider.cs index e7958c1..5dd6994 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Registry/ResourceProvider.cs +++ b/PkmnLib.Dynamic/Plugins/ResourceProvider.cs @@ -1,7 +1,7 @@ using System.Reflection; using PkmnLib.Static.Libraries; -namespace PkmnLib.Dynamic.ScriptHandling.Registry; +namespace PkmnLib.Dynamic.Plugins; /// /// Interface for plugins that provide resources. diff --git a/PkmnLib.Dynamic/ScriptHandling/Registry/ScriptRegistry.cs b/PkmnLib.Dynamic/ScriptHandling/Registry/ScriptRegistry.cs index 0b7d9bf..756ac62 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Registry/ScriptRegistry.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Registry/ScriptRegistry.cs @@ -1,5 +1,6 @@ using System.Linq.Expressions; using System.Reflection; +using PkmnLib.Dynamic.AI.Explicit; using PkmnLib.Dynamic.Libraries; using PkmnLib.Static; using PkmnLib.Static.Utils; @@ -115,4 +116,5 @@ public class ScriptRegistry internal IDamageCalculator? DamageCalculator => _damageCalculator; internal IMiscLibrary? MiscLibrary => _miscLibrary; internal ICaptureLibrary? CaptureLibrary => _captureLibrary; + public ExplicitAIHandlers ExplicitAIHandlers { get; } = new(); } \ No newline at end of file diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs index 2efd10f..aa65740 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Script.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs @@ -52,1156 +52,4 @@ public abstract class Script : IDeepCloneable, IScriptOnRemove public virtual void OnAddedToParent(IScriptSource source) { } -} - -/// -/// This interface is used to allow scripts to run when they are removed from their owner. -/// -public interface IScriptOnRemove -{ - /// - /// This function is ran when this script stops being in effect, and is removed from its owner. - /// - void OnRemove(); -} - -/// -/// This interface is used to allow scripts to run before any hook is invoked. This allows for -/// suppressing certain categories of scripts, which can be useful for preventing certain effects -/// from running. -/// -public interface IScriptOnBeforeAnyHookInvoked -{ - /// - /// This function is ran before any hook is invoked. This allows for suppressing certain categories - /// of scripts. This is useful for example to prevent certain effects from running. - /// - void OnBeforeAnyHookInvoked(ref List? suppressedCategories); -} - -/// -/// This interface is used to allow scripts to run when they are initialized. This allows for -/// setting up the script with parameters that are passed to it. -/// -public interface IScriptOnInitialize -{ - /// - /// This function is ran when the script is initialized. This allows for setting up the script - /// with parameters that are passed to it. - /// - void OnInitialize(IReadOnlyDictionary? parameters); -} - -/// -/// This interface is used to allow scripts to prevent a move from being selected. -/// -public interface IScriptPreventMoveSelection -{ - /// - /// Override to customize whether the move can be selected at all. - /// - void PreventMoveSelection(IMoveChoice choice, ref bool prevent); -} - -/// -/// This interface is used to allow scripts to force a certain turn choice to be selected. -/// -public interface IScriptForceTurnSelection -{ - /// - /// Force a certain move choice to be selected. If the choice is set, the Pokemon will be forced - /// to use it, and will not be able to select any other choice. - /// - void ForceTurnSelection(IBattle battle, byte sideIndex, byte position, ref ITurnChoice? choice); -} - -/// -/// This interface is used to allow scripts to run before the start of a turn. -/// -public interface IScriptOnBeforeTurnStart -{ - /// - /// This function is ran just before the start of the turn. Everyone has made its choices here, - /// and the turn is about to start. This is a great place to initialize data if you need to know - /// something has happened during a turn. - /// - void OnBeforeTurnStart(ITurnChoice choice); -} - -/// -/// This interface allows scripts to modify the effective speed of the Pokemon. -/// -public interface IScriptChangeSpeed -{ - /// - /// This function allows you to modify the effective speed of the Pokemon. This is run before - /// turn ordering, so overriding here will allow you to put certain Pokemon before others. - /// - void ChangeSpeed(ITurnChoice choice, ref uint speed); -} - -/// -/// This interface allows scripts to modify the effective priority of the Pokemon. -/// -public interface IScriptChangePriority -{ - /// - /// This function allows you to modify the effective priority of the Pokemon. This is run before - /// turn ordering, so overriding here will allow you to put certain Pokemon before others. Note - /// that this is only relevant on move choices, as other turn choice types do not have a priority. - /// - void ChangePriority(IMoveChoice choice, ref sbyte priority); -} - -/// -/// This interface allows scripts to change the move that is used during execution. -/// -public interface IScriptChangeMove -{ - /// - /// This function allows you to change the move that is used during execution. This is useful for - /// moves such as metronome, where the move chosen actually differs from the move used. - /// - void ChangeMove(IMoveChoice choice, ref StringKey moveName); -} - -/// -/// This interface allows scripts to change the targets of a move choice. -/// -public interface IScriptChangeTargets -{ - /// - /// Changes the targets of a move choice. This allows for changing the targets of a move before the move starts. - /// - void ChangeTargets(IMoveChoice moveChoice, ref IReadOnlyList targets); -} - -/// -/// This interface allows scripts to change the incoming targets of a move choice. -/// -public interface IScriptChangeIncomingTargets -{ - /// - /// This function allows you to change the targets of a move choice before the move starts. - /// - void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList targets); -} - -/// -/// This interface allows scripts to change a move into a multi-hit move. -/// -public interface IScriptChangeNumberOfHits -{ - /// - /// This function allows you to change a move into a multi-hit move. The number of hits set here - /// gets used as the number of hits. If set to 0, this will behave as if the move missed on its - /// first hit. - /// - void ChangeNumberOfHits(IMoveChoice choice, ref byte numberOfHits); -} - -/// -/// This interface allows scripts to prevent a move from running. -/// -public interface IScriptPreventMove -{ - /// - /// This function allows you to prevent a move from running. If this gets set to true, the move - /// ends execution here. No PP will be decreased in this case. - /// - void PreventMove(IExecutingMove move, ref bool prevent); -} - -/// -/// This interface allows scripts to make the move fail. -/// -public interface IScriptFailMove -{ - /// - /// This function makes the move fail. If the fail field gets set to true, the move ends execution, - /// and fail events get triggered. - /// - void FailMove(IExecutingMove move, ref bool fail); -} - -/// -/// This interface allows scripts to stop execution of the move before it starts. -/// -public interface IScriptStopBeforeMove -{ - /// - /// Similar to . This function will also stop execution of the move, but - /// PP will still be decreased. - /// - void StopBeforeMove(IExecutingMove move, ref bool stop); -} - -/// -/// This interface allows scripts to run just before the move starts its execution. -/// -public interface IScriptOnBeforeMove -{ - /// - /// This function runs just before the move starts its execution. - /// - void OnBeforeMove(IExecutingMove move); -} - -/// -/// This interface allows scripts to run immediately after all targets have had their hits executed. -/// -public interface IScriptOnAfterMove -{ - /// - /// This function runs immediately after all targets have had their hits executed. - /// - void OnAfterMove(IExecutingMove move); -} - -/// -/// This interface allows scripts to change the type of a move that is used on a target. -/// -public interface IScriptChangeMoveType -{ - /// - /// This function allows the script to change the actual type that is used for the move on a target. - /// If this is set to null, the move will be treated as a typeless move. - /// - void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier); -} - -/// -/// This interface allows scripts to change the effectiveness of a move on a target. -/// -public interface IScriptChangeEffectiveness -{ - /// - /// This function allows the script to change how effective a move is on a target. - /// - void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness); -} - -/// -/// This interface allows scripts to change the effectiveness of a move on a target. -/// -public interface IScriptChangeIncomingEffectiveness -{ - /// - /// This function allows the script to override how effective a move is on a target. - /// - void ChangeIncomingEffectiveness(IExecutingMove executingMove, IPokemon target, byte hitIndex, - ref float effectiveness); -} - -/// -/// This interface allows scripts to block a critical hit from being applied to a move. -/// -public interface IScriptBlockCriticalHit -{ - /// - /// This function allows a script to block an outgoing move from being critical. - /// - void BlockCriticalHit(IExecutingMove move, IPokemon target, byte hit, ref bool block); -} - -/// -/// This interface allows scripts to block an incoming critical hit from being applied to a move. -/// -public interface IScriptBlockIncomingCriticalHit -{ - /// - /// This function allows a script to block an incoming move from being critical. - /// - void BlockIncomingCriticalHit(IExecutingMove move, IPokemon target, byte hit, ref bool block); -} - -/// -/// This interface allows scripts to run when an incoming hit happens. -/// -public interface IScriptOnIncomingHit -{ - /// - /// This function triggers when an incoming hit happens. This triggers after the damage is done, - /// but before the secondary effect of the move happens. - /// - void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit); -} - -/// -/// This interface allows scripts to run when an opponent faints due to the move that is being executed. -/// -public interface IScriptOnOpponentFaints -{ - /// - /// This function triggers when an opponent on the f ield faints due to the move that is being executed. - /// - void OnOpponentFaints(IExecutingMove move, IPokemon target, byte hit); -} - -/// -/// This interface allows scripts to run when the move uses its secondary effect. -/// -public interface IScriptOnSecondaryEffect -{ - /// - /// This function triggers when the move uses its secondary effect. Moves should implement their - /// secondary effects here. Status moves should implement their actual functionality in this - /// function as well, as status moves effects are defined as secondary effects for simplicity. - /// - void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit); -} - -/// -/// This interface allows scripts to run when a move fails to hit its target. -/// -public interface IScriptFailIncomingMove -{ - /// - /// This function allows a script to prevent a move that is targeted at its owner. If set to true - /// the move fails, and fail events get triggered. - /// - void FailIncomingMove(IExecutingMove move, IPokemon target, ref bool fail); -} - -/// -/// This interface allows scripts to run making the owner invulnerable to an incoming move. -/// -public interface IScriptIsInvulnerableToMove -{ - /// - /// This function allows a script to make its owner invulnerable to an incoming move. - /// - void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable); -} - -/// -/// This interface allows scripts to run when a move misses its target. -/// -public interface IScriptOnMoveMiss -{ - /// - /// This function allows a script to run when a move misses its target. This is used for moves - /// that have a secondary effect that should run even if the move misses, such as Spore. - /// - void OnMoveMiss(IExecutingMove move, IPokemon target); -} - -/// -/// This interface allows scripts to modify the accuracy modifier of a move. -/// -public interface IScriptChangeAccuracyModifier -{ - /// - /// This function allows a script to modify the accuracy of a move used. This value represents - /// the percentage accuracy, so anything above 100% will make it always hit. - /// - void ChangeAccuracyModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier); -} - -/// -/// This interface allows scripts to change the critical stage of a move. -/// -public interface IScriptChangeCriticalStage -{ - /// - /// This function allows a script to change the critical stage of the move used. - /// - void ChangeCriticalStage(IExecutingMove move, IPokemon target, byte hit, ref byte stage); -} - -/// -/// This interface allows scripts to change the damage modifier of a critical hit. -/// -public interface IScriptChangeCriticalModifier -{ - /// - /// This function allows a script to change the damage modifier of a critical hit. This will only - /// run when a hit is critical. - /// - void ChangeCriticalModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier); -} - -/// -/// This interface allows scripts to change the STAB (Same Type Attack Bonus) modifier. -/// -public interface IScriptChangeStabModifier -{ - /// - /// This function allows a script to change the damage modifier of a Same Type Attack Bonus, which - /// occurs when the user has the move type as one of its own types. - /// - void ChangeStabModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, bool isStab, - ref float modifier); -} - -/// -/// This interface allows scripts to change the effective base power of a move. -/// -public interface IScriptChangeBasePower -{ - /// - /// This function allows a script to change the effective base power of a move hit. - /// - void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower); -} - -/// -/// This interface allows scripts to bypass defensive stat boosts for a move hit. -/// -public interface IScriptBypassDefensiveStatBoosts -{ - /// - /// This function allows a script to bypass defensive stat boosts for a move hit. - /// If this is true, the damage will be calculated as if the target has no positive stat boosts. Negative - /// stat boosts will still be applied. - /// - void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass); -} - -/// -/// This interface allows scripts to bypass evasion stat boosts for a move hit. -/// -public interface IScriptBypassEvasionStatBoosts -{ - /// - /// This function allows a script to bypass evasion stat boosts for a move hit. - /// If this is true, the move will handle the evasion stat boosts as if the target has no positive stat boosts. - /// - void BypassEvasionStatBoosts(IExecutingMove move, IPokemon target, byte hitIndex, ref bool bypass); -} - -/// -/// This interface allows scripts to bypass offensive stat boosts for a move hit. -/// -public interface IScriptBypassOffensiveStatBoosts -{ - /// - /// This function allows a script to bypass offensive stat boosts for a move hit. - /// If this is true, the damage will be calculated as if the user has no negative offensive stat boosts. Positive - /// stat boosts will still be applied. - /// - void BypassOffensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass); -} - -/// -/// This interface allows scripts to change the actual offensive stat values used when calculating damage. -/// -public interface IScriptChangeOffensiveStatValue -{ - /// - /// This function allows a script to change the actual offensive stat values used when calculating damage - /// - void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat, - ImmutableStatisticSet targetStats, Statistic stat, ref uint value); -} - -/// -/// This interface allows scripts to change the actual defensive stat values used when calculating damage. -/// -public interface IScriptChangeDefensiveStatValue -{ - /// - /// This function allows a script to change the actual defensive stat values used when calculating damage. - /// - void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat, - ImmutableStatisticSet targetStats, Statistic stat, ref uint value); -} - -/// -/// This interface allows scripts to change the offensive stat value of an incoming move. -/// -public interface IScriptChangeIncomingMoveOffensiveStatValue -{ - /// - /// This function allows a script to change the offensive stat value of an incoming move. - /// - void ChangeIncomingMoveOffensiveStatValue(IExecutingMove executingMove, IPokemon target, byte hitNumber, - uint defensiveStat, StatisticSet targetStats, Statistic offensive, ref uint offensiveStat); -} - -/// -/// This interface allows scripts to change the defensive stat value of an incoming move. -/// -public interface IScriptChangeIncomingMoveDefensiveStatValue -{ - /// - /// This function allows a script to change the defensive stat value of an incoming move. - /// - void ChangeIncomingMoveDefensiveStatValue(IExecutingMove executingMove, IPokemon target, byte hitNumber, - uint origOffensiveStat, StatisticSet targetStats, Statistic defensive, ref uint defensiveStat); -} - -/// -/// This interface allows scripts to change the raw modifier retrieved from the stats of the defender and attacker. -/// -public interface IScriptChangeDamageStatModifier -{ - /// - /// This function allows a script to change the raw modifier we retrieved from the stats of the - /// defender and attacker. The default value is the offensive stat divided by the defensive stat. - /// - void ChangeDamageStatModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier); -} - -/// -/// This interface allows scripts to apply a raw multiplier to the damage done by a move. -/// -public interface IScriptChangeDamageModifier -{ - /// - /// This function allows a script to apply a raw multiplier to the damage done by a move. - /// - void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier); -} - -/// -/// This interface allows scripts to change the damage modifier of an incoming move. -/// -public interface IScriptChangeIncomingMoveDamageModifier -{ - /// - /// This function allows a script to change the damage modifier of an incoming move. - /// - void ChangeIncomingMoveDamageModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, - ref float modifier); -} - -/// -/// This interface allows scripts to modify the outgoing damage done by a move. -/// -public interface IScriptChangeMoveDamage -{ - /// - /// This function allows a script to modify the outgoing damage done by a move. - /// - void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage); -} - -/// -/// This interface allows scripts to modify the incoming damage done by a move. -/// -public interface IScriptChangeIncomingMoveDamage -{ - /// - /// This function allows a script to modify the incoming damage done by a move. - /// - void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage); -} - -/// -/// This interface allows scripts attached to a Pokemon or its parents to prevent stat boost changes. -/// -public interface IScriptPreventStatBoostChange -{ - /// - /// This function allows a script attached to a Pokemon or its parents to prevent stat boost - /// changes on that Pokemon. - /// - void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted, ref bool prevent); -} - -/// -/// This interface allows scripts attached to a Pokemon or its parents to modify stat boost changes. -/// -public interface IScriptChangeStatBoostChange -{ - /// - /// This function allows a script attached to a Pokemon or its parents to modify the amount by - /// which the stat boost will change. If the stat boost is done by the user itself, self - /// inflicted will be true, otherwise it will be false. - /// - void ChangeStatBoostChange(IPokemon target, Statistic stat, bool selfInflicted, ref sbyte amount); -} - -/// -/// This interface allows scripts to run after a stat boost change has been applied. -/// -public interface IScriptOnAfterStatBoostChange -{ - /// - /// This function allows a script to run after a stat boost change has been applied. - /// - void OnAfterStatBoostChange(IPokemon pokemon, Statistic stat, bool selfInflicted, sbyte change); -} - -/// -/// This interface allows scripts to prevent a secondary effect of a move from being applied. -/// -public interface IScriptPreventSecondaryEffect -{ - /// - /// This function allows a script to prevent a secondary effect of a move from being applied. - /// This means the move will still hit and do damage, but not trigger its secondary effect. Note that this - /// function is not called for status moves. - /// - void PreventSecondaryEffect(IExecutingMove move, IPokemon target, byte hit, ref bool prevent); -} - -/// -/// This interface allows scripts attached to a Pokemon or its parents to prevent incoming secondary effects. -/// -public interface IScriptPreventIncomingSecondaryEffect -{ - /// - /// This function allows a script attached to a Pokemon or its parents to prevent an incoming - /// secondary effect. This means the move will still hit and do damage, but not trigger its - /// secondary effect. Note that this function is not called for status moves. - /// - void PreventIncomingSecondaryEffect(IExecutingMove move, IPokemon target, byte hit, ref bool prevent); -} - -/// -/// This interface allows scripts attached to a move or its parents to change the chance of secondary effects. -/// -public interface IScriptChangeEffectChance -{ - /// - /// This function allows a script attached to a move or its parents to change the chance the - /// secondary effect of a move will trigger. The chance is depicted in percentage here, so - /// changing this to above or equal to 100 will make it always hit, while setting it to equal or - /// below 0 will make it never hit. - /// - void ChangeEffectChance(IExecutingMove move, IPokemon target, byte hit, ref float chance); -} - -/// -/// This interface allows scripts attached to a Pokemon or its parents to change the chance of incoming secondary effects. -/// -public interface IScriptChangeIncomingEffectChance -{ - /// - /// This function allows a script attached to a Pokemon or its parents to change the chance the - /// secondary effect of an incoming move will trigger. The chance is depicted in percentage here, - /// so changing this to above or equal to 100 will make it always hit, while setting it to equal - /// or below 0 will make it never hit. - /// - void ChangeIncomingEffectChance(IExecutingMove move, IPokemon target, byte hit, ref float chance); -} - -/// -/// This interface allows scripts to trigger when all hits on a target are finished. -/// -public interface IScriptOnAfterHits -{ - /// - /// This function triggers on a move or its parents when all hits on a target are finished. - /// - void OnAfterHits(IExecutingMove move, IPokemon target); -} - -/// -/// This interface allows scripts to trigger at the end of each turn. -/// -public interface IScriptOnEndTurn -{ - /// - /// This function id triggered on all scripts active in the battle after all choices have finished - /// running. Note that choices are not active anymore here, so their scripts do not call this - /// function. - /// - void OnEndTurn(IScriptSource owner, IBattle battle); -} - -/// -/// This interface allows scripts to prevent the Pokemon it is attached to from being able to switch out. -/// -public interface IScriptPreventSelfSwitch -{ - /// - /// This function prevents the Pokemon it is attached to from being able to switch out. - /// - void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent); -} - -/// -/// This interface allows scripts to prevent switching for any opponent. -/// -public interface IScriptPreventOpponentSwitch -{ - /// - /// This function allows the prevention of switching for any opponent. - /// - void PreventOpponentSwitch(ISwitchChoice choice, ref bool prevent); -} - -/// -/// This interface allows scripts to prevent the Pokemon its attached to from running away. -/// -public interface IScriptPreventSelfRunAway -{ - /// - /// This function allows preventing the running away of the Pokemon its attached to - /// - void PreventSelfRunAway(IFleeChoice choice, ref bool prevent); -} - -/// -/// This interface allows scripts to prevent a Pokemon on another side from running away. -/// -public interface IScriptPreventOpponentRunAway -{ - /// - /// This function prevents a Pokemon on another side than where its attached to from running away. - /// - void PreventOpponentRunAway(IFleeChoice choice, ref bool prevent); -} - -/// -/// This interface allows scripts to trigger when a Pokemon takes damage. -/// -public interface IScriptOnDamage -{ - /// - /// This function is triggered on a Pokemon and its parents when the given Pokemon takes damage. - /// - void OnDamage(IPokemon pokemon, DamageSource source, uint oldHealth, uint newHealth); -} - -/// -/// This interface allows scripts to trigger when a Pokemon faints. -/// -public interface IScriptOnFaint -{ - /// - /// This function is triggered on a Pokemon and its parents when the given Pokemon faints. - /// - void OnFaint(IPokemon pokemon, DamageSource source); -} - -/// -/// This interface allows scripts to trigger when an ally Pokemon faints. -/// -public interface IScriptOnAllyFaint -{ - /// - /// This function is triggered on a Pokemon when an ally Pokemon faints. - /// - void OnAllyFaint(IPokemon ally, IPokemon faintedPokemon); -} - -/// -/// This interface allows scripts to trigger when a Pokemon switches out of the battlefield. -/// -public interface IScriptOnSwitchOut -{ - /// - /// This function is triggered on a Pokemon and its parents when the given Pokemon switches out - /// of the battlefield. - /// - void OnSwitchOut(IPokemon oldPokemon, byte position); -} - -/// -/// This interface allows scripts to trigger when a Pokemon is switched into the battlefield. -/// -public interface IScriptOnSwitchIn -{ - /// - /// This function is triggered on a Pokemon and its parents when the given Pokemon is switched into - /// the battlefield. - /// - void OnSwitchIn(IPokemon pokemon, byte position); -} - -/// -/// This interface allows scripts to trigger when an opponent Pokemon switches in. -/// -public interface IScriptOnOpponentSwitchIn -{ - /// - /// This function is triggered on a Pokemon and its parents when an opponent switches in. - /// - void OnOpponentSwitchIn(IPokemon pokemon, byte position); -} - -/// -/// This interface allows scripts to stack when a volatile effect is added while already in place. -/// -public interface IScriptStack -{ - /// - /// This function is ran when a volatile effect is added while that volatile effect already is - /// in place. Instead of adding the volatile effect twice, it will execute this function instead. - /// - void Stack(); -} - -/// -/// This interface allows scripts to trigger after a Pokemon consumes an item. -/// -public interface IScriptOnAfterItemConsume -{ - /// - /// This function is triggered on a Pokemon and its parents when the given Pokemon consumes the - /// held item it had. - /// - void OnAfterItemConsume(IPokemon pokemon, IItem item); -} - -/// -/// This interface allows scripts to block incoming hits on a target. -/// -public interface IScriptBlockIncomingHit -{ - /// - /// This function allows a script to block an incoming hit. - /// - void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block); -} - -/// -/// This interface allows scripts to block outgoing hits from a move. -/// -public interface IScriptBlockOutgoingHit -{ - /// - /// This function allows a script to block an outgoing hit. - /// - void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block); -} - -/// -/// This interface allows scripts to prevent held item consumption. -/// -public interface IScriptPreventHeldItemConsume -{ - /// - /// This function allows a script to prevent a held item from being consumed. - /// - void PreventHeldItemConsume(IPokemon pokemon, IItem heldItem, ref bool prevented); -} - -/// -/// This interface allows scripts to change incoming damage to a Pokemon. -/// -public interface IScriptChangeIncomingDamage -{ - /// - /// This function allows a script to change any kind of damage that is incoming. - /// - void ChangeIncomingDamage(IPokemon pokemon, DamageSource source, ref uint damage); -} - -/// -/// This interface allows scripts to change the weather duration of a weather effect. -/// -public interface IScriptChangeWeatherDuration -{ - /// - /// This function allows a script to change the weather duration of a weather effect. - /// - void ChangeWeatherDuration(StringKey weatherName, ref int duration); -} - -/// -/// This interface allows scripts to prevent a Pokemon from being healed. -/// -public interface IScriptPreventHeal -{ - /// - /// This function allows a script to prevent a Pokemon from being healed. - /// - void PreventHeal(IPokemon pokemon, uint heal, bool allowRevive, ref bool prevented); -} - -/// -/// This interface allows scripts to change the types a target has for effectiveness calculation. -/// -public interface IScriptChangeTypesForMove -{ - /// - /// This function allows a script to change the types a target has. Multiple types can be set, and will be used - /// for the effectiveness calculation. - /// - void ChangeTypesForMove(IExecutingMove executingMove, IPokemon target, byte hitIndex, IList types); -} - -/// -/// This interface allows scripts to change the types a Pokemon has for incoming moves. -/// -public interface IScriptChangeTypesForIncomingMove -{ - /// - /// This function allows a script to change the types a Pokemon has for a move that's incoming. Multiple types can - /// be set, and will be used for the effectiveness calculation. - /// - void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex, - IList types); -} - -/// -/// This interface allows scripts to change the handling of the move category. -/// -public interface IScriptChangeCategory -{ - /// - /// This function allows a script to change the handling of the move category. This is used for moves that - /// are sometimes a status move, and sometimes a damaging move, such as pollen puff. - /// - void ChangeCategory(IExecutingMove move, IPokemon target, byte hitIndex, ref MoveCategory category); -} - -/// -/// This interface allows scripts to trigger when about to hit a target. -/// -public interface IScriptOnBeforeHit -{ - /// - /// Triggers first when we're about to hit a target. - /// - void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex); -} - -/// -/// This interface allows scripts to prevent a Pokemon from being affected by a status condition. -/// -public interface IScriptPreventStatusChange -{ - /// - /// This function allows a script to prevent a Pokemon from being affected by a status condition. - /// - void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted, ref bool preventStatus); -} - -/// -/// This interface allows scripts to trigger after a status condition has been applied. -/// -public interface IScriptOnAfterStatusChange -{ - /// - /// This function triggers after a status condition has been applied to a Pokemon. - /// - void OnAfterStatusChange(IPokemon pokemon, StringKey status, IPokemon? originPokemon); -} - -/// -/// This interface allows scripts to prevent a Pokemon from being affected by a volatile status condition. -/// -public interface IScriptPreventVolatileAdd -{ - /// - /// This function allows a script to prevent a Pokémon from being affected by a volatile status condition. - /// - void PreventVolatileAdd(IScriptSource parent, Script script, ref bool preventVolatileAdd); -} - -/// -/// This interface allows scripts to make the Pokemon float. -/// -public interface IScriptIsFloating -{ - /// - /// This function allows a script to make the Pokémon it is attached to float. This is used for moves - /// such as levitate, and allows for moves such as earthquake to not hit the Pokémon. - /// - void IsFloating(IPokemon pokemon, ref bool isFloating); -} - -/// -/// This interface allows scripts to prevent weather from changing. -/// -public interface IScriptPreventWeatherChange -{ - /// - /// This function allows a script to prevent the weather from changing. This is used for abilities such as - /// Delta Stream, which prevent the weather from changing to anything other than strong winds. - /// - void PreventWeatherChange(StringKey? weatherName, ref bool preventWeatherChange); -} - -/// -/// This interface allows scripts to trigger when the weather changes. -/// -public interface IScriptOnWeatherChange -{ - /// - /// This function triggers when the weather changes. - /// - void OnWeatherChange(IBattle battle, StringKey? weatherName, StringKey? oldWeatherName); -} - -/// -/// This interface allows scripts to modify the weight of a Pokemon. -/// -public interface IScriptModifyWeight -{ - /// - /// Modifies the weight of a Pokemon. - /// - void ModifyWeight(ref float weight); -} - -/// -/// This interface allows scripts to modify whether a move makes contact. -/// -public interface IScriptModifyIsContact -{ - /// - /// Modifies whether a move is a contact move or not. This is used for abilities such as Long Reach. - /// - void ModifyIsContact(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool isContact); -} - -/// -/// This interface allows scripts to prevent held item theft. -/// -public interface IScriptPreventHeldItemSteal -{ - /// - /// This function allows a script to prevent a held item from being stolen by an effect such as Thief or Covet. - /// - void PreventHeldItemSteal(IPokemon pokemon, IItem heldItem, ref bool prevent); -} - -/// -/// This interface allows scripts to trigger after held item changes. -/// -public interface IScriptOnAfterHeldItemChange -{ - /// - /// This function allows a script to run after a held item has changed. - /// - void OnAfterHeldItemChange(IPokemon pokemon, IItem? previous, IItem? item); -} - -/// -/// This interface allows scripts to modify the PP used by a move. -/// -public interface IScriptModifyPPUsed -{ - /// - /// This function allows a script to modify the PP used by a move. - /// - void ModifyPPUsed(IExecutingMove executingMove, ref byte ppUsed); -} - -/// -/// This interface allows scripts to modify the PP used by an incoming move. -/// -public interface IScriptModifyPPUsedForIncomingMove -{ - /// - /// This function allows a script to modify the PP used by an incoming move. This is used for abilities such as Pressure. - /// - void ModifyPPUsedForIncomingMove(IExecutingMove executingMove, ref byte ppUsed); -} - -/// -/// This interface allows scripts to trigger just before a move choice is executed. -/// -public interface IScriptOnBeforeMoveChoice -{ - /// - /// This function triggers just before a move choice is executed. - /// - void OnBeforeMoveChoice(IMoveChoice moveChoice); -} - -/// -/// This interface allows scripts to trigger after a move choice has been executed. -/// -public interface IScriptOnAfterMoveChoice -{ - /// - /// This function triggers after a move choice has been executed in its entirety. - /// - void OnAfterMoveChoice(IMoveChoice moveChoice); -} - -/// -/// This interface allows scripts to change the turn choice that is made by a Pokemon. -/// -public interface IScriptChangeTurnChoice -{ - /// - /// This function allows a script to change the turn choice that is made by a Pokemon. - /// - void ChangeTurnChoice(ref ITurnChoice choice); -} - -/// -/// This interface allows scripts to change the experience gained when a Pokemon faints. -/// -public interface IScriptChangeExperienceGained -{ - /// - /// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience, - /// and allows for changing this amount of experience. - /// - void ChangeExperienceGained(IPokemon faintedPokemon, IPokemon winningPokemon, ref uint amount); -} - -/// -/// This interface allows scripts to share experience across multiple Pokemon. -/// -public interface IScriptShareExperience -{ - /// - /// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience, - /// and allows for making the experience be shared across multiple Pokemon. - /// Amount is the modifier for how much experience is shared, with 1 being the default amount. - /// - void ShareExperience(IPokemon faintedPokemon, IPokemon winningPokemon, ref bool share, ref float amount); -} - -/// -/// This interface allows scripts to block weather changes. -/// -public interface IScriptBlockWeatherChange -{ - /// - /// This function allows a script to block weather changes. - /// - void BlockWeatherChange(IBattle battle, ref bool block); -} - -/// -/// This interface allows scripts to change the catch rate bonus when a Pokeball is thrown. -/// -public interface IScriptChangeCatchRateBonus -{ - /// - /// This function is called when a Pokeball is thrown at a Pokemon, and allows modifying the catch - /// rate of this attempt. Pokeball modifier effects should be implemented here, as well as for - /// example status effects that change capture rates. - /// - void ChangeCatchRateBonus(IPokemon pokemon, IItem pokeball, ref byte modifier); -} - -/// -/// This interface allows scripts to handle custom trigger events. -/// -public interface IScriptCustomTrigger -{ - /// - /// Custom triggers for scripts. This allows scripts to run custom events that are not part of the - /// standard battle flow. - /// - void CustomTrigger(StringKey eventName, ICustomTriggerArgs args); -} - -/// -/// This interface allows scripts to change the accuracy of a move. -/// -public interface IScriptChangeAccuracy -{ - /// - /// This function allows a script to change the accuracy of a move used. The value for accuracy is in percentage. - /// A custom case goes when 255 is returned, in which case the entire accuracy check is skipped, and the move - /// will always hit. - /// - void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref int modifiedAccuracy); -} - -/// -/// This interface allows scripts to change the accuracy of an incoming move. -/// -public interface IScriptChangeIncomingAccuracy -{ - /// - /// This function allows a script to change the accuracy of a move that is incoming. The value for accuracy is in percentage. - /// A custom case goes when 255 is returned, in which case the entire accuracy check is skipped, and the move - /// will always hit. - /// - void ChangeIncomingAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref int modifiedAccuracy); } \ No newline at end of file diff --git a/PkmnLib.Dynamic/ScriptHandling/ScriptAIInformationInterfaces.cs b/PkmnLib.Dynamic/ScriptHandling/ScriptAIInformationInterfaces.cs new file mode 100644 index 0000000..41ab263 --- /dev/null +++ b/PkmnLib.Dynamic/ScriptHandling/ScriptAIInformationInterfaces.cs @@ -0,0 +1,29 @@ +using PkmnLib.Dynamic.Models; + +namespace PkmnLib.Dynamic.ScriptHandling; + +// These interfaces are used for the AI to determine contextual information about the battle. + +/// +/// Script for getting the number of turns left on a script by the AI. +/// +public interface IAIInfoScriptNumberTurnsLeft +{ + /// + /// This function returns the number of turns left on the script. This is used for scripts that have a + /// limited number of turns, such as Sleep or Confusion. + /// + int TurnsLeft(); +} + +/// +/// Script for getting the expected end of turn damage by the AI. +/// +public interface IAIInfoScriptExpectedEndOfTurnDamage +{ + /// + /// This function returns the expected end of turn damage for the script. This is used for scripts that + /// have an end of turn effect, such as Poison or Burn. + /// + void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage); +} \ No newline at end of file diff --git a/PkmnLib.Dynamic/ScriptHandling/ScriptInterfaces.cs b/PkmnLib.Dynamic/ScriptHandling/ScriptInterfaces.cs new file mode 100644 index 0000000..067ea73 --- /dev/null +++ b/PkmnLib.Dynamic/ScriptHandling/ScriptInterfaces.cs @@ -0,0 +1,1159 @@ +using PkmnLib.Dynamic.Models; +using PkmnLib.Dynamic.Models.Choices; +using PkmnLib.Static; +using PkmnLib.Static.Moves; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Dynamic.ScriptHandling; + +/// +/// This interface is used to allow scripts to run when they are removed from their owner. +/// +public interface IScriptOnRemove +{ + /// + /// This function is ran when this script stops being in effect, and is removed from its owner. + /// + void OnRemove(); +} + +/// +/// This interface is used to allow scripts to run before any hook is invoked. This allows for +/// suppressing certain categories of scripts, which can be useful for preventing certain effects +/// from running. +/// +public interface IScriptOnBeforeAnyHookInvoked +{ + /// + /// This function is ran before any hook is invoked. This allows for suppressing certain categories + /// of scripts. This is useful for example to prevent certain effects from running. + /// + void OnBeforeAnyHookInvoked(ref List? suppressedCategories); +} + +/// +/// This interface is used to allow scripts to run when they are initialized. This allows for +/// setting up the script with parameters that are passed to it. +/// +public interface IScriptOnInitialize +{ + /// + /// This function is ran when the script is initialized. This allows for setting up the script + /// with parameters that are passed to it. + /// + void OnInitialize(IReadOnlyDictionary? parameters); +} + +/// +/// This interface is used to allow scripts to prevent a move from being selected. +/// +public interface IScriptPreventMoveSelection +{ + /// + /// Override to customize whether the move can be selected at all. + /// + void PreventMoveSelection(IMoveChoice choice, ref bool prevent); +} + +/// +/// This interface is used to allow scripts to force a certain turn choice to be selected. +/// +public interface IScriptForceTurnSelection +{ + /// + /// Force a certain move choice to be selected. If the choice is set, the Pokemon will be forced + /// to use it, and will not be able to select any other choice. + /// + void ForceTurnSelection(IBattle battle, byte sideIndex, byte position, ref ITurnChoice? choice); +} + +/// +/// This interface is used to allow scripts to run before the start of a turn. +/// +public interface IScriptOnBeforeTurnStart +{ + /// + /// This function is ran just before the start of the turn. Everyone has made its choices here, + /// and the turn is about to start. This is a great place to initialize data if you need to know + /// something has happened during a turn. + /// + void OnBeforeTurnStart(ITurnChoice choice); +} + +/// +/// This interface allows scripts to modify the effective speed of the Pokemon. +/// +public interface IScriptChangeSpeed +{ + /// + /// This function allows you to modify the effective speed of the Pokemon. This is run before + /// turn ordering, so overriding here will allow you to put certain Pokemon before others. + /// + void ChangeSpeed(ITurnChoice choice, ref uint speed); +} + +/// +/// This interface allows scripts to modify the effective priority of the Pokemon. +/// +public interface IScriptChangePriority +{ + /// + /// This function allows you to modify the effective priority of the Pokemon. This is run before + /// turn ordering, so overriding here will allow you to put certain Pokemon before others. Note + /// that this is only relevant on move choices, as other turn choice types do not have a priority. + /// + void ChangePriority(IMoveChoice choice, ref sbyte priority); +} + +/// +/// This interface allows scripts to change the move that is used during execution. +/// +public interface IScriptChangeMove +{ + /// + /// This function allows you to change the move that is used during execution. This is useful for + /// moves such as metronome, where the move chosen actually differs from the move used. + /// + void ChangeMove(IMoveChoice choice, ref StringKey moveName); +} + +/// +/// This interface allows scripts to change the targets of a move choice. +/// +public interface IScriptChangeTargets +{ + /// + /// Changes the targets of a move choice. This allows for changing the targets of a move before the move starts. + /// + void ChangeTargets(IMoveChoice moveChoice, ref IReadOnlyList targets); +} + +/// +/// This interface allows scripts to change the incoming targets of a move choice. +/// +public interface IScriptChangeIncomingTargets +{ + /// + /// This function allows you to change the targets of a move choice before the move starts. + /// + void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList targets); +} + +/// +/// This interface allows scripts to change a move into a multi-hit move. +/// +public interface IScriptChangeNumberOfHits +{ + /// + /// This function allows you to change a move into a multi-hit move. The number of hits set here + /// gets used as the number of hits. If set to 0, this will behave as if the move missed on its + /// first hit. + /// + void ChangeNumberOfHits(IMoveChoice choice, ref byte numberOfHits); +} + +/// +/// This interface allows scripts to prevent a move from running. +/// +public interface IScriptPreventMove +{ + /// + /// This function allows you to prevent a move from running. If this gets set to true, the move + /// ends execution here. No PP will be decreased in this case. + /// + void PreventMove(IExecutingMove move, ref bool prevent); +} + +/// +/// This interface allows scripts to make the move fail. +/// +public interface IScriptFailMove +{ + /// + /// This function makes the move fail. If the fail field gets set to true, the move ends execution, + /// and fail events get triggered. + /// + void FailMove(IExecutingMove move, ref bool fail); +} + +/// +/// This interface allows scripts to stop execution of the move before it starts. +/// +public interface IScriptStopBeforeMove +{ + /// + /// Similar to . This function will also stop execution of the move, but + /// PP will still be decreased. + /// + void StopBeforeMove(IExecutingMove move, ref bool stop); +} + +/// +/// This interface allows scripts to run just before the move starts its execution. +/// +public interface IScriptOnBeforeMove +{ + /// + /// This function runs just before the move starts its execution. + /// + void OnBeforeMove(IExecutingMove move); +} + +/// +/// This interface allows scripts to run immediately after all targets have had their hits executed. +/// +public interface IScriptOnAfterMove +{ + /// + /// This function runs immediately after all targets have had their hits executed. + /// + void OnAfterMove(IExecutingMove move); +} + +/// +/// This interface allows scripts to change the type of a move that is used on a target. +/// +public interface IScriptChangeMoveType +{ + /// + /// This function allows the script to change the actual type that is used for the move on a target. + /// If this is set to null, the move will be treated as a typeless move. + /// + void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier); +} + +/// +/// This interface allows scripts to change the effectiveness of a move on a target. +/// +public interface IScriptChangeEffectiveness +{ + /// + /// This function allows the script to change how effective a move is on a target. + /// + void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness); +} + +/// +/// This interface allows scripts to change the effectiveness of a move on a target. +/// +public interface IScriptChangeIncomingEffectiveness +{ + /// + /// This function allows the script to override how effective a move is on a target. + /// + void ChangeIncomingEffectiveness(IExecutingMove executingMove, IPokemon target, byte hitIndex, + ref float effectiveness); +} + +/// +/// This interface allows scripts to block a critical hit from being applied to a move. +/// +public interface IScriptBlockCriticalHit +{ + /// + /// This function allows a script to block an outgoing move from being critical. + /// + void BlockCriticalHit(IExecutingMove move, IPokemon target, byte hit, ref bool block); +} + +/// +/// This interface allows scripts to block an incoming critical hit from being applied to a move. +/// +public interface IScriptBlockIncomingCriticalHit +{ + /// + /// This function allows a script to block an incoming move from being critical. + /// + void BlockIncomingCriticalHit(IExecutingMove move, IPokemon target, byte hit, ref bool block); +} + +/// +/// This interface allows scripts to run when an incoming hit happens. +/// +public interface IScriptOnIncomingHit +{ + /// + /// This function triggers when an incoming hit happens. This triggers after the damage is done, + /// but before the secondary effect of the move happens. + /// + void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit); +} + +/// +/// This interface allows scripts to run when an opponent faints due to the move that is being executed. +/// +public interface IScriptOnOpponentFaints +{ + /// + /// This function triggers when an opponent on the f ield faints due to the move that is being executed. + /// + void OnOpponentFaints(IExecutingMove move, IPokemon target, byte hit); +} + +/// +/// This interface allows scripts to run when the move uses its secondary effect. +/// +public interface IScriptOnSecondaryEffect +{ + /// + /// This function triggers when the move uses its secondary effect. Moves should implement their + /// secondary effects here. Status moves should implement their actual functionality in this + /// function as well, as status moves effects are defined as secondary effects for simplicity. + /// + void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit); +} + +/// +/// This interface allows scripts to run when a move fails to hit its target. +/// +public interface IScriptFailIncomingMove +{ + /// + /// This function allows a script to prevent a move that is targeted at its owner. If set to true + /// the move fails, and fail events get triggered. + /// + void FailIncomingMove(IExecutingMove move, IPokemon target, ref bool fail); +} + +/// +/// This interface allows scripts to run making the owner invulnerable to an incoming move. +/// +public interface IScriptIsInvulnerableToMove +{ + /// + /// This function allows a script to make its owner invulnerable to an incoming move. + /// + void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable); +} + +/// +/// This interface allows scripts to run when a move misses its target. +/// +public interface IScriptOnMoveMiss +{ + /// + /// This function allows a script to run when a move misses its target. This is used for moves + /// that have a secondary effect that should run even if the move misses, such as Spore. + /// + void OnMoveMiss(IExecutingMove move, IPokemon target); +} + +/// +/// This interface allows scripts to modify the accuracy modifier of a move. +/// +public interface IScriptChangeAccuracyModifier +{ + /// + /// This function allows a script to modify the accuracy of a move used. This value represents + /// the percentage accuracy, so anything above 100% will make it always hit. + /// + void ChangeAccuracyModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier); +} + +/// +/// This interface allows scripts to change the critical stage of a move. +/// +public interface IScriptChangeCriticalStage +{ + /// + /// This function allows a script to change the critical stage of the move used. + /// + void ChangeCriticalStage(IExecutingMove move, IPokemon target, byte hit, ref byte stage); +} + +/// +/// This interface allows scripts to change the damage modifier of a critical hit. +/// +public interface IScriptChangeCriticalModifier +{ + /// + /// This function allows a script to change the damage modifier of a critical hit. This will only + /// run when a hit is critical. + /// + void ChangeCriticalModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier); +} + +/// +/// This interface allows scripts to change the STAB (Same Type Attack Bonus) modifier. +/// +public interface IScriptChangeStabModifier +{ + /// + /// This function allows a script to change the damage modifier of a Same Type Attack Bonus, which + /// occurs when the user has the move type as one of its own types. + /// + void ChangeStabModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, bool isStab, + ref float modifier); +} + +/// +/// This interface allows scripts to change the effective base power of a move. +/// +public interface IScriptChangeBasePower +{ + /// + /// This function allows a script to change the effective base power of a move hit. + /// + void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower); +} + +/// +/// This interface allows scripts to bypass defensive stat boosts for a move hit. +/// +public interface IScriptBypassDefensiveStatBoosts +{ + /// + /// This function allows a script to bypass defensive stat boosts for a move hit. + /// If this is true, the damage will be calculated as if the target has no positive stat boosts. Negative + /// stat boosts will still be applied. + /// + void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass); +} + +/// +/// This interface allows scripts to bypass evasion stat boosts for a move hit. +/// +public interface IScriptBypassEvasionStatBoosts +{ + /// + /// This function allows a script to bypass evasion stat boosts for a move hit. + /// If this is true, the move will handle the evasion stat boosts as if the target has no positive stat boosts. + /// + void BypassEvasionStatBoosts(IExecutingMove move, IPokemon target, byte hitIndex, ref bool bypass); +} + +/// +/// This interface allows scripts to bypass offensive stat boosts for a move hit. +/// +public interface IScriptBypassOffensiveStatBoosts +{ + /// + /// This function allows a script to bypass offensive stat boosts for a move hit. + /// If this is true, the damage will be calculated as if the user has no negative offensive stat boosts. Positive + /// stat boosts will still be applied. + /// + void BypassOffensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass); +} + +/// +/// This interface allows scripts to change the actual offensive stat values used when calculating damage. +/// +public interface IScriptChangeOffensiveStatValue +{ + /// + /// This function allows a script to change the actual offensive stat values used when calculating damage + /// + void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat, + ImmutableStatisticSet targetStats, Statistic stat, ref uint value); +} + +/// +/// This interface allows scripts to change the actual defensive stat values used when calculating damage. +/// +public interface IScriptChangeDefensiveStatValue +{ + /// + /// This function allows a script to change the actual defensive stat values used when calculating damage. + /// + void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat, + ImmutableStatisticSet targetStats, Statistic stat, ref uint value); +} + +/// +/// This interface allows scripts to change the offensive stat value of an incoming move. +/// +public interface IScriptChangeIncomingMoveOffensiveStatValue +{ + /// + /// This function allows a script to change the offensive stat value of an incoming move. + /// + void ChangeIncomingMoveOffensiveStatValue(IExecutingMove executingMove, IPokemon target, byte hitNumber, + uint defensiveStat, StatisticSet targetStats, Statistic offensive, ref uint offensiveStat); +} + +/// +/// This interface allows scripts to change the defensive stat value of an incoming move. +/// +public interface IScriptChangeIncomingMoveDefensiveStatValue +{ + /// + /// This function allows a script to change the defensive stat value of an incoming move. + /// + void ChangeIncomingMoveDefensiveStatValue(IExecutingMove executingMove, IPokemon target, byte hitNumber, + uint origOffensiveStat, StatisticSet targetStats, Statistic defensive, ref uint defensiveStat); +} + +/// +/// This interface allows scripts to change the raw modifier retrieved from the stats of the defender and attacker. +/// +public interface IScriptChangeDamageStatModifier +{ + /// + /// This function allows a script to change the raw modifier we retrieved from the stats of the + /// defender and attacker. The default value is the offensive stat divided by the defensive stat. + /// + void ChangeDamageStatModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier); +} + +/// +/// This interface allows scripts to apply a raw multiplier to the damage done by a move. +/// +public interface IScriptChangeDamageModifier +{ + /// + /// This function allows a script to apply a raw multiplier to the damage done by a move. + /// + void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier); +} + +/// +/// This interface allows scripts to change the damage modifier of an incoming move. +/// +public interface IScriptChangeIncomingMoveDamageModifier +{ + /// + /// This function allows a script to change the damage modifier of an incoming move. + /// + void ChangeIncomingMoveDamageModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, + ref float modifier); +} + +/// +/// This interface allows scripts to modify the outgoing damage done by a move. +/// +public interface IScriptChangeMoveDamage +{ + /// + /// This function allows a script to modify the outgoing damage done by a move. + /// + void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage); +} + +/// +/// This interface allows scripts to modify the incoming damage done by a move. +/// +public interface IScriptChangeIncomingMoveDamage +{ + /// + /// This function allows a script to modify the incoming damage done by a move. + /// + void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage); +} + +/// +/// This interface allows scripts attached to a Pokemon or its parents to prevent stat boost changes. +/// +public interface IScriptPreventStatBoostChange +{ + /// + /// This function allows a script attached to a Pokemon or its parents to prevent stat boost + /// changes on that Pokemon. + /// + void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted, ref bool prevent); +} + +/// +/// This interface allows scripts attached to a Pokemon or its parents to modify stat boost changes. +/// +public interface IScriptChangeStatBoostChange +{ + /// + /// This function allows a script attached to a Pokemon or its parents to modify the amount by + /// which the stat boost will change. If the stat boost is done by the user itself, self + /// inflicted will be true, otherwise it will be false. + /// + void ChangeStatBoostChange(IPokemon target, Statistic stat, bool selfInflicted, ref sbyte amount); +} + +/// +/// This interface allows scripts to run after a stat boost change has been applied. +/// +public interface IScriptOnAfterStatBoostChange +{ + /// + /// This function allows a script to run after a stat boost change has been applied. + /// + void OnAfterStatBoostChange(IPokemon pokemon, Statistic stat, bool selfInflicted, sbyte change); +} + +/// +/// This interface allows scripts to prevent a secondary effect of a move from being applied. +/// +public interface IScriptPreventSecondaryEffect +{ + /// + /// This function allows a script to prevent a secondary effect of a move from being applied. + /// This means the move will still hit and do damage, but not trigger its secondary effect. Note that this + /// function is not called for status moves. + /// + void PreventSecondaryEffect(IExecutingMove move, IPokemon target, byte hit, ref bool prevent); +} + +/// +/// This interface allows scripts attached to a Pokemon or its parents to prevent incoming secondary effects. +/// +public interface IScriptPreventIncomingSecondaryEffect +{ + /// + /// This function allows a script attached to a Pokemon or its parents to prevent an incoming + /// secondary effect. This means the move will still hit and do damage, but not trigger its + /// secondary effect. Note that this function is not called for status moves. + /// + void PreventIncomingSecondaryEffect(IExecutingMove move, IPokemon target, byte hit, ref bool prevent); +} + +/// +/// This interface allows scripts attached to a move or its parents to change the chance of secondary effects. +/// +public interface IScriptChangeEffectChance +{ + /// + /// This function allows a script attached to a move or its parents to change the chance the + /// secondary effect of a move will trigger. The chance is depicted in percentage here, so + /// changing this to above or equal to 100 will make it always hit, while setting it to equal or + /// below 0 will make it never hit. + /// + void ChangeEffectChance(IExecutingMove move, IPokemon target, byte hit, ref float chance); +} + +/// +/// This interface allows scripts attached to a Pokemon or its parents to change the chance of incoming secondary effects. +/// +public interface IScriptChangeIncomingEffectChance +{ + /// + /// This function allows a script attached to a Pokemon or its parents to change the chance the + /// secondary effect of an incoming move will trigger. The chance is depicted in percentage here, + /// so changing this to above or equal to 100 will make it always hit, while setting it to equal + /// or below 0 will make it never hit. + /// + void ChangeIncomingEffectChance(IExecutingMove move, IPokemon target, byte hit, ref float chance); +} + +/// +/// This interface allows scripts to trigger when all hits on a target are finished. +/// +public interface IScriptOnAfterHits +{ + /// + /// This function triggers on a move or its parents when all hits on a target are finished. + /// + void OnAfterHits(IExecutingMove move, IPokemon target); +} + +/// +/// This interface allows scripts to trigger at the end of each turn. +/// +public interface IScriptOnEndTurn +{ + /// + /// This function id triggered on all scripts active in the battle after all choices have finished + /// running. Note that choices are not active anymore here, so their scripts do not call this + /// function. + /// + void OnEndTurn(IScriptSource owner, IBattle battle); +} + +/// +/// This interface allows scripts to prevent the Pokemon it is attached to from being able to switch out. +/// +public interface IScriptPreventSelfSwitch +{ + /// + /// This function prevents the Pokemon it is attached to from being able to switch out. + /// + void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent); +} + +/// +/// This interface allows scripts to prevent switching for any opponent. +/// +public interface IScriptPreventOpponentSwitch +{ + /// + /// This function allows the prevention of switching for any opponent. + /// + void PreventOpponentSwitch(ISwitchChoice choice, ref bool prevent); +} + +/// +/// This interface allows scripts to prevent the Pokemon its attached to from running away. +/// +public interface IScriptPreventSelfRunAway +{ + /// + /// This function allows preventing the running away of the Pokemon its attached to + /// + void PreventSelfRunAway(IFleeChoice choice, ref bool prevent); +} + +/// +/// This interface allows scripts to prevent a Pokemon on another side from running away. +/// +public interface IScriptPreventOpponentRunAway +{ + /// + /// This function prevents a Pokemon on another side than where its attached to from running away. + /// + void PreventOpponentRunAway(IFleeChoice choice, ref bool prevent); +} + +/// +/// This interface allows scripts to trigger when a Pokemon takes damage. +/// +public interface IScriptOnDamage +{ + /// + /// This function is triggered on a Pokemon and its parents when the given Pokemon takes damage. + /// + void OnDamage(IPokemon pokemon, DamageSource source, uint oldHealth, uint newHealth); +} + +/// +/// This interface allows scripts to trigger when a Pokemon faints. +/// +public interface IScriptOnFaint +{ + /// + /// This function is triggered on a Pokemon and its parents when the given Pokemon faints. + /// + void OnFaint(IPokemon pokemon, DamageSource source); +} + +/// +/// This interface allows scripts to trigger when an ally Pokemon faints. +/// +public interface IScriptOnAllyFaint +{ + /// + /// This function is triggered on a Pokemon when an ally Pokemon faints. + /// + void OnAllyFaint(IPokemon ally, IPokemon faintedPokemon); +} + +/// +/// This interface allows scripts to trigger when a Pokemon switches out of the battlefield. +/// +public interface IScriptOnSwitchOut +{ + /// + /// This function is triggered on a Pokemon and its parents when the given Pokemon switches out + /// of the battlefield. + /// + void OnSwitchOut(IPokemon oldPokemon, byte position); +} + +/// +/// This interface allows scripts to trigger when a Pokemon is switched into the battlefield. +/// +public interface IScriptOnSwitchIn +{ + /// + /// This function is triggered on a Pokemon and its parents when the given Pokemon is switched into + /// the battlefield. + /// + void OnSwitchIn(IPokemon pokemon, byte position); +} + +/// +/// This interface allows scripts to trigger when an opponent Pokemon switches in. +/// +public interface IScriptOnOpponentSwitchIn +{ + /// + /// This function is triggered on a Pokemon and its parents when an opponent switches in. + /// + void OnOpponentSwitchIn(IPokemon pokemon, byte position); +} + +/// +/// This interface allows scripts to stack when a volatile effect is added while already in place. +/// +public interface IScriptStack +{ + /// + /// This function is ran when a volatile effect is added while that volatile effect already is + /// in place. Instead of adding the volatile effect twice, it will execute this function instead. + /// + void Stack(); +} + +/// +/// This interface allows scripts to trigger after a Pokemon consumes an item. +/// +public interface IScriptOnAfterItemConsume +{ + /// + /// This function is triggered on a Pokemon and its parents when the given Pokemon consumes the + /// held item it had. + /// + void OnAfterItemConsume(IPokemon pokemon, IItem item); +} + +/// +/// This interface allows scripts to block incoming hits on a target. +/// +public interface IScriptBlockIncomingHit +{ + /// + /// This function allows a script to block an incoming hit. + /// + void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block); +} + +/// +/// This interface allows scripts to block outgoing hits from a move. +/// +public interface IScriptBlockOutgoingHit +{ + /// + /// This function allows a script to block an outgoing hit. + /// + void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block); +} + +/// +/// This interface allows scripts to prevent held item consumption. +/// +public interface IScriptPreventHeldItemConsume +{ + /// + /// This function allows a script to prevent a held item from being consumed. + /// + void PreventHeldItemConsume(IPokemon pokemon, IItem heldItem, ref bool prevented); +} + +/// +/// This interface allows scripts to change incoming damage to a Pokemon. +/// +public interface IScriptChangeIncomingDamage +{ + /// + /// This function allows a script to change any kind of damage that is incoming. + /// + void ChangeIncomingDamage(IPokemon pokemon, DamageSource source, ref uint damage); +} + +/// +/// This interface allows scripts to change the weather duration of a weather effect. +/// +public interface IScriptChangeWeatherDuration +{ + /// + /// This function allows a script to change the weather duration of a weather effect. + /// + void ChangeWeatherDuration(StringKey weatherName, ref int duration); +} + +/// +/// This interface allows scripts to prevent a Pokemon from being healed. +/// +public interface IScriptPreventHeal +{ + /// + /// This function allows a script to prevent a Pokemon from being healed. + /// + void PreventHeal(IPokemon pokemon, uint heal, bool allowRevive, ref bool prevented); +} + +/// +/// This interface allows scripts to change the types a target has for effectiveness calculation. +/// +public interface IScriptChangeTypesForMove +{ + /// + /// This function allows a script to change the types a target has. Multiple types can be set, and will be used + /// for the effectiveness calculation. + /// + void ChangeTypesForMove(IExecutingMove executingMove, IPokemon target, byte hitIndex, IList types); +} + +/// +/// This interface allows scripts to change the types a Pokemon has for incoming moves. +/// +public interface IScriptChangeTypesForIncomingMove +{ + /// + /// This function allows a script to change the types a Pokemon has for a move that's incoming. Multiple types can + /// be set, and will be used for the effectiveness calculation. + /// + void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex, + IList types); +} + +/// +/// This interface allows scripts to change the handling of the move category. +/// +public interface IScriptChangeCategory +{ + /// + /// This function allows a script to change the handling of the move category. This is used for moves that + /// are sometimes a status move, and sometimes a damaging move, such as pollen puff. + /// + void ChangeCategory(IExecutingMove move, IPokemon target, byte hitIndex, ref MoveCategory category); +} + +/// +/// This interface allows scripts to trigger when about to hit a target. +/// +public interface IScriptOnBeforeHit +{ + /// + /// Triggers first when we're about to hit a target. + /// + void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex); +} + +/// +/// This interface allows scripts to prevent a Pokemon from being affected by a status condition. +/// +public interface IScriptPreventStatusChange +{ + /// + /// This function allows a script to prevent a Pokemon from being affected by a status condition. + /// + void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted, ref bool preventStatus); +} + +/// +/// This interface allows scripts to trigger after a status condition has been applied. +/// +public interface IScriptOnAfterStatusChange +{ + /// + /// This function triggers after a status condition has been applied to a Pokemon. + /// + void OnAfterStatusChange(IPokemon pokemon, StringKey status, IPokemon? originPokemon); +} + +/// +/// This interface allows scripts to prevent a Pokemon from being affected by a volatile status condition. +/// +public interface IScriptPreventVolatileAdd +{ + /// + /// This function allows a script to prevent a Pokémon from being affected by a volatile status condition. + /// + void PreventVolatileAdd(IScriptSource parent, Script script, ref bool preventVolatileAdd); +} + +/// +/// This interface allows scripts to make the Pokemon float. +/// +public interface IScriptIsFloating +{ + /// + /// This function allows a script to make the Pokémon it is attached to float. This is used for moves + /// such as levitate, and allows for moves such as earthquake to not hit the Pokémon. + /// + void IsFloating(IPokemon pokemon, ref bool isFloating); +} + +/// +/// This interface allows scripts to prevent weather from changing. +/// +public interface IScriptPreventWeatherChange +{ + /// + /// This function allows a script to prevent the weather from changing. This is used for abilities such as + /// Delta Stream, which prevent the weather from changing to anything other than strong winds. + /// + void PreventWeatherChange(StringKey? weatherName, ref bool preventWeatherChange); +} + +/// +/// This interface allows scripts to trigger when the weather changes. +/// +public interface IScriptOnWeatherChange +{ + /// + /// This function triggers when the weather changes. + /// + void OnWeatherChange(IBattle battle, StringKey? weatherName, StringKey? oldWeatherName); +} + +/// +/// This interface allows scripts to modify the weight of a Pokemon. +/// +public interface IScriptModifyWeight +{ + /// + /// Modifies the weight of a Pokemon. + /// + void ModifyWeight(ref float weight); +} + +/// +/// This interface allows scripts to modify whether a move makes contact. +/// +public interface IScriptModifyIsContact +{ + /// + /// Modifies whether a move is a contact move or not. This is used for abilities such as Long Reach. + /// + void ModifyIsContact(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool isContact); +} + +/// +/// This interface allows scripts to prevent held item theft. +/// +public interface IScriptPreventHeldItemSteal +{ + /// + /// This function allows a script to prevent a held item from being stolen by an effect such as Thief or Covet. + /// + void PreventHeldItemSteal(IPokemon pokemon, IItem heldItem, ref bool prevent); +} + +/// +/// This interface allows scripts to trigger after held item changes. +/// +public interface IScriptOnAfterHeldItemChange +{ + /// + /// This function allows a script to run after a held item has changed. + /// + void OnAfterHeldItemChange(IPokemon pokemon, IItem? previous, IItem? item); +} + +/// +/// This interface allows scripts to modify the PP used by a move. +/// +public interface IScriptModifyPPUsed +{ + /// + /// This function allows a script to modify the PP used by a move. + /// + void ModifyPPUsed(IExecutingMove executingMove, ref byte ppUsed); +} + +/// +/// This interface allows scripts to modify the PP used by an incoming move. +/// +public interface IScriptModifyPPUsedForIncomingMove +{ + /// + /// This function allows a script to modify the PP used by an incoming move. This is used for abilities such as Pressure. + /// + void ModifyPPUsedForIncomingMove(IExecutingMove executingMove, ref byte ppUsed); +} + +/// +/// This interface allows scripts to trigger just before a move choice is executed. +/// +public interface IScriptOnBeforeMoveChoice +{ + /// + /// This function triggers just before a move choice is executed. + /// + void OnBeforeMoveChoice(IMoveChoice moveChoice); +} + +/// +/// This interface allows scripts to trigger after a move choice has been executed. +/// +public interface IScriptOnAfterMoveChoice +{ + /// + /// This function triggers after a move choice has been executed in its entirety. + /// + void OnAfterMoveChoice(IMoveChoice moveChoice); +} + +/// +/// This interface allows scripts to change the turn choice that is made by a Pokemon. +/// +public interface IScriptChangeTurnChoice +{ + /// + /// This function allows a script to change the turn choice that is made by a Pokemon. + /// + void ChangeTurnChoice(ref ITurnChoice choice); +} + +/// +/// This interface allows scripts to change the experience gained when a Pokemon faints. +/// +public interface IScriptChangeExperienceGained +{ + /// + /// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience, + /// and allows for changing this amount of experience. + /// + void ChangeExperienceGained(IPokemon faintedPokemon, IPokemon winningPokemon, ref uint amount); +} + +/// +/// This interface allows scripts to share experience across multiple Pokemon. +/// +public interface IScriptShareExperience +{ + /// + /// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience, + /// and allows for making the experience be shared across multiple Pokemon. + /// Amount is the modifier for how much experience is shared, with 1 being the default amount. + /// + void ShareExperience(IPokemon faintedPokemon, IPokemon winningPokemon, ref bool share, ref float amount); +} + +/// +/// This interface allows scripts to block weather changes. +/// +public interface IScriptBlockWeatherChange +{ + /// + /// This function allows a script to block weather changes. + /// + void BlockWeatherChange(IBattle battle, ref bool block); +} + +/// +/// This interface allows scripts to change the catch rate bonus when a Pokeball is thrown. +/// +public interface IScriptChangeCatchRateBonus +{ + /// + /// This function is called when a Pokeball is thrown at a Pokemon, and allows modifying the catch + /// rate of this attempt. Pokeball modifier effects should be implemented here, as well as for + /// example status effects that change capture rates. + /// + void ChangeCatchRateBonus(IPokemon pokemon, IItem pokeball, ref byte modifier); +} + +/// +/// This interface allows scripts to handle custom trigger events. +/// +public interface IScriptCustomTrigger +{ + /// + /// Custom triggers for scripts. This allows scripts to run custom events that are not part of the + /// standard battle flow. + /// + void CustomTrigger(StringKey eventName, ICustomTriggerArgs args); +} + +/// +/// This interface allows scripts to change the accuracy of a move. +/// +public interface IScriptChangeAccuracy +{ + /// + /// This function allows a script to change the accuracy of a move used. The value for accuracy is in percentage. + /// A custom case goes when 255 is returned, in which case the entire accuracy check is skipped, and the move + /// will always hit. + /// + void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref int modifiedAccuracy); +} + +/// +/// This interface allows scripts to change the accuracy of an incoming move. +/// +public interface IScriptChangeIncomingAccuracy +{ + /// + /// This function allows a script to change the accuracy of a move that is incoming. The value for accuracy is in percentage. + /// A custom case goes when 255 is returned, in which case the entire accuracy check is skipped, and the move + /// will always hit. + /// + void ChangeIncomingAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref int modifiedAccuracy); +} \ No newline at end of file diff --git a/PkmnLib.Static/Moves/MoveData.cs b/PkmnLib.Static/Moves/MoveData.cs index a04311e..359d07c 100644 --- a/PkmnLib.Static/Moves/MoveData.cs +++ b/PkmnLib.Static/Moves/MoveData.cs @@ -196,4 +196,12 @@ public class MoveDataImpl : IMoveData /// public bool HasFlag(StringKey key) => _flags.Contains(key); +} + +public static class MoveTargetHelpers +{ + public static bool TargetsFoe(this MoveTarget target) => + target is MoveTarget.AdjacentOpponent or MoveTarget.RandomOpponent or MoveTarget.AllAdjacentOpponent + or MoveTarget.AllOpponent or MoveTarget.Adjacent or MoveTarget.AllAdjacent or MoveTarget.Any + or MoveTarget.All; } \ No newline at end of file diff --git a/PkmnLib.Static/StatisticSet.cs b/PkmnLib.Static/StatisticSet.cs index 1062172..18e044a 100644 --- a/PkmnLib.Static/StatisticSet.cs +++ b/PkmnLib.Static/StatisticSet.cs @@ -139,7 +139,8 @@ public record StatisticSet : ImmutableStatisticSet, IEnumerable<(Statistic Speed = value; break; default: - throw new ArgumentException("Invalid statistic."); + SetUnknownStat(stat, value); + break; } } @@ -237,6 +238,9 @@ public record StatisticSet : ImmutableStatisticSet, IEnumerable<(Statistic /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public bool IsEmpty => Hp.Equals(0) && Attack.Equals(0) && Defense.Equals(0) && SpecialAttack.Equals(0) && + SpecialDefense.Equals(0) && Speed.Equals(0); } /// @@ -320,6 +324,9 @@ public abstract record ClampedStatisticSet : StatisticSet where T : struct /// public record StatBoostStatisticSet : ClampedStatisticSet { + public const sbyte MaxStatBoost = 6; + public const sbyte MinStatBoost = -6; + /// protected override sbyte Min => -6; diff --git a/PkmnLib.Tests/Dataloader/AbilityDataLoaderTests.cs b/PkmnLib.Tests/Dataloader/AbilityDataLoaderTests.cs index c07c290..0560d5d 100644 --- a/PkmnLib.Tests/Dataloader/AbilityDataLoaderTests.cs +++ b/PkmnLib.Tests/Dataloader/AbilityDataLoaderTests.cs @@ -1,4 +1,5 @@ using PkmnLib.Dynamic.Libraries.DataLoaders; +using PkmnLib.Dynamic.Plugins; using PkmnLib.Dynamic.ScriptHandling.Registry; namespace PkmnLib.Tests.Dataloader; diff --git a/PkmnLib.Tests/Dataloader/GrowthRateDataLoaderTests.cs b/PkmnLib.Tests/Dataloader/GrowthRateDataLoaderTests.cs index 2b67e31..ec64af4 100644 --- a/PkmnLib.Tests/Dataloader/GrowthRateDataLoaderTests.cs +++ b/PkmnLib.Tests/Dataloader/GrowthRateDataLoaderTests.cs @@ -1,4 +1,5 @@ using PkmnLib.Dynamic.Libraries.DataLoaders; +using PkmnLib.Dynamic.Plugins; using PkmnLib.Dynamic.ScriptHandling.Registry; namespace PkmnLib.Tests.Dataloader; diff --git a/PkmnLib.Tests/Dataloader/ItemDataLoaderTests.cs b/PkmnLib.Tests/Dataloader/ItemDataLoaderTests.cs index a036048..0178184 100644 --- a/PkmnLib.Tests/Dataloader/ItemDataLoaderTests.cs +++ b/PkmnLib.Tests/Dataloader/ItemDataLoaderTests.cs @@ -1,4 +1,5 @@ using PkmnLib.Dynamic.Libraries.DataLoaders; +using PkmnLib.Dynamic.Plugins; using PkmnLib.Dynamic.ScriptHandling.Registry; namespace PkmnLib.Tests.Dataloader; diff --git a/PkmnLib.Tests/Dataloader/MoveDataLoaderTests.cs b/PkmnLib.Tests/Dataloader/MoveDataLoaderTests.cs index 868995e..33fc731 100644 --- a/PkmnLib.Tests/Dataloader/MoveDataLoaderTests.cs +++ b/PkmnLib.Tests/Dataloader/MoveDataLoaderTests.cs @@ -1,4 +1,5 @@ using PkmnLib.Dynamic.Libraries.DataLoaders; +using PkmnLib.Dynamic.Plugins; using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Static.Libraries; diff --git a/PkmnLib.Tests/Dataloader/NatureDataloaderTests.cs b/PkmnLib.Tests/Dataloader/NatureDataloaderTests.cs index 4840cf4..02c3bdf 100644 --- a/PkmnLib.Tests/Dataloader/NatureDataloaderTests.cs +++ b/PkmnLib.Tests/Dataloader/NatureDataloaderTests.cs @@ -1,4 +1,5 @@ using PkmnLib.Dynamic.Libraries.DataLoaders; +using PkmnLib.Dynamic.Plugins; using PkmnLib.Dynamic.ScriptHandling.Registry; namespace PkmnLib.Tests.Dataloader; diff --git a/PkmnLib.Tests/Dataloader/SpeciesDataloaderTests.cs b/PkmnLib.Tests/Dataloader/SpeciesDataloaderTests.cs index d429a89..dd0ef52 100644 --- a/PkmnLib.Tests/Dataloader/SpeciesDataloaderTests.cs +++ b/PkmnLib.Tests/Dataloader/SpeciesDataloaderTests.cs @@ -1,4 +1,5 @@ using PkmnLib.Dynamic.Libraries.DataLoaders; +using PkmnLib.Dynamic.Plugins; using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Static.Libraries; diff --git a/PkmnLib.Tests/Dataloader/TypeDataloaderTests.cs b/PkmnLib.Tests/Dataloader/TypeDataloaderTests.cs index 9f5d11d..a3acb20 100644 --- a/PkmnLib.Tests/Dataloader/TypeDataloaderTests.cs +++ b/PkmnLib.Tests/Dataloader/TypeDataloaderTests.cs @@ -1,4 +1,5 @@ using PkmnLib.Dynamic.Libraries.DataLoaders; +using PkmnLib.Dynamic.Plugins; using PkmnLib.Dynamic.ScriptHandling.Registry; namespace PkmnLib.Tests.Dataloader; diff --git a/Plugins/PkmnLib.Plugin.Gen7.Tests/DataTests/MoveDataTests.cs b/Plugins/PkmnLib.Plugin.Gen7.Tests/DataTests/MoveDataTests.cs index 6f93f40..c4652d4 100644 --- a/Plugins/PkmnLib.Plugin.Gen7.Tests/DataTests/MoveDataTests.cs +++ b/Plugins/PkmnLib.Plugin.Gen7.Tests/DataTests/MoveDataTests.cs @@ -1,6 +1,7 @@ using System.Buffers; using System.Text.Json; using PkmnLib.Dynamic.Libraries; +using PkmnLib.Dynamic.Plugins; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Static.Moves; diff --git a/Plugins/PkmnLib.Plugin.Gen7/AI/AIHelperFunctions.cs b/Plugins/PkmnLib.Plugin.Gen7/AI/AIHelperFunctions.cs new file mode 100644 index 0000000..925b531 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/AI/AIHelperFunctions.cs @@ -0,0 +1,337 @@ +using PkmnLib.Dynamic.AI.Explicit; +using PkmnLib.Plugin.Gen7.Libraries.Battling; +using PkmnLib.Plugin.Gen7.Scripts.Side; +using PkmnLib.Static.Moves; + +namespace PkmnLib.Plugin.Gen7.AI; + +public static class AIHelperFunctions +{ + public static int GetScoreForTargetStatRaise(int score, AIMoveState move, IPokemon target, + StatisticSet statChanges, bool fixedChange = false, bool ignoreContrary = false) + { + var wholeEffect = move.Move.Category != MoveCategory.Status; + + var desireMult = 1; + if (move.User.BattleData?.SideIndex != target.BattleData?.SideIndex) + desireMult = -1; + + if (!ignoreContrary && !fixedChange && target.ActiveAbility?.Name == "contrary") + { + if (desireMult > 0 && wholeEffect) + { + return ExplicitAI.MoveUselessScore; + } + return GetScoreForTargetStatDrop(move, target, statChanges, fixedChange, true); + } + + var addEffect = move.GetScoreChangeForAdditionalEffect(target); + if (addEffect == -999) + { + return score; + } + var expectedEndOfTurnDamage = 0; + target.RunScriptHook(x => + x.ExpectedEndOfTurnDamage(target, ref expectedEndOfTurnDamage)); + // If the target is expected to faint from the end of turn damage, we don't want to + // apply the score for the stat raise, as it will not be able to use it. + if (expectedEndOfTurnDamage >= target.CurrentHealth) + return wholeEffect ? ExplicitAI.MoveUselessScore : score; + + if (!move.User.HasMoveWithEffect("power_trip")) + { + var foeIsAware = target.BattleData?.BattleSide.Pokemon.Any(x => x?.ActiveAbility?.Name == "unaware") != + true; + if (!foeIsAware) + { + return wholeEffect ? ExplicitAI.MoveUselessScore : score; + } + } + var realStatChanges = new StatBoostStatisticSet(); + foreach (var (stat, i) in statChanges) + { + var increment = i; + if (!IsStatRaiseWorthwhile(target, stat, increment, fixedChange)) + { + continue; + } + if (!fixedChange && target.ActiveAbility?.Name == "simple") + { + increment *= 2; + } + increment = (sbyte)Math.Max(increment, + StatBoostStatisticSet.MaxStatBoost - target.StatBoost.GetStatistic(stat)); + realStatChanges.SetStatistic(stat, increment); + } + if (realStatChanges.IsEmpty) + { + return wholeEffect ? ExplicitAI.MoveUselessScore : score; + } + + score += addEffect; + score = GetTargetStatRaiseScoreGeneric(score, target, realStatChanges, move, desireMult); + + foreach (var realStatChange in realStatChanges.Where(x => x.value > 0)) + { + GetTargetStatRaiseScoreOne(ref score, target, realStatChange.statistic, realStatChange.value, move, + desireMult); + } + return score; + } + + public static int GetScoreForTargetStatDrop(AIMoveState move, IPokemon target, StatisticSet statChanges, + bool fixedChange = false, bool ignoreContrary = false) => + throw new NotImplementedException("This method is not implemented"); + + /// + /// Checks if a stat raise is worthwhile for the given Pokémon and stat. + /// + private static bool IsStatRaiseWorthwhile(IPokemon pokemon, Statistic stat, sbyte amount, bool fixedChange = false) + { + if (!fixedChange && pokemon.StatBoost.GetStatistic(stat) == StatBoostStatisticSet.MaxStatBoost) + return false; + if (!pokemon.HasMoveWithEffect("power_trip", "baton_pass")) + return true; + + switch (stat) + { + case Statistic.Attack: + { + if (!pokemon.Moves.WhereNotNull().Any(x => x.MoveData.Category == MoveCategory.Physical && + x.MoveData.SecondaryEffect?.Name != "foul_play")) + { + return false; + } + break; + } + case Statistic.Defense: + { + var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide); + return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y => + y.MoveData.Category == MoveCategory.Physical || y.MoveData.SecondaryEffect?.Name == "psyshock")); + } + case Statistic.SpecialAttack: + { + if (pokemon.Moves.WhereNotNull().All(x => x.MoveData.Category != MoveCategory.Special)) + { + return false; + } + break; + } + case Statistic.SpecialDefense: + { + var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide); + return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y => + y.MoveData.Category == MoveCategory.Special && y.MoveData.SecondaryEffect?.Name != "psyshock")); + } + case Statistic.Speed: + { + if (!pokemon.HasMoveWithEffect("electro_ball", "power_trip")) + { + var targetSpeed = pokemon.BoostedStats.Speed; + var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide); + var meaningful = opponentSide.Pokemon.WhereNotNull().Select(opponent => opponent.BoostedStats.Speed) + .Any(foeSpeed => targetSpeed < foeSpeed && targetSpeed * 2.5 > foeSpeed); + if (!meaningful) + return false; + } + break; + } + case Statistic.Accuracy: + { + var minAccuracy = pokemon.Moves.WhereNotNull().Min(x => x.MoveData.Accuracy); + if (minAccuracy >= 90 && pokemon.StatBoost.Accuracy >= 0) + { + var meaningful = false; + var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide); + if (opponentSide.Pokemon.WhereNotNull().Any(x => x.StatBoost.Evasion > 0)) + { + meaningful = true; + } + if (!meaningful) + return false; + } + break; + } + } + return true; + } + + private static void GetTargetStatRaiseScoreOne(ref int score, IPokemon target, Statistic stat, sbyte increment, + AIMoveState move, float desireMult = 1) + { + var oldStage = target.StatBoost.GetStatistic(stat); + var newStage = (sbyte)(oldStage + increment); + var incMult = Gen7BattleStatCalculator.GetStatBoostModifier(Math.Min(newStage, (sbyte)6)) / + Gen7BattleStatCalculator.GetStatBoostModifier(oldStage); + var actualIncrement = incMult; + incMult -= 1; + incMult *= desireMult; + var opponentSide = target.BattleData!.Battle.Sides.First(x => x != target.BattleData.BattleSide); + + switch (stat) + { + case Statistic.Attack: + { + if (oldStage >= 2 && increment == 1) + score -= (int)(10 * (target.Opposes(move.User) ? 1 : desireMult)); + else + { + var hasSpecialMoves = target.Moves.WhereNotNull() + .Any(x => x.MoveData.Category == MoveCategory.Special); + var inc = hasSpecialMoves ? 8 : 12; + score += (int)(inc * incMult); + } + break; + } + case Statistic.Defense: + { + if (oldStage >= 2 && increment == 1) + score -= (int)(10 * (target.Opposes(move.User) ? 1 : desireMult)); + else + score += (int)(10 * incMult); + break; + } + case Statistic.SpecialAttack: + { + if (oldStage >= 2 && increment == 1) + score -= (int)(10 * (target.Opposes(move.User) ? 1 : desireMult)); + else + { + var hasPhysicalMoves = target.Moves.WhereNotNull().Any(x => + x.MoveData.Category == MoveCategory.Physical && + x.MoveData.SecondaryEffect?.Name != "foul_play"); + var inc = hasPhysicalMoves ? 8 : 12; + score += (int)(inc * incMult); + } + break; + } + case Statistic.SpecialDefense: + { + if (oldStage >= 2 && increment == 1) + score -= (int)(10 * (target.Opposes(move.User) ? 1 : desireMult)); + else + score += (int)(10 * incMult); + break; + } + case Statistic.Speed: + { + var targetSpeed = target.BoostedStats.Speed; + foreach (var opponent in opponentSide.Pokemon.WhereNotNull()) + { + var foeSpeed = opponent.BoostedStats.Speed; + if (foeSpeed <= targetSpeed) + continue; + if (foeSpeed > targetSpeed * 2.5) + continue; + if (targetSpeed * actualIncrement > foeSpeed) + score += (int)(15 * incMult); + else + score += (int)(8 * incMult); + } + if (target.HasMoveWithEffect("electro_ball", "power_trip")) + { + score += (int)(5 * incMult); + } + if (opponentSide.Pokemon.WhereNotNull().Any(x => x.HasMoveWithEffect("gyro_ball"))) + { + score -= (int)(5 * incMult); + } + if (target.ActiveAbility?.Name == "speed_boost") + { + score -= (int)(15 * (target.Opposes(move.User) ? 1 : desireMult)); + } + break; + } + case Statistic.Accuracy: + { + if (oldStage >= 2 && increment == 1) + { + score -= (int)(10 * (target.Opposes(move.User) ? 1 : desireMult)); + } + else + { + var minAccuracy = target.Moves.WhereNotNull().Min(x => x.MoveData.Accuracy); + var previousMinAccuracy = + minAccuracy * Gen7BattleStatCalculator.GetAccuracyEvasionStatModifier(0, oldStage); + if (previousMinAccuracy < 90) + { + score += (int)(10 * incMult); + } + } + break; + } + case Statistic.Evasion: + { + foreach (var opponent in opponentSide.Pokemon.WhereNotNull()) + { + var endOfTurnDamage = 0; + opponent.RunScriptHook(x => + x.ExpectedEndOfTurnDamage(opponent, ref endOfTurnDamage)); + if (endOfTurnDamage > 0) + score += (int)(5 * incMult); + } + if (oldStage >= 2 && increment == 1) + { + score -= (int)(10 * (target.Opposes(move.User) ? 1 : desireMult)); + } + else + { + score += (int)(10 * incMult); + } + break; + } + } + if (target.HasMoveWithEffect("power_trip")) + { + score += (int)(5 * increment * desireMult); + } + if (opponentSide.Pokemon.WhereNotNull().Any(x => x.HasMoveWithEffect("punishment"))) + { + score -= (int)(5 * increment * desireMult); + } + } + + /// + /// Calculates the score for the generic concept of raising a target's stats. + /// + private static int GetTargetStatRaiseScoreGeneric(int score, IPokemon target, StatisticSet statChanges, + AIMoveState move, float desireMult = 1) + { + var totalIncrement = statChanges.Sum(x => x.value); + var turns = target.BattleData!.Battle.CurrentTurnNumber - target.BattleData!.SwitchInTurn; + if (turns < 2 && move.Move.Category == MoveCategory.Status) + score += (int)(totalIncrement * desireMult * 5); + + score += + (int)(totalIncrement * desireMult * ((100 * (target.CurrentHealth / (float)target.MaxHealth) - 50) / 8)); + return score; + } + + private static int GetScoreChangeForAdditionalEffect(this AIMoveState move, IPokemon? target) + { + if (move.Move.SecondaryEffect is null) + return 0; + if (move.User.ActiveAbility?.Name == "sheer_force") + return -999; + if (target is not null && target.BattleData?.Position != move.User.BattleData?.Position && + target.ActiveAbility?.Name == "shield_dust") + return -999; + + if ((move.Move.SecondaryEffect.Chance < 100 && move.User.ActiveAbility?.Name == "serene_grace") || + move.User.BattleData?.BattleSide.VolatileScripts.Contains() == true) + { + return 5; + } + return 0; + } + + private static bool HasMoveWithEffect(this IPokemon pokemon, params StringKey[] effect) + { + return pokemon.Moves.WhereNotNull().Any(move => move.MoveData.SecondaryEffect?.Name is not null && + effect.Contains(move.MoveData.SecondaryEffect.Name)); + } + + private static bool Opposes(this IPokemon pokemon, IPokemon target) => + pokemon.BattleData?.BattleSide != target.BattleData?.BattleSide; +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/AI/AIMoveScoreFunctionAttribute.cs b/Plugins/PkmnLib.Plugin.Gen7/AI/AIMoveScoreFunctionAttribute.cs new file mode 100644 index 0000000..ff73d4d --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/AI/AIMoveScoreFunctionAttribute.cs @@ -0,0 +1,8 @@ +using JetBrains.Annotations; + +namespace PkmnLib.Plugin.Gen7.AI; + +[AttributeUsage(AttributeTargets.Method), MeansImplicitUse] +public class AIMoveScoreFunctionAttribute : Attribute +{ +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/AI/ExplicitAIFunctions.cs b/Plugins/PkmnLib.Plugin.Gen7/AI/ExplicitAIFunctions.cs new file mode 100644 index 0000000..82c1c5b --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/AI/ExplicitAIFunctions.cs @@ -0,0 +1,70 @@ +using System.Linq.Expressions; +using System.Reflection; +using PkmnLib.Dynamic.AI.Explicit; +using PkmnLib.Plugin.Gen7.Scripts.Moves; +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; +using PkmnLib.Static.Moves; + +namespace PkmnLib.Plugin.Gen7.AI; + +public static class ExplicitAIFunctions +{ + public static void RegisterAIFunctions(ExplicitAIHandlers handlers) + { + var baseType = typeof(Script); + foreach (var type in typeof(ExplicitAIFunctions).Assembly.GetTypes().Where(t => baseType.IsAssignableFrom(t))) + { + var attribute = type.GetCustomAttribute(); + if (attribute == null) + continue; + + if (attribute.Category == ScriptCategory.Move) + { + // Check if the move type has a static function in the following format: + // public static void AIMoveEffectScore(MoveOption option, ref int score); + var method = type.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(m => + m.GetCustomAttribute() != null && m.ReturnType == typeof(void) && + m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType == typeof(MoveOption) && + m.GetParameters()[1].ParameterType == typeof(int).MakeByRefType()); + if (method != null) + { + var optionParam = Expression.Parameter(typeof(MoveOption), "option"); + var scoreParam = Expression.Parameter(typeof(int).MakeByRefType(), "score"); + var functionExpression = Expression.Lambda( + Expression.Call(null, method, optionParam, scoreParam), optionParam, scoreParam).Compile(); + handlers.MoveEffectScore.Add(attribute.Name, functionExpression); + } + } + } + + handlers.GeneralMoveAgainstTargetScore.Add("predicated_damage", PredictedDamageScore); + } + + private static void PredictedDamageScore(MoveOption option, ref int score) + { + var target = option.Target; + if (target == null) + return; + if (option.Move.Move.Category == MoveCategory.Status) + return; + var damage = option.EstimatedDamage; + if (target.Volatile.TryGet(out var substitute)) + { + var health = substitute.Health; + score += (int)Math.Min(15.0f * damage / health, 20); + return; + } + + score += (int)Math.Min(15.0f * damage / target.CurrentHealth, 30); + + if (damage > target.CurrentHealth * 1.1f) + { + score += 10; + if ((option.Move.Move.HasFlag("multi_hit") && target.CurrentHealth == target.MaxHealth && + target.ActiveAbility?.Name == "sturdy") || target.HasHeldItem("focus_sash")) + { + score += 8; + } + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Data/Moves.jsonc b/Plugins/PkmnLib.Plugin.Gen7/Data/Moves.jsonc index f88a312..0649d1b 100755 --- a/Plugins/PkmnLib.Plugin.Gen7/Data/Moves.jsonc +++ b/Plugins/PkmnLib.Plugin.Gen7/Data/Moves.jsonc @@ -438,7 +438,8 @@ "flags": [ "contact", "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -704,7 +705,8 @@ "flags": [ "protect", "mirror", - "ballistics" + "ballistics", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -770,7 +772,8 @@ "category": "physical", "flags": [ "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "beat_up" @@ -1101,7 +1104,8 @@ "category": "physical", "flags": [ "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -1413,7 +1417,8 @@ "flags": [ "protect", "mirror", - "ballistics" + "ballistics", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -1747,7 +1752,8 @@ "contact", "protect", "mirror", - "punch" + "punch", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -2580,7 +2586,8 @@ "flags": [ "contact", "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "2_hit_move" @@ -2616,7 +2623,8 @@ "flags": [ "contact", "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -3681,7 +3689,8 @@ "category": "special", "flags": [ "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "fire_spin" @@ -4263,7 +4272,8 @@ "flags": [ "contact", "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -4299,7 +4309,8 @@ "flags": [ "contact", "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -5726,7 +5737,8 @@ "category": "physical", "flags": [ "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -6579,7 +6591,8 @@ "category": "special", "flags": [ "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "magma_storm" @@ -7895,7 +7908,8 @@ "category": "physical", "flags": [ "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -9068,7 +9082,8 @@ "category": "physical", "flags": [ "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -10663,7 +10678,8 @@ "category": "physical", "flags": [ "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -11540,7 +11556,8 @@ "flags": [ "contact", "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "2_5_hit_move" @@ -12168,7 +12185,8 @@ "flags": [ "contact", "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "triple_kick" @@ -12224,7 +12242,8 @@ "category": "physical", "flags": [ "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "twineedle" @@ -12550,7 +12569,8 @@ "category": "special", "flags": [ "protect", - "mirror" + "mirror", + "multi_hit" ], "effect": { "name": "2_5_hit_move" diff --git a/Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs b/Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs index a7c2442..713518e 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs @@ -1,3 +1,5 @@ +using PkmnLib.Dynamic.Plugins; +using PkmnLib.Plugin.Gen7.AI; using PkmnLib.Plugin.Gen7.Libraries.Battling; using PkmnLib.Static.Libraries; using PkmnLib.Static.Species; @@ -47,6 +49,8 @@ public class Gen7Plugin : Plugin, IResourceProvider registry.RegisterDamageCalculator(new Gen7DamageCalculator(Configuration)); registry.RegisterMiscLibrary(new Gen7MiscLibrary()); registry.RegisterCaptureLibrary(new Gen7CaptureLibrary(Configuration)); + + ExplicitAIFunctions.RegisterAIFunctions(registry.ExplicitAIHandlers); } /// diff --git a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7BattleStatCalculator.cs b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7BattleStatCalculator.cs index 0af1ac6..bd08a61 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7BattleStatCalculator.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7BattleStatCalculator.cs @@ -40,7 +40,7 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator public uint CalculateBoostedStat(IPokemon pokemon, Statistic stat) { var flatStat = CalculateFlatStat(pokemon, stat); - var boostModifier = GetStatBoostModifier(pokemon, stat); + var boostModifier = GetStatBoostModifier(pokemon.StatBoost.GetStatistic(stat)); var boostedStat = flatStat * boostModifier; if (boostedStat > uint.MaxValue) boostedStat = uint.MaxValue; @@ -71,13 +71,7 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator if (ignoreEvasion) targetEvasion = 0; var userAccuracy = executingMove.User.StatBoost.Accuracy; - var difference = targetEvasion - userAccuracy; - var statModifier = difference switch - { - > 0 => 3.0f / (3.0f + Math.Min(difference, 6)), - < 0 => 3.0f + -Math.Max(difference, -6) / 3.0f, - _ => 1.0f, - }; + var statModifier = GetAccuracyEvasionStatModifier(targetEvasion, userAccuracy); modifiedAccuracy = (int)(modifiedAccuracy * statModifier); modifiedAccuracy = modifiedAccuracy switch { @@ -116,10 +110,9 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator return (uint)modified; } - private static float GetStatBoostModifier(IPokemon pokemon, Statistic statistic) + public static float GetStatBoostModifier(sbyte amount) { - var boost = pokemon.StatBoost.GetStatistic(statistic); - return boost switch + return amount switch { -6 => 2.0f / 8.0f, -5 => 2.0f / 7.0f, @@ -134,7 +127,18 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator 4 => 6.0f / 2.0f, 5 => 7.0f / 2.0f, 6 => 8.0f / 2.0f, - _ => throw new ArgumentException($"Stat boost was out of expected range of -6 to 6: {boost}"), + _ => throw new ArgumentException($"Stat boost was out of expected range of -6 to 6: {amount}"), + }; + } + + public static float GetAccuracyEvasionStatModifier(sbyte evasion, sbyte accuracy) + { + var difference = evasion - accuracy; + return difference switch + { + > 0 => 3.0f / (3.0f + Math.Min(difference, 6)), + < 0 => 3.0f + -Math.Max(difference, -6) / 3.0f, + _ => 1.0f, }; } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/DrySkin.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/DrySkin.cs index ed1fa16..874d3e9 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/DrySkin.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/DrySkin.cs @@ -8,7 +8,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; /// Bulbapedia - Dry Skin /// [Script(ScriptCategory.Ability, "dry_skin")] -public class DrySkin : Script, IScriptChangeDamageModifier, IScriptOnEndTurn +public class DrySkin : Script, IScriptChangeDamageModifier, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage { private IPokemon? _owningPokemon; @@ -55,4 +55,13 @@ public class DrySkin : Script, IScriptChangeDamageModifier, IScriptOnEndTurn _owningPokemon.Damage(_owningPokemon.MaxHealth / 8, DamageSource.Weather); } } + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + if (pokemon.BattleData?.Battle.WeatherName == ScriptUtils.ResolveName()) + { + damage += (int)(pokemon.MaxHealth / 8f); + } + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/SolarPower.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/SolarPower.cs index 6234470..0d275d4 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/SolarPower.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/SolarPower.cs @@ -6,7 +6,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; /// Bulbapedia - Solar Power /// [Script(ScriptCategory.Ability, "solar_power")] -public class SolarPower : Script, IScriptChangeOffensiveStatValue, IScriptOnEndTurn +public class SolarPower : Script, IScriptChangeOffensiveStatValue, IScriptOnEndTurn, + IAIInfoScriptExpectedEndOfTurnDamage { /// public void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat, @@ -29,6 +30,26 @@ public class SolarPower : Script, IScriptChangeOffensiveStatValue, IScriptOnEndT if (!pokemon.IsUsable) return; + var weatherName = pokemon.BattleData?.Battle.WeatherName; + if (weatherName != ScriptUtils.ResolveName() && + weatherName != ScriptUtils.ResolveName()) + { + return; + } + pokemon.Damage(pokemon.MaxHealth / 8, DamageSource.Weather); } + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + var weatherName = pokemon.BattleData?.Battle.WeatherName; + if (weatherName != ScriptUtils.ResolveName() && + weatherName != ScriptUtils.ResolveName()) + { + return; + } + + damage += (int)(pokemon.MaxHealth / 8f); + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeUserStats.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeUserStats.cs index f241ec0..bbcb771 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeUserStats.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeUserStats.cs @@ -1,3 +1,6 @@ +using PkmnLib.Dynamic.AI.Explicit; +using PkmnLib.Plugin.Gen7.AI; + namespace PkmnLib.Plugin.Gen7.Scripts.Moves; public abstract class ChangeUserStats : Script, IScriptOnInitialize, IScriptOnSecondaryEffect @@ -31,6 +34,19 @@ public abstract class ChangeUserStats : Script, IScriptOnInitialize, IScriptOnSe { move.User.ChangeStatBoost(_stat, _amount, true, false); } + + protected static void GetMoveEffectScore(MoveOption option, Statistic stat, ref int score) + { + if (option.Move.Move.SecondaryEffect == null || + !option.Move.Move.SecondaryEffect.Parameters.TryGetValue("amount", out var amountObj) || + amountObj is not int amount) + { + return; + } + var statisticSet = new StatBoostStatisticSet(); + statisticSet.SetStatistic(stat, (sbyte)amount); + score = AIHelperFunctions.GetScoreForTargetStatRaise(score, option.Move, option.Move.User, statisticSet); + } } [Script(ScriptCategory.Move, "change_user_attack")] @@ -39,6 +55,10 @@ public class ChangeUserAttack : ChangeUserStats public ChangeUserAttack() : base(Statistic.Attack) { } + + [AIMoveScoreFunction] + public static void AIMoveEffectScore(MoveOption option, ref int score) => + GetMoveEffectScore(option, Statistic.Attack, ref score); } [Script(ScriptCategory.Move, "change_user_defense")] @@ -47,6 +67,10 @@ public class ChangeUserDefense : ChangeUserStats public ChangeUserDefense() : base(Statistic.Defense) { } + + [AIMoveScoreFunction] + public static void AIMoveEffectScore(MoveOption option, ref int score) => + GetMoveEffectScore(option, Statistic.Defense, ref score); } [Script(ScriptCategory.Move, "change_user_special_attack")] @@ -55,6 +79,10 @@ public class ChangeUserSpecialAttack : ChangeUserStats public ChangeUserSpecialAttack() : base(Statistic.SpecialAttack) { } + + [AIMoveScoreFunction] + public static void AIMoveEffectScore(MoveOption option, ref int score) => + GetMoveEffectScore(option, Statistic.SpecialAttack, ref score); } [Script(ScriptCategory.Move, "change_user_special_defense")] @@ -63,6 +91,10 @@ public class ChangeUserSpecialDefense : ChangeUserStats public ChangeUserSpecialDefense() : base(Statistic.SpecialDefense) { } + + [AIMoveScoreFunction] + public static void AIMoveEffectScore(MoveOption option, ref int score) => + GetMoveEffectScore(option, Statistic.SpecialDefense, ref score); } [Script(ScriptCategory.Move, "change_user_speed")] @@ -71,6 +103,10 @@ public class ChangeUserSpeed : ChangeUserStats public ChangeUserSpeed() : base(Statistic.Speed) { } + + [AIMoveScoreFunction] + public static void AIMoveEffectScore(MoveOption option, ref int score) => + GetMoveEffectScore(option, Statistic.Speed, ref score); } [Script(ScriptCategory.Move, "change_user_accuracy")] @@ -79,6 +115,10 @@ public class ChangeUserAccuracy : ChangeUserStats public ChangeUserAccuracy() : base(Statistic.Accuracy) { } + + [AIMoveScoreFunction] + public static void AIMoveEffectScore(MoveOption option, ref int score) => + GetMoveEffectScore(option, Statistic.Accuracy, ref score); } [Script(ScriptCategory.Move, "change_user_evasion")] @@ -87,4 +127,8 @@ public class ChangeUserEvasion : ChangeUserStats public ChangeUserEvasion() : base(Statistic.Evasion) { } + + [AIMoveScoreFunction] + public static void AIMoveEffectScore(MoveOption option, ref int score) => + GetMoveEffectScore(option, Statistic.Evasion, ref score); } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MirrorMove.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MirrorMove.cs index ea27524..2cb0ae3 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MirrorMove.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MirrorMove.cs @@ -13,11 +13,13 @@ public class MirrorMove : Script, IScriptChangeMove return; var battle = battleData.Battle; - var currentTurn = battle.ChoiceQueue!.LastRanChoice; + if (battle.ChoiceQueue == null) + return; + var currentTurn = battle.ChoiceQueue.LastRanChoice; var lastMove = battle.PreviousTurnChoices.SelectMany(x => x).OfType() - .TakeWhile(x => x != currentTurn).LastOrDefault(x => x.TargetPosition == choice.TargetPosition && - x.TargetSide == choice.TargetSide && - x.User.BattleData?.IsOnBattlefield == true); + .TakeWhile(x => !Equals(x, currentTurn)).LastOrDefault(x => x.TargetPosition == choice.TargetPosition && + x.TargetSide == choice.TargetSide && + x.User.BattleData?.IsOnBattlefield == true); if (lastMove == null || !lastMove.ChosenMove.MoveData.CanCopyMove()) { choice.Fail(); diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/BindEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/BindEffect.cs index 50f5f03..e9d542b 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/BindEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/BindEffect.cs @@ -1,7 +1,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; [Script(ScriptCategory.Pokemon, "bind")] -public class BindEffect : Script, IScriptOnEndTurn, IScriptPreventSelfSwitch, IScriptPreventSelfRunAway +public class BindEffect : Script, IScriptOnEndTurn, IScriptPreventSelfSwitch, IScriptPreventSelfRunAway, + IAIInfoScriptExpectedEndOfTurnDamage { private readonly IPokemon? _owner; private int _turns; @@ -33,4 +34,10 @@ public class BindEffect : Script, IScriptOnEndTurn, IScriptPreventSelfSwitch, IS /// public void PreventSelfRunAway(IFleeChoice choice, ref bool prevent) => prevent = _turns > 0; + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + damage += (int)(_owner?.MaxHealth * _percentOfMaxHealth ?? 0); + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/FireSpinEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/FireSpinEffect.cs index 56896ed..4aae0c7 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/FireSpinEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/FireSpinEffect.cs @@ -1,7 +1,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; [Script(ScriptCategory.Pokemon, "fire_spin")] -public class FireSpinEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunAway, IScriptPreventSelfSwitch +public class FireSpinEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunAway, IScriptPreventSelfSwitch, + IAIInfoScriptExpectedEndOfTurnDamage { private readonly IPokemon _owner; @@ -21,4 +22,10 @@ public class FireSpinEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunAwa /// public void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent) => prevent = true; + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + damage += (int)(pokemon.MaxHealth / 8f); + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/GhostCurseEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/GhostCurseEffect.cs index 6274179..6347d75 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/GhostCurseEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/GhostCurseEffect.cs @@ -1,7 +1,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; [Script(ScriptCategory.Pokemon, "ghostcurse")] -public class GhostCurseEffect : Script, IScriptOnEndTurn +public class GhostCurseEffect : Script, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage { private IPokemon _pokemon; @@ -15,4 +15,10 @@ public class GhostCurseEffect : Script, IScriptOnEndTurn { _pokemon.Damage(_pokemon.CurrentHealth / 4, DamageSource.Misc); } + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + damage += (int)(_pokemon.CurrentHealth / 4f); + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/InfestationEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/InfestationEffect.cs index 4198c8e..2e514eb 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/InfestationEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/InfestationEffect.cs @@ -1,7 +1,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; [Script(ScriptCategory.Pokemon, "infestation")] -public class InfestationEffect : Script, IScriptOnEndTurn, IScriptPreventSelfSwitch, IScriptPreventSelfRunAway +public class InfestationEffect : Script, IScriptOnEndTurn, IScriptPreventSelfSwitch, IScriptPreventSelfRunAway, + IAIInfoScriptExpectedEndOfTurnDamage { private readonly IPokemon _owner; private int _turns; @@ -30,4 +31,10 @@ public class InfestationEffect : Script, IScriptOnEndTurn, IScriptPreventSelfSwi RemoveSelf(); } } + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + damage += (int)(_owner.MaxHealth / 8f); + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/LeechSeedEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/LeechSeedEffect.cs index 0623331..6b54c2f 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/LeechSeedEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/LeechSeedEffect.cs @@ -1,7 +1,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; [Script(ScriptCategory.Pokemon, "leech_seed")] -public class LeechSeedEffect : Script, IScriptOnEndTurn +public class LeechSeedEffect : Script, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage { private readonly IPokemon _owner; private readonly IPokemon _placer; @@ -15,7 +15,7 @@ public class LeechSeedEffect : Script, IScriptOnEndTurn /// public void OnEndTurn(IScriptSource owner, IBattle battle) { - var damage = _owner.BoostedStats.Hp / 8; + var damage = _owner.MaxHealth / 8; if (_owner.CurrentHealth <= damage) damage = _owner.CurrentHealth; @@ -25,4 +25,10 @@ public class LeechSeedEffect : Script, IScriptOnEndTurn else _placer.Heal(damage); } + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + damage += (int)(_owner.MaxHealth / 8f); + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/MagmaStormEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/MagmaStormEffect.cs index 9cac62f..28bb278 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/MagmaStormEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/MagmaStormEffect.cs @@ -1,7 +1,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; [Script(ScriptCategory.Pokemon, "magma_storm")] -public class MagmaStormEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunAway, IScriptPreventSelfSwitch +public class MagmaStormEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunAway, IScriptPreventSelfSwitch, + IAIInfoScriptExpectedEndOfTurnDamage { private readonly IPokemon _owner; @@ -21,4 +22,10 @@ public class MagmaStormEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunA /// public void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent) => prevent = true; + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + damage += (int)(pokemon.MaxHealth / 16f); + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/NightmareEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/NightmareEffect.cs index 91efe2b..9429267 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/NightmareEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/NightmareEffect.cs @@ -3,7 +3,7 @@ using PkmnLib.Plugin.Gen7.Scripts.Status; namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; [Script(ScriptCategory.Pokemon, "nightmare")] -public class NightmareEffect : Script, IScriptOnEndTurn +public class NightmareEffect : Script, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage { private readonly IPokemon _owner; @@ -23,4 +23,10 @@ public class NightmareEffect : Script, IScriptOnEndTurn var maxHp = _owner.MaxHealth; _owner.Damage(maxHp / 4, DamageSource.Misc); } + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + damage += (int)(_owner.MaxHealth / 4f); + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/SubstituteEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/SubstituteEffect.cs index b8dafd3..b524834 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/SubstituteEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/SubstituteEffect.cs @@ -3,7 +3,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; [Script(ScriptCategory.Pokemon, "substitute")] public class SubstituteEffect(uint health) : Script, IScriptBlockIncomingHit { - private uint _health = health; + public uint Health { get; private set; } = health; /// public void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) @@ -19,12 +19,12 @@ public class SubstituteEffect(uint health) : Script, IScriptBlockIncomingHit block = true; var damage = executingMove.GetHitData(target, hitIndex).Damage; - if (damage >= _health) + if (damage >= Health) { executingMove.Battle.EventHook.Invoke(new DialogEvent("substitute_broken")); RemoveSelf(); return; } - _health -= damage; + Health -= damage; } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/WhirlpoolEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/WhirlpoolEffect.cs index 02da252..9f32ef0 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/WhirlpoolEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/WhirlpoolEffect.cs @@ -1,7 +1,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; [Script(ScriptCategory.Pokemon, "whirlpool")] -public class WhirlpoolEffect : Script, IScriptOnEndTurn, IScriptPreventOpponentRunAway, IScriptPreventOpponentSwitch +public class WhirlpoolEffect : Script, IScriptOnEndTurn, IScriptPreventOpponentRunAway, IScriptPreventOpponentSwitch, + IAIInfoScriptExpectedEndOfTurnDamage { public record PokemonTurn { @@ -80,4 +81,14 @@ public class WhirlpoolEffect : Script, IScriptOnEndTurn, IScriptPreventOpponentR _targetedPokemon.Remove(turn); } } + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + var turn = _targetedPokemon.FirstOrDefault(x => x.Pokemon == pokemon); + if (turn != null) + { + damage += (int)(pokemon.MaxHealth * turn.DamagePercent); + } + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/BadlyPoisoned.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/BadlyPoisoned.cs index 3d88e50..ea308c7 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/BadlyPoisoned.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/BadlyPoisoned.cs @@ -1,7 +1,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Status; [Script(ScriptCategory.Status, "badly_poisoned")] -public class BadlyPoisoned : Poisoned, IScriptOnEndTurn +public class BadlyPoisoned : Poisoned { private int _turns = 1; diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Burned.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Burned.cs index b0b20ae..fb3edda 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Burned.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Burned.cs @@ -3,7 +3,7 @@ using PkmnLib.Static.Moves; namespace PkmnLib.Plugin.Gen7.Scripts.Status; [Script(ScriptCategory.Status, "burned")] -public class Burned : Script, IScriptChangeMoveDamage, IScriptOnEndTurn +public class Burned : Script, IScriptChangeMoveDamage, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage { private IPokemon? _target; @@ -41,4 +41,11 @@ public class Burned : Script, IScriptChangeMoveDamage, IScriptOnEndTurn }); _target.Damage(damage, DamageSource.Status, eventBatch); } + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + if (_target != null) + damage += (int)(_target.MaxHealth / 16f); + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Poisoned.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Poisoned.cs index f013b19..a1b540b 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Poisoned.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Poisoned.cs @@ -1,7 +1,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Status; [Script(ScriptCategory.Status, "poisoned")] -public class Poisoned : Script, IScriptOnEndTurn +public class Poisoned : Script, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage { private IPokemon? _pokemon; @@ -46,4 +46,11 @@ public class Poisoned : Script, IScriptOnEndTurn else _pokemon.Damage(damage, DamageSource.Status, eventBatchId); } + + /// + public virtual void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + if (_pokemon != null) + damage += (int)(_pokemon.MaxHealth * GetPoisonMultiplier()); + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs index d66f8bc..e096c2e 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs @@ -1,7 +1,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Status; [Script(ScriptCategory.Status, "sleep")] -public class Sleep : Script, IScriptPreventMove +public class Sleep : Script, IScriptPreventMove, IAIInfoScriptNumberTurnsLeft { private IPokemon? _pokemon; public int Turns { get; set; } @@ -54,4 +54,7 @@ public class Sleep : Script, IScriptPreventMove { "pokemon", _pokemon }, })); } + + /// + public int TurnsLeft() => Turns; } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Terrain/PsychicTerrain.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Terrain/PsychicTerrain.cs index 62bad21..9e7a31d 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Terrain/PsychicTerrain.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Terrain/PsychicTerrain.cs @@ -27,7 +27,7 @@ public class PsychicTerrain : Script, IScriptIsInvulnerableToMove, IScriptChange if (!IsAffectedByTerrain(target)) return; - // Psychic Terrain prevents priority moves from affecting affected Pokémon. + // Psychic Terrain prevents priority moves from affected Pokémon. if (move.MoveChoice.Priority > 0) { invulnerable = true; diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs index 4de4a23..1a4e0f3 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs @@ -1,7 +1,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Weather; [Script(ScriptCategory.Weather, "hail")] -public class Hail : Script, ILimitedTurnsScript, IScriptOnEndTurn +public class Hail : Script, ILimitedTurnsScript, IScriptOnEndTurn, IAIInfoScriptExpectedEndOfTurnDamage { private int? _duration; @@ -46,4 +46,15 @@ public class Hail : Script, ILimitedTurnsScript, IScriptOnEndTurn battle.SetWeather(null, 0); } } + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + if (pokemon.Types.Any(x => x.Name == "ice")) + return; // Ice types are immune to Hail damage. + if (_duration.HasValue) + { + damage += (int)(pokemon.MaxHealth / 16f); + } + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Sandstorm.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Sandstorm.cs index 40945f3..c29359c 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Sandstorm.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Sandstorm.cs @@ -1,7 +1,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Weather; [Script(ScriptCategory.Weather, "sandstorm")] -public class Sandstorm : Script, IScriptChangeBasePower, IScriptChangeDefensiveStatValue, IScriptOnEndTurn +public class Sandstorm : Script, IScriptChangeBasePower, IScriptChangeDefensiveStatValue, IScriptOnEndTurn, + IAIInfoScriptExpectedEndOfTurnDamage { /// public void OnEndTurn(IScriptSource owner, IBattle battle) @@ -39,4 +40,12 @@ public class Sandstorm : Script, IScriptChangeBasePower, IScriptChangeDefensiveS if (move.UseMove.Name == "solar_beam") basePower /= 2; } + + /// + public void ExpectedEndOfTurnDamage(IPokemon pokemon, ref int damage) + { + if (pokemon.Types.Any(x => x.Name == "rock" || x.Name == "ground" || x.Name == "steel")) + return; // Rock, Ground, and Steel types are immune to Sandstorm damage. + damage += (int)(pokemon.MaxHealth / 16f); + } } \ No newline at end of file