Many more tests and fixes
All checks were successful
Build / Build (push) Successful in 3m12s

This commit is contained in:
2026-08-27 17:11:12 +02:00
parent 32b3ef9c4a
commit d2a82b5fe3
204 changed files with 20255 additions and 242 deletions

View File

@@ -12,6 +12,11 @@ public abstract class BaseChargeMove<TVolatile> : Script, IScriptPreventMove, IS
if (move.User.Volatile.Contains<TVolatile>())
return;
var args = new CustomTriggers.BypassChargeMoveArgs(move, false);
move.RunScriptHook<IScriptCustomTrigger>(script => script.CustomTrigger(CustomTriggers.BypassChargeMove, args));
if (args.Bypass)
return;
move.User.Volatile.Add(CreateVolatile(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("began_charging", new Dictionary<string, object>
{

View File

@@ -30,7 +30,7 @@ public abstract class ChangeUserStats : Script, IScriptOnInitialize, IScriptOnSe
}
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
public virtual void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
move.User.ChangeStatBoost(_stat, _amount, true, false);
}

View File

@@ -0,0 +1,13 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "defense_curl")]
public class DefenseCurl : ChangeUserDefense
{
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
base.OnSecondaryEffect(move, target, hit);
move.User.Volatile.StackOrAdd(ScriptUtils.ResolveName<DefenseCurlEffect>(), () => new DefenseCurlEffect());
}
}

View File

@@ -1,15 +1,31 @@
using PkmnLib.Plugin.Gen7.Scripts.Status;
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "facade")]
public class Facade : Script, IScriptChangeBasePower
public class Facade : Script, IScriptChangeBasePower, IScriptChangeMoveDamage
{
/// <inheritdoc />
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
{
var status = move.User.StatusScript.Script?.Name;
if (status == "paralyzed" || status == "burned" || status == "poisoned")
if (status == ScriptUtils.ResolveName<Paralyzed>() || status == ScriptUtils.ResolveName<Burned>() ||
status == ScriptUtils.ResolveName<Poisoned>() || status == ScriptUtils.ResolveName<BadlyPoisoned>())
{
basePower = basePower.MultiplyOrMax(2);
}
}
public void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
var status = move.User.StatusScript.Script?.Name;
if (status != ScriptUtils.ResolveName<Burned>())
return;
// Ignore Burn's effect of halving the damage done by physical moves.
if (move.UseMove.Category == MoveCategory.Physical)
{
damage = (uint)(damage * 2f);
}
}
}

View File

@@ -6,7 +6,7 @@ public class FalseSwipe : Script, IScriptChangeMoveDamage
/// <inheritdoc />
public void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
if (target.CurrentHealth - damage < 1)
if ((int)target.CurrentHealth - damage < 1)
{
damage = target.CurrentHealth - 1;
}

View File

@@ -1,4 +1,5 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Plugin.Gen7.Scripts.Side;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
@@ -8,9 +9,26 @@ public class Feint : Script, IScriptOnBeforeHit
/// <inheritdoc />
public void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex)
{
if (target.Volatile.Contains<ProtectionEffectScript>())
var toRemove = new List<ScriptContainer>();
foreach (var script in target.Volatile)
{
target.Volatile.Remove<ProtectionEffectScript>();
if (script.Script is ProtectionEffectScript)
{
toRemove.Add(script);
}
}
foreach (var script in toRemove)
{
script.Clear();
}
var battleData = target.BattleData;
if (battleData is not null)
{
if (battleData.BattleSide.VolatileScripts.Contains<CraftyShieldEffect>())
battleData.BattleSide.VolatileScripts.Remove<CraftyShieldEffect>();
if (battleData.BattleSide.VolatileScripts.Contains<MatBlockEffect>())
battleData.BattleSide.VolatileScripts.Remove<MatBlockEffect>();
}
}
}

View File

@@ -8,7 +8,7 @@ public class FellStinger : Script, IScriptOnAfterHits
{
if (target.IsFainted)
{
move.User.ChangeStatBoost(Statistic.Attack, 2, true, false);
move.User.ChangeStatBoost(Statistic.Attack, 3, true, false);
}
}
}

View File

@@ -3,11 +3,15 @@ using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "fire_spin")]
public class FireSpin : MultiHitMove, IScriptOnSecondaryEffect
public class FireSpin : Script, IScriptOnSecondaryEffect
{
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.StackOrAdd("fire_spin", () => new FireSpinEffect(target));
target.Volatile.StackOrAdd("fire_spin", () =>
{
var turns = target.BattleData!.Battle.Random.GetInt(4, 6);
return new FireSpinEffect(target, turns, move.User);
});
}
}

View File

