More move effects

This commit is contained in:
Deukhoofd 2025-03-02 14:03:51 +01:00
parent 9b0ac36597
commit c0bc905c46
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
40 changed files with 804 additions and 46 deletions

View File

@ -4,6 +4,7 @@ using System.Collections.Immutable;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using JetBrains.Annotations;
using PkmnLib.Dataloader.Models; using PkmnLib.Dataloader.Models;
using PkmnLib.Static; using PkmnLib.Static;
using PkmnLib.Static.Libraries; using PkmnLib.Static.Libraries;
@ -26,12 +27,14 @@ public static class ItemDataLoader
return library; return library;
} }
// ReSharper disable once MemberCanBePrivate.Global public delegate IItem ItemFactoryDelegate(SerializedItem serialized, StringKey name, ItemCategory type,
public static Func<SerializedItem, StringKey, ItemCategory, BattleItemCategory, int, BattleItemCategory battleType, int price, ImmutableHashSet<StringKey> flags,
IEnumerable<StringKey>, ISecondaryEffect?, ISecondaryEffect?, ISecondaryEffect? effect, ISecondaryEffect? battleTriggerEffect, byte flingPower);
// ReSharper disable once FieldCanBeMadeReadOnly.Global
IItem> ItemConstructor = (_, name, type, battleType, price, flags, effect, battleTriggerEffect) => [PublicAPI]
new ItemImpl(name, type, battleType, price, flags, effect, battleTriggerEffect); public static ItemFactoryDelegate ItemConstructor { get; set; } = (_, name, type, battleType, price, flags, effect,
battleTriggerEffect, flingPower) =>
new ItemImpl(name, type, battleType, price, flags, effect, battleTriggerEffect, flingPower);
private static IItem DeserializeItem(SerializedItem serialized) private static IItem DeserializeItem(SerializedItem serialized)
{ {
@ -42,6 +45,7 @@ public static class ItemDataLoader
var battleTriggerEffect = serialized.BattleEffect?.ParseEffect(); var battleTriggerEffect = serialized.BattleEffect?.ParseEffect();
return ItemConstructor(serialized, serialized.Name, itemType, battleType, serialized.Price, return ItemConstructor(serialized, serialized.Name, itemType, battleType, serialized.Price,
serialized.Flags.Select(x => (StringKey)x).ToImmutableHashSet(), effect, battleTriggerEffect); serialized.Flags.Select(x => (StringKey)x).ToImmutableHashSet(), effect, battleTriggerEffect,
serialized.FlingPower);
} }
} }

View File

@ -14,4 +14,10 @@
<ProjectReference Include="..\PkmnLib.Static\PkmnLib.Static.csproj" /> <ProjectReference Include="..\PkmnLib.Static\PkmnLib.Static.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Reference Include="JetBrains.Annotations">
<HintPath>..\..\..\..\.nuget\packages\jetbrains.annotations\2024.2.0\lib\netstandard2.0\JetBrains.Annotations.dll</HintPath>
</Reference>
</ItemGroup>
</Project> </Project>

View File

