More abilities
All checks were successful
Build / Build (push) Successful in 48s

This commit is contained in:
Deukhoofd 2025-06-09 13:44:26 +02:00
parent 00005aa4bf
commit 97868ab4c6
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
94 changed files with 829 additions and 150 deletions

View File

@ -1,5 +1,6 @@
using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models;
using PkmnLib.Static.Species; using PkmnLib.Static.Species;
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.Events; namespace PkmnLib.Dynamic.Events;
@ -18,6 +19,8 @@ public record AbilityTriggerEvent : IEventData
/// </summary> /// </summary>
public IAbility? Ability { get; } public IAbility? Ability { get; }
public Dictionary<StringKey, object?>? Metadata { get; init; } = null;
/// <inheritdoc cref="AbilityTriggerEvent"/> /// <inheritdoc cref="AbilityTriggerEvent"/>
public AbilityTriggerEvent(IPokemon pokemon) public AbilityTriggerEvent(IPokemon pokemon)
{ {

View File

@ -274,8 +274,9 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// <param name="stat">The stat to be changed</param> /// <param name="stat">The stat to be changed</param>
/// <param name="change">The amount to change the stat by</param> /// <param name="change">The amount to change the stat by</param>
/// <param name="selfInflicted">Whether the change was self-inflicted. This can be relevant in scripts.</param> /// <param name="selfInflicted">Whether the change was self-inflicted. This can be relevant in scripts.</param>
/// <param name="force"></param>
/// <param name="batchId">The event batch ID this change is a part of. This is relevant for visual handling</param> /// <param name="batchId">The event batch ID this change is a part of. This is relevant for visual handling</param>
bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted, EventBatchId batchId = default); bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted, bool force, EventBatchId batchId = default);
/// <summary> /// <summary>
/// Suppresses the ability of the Pokémon. /// Suppresses the ability of the Pokémon.
@ -354,7 +355,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// <summary> /// <summary>
/// Adds a non-volatile status to the Pokemon. /// Adds a non-volatile status to the Pokemon.
/// </summary> /// </summary>
bool SetStatus(StringKey status, EventBatchId batchId = default); bool SetStatus(StringKey status, bool selfInflicted, EventBatchId batchId = default);
/// <summary> /// <summary>
/// Removes the current non-volatile status from the Pokemon. /// Removes the current non-volatile status from the Pokemon.
@ -841,15 +842,20 @@ public class PokemonImpl : ScriptSource, IPokemon
} }
/// <inheritdoc /> /// <inheritdoc />
public bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted, EventBatchId batchId = default) public bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted, bool force,
EventBatchId batchId = default)
{
if (!force)
{ {
var prevented = false; var prevented = false;
this.RunScriptHook(script => script.PreventStatBoostChange(this, stat, change, selfInflicted, ref prevented)); this.RunScriptHook(script =>
script.PreventStatBoostChange(this, stat, change, selfInflicted, ref prevented));
if (prevented) if (prevented)
return false; return false;
this.RunScriptHook(script => script.ChangeStatBoostChange(this, stat, selfInflicted, ref change)); this.RunScriptHook(script => script.ChangeStatBoostChange(this, stat, selfInflicted, ref change));
if (change == 0) if (change == 0)
return false; return false;
}
var changed = false; var changed = false;
var oldBoost = StatBoost.GetStatistic(stat); var oldBoost = StatBoost.GetStatistic(stat);
changed = change switch changed = change switch
@ -1135,7 +1141,7 @@ public class PokemonImpl : ScriptSource, IPokemon
public bool HasStatus(StringKey status) => StatusScript.Script?.Name == status; public bool HasStatus(StringKey status) => StatusScript.Script?.Name == status;
/// <inheritdoc /> /// <inheritdoc />
public bool SetStatus(StringKey status, EventBatchId batchId = default) public bool SetStatus(StringKey status, bool selfInflicted, EventBatchId batchId = default)
{ {
if (!Library.ScriptResolver.TryResolve(ScriptCategory.Status, status, null, out var statusScript)) if (!Library.ScriptResolver.TryResolve(ScriptCategory.Status, status, null, out var statusScript))
throw new KeyNotFoundException($"Status script {status} not found"); throw new KeyNotFoundException($"Status script {status} not found");
@ -1145,7 +1151,7 @@ public class PokemonImpl : ScriptSource, IPokemon
var oldStatus = StatusScript.Script?.Name; var oldStatus = StatusScript.Script?.Name;
var preventStatus = false; var preventStatus = false;
this.RunScriptHook(script => script.PreventStatusChange(this, status, ref preventStatus)); this.RunScriptHook(script => script.PreventStatusChange(this, status, selfInflicted, ref preventStatus));
if (preventStatus) if (preventStatus)
return false; return false;

View File

@ -0,0 +1,36 @@
using PkmnLib.Dynamic.Models;
using PkmnLib.Static;
namespace PkmnLib.Dynamic.ScriptHandling;
public class ProxyScript : Script
{
public delegate void ChangeOffensiveStatValueEventHandler(IExecutingMove move, IPokemon target, byte hit,
uint defensiveStat, ImmutableStatisticSet<uint> targetStats, ref uint value);
private readonly List<(Script, ChangeOffensiveStatValueEventHandler)> _changeOffensiveStatValueEvents = new();
public void AddChangeOffensiveStatValueEvent(Script script, ChangeOffensiveStatValueEventHandler handler)
{
_changeOffensiveStatValueEvents.Add((script, handler));
script.OnRemoveEvent += OnRemoveScriptEvent;
}
private void OnRemoveScriptEvent(Script script)
{
_changeOffensiveStatValueEvents.RemoveAll(x => x.Item1 == script);
}
/// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, ref uint value)
{
foreach (var (script, handler) in _changeOffensiveStatValueEvents)
{
if (!script.IsSuppressed)
{
handler(move, target, hit, defensiveStat, targetStats, ref value);
}
}
}
}

View File

@ -728,7 +728,8 @@ public abstract class Script : IDeepCloneable
/// <summary> /// <summary>
/// This function allows a script to prevent a Pokemon from being affected by a status condition. /// This function allows a script to prevent a Pokemon from being affected by a status condition.
/// </summary> /// </summary>
public virtual void PreventStatusChange(IPokemon pokemonImpl, StringKey status, ref bool preventStatus) public virtual void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted,
ref bool preventStatus)
{ {
} }

View File

@ -65,10 +65,7 @@ public class ScriptContainer : IReadOnlyScriptContainer
/// </summary> /// </summary>
public Script? Clear() public Script? Clear()
{ {
if (Script is not null) Script?.OnRemove();
{
Script.OnRemove();
}
var script = Script; var script = Script;
Script = null; Script = null;
return script; return script;

View File

@ -1,4 +1,5 @@
using System.Collections; using System.Collections;
using System.Diagnostics.CodeAnalysis;
using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Static.Utils; using PkmnLib.Static.Utils;
@ -31,6 +32,11 @@ public interface IScriptSet : IEnumerable<ScriptContainer>
/// </summary> /// </summary>
ScriptContainer? Get(StringKey scriptKey); ScriptContainer? Get(StringKey scriptKey);
/// <summary>
/// Tries to get a script from the set using its type.
/// </summary>
bool TryGet<T>([NotNullWhen(true)] out T? script) where T : Script;
/// <summary> /// <summary>
/// Gets a script from the set using its type. /// Gets a script from the set using its type.
/// </summary> /// </summary>
@ -144,6 +150,20 @@ public class ScriptSet : IScriptSet
/// <inheritdoc /> /// <inheritdoc />
public ScriptContainer? Get(StringKey scriptKey) => _scripts.FirstOrDefault(s => s.Script?.Name == scriptKey); public ScriptContainer? Get(StringKey scriptKey) => _scripts.FirstOrDefault(s => s.Script?.Name == scriptKey);
/// <inheritdoc />
public bool TryGet<T>([NotNullWhen(true)] out T? script) where T : Script
{
var scriptName = ScriptUtils.ResolveName<T>();
var container = _scripts.FirstOrDefault(sc => sc.Script?.Name == scriptName);
if (container?.Script is not T s)
{
script = null;
return false;
}
script = s;
return true;
}
/// <inheritdoc /> /// <inheritdoc />
public T? Get<T>() where T : Script => Get(ScriptUtils.ResolveName<T>())?.Script as T; public T? Get<T>() where T : Script => Get(ScriptUtils.ResolveName<T>())?.Script as T;

View File

@ -111,7 +111,7 @@ public class DeepCloneTests
using var battle = new BattleImpl(library, parties, false, 2, 3, false, 0); using var battle = new BattleImpl(library, parties, false, 2, 3, false, 0);
battle.Sides[0].SwapPokemon(0, party1[0]); battle.Sides[0].SwapPokemon(0, party1[0]);
battle.Sides[1].SwapPokemon(0, party2[0]); battle.Sides[1].SwapPokemon(0, party2[0]);
party1[0]!.ChangeStatBoost(Statistic.Defense, 2, true); party1[0]!.ChangeStatBoost(Statistic.Defense, 2, true, false);
await Assert.That(party1[0]!.StatBoost.Defense).IsEqualTo((sbyte)2); await Assert.That(party1[0]!.StatBoost.Defense).IsEqualTo((sbyte)2);
var clone = battle.DeepClone(); var clone = battle.DeepClone();

View File

@ -180,24 +180,36 @@
"flare_boost": { "flare_boost": {
"effect": "flare_boost" "effect": "flare_boost"
}, },
"flash_fire": {}, "flash_fire": {
"effect": "flash_fire"
},
"flower_gift": { "flower_gift": {
"flags": [ "effect": "flower_gift"
"cant_be_copied" },
] "flower_veil": {
"effect": "flower_veil"
},
"fluffy": {
"effect": "fluffy"
}, },
"flower_veil": {},
"fluffy": {},
"forecast": { "forecast": {
"flags": [ "effect": "forecast"
"cant_be_copied" },
] "forewarn": {
"effect": "forewarn"
},
"friend_guard": {
"effect": "friend_guard"
},
"frisk": {
"effect": "frisk"
},
"full_metal_body": {
"effect": "full_metal_body"
},
"fur_coat": {
"effect": "fur_coat"
}, },
"forewarn": {},
"friend_guard": {},
"frisk": {},
"full_metal_body": {},
"fur_coat": {},
"gale_wings": {}, "gale_wings": {},
"galvanize": {}, "galvanize": {},
"gluttony": {}, "gluttony": {},

View File

@ -18247,6 +18247,126 @@
], ],
"formeChange": [] "formeChange": []
} }
},
"sunshine": {
"abilities": [
"flower_gift"
],
"hiddenAbilities": [],
"baseStats": {
"attack": 90,
"defense": 70,
"hp": 70,
"specialAttack": 87,
"specialDefense": 117,
"speed": 85
},
"evReward": {
"specialAttack": 2
},
"types": [
"grass"
],
"height": 0.5,
"weight": 9.3,
"baseExp": 158,
"moves": {
"levelMoves": [
{
"name": "petal_dance",
"level": 0
},
{
"name": "morning_sun",
"level": 1
},
{
"name": "leech_seed",
"level": 1
},
{
"name": "petal_dance",
"level": 1
},
{
"name": "growth",
"level": 1
},
{
"name": "tackle",
"level": 1
},
{
"name": "growth",
"level": 7
},
{
"name": "leech_seed",
"level": 10
},
{
"name": "helping_hand",
"level": 13
},
{
"name": "magical_leaf",
"level": 19
},
{
"name": "sunny_day",
"level": 22
},
{
"name": "worry_seed",
"level": 30
},
{
"name": "take_down",
"level": 35
},
{
"name": "solar_beam",
"level": 43
},
{
"name": "lucky_chant",
"level": 48
},
{
"name": "petal_blizzard",
"level": 50
}
],
"eggMoves": [],
"tutorMoves": [],
"machine": [
"swords_dance",
"hyper_beam",
"solar_beam",
"toxic",
"double_team",
"rest",
"substitute",
"protect",
"swagger",
"attract",
"sleep_talk",
"return",
"frustration",
"safeguard",
"hidden_power",
"sunny_day",
"facade",
"nature_power",
"energy_ball",
"giga_impact",
"grass_knot",
"round",
"confide",
"dazzling_gleam"
],
"formeChange": []
}
} }
}, },
"evolutions": [] "evolutions": []

