diff --git a/PkmnLib.Dataloader/CommonDataLoaderHelper.cs b/PkmnLib.Dataloader/CommonDataLoaderHelper.cs index 86d09c4..abd6b9c 100644 --- a/PkmnLib.Dataloader/CommonDataLoaderHelper.cs +++ b/PkmnLib.Dataloader/CommonDataLoaderHelper.cs @@ -13,7 +13,7 @@ internal static class CommonDataLoaderHelper if (effect == null) return null; var name = effect.Name; - var chance = effect.Chance; + var chance = effect.Chance ?? -1; var parameters = effect.Parameters?.ToDictionary(x => (StringKey)x.Key, x => x.Value.ToParameter()) ?? new Dictionary(); return new SecondaryEffectImpl(chance, name, parameters); diff --git a/PkmnLib.Dataloader/Models/SerializedMove.cs b/PkmnLib.Dataloader/Models/SerializedMove.cs index f6bcc48..0eaeee3 100644 --- a/PkmnLib.Dataloader/Models/SerializedMove.cs +++ b/PkmnLib.Dataloader/Models/SerializedMove.cs @@ -29,6 +29,6 @@ public class SerializedMove public class SerializedMoveEffect { public string Name { get; set; } = null!; - public float Chance { get; set; } + public float? Chance { get; set; } public Dictionary? Parameters { get; set; } = null!; } \ No newline at end of file diff --git a/PkmnLib.Dynamic/Models/BattleSide.cs b/PkmnLib.Dynamic/Models/BattleSide.cs index 895be12..d44c0db 100644 --- a/PkmnLib.Dynamic/Models/BattleSide.cs +++ b/PkmnLib.Dynamic/Models/BattleSide.cs @@ -255,7 +255,7 @@ public class BattleSideImpl : ScriptSource, IBattleSide } } Battle.EventHook.Invoke(new SwitchEvent(Index, position, pokemon)); - pokemon.RunScriptHook(script => script.OnSwitchIn(pokemon)); + pokemon.RunScriptHook(script => script.OnSwitchIn(pokemon, position)); } else { diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs index f02e0b0..797ea0a 100644 --- a/PkmnLib.Dynamic/Models/Pokemon.cs +++ b/PkmnLib.Dynamic/Models/Pokemon.cs @@ -317,6 +317,11 @@ public interface IPokemon : IScriptSource, IDeepCloneable /// void Damage(uint damage, DamageSource source, EventBatchId batchId = default); + /// + /// Forces the Pokémon to faint. + /// + void Faint(DamageSource source, EventBatchId batchId = default); + /// /// Heals the Pokemon by a specific amount. Unless allow_revive is set to true, this will not /// heal if the Pokemon has 0 health. If the amount healed is 0, this will return false. @@ -967,6 +972,13 @@ public class PokemonImpl : ScriptSource, IPokemon } } + /// + public void Faint(DamageSource source, EventBatchId batchId = default) + { + CurrentHealth = 0; + OnFaint(source); + } + private void OnFaint(DamageSource source) { // If the Pokémon is not in a battle, we don't need to do anything. @@ -996,11 +1008,17 @@ public class PokemonImpl : ScriptSource, IPokemon { if (IsFainted && !allowRevive) return false; + var maxAmount = BoostedStats.Hp - CurrentHealth; if (heal > maxAmount) heal = maxAmount; if (heal == 0) return false; + var prevented = false; + this.RunScriptHook(x => x.PreventHeal(this, heal, allowRevive, ref prevented)); + if (prevented) + return false; + var newHealth = CurrentHealth + heal; BattleData?.Battle.EventHook.Invoke(new HealEvent(this, CurrentHealth, newHealth)); CurrentHealth = newHealth; diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs index 4377dce..6ce488c 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Script.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs @@ -477,7 +477,7 @@ public abstract class Script : IDeepCloneable /// This function is triggered on a Pokemon and its parents when the given Pokemon is switched into /// the battlefield. /// - public virtual void OnSwitchIn(IPokemon pokemon) + public virtual void OnSwitchIn(IPokemon pokemon, byte position) { } @@ -563,4 +563,8 @@ public abstract class Script : IDeepCloneable public virtual void ChangeWeatherDuration(StringKey weatherName, ref int duration) { } + + public virtual void PreventHeal(IPokemon pokemon, uint heal, bool allowRevive, ref bool prevented) + { + } } \ No newline at end of file diff --git a/PkmnLib.Static/Libraries/TypeLibrary.cs b/PkmnLib.Static/Libraries/TypeLibrary.cs index c0b0d78..965deae 100644 --- a/PkmnLib.Static/Libraries/TypeLibrary.cs +++ b/PkmnLib.Static/Libraries/TypeLibrary.cs @@ -13,6 +13,11 @@ public interface IReadOnlyTypeLibrary /// bool TryGetTypeIdentifier(StringKey key, out TypeIdentifier typeIdentifier); + /// + /// Gets the type identifier for a type with an index. + /// + bool TryGetTypeIdentifierFromIndex(byte index, [MaybeNullWhen(false)] out TypeIdentifier typeIdentifier); + /// /// Gets the effectiveness for a single attacking type against a single defending type. /// @@ -46,6 +51,18 @@ public class TypeLibrary : IReadOnlyTypeLibrary return false; } + /// + public bool TryGetTypeIdentifierFromIndex(byte index, out TypeIdentifier typeIdentifier) + { + if (index < 1 || index > _types.Count) + { + typeIdentifier = default; + return false; + } + typeIdentifier = _types[index - 1]; + return true; + } + /// public float GetSingleEffectiveness(TypeIdentifier attacking, TypeIdentifier defending) { diff --git a/PkmnLib.Tests/Data/Moves.json b/PkmnLib.Tests/Data/Moves.json index 48201d3..a380f59 100755 --- a/PkmnLib.Tests/Data/Moves.json +++ b/PkmnLib.Tests/Data/Moves.json @@ -4788,7 +4788,13 @@ "protect", "mirror", "punch" - ] + ], + "effect": { + "name": "change_user_speed", + "parameters": { + "amount": -1 + } + } }, { "name": "happy_hour", @@ -4800,6 +4806,7 @@ "target": "AllAlly", "category": "status", "flags": [] + // TODO: Add effect }, { "name": "harden", @@ -4812,7 +4819,13 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "change_user_defense", + "parameters": { + "amount": 1 + } + } }, { "name": "haze", @@ -4825,7 +4838,10 @@ "category": "status", "flags": [ "ignore-substitute" - ] + ], + "effect": { + "name": "reset_target_stats" + } }, { "name": "head_charge", @@ -4840,7 +4856,13 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "recoil", + "parameters": { + "amount": 0.25 + } + } }, { "name": "head_smash", @@ -4855,7 +4877,13 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "recoil", + "parameters": { + "amount": 0.5 + } + } }, { "name": "headbutt", @@ -4870,7 +4898,11 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "flinch", + "chance": 30 + } }, { "name": "heal_bell", @@ -4886,7 +4918,10 @@ "sound", "distance", "ignore-substitute" - ] + ], + "effect": { + "name": "heal_bell" + } }, { "name": "heal_block", @@ -4902,7 +4937,10 @@ "reflectable", "mirror", "limit_move_choice" - ] + ], + "effect": { + "name": "heal_block" + } }, { "name": "heal_order", @@ -4916,7 +4954,13 @@ "flags": [ "snatch", "heal" - ] + ], + "effect": { + "name": "heal_percent", + "parameters": { + "healPercent": 0.5 + } + } }, { "name": "heal_pulse", @@ -4933,7 +4977,13 @@ "distance", "heal", "pulse" - ] + ], + "effect": { + "name": "heal_percent", + "parameters": { + "healPercent": 0.5 + } + } }, { "name": "healing_wish", @@ -4947,7 +4997,10 @@ "flags": [ "snatch", "heal" - ] + ], + "effect": { + "name": "healing_wish" + } }, { "name": "heart_stamp", @@ -4962,7 +5015,11 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "flinch", + "chance": 30 + } }, { "name": "heart_swap", @@ -4977,7 +5034,10 @@ "protect", "mirror", "ignore-substitute" - ] + ], + "effect": { + "name": "heart_swap" + } }, { "name": "heat_crash", @@ -4993,7 +5053,10 @@ "protect", "mirror", "nonskybattle" - ] + ], + "effect": { + "name": "heat_crash" + } }, { "name": "heat_wave", @@ -5007,7 +5070,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "set_status", + "chance": 10, + "parameters": { + "status": "burned" + } + } }, { "name": "heavy_slam", @@ -5023,7 +5093,10 @@ "protect", "mirror", "nonskybattle" - ] + ], + "effect": { + "name": "heat_crash" + } }, { "name": "helping_hand", @@ -5036,7 +5109,10 @@ "category": "status", "flags": [ "ignore-substitute" - ] + ], + "effect": { + "name": "helping_hand" + } }, { "name": "hex", @@ -5050,7 +5126,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "hex" + } }, { "name": "hidden_power", @@ -5064,7 +5143,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "hidden_power" + } }, { "name": "high_horsepower", @@ -5080,6 +5162,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "high_jump_kick", @@ -5095,7 +5178,10 @@ "protect", "mirror", "gravity" - ] + ], + "effect": { + "name": "high_jump_kick" + } }, { "name": "hold_back", @@ -5110,7 +5196,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "false_swipe" + } }, { "name": "hold_hands", @@ -5124,6 +5213,7 @@ "flags": [ "ignore-substitute" ] + // Does nothing }, { "name": "hone_claws", @@ -5136,7 +5226,14 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "change_multiple_user_stat_boosts", + "parameters": { + "attack": 1, + "accuracy": 1 + } + } }, { "name": "horn_attack", @@ -5152,6 +5249,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "horn_drill", @@ -5166,7 +5264,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "one_hit_ko" + } }, { "name": "horn_leech", @@ -5182,7 +5283,13 @@ "protect", "mirror", "heal" - ] + ], + "effect": { + "name": "drain", + "parameters": { + "drain_modifier": 0.5 + } + } }, { "name": "howl", @@ -5195,7 +5302,13 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "change_user_attack", + "parameters": { + "amount": 1 + } + } }, { "name": "hurricane", @@ -5210,7 +5323,11 @@ "protect", "mirror", "distance" - ] + ], + "effect": { + "name": "confuse", + "chance": 30 + } }, { "name": "hydro_cannon", @@ -5225,7 +5342,10 @@ "recharge", "protect", "mirror" - ] + ], + "effect": { + "name": "requires_recharge" + } }, { "name": "hydro_pump", @@ -5240,6 +5360,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "hydro_vortex__physical", @@ -5276,7 +5397,10 @@ "recharge", "protect", "mirror" - ] + ], + "effect": { + "name": "requires_recharge" + } }, { "name": "hyper_fang", @@ -5291,7 +5415,11 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "flinch", + "chance": 10 + } }, { "name": "hyper_voice", @@ -5308,6 +5436,7 @@ "sound", "ignore-substitute" ] + // No secondary effect }, { "name": "hyperspace_fury", @@ -5320,8 +5449,12 @@ "category": "physical", "flags": [ "mirror", - "ignore-substitute" - ] + "ignore-substitute", + "protect" + ], + "effect": { + "name": "hyperspace_fury" + } }, { "name": "hyperspace_hole", @@ -5334,8 +5467,12 @@ "category": "special", "flags": [ "mirror", - "ignore-substitute" - ] + "ignore-substitute", + "protect" + ], + "effect": { + "name": "hyperspace_fury" + } }, { "name": "hypnosis", @@ -5350,7 +5487,13 @@ "protect", "reflectable", "mirror" - ] + ], + "effect": { + "name": "set_status", + "parameters": { + "status": "sleep" + } + } }, { "name": "ice_ball", @@ -5366,7 +5509,10 @@ "protect", "mirror", "ballistics" - ] + ], + "effect": { + "name": "ice_ball" + } }, { "name": "ice_beam", @@ -5380,7 +5526,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "set_status", + "chance": 10, + "parameters": { + "status": "frozen" + } + } }, { "name": "ice_burn", @@ -5395,7 +5548,10 @@ "charge", "protect", "mirror" - ] + ], + "effect": { + "name": "ice_burn" + } }, { "name": "ice_fang", @@ -5411,7 +5567,10 @@ "protect", "mirror", "bite" - ] + ], + "effect": { + "name": "ice_fang" + } }, { "name": "ice_hammer", @@ -5427,7 +5586,13 @@ "protect", "mirror", "punch" - ] + ], + "effect": { + "name": "change_target_speed", + "parameters": { + "amount": -1 + } + } }, { "name": "ice_punch", @@ -5443,7 +5608,14 @@ "protect", "mirror", "punch" - ] + ], + "effect": { + "name": "set_status", + "chance": 10, + "parameters": { + "status": "frozen" + } + } }, { "name": "ice_shard", @@ -5458,6 +5630,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "icicle_crash", @@ -5471,7 +5644,11 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "flinch", + "chance": 30 + } }, { "name": "icicle_spear", @@ -5485,7 +5662,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "2_5_hit_move" + } }, { "name": "icy_wind", @@ -5499,7 +5679,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "change_target_speed", + "chance": 100, + "parameters": { + "amount": -1 + } + } }, { "name": "imprison", @@ -5513,7 +5700,10 @@ "flags": [ "snatch", "ignore-substitute" - ] + ], + "effect": { + "name": "imprison" + } }, { "name": "incinerate", @@ -5527,7 +5717,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "incinerate" + } }, { "name": "inferno", @@ -5541,7 +5734,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "set_status", + "chance": 100, + "parameters": { + "status": "burned" + } + } }, { "name": "inferno_overdrive__physical", @@ -5578,7 +5778,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "infestation" + } }, { "name": "ingrain", diff --git a/Plugins/PkmnLib.Plugin.Gen7.Tests/PkmnLib.Plugin.Gen7.Tests.csproj b/Plugins/PkmnLib.Plugin.Gen7.Tests/PkmnLib.Plugin.Gen7.Tests.csproj index 1d8c935..a45e414 100644 --- a/Plugins/PkmnLib.Plugin.Gen7.Tests/PkmnLib.Plugin.Gen7.Tests.csproj +++ b/Plugins/PkmnLib.Plugin.Gen7.Tests/PkmnLib.Plugin.Gen7.Tests.csproj @@ -10,14 +10,15 @@ - + - + - - + + + diff --git a/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/HiddenPowerTests.cs b/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/HiddenPowerTests.cs new file mode 100644 index 0000000..0db770c --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/HiddenPowerTests.cs @@ -0,0 +1,90 @@ +using PkmnLib.Dynamic.Libraries; +using PkmnLib.Dynamic.Models; +using PkmnLib.Plugin.Gen7.Scripts.Moves; +using PkmnLib.Static; +using PkmnLib.Static.Libraries; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; + +public class HiddenPowerTests +{ + public record TestCaseData(IndividualValueStatisticSet Ivs, StringKey ExpectedType, byte ExpectedPower) + { + /// + public override string ToString() => + $"Hidden Power type is {ExpectedType}, base power is {ExpectedPower} " + + $"with IVs: HP {Ivs.Hp}, Atk {Ivs.Attack}, Def {Ivs.Defense}, SpA {Ivs.SpecialAttack}, SpD {Ivs.SpecialDefense}, Spe {Ivs.Speed}"; + } + + public static IEnumerable> HiddenPowerTestData() + { + yield return () => new TestCaseData(new IndividualValueStatisticSet(31, 31, 31, 31, 31, 31), "dark", 70); + yield return () => new TestCaseData(new IndividualValueStatisticSet(25, 2, 12, 5, 8, 17), "bug", 31); + yield return () => new TestCaseData(new IndividualValueStatisticSet(29, 19, 18, 22, 15, 28), "fire", 64); + } + + [Test, MethodDataSource(nameof(HiddenPowerTestData))] + public async Task HiddenPower_ChangesType(TestCaseData test) + { + var typeLibrary = new TypeLibrary(); + typeLibrary.RegisterType("normal"); + typeLibrary.RegisterType("fighting"); + typeLibrary.RegisterType("flying"); + typeLibrary.RegisterType("poison"); + typeLibrary.RegisterType("ground"); + typeLibrary.RegisterType("rock"); + typeLibrary.RegisterType("bug"); + typeLibrary.RegisterType("ghost"); + typeLibrary.RegisterType("steel"); + typeLibrary.RegisterType("fire"); + typeLibrary.RegisterType("water"); + typeLibrary.RegisterType("grass"); + typeLibrary.RegisterType("electric"); + typeLibrary.RegisterType("psychic"); + typeLibrary.RegisterType("ice"); + typeLibrary.RegisterType("dragon"); + typeLibrary.RegisterType("dark"); + typeLibrary.RegisterType("fairy"); + + var executingMove = new Mock(MockBehavior.Strict); + var user = new Mock(MockBehavior.Strict); + var target = new Mock(MockBehavior.Strict); + var dynamicLibrary = new Mock(MockBehavior.Strict); + var staticLibrary = new Mock(MockBehavior.Strict); + + executingMove.SetupGet(x => x.User).Returns(user.Object); + user.SetupGet(x => x.IndividualValues).Returns(test.Ivs); + user.SetupGet(x => x.Library).Returns(dynamicLibrary.Object); + staticLibrary.Setup(x => x.Types).Returns(typeLibrary); + dynamicLibrary.Setup(x => x.StaticLibrary).Returns(staticLibrary.Object); + + var moveType = new TypeIdentifier(1, "normal"); + + var hiddenPower = new HiddenPower(); + hiddenPower.ChangeMoveType(executingMove.Object, target.Object, 0, ref moveType); + + await Assert.That(moveType.Name).IsEqualTo(test.ExpectedType); + } + + [Test, MethodDataSource(nameof(HiddenPowerTestData))] + public async Task HiddenPower_ChangesBasePower(TestCaseData test) + { + var executingMove = new Mock(MockBehavior.Strict); + var user = new Mock(MockBehavior.Strict); + var target = new Mock(MockBehavior.Strict); + var dynamicLibrary = new Mock(MockBehavior.Strict); + var staticLibrary = new Mock(MockBehavior.Strict); + + executingMove.SetupGet(x => x.User).Returns(user.Object); + user.SetupGet(x => x.IndividualValues).Returns(test.Ivs); + user.SetupGet(x => x.Library).Returns(dynamicLibrary.Object); + dynamicLibrary.Setup(x => x.StaticLibrary).Returns(staticLibrary.Object); + + var hiddenPower = new HiddenPower(); + byte power = 0; + hiddenPower.ChangeBasePower(executingMove.Object, target.Object, 0, ref power); + + await Assert.That(power).IsEqualTo(test.ExpectedPower); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/FuryCutter.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/FuryCutter.cs index eb50c30..dd88079 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/FuryCutter.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/FuryCutter.cs @@ -1,4 +1,6 @@ +using System; using PkmnLib.Plugin.Gen7.Scripts.Pokemon; +using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Scripts.Moves; @@ -12,7 +14,8 @@ public class FuryCutter : Script if (userEffect == null) return; - userEffect.TurnCount++; - basePower = (byte)(basePower * (userEffect.TurnCount + 1)); + if (userEffect.TurnCount < 5) + userEffect.TurnCount++; + basePower = basePower.MultiplyOrMax((byte)Math.Pow(2, userEffect.TurnCount)); } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealBell.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealBell.cs new file mode 100644 index 0000000..6f1fe63 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealBell.cs @@ -0,0 +1,25 @@ +using System.Linq; +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "heal_bell")] +public class HealBell : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var party = move.User.BattleData?.Battle.Parties.FirstOrDefault(p => p.Party.Contains(target)); + if (party == null) + return; + + foreach (var pokemon in party.Party.WhereNotNull()) + { + pokemon.ClearStatus(); + var confusion = ScriptUtils.ResolveName(); + if (pokemon.Volatile.Contains(confusion)) + pokemon.Volatile.Remove(confusion); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealBlock.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealBlock.cs new file mode 100644 index 0000000..f2a5f29 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealBlock.cs @@ -0,0 +1,13 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "heal_block")] +public class HealBlock : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + target.Volatile.Add(new HealBlockEffect()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealPercent.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealPercent.cs new file mode 100644 index 0000000..62d614a --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealPercent.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "heal_percent")] +public class HealPercent : Script +{ + private float _healPercent = 0.5f; + + /// + public override void OnInitialize(IReadOnlyDictionary? parameters) + { + if (parameters?.TryGetValue("healPercent", out var variable) == true && variable is float healPercent) + { + _healPercent = healPercent; + } + } + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + target.Heal((uint)(move.User.BoostedStats.Hp * _healPercent)); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealingWish.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealingWish.cs new file mode 100644 index 0000000..44da194 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealingWish.cs @@ -0,0 +1,20 @@ +using PkmnLib.Plugin.Gen7.Scripts.Side; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "healing_wish")] +public class HealingWish : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var battleData = move.User.BattleData; + if (battleData == null) + return; + + var side = battleData.Battle.Sides[battleData.SideIndex]; + side.VolatileScripts.Add(new HealingWishEffect(battleData.Position)); + + move.User.Faint(DamageSource.Misc); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HeartSwap.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HeartSwap.cs new file mode 100644 index 0000000..8fc2073 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HeartSwap.cs @@ -0,0 +1,26 @@ +using System; +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "heart_swap")] +public class HeartSwap : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + EventBatchId eventBatchId = new(); + var userStats = move.User.StatBoost; + var targetStats = target.StatBoost; + + foreach (Statistic stat in Enum.GetValues(typeof(Statistic))) + { + var userStat = userStats.GetStatistic(stat); + var targetStat = targetStats.GetStatistic(stat); + if (userStat == targetStat) + continue; + move.User.ChangeStatBoost(stat, (sbyte)(userStat - targetStat), true, eventBatchId); + target.ChangeStatBoost(stat, (sbyte)(targetStat - userStat), false, eventBatchId); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HeatCrash.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HeatCrash.cs new file mode 100644 index 0000000..2d0330d --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HeatCrash.cs @@ -0,0 +1,19 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "heat_crash")] +public class HeatCrash : Script +{ + /// + public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) + { + var weightMultiplier = move.User.WeightInKg / target.WeightInKg; + basePower = weightMultiplier switch + { + > 5 => 120, + > 4 => 100, + > 3 => 80, + > 2 => 60, + _ => 40, + }; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HelpingHand.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HelpingHand.cs new file mode 100644 index 0000000..32dbe7f --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HelpingHand.cs @@ -0,0 +1,11 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "helping_hand")] +public class HelpingHand : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) => + target.Volatile.Add(new HelpingHandEffect()); +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Hex.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Hex.cs new file mode 100644 index 0000000..3cb2883 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Hex.cs @@ -0,0 +1,16 @@ +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "hex")] +public class Hex : Script +{ + /// + public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) + { + if (!target.StatusScript.IsEmpty) + { + basePower = basePower.MultiplyOrMax(2); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HiddenPower.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HiddenPower.cs new file mode 100644 index 0000000..a2456ca --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HiddenPower.cs @@ -0,0 +1,39 @@ +using System; +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "hidden_power")] +public class HiddenPower : Script +{ + /// + public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType) + { + var ivs = move.User.IndividualValues; + + var type = GetHiddenPowerValue(ivs, 0x00000001) * 15 / 63; + + move.User.Library.StaticLibrary.Types.TryGetTypeIdentifierFromIndex((byte)(type + 2), out moveType); + } + + /// + public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) + { + var ivs = move.User.IndividualValues; + + var power = GetHiddenPowerValue(ivs, 0x00000002) * 40 / 63 + 30; + // cast to byte with overflow check + basePower = (byte)Math.Min(power, byte.MaxValue); + } + + /// + /// Helper method to calculate the hidden power value from the IVs. + /// This is used to determine the type and power of the move. + /// + private static int GetHiddenPowerValue(IndividualValueStatisticSet ivs, int significance) => + ((ivs.Hp & significance) >> (significance - 1)) + (((ivs.Attack & significance) >> (significance - 1)) << 1) + + (((ivs.Defense & significance) >> (significance - 1)) << 2) + + (((ivs.Speed & significance) >> (significance - 1)) << 3) + + (((ivs.SpecialAttack & significance) >> (significance - 1)) << 4) + + (((ivs.SpecialDefense & significance) >> (significance - 1)) << 5); +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HighJumpKick.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HighJumpKick.cs new file mode 100644 index 0000000..23c3d7f --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HighJumpKick.cs @@ -0,0 +1,18 @@ +using System; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "high_jump_kick")] +public class HighJumpKick : Script +{ + /// + public override void OnMoveMiss(IExecutingMove move, IPokemon target) + { + var damage = move.GetHitData(target, 0).Damage; + var recoil = damage / 2; + // This recoil damage will not exceed half the user's max HP + var maxHp = move.User.BoostedStats.Hp; + recoil = Math.Min(recoil, maxHp / 2); + move.User.Damage(recoil, DamageSource.Misc); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HyperspaceFury.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HyperspaceFury.cs new file mode 100644 index 0000000..a85f735 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HyperspaceFury.cs @@ -0,0 +1,18 @@ +using System.Linq; +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "hyperspace_fury")] +public class HyperspaceFury : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var protectionScripts = target.Volatile.Select(x => x.Script).OfType(); + foreach (var protectionScript in protectionScripts.ToList()) + { + target.Volatile.Remove(protectionScript.Name); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IceBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IceBall.cs new file mode 100644 index 0000000..5fdf1a3 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IceBall.cs @@ -0,0 +1,34 @@ +using System; +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "ice_ball")] +public class IceBall : Script +{ + /// + public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) + { + var userEffect = move.User.Volatile.Get(); + if (userEffect == null) + return; + + basePower = basePower.MultiplyOrMax((byte)Math.Pow(2, userEffect.TurnCount)); + } + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var userEffect = move.User.Volatile.Get(); + if (userEffect == null) + { + userEffect = new IceBallEffect(move.User, move.UseMove.Name); + move.User.Volatile.Add(userEffect); + } + else + { + userEffect.TurnCount++; + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IceBurn.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IceBurn.cs new file mode 100644 index 0000000..d0f8dbe --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IceBurn.cs @@ -0,0 +1,24 @@ +using System; +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "ice_burn")] +public class IceBurn : BaseChargeMove +{ + /// + public override RequireChargeEffect CreateVolatile(IPokemon user) => new(user, "ice_burn"); + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var battleData = move.User.BattleData; + if (battleData == null) + return; + + if (battleData.Battle.Random.EffectChance(30, move, target, hit)) + { + target.SetStatus("burned"); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IceFang.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IceFang.cs new file mode 100644 index 0000000..cb72ea7 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IceFang.cs @@ -0,0 +1,24 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "ice_fang")] +public class IceFang : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var battleData = move.User.BattleData; + if (battleData == null) + return; + + if (battleData.Battle.Random.EffectChance(10, move, target, hit)) + { + target.SetStatus("frozen"); + } + if (battleData.Battle.Random.EffectChance(10, move, target, hit)) + { + target.Volatile.Add(new FlinchEffect()); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Imprison.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Imprison.cs new file mode 100644 index 0000000..8ba81a7 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Imprison.cs @@ -0,0 +1,13 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "imprison")] +public class Imprison : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + target.Volatile.Add(new ImprisonEffect(move.User)); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Incinerate.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Incinerate.cs new file mode 100644 index 0000000..9ee4e26 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Incinerate.cs @@ -0,0 +1,16 @@ +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "incinerate")] +public class Incinerate : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + if (target.HeldItem is { Category: ItemCategory.Berry }) + { + target.RemoveHeldItemForBattle(); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Infestation.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Infestation.cs new file mode 100644 index 0000000..88425f5 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Infestation.cs @@ -0,0 +1,29 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "infestation")] +public class Infestation : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var infestationEffect = ScriptUtils.ResolveName(); + if (target.Volatile.Contains(infestationEffect)) + { + move.GetHitData(target, hit).Fail(); + return; + } + + var battleData = move.User.BattleData; + if (battleData == null) + return; + + var turns = 4; + if (battleData.Battle.Random.GetBool()) + { + turns = 5; + } + target.Volatile.Add(new InfestationEffect(target, turns)); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/HealBlockEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/HealBlockEffect.cs new file mode 100644 index 0000000..09ecc53 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/HealBlockEffect.cs @@ -0,0 +1,40 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "heal_block")] +public class HealBlockEffect : Script +{ + private int _duration; + + public HealBlockEffect(int duration = 5) + { + _duration = duration; + } + + /// + public override void OnEndTurn(IBattle battle) + { + _duration--; + if (_duration <= 0) + RemoveSelf(); + } + + /// + public override void PreventMoveSelection(IMoveChoice choice, ref bool prevent) + { + if (choice.ChosenMove.MoveData.HasFlag("heal")) + prevent = true; + } + + /// + public override void PreventMove(IExecutingMove move, ref bool prevent) + { + if (move.ChosenMove.MoveData.HasFlag("heal")) + prevent = true; + } + + /// + public override void PreventHeal(IPokemon pokemon, uint heal, bool allowRevive, ref bool prevented) + { + prevented = true; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/HelpingHandEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/HelpingHandEffect.cs new file mode 100644 index 0000000..18aa2a1 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/HelpingHandEffect.cs @@ -0,0 +1,13 @@ +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +public class HelpingHandEffect : Script +{ + /// + public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) => + basePower = basePower.MultiplyOrMax(1.5f); + + /// + public override void OnEndTurn(IBattle battle) => RemoveSelf(); +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/IceBallEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/IceBallEffect.cs new file mode 100644 index 0000000..65ce0d3 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/IceBallEffect.cs @@ -0,0 +1,41 @@ +using PkmnLib.Plugin.Gen7.Scripts.Utils; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "ice_ball")] +public class IceBallEffect : Script +{ + private readonly IPokemon _owner; + private readonly StringKey _moveName; + public int TurnCount { get; set; } + + public IceBallEffect(IPokemon owner, StringKey moveName) + { + _owner = owner; + _moveName = moveName; + TurnCount = 0; + } + + /// + public override void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice) + { + var opposingSideIndex = (byte)(_owner.BattleData?.SideIndex == 0 ? 1 : 0); + choice = TurnChoiceHelper.CreateMoveChoice(_owner, _moveName, opposingSideIndex, position); + } + + /// + public override void OnMoveMiss(IExecutingMove move, IPokemon target) + { + RemoveSelf(); + } + + /// + public override void OnEndTurn(IBattle battle) + { + if (TurnCount < 5) + TurnCount++; + else + RemoveSelf(); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ImprisonEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ImprisonEffect.cs new file mode 100644 index 0000000..e33b455 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ImprisonEffect.cs @@ -0,0 +1,22 @@ +using System.Linq; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "imprison")] +public class ImprisonEffect : Script +{ + private readonly IPokemon _user; + + public ImprisonEffect(IPokemon user) + { + _user = user; + } + + /// + public override void PreventMoveSelection(IMoveChoice choice, ref bool prevent) + { + if (_user.Moves.WhereNotNull().Any(x => x.MoveData.Name == choice.ChosenMove.MoveData.Name)) + prevent = true; + } +} \ 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 new file mode 100644 index 0000000..150fad4 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/InfestationEffect.cs @@ -0,0 +1,33 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "infestation")] +public class InfestationEffect : Script +{ + private readonly IPokemon _owner; + private int _turns; + + public InfestationEffect(IPokemon owner, int turns) + { + _owner = owner; + _turns = turns; + } + + /// + public override void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent) => prevent = true; + + /// + public override void PreventSelfRunAway(IFleeChoice choice, ref bool prevent) => prevent = true; + + /// + public override void OnEndTurn(IBattle battle) + { + var damage = _owner.BoostedStats.Hp / 8; + _owner.Damage(damage, DamageSource.Misc); + + _turns--; + if (_turns <= 0) + { + RemoveSelf(); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/HealingWishEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/HealingWishEffect.cs new file mode 100644 index 0000000..cf6e04a --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/HealingWishEffect.cs @@ -0,0 +1,23 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Side; + +[Script(ScriptCategory.Side, "healing_wish")] +public class HealingWishEffect : Script +{ + private readonly byte _position; + + public HealingWishEffect(byte position) + { + _position = position; + } + + /// + public override void OnSwitchIn(IPokemon pokemon, byte position) + { + if (position == _position) + { + pokemon.Heal(pokemon.BoostedStats.Hp); + pokemon.ClearStatus(); + RemoveSelf(); + } + } +} \ No newline at end of file