Even more abilities

This commit is contained in:
Deukhoofd 2025-06-09 14:23:51 +02:00
parent 97868ab4c6
commit 074f92bfc0
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
31 changed files with 319 additions and 51 deletions

View File

@ -23,7 +23,7 @@ public class ProxyScript : Script
/// <inheritdoc /> /// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat, public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, ref uint value) ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{ {
foreach (var (script, handler) in _changeOffensiveStatValueEvents) foreach (var (script, handler) in _changeOffensiveStatValueEvents)
{ {

View File

@ -344,7 +344,7 @@ public abstract class Script : IDeepCloneable
/// This function allows a script to change the actual offensive stat values used when calculating damage /// This function allows a script to change the actual offensive stat values used when calculating damage
/// </summary> /// </summary>
public virtual void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat, public virtual void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, ref uint value) ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{ {
} }
@ -352,7 +352,7 @@ public abstract class Script : IDeepCloneable
/// This function allows a script to change the actual defensive stat values used when calculating damage. /// This function allows a script to change the actual defensive stat values used when calculating damage.
/// </summary> /// </summary>
public virtual void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat, public virtual void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat,
ImmutableStatisticSet<uint> targetStats, ref uint value) ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{ {
} }

View File

@ -210,16 +210,36 @@
"fur_coat": { "fur_coat": {
"effect": "fur_coat" "effect": "fur_coat"
}, },
"gale_wings": {}, "gale_wings": {
"galvanize": {}, "effect": "gale_wings"
"gluttony": {}, },
"gooey": {}, "galvanize": {
"grass_pelt": {}, "effect": "galvanize"
"grassy_surge": {}, },
"guts": {}, "gluttony": {
"harvest": {}, "effect": "gluttony"
"healer": {}, },
"heatproof": {}, "gooey": {
"effect": "gooey"
},
"grass_pelt": {
"effect": "grass_pelt"
},
"grassy_surge": {
"effect": "grassy_surge"
},
"guts": {
"effect": "guts"
},
"harvest": {
"effect": "harvest"
},
"healer": {
"effect": "healer"
},
"heatproof": {
"effect": "heatproof"
},
"heavy_metal": {}, "heavy_metal": {},
"honey_gather": {}, "honey_gather": {},
"huge_power": {}, "huge_power": {},

View File

@ -132,11 +132,10 @@ public class Gen7DamageCalculator(Gen7PluginConfiguration configuration) : IDama
var defensiveStat = targetStats.GetStatistic(defensive); var defensiveStat = targetStats.GetStatistic(defensive);
var origOffensiveStat = offensiveStat; var origOffensiveStat = offensiveStat;
executingMove.RunScriptHook(script => executingMove.RunScriptHook(script => script.ChangeOffensiveStatValue(executingMove, target, hitNumber,
script.ChangeOffensiveStatValue(executingMove, target, hitNumber, defensiveStat, targetStats, defensiveStat, targetStats, offensive, ref offensiveStat));
ref offensiveStat));
executingMove.RunScriptHook(script => script.ChangeDefensiveStatValue(executingMove, target, hitNumber, executingMove.RunScriptHook(script => script.ChangeDefensiveStatValue(executingMove, target, hitNumber,
origOffensiveStat, targetStats, ref defensiveStat)); origOffensiveStat, targetStats, defensive, ref defensiveStat));
var modifier = (float)offensiveStat / defensiveStat; var modifier = (float)offensiveStat / defensiveStat;
executingMove.RunScriptHook(script => executingMove.RunScriptHook(script =>

View File

@ -1,5 +1,3 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary> /// <summary>

View File

@ -1,5 +1,3 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary> /// <summary>

View File

@ -1,5 +1,3 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary> /// <summary>

View File

@ -1,5 +1,3 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary> /// <summary>

View File

@ -1,5 +1,3 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary> /// <summary>

View File

@ -1,5 +1,3 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary> /// <summary>

View File

@ -1,5 +1,3 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary> /// <summary>

View File

@ -0,0 +1,23 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Gale Wings is an ability that gives priority to Flying-type moves when the Pokémon's HP is full.
/// This ability is commonly associated with Fletchling, Fletchinder, and Talonflame.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Gale_Wings_(Ability)">Bulbapedia - Gale Wings</see>
/// </summary>
[Script(ScriptCategory.Ability, "gale_wings")]
public class GaleWings : Script
{
/// <inheritdoc />
public override void ChangePriority(IMoveChoice choice, ref sbyte priority)
{
if (choice.User.CurrentHealth != choice.User.MaxHealth)
return;
// Defensive programming, should never happen
if (priority == sbyte.MaxValue)
return;
priority++;
}
}

View File

@ -0,0 +1,29 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Galvanize is an ability that turns Normal-type moves into Electric-type moves and boosts their power.
/// This ability is exclusive to Alolan Golem.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Galvanize_(Ability)">Bulbapedia - Galvanize</see>
/// </summary>
[Script(ScriptCategory.Ability, "galvanize")]
public class Galvanize : Script
{
/// <inheritdoc />
public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit,
ref TypeIdentifier? typeIdentifier)
{
if (typeIdentifier?.Name == "normal" &&
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("electric", out var electricType))
{
typeIdentifier = electricType;
}
}
/// <inheritdoc />
public override void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
{
if (move.GetHitData(target, hit).Type?.Name == "electric")
modifier *= 1.2f;
}
}

View File

@ -0,0 +1,26 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Gluttony is an ability that causes a Pokémon to eat a held Berry when its HP drops to half or less, rather than a third or less.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Gluttony_(Ability)">Bulbapedia - Gluttony</see>
/// </summary>
[Script(ScriptCategory.Ability, "gluttony")]
public class Gluttony : Script
{
/// <inheritdoc />
public override void OnDamage(IPokemon pokemon, DamageSource source, uint oldHealth, uint newHealth)
{
if (pokemon.BattleData is null)
return;
var oldHealthFraction = (float)oldHealth / pokemon.MaxHealth;
var newHealthFraction = (float)newHealth / pokemon.MaxHealth;
if (!(oldHealthFraction >= 0.5f) || !(newHealthFraction < 0.5f))
return;
if (pokemon.HeldItem?.Category != ItemCategory.Berry)
return;
// FIXME: Implement after Berry triggers are implemented
}
}

View File

@ -0,0 +1,18 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Gooey is an ability that lowers the Speed of attackers making contact with the Pokémon.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Gooey_(Ability)">Bulbapedia - Gooey</see>
/// </summary>
[Script(ScriptCategory.Ability, "gooey")]
public class Gooey : Script
{
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (!move.UseMove.HasFlag("contact"))
return;
move.User.ChangeStatBoost(Statistic.Speed, -1, false, false);
}
}