View File

@ -19,7 +19,7 @@ public class AngerPoint : Script
{ {
BatchId = batchId, BatchId = batchId,
}); });
target.ChangeStatBoost(Statistic.Attack, 12, true, batchId); target.ChangeStatBoost(Statistic.Attack, 12, true, false, batchId);
} }
} }
} }

View File

@ -18,6 +18,6 @@ public class BeastBoost : Script
{ {
BatchId = batchId, BatchId = batchId,
}); });
move.User.ChangeStatBoost(highestStat, 1, true, batchId); move.User.ChangeStatBoost(highestStat, 1, true, false, batchId);
} }
} }

View File

@ -22,6 +22,6 @@ public class Berserk : Script
{ {
BatchId = batchId, BatchId = batchId,
}); });
pokemon.ChangeStatBoost(Statistic.SpecialAttack, 1, true, batchId); pokemon.ChangeStatBoost(Statistic.SpecialAttack, 1, true, false, batchId);
} }
} }

View File

@ -13,7 +13,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
public class Comatose : Script public class Comatose : Script
{ {
/// <inheritdoc /> /// <inheritdoc />
public override void PreventStatusChange(IPokemon pokemonImpl, StringKey status, ref bool preventStatus) public override void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted,
ref bool preventStatus)
{ {
if (status == ScriptUtils.ResolveName<Status.Sleep>()) if (status == ScriptUtils.ResolveName<Status.Sleep>())
{ {

View File

@ -22,6 +22,6 @@ public class Competitive : Script
{ {
BatchId = batchId, BatchId = batchId,
}); });
pokemon.ChangeStatBoost(Statistic.SpecialAttack, 2, true, batchId); pokemon.ChangeStatBoost(Statistic.SpecialAttack, 2, true, false, batchId);
} }
} }

View File

@ -22,6 +22,6 @@ public class Defiant : Script
{ {
BatchId = batchId, BatchId = batchId,
}); });
pokemon.ChangeStatBoost(Statistic.Attack, 2, true, batchId); pokemon.ChangeStatBoost(Statistic.Attack, 2, true, false, batchId);
} }
} }

View File

@ -31,6 +31,6 @@ public class Download : Script
}); });
pokemon.ChangeStatBoost( pokemon.ChangeStatBoost(
opponentAverageDefense < opponentAverageSpecialDefense ? Statistic.Attack : Statistic.SpecialAttack, 1, opponentAverageDefense < opponentAverageSpecialDefense ? Statistic.Attack : Statistic.SpecialAttack, 1,
true, batchId); true, false, batchId);
} }
} }

View File

@ -36,13 +36,13 @@ public class EffectSpore : Script
switch (chance) switch (chance)
{ {
case < 9: case < 9:
move.User.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), batchId); move.User.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), false, batchId);
break; break;
case < 19: case < 19:
move.User.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), batchId); move.User.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), false, batchId);
break; break;
case < 30: case < 30:
move.User.SetStatus(ScriptUtils.ResolveName<Status.Sleep>(), batchId); move.User.SetStatus(ScriptUtils.ResolveName<Status.Sleep>(), false, batchId);
break; break;
} }
} }

View File

@ -20,6 +20,6 @@ public class FlameBody : Script
return; return;
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)); move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
move.User.SetStatus(ScriptUtils.ResolveName<Status.Burned>()); move.User.SetStatus(ScriptUtils.ResolveName<Status.Burned>(), false);
} }
} }