@@ -6,7 +6,7 @@ public class Flail : Script, IScriptChangeBasePower
/// <inheritdoc />
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
{
var remainingHealth = move.User.CurrentHealth / move.User.BoostedStats.Hp;
var remainingHealth = move.User.CurrentHealth / (float)move.User.BoostedStats.Hp;
var fraction = remainingHealth * 48;
basePower = fraction switch
{

View File

@@ -6,7 +6,7 @@ public class FlameBurst : Script, IScriptOnSecondaryEffect
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var adjacentFoes = GetAdjacentFoes(move.User).WhereNotNull();
var adjacentFoes = GetAdjacentFoes(target).WhereNotNull();
EventBatchId batchId = new();
foreach (var adjacentFoe in adjacentFoes)
{

View File

@@ -11,18 +11,18 @@ public class FlareBlitz : Script, IScriptOnSecondaryEffect
return;
var hitData = move.GetHitData(target, hit);
var recoilDamage = hitData.Damage * (1 / 3);
var triggerArgs = new CustomTriggers.ModifyRecoilArgs(move, target, hit, hitData.Damage, recoilDamage);
move.RunScriptHook<IScriptCustomTrigger>(x => x.CustomTrigger(CustomTriggers.ModifyRecoil, triggerArgs));
if (triggerArgs.Prevent)
return;
var recoilDamage = hitData.Damage * (1 / 3f);
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{
target.SetStatus("burned", move.User);
}
move.User.Damage(recoilDamage, DamageSource.Misc);
var triggerArgs = new CustomTriggers.ModifyRecoilArgs(move, target, hit, hitData.Damage, (uint)recoilDamage);
move.RunScriptHook<IScriptCustomTrigger>(x => x.CustomTrigger(CustomTriggers.ModifyRecoil, triggerArgs));
if (triggerArgs.Prevent)
return;
move.User.Damage((uint)recoilDamage, DamageSource.Misc);
}
}

View File

@@ -19,7 +19,7 @@ public class Fling : Script, IScriptOnSecondaryEffect, IScriptChangeBasePower
move.GetHitData(target, hit).Fail();
return;
}
if (!item.TryGetAdditionalData<byte>("fling_power", out var flingPower))
if (!item.TryGetAdditionalData<byte>("flingPower", out var flingPower))
{
move.GetHitData(target, hit).Fail();
return;

View File

@@ -1,3 +1,5 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "flower_shield")]
@@ -14,6 +16,7 @@ public class FlowerShield : Script, IScriptOnSecondaryEffect
var batchId = new EventBatchId();
var sides = battleData.Battle.Sides;
var anyFound = false;
foreach (var side in sides)
{
foreach (var pokemon in side.Pokemon)
@@ -22,8 +25,13 @@ public class FlowerShield : Script, IScriptOnSecondaryEffect
continue;
if (!pokemon.Types.Contains(grassType))
continue;
if (pokemon.Volatile.Any(x => x.Script?.IsSemiInvulnerableTurn == true))
continue;
pokemon.ChangeStatBoost(Statistic.Defense, 1, pokemon == move.User, false, batchId);
anyFound = true;
}
}
if (!anyFound)
move.GetHitData(target, hit).Fail();
}
}

View File

@@ -6,6 +6,11 @@ public class FocusEnergy : Script, IScriptOnSecondaryEffect
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.Add(new Pokemon.IncreasedCriticalStage());
if (target.Volatile.Contains<Pokemon.FocusEnergyEffect>())
{
move.GetHitData(target, hit).Fail();
return;
}
target.Volatile.Add(new Pokemon.FocusEnergyEffect());
}
}

View File

@@ -1,20 +1,13 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "follow_me")]
public class FollowMe : Script, IScriptChangeTargets
public class FollowMe : Script, IScriptOnSecondaryEffect
{
/// <inheritdoc />
public void ChangeTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
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];
move.User.BattleData!.BattleSide.VolatileScripts.StackOrAdd(ScriptUtils.ResolveName<FollowMeEffect>(),
() => new FollowMeEffect(move.User));
}
}

View File

@@ -11,8 +11,15 @@ public class Foresight : Script, IScriptOnSecondaryEffect
var battleData = target.BattleData;
if (battleData == null)
return;
if (target.StatBoost.Evasion > 0)
target.ChangeStatBoost(Statistic.Evasion, (sbyte)-target.StatBoost.Evasion, false, false);
if (target.Volatile.Contains<ForesightEffect>())
{
move.GetHitData(target, hit).Fail();
return;
}
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
target.Volatile.Add(new ForesightEffect(typeLibrary));
target.ChangeStatBoost(Statistic.Evasion, (sbyte)-target.StatBoost.Evasion, false, false);
}
}

View File