@ -241,6 +241,20 @@ public interface IPokemon : IScriptSource, IDeepCloneable
[MustUseReturnValue] [MustUseReturnValue]
IItem? RemoveHeldItem(); IItem? RemoveHeldItem();
/// <summary>
/// Removes the held item from the Pokemon for the duration of the battle. Returns the previously held item.
/// </summary>
/// <remarks>
/// This is used for moves that remove a held item, but do not consume it. In this case, the item needs to be
/// restored after the battle.
/// </remarks>
IItem? RemoveHeldItemForBattle();
/// <summary>
/// Restores the held item of a Pokémon if it was temporarily removed.
/// </summary>
void RestoreStolenHeldItem();
/// <summary> /// <summary>
/// Makes the Pokemon uses its held item. Returns whether the item was consumed. /// Makes the Pokemon uses its held item. Returns whether the item was consumed.
/// </summary> /// </summary>
@ -717,6 +731,21 @@ public class PokemonImpl : ScriptSource, IPokemon
return previous; return previous;
} }
private IItem? _stolenHeldItem;
/// <inheritdoc />
public IItem? RemoveHeldItemForBattle()
{
return _stolenHeldItem = RemoveHeldItem();
}
/// <inheritdoc />
public void RestoreStolenHeldItem()
{
_ = SetHeldItem(_stolenHeldItem);
_stolenHeldItem = null;
}
/// <inheritdoc /> /// <inheritdoc />
public bool ConsumeHeldItem() public bool ConsumeHeldItem()
{ {

View File

@ -6,7 +6,7 @@ namespace PkmnLib.Dynamic.ScriptHandling.Registry;
/// <summary> /// <summary>
/// Helper attribute to register scripts through reflection. /// Helper attribute to register scripts through reflection.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class, Inherited = false)]
[MeansImplicitUse] [MeansImplicitUse]
public class ScriptAttribute : Attribute public class ScriptAttribute : Attribute
{ {

View File

@ -554,4 +554,8 @@ public abstract class Script : IDeepCloneable
public virtual void ChangeIncomingDamage(IPokemon pokemon, DamageSource source, ref uint damage) public virtual void ChangeIncomingDamage(IPokemon pokemon, DamageSource source, ref uint damage)
{ {
} }
public virtual void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref int modifiedAccuracy)
{
}
} }

View File

@ -109,6 +109,8 @@ public interface IItem : INamedValue
ISecondaryEffect? Effect { get; } ISecondaryEffect? Effect { get; }
ISecondaryEffect? BattleEffect { get; } ISecondaryEffect? BattleEffect { get; }
byte FlingPower { get; }
/// <summary> /// <summary>
/// Checks whether the item has a specific flag. /// Checks whether the item has a specific flag.
/// </summary> /// </summary>
@ -122,7 +124,7 @@ public class ItemImpl : IItem
{ {
/// <inheritdoc cref="ItemImpl"/> /// <inheritdoc cref="ItemImpl"/>
public ItemImpl(StringKey name, ItemCategory category, BattleItemCategory battleCategory, int price, public ItemImpl(StringKey name, ItemCategory category, BattleItemCategory battleCategory, int price,
IEnumerable<StringKey> flags, ISecondaryEffect? effect, ISecondaryEffect? battleTriggerEffect) IEnumerable<StringKey> flags, ISecondaryEffect? effect, ISecondaryEffect? battleTriggerEffect, byte flingPower)
{ {
Name = name; Name = name;
Category = category; Category = category;
@ -130,6 +132,7 @@ public class ItemImpl : IItem
Price = price; Price = price;
Effect = effect; Effect = effect;
BattleEffect = battleTriggerEffect; BattleEffect = battleTriggerEffect;
FlingPower = flingPower;
Flags = [..flags]; Flags = [..flags];
} }
@ -154,6 +157,10 @@ public class ItemImpl : IItem
/// <inheritdoc /> /// <inheritdoc />
public ISecondaryEffect? BattleEffect { get; } public ISecondaryEffect? BattleEffect { get; }
/// <inheritdoc />
public byte FlingPower { get; }
/// <inheritdoc /> /// <inheritdoc />
public bool HasFlag(string key) public bool HasFlag(string key)
{ {

View File

@ -27,7 +27,7 @@ public interface IReadOnlyTypeLibrary
/// Gets the effectiveness for a single attacking type against an amount of defending types. /// Gets the effectiveness for a single attacking type against an amount of defending types.
/// This is equivalent to running get_single_effectiveness on each defending type, and multiplying the results with each other. /// This is equivalent to running get_single_effectiveness on each defending type, and multiplying the results with each other.
/// </summary> /// </summary>
float GetEffectiveness(TypeIdentifier attacking, IReadOnlyList<TypeIdentifier> defending); float GetEffectiveness(TypeIdentifier attacking, IEnumerable<TypeIdentifier> defending);
IEnumerable<(TypeIdentifier type, float effectiveness)> GetAllEffectivenessFromAttacking(TypeIdentifier attacking); IEnumerable<(TypeIdentifier type, float effectiveness)> GetAllEffectivenessFromAttacking(TypeIdentifier attacking);
} }
@ -68,7 +68,7 @@ public class TypeLibrary : IReadOnlyTypeLibrary
} }
/// <inheritdoc /> /// <inheritdoc />
public float GetEffectiveness(TypeIdentifier attacking, IReadOnlyList<TypeIdentifier> defending) => public float GetEffectiveness(TypeIdentifier attacking, IEnumerable<TypeIdentifier> defending) =>
defending.Aggregate<TypeIdentifier, float>(1, defending.Aggregate<TypeIdentifier, float>(1,
(current, type) => current * GetSingleEffectiveness(attacking, type)); (current, type) => current * GetSingleEffectiveness(attacking, type));

View File

@ -3614,7 +3614,6 @@
} }
} }
}, },
// Done up to here
{ {
"name": "fire_spin", "name": "fire_spin",
"type": "fire", "type": "fire",
@ -3627,7 +3626,10 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "fire_spin"
}
}, },
{ {
"name": "first_impression", "name": "first_impression",
@ -3642,7 +3644,10 @@
"contact", "contact",
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "first_impression"
}
}, },
{ {
"name": "fissure", "name": "fissure",
@ -3657,7 +3662,10 @@
"protect", "protect",
"mirror", "mirror",
"nonskybattle" "nonskybattle"
] ],
"effect": {
"name": "one_hit_ko"
}
}, },
{ {
"name": "flail", "name": "flail",
@ -3672,7 +3680,10 @@
"contact", "contact",
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "flail"
}
}, },
{ {
"name": "flame_burst", "name": "flame_burst",
@ -3686,7 +3697,10 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "flame_burst"
}
}, },
{ {
"name": "flame_charge", "name": "flame_charge",
@ -3701,7 +3715,13 @@
"contact", "contact",
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "change_user_speed",
"parameters": {
"amount": 1
}
}
}, },
{ {
"name": "flame_wheel", "name": "flame_wheel",
@ -3717,7 +3737,14 @@
"protect", "protect",
"mirror", "mirror",
"defrost" "defrost"
] ],
"effect": {
"name": "set_status",
"chance": 10,
"parameters": {
"status": "burned"
}
}
}, },
{ {
"name": "flamethrower", "name": "flamethrower",
@ -3731,7 +3758,14 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "set_status",
"chance": 10,
"parameters": {
"status": "burned"
}
}
}, },
{ {
"name": "flare_blitz", "name": "flare_blitz",
@ -3747,7 +3781,10 @@
"protect", "protect",
"mirror", "mirror",
"defrost" "defrost"
] ],
"effect": {
"name": "flare_blitz"
}
}, },
{ {
"name": "flash", "name": "flash",
@ -3762,7 +3799,13 @@
"protect", "protect",
"reflectable", "reflectable",
"mirror" "mirror"
] ],
"effect": {
"name": "change_target_accuracy",
"parameters": {
"amount": -1
}
}
}, },
{ {
"name": "flash_cannon", "name": "flash_cannon",
@ -3776,7 +3819,14 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "change_target_special_defense",
"chance": 10,
"parameters": {
"amount": -1
}
}
}, },
{ {
"name": "flatter", "name": "flatter",
@ -3791,7 +3841,10 @@
"protect", "protect",
"reflectable", "reflectable",
"mirror" "mirror"
] ],
"effect": {
"name": "flatter"
}
}, },
{ {
"name": "fleur_cannon", "name": "fleur_cannon",
@ -3805,7 +3858,13 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "change_user_special_attack",
"parameters": {
"amount": -2
}
}
}, },
{ {
"name": "fling", "name": "fling",
@ -3819,7 +3878,10 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "fling"
}
}, },
{ {
"name": "floral_healing", "name": "floral_healing",
@ -3834,7 +3896,10 @@
"protect", "protect",
"reflectable", "reflectable",
"heal" "heal"
] ],
"effect": {
"name": "floral_healing"
}
}, },
{ {
"name": "flower_shield", "name": "flower_shield",
@ -3847,7 +3912,10 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"distance" "distance"
] ],
"effect": {
"name": "flower_shield"
}
}, },
{ {
"name": "fly", "name": "fly",
@ -3865,7 +3933,10 @@
"mirror", "mirror",
"gravity", "gravity",
"distance" "distance"
] ],
"effect": {
"name": "fly"
}
}, },
{ {
"name": "flying_press", "name": "flying_press",
@ -3883,7 +3954,10 @@
"gravity", "gravity",
"distance", "distance",
"nonskybattle" "nonskybattle"
] ],
"effect": {
"name": "flying_press"
}
}, },
{ {
"name": "focus_blast", "name": "focus_blast",
@ -3898,7 +3972,14 @@
"protect", "protect",
"mirror", "mirror",
"ballistics" "ballistics"
] ],
"effect": {
"name": "change_target_special_defense",
"chance": 10,
"parameters": {
"amount": -1
}
}
}, },
{ {
"name": "focus_energy", "name": "focus_energy",
@ -3911,7 +3992,10 @@
"category": "status", "category": "status",
"flags": [ "flags": [
"snatch" "snatch"
] ],
"effect": {
"name": "focus_energy"
}
}, },
{ {
"name": "focus_punch", "name": "focus_punch",
@ -3926,7 +4010,10 @@
"contact", "contact",
"protect", "protect",
"punch" "punch"
] ],
"effect": {
"name": "focus_punch"
}
}, },
{ {
"name": "follow_me", "name": "follow_me",
@ -3937,7 +4024,10 @@
"priority": 2, "priority": 2,
"target": "Self", "target": "Self",
"category": "status", "category": "status",
"flags": [] "flags": [],
"effect": {
"name": "follow_me"
}
}, },
{ {
"name": "force_palm", "name": "force_palm",
@ -3952,7 +4042,14 @@
"contact", "contact",
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "set_status",
"chance": 30,
"parameters": {
"status": "paralyzed"
}
}
}, },
{ {
"name": "foresight", "name": "foresight",
@ -3968,7 +4065,10 @@
"reflectable", "reflectable",
"mirror", "mirror",
"ignore-substitute" "ignore-substitute"
] ],
"effect": {
"name": "foresight"
}
}, },
{ {
"name": "forests_curse", "name": "forests_curse",
@ -3983,7 +4083,10 @@
"protect", "protect",
"reflectable", "reflectable",
"mirror" "mirror"
] ],
"effect": {
"name": "forests_curse"
}
}, },
{ {
"name": "foul_play", "name": "foul_play",
@ -3998,7 +4101,10 @@
"contact", "contact",
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "foul_play"
}
}, },
{ {
"name": "freeze_dry", "name": "freeze_dry",
@ -4012,7 +4118,10 @@
"flags": [ "flags": [
"protect", "protect",
"mirror" "mirror"
] ],
"effect": {
"name": "freeze_dry"
}
}, },
{ {
"name": "freeze_shock", "name": "freeze_shock",

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>

View File

@ -54,6 +54,7 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator
var accuracyModifier = 1.0f; var accuracyModifier = 1.0f;
executingMove.RunScriptHook(x => x.ChangeAccuracyModifier(executingMove, target, hitIndex, ref accuracyModifier)); executingMove.RunScriptHook(x => x.ChangeAccuracyModifier(executingMove, target, hitIndex, ref accuracyModifier));
var modifiedAccuracy = (int)(moveAccuracy * accuracyModifier); var modifiedAccuracy = (int)(moveAccuracy * accuracyModifier);
executingMove.RunScriptHook(x => x.ChangeAccuracy(executingMove, target, hitIndex, ref modifiedAccuracy));
var targetEvasion = target.StatBoost.Evasion; var targetEvasion = target.StatBoost.Evasion;
var ignoreEvasion = false; var ignoreEvasion = false;
executingMove.RunScriptHook(x => x.BypassEvasionStatBoosts(executingMove, target, hitIndex, ref ignoreEvasion)); executingMove.RunScriptHook(x => x.BypassEvasionStatBoosts(executingMove, target, hitIndex, ref ignoreEvasion));

View File

@ -7,7 +7,7 @@ public class Bestow : Script
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
var user = move.User; var user = move.User;
var userHeldItem = user.HeldItem; var userHeldItem = user.RemoveHeldItemForBattle();
var targetHeldItem = target.HeldItem; var targetHeldItem = target.HeldItem;
if (userHeldItem == null || targetHeldItem != null) if (userHeldItem == null || targetHeldItem != null)

View File

@ -20,6 +20,12 @@ public class Bounce : Script
prevent = true; prevent = true;
} }
/// <inheritdoc />
public override void OnBeforeMove(IExecutingMove move)
{
move.User.Volatile.Remove(ScriptUtils.ResolveName<ChargeBounceEffect>());
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
@ -31,6 +37,5 @@ public class Bounce : Script
{ {
target.SetStatus("paralyzed"); target.SetStatus("paralyzed");
} }
move.User.Volatile.Remove(ScriptUtils.ResolveName<ChargeBounceEffect>());
} }
} }