View File

@ -16,8 +16,8 @@ public class FlashFire : Script
public override void ChangeIncomingEffectiveness(IExecutingMove executingMove, IPokemon target, byte hitIndex, public override void ChangeIncomingEffectiveness(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref float effectiveness) ref float effectiveness)
{ {
if (executingMove.GetHitData(target, hitIndex).Type?.Name == "fire") if (executingMove.GetHitData(target, hitIndex).Type?.Name != "fire")
{ return;
effectiveness = 0f; effectiveness = 0f;
if (target.Volatile.Contains<FlashFireEffect>()) if (target.Volatile.Contains<FlashFireEffect>())
@ -26,4 +26,3 @@ public class FlashFire : Script
executingMove.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)); executingMove.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
} }
} }
}

View File

@ -0,0 +1,60 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Flower Gift is an ability that boosts the Attack and Special Defense of the Pokémon and its allies in sunlight.
/// This ability is exclusive to Cherrim.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Flower_Gift_(Ability)">Bulbapedia - Flower Gift</see>
/// </summary>
[Script(ScriptCategory.Ability, "flower_gift")]
public class FlowerGift : Script
{
private IPokemon? _pokemon;
/// <inheritdoc />
public override void OnAddedToParent(IScriptSource source)
{
if (source is not IPokemon pokemon)
throw new InvalidOperationException("Flower Gift can only be added to a Pokemon script source.");
_pokemon = pokemon;
var effect = _pokemon.BattleData?.BattleSide.VolatileScripts.Add(new Side.FlowerGiftEffect());
(effect?.Script as Side.FlowerGiftEffect)?.OnAdded(_pokemon);
}
/// <inheritdoc />
public override void OnWeatherChange(IBattle battle, StringKey? weatherName, StringKey? oldWeatherName)
{
if (_pokemon is null)
return;
if (weatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>())
return;
if (_pokemon.Species.Name != "cherrim")
return;
EventBatchId batchId = new();
if (_pokemon.Species.TryGetForm("sunshine", out var form) && _pokemon.Form != form)
{
_pokemon.ChangeForm(form, batchId);
}
}
/// <inheritdoc />
public override void OnRemove()
{
if (_pokemon is null)
return;
if (_pokemon.BattleData?.BattleSide.VolatileScripts.TryGet<Side.FlowerGiftEffect>(out var script) == true)
{
script.OnRemoved(_pokemon);
}
if (_pokemon.Species.Name != "cherrim")
return;
EventBatchId batchId = new();
var defaultForm = _pokemon.Species.GetDefaultForm();
if (_pokemon.Form != defaultForm)
{
_pokemon.ChangeForm(defaultForm, batchId);
}
}
}

View File

@ -0,0 +1,34 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Flower Veil is an ability that prevents Grass-type allies from having their stats lowered or being afflicted by status conditions.
/// This ability is exclusive to Florges and its evolutionary line.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Flower_Veil_(Ability)">Bulbapedia - Flower Veil</see>
/// </summary>
[Script(ScriptCategory.Ability, "flower_veil")]
public class FlowerVeil : Script
{
private IPokemon? _pokemon;
/// <inheritdoc />
public override void OnAddedToParent(IScriptSource source)
{
if (source is not IPokemon pokemon)
throw new InvalidOperationException("Flower Veil can only be added to a Pokemon script source.");
_pokemon = pokemon;
var effect = _pokemon.BattleData?.BattleSide.VolatileScripts.Add(new Side.FlowerVeilEffect());
(effect?.Script as Side.FlowerVeilEffect)?.OnAdded(_pokemon);
}
/// <inheritdoc />
public override void OnRemove()
{
if (_pokemon is null)
return;
if (_pokemon.BattleData?.BattleSide.VolatileScripts.TryGet<Side.FlowerVeilEffect>(out var script) == true)
{
script.OnRemoved(_pokemon);
}
}
}

View File

@ -0,0 +1,24 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Fluffy is an ability that halves the damage taken from contact moves but doubles the damage taken from Fire-type moves.
/// This ability is exclusive to Stufful and Bewear.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Fluffy_(Ability)">Bulbapedia - Fluffy</see>
/// </summary>
[Script(ScriptCategory.Ability, "fluffy")]
public class Fluffy : Script
{
/// <inheritdoc />
public override void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
{
if (move.GetHitData(target, hit).Type?.Name == "fire")
{
modifier *= 2f;
}
if (move.UseMove.HasFlag("contact"))
{
modifier *= 0.5f;
}
}
}

View File

@ -0,0 +1,71 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Forecast is an ability that changes the Pokémon's form depending on the weather.
/// This ability is exclusive to Castform.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Forecast_(Ability)">Bulbapedia - Forecast</see>
/// </summary>
[Script(ScriptCategory.Ability, "forecast")]
public class Forecast : Script
{
private IPokemon? _pokemon;
/// <inheritdoc />
public override void OnAddedToParent(IScriptSource source)
{
if (source is not IPokemon pokemon)
throw new InvalidOperationException("Forecast can only be added to a Pokemon script source.");
_pokemon = pokemon;
}
/// <inheritdoc />
public override void OnSwitchIn(IPokemon pokemon, byte position)
{
ChangeForm(pokemon, pokemon.BattleData?.Battle.WeatherName);
}
/// <inheritdoc />
public override void OnWeatherChange(IBattle battle, StringKey? weatherName, StringKey? oldWeatherName)
{
if (_pokemon is null)
return;
ChangeForm(_pokemon, weatherName);
}
/// <inheritdoc />
public override void OnRemove()
{
if (_pokemon is null)
return;
ChangeForm(_pokemon, null);
}
private static void ChangeForm(IPokemon pokemon, StringKey? weather)
{
if (pokemon.Species.Name != "castform")
return;
if (weather == ScriptUtils.ResolveName<Weather.HarshSunlight>() &&
pokemon.Species.TryGetForm("sunny", out var sunnyForm) && pokemon.Form != sunnyForm)
{
pokemon.ChangeForm(sunnyForm);
}
else if (weather == ScriptUtils.ResolveName<Weather.Rain>() &&
pokemon.Species.TryGetForm("rainy", out var rainyForm) && pokemon.Form != rainyForm)
{
pokemon.ChangeForm(rainyForm);
}
else if (weather == ScriptUtils.ResolveName<Weather.Hail>() &&
pokemon.Species.TryGetForm("snowy", out var snowyForm) && pokemon.Form != snowyForm)
{
pokemon.ChangeForm(snowyForm);
}
else if (pokemon.Form != pokemon.Species.GetDefaultForm())
{
pokemon.ChangeForm(pokemon.Species.GetDefaultForm());
}
}
}

View File

