More move effects

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

View File

@@ -54,6 +54,7 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator
var accuracyModifier = 1.0f;
executingMove.RunScriptHook(x => x.ChangeAccuracyModifier(executingMove, target, hitIndex, ref accuracyModifier));
var modifiedAccuracy = (int)(moveAccuracy * accuracyModifier);
executingMove.RunScriptHook(x => x.ChangeAccuracy(executingMove, target, hitIndex, ref modifiedAccuracy));
var targetEvasion = target.StatBoost.Evasion;
var ignoreEvasion = false;
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)
{
var user = move.User;
var userHeldItem = user.HeldItem;
var userHeldItem = user.RemoveHeldItemForBattle();
var targetHeldItem = target.HeldItem;
if (userHeldItem == null || targetHeldItem != null)

View File

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

View File

@@ -76,4 +76,12 @@ public class ChangeTargetSpeed : ChangeTargetStats
public ChangeTargetSpeed() : base(Statistic.Speed)
{
}
}
[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;
if (move.User.HeldItem != null)
return;
_ = move.User.SetHeldItem(target.RemoveHeldItem());
_ = move.User.SetHeldItem(target.RemoveHeldItemForBattle());
}
}

View File

@@ -19,11 +19,10 @@ public class Dig : Script
}));
prevent = true;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
public override void OnBeforeMove(IExecutingMove move)
{
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
}