View File

@ -0,0 +1,19 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Grass Pelt is an ability that boosts Defense in Grassy Terrain.
/// This ability is exclusive to Gogoat.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Grass_Pelt_(Ability)">Bulbapedia - Grass Pelt</see>
/// </summary>
[Script(ScriptCategory.Ability, "grass_pelt")]
public class GrassPelt : Script
{
/// <inheritdoc />
public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{
if (move.Battle.TerrainName == ScriptUtils.ResolveName<Terrain.GrassyTerrain>() && stat == Statistic.Defense)
value = value.MultiplyOrMax(1.5f);
}
}

View File

@ -0,0 +1,25 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Grassy Surge is an ability that creates Grassy Terrain when the Pokémon enters battle.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Grassy_Surge_(Ability)">Bulbapedia - Grassy Surge</see>
/// </summary>
[Script(ScriptCategory.Ability, "grassy_surge")]
public class GrassySurge : Script
{
/// <inheritdoc />
public override void OnSwitchIn(IPokemon pokemon, byte position)
{
var battleData = pokemon.BattleData;
if (battleData == null)
return;
EventBatchId batchId = new();
battleData.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon)
{
BatchId = batchId,
});
battleData.Battle.SetTerrain(ScriptUtils.ResolveName<Terrain.GrassyTerrain>(), batchId);
}
}

View File

@ -0,0 +1,23 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Guts is an ability that boosts the Attack stat if the Pokémon has a status condition.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Guts_(Ability)">Bulbapedia - Guts</see>
/// </summary>
[Script(ScriptCategory.Ability, "guts")]
public class Guts : Script
{
/// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{
if (target.StatusScript.IsEmpty)
return;
if (stat != Statistic.Attack)
return;
value = value.MultiplyOrMax(1.5f);
}
}

View File

@ -0,0 +1,39 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Harvest is an ability that may restore a consumed Berry at the end of each turn.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Harvest_(Ability)">Bulbapedia - Harvest</see>
/// </summary>
[Script(ScriptCategory.Ability, "harvest")]
public class Harvest : Script
{
private IPokemon? _pokemon;
/// <inheritdoc />
public override void OnAddedToParent(IScriptSource source)
{
if (source is not IPokemon pokemon)
throw new InvalidOperationException("Harvest can only be added to a Pokemon script source.");
_pokemon = pokemon;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
if (_pokemon?.BattleData is null)
return;
if (_pokemon.HeldItem is not null)
return;
var consumedBerry = _pokemon.BattleData.ConsumedItems.FirstOrDefault(x => x.Category == ItemCategory.Berry);
if (consumedBerry != null)
{
var rng = battle.Random;
if (battle.WeatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>() && rng.GetInt(1) != 0)
return;
battle.EventHook.Invoke(new AbilityTriggerEvent(_pokemon));
_ = _pokemon.SetHeldItem(consumedBerry);
}
}
}

View File