@ -0,0 +1,64 @@
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Forewarn is an ability that reveals the opponent's move with the highest base power when the Pokémon enters battle.
/// This ability is commonly associated with Drowzee and Hypno.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Forewarn_(Ability)">Bulbapedia - Forewarn</see>
/// </summary>
[Script(ScriptCategory.Ability, "forewarn")]
public class Forewarn : Script
{
/// <inheritdoc />
public override void OnSwitchIn(IPokemon pokemon, byte position)
{
var battleData = pokemon.BattleData;
if (battleData == null)
return;
var opponents = battleData.Battle.Sides.Where(x => x != battleData.BattleSide).SelectMany(x => x.Pokemon)
.WhereNotNull();
var highestPowerMove = opponents.SelectMany(opponent => opponent.Moves).WhereNotNull().Select(move => new
{
Move = move,
Power = GetBasePower(move.MoveData),
}).OrderByDescending(move => move.Power).FirstOrDefault();
battleData.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon)
{
Metadata = new Dictionary<StringKey, object?>
{
{ "highest_power_move", highestPowerMove?.Move },
{ "power", highestPowerMove?.Power },
},
});
}
private static byte GetBasePower(IMoveData moveData)
{
// OHKO moves (handled by secondary effect)
if (moveData.SecondaryEffect?.Name == "one_hit_ko")
return 150;
return moveData.Name.ToString() switch
{
// 150 BP: Specific moves
"blast_burn" or "eruption" or "water_spout" or "fissure" or "guillotine" or "horn_drill"
or "sheer_cold" => 150,
// 120 BP: Counter, Metal Burst, Mirror Coat
"counter" or "metal_burst" or "mirror_coat" => 120,
// 80 BP: List of variable power and fixed-damage moves
"crush_grip" or "dragon_rage" or "electro_ball" or "endeavor" or "final_gambit" or "flail" or "fling"
or "frustration" or "grass_knot" or "guardian_of_alola" or "gyro_ball" or "heat_crash" or "heavy_slam"
or "hidden_power" or "low_kick" or "natural_gift" or "natures_madness" or "night_shade" or "present"
or "psywave" or "punishment" or "return" or "reversal" or "seismic_toss" or "sonic_boom" or "spit_up"
or "super_fang" or "trump_card" or "wring_out" => 80,
// 20 BP: Stored Power, Power Trip
"stored_power" or "power_trip" => 20,
_ => moveData.BasePower == 0 ? (byte)1 : moveData.BasePower,
};
}
}

View File

@ -0,0 +1,34 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Friend Guard is an ability that reduces the damage taken by allies by 25%.
/// This ability is commonly associated with Clefairy and Vivillon.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Friend_Guard_(Ability)">Bulbapedia - Friend Guard</see>
/// </summary>
[Script(ScriptCategory.Ability, "friend_guard")]
public class FriendGuard : Script
{
private IPokemon? _pokemon;
/// <inheritdoc />
public override void OnAddedToParent(IScriptSource source)
{
if (source is not IPokemon pokemon)
throw new InvalidOperationException("Friend Guard can only be added to a Pokemon script source.");
_pokemon = pokemon;
var effect = _pokemon.BattleData?.BattleSide.VolatileScripts.Add(new Side.FriendGuardEffect());
(effect?.Script as Side.FriendGuardEffect)?.OnAdded(_pokemon);
}
/// <inheritdoc />
public override void OnRemove()
{
if (_pokemon is null)
return;
if (_pokemon.BattleData?.BattleSide.VolatileScripts.TryGet<Side.FriendGuardEffect>(out var script) == true)
{
script.OnRemoved(_pokemon);
}
}
}

View File

@ -0,0 +1,43 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Frisk is an ability that reveals the held items of opposing Pokémon when the Pokémon enters battle.
/// This ability is commonly associated with Banette and Gothitelle.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Frisk_(Ability)">Bulbapedia - Frisk</see>
/// </summary>
[Script(ScriptCategory.Ability, "frisk")]
public class Frisk : Script
{
/// <inheritdoc />
public override void OnSwitchIn(IPokemon pokemon, byte position)
{
if (pokemon.BattleData?.BattleSide is null)
return;
// Check if the Pokémon has an opposing side
var opposingSide =
pokemon.BattleData.Battle.Sides.FirstOrDefault(side => side != pokemon.BattleData.BattleSide);
if (opposingSide is null)
return;
EventBatchId batchId = new();
// Iterate through the opposing Pokémon
foreach (var opponent in opposingSide.Pokemon.WhereNotNull())
{
// If the opponent has a held item, reveal it
if (opponent.HeldItem != null)
{
pokemon.BattleData.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon)
{
Metadata = new Dictionary<StringKey, object?>
{
{ "opponent", opponent },
{ "held_item", opponent.HeldItem },
},
BatchId = batchId,
});
}
}
}
}

View File

@ -0,0 +1,21 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Full Metal Body is an ability that prevents the Pokémon's stats from being lowered by other Pokémon's moves or abilities.
/// This ability is exclusive to Solgaleo.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Full_Metal_Body_(Ability)">Bulbapedia - Full Metal Body</see>
/// </summary>
[Script(ScriptCategory.Ability, "full_metal_body")]
public class FullMetalBody : Script
{
/// <inheritdoc />
public override void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted,
ref bool prevent)
{
if (selfInflicted)
return;
prevent = true;
}
}

View File

@ -0,0 +1,22 @@
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Fur Coat is an ability that halves the damage taken from physical moves.
/// This ability is exclusive to Furfrou.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Fur_Coat_(Ability)">Bulbapedia - Fur Coat</see>
/// </summary>
[Script(ScriptCategory.Ability, "fur_coat")]
public class FurCoat : Script
{
/// <inheritdoc />
public override void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
if (move.UseMove.Category == MoveCategory.Physical)
{
damage /= 2;
}
}
}

View File

@ -16,7 +16,8 @@ public class UproarEffect : Script
} }
/// <inheritdoc /> /// <inheritdoc />
public override void PreventStatusChange(IPokemon pokemonImpl, StringKey status, ref bool preventStatus) public override void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted,
ref bool preventStatus)
{ {
if (status == ScriptUtils.ResolveName<Status.Sleep>()) if (status == ScriptUtils.ResolveName<Status.Sleep>())
{ {

View File

@ -23,6 +23,6 @@ public class Acupressure : Script
// Choose a random stat to raise. 0 is HP, so we start at 1. // Choose a random stat to raise. 0 is HP, so we start at 1.
var stat = (Statistic)move.User.BattleData!.Battle.Random.GetInt(1, (int)Statistic.Speed + 1); var stat = (Statistic)move.User.BattleData!.Battle.Random.GetInt(1, (int)Statistic.Speed + 1);
target.ChangeStatBoost(stat, 2, false); target.ChangeStatBoost(stat, 2, false, false);
} }
} }

View File

@ -21,7 +21,7 @@ public class Autotomize : 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;
if (user.ChangeStatBoost(Statistic.Speed, 2, true) && user.ChangeWeightInKgBy(-100.0f)) if (user.ChangeStatBoost(Statistic.Speed, 2, true, false) && user.ChangeWeightInKgBy(-100.0f))
{ {
var battle = user.BattleData?.Battle; var battle = user.BattleData?.Battle;
battle?.EventHook.Invoke(new DialogEvent("pokemon_became_nimble", new Dictionary<string, object> battle?.EventHook.Invoke(new DialogEvent("pokemon_became_nimble", new Dictionary<string, object>

View File

@ -15,6 +15,6 @@ public class BellyDrum : Script
target.Damage(maxHealthHalved, DamageSource.Misc); target.Damage(maxHealthHalved, DamageSource.Misc);
// Raising the user's Attack by 12 stages should always set it to +6. // Raising the user's Attack by 12 stages should always set it to +6.
target.ChangeStatBoost(Statistic.Attack, 12, true); target.ChangeStatBoost(Statistic.Attack, 12, true, false);
} }
} }

View File

@ -34,7 +34,7 @@ public class Bounce : Script
var random = battle.Random; var random = battle.Random;
if (random.EffectChance(30, move, target, hit)) if (random.EffectChance(30, move, target, hit))
{ {
target.SetStatus("paralyzed"); target.SetStatus("paralyzed", false);
} }
} }
} }

View File

@ -19,6 +19,6 @@ public class Captivate : Script
move.GetHitData(target, hit).Fail(); move.GetHitData(target, hit).Fail();
return; return;
} }
target.ChangeStatBoost(Statistic.SpecialAttack, -2, false); target.ChangeStatBoost(Statistic.SpecialAttack, -2, false, false);
} }
} }

View File