@@ -1,3 +1,5 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "forests_curse")]
@@ -10,8 +12,18 @@ public class ForestsCurse : Script, IScriptOnSecondaryEffect
if (battleData == null)
return;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
if (!typeLibrary.TryGetTypeIdentifier("grass", out var grassType))
if (!typeLibrary.TryGetTypeIdentifier("grass", out var grassType) || target.Types.Contains(grassType))
{
move.GetHitData(target, hit).Fail();
return;
}
if (target.Volatile.TryGet<HasHadTypeAddedEffect>(out var effect))
{
target.RemoveType(effect.TypeIdentifier);
target.Volatile.Remove<HasHadTypeAddedEffect>();
}
target.AddType(grassType);
target.Volatile.Add(new HasHadTypeAddedEffect(grassType));
}
}

View File

@@ -14,10 +14,11 @@ public class FreezeDry : Script, IScriptChangeEffectiveness, IScriptOnSecondaryE
if (!typeLibrary.TryGetTypeIdentifier("water", out var waterType))
return;
if (target.Types.Contains(waterType))
var hitDataType = move.GetHitData(target, hit).Type;
if (target.Types.Contains(waterType) && hitDataType.HasValue)
{
var effectivenessWithoutWater = target.Types.Where(x => x != waterType)
.Select(x => typeLibrary.GetEffectiveness(x, target.Types)).Aggregate(1f, (a, b) => a * b);
.Select(x => typeLibrary.GetSingleEffectiveness(hitDataType.Value, x)).Aggregate(1f, (a, b) => a * b);
effectiveness = effectivenessWithoutWater * 2;
}
}

View File

@@ -15,7 +15,7 @@ public class FuryCutter : Script, IScriptChangeBasePower
return;
}
if (userEffect.TurnCount < 5)
if (userEffect.TurnCount < 2)
userEffect.TurnCount++;
basePower = basePower.MultiplyOrMax((byte)Math.Pow(2, userEffect.TurnCount));
}

View File

@@ -12,11 +12,11 @@ public class FusionBolt : Script, IScriptChangeDamageModifier
// Grab the choices for the current turn, that have been executed before this move.
var choice = battleData.Battle.PreviousTurnChoices.Last().TakeWhile(x => !Equals(x, move.MoveChoice))
// Of these, find the move choice that used Fusion Flare.
.OfType<MoveChoice>().FirstOrDefault(x => x.ChosenMove.MoveData.Name == "fusion_flare");
// Of these, find the last successfully executed move. Failed moves don't break the combination.
.OfType<MoveChoice>().LastOrDefault(x => !x.HasFailed);
// If Fusion Flare was used, Fusion Bolt's power is doubled.
if (choice != null)
// If Fusion Flare was used immediately preceding, Fusion Bolt's power is doubled.
if (choice is not null && choice.ChosenMove.MoveData.Name == "fusion_flare")
{
modifier *= 2;
}

View File

@@ -12,11 +12,11 @@ public class FusionFlare : Script, IScriptChangeDamageModifier
// Grab the choices for the current turn, that have been executed before this move.
var choice = battleData.Battle.PreviousTurnChoices.Last().TakeWhile(x => !Equals(x, move.MoveChoice))
// Of these, find the move choice that used Fusion Bolt.
.OfType<MoveChoice>().FirstOrDefault(x => x.ChosenMove.MoveData.Name == "fusion_bolt");
// Of these, find the last successfully executed move. Failed moves don't break the combination.
.OfType<MoveChoice>().LastOrDefault(x => !x.HasFailed);
// If Fusion Bolt was used, Fusion Flare's power is doubled.
if (choice != null)
// If Fusion Bolt was used immediately preceding, Fusion Flare's power is doubled.
if (choice is not null && choice.ChosenMove.MoveData.Name == "fusion_bolt")
{
modifier *= 2;
}

View File

@@ -1,4 +1,5 @@
using PkmnLib.Plugin.Gen7.Scripts.Battle;
using PkmnLib.Plugin.Gen7.Scripts.Side;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
@@ -11,8 +12,28 @@ public class FutureSight : Script, IScriptStopBeforeMove
var battleData = move.User.BattleData;
if (battleData == null)
return;
battleData.Battle.Volatile.Add(new FutureSightEffect(move.MoveChoice));
var battle = battleData.Battle;
var moveChoice = move.MoveChoice;
prevent = true;
if (IsTargetAlreadySetToBeHit(battle, moveChoice.TargetSide, moveChoice.TargetPosition))
{
moveChoice.Fail();
return;
}
if (battle.Volatile.TryGet<FutureSightEffect>(out var effect))
effect.AddStrike(moveChoice);
else
battle.Volatile.Add(new FutureSightEffect(moveChoice));
}
private static bool IsTargetAlreadySetToBeHit(IBattle battle, byte side, byte position)
{
if (battle.Volatile.TryGet<FutureSightEffect>(out var futureSight) && futureSight.HasStrikeAt(side, position))
return true;
var doomDesire = battle.Sides[side].VolatileScripts.Get<DoomDesireEffect>();
return doomDesire != null && doomDesire.HasTarget(position);
}
}