View File

@ -77,3 +77,11 @@ public class ChangeTargetSpeed : ChangeTargetStats
{ {
} }
} }
[Script(ScriptCategory.Move, "change_target_accuracy")]
public class ChangeTargetAccuracy : ChangeTargetStats
{
public ChangeTargetAccuracy() : base(Statistic.Accuracy)
{
}
}

View File

@ -10,6 +10,6 @@ public class Covet : Script
return; return;
if (move.User.HeldItem != null) if (move.User.HeldItem != null)
return; return;
_ = move.User.SetHeldItem(target.RemoveHeldItem()); _ = move.User.SetHeldItem(target.RemoveHeldItemForBattle());
} }
} }

View File

@ -21,9 +21,8 @@ public class Dig : Script
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnBeforeMove(IExecutingMove move)
{ {
move.User.Volatile.Remove(ScriptUtils.ResolveName<DigEffect>()); move.User.Volatile.Remove(ScriptUtils.ResolveName<DigEffect>());
} }
} }

View File

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

View File

@ -0,0 +1,14 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "first_impression")]
public class FirstImpression : Script
{
public override void StopBeforeMove(IExecutingMove move, ref bool stop)
{
var battleData = move.User.BattleData;
if (battleData == null)
return;
if (battleData.SwitchInTurn != battleData.Battle.CurrentTurnNumber)
stop = true;
}
}