@ -0,0 +1,51 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Healer is an ability that may heal an ally's status condition at the end of each turn in a double battle.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Healer_(Ability)">Bulbapedia - Healer</see>
/// </summary>
[Script(ScriptCategory.Ability, "healer")]
public class Healer : Script
{
private IPokemon? _pokemon;
/// <inheritdoc />
public override void OnAddedToParent(IScriptSource source)
{
if (source is not IPokemon pokemon)
throw new InvalidOperationException("Harvest can only be added to a Pokemon script source.");
_pokemon = pokemon;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
if (_pokemon?.BattleData is null)
return;
if (_pokemon.BattleData.Position > 0)
{
var leftAlly = _pokemon.BattleData.BattleSide.Pokemon[_pokemon.BattleData.Position - 1];
TryClearStatus(battle, leftAlly);
}
if (_pokemon.BattleData.Position < _pokemon.BattleData.BattleSide.Pokemon.Count - 1)
{
var rightAlly = _pokemon.BattleData.BattleSide.Pokemon[_pokemon.BattleData.Position + 1];
TryClearStatus(battle, rightAlly);
}
}
private static void TryClearStatus(IBattle battle, IPokemon? leftAlly)
{
if (leftAlly is null)
return;
if (leftAlly.StatusScript.IsEmpty)
return;
var rng = battle.Random;
if (rng.GetInt(3) != 0) // 1 in 3 chance to heal
return;
battle.EventHook.Invoke(new AbilityTriggerEvent(leftAlly));
leftAlly.ClearStatus();
}
}

View File

@ -0,0 +1,19 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Heatproof is an ability that halves the damage taken from Fire-type moves and burn.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Heatproof_(Ability)">Bulbapedia - Heatproof</see>
/// </summary>
[Script(ScriptCategory.Ability, "heatproof")]
public class Heatproof : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
{
if (move.GetHitData(target, hit).Type?.Name == "fire")
{
basePower = (ushort)(basePower / 2);
}
}
}

View File

@ -1,5 +1,3 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary> /// <summary>
@ -15,10 +13,9 @@ public class IncreasedStab : Script
public override void ChangeStabModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, public override void ChangeStabModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber,
ref float modifier) ref float modifier)
{ {
if (modifier.IsApproximatelyEqualTo(1.5f)) if (!modifier.IsApproximatelyEqualTo(1.5f))
{ return;
executingMove.Battle.EventHook.Invoke(new AbilityTriggerEvent(executingMove.User)); executingMove.Battle.EventHook.Invoke(new AbilityTriggerEvent(executingMove.User));
modifier = 2.0f; modifier = 2.0f;
} }
}
} }

View File

@ -1,5 +1,3 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary> /// <summary>

View File

@ -1,5 +1,3 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary> /// <summary>

View File

@ -1,5 +1,3 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary> /// <summary>

View File

@ -8,7 +8,7 @@ public class WonderRoomEffect : Script
/// <inheritdoc /> /// <inheritdoc />
public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat, public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat,
ImmutableStatisticSet<uint> targetStats, ref uint value) ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{ {
value = move.UseMove.Category switch value = move.UseMove.Category switch
{ {

View File

@ -7,7 +7,7 @@ public class FoulPlay : Script
{ {
/// <inheritdoc /> /// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint _, public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint _,
ImmutableStatisticSet<uint> targetStats, ref uint value) ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{ {
value = move.UseMove.Category == MoveCategory.Physical value = move.UseMove.Category == MoveCategory.Physical
? target.BoostedStats.Attack ? target.BoostedStats.Attack

View File

@ -5,7 +5,7 @@ public class Psyshock : Script
{ {
/// <inheritdoc /> /// <inheritdoc />
public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat, public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat,
ImmutableStatisticSet<uint> targetStats, ref uint value) ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{ {
value = targetStats.Defense; value = targetStats.Defense;
} }

View File

@ -5,7 +5,7 @@ public class FlashFireEffect : Script
{ {
/// <inheritdoc /> /// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat, public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, ref uint value) ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{ {
if (move.GetHitData(target, hit).Type?.Name == "fire") if (move.GetHitData(target, hit).Type?.Name == "fire")
{ {

View File

@ -5,14 +5,14 @@ public class PowerTrickEffect : Script
{ {
/// <inheritdoc /> /// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat, public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, ref uint value) ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{ {
value = defensiveStat; value = defensiveStat;
} }
/// <inheritdoc /> /// <inheritdoc />
public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat, public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat,
ImmutableStatisticSet<uint> targetStats, ref uint value) ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{ {
value = offensiveStat; value = offensiveStat;
} }

View File

@ -21,7 +21,7 @@ public class FlowerGiftEffect : Script
/// <inheritdoc /> /// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat, public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, ref uint value) ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{ {
if (move.Battle.WeatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>()) if (move.Battle.WeatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>())
return; return;