@ -24,10 +24,10 @@ public class ChangeAllTargetStats : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
target.ChangeStatBoost(Statistic.Attack, _amount, target == move.User); target.ChangeStatBoost(Statistic.Attack, _amount, target == move.User, false);
target.ChangeStatBoost(Statistic.Defense, _amount, target == move.User); target.ChangeStatBoost(Statistic.Defense, _amount, target == move.User, false);
target.ChangeStatBoost(Statistic.SpecialAttack, _amount, target == move.User); target.ChangeStatBoost(Statistic.SpecialAttack, _amount, target == move.User, false);
target.ChangeStatBoost(Statistic.SpecialDefense, _amount, target == move.User); target.ChangeStatBoost(Statistic.SpecialDefense, _amount, target == move.User, false);
target.ChangeStatBoost(Statistic.Speed, _amount, target == move.User); target.ChangeStatBoost(Statistic.Speed, _amount, target == move.User, false);
} }
} }

View File

@ -32,7 +32,7 @@ public class ChangeMultipleTargetStatBoosts : Script
EventBatchId batchId = new(); EventBatchId batchId = new();
foreach (var stat in _statBoosts) foreach (var stat in _statBoosts)
{ {
target.ChangeStatBoost(stat.Key, stat.Value, true, batchId); target.ChangeStatBoost(stat.Key, stat.Value, true, false, batchId);
} }
} }
} }

View File

@ -32,7 +32,7 @@ public class ChangeMultipleUserStatBoosts : Script
EventBatchId batchId = new(); EventBatchId batchId = new();
foreach (var stat in _statBoosts) foreach (var stat in _statBoosts)
{ {
move.User.ChangeStatBoost(stat.Key, stat.Value, true, batchId); move.User.ChangeStatBoost(stat.Key, stat.Value, true, false, batchId);
} }
} }
} }

View File

@ -31,7 +31,7 @@ public abstract class ChangeTargetStats : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
target.ChangeStatBoost(_stat, _amount, target == move.User); target.ChangeStatBoost(_stat, _amount, target == move.User, false);
} }
} }

View File

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

View File

@ -8,7 +8,7 @@ public class Charge : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
move.User.ChangeStatBoost(Statistic.SpecialDefense, 1, true); move.User.ChangeStatBoost(Statistic.SpecialDefense, 1, true, false);
move.User.Volatile.Add(new ChargeEffect()); move.User.Volatile.Add(new ChargeEffect());
} }
} }

View File

@ -24,9 +24,9 @@ public class Curse : Script
else else
{ {
EventBatchId batchId = new(); EventBatchId batchId = new();
move.User.ChangeStatBoost(Statistic.Speed, -1, true, batchId); move.User.ChangeStatBoost(Statistic.Speed, -1, true, false, batchId);
move.User.ChangeStatBoost(Statistic.Defense, 1, true, batchId); move.User.ChangeStatBoost(Statistic.Defense, 1, true, false, batchId);
move.User.ChangeStatBoost(Statistic.Attack, 1, true, batchId); move.User.ChangeStatBoost(Statistic.Attack, 1, true, false, batchId);
} }
} }
} }

View File

@ -7,7 +7,7 @@ public class DragonAscent : Script
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
EventBatchId batchId = new(); EventBatchId batchId = new();
move.User.ChangeStatBoost(Statistic.Defense, -1, true, batchId); move.User.ChangeStatBoost(Statistic.Defense, -1, true, false, batchId);
move.User.ChangeStatBoost(Statistic.SpecialDefense, -1, true, batchId); move.User.ChangeStatBoost(Statistic.SpecialDefense, -1, true, false, batchId);
} }
} }

View File

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

View File

@ -14,7 +14,7 @@ public class FireFang : Script
var random = battleData.Battle.Random; var random = battleData.Battle.Random;
if (random.EffectChance(10, move, target, hit)) if (random.EffectChance(10, move, target, hit))
{ {
target.SetStatus("burned"); target.SetStatus("burned", false);
} }
// It also has an independent 10% chance of causing the target to flinch, if the user attacks before the target. // It also has an independent 10% chance of causing the target to flinch, if the user attacks before the target.

View File

@ -28,7 +28,7 @@ public class FlameWheel : Script
if (move.Battle.Random.EffectChance(_burnChance, move, target, hit)) if (move.Battle.Random.EffectChance(_burnChance, move, target, hit))
{ {
target.SetStatus("burned"); target.SetStatus("burned", false);
} }
} }
} }

View File

@ -12,7 +12,7 @@ public class FlareBlitz : Script
if (battleData.Battle.Random.EffectChance(10, move, target, hit)) if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{ {
target.SetStatus("burned"); target.SetStatus("burned", false);
} }
var hitData = move.GetHitData(target, hit); var hitData = move.GetHitData(target, hit);

View File

@ -8,7 +8,7 @@ public class Flatter : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
target.ChangeStatBoost(Statistic.SpecialAttack, 1, false); target.ChangeStatBoost(Statistic.SpecialAttack, 1, false, false);
target.Volatile.StackOrAdd("confusion", () => new Confusion()); target.Volatile.StackOrAdd("confusion", () => new Confusion());
} }
} }

View File

@ -22,7 +22,7 @@ public class FlowerShield : Script
continue; continue;
if (!pokemon.Types.Contains(grassType)) if (!pokemon.Types.Contains(grassType))
continue; continue;
pokemon.ChangeStatBoost(Statistic.Defense, 1, pokemon == move.User, batchId); pokemon.ChangeStatBoost(Statistic.Defense, 1, pokemon == move.User, false, batchId);
} }
} }
} }

View File

@ -13,6 +13,6 @@ public class Foresight : Script
return; return;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types; var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
target.Volatile.Add(new ForesightEffect(typeLibrary)); target.Volatile.Add(new ForesightEffect(typeLibrary));
target.ChangeStatBoost(Statistic.Evasion, (sbyte)-target.StatBoost.Evasion, false); target.ChangeStatBoost(Statistic.Evasion, (sbyte)-target.StatBoost.Evasion, false, false);
} }
} }

View File

@ -31,7 +31,7 @@ public class FreezeDry : Script
if (battleData.Battle.Random.EffectChance(10, move, target, hit)) if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{ {
target.SetStatus("frozen"); target.SetStatus("frozen", false);
} }
} }
} }

View File

@ -17,7 +17,7 @@ public class FreezeShock : BaseChargeMove<RequireChargeEffect>
if (battleData.Battle.Random.EffectChance(30, move, target, hit)) if (battleData.Battle.Random.EffectChance(30, move, target, hit))
{ {
target.SetStatus("paralyzed"); target.SetStatus("paralyzed", false);
} }
} }
} }

View File

@ -19,8 +19,8 @@ public class GearUp : Script
var ability = pokemon.ActiveAbility?.Name; var ability = pokemon.ActiveAbility?.Name;
if (ability != "plus" && ability != "minus") if (ability != "plus" && ability != "minus")
continue; continue;
pokemon.ChangeStatBoost(Statistic.Attack, 1, pokemon == move.User, evtBatchId); pokemon.ChangeStatBoost(Statistic.Attack, 1, pokemon == move.User, false, evtBatchId);
pokemon.ChangeStatBoost(Statistic.SpecialAttack, 1, pokemon == move.User, evtBatchId); pokemon.ChangeStatBoost(Statistic.SpecialAttack, 1, pokemon == move.User, false, evtBatchId);
} }
} }
} }

View File

@ -12,8 +12,8 @@ public class Geomancy : BaseChargeMove<RequireChargeEffect>
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
EventBatchId eventBatchId = new(); EventBatchId eventBatchId = new();
move.User.ChangeStatBoost(Statistic.SpecialAttack, 2, true, eventBatchId); move.User.ChangeStatBoost(Statistic.SpecialAttack, 2, true, false, eventBatchId);
move.User.ChangeStatBoost(Statistic.SpecialDefense, 2, true, eventBatchId); move.User.ChangeStatBoost(Statistic.SpecialDefense, 2, true, false, eventBatchId);
move.User.ChangeStatBoost(Statistic.Speed, 2, true, eventBatchId); move.User.ChangeStatBoost(Statistic.Speed, 2, true, false, eventBatchId);
} }
} }