View File

@ -0,0 +1,21 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "flail")]
public class Flail : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower)
{
var remainingHealth = move.User.CurrentHealth / move.User.BoostedStats.Hp;
var fraction = remainingHealth * 48;
basePower = fraction switch
{
< 2 => 200,
< 5 => 150,
< 10 => 100,
< 17 => 80,
< 33 => 40,
_ => 20
};
}
}

View File

@ -0,0 +1,43 @@
using System.Collections.Generic;
using System.Linq;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "flame_burst")]
public class FlameBurst : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var adjacentFoes = GetAdjacentFoes(move.User).WhereNotNull();
EventBatchId batchId = new();
foreach (var adjacentFoe in adjacentFoes)
{
adjacentFoe.Damage(adjacentFoe.BoostedStats.Hp / 16, DamageSource.Misc, batchId);
}
}
private static IEnumerable<IPokemon?> GetAdjacentFoes(IPokemon pokemon)
{
var battleData = pokemon.BattleData;
if (battleData == null)
yield break;
if (battleData.Battle.PositionsPerSide == 1)
yield break;
var position = battleData.Position;
var side = battleData.Battle.Sides[battleData.SideIndex];
if (position == 0)
{
yield return side.Pokemon[1];
}
else
{
yield return side.Pokemon[position - 1];
if (position < side.Pokemon.Count - 1)
yield return side.Pokemon[position + 1];
}
}
}