View File

@@ -23,17 +23,15 @@ public class Gravity : Script, IScriptOnSecondaryEffect
var flyEffect = ScriptUtils.ResolveName<ChargeFlyEffect>();
var skyDropEffect = ScriptUtils.ResolveName<ChargeSkyDropEffect>();
var telekinesisEffect = ScriptUtils.ResolveName<TelekinesisEffect>();
var magnetRiseEffect = ScriptUtils.ResolveName<MagnetRiseEffect>();
foreach (var pokemon in battleData.Battle.Sides.SelectMany(x => x.Pokemon).WhereNotNull())
{
if (pokemon.Volatile.Contains(chargeBounceEffect))
pokemon.Volatile.Remove(chargeBounceEffect);
if (pokemon.Volatile.Contains(flyEffect))
pokemon.Volatile.Remove(flyEffect);
if (pokemon.Volatile.Contains(skyDropEffect))
pokemon.Volatile.Remove(skyDropEffect);
if (pokemon.Volatile.Contains(telekinesisEffect))
pokemon.Volatile.Remove(telekinesisEffect);
pokemon.Volatile.Remove(chargeBounceEffect);
pokemon.Volatile.Remove(flyEffect);
pokemon.Volatile.Remove(skyDropEffect);
pokemon.Volatile.Remove(telekinesisEffect);
pokemon.Volatile.Remove(magnetRiseEffect);
}
}
}

View File

@@ -6,7 +6,6 @@ public class GuardianOfAlola : Script, IScriptChangeMoveDamage
/// <inheritdoc />
public void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
var maxHp = target.BoostedStats.Hp;
damage = (uint)(maxHp * (3f / 4f));
damage = (uint)Math.Max(target.CurrentHealth * (3f / 4f), 1);
}
}

View File

@@ -6,6 +6,11 @@ public class GyroBall : Script, IScriptChangeBasePower
/// <inheritdoc />
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
{
basePower = Math.Min((byte)150, (byte)(25 * target.BoostedStats.Speed / move.User.BoostedStats.Speed + 1));
if (move.User.BoostedStats.Speed == 0)
{
basePower = 1;
return;
}
basePower = (ushort)Math.Min(150, 25 * target.BoostedStats.Speed / move.User.BoostedStats.Speed + 1);
}
}

View File

@@ -14,10 +14,9 @@ public class HealBell : Script, IScriptOnSecondaryEffect
foreach (var pokemon in party.Party.WhereNotNull())
{
if (pokemon.BattleData?.IsOnBattlefield == true && pokemon.ActiveAbility?.Name == "soundproof")
continue;
pokemon.ClearStatus();
var confusion = ScriptUtils.ResolveName<Confusion>();
if (pokemon.Volatile.Contains(confusion))
pokemon.Volatile.Remove(confusion);
}
}
}

View File

@@ -12,6 +12,14 @@ public class HealingWish : Script, IScriptOnSecondaryEffect
if (battleData == null)
return;
var responsibleIndex = new ResponsibleIndex(battleData.SideIndex, battleData.Position);
var party = battleData.Battle.Parties.FirstOrDefault(x => x.IsResponsibleForIndex(responsibleIndex));
if (party == null || !party.HasUsablePokemonNotInField())
{
move.GetHitData(target, hit).Fail();
return;
}
var side = battleData.Battle.Sides[battleData.SideIndex];
side.VolatileScripts.Add(new HealingWishEffect(battleData.Position));

View File

@@ -16,8 +16,8 @@ public class HeartSwap : Script, IScriptOnSecondaryEffect
var targetStat = targetStats.GetStatistic(stat);
if (userStat == targetStat)
continue;
move.User.ChangeStatBoost(stat, (sbyte)(userStat - targetStat), true, true, eventBatchId);
target.ChangeStatBoost(stat, (sbyte)(targetStat - userStat), false, true, eventBatchId);
move.User.ChangeStatBoost(stat, (sbyte)(targetStat - userStat), true, true, eventBatchId);
target.ChangeStatBoost(stat, (sbyte)(userStat - targetStat), false, true, eventBatchId);
}
}
}

View File

@@ -9,10 +9,10 @@ public class HeatCrash : Script, IScriptChangeBasePower
var weightMultiplier = move.User.WeightInKg / target.WeightInKg;
basePower = weightMultiplier switch
{
> 5 => 120,
> 4 => 100,
> 3 => 80,
> 2 => 60,
>= 5 => 120,
>= 4 => 100,
>= 3 => 80,
>= 2 => 60,
_ => 40,
};
}

View File