View File

@ -12,7 +12,7 @@ public class Growth : Script
sbyte amount = 1; sbyte amount = 1;
if (move.Battle.WeatherName == ScriptUtils.ResolveName<HarshSunlight>()) if (move.Battle.WeatherName == ScriptUtils.ResolveName<HarshSunlight>())
amount = 2; amount = 2;
move.User.ChangeStatBoost(Statistic.Attack, amount, true, batchId); move.User.ChangeStatBoost(Statistic.Attack, amount, true, false, batchId);
move.User.ChangeStatBoost(Statistic.SpecialAttack, amount, true, batchId); move.User.ChangeStatBoost(Statistic.SpecialAttack, amount, true, false, batchId);
} }
} }

View File

@ -17,11 +17,11 @@ public class GuardSwap : Script
var userSpecialDefense = userStats.GetStatistic(Statistic.SpecialDefense); var userSpecialDefense = userStats.GetStatistic(Statistic.SpecialDefense);
var targetSpecialDefense = targetStats.GetStatistic(Statistic.SpecialDefense); var targetSpecialDefense = targetStats.GetStatistic(Statistic.SpecialDefense);
user.ChangeStatBoost(Statistic.Defense, (sbyte)(targetDefense - userDefense), true, eventBatchId); user.ChangeStatBoost(Statistic.Defense, (sbyte)(targetDefense - userDefense), true, true, eventBatchId);
user.ChangeStatBoost(Statistic.SpecialDefense, (sbyte)(targetSpecialDefense - userSpecialDefense), true, user.ChangeStatBoost(Statistic.SpecialDefense, (sbyte)(targetSpecialDefense - userSpecialDefense), true, true,
eventBatchId); eventBatchId);
target.ChangeStatBoost(Statistic.Defense, (sbyte)(userDefense - targetDefense), false, eventBatchId); target.ChangeStatBoost(Statistic.Defense, (sbyte)(userDefense - targetDefense), false, true, eventBatchId);
target.ChangeStatBoost(Statistic.SpecialDefense, (sbyte)(userSpecialDefense - targetSpecialDefense), false, target.ChangeStatBoost(Statistic.SpecialDefense, (sbyte)(userSpecialDefense - targetSpecialDefense), false,
eventBatchId); true, eventBatchId);
} }
} }

View File

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

View File

@ -17,7 +17,7 @@ public class IceBurn : BaseChargeMove<RequireChargeEffect>
if (battleData.Battle.Random.EffectChance(30, move, target, hit)) if (battleData.Battle.Random.EffectChance(30, move, target, hit))
{ {
target.SetStatus("burned"); target.SetStatus("burned", false);
} }
} }
} }

View File

@ -14,7 +14,7 @@ public class IceFang : Script
if (battleData.Battle.Random.EffectChance(10, move, target, hit)) if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{ {
target.SetStatus("frozen"); target.SetStatus("frozen", false);
} }
if (battleData.Battle.Random.EffectChance(10, move, target, hit)) if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{ {

View File

@ -18,8 +18,8 @@ public class MagneticFlux : Script
continue; continue;
EventBatchId batch = new(); EventBatchId batch = new();
pokemon.ChangeStatBoost(Statistic.Defense, 1, pokemon == move.User, batch); pokemon.ChangeStatBoost(Statistic.Defense, 1, pokemon == move.User, false, batch);
pokemon.ChangeStatBoost(Statistic.SpecialDefense, 1, pokemon == move.User, batch); pokemon.ChangeStatBoost(Statistic.SpecialDefense, 1, pokemon == move.User, false, batch);
} }
} }
} }

View File

@ -7,8 +7,8 @@ public class Memento : Script
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
var evtBatch = new EventBatchId(); var evtBatch = new EventBatchId();
target.ChangeStatBoost(Statistic.Attack, -2, false, evtBatch); target.ChangeStatBoost(Statistic.Attack, -2, false, false, evtBatch);
target.ChangeStatBoost(Statistic.SpecialAttack, -2, false, evtBatch); target.ChangeStatBoost(Statistic.SpecialAttack, -2, false, false, evtBatch);
move.User.Faint(DamageSource.Misc); move.User.Faint(DamageSource.Misc);
} }

View File

@ -10,7 +10,7 @@ public class MiracleEye : Script
{ {
if (target.StatBoost.Evasion > 0) if (target.StatBoost.Evasion > 0)
{ {
target.ChangeStatBoost(Statistic.Evasion, (sbyte)-target.StatBoost.Evasion, false); target.ChangeStatBoost(Statistic.Evasion, (sbyte)-target.StatBoost.Evasion, false, false);
} }
target.Volatile.Add(new MiracleEyeEffect()); target.Volatile.Add(new MiracleEyeEffect());
} }

View File

@ -15,8 +15,8 @@ public class PartingShot : Script
return; return;
var evtBatch = new EventBatchId(); var evtBatch = new EventBatchId();
var attackChanged = target.ChangeStatBoost(Statistic.Attack, -1, false, evtBatch); var attackChanged = target.ChangeStatBoost(Statistic.Attack, -1, false, false, evtBatch);
var specialAttackChanged = target.ChangeStatBoost(Statistic.SpecialAttack, -1, false, evtBatch); var specialAttackChanged = target.ChangeStatBoost(Statistic.SpecialAttack, -1, false, false, evtBatch);
if (attackChanged || specialAttackChanged) if (attackChanged || specialAttackChanged)
{ {

View File

@ -22,7 +22,7 @@ public class PoisonTail : Script
if (battleData.Battle.Random.EffectChance(10, move, target, hit)) if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{ {
target.SetStatus(ScriptUtils.ResolveName<BadlyPoisoned>()); target.SetStatus(ScriptUtils.ResolveName<BadlyPoisoned>(), false);
} }
} }
} }

View File

@ -14,11 +14,11 @@ public class PowerSwap : Script
var userSpecialAttack = user.StatBoost.SpecialAttack; var userSpecialAttack = user.StatBoost.SpecialAttack;
var targetSpecialAttack = target.StatBoost.SpecialAttack; var targetSpecialAttack = target.StatBoost.SpecialAttack;
user.ChangeStatBoost(Statistic.Attack, (sbyte)(targetAttack - userAttack), true, eventBatchId); user.ChangeStatBoost(Statistic.Attack, (sbyte)(targetAttack - userAttack), true, true, eventBatchId);
user.ChangeStatBoost(Statistic.SpecialAttack, (sbyte)(targetSpecialAttack - userSpecialAttack), true, user.ChangeStatBoost(Statistic.SpecialAttack, (sbyte)(targetSpecialAttack - userSpecialAttack), true, true,
eventBatchId); eventBatchId);
target.ChangeStatBoost(Statistic.Attack, (sbyte)(userAttack - targetAttack), true, eventBatchId); target.ChangeStatBoost(Statistic.Attack, (sbyte)(userAttack - targetAttack), true, true, eventBatchId);
target.ChangeStatBoost(Statistic.SpecialAttack, (sbyte)(userSpecialAttack - targetSpecialAttack), true, target.ChangeStatBoost(Statistic.SpecialAttack, (sbyte)(userSpecialAttack - targetSpecialAttack), true, true,
eventBatchId); eventBatchId);
} }
} }

View File

@ -9,7 +9,7 @@ public class ResetTargetStats : Script
EventBatchId eventBatchId = new(); EventBatchId eventBatchId = new();
foreach (Statistic stat in Enum.GetValues(typeof(Statistic))) foreach (Statistic stat in Enum.GetValues(typeof(Statistic)))
{ {
target.ChangeStatBoost(stat, (sbyte)-target.StatBoost.GetStatistic(stat), true, eventBatchId); target.ChangeStatBoost(stat, (sbyte)-target.StatBoost.GetStatistic(stat), true, true, eventBatchId);
} }
} }
} }

View File

@ -18,7 +18,7 @@ public class Rest : Script
move.GetHitData(target, hit).Fail(); move.GetHitData(target, hit).Fail();
return; return;
} }
if (move.User.SetStatus(ScriptUtils.ResolveName<Sleep>()) && move.User.StatusScript.Script is Sleep sleep) if (move.User.SetStatus(ScriptUtils.ResolveName<Sleep>(), true) && move.User.StatusScript.Script is Sleep sleep)
sleep.Turns = 2; sleep.Turns = 2;
} }
} }

View File

@ -13,8 +13,8 @@ public class Rototiller : Script
EventBatchId batchId = new(); EventBatchId batchId = new();
foreach (var pkmn in pokemon) foreach (var pkmn in pokemon)
{ {
pkmn.ChangeStatBoost(Statistic.Attack, 1, pkmn == move.User, batchId); pkmn.ChangeStatBoost(Statistic.Attack, 1, pkmn == move.User, false, batchId);
pkmn.ChangeStatBoost(Statistic.SpecialAttack, 1, pkmn == move.User, batchId); pkmn.ChangeStatBoost(Statistic.SpecialAttack, 1, pkmn == move.User, false, batchId);
} }
} }
} }

View File

@ -20,6 +20,6 @@ public class SetStatus : Script
{ {
if (_status == null) if (_status == null)
throw new Exception("Missing required parameter 'status'"); throw new Exception("Missing required parameter 'status'");
target.SetStatus(_status); target.SetStatus(_status, false);
} }
} }

View File

@ -7,11 +7,11 @@ public class ShellSmash : Script
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
EventBatchId eventBatchId = new(); EventBatchId eventBatchId = new();
move.User.ChangeStatBoost(Statistic.Attack, 2, true, eventBatchId); move.User.ChangeStatBoost(Statistic.Attack, 2, true, false, eventBatchId);
move.User.ChangeStatBoost(Statistic.SpecialAttack, 2, true, eventBatchId); move.User.ChangeStatBoost(Statistic.SpecialAttack, 2, true, false, eventBatchId);
move.User.ChangeStatBoost(Statistic.Speed, 2, true, eventBatchId); move.User.ChangeStatBoost(Statistic.Speed, 2, true, false, eventBatchId);
eventBatchId = new EventBatchId(); eventBatchId = new EventBatchId();
move.User.ChangeStatBoost(Statistic.Defense, -1, true, eventBatchId); move.User.ChangeStatBoost(Statistic.Defense, -1, true, false, eventBatchId);
move.User.ChangeStatBoost(Statistic.SpecialDefense, -1, true, eventBatchId); move.User.ChangeStatBoost(Statistic.SpecialDefense, -1, true, false, eventBatchId);
} }
} }

View File

@ -10,7 +10,7 @@ public class SkullBash : Script
{ {
if (move.User.Volatile.Contains<SkullBashEffect>()) if (move.User.Volatile.Contains<SkullBashEffect>())
return; return;
move.User.ChangeStatBoost(Statistic.Defense, 1, true); move.User.ChangeStatBoost(Statistic.Defense, 1, true, false);
move.User.Volatile.Add(new SkullBashEffect(move.User)); move.User.Volatile.Add(new SkullBashEffect(move.User));
prevent = true; prevent = true;
} }

View File

@ -12,8 +12,8 @@ public class SpectralThief : Script
EventBatchId batchId = new(); EventBatchId batchId = new();
foreach (var positiveStat in positiveStats) foreach (var positiveStat in positiveStats)
{ {
move.User.ChangeStatBoost(positiveStat.statistic, positiveStat.value, true, batchId); move.User.ChangeStatBoost(positiveStat.statistic, positiveStat.value, true, true, batchId);
target.ChangeStatBoost(positiveStat.statistic, (sbyte)-positiveStat.value, true, batchId); target.ChangeStatBoost(positiveStat.statistic, (sbyte)-positiveStat.value, true, true, batchId);
} }
} }
} }

View File

@ -7,7 +7,7 @@ public class StrengthSap : Script
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
var attack = target.BoostedStats.Attack; var attack = target.BoostedStats.Attack;
if (!target.ChangeStatBoost(Statistic.Attack, -1, false)) if (!target.ChangeStatBoost(Statistic.Attack, -1, false, false))
{ {
move.GetHitData(target, hit).Fail(); move.GetHitData(target, hit).Fail();
return; return;

View File

@ -7,7 +7,7 @@ public class Superpower : Script
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
EventBatchId eventBatch = new(); EventBatchId eventBatch = new();
move.User.ChangeStatBoost(Statistic.Attack, -1, true, eventBatch); move.User.ChangeStatBoost(Statistic.Attack, -1, true, false, eventBatch);
move.User.ChangeStatBoost(Statistic.Defense, -1, true, eventBatch); move.User.ChangeStatBoost(Statistic.Defense, -1, true, false, eventBatch);
} }
} }

View File

@ -6,7 +6,7 @@ public class Swagger : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
target.ChangeStatBoost(Statistic.Attack, 2, false); target.ChangeStatBoost(Statistic.Attack, 2, false, false);
target.Volatile.Add(new Pokemon.Confusion()); target.Volatile.Add(new Pokemon.Confusion());
} }
} }

View File

@ -12,7 +12,7 @@ public class ThunderFang : Script
var random = move.Battle.Random; var random = move.Battle.Random;
if (random.EffectChance(10, move, target, hit)) if (random.EffectChance(10, move, target, hit))
{ {
target.SetStatus(ScriptUtils.ResolveName<Paralyzed>()); target.SetStatus(ScriptUtils.ResolveName<Paralyzed>(), false);
} }
if (random.EffectChance(10, move, target, hit)) if (random.EffectChance(10, move, target, hit))
{ {

View File

@ -16,7 +16,7 @@ public class TopsyTurvy : Script
hasChanged = true; hasChanged = true;
var newStatBoost = -statBoost; var newStatBoost = -statBoost;
target.ChangeStatBoost(stat, (sbyte)newStatBoost, target == move.User, batchId); target.ChangeStatBoost(stat, (sbyte)newStatBoost, target == move.User, true, batchId);
} }
if (!hasChanged) if (!hasChanged)
{ {

View File

@ -6,7 +6,7 @@ public class ToxicThread : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
target.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>()); target.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), false);
target.ChangeStatBoost(Statistic.Speed, -1, false); target.ChangeStatBoost(Statistic.Speed, -1, false, false);
} }
} }

View File

@ -14,6 +14,6 @@ public class TriAttack : Script
ScriptUtils.ResolveName<Status.Paralyzed>(), ScriptUtils.ResolveName<Status.Paralyzed>(),
ScriptUtils.ResolveName<Status.Frozen>(), ScriptUtils.ResolveName<Status.Frozen>(),
]); ]);
target.SetStatus(status); target.SetStatus(status, false);
} }
} }

View File

@ -11,7 +11,7 @@ public class Twineedle : Script
{ {
if (move.Battle.Random.EffectChance(20, move, target, hit)) if (move.Battle.Random.EffectChance(20, move, target, hit))
{ {
target.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>()); target.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), false);
} }
} }
} }

View File

@ -11,8 +11,8 @@ public class VenomDrench : Script
return; return;
EventBatchId eventBatch = new(); EventBatchId eventBatch = new();
target.ChangeStatBoost(Statistic.Attack, -1, false, eventBatch); target.ChangeStatBoost(Statistic.Attack, -1, false, false, eventBatch);
target.ChangeStatBoost(Statistic.SpecialAttack, -1, false, eventBatch); target.ChangeStatBoost(Statistic.SpecialAttack, -1, false, false, eventBatch);
target.ChangeStatBoost(Statistic.Speed, -1, false, eventBatch); target.ChangeStatBoost(Statistic.Speed, -1, false, false, eventBatch);
} }
} }

View File

@ -12,7 +12,7 @@ public class VoltTackle : Script
if (move.Battle.Random.EffectChance(10, move, target, hit)) if (move.Battle.Random.EffectChance(10, move, target, hit))
{ {
target.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>()); target.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), false);
} }
} }
} }

View File

@ -11,7 +11,7 @@ public class BanefulBunkerEffect : ProtectionEffectScript
base.BlockIncomingHit(executingMove, target, hitIndex, ref block); base.BlockIncomingHit(executingMove, target, hitIndex, ref block);
if (executingMove.UseMove.Category != MoveCategory.Status && executingMove.UseMove.HasFlag("contact")) if (executingMove.UseMove.Category != MoveCategory.Status && executingMove.UseMove.HasFlag("contact"))
{ {
executingMove.User.SetStatus("poisoned"); executingMove.User.SetStatus("poisoned", false);
} }
} }
} }

View File

@ -8,7 +8,7 @@ public class BeakBlastEffect : Script
{ {
if (move.UseMove.HasFlag("contact")) if (move.UseMove.HasFlag("contact"))
{ {
move.User.SetStatus("burned"); move.User.SetStatus("burned", false);
} }
} }
} }

View File

@ -11,7 +11,7 @@ public class KingsShield : ProtectionEffectScript
base.BlockIncomingHit(executingMove, target, hitIndex, ref block); base.BlockIncomingHit(executingMove, target, hitIndex, ref block);
if (executingMove.UseMove.Category != MoveCategory.Status && executingMove.UseMove.HasFlag("contact")) if (executingMove.UseMove.Category != MoveCategory.Status && executingMove.UseMove.HasFlag("contact"))
{ {
executingMove.User.ChangeStatBoost(Statistic.Accuracy, -2, false); executingMove.User.ChangeStatBoost(Statistic.Accuracy, -2, false, false);
} }
} }
} }

View File

@ -6,7 +6,7 @@ public class RageEffect : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit) public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{ {
move.User.ChangeStatBoost(Statistic.Attack, 1, true); move.User.ChangeStatBoost(Statistic.Attack, 1, true, false);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -15,8 +15,8 @@ public class StockpileEffect : Script
} }
_pokemon = pokemon; _pokemon = pokemon;
EventBatchId batchId = new(); EventBatchId batchId = new();
pokemon.ChangeStatBoost(Statistic.Defense, 1, true, batchId); pokemon.ChangeStatBoost(Statistic.Defense, 1, true, false, batchId);
pokemon.ChangeStatBoost(Statistic.SpecialDefense, 1, true, batchId); pokemon.ChangeStatBoost(Statistic.SpecialDefense, 1, true, false, batchId);
StockpileCount = 1; StockpileCount = 1;
} }
@ -26,8 +26,8 @@ public class StockpileEffect : Script
if (StockpileCount < 3) if (StockpileCount < 3)
{ {
EventBatchId batchId = new(); EventBatchId batchId = new();
_pokemon?.ChangeStatBoost(Statistic.Defense, 1, true, batchId); _pokemon?.ChangeStatBoost(Statistic.Defense, 1, true, false, batchId);
_pokemon?.ChangeStatBoost(Statistic.SpecialDefense, 1, true, batchId); _pokemon?.ChangeStatBoost(Statistic.SpecialDefense, 1, true, false, batchId);
StockpileCount++; StockpileCount++;
} }
} }
@ -40,8 +40,8 @@ public class StockpileEffect : Script
return; return;
} }
EventBatchId batchId = new(); EventBatchId batchId = new();
_pokemon.ChangeStatBoost(Statistic.Defense, (sbyte)-StockpileCount, true, batchId); _pokemon.ChangeStatBoost(Statistic.Defense, (sbyte)-StockpileCount, true, false, batchId);
_pokemon.ChangeStatBoost(Statistic.SpecialDefense, (sbyte)-StockpileCount, true, batchId); _pokemon.ChangeStatBoost(Statistic.SpecialDefense, (sbyte)-StockpileCount, true, false, batchId);
StockpileCount = 0; StockpileCount = 0;
} }
} }

View File

@ -31,6 +31,6 @@ public class YawnEffect : Script
_hasDoneFirstTurn = true; _hasDoneFirstTurn = true;
return; return;
} }
_pokemon.SetStatus(ScriptUtils.ResolveName<Status.Sleep>()); _pokemon.SetStatus(ScriptUtils.ResolveName<Status.Sleep>(), true);
} }
} }

View File

@ -0,0 +1,31 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
[Script(ScriptCategory.Side, "flower_gift")]
public class FlowerGiftEffect : Script
{
private readonly HashSet<IPokemon> _placerPokemon = [];
public void OnAdded(IPokemon placer)
{
_placerPokemon.Add(placer);
}
public void OnRemoved(IPokemon placer)
{
_placerPokemon.Remove(placer);
if (_placerPokemon.Count == 0)
{
RemoveSelf();
}
}
/// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, ref uint value)
{
if (move.Battle.WeatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>())
return;
value = value.MultiplyOrMax(1.5f);
}
}

View File

@ -0,0 +1,49 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
[Script(ScriptCategory.Side, "flower_veil")]
public class FlowerVeilEffect : Script
{
private readonly HashSet<IPokemon> _placerPokemon = [];
public void OnAdded(IPokemon placer)
{
_placerPokemon.Add(placer);
}
public void OnRemoved(IPokemon placer)
{
_placerPokemon.Remove(placer);
if (_placerPokemon.Count == 0)
{
RemoveSelf();
}
}
/// <inheritdoc />
public override void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted,
ref bool prevent)
{
if (selfInflicted)
return;
if (amount > 0)
return;
if (target.Types.All(x => x.Name != "grass"))
return;
prevent = true;
}
/// <inheritdoc />
public override void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted,
ref bool preventStatus)
{
if (selfInflicted)
return;
if (pokemon.Types.All(x => x.Name != "grass"))
return;
preventStatus = true;
}
}

View File

@ -0,0 +1,28 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
[Script(ScriptCategory.Side, "friend_guard")]
public class FriendGuardEffect : Script
{
private readonly HashSet<IPokemon> _placerPokemon = [];
public void OnAdded(IPokemon placer)
{
_placerPokemon.Add(placer);
}
public void OnRemoved(IPokemon placer)
{
_placerPokemon.Remove(placer);
if (_placerPokemon.Count == 0)
{
RemoveSelf();
}
}
/// <inheritdoc />
public override void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
var modifier = Math.Pow(0.75f, _placerPokemon.Count);
damage = (uint)(damage * modifier);
}
}

View File

@ -6,7 +6,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Side;
public class SafeguardEffect : Script public class SafeguardEffect : Script
{ {
/// <inheritdoc /> /// <inheritdoc />
public override void PreventStatusChange(IPokemon pokemonImpl, StringKey status, ref bool preventStatus) public override void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted,
ref bool preventStatus)
{ {
preventStatus = true; preventStatus = true;
} }

View File

@ -8,6 +8,6 @@ public class StickyWebEffect : Script
{ {
if (pokemon.IsFloating) if (pokemon.IsFloating)
return; return;
pokemon.ChangeStatBoost(Statistic.Speed, -1, false); pokemon.ChangeStatBoost(Statistic.Speed, -1, false, false);
} }
} }

View File

@ -9,6 +9,6 @@ public class ToxicSpikesEffect : Script
if (pokemon.IsFloating) if (pokemon.IsFloating)
return; return;
pokemon.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>()); pokemon.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), false);
} }
} }

View File

@ -39,7 +39,8 @@ public class HarshSunlight : Script
} }
/// <inheritdoc /> /// <inheritdoc />
public override void PreventStatusChange(IPokemon pokemonImpl, StringKey status, ref bool preventStatus) public override void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted,
ref bool preventStatus)
{ {
if (status == ScriptUtils.ResolveName<Status.Frozen>()) if (status == ScriptUtils.ResolveName<Status.Frozen>())
{ {