View File

@ -0,0 +1,23 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "flare_blitz")]
public class FlareBlitz : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = target.BattleData;
if (battleData == null)
return;
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{
target.SetStatus("burned");
}
var hitData = move.GetHitData(target, hit);
var recoilDamage = (uint)(hitData.Damage * (1 / 3));
move.User.Damage(recoilDamage, DamageSource.Misc);
}
}

View File

@ -0,0 +1,15 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "flatter")]
public class Flatter : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.ChangeStatBoost(Statistic.SpecialAttack, 1, false);
target.Volatile.StackOrAdd("confusion", () => new Confusion());
}
}

View File

@ -0,0 +1,31 @@
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "fling")]
public class Fling : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower)
{
var item = move.User.HeldItem;
if (item == null)
{
move.GetHitData(target, hit).Fail();
return;
}
if (item.Category is ItemCategory.FormChanger or ItemCategory.Pokeball or ItemCategory.Mail
or ItemCategory.KeyItem or ItemCategory.TmHm)
{
move.GetHitData(target, hit).Fail();
return;
}
basePower = item.FlingPower;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) =>
move.User.RemoveHeldItemForBattle();
}

View File

@ -0,0 +1,24 @@
using PkmnLib.Plugin.Gen7.Scripts.Terrain;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "floral_healing")]
public class FloralHealing : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (target.IsFainted)
return;
var battleData = target.BattleData;
if (battleData == null)
return;
var modifier = 1f / 2;
if (battleData.Battle.TerrainName == ScriptUtils.ResolveName<GrassyTerrain>())
modifier = 2f / 3;
var healing = target.BoostedStats.Hp * modifier;
target.Heal((uint)healing);
}
}

View File

@ -0,0 +1,32 @@
using System.Linq;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "flower_shield")]
public class FlowerShield : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = target.BattleData;
if (battleData == null)
return;
if (!battleData.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("grass", out var grassType))
return;
var batchId = new EventBatchId();
var sides = battleData.Battle.Sides;
foreach (var side in sides)
{
foreach (var pokemon in side.Pokemon)
{
if (pokemon == null || pokemon.IsFainted)
continue;
if (!pokemon.Types.Contains(grassType))
continue;
pokemon.ChangeStatBoost(Statistic.Defense, 1, pokemon == move.User, batchId);
}
}
}
}

View File

@ -0,0 +1,28 @@
using System.Collections.Generic;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "fly")]
public class Fly : Script
{
/// <inheritdoc />
public override void PreventMove(IExecutingMove move, ref bool prevent)
{
if (move.User.Volatile.Contains<ChargeFlyEffect>())
return;
move.User.Volatile.Add(new ChargeFlyEffect(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("fly_charge", new Dictionary<string, object>()
{
{ "user", move.User }
}));
prevent = true;
}
/// <inheritdoc />
public override void OnBeforeMove(IExecutingMove move)
{
move.User.Volatile.Remove(ScriptUtils.ResolveName<ChargeFlyEffect>());
}
}

View File

@ -0,0 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "flying_press")]
public class FlyingPress : Script
{
/// <inheritdoc />
public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
{
var battleData = move.User.BattleData;
if (battleData == null)
return;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
// If flying type is not found, return
if (!typeLibrary.TryGetTypeIdentifier("flying", out var flyingType))
return;
var flyingEffectiveness = typeLibrary.GetEffectiveness(flyingType, target.Types);
effectiveness *= flyingEffectiveness;
}
}

View File

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

View File

@ -0,0 +1,29 @@
using System.Collections.Generic;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "focus_punch")]
public class FocusPunch : Script
{
/// <inheritdoc />
public override void OnBeforeTurnStart(ITurnChoice choice)
{
choice.User.Volatile.Add(new FocusPunchEffect());
choice.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("focus_punch_charge",
new Dictionary<string, object>()
{
{ "pokemon", choice.User }
}));
}
/// <inheritdoc />
public override void PreventMove(IExecutingMove move, ref bool prevent)
{
var focusPunchEffect = move.User.Volatile.Get<FocusPunchEffect>();
if (focusPunchEffect == null || focusPunchEffect.WasHit)
{
prevent = true;
}
}
}

View File

@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "follow_me")]
public class FollowMe : Script
{
/// <inheritdoc />
public override void ChangeTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
{
if (targets.Count != 1)
return;
var target = targets[0];
if (target == null)
return;
if (target.BattleData?.SideIndex != moveChoice.User.BattleData?.SideIndex)
return;
targets = [moveChoice.User];
}
}

View File

@ -0,0 +1,19 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "foresight")]
public class Foresight : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = target.BattleData;
if (battleData == null)
return;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
target.Volatile.Add(new ForesightEffect(typeLibrary));
target.ChangeStatBoost(Statistic.Evasion, (sbyte)-target.StatBoost.Evasion, false);
}
}

View File

@ -0,0 +1,17 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "forests_curse")]
public class ForestsCurse : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = target.BattleData;
if (battleData == null)
return;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
if (!typeLibrary.TryGetTypeIdentifier("grass", out var grassType))
return;
target.AddType(grassType);
}
}

View File

@ -0,0 +1,15 @@
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "foul_play")]
public class FoulPlay : Script
{
/// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, ref uint value)
{
value = move.UseMove.Category == MoveCategory.Physical
? target.BoostedStats.Attack
: target.BoostedStats.SpecialAttack;
}
}

View File

@ -0,0 +1,41 @@
using System.Linq;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "freeze_dry")]
public class FreezeDry : Script
{
/// <inheritdoc />
public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
{
var battleData = target.BattleData;
if (battleData == null)
return;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
if (!typeLibrary.TryGetTypeIdentifier("water", out var waterType))
return;
if (target.Types.Contains(waterType))
{
var effectivenessWithoutWater = target.Types
.Where(x => x != waterType)
.Select(x => typeLibrary.GetEffectiveness(x, target.Types))
.Aggregate(1f, (a, b) => a * b);
effectiveness = effectivenessWithoutWater * 2;
}
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = target.BattleData;
if (battleData == null)
return;
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{
target.SetStatus("frozen");
}
}
}

View File

@ -0,0 +1,25 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "one_hit_ko")]
public class OneHitKo : Script
{
/// <inheritdoc />
public override void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref int modifiedAccuracy)
{
var levelDifference = executingMove.User.Level - target.Level;
if (levelDifference < 0)
{
executingMove.GetHitData(target, hitIndex).Fail();
return;
}
modifiedAccuracy = 30 + levelDifference;
}
/// <inheritdoc />
public override void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
damage = target.BoostedStats.Hp.MultiplyOrMax(10);
}
}

View File

@ -0,0 +1,35 @@
using PkmnLib.Plugin.Gen7.Scripts.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "charge_fly")]
public class ChargeFlyEffect : Script
{
private readonly IPokemon _owner;
public ChargeFlyEffect(IPokemon owner)
{
_owner = owner;
}
/// <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, "fly", opposingSideIndex, position);
}
/// <inheritdoc />
public override void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{
if (!executingMove.UseMove.HasFlag("hit_flying"))
block = true;
}
/// <inheritdoc />
public override void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
if (!move.UseMove.HasFlag("effective_against_fly"))
damage *= 2;
}
}

View File

@ -0,0 +1,24 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "fire_spin")]
public class FireSpinEffect : Script
{
private readonly IPokemon _owner;
public FireSpinEffect(IPokemon owner)
{
_owner = owner;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
_owner.Damage(_owner.BoostedStats.Hp / 8, DamageSource.Misc);
}
/// <inheritdoc />
public override void PreventSelfRunAway(IFleeChoice choice, ref bool prevent) => prevent = true;
/// <inheritdoc />
public override void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent) => prevent = true;
}

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "focus_punch")]
public class FocusPunchEffect : Script
{
public bool WasHit { get; private set; }
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
WasHit = true;
target.BattleData?.Battle.EventHook.Invoke(new DialogEvent("focus_punch_lost_focus",
new Dictionary<string, object>()
{
{ "pokemon", target }
}));
}
}

View File

@ -0,0 +1,40 @@
using System.Linq;
using PkmnLib.Static;
using PkmnLib.Static.Libraries;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "foresight")]
public class ForesightEffect : Script
{
private readonly IReadOnlyTypeLibrary _typeLibrary;
private readonly TypeIdentifier _normalType;
private readonly TypeIdentifier _fightingType;
private readonly TypeIdentifier _ghostType;
public ForesightEffect(IReadOnlyTypeLibrary typeLibrary)
{
_typeLibrary = typeLibrary;
typeLibrary.TryGetTypeIdentifier("normal", out _normalType);
typeLibrary.TryGetTypeIdentifier("fighting", out _fightingType);
typeLibrary.TryGetTypeIdentifier("ghost", out _ghostType);
}
/// <inheritdoc />
public override void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted, ref bool prevent)
{
if (stat == Statistic.Evasion)
prevent = true;
}
/// <inheritdoc />
public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
{
var hitData = move.GetHitData(target, hit);
if (hitData.Type == _normalType && target.Types.Contains(_fightingType))
effectiveness = _typeLibrary.GetEffectiveness(_normalType, target.Types.Where(x => x != _ghostType));
else if (hitData.Type == _fightingType && target.Types.Contains(_ghostType))
effectiveness = _typeLibrary.GetEffectiveness(_fightingType, target.Types.Where(x => x != _ghostType));
}
}

View File

@ -0,0 +1,17 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "increased_critical_stage")]
public class IncreasedCriticalStage : Script
{
/// <inheritdoc />
public override void ChangeCriticalStage(IExecutingMove move, IPokemon target, byte hit, ref byte stage)
{
// Extreme edge case, should never happen
if (stage == byte.MaxValue)
{
move.GetHitData(target, hit).Fail();
return;
}
stage += 1;
}
}

View File

@ -0,0 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Terrain;
[Script(ScriptCategory.Terrain, "grassy_terrain")]
public class GrassyTerrain : Script
{
// TODO: Implement Electric Terrain
}