@@ -6,7 +6,7 @@ public class Hex : Script, IScriptChangeBasePower
/// <inheritdoc />
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
{
if (!target.StatusScript.IsEmpty)
if (!target.StatusScript.IsEmpty || target.ActiveAbility?.Name == "comatose")
{
basePower = basePower.MultiplyOrMax(2);
}

View File

@@ -1,7 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "hidden_power")]
public class HiddenPower : Script, IScriptChangeMoveType, IScriptChangeBasePower
public class HiddenPower : Script, IScriptChangeMoveType
{
/// <inheritdoc />
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType)
@@ -14,16 +14,6 @@ public class HiddenPower : Script, IScriptChangeMoveType, IScriptChangeBasePower
moveType = t;
}
/// <inheritdoc />
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort 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.

View File

@@ -1,16 +1,23 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "high_jump_kick")]
public class HighJumpKick : Script, IScriptOnMoveMiss
public class HighJumpKick : Script, IScriptOnAfterHits
{
/// <inheritdoc />
public void OnMoveMiss(IExecutingMove move, IPokemon target)
public void OnAfterHits(IExecutingMove move, IPokemon target)
{
var damage = move.Hits.Where(h => h.HasExecuted).Sum(h => h.Damage);
if (damage == 0)
{
DoRecoil(move);
}
}
private static void DoRecoil(IExecutingMove move)
{
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);
var recoil = maxHp / 2;
if (recoil == 0)
return;
move.User.Damage(recoil, DamageSource.Misc);
}
}

View File

@@ -1,9 +1,10 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Plugin.Gen7.Scripts.Side;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "hyperspace_fury")]
public class HyperspaceFury : Script, IScriptOnSecondaryEffect
public class HyperspaceFury : Script, IScriptFailMove, IScriptOnSecondaryEffect
{
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
@@ -13,5 +14,21 @@ public class HyperspaceFury : Script, IScriptOnSecondaryEffect
{
target.Volatile.Remove(protectionScript.Name);
}
target.BattleData?.BattleSide.VolatileScripts.Remove(ScriptUtils.ResolveName<CraftyShieldEffect>());
target.BattleData?.BattleSide.VolatileScripts.Remove(ScriptUtils.ResolveName<MatBlockEffect>());
target.BattleData?.BattleSide.VolatileScripts.Remove(ScriptUtils.ResolveName<QuickGuardEffect>());
target.BattleData?.BattleSide.VolatileScripts.Remove(ScriptUtils.ResolveName<WideGuardEffect>());
move.User.ChangeStatBoost(Statistic.Defense, -1, true, false);
}
private static StringKey HoopaName = "hoopa";
private static StringKey UnboundName = "unbound";
public void FailMove(IExecutingMove move, ref bool fail)
{
if (move.User.Species.Name != HoopaName || move.User.Form.Name != UnboundName)
{
fail = true;
}
}
}

View File

@@ -24,9 +24,5 @@ public class IceBall : Script, IScriptOnSecondaryEffect, IScriptChangeBasePower
userEffect = new IceBallEffect(move.User, move.UseMove.Name);
move.User.Volatile.Add(userEffect);
}
else
{
userEffect.TurnCount++;
}
}
}

View File

@@ -8,17 +8,23 @@ public class IceFang : Script, IScriptOnSecondaryEffect
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = move.User.BattleData;
var battleData = target.BattleData;
if (battleData == null)
return;
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
var random = battleData.Battle.Random;
if (random.EffectChance(10, move, target, hit))
{
target.SetStatus("frozen", move.User);
}
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
// It also has an independent 10% chance of causing the target to flinch, if the user attacks before the target.
var choiceQueue = battleData.Battle.ChoiceQueue;
if (choiceQueue?.FirstOrDefault(x => x.User == target) != null)
{
target.Volatile.Add(new FlinchEffect());
if (random.EffectChance(10, move, target, hit))
{
target.Volatile.Add(new FlinchEffect());
}
}
}
}

View File

@@ -8,6 +8,6 @@ public class Imprison : Script, IScriptOnSecondaryEffect
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.Add(new ImprisonEffect(move.User));
target.BattleData?.Battle.Volatile.Add(new ImprisonEffect(move.User));
}
}

View File

@@ -6,15 +6,18 @@ public class Incinerate : Script, IScriptOnSecondaryEffect
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (target.HeldItem is not { Category: ItemCategory.Berry } || !target.TryStealHeldItem(out _))
var isValidItem = target.HeldItem is { Category: ItemCategory.Berry } ||
(target.HeldItem?.Name.ToString().EndsWith("_gem") == true && target.HeldItem is
{ Category: ItemCategory.MiscItem });
if (!isValidItem || !target.TryStealHeldItem(out _))
{
move.GetHitData(target, hit).Fail();
return;
}
move.Battle.EventHook.Invoke(new DialogEvent("item_incinerated", new Dictionary<string, object>
{
{ "pokemon", target },
{ "item", target.HeldItem },
{ "item", target.HeldItem! },
}));
}
}

View File

@@ -24,6 +24,16 @@ public class Infestation : Script, IScriptOnSecondaryEffect
{
turns = 5;
}
target.Volatile.Add(new InfestationEffect(target, turns));
var args = new CustomTriggers.ModifyBindArgs(move)
{
Duration = turns,
DamagePercent = 1 / 8f,
};
move.User.RunScriptHook<IScriptCustomTrigger>(x => x.CustomTrigger(CustomTriggers.ModifyBind, args));
var bindTurns = args.Duration;
var bindDamage = args.DamagePercent;
target.Volatile.Add(new InfestationEffect(target, bindTurns, bindDamage));
}
}

View File

@@ -1,4 +1,5 @@
using PkmnLib.Dynamic.BattleFlow;
using PkmnLib.Plugin.Gen7.Scripts.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
@@ -16,7 +17,8 @@ public class Instruct : Script, IScriptOnSecondaryEffect
var lastMoveChoiceByTarget = target.BattleData?.LastMoveChoice;
if (lastMoveChoiceByTarget == null || !battleData.Battle.CanUse(lastMoveChoiceByTarget))
if (lastMoveChoiceByTarget == null || !battleData.Battle.CanUse(lastMoveChoiceByTarget) ||
!lastMoveChoiceByTarget.ChosenMove.MoveData.CanCopyMove())
{
move.GetHitData(target, hit).Fail();
return;

View File

@@ -1,3 +1,5 @@
using PkmnLib.Plugin.Gen7.Scripts.Battle;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "judgement")]
@@ -6,6 +8,12 @@ public class Judgement : Script, IScriptChangeMoveType
/// <inheritdoc />
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType)
{
//If Magic Room is in effect, Judgment's type will always be Normal regardless of the Plate held
if (move.Battle.Volatile.Contains<MagicRoomEffect>())
{
return;
}
var heldItem = move.User.HeldItem;
if (heldItem == null)
return;

View File

@@ -3,10 +3,16 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "kings_shield")]
public class KingsShield : ProtectionScript
{
protected override Script GetEffectScript() => new Pokemon.KingsShieldEffect();
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
base.OnSecondaryEffect(move, target, hit);
// Only change Aegislash form if the move has not failed
if (move.GetHitData(target, hit).HasFailed)
return;
// Default form is shield form
if (move.User.Species.Name == "aegislash" && move.User.Form.Name != "default")
{

View File

@@ -1,14 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "knock_off")]
public class KnockOff : Script, IScriptOnSecondaryEffect
public class KnockOff : Script, IScriptOnSecondaryEffect, IScriptChangeBasePower
{
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (!target.TryStealHeldItem(out _))
{
move.GetHitData(target, hit).Fail();
}
if (move.User.IsFainted)
return;
target.TryStealHeldItem(out _);
}
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
{
if (target.HeldItem == null || target.HeldItem.Category == ItemCategory.FormChanger)
return;
basePower = basePower.MultiplyOrMax(1.5f);
}
}

View File

@@ -20,7 +20,7 @@ public class LastResort : Script, IScriptPreventMoveSelection
}
// Grab all move choices
var movesForUserSinceEnteringField = battleData.Battle.PreviousTurnChoices
var movesForUserSinceEnteringField = battleData.Battle.PreviousTurnChoices.Skip((int)battleData.SwitchInTurn)
// Reading backwards
.Reverse().SelectMany(x => x.Reverse())
// We only care about move choices since the user entered the field
@@ -29,7 +29,7 @@ public class LastResort : Script, IScriptPreventMoveSelection
.Where(x => x.User == choice.User)
// Grab the chosen move, and remove duplicates
.Select(x => x.ChosenMove).Distinct().ToList();
if (!userMoves.All(x => movesForUserSinceEnteringField.Contains(x)))
if (!userMoves.All(movesForUserSinceEnteringField.Contains))
{
prevent = true;
}

View File

@@ -8,6 +8,6 @@ public class LockOn : Script, IScriptOnSecondaryEffect
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.Add(new LockOnEffect(target));
target.Volatile.Add(new LockOnEffect(move.User));
}
}

View File

@@ -1,4 +1,5 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Plugin.Gen7.Scripts.Side;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
@@ -8,6 +9,6 @@ public class LuckyChant : Script, IScriptOnSecondaryEffect
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.Add(new LuckyChantEffect());
target.BattleData?.BattleSide.VolatileScripts.Add(new LuckyChantEffect());
}
}

View File

@@ -9,6 +9,9 @@ public class LunarDance : Script, IScriptOnSecondaryEffect
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = move.User.BattleData;
battleData?.BattleSide.VolatileScripts.Add(new LunarDanceEffect(battleData.Position));
if (battleData is null)
return;
battleData.BattleSide.VolatileScripts.Add(new LunarDanceEffect(battleData.Position));
move.User.Faint(DamageSource.Misc);
}
}

View File

@@ -13,6 +13,13 @@ public class MagicRoom : Script, IScriptOnSecondaryEffect
return;
var battle = battleData.Battle;
battle.Volatile.Add(new MagicRoomEffect());
if (battle.Volatile.Contains<MagicRoomEffect>())
{
battle.Volatile.Remove<MagicRoomEffect>();
}
else
{
battle.Volatile.Add(new MagicRoomEffect());
}
}
}

View File

@@ -3,11 +3,17 @@ using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "magma_storm")]
public class MagmaStorm : MultiHitMove, IScriptOnSecondaryEffect
public class MagmaStorm : Script, IScriptOnSecondaryEffect
{
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.StackOrAdd("magma_storm", () => new MagmaStormEffect(target));
var args = new CustomTriggers.ModifyBindArgs(move);
move.User.RunScriptHook<IScriptCustomTrigger>(x => x.CustomTrigger(CustomTriggers.ModifyBind, args));
var bindTurns = args.Duration;
var bindDamage = args.DamagePercent;
target.Volatile.StackOrAdd("magma_storm", () => new MagmaStormEffect(target, bindTurns, bindDamage));
}
}

View File

@@ -9,7 +9,7 @@ public class MagnetRise : Script, IScriptOnSecondaryEffect
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (move.User.Volatile.Contains(ScriptUtils.ResolveName<IngrainEffect>()) ||
move.User.ActiveAbility?.Name == "levitate")
move.User.ActiveAbility?.Name == "levitate" || move.User.Volatile.Contains<MagnetRiseEffect>())
{
move.GetHitData(target, hit).Fail();
return;

View File

@@ -1,4 +1,6 @@
using PkmnLib.Plugin.Gen7.Scripts.MoveVolatile;
using PkmnLib.Plugin.Gen7.Scripts.Utils;
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
@@ -18,12 +20,16 @@ public class MeFirst : Script, IScriptChangeMove
choice.Fail();
return;
}
if (battleData.Battle.ChoiceQueue?.FirstOrDefault(x => x.User == target) is not IMoveChoice targetMove)
if (battleData.Battle.ChoiceQueue?.FirstOrDefault(x => x.User == target) is not IMoveChoice
{
ChosenMove.MoveData.Category: MoveCategory.Physical or MoveCategory.Special,
} targetMove)
{
choice.Fail();
return;
}
if (battleData.Battle.Library.MiscLibrary.IsReplacementChoice(targetMove))
if (!targetMove.ChosenMove.MoveData.CanCopyMove() || targetMove.ChosenMove.MoveData.Name == "metal_burst" ||
battleData.Battle.Library.MiscLibrary.IsReplacementChoice(targetMove))
{
choice.Fail();
return;

View File

@@ -5,9 +5,13 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "mean_look")]
public class MeanLook : Script, IScriptOnSecondaryEffect
{
private static StringKey GhostTypeName => new("ghost");
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (target.Types.Any(x => x.Name == GhostTypeName))
return;
var targetEffect = target.Volatile.Add(new MeanLookEffectTarget());
if (targetEffect == null)
{

View File

@@ -3,7 +3,7 @@ using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "metal_burst")]
public class MetalBurst : Script, IScriptOnBeforeTurnStart, IScriptChangeMoveDamage
public class MetalBurst : Script, IScriptOnBeforeTurnStart, IScriptChangeTargets, IScriptChangeMoveDamage
{
/// <inheritdoc />
public void OnBeforeTurnStart(ITurnChoice choice)
@@ -11,19 +11,31 @@ public class MetalBurst : Script, IScriptOnBeforeTurnStart, IScriptChangeMoveDam
choice.User.Volatile.Add(new MetalBurstHelper());
}
/// <inheritdoc />
public void ChangeTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
{
var helper = moveChoice.User.Volatile.Get<MetalBurstHelper>();
var lastAttacker = helper?.LastAttacker;
if (lastAttacker == null)
{
moveChoice.Fail();
return;
}
targets = [lastAttacker];
}
/// <inheritdoc />
public void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
var helper = target.Volatile.Get<MetalBurstHelper>();
var helper = move.User.Volatile.Get<MetalBurstHelper>();
if (helper?.LastAttacker == null || helper.LastAttacker != move.User)
if (helper?.LastAttacker == null || helper.LastAttacker != target)
{
move.GetHitData(target, hit).Fail();
return;
}
damage = helper.LastDamage.MultiplyOrMax(1.5f);
target.Volatile.Remove<MetalBurstHelper>();
}
[Script(ScriptCategory.Pokemon, "metal_burst_helper")]

View File

@@ -18,13 +18,13 @@ public class Metronome : Script, IScriptChangeMove
var retryCount = 0;
while (true)
{
// Breaker for infinite loop. The odds that we'd accidentally roll a non-copyable move 100 times in a row
// is extremely unlikely, unless the loaded move library only contains non-copyable moves.
// Breaker for infinite loop. The odds that we'd accidentally roll an unselectable move 100 times in a row
// is extremely unlikely, unless the loaded move library only contains unselectable moves.
// This is a failsafe.
if (retryCount > 100)
throw new Exception("Metronome failed to find a valid move after 100 attempts.");
var randomMove = moveLibrary.GetRandom(battleData.Battle.Random);
if (!randomMove.CanCopyMove())
if (!randomMove.CanBeSelectedByMetronome())
{
retryCount++;
continue;

View File

@@ -8,6 +8,12 @@ public class MiracleEye : Script, IScriptOnSecondaryEffect
/// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (target.Volatile.Contains<MiracleEyeEffect>())
{
move.GetHitData(target, hit).Fail();
return;
}
if (target.StatBoost.Evasion > 0)
{
target.ChangeStatBoost(Statistic.Evasion, (sbyte)-target.StatBoost.Evasion, false, false);

View File

@@ -3,7 +3,7 @@ using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "mirror_coat")]
public class MirrorCoat : Script, IScriptOnBeforeTurnStart, IScriptChangeMoveDamage
public class MirrorCoat : Script, IScriptOnBeforeTurnStart, IScriptChangeTargets, IScriptChangeMoveDamage
{
/// <inheritdoc />
public void OnBeforeTurnStart(ITurnChoice choice)
@@ -11,19 +11,31 @@ public class MirrorCoat : Script, IScriptOnBeforeTurnStart, IScriptChangeMoveDam
choice.User.Volatile.Add(new MirrorCoatHelper());
}
/// <inheritdoc />
public void ChangeTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
{
var helper = moveChoice.User.Volatile.Get<MirrorCoatHelper>();
var lastAttacker = helper?.LastAttacker;
if (lastAttacker == null)
{
moveChoice.Fail();
return;
}
targets = [lastAttacker];
}
/// <inheritdoc />
public void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
var helper = target.Volatile.Get<MirrorCoatHelper>();
var helper = move.User.Volatile.Get<MirrorCoatHelper>();
if (helper?.LastAttacker == null || helper.LastAttacker != move.User)
if (helper?.LastAttacker == null || helper.LastAttacker != target)
{
move.GetHitData(target, hit).Fail();
return;
}
damage = helper.LastDamage.MultiplyOrMax(2f);
target.Volatile.Remove<MirrorCoatHelper>();
damage = Math.Max(helper.LastDamage.MultiplyOrMax(2f), 1);
}
[Script(ScriptCategory.Pokemon, "mirror_coat_helper")]

View File

@@ -16,10 +16,14 @@ public class MirrorMove : Script, IScriptChangeMove
if (battle.ChoiceQueue == null)
return;
var currentTurn = battle.ChoiceQueue.LastRanChoice;
var target = battle.GetPokemon(choice.TargetSide, choice.TargetPosition);
if (target is null)
{
choice.Fail();
return;
}
var lastMove = battle.PreviousTurnChoices.SelectMany(x => x).OfType<IMoveChoice>()
.TakeWhile(x => !Equals(x, currentTurn)).LastOrDefault(x => x.TargetPosition == choice.TargetPosition &&
x.TargetSide == choice.TargetSide &&
x.User.BattleData?.IsOnBattlefield == true);
.TakeWhile(x => !Equals(x, currentTurn)).LastOrDefault(x => x.User == target);
if (lastMove == null || !lastMove.ChosenMove.MoveData.CanCopyMove())
{
choice.Fail();

View File

@@ -1,3 +1,5 @@
using PkmnLib.Plugin.Gen7.Scripts.Battle;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "multi_attack")]
@@ -6,6 +8,11 @@ public class MultiAttack : Script, IScriptChangeMoveType
/// <inheritdoc />
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType)
{
if (move.User.ActiveAbility?.Name == "klutz")
return;
if (move.Battle.Volatile.Contains<MagicRoomEffect>())
return;
var item = move.User.HeldItem?.Name.ToString();
var typeLibrary = move.User.Library.StaticLibrary.Types;

View File

@@ -1,3 +1,5 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "trick_or_treat")]
@@ -7,8 +9,18 @@ public class TrickOrTreat : Script, IScriptOnSecondaryEffect
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var library = move.Battle.Library.StaticLibrary.Types;
if (!library.TryGetTypeIdentifier("ghost", out var ghostType))
if (!library.TryGetTypeIdentifier("ghost", out var ghostType) || target.Types.Contains(ghostType))
{
move.GetHitData(target, hit).Fail();
return;
}
if (target.Volatile.TryGet<HasHadTypeAddedEffect>(out var effect))
{
target.RemoveType(effect.TypeIdentifier);
target.Volatile.Remove<HasHadTypeAddedEffect>();
}
target.AddType(ghostType);
target.Volatile.Add(new HasHadTypeAddedEffect(ghostType));
}
}