diff --git a/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs b/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs index f95f92a..cd73eee 100644 --- a/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs +++ b/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs @@ -76,7 +76,8 @@ public static class MoveTurnExecutor return; byte ppUsed = 1; - // TODO: Modify the PP used by the move. + executingMove.RunScriptHook(x => x.ModifyPPUsed(executingMove, ref ppUsed)); + targets.WhereNotNull().RunScriptHook(x => x.ModifyPPUsedForIncomingMove(executingMove, ref ppUsed)); if (!executingMove.ChosenMove.TryUse(ppUsed)) return; diff --git a/PkmnLib.Dynamic/Models/Battle.cs b/PkmnLib.Dynamic/Models/Battle.cs index b4bdf8d..311d48a 100644 --- a/PkmnLib.Dynamic/Models/Battle.cs +++ b/PkmnLib.Dynamic/Models/Battle.cs @@ -311,7 +311,6 @@ public class BattleImpl : ScriptSource, IBattle if (choice is IMoveChoice moveChoice) { - // TODO: Hook to change number of PP needed. if (moveChoice.ChosenMove.CurrentPp < 1) return false; if (!TargetResolver.IsValidTarget(moveChoice.TargetSide, moveChoice.TargetPosition, diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs index 37b3a9d..60d9186 100644 --- a/PkmnLib.Dynamic/Models/Pokemon.cs +++ b/PkmnLib.Dynamic/Models/Pokemon.cs @@ -1072,6 +1072,11 @@ public class PokemonImpl : ScriptSource, IPokemon // Allow scripts to trigger based on the faint. this.RunScriptHook(script => script.OnFaint(this, source)); + foreach (var ally in BattleData.BattleSide.Pokemon.WhereNotNull().Where(x => x != this)) + { + ally.RunScriptHook(script => script.OnAllyFaint(ally, this)); + } + // Make sure the OnRemove script is run. this.RunScriptHook(script => script.OnRemove()); diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs index ef403c3..14dcdab 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Script.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs @@ -562,6 +562,13 @@ public abstract class Script : IDeepCloneable { } + /// + /// This function is triggered on a Pokemon when an ally Pokemon faints. + /// + public virtual void OnAllyFaint(IPokemon ally, IPokemon faintedPokemon) + { + } + /// /// This function is triggered on a Pokemon and its parents when the given Pokemon switches out /// of the battlefield. @@ -801,4 +808,18 @@ public abstract class Script : IDeepCloneable public virtual void OnAfterHeldItemChange(IPokemon pokemon, IItem? previous, IItem? item) { } + + /// + /// This function allows a script to modify the PP used by a move. + /// + public virtual void ModifyPPUsed(IExecutingMove executingMove, ref byte ppUsed) + { + } + + /// + /// This function allows a script to modify the PP used by an incoming move. This is used for abilities such as Pressure. + /// + public virtual void ModifyPPUsedForIncomingMove(IExecutingMove executingMove, ref byte ppUsed) + { + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.jsonc b/Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.jsonc index 227de6a..c229176 100755 --- a/Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.jsonc +++ b/Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.jsonc @@ -424,23 +424,30 @@ "pixilate": { "effect": "pixilate" }, - "plus": {}, - "poison_heal": {}, - "poison_point": {}, - "poison_touch": {}, + "plus": { + "effect": "plus" + }, + "poison_heal": { + "effect": "poison_heal" + }, + "poison_point": { + "effect": "poison_point" + }, + "poison_touch": { + "effect": "poison_touch" + }, "power_construct": { - "canBeChanged": false, - "flags": [ - "cant_be_copied" - ] + "effect": "power_construct" }, "power_of_alchemy": { - "flags": [ - "cant_be_copied" - ] + "effect": "power_of_alchemy" + }, + "prankster": { + "effect": "prankster" + }, + "pressure": { + "effect": "pressure" }, - "prankster": {}, - "pressure": {}, "primordial_sea": {}, "prism_armor": {}, "protean": {}, diff --git a/Plugins/PkmnLib.Plugin.Gen7/Data/Pokemon.json b/Plugins/PkmnLib.Plugin.Gen7/Data/Pokemon.json index 0f89d5c..12e7e4f 100755 --- a/Plugins/PkmnLib.Plugin.Gen7/Data/Pokemon.json +++ b/Plugins/PkmnLib.Plugin.Gen7/Data/Pokemon.json @@ -145666,6 +145666,7 @@ } }, "complete": { + "isBattleOnly": true, "abilities": [ "power_construct" ], diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Plus.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Plus.cs new file mode 100644 index 0000000..35af293 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Plus.cs @@ -0,0 +1,23 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Plus is an ability that boosts Special Attack if another ally has Plus or Minus. +/// +/// Bulbapedia - Plus +/// +[Script(ScriptCategory.Ability, "plus")] +public class Plus : Script +{ + /// + public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat, + ImmutableStatisticSet targetStats, Statistic stat, ref uint value) + { + var battleData = move.User.BattleData; + if (battleData is null) + return; + if (battleData.BattleSide.Pokemon.WhereNotNull().Any(x => x.IsUsable && x.ActiveAbility?.Name == "minus")) + { + value = value.MultiplyOrMax(1.5f); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PoisonHeal.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PoisonHeal.cs new file mode 100644 index 0000000..17ff511 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PoisonHeal.cs @@ -0,0 +1,20 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Poison Heal is an ability that heals the Pokémon when it is poisoned instead of damaging it. +/// +/// Bulbapedia - Poison Heal +/// +[Script(ScriptCategory.Ability, "poison_heal")] +public class PoisonHeal : Script +{ + /// + public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) + { + if (eventName != CustomTriggers.PoisonedDamage) + return; + if (args is not CustomTriggers.PoisonedDamageArgs poisonArgs) + return; + poisonArgs.Invert = true; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PoisonPoint.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PoisonPoint.cs new file mode 100644 index 0000000..0a16af0 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PoisonPoint.cs @@ -0,0 +1,17 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Poison Point is an ability that may poison attackers making contact with the Pokémon. +/// +/// Bulbapedia - Poison Point +/// +[Script(ScriptCategory.Ability, "poison_point")] +public class PoisonPoint : Script +{ + /// + public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit) + { + if (move.GetHitData(target, hit).IsContact) + move.User.SetStatus(ScriptUtils.ResolveName(), false); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PoisonTouch.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PoisonTouch.cs new file mode 100644 index 0000000..f9b27f7 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PoisonTouch.cs @@ -0,0 +1,19 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Poison Touch is an ability that may poison targets when the Pokémon uses a contact move. +/// +/// Bulbapedia - Poison Touch +/// +[Script(ScriptCategory.Ability, "poison_touch")] +public class PoisonTouch : Script +{ + private const int PoisonChance = 30; + + /// + public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit) + { + if (move.GetHitData(target, hit).IsContact && move.Battle.Random.GetInt(0, 100) < PoisonChance) + move.User.SetStatus(ScriptUtils.ResolveName(), false); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PowerConstruct.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PowerConstruct.cs new file mode 100644 index 0000000..e3fc87e --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PowerConstruct.cs @@ -0,0 +1,37 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Power Construct is an ability that allows Zygarde to change form when its HP drops below half. +/// +/// Bulbapedia - Power Construct +/// +[Script(ScriptCategory.Ability, "power_construct")] +public class PowerConstruct : Script +{ + private IPokemon? _pokemon; + + /// + public override void OnAddedToParent(IScriptSource source) + { + if (source is not IPokemon pokemon) + throw new InvalidOperationException("PowerConstruct script must be attached to a Pokemon."); + _pokemon = pokemon; + } + + /// + public override void OnEndTurn(IBattle battle) + { + if (_pokemon?.BattleData?.Battle == null) + return; + if (_pokemon.Species.Name != "zygarde") + return; + if (_pokemon.CurrentHealth > _pokemon.BoostedStats.Hp / 2) + return; + if (_pokemon.Form.Name != "10" || _pokemon.Form.Name != "50") + return; + if (!_pokemon.Species.TryGetForm("complete", out var completeForm)) + return; + + _pokemon.ChangeForm(completeForm); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PowerOfAlchemy.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PowerOfAlchemy.cs new file mode 100644 index 0000000..3614b5c --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/PowerOfAlchemy.cs @@ -0,0 +1,20 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Power of Alchemy is an ability that copies the ability of a fainted ally. +/// +/// Bulbapedia - Power of Alchemy +/// +[Script(ScriptCategory.Ability, "power_of_alchemy")] +public class PowerOfAlchemy : Script +{ + /// + public override void OnAllyFaint(IPokemon ally, IPokemon faintedPokemon) + { + if (faintedPokemon.ActiveAbility?.HasFlag("cant_be_copied") != true) + return; + + ally.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(ally)); + ally.ChangeAbility(faintedPokemon.ActiveAbility); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Prankster.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Prankster.cs new file mode 100644 index 0000000..32d43d6 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Prankster.cs @@ -0,0 +1,19 @@ +using PkmnLib.Static.Moves; + +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Prankster is an ability that gives priority to status moves. +/// +/// Bulbapedia - Prankster +/// +[Script(ScriptCategory.Ability, "prankster")] +public class Prankster : Script +{ + /// + public override void ChangePriority(IMoveChoice choice, ref sbyte priority) + { + if (choice.ChosenMove.MoveData.Category == MoveCategory.Status && priority != sbyte.MaxValue) + priority++; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Pressure.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Pressure.cs new file mode 100644 index 0000000..c2b2eed --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Pressure.cs @@ -0,0 +1,18 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Pressure is an ability that makes the opponent use more PP when using moves against the Pokémon. +/// +/// Bulbapedia - Pressure +/// +[Script(ScriptCategory.Ability, "pressure")] +public class Pressure : Script +{ + /// + public override void ModifyPPUsedForIncomingMove(IExecutingMove executingMove, ref byte ppUsed) + { + if (ppUsed == byte.MaxValue) + return; + ppUsed++; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs index 477f58d..4c7d6d6 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs @@ -108,4 +108,12 @@ public static class CustomTriggers { public float HealPercent { get; set; } = HealPercent; } + + public static readonly StringKey PoisonedDamage = "poisoned_damage"; + + public record PoisonedDamageArgs(IPokemon Pokemon, uint Damage) : ICustomTriggerArgs + { + public uint Damage { get; set; } = Damage; + public bool Invert { get; set; } = false; + } } \ 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 3d5129c..8b6786c 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Poisoned.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Poisoned.cs @@ -28,6 +28,9 @@ public class Poisoned : Script damage = 1; var battleData = _pokemon.BattleData; + var args = new CustomTriggers.PoisonedDamageArgs(_pokemon, damage); + _pokemon.RunScriptHook(x => x.CustomTrigger(CustomTriggers.PoisonedDamage, args)); + var eventBatchId = new EventBatchId(); battleData?.Battle.EventHook.Invoke(new DialogEvent("poisoned_damage", new Dictionary { @@ -37,6 +40,10 @@ public class Poisoned : Script { BatchId = eventBatchId, }); - _pokemon.Damage(damage, DamageSource.Status, eventBatchId); + + if (args.Invert) + _pokemon.Heal(damage, batchId: eventBatchId); + else + _pokemon.Damage(damage, DamageSource.Status, eventBatchId); } } \ No newline at end of file