Implements a bunch more moves

This commit is contained in:
Deukhoofd 2025-03-08 14:39:50 +01:00
parent 8f262cb4a6
commit 77f1ab243b
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
33 changed files with 935 additions and 57 deletions

View File

@ -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<StringKey, object?>();
return new SecondaryEffectImpl(chance, name, parameters);

View File

@ -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<string, JsonNode>? Parameters { get; set; } = null!;
}

View File

@ -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
{

View File

@ -317,6 +317,11 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// </summary>
void Damage(uint damage, DamageSource source, EventBatchId batchId = default);
/// <summary>
/// Forces the Pokémon to faint.
/// </summary>
void Faint(DamageSource source, EventBatchId batchId = default);
/// <summary>
/// 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
}
}
/// <inheritdoc />
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;

View File

@ -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.
/// </summary>
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)
{
}
}

View File

@ -13,6 +13,11 @@ public interface IReadOnlyTypeLibrary
/// </summary>
bool TryGetTypeIdentifier(StringKey key, out TypeIdentifier typeIdentifier);
/// <summary>
/// Gets the type identifier for a type with an index.
/// </summary>
bool TryGetTypeIdentifierFromIndex(byte index, [MaybeNullWhen(false)] out TypeIdentifier typeIdentifier);
/// <summary>
/// Gets the effectiveness for a single attacking type against a single defending type.
/// </summary>
@ -46,6 +51,18 @@ public class TypeLibrary : IReadOnlyTypeLibrary
return false;
}
/// <inheritdoc />
public bool TryGetTypeIdentifierFromIndex(byte index, out TypeIdentifier typeIdentifier)
{
if (index < 1 || index > _types.Count)
{
typeIdentifier = default;
return false;
}
typeIdentifier = _types[index - 1];
return true;
}
/// <inheritdoc />
public float GetSingleEffectiveness(TypeIdentifier attacking, TypeIdentifier defending)
{

View File

@ -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",

View File

@ -10,14 +10,15 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="Moq" Version="4.20.70"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
<PackageReference Include="TUnit" Version="0.5.18" />
<PackageReference Include="TUnit" Version="0.5.18"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj" />
<ProjectReference Include="..\PkmnLib.Plugin.Gen7\PkmnLib.Plugin.Gen7.csproj" />
<ProjectReference Include="..\..\PkmnLib.Dataloader\PkmnLib.Dataloader.csproj"/>
<ProjectReference Include="..\..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj"/>
<ProjectReference Include="..\PkmnLib.Plugin.Gen7\PkmnLib.Plugin.Gen7.csproj"/>
</ItemGroup>
</Project>

View File

@ -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)
{
/// <inheritdoc />
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<Func<TestCaseData>> 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<IExecutingMove>(MockBehavior.Strict);
var user = new Mock<IPokemon>(MockBehavior.Strict);
var target = new Mock<IPokemon>(MockBehavior.Strict);
var dynamicLibrary = new Mock<IDynamicLibrary>(MockBehavior.Strict);
var staticLibrary = new Mock<IStaticLibrary>(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<IExecutingMove>(MockBehavior.Strict);
var user = new Mock<IPokemon>(MockBehavior.Strict);
var target = new Mock<IPokemon>(MockBehavior.Strict);
var dynamicLibrary = new Mock<IDynamicLibrary>(MockBehavior.Strict);
var staticLibrary = new Mock<IStaticLibrary>(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);
}
}

View File

@ -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));
}
}

View File

@ -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
{
/// <inheritdoc />
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<Confusion>();
if (pokemon.Volatile.Contains(confusion))
pokemon.Volatile.Remove(confusion);
}
}
}

View File

@ -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
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.Add(new HealBlockEffect());
}
}

View File

@ -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;
/// <inheritdoc />
public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
{
if (parameters?.TryGetValue("healPercent", out var variable) == true && variable is float healPercent)
{
_healPercent = healPercent;
}
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Heal((uint)(move.User.BoostedStats.Hp * _healPercent));
}
}

View File

@ -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
{
/// <inheritdoc />
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);
}
}

View File

@ -0,0 +1,26 @@
using System;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "heart_swap")]
public class HeartSwap : Script
{
/// <inheritdoc />
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);
}
}
}

View File

@ -0,0 +1,19 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "heat_crash")]
public class HeatCrash : Script
{
/// <inheritdoc />
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,
};
}
}

View File

@ -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
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) =>
target.Volatile.Add(new HelpingHandEffect());
}

View File

@ -0,0 +1,16 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "hex")]
public class Hex : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower)
{
if (!target.StatusScript.IsEmpty)
{
basePower = basePower.MultiplyOrMax(2);
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "hidden_power")]
public class HiddenPower : Script
{
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
}
/// <summary>
/// Helper method to calculate the hidden power value from the IVs.
/// This is used to determine the type and power of the move.
/// </summary>
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);
}

View File

@ -0,0 +1,18 @@
using System;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "high_jump_kick")]
public class HighJumpKick : Script
{
/// <inheritdoc />
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);
}
}

View File

@ -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
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var protectionScripts = target.Volatile.Select(x => x.Script).OfType<ProtectionEffectScript>();
foreach (var protectionScript in protectionScripts.ToList())
{
target.Volatile.Remove(protectionScript.Name);
}
}
}

View File

@ -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
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower)
{
var userEffect = move.User.Volatile.Get<IceBallEffect>();
if (userEffect == null)
return;
basePower = basePower.MultiplyOrMax((byte)Math.Pow(2, userEffect.TurnCount));
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var userEffect = move.User.Volatile.Get<IceBallEffect>();
if (userEffect == null)
{
userEffect = new IceBallEffect(move.User, move.UseMove.Name);
move.User.Volatile.Add(userEffect);
}
else
{
userEffect.TurnCount++;
}
}
}

View File

@ -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<RequireChargeEffect>
{
/// <inheritdoc />
public override RequireChargeEffect CreateVolatile(IPokemon user) => new(user, "ice_burn");
/// <inheritdoc />
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");
}
}
}

View File

@ -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
{
/// <inheritdoc />
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());
}
}
}

View File

@ -0,0 +1,13 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "imprison")]
public class Imprison : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.Add(new ImprisonEffect(move.User));
}
}

View File

@ -0,0 +1,16 @@
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "incinerate")]
public class Incinerate : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (target.HeldItem is { Category: ItemCategory.Berry })
{
target.RemoveHeldItemForBattle();
}
}
}

View File

@ -0,0 +1,29 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "infestation")]
public class Infestation : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var infestationEffect = ScriptUtils.ResolveName<InfestationEffect>();
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));
}
}

View File

@ -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;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
_duration--;
if (_duration <= 0)
RemoveSelf();
}
/// <inheritdoc />
public override void PreventMoveSelection(IMoveChoice choice, ref bool prevent)
{
if (choice.ChosenMove.MoveData.HasFlag("heal"))
prevent = true;
}
/// <inheritdoc />
public override void PreventMove(IExecutingMove move, ref bool prevent)
{
if (move.ChosenMove.MoveData.HasFlag("heal"))
prevent = true;
}
/// <inheritdoc />
public override void PreventHeal(IPokemon pokemon, uint heal, bool allowRevive, ref bool prevented)
{
prevented = true;
}
}

View File

@ -0,0 +1,13 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
public class HelpingHandEffect : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) =>
basePower = basePower.MultiplyOrMax(1.5f);
/// <inheritdoc />
public override void OnEndTurn(IBattle battle) => RemoveSelf();
}

View File

@ -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;
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
public override void OnMoveMiss(IExecutingMove move, IPokemon target)
{
RemoveSelf();
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
if (TurnCount < 5)
TurnCount++;
else
RemoveSelf();
}
}

View File

@ -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;
}
/// <inheritdoc />
public override void PreventMoveSelection(IMoveChoice choice, ref bool prevent)
{
if (_user.Moves.WhereNotNull().Any(x => x.MoveData.Name == choice.ChosenMove.MoveData.Name))
prevent = true;
}
}

View File

@ -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;
}
/// <inheritdoc />
public override void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent) => prevent = true;
/// <inheritdoc />
public override void PreventSelfRunAway(IFleeChoice choice, ref bool prevent) => prevent = true;
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
var damage = _owner.BoostedStats.Hp / 8;
_owner.Damage(damage, DamageSource.Misc);
_turns--;
if (_turns <= 0)
{
RemoveSelf();
}
}
}

View File

@ -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;
}
/// <inheritdoc />
public override void OnSwitchIn(IPokemon pokemon, byte position)
{
if (position == _position)
{
pokemon.Heal(pokemon.BoostedStats.Hp);
pokemon.ClearStatus();
RemoveSelf();
}
}
}