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

This commit is contained in:
Deukhoofd 2025-06-09 15:24:37 +02:00
parent 074f92bfc0
commit 1579d46671
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
23 changed files with 480 additions and 41 deletions

View File

@ -0,0 +1,21 @@
using PkmnLib.Dynamic.Events;
using PkmnLib.Static.Species;
namespace PkmnLib.Dynamic.Models;
public class DisplaySpeciesChangeEvent : IEventData
{
public IPokemon Pokemon { get; }
public ISpecies? Species { get; }
public IForm? Form { get; }
public DisplaySpeciesChangeEvent(IPokemon pokemon, ISpecies? species, IForm? form)
{
Pokemon = pokemon;
Species = species;
Form = form;
}
/// <inheritdoc />
public EventBatchId BatchId { get; init; }
}

View File

@ -41,6 +41,11 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// </summary>
IForm? DisplayForm { get; }
/// <summary>
/// Sets the display species and form of the Pokemon. This is used for abilities like Illusion.
/// </summary>
void SetDisplaySpecies(ISpecies? species, IForm? form);
/// <summary>
/// The current level of the Pokemon.
/// </summary>
@ -90,14 +95,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// <summary>
/// The weight of the Pokemon in kilograms.
/// </summary>
float WeightInKg { get; set; }
/// <summary>
/// Sets the weight of the Pokémon in kilograms. Returns whether the weight was changed.
/// </summary>
/// <param name="weightInKg">The new weight in kilograms</param>
/// <returns></returns>
public bool ChangeWeightInKgBy(float weightInKg);
float WeightInKg { get; }
/// <summary>
/// The height of the Pokémon in meters.
@ -511,7 +509,6 @@ public class PokemonImpl : ScriptSource, IPokemon
Types = form.Types.ToList();
Experience = library.StaticLibrary.GrowthRates.CalculateExperience(species.GrowthRate, level);
WeightInKg = form.Weight;
HeightInMeters = form.Height;
Happiness = species.BaseHappiness;
Volatile = new ScriptSet(this);
@ -546,7 +543,6 @@ public class PokemonImpl : ScriptSource, IPokemon
}
CurrentHealth = serializedPokemon.CurrentHealth;
WeightInKg = form.Weight;
HeightInMeters = form.Height;
Happiness = serializedPokemon.Happiness;
IndividualValues = serializedPokemon.IndividualValues.ToIndividualValueStatisticSet();
@ -599,6 +595,18 @@ public class PokemonImpl : ScriptSource, IPokemon
/// <inheritdoc />
public IForm? DisplayForm { get; set; }
/// <inheritdoc />
public void SetDisplaySpecies(ISpecies? species, IForm? form)
{
DisplaySpecies = species;
DisplayForm = form;
BattleData?.Battle.EventHook.Invoke(new DisplaySpeciesChangeEvent(this, species, form)
{
BatchId = new EventBatchId(),
});
}
/// <inheritdoc />
public LevelInt Level { get; private set; }
@ -660,18 +668,18 @@ public class PokemonImpl : ScriptSource, IPokemon
public uint CurrentHealth { get; private set; }
/// <inheritdoc />
public float WeightInKg { get; set; }
/// <inheritdoc />
public bool ChangeWeightInKgBy(float weightInKg)
public float WeightInKg
{
if (WeightInKg <= 0.1f)
return false;
var newWeight = WeightInKg + weightInKg;
if (newWeight <= 0.1f)
newWeight = 0.1f;
WeightInKg = newWeight;
return true;
get
{
var weight = Form.Weight;
if (BattleData is not null)
// ReSharper disable once AccessToModifiedClosure
this.RunScriptHook(script => script.ModifyWeight(ref weight));
if (weight < 0.1f)
weight = 0.1f;
return weight;
}
}
/// <inheritdoc />
@ -958,7 +966,6 @@ public class PokemonImpl : ScriptSource, IPokemon
Form = form;
Types = form.Types.ToList();
WeightInKg = form.Weight;
HeightInMeters = form.Height;
var newAbility = Form.GetAbility(AbilityIndex);
@ -1216,7 +1223,6 @@ public class PokemonImpl : ScriptSource, IPokemon
if (!onBattleField)
{
Volatile.Clear();
WeightInKg = Form.Weight;
HeightInMeters = Form.Height;
Types = Form.Types;
OverrideAbility = null;
@ -1241,7 +1247,6 @@ public class PokemonImpl : ScriptSource, IPokemon
var battleData = BattleData;
BattleData = null;
Volatile.Clear();
WeightInKg = Form.Weight;
HeightInMeters = Form.Height;
Types = Form.Types;
OverrideAbility = null;

View File

@ -761,4 +761,12 @@ public abstract class Script : IDeepCloneable
public virtual void OnWeatherChange(IBattle battle, StringKey? weatherName, StringKey? oldWeatherName)
{
}
/// <summary>
/// Modifies the weight of a Pokemon.
/// </summary>
/// <param name="weight">The weight in kilograms</param>
public virtual void ModifyWeight(ref float weight)
{
}
}

View File

@ -184,6 +184,9 @@
"effect": "flash_fire"
},
"flower_gift": {
"flags": [
"cant_be_copied"
],
"effect": "flower_gift"
},
"flower_veil": {
@ -193,6 +196,9 @@
"effect": "fluffy"
},
"forecast": {
"flags": [
"cant_be_copied"
],
"effect": "forecast"
},
"forewarn": {
@ -240,26 +246,48 @@
"heatproof": {
"effect": "heatproof"
},
"heavy_metal": {},
"honey_gather": {},
"huge_power": {},
"hustle": {},
"hydration": {},
"hyper_cutter": {},
"ice_body": {},
"illuminate": {},
"heavy_metal": {
"effect": "heavy_metal"
},
"honey_gather": {
"effect": "honey_gather"
},
"huge_power": {
"effect": "huge_power"
},
"hustle": {
"effect": "hustle"
},
"hydration": {
"effect": "hydration"
},
"hyper_cutter": {
"effect": "hyper_cutter"
},
"ice_body": {
"effect": "ice_body"
},
"illuminate": {
"effect": "illuminate"
},
"illusion": {
"effect": "illusion",
"flags": [
"cant_be_copied"
]
},
"immunity": {},
"immunity": {
"effect": "immunity"
},
"imposter": {
"effect": "imposter",
"flags": [
"cant_be_copied"
]
},
"infiltrator": {},
"infiltrator": {
"effect": "infiltrator"
},
"innards_out": {},
"inner_focus": {},
"insomnia": {},

View File

@ -0,0 +1,16 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Heavy Metal is an ability that doubles the Pokémon's weight.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Heavy_Metal_(Ability)">Bulbapedia - Heavy Metal</see>
/// </summary>
[Script(ScriptCategory.Ability, "heavy_metal")]
public class HeavyMetal : Script
{
/// <inheritdoc />
public override void ModifyWeight(ref float weight)
{
weight *= 2f;
}
}

View File

@ -0,0 +1,12 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Honey Gather is an ability that may allow the Pokémon to collect Honey after battle.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Honey_Gather_(Ability)">Bulbapedia - Honey Gather</see>
/// </summary>
[Script(ScriptCategory.Ability, "honey_gather")]
public class HoneyGather : Script
{
// No Effect in battle
}

View File

@ -0,0 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Huge Power is an ability that doubles the Pokémon's Attack stat.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Huge_Power_(Ability)">Bulbapedia - Huge Power</see>
/// </summary>
[Script(ScriptCategory.Ability, "huge_power")]
public class HugePower : Script
{
/// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{
if (stat == Statistic.Attack)
{
value = value.MultiplyOrMax(2);
}
}
}

View File

@ -0,0 +1,31 @@
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Hustle is an ability that increases the Pokémon's Attack by 50% but lowers the accuracy of its physical moves.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Hustle_(Ability)">Bulbapedia - Hustle</see>
/// </summary>
[Script(ScriptCategory.Ability, "hustle")]
public class Hustle : Script
{
/// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{
if (stat != Statistic.Attack)
return;
value = value.MultiplyOrMax(1.5f);
}
/// <inheritdoc />
public override void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref int modifiedAccuracy)
{
if (executingMove.UseMove.Category == MoveCategory.Physical)
{
modifiedAccuracy = (int)(modifiedAccuracy * 0.8f);
}
}
}

View File

@ -0,0 +1,40 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Hydration is an ability that heals status conditions if it is raining at the end of the turn.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Hydration_(Ability)">Bulbapedia - Hydration</see>
/// </summary>
[Script(ScriptCategory.Ability, "hydration")]
public class Hydration : Script
{
private IPokemon? _pokemon;
/// <inheritdoc />
public override void OnAddedToParent(IScriptSource source)
{
if (source is not IPokemon pokemon)
throw new InvalidOperationException("Hydration can only be added to a Pokemon script source.");
_pokemon = pokemon;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
if (_pokemon is null)
return;
if (battle.WeatherName != ScriptUtils.ResolveName<Weather.Rain>())
return;
if (_pokemon.StatusScript.IsEmpty)
return;
EventBatchId batchId = new();
battle.EventHook.Invoke(new AbilityTriggerEvent(_pokemon)
{
BatchId = batchId,
});
_pokemon.ClearStatus(batchId);
}
}

View File

@ -0,0 +1,22 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Hyper Cutter is an ability that prevents the Pokémon's Attack stat from being lowered by other Pokémon.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Hyper_Cutter_(Ability)">Bulbapedia - Hyper Cutter</see>
/// </summary>
[Script(ScriptCategory.Ability, "hyper_cutter")]
public class HyperCutter : Script
{
/// <inheritdoc />
public override void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted,
ref bool prevent)
{
if (stat != Statistic.Attack)
return;
// Prevent the Attack stat from being lowered by any means
if (amount < 0 && !selfInflicted)
prevent = true;
}
}

View File

@ -0,0 +1,51 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Ice Body is an ability that heals the Pokémon for 1/16 of its maximum HP each turn during hail.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Ice_Body_(Ability)">Bulbapedia - Ice Body</see>
/// </summary>
[Script(ScriptCategory.Ability, "ice_body")]
public class IceBody : Script
{
private IPokemon? _pokemon;
/// <inheritdoc />
public override void OnAddedToParent(IScriptSource source)
{
if (source is not IPokemon pokemon)
throw new InvalidOperationException("Ice Body can only be added to a Pokemon script source.");
_pokemon = pokemon;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
if (_pokemon is null)
return;
// Check if the weather is hail
if (battle.WeatherName != ScriptUtils.ResolveName<Weather.Hail>())
return;
// Heal the Pokémon for 1/16 of its maximum HP
EventBatchId batchId = new();
var healAmount = _pokemon.MaxHealth / 16;
_pokemon.Heal(healAmount, true, batchId);
// Trigger the ability event
battle.EventHook.Invoke(new AbilityTriggerEvent(_pokemon)
{
BatchId = batchId,
});
}
/// <inheritdoc />
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? parameters)
{
if (eventName != CustomTriggers.IgnoreHail || parameters is null)
return;
parameters["ignoresHail"] = true;
}
}

View File

@ -0,0 +1,12 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Illuminate is an ability that increases the wild encounter rate when the Pokémon is leading the party.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Illuminate_(Ability)">Bulbapedia - Illuminate</see>
/// </summary>
[Script(ScriptCategory.Ability, "illuminate")]
public class Illuminate : Script
{
// No effect in battle.
}

View File

@ -0,0 +1,55 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Illusion is an ability that disguises the Pokémon as the last non-fainted Pokémon in the party until it takes damage.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Illusion_(Ability)">Bulbapedia - Illusion</see>
/// </summary>
[Script(ScriptCategory.Ability, "illusion")]
public class Illusion : Script
{
private IPokemon? _pokemon;
/// <inheritdoc />
public override void OnAddedToParent(IScriptSource source)
{
if (source is not IPokemon pokemon)
throw new InvalidOperationException("Illusion can only be added to a Pokemon script source.");
_pokemon = pokemon;
}
/// <inheritdoc />
public override void OnSwitchIn(IPokemon pokemon, byte position)
{
var battleData = pokemon.BattleData;
if (battleData is null)
return;
var lastNonFaintedPokemon = battleData.Battle.Parties.FirstOrDefault(p => p.Party.Any(pkmn => pkmn == pokemon))
?.Party.WhereNotNull().FirstOrDefault(x => x.IsUsable);
if (lastNonFaintedPokemon is null || lastNonFaintedPokemon == pokemon)
return;
pokemon.SetDisplaySpecies(lastNonFaintedPokemon.Species, lastNonFaintedPokemon.Form);
}
/// <inheritdoc />
public override void OnRemove()
{
if (_pokemon is null)
return;
_pokemon.SetDisplaySpecies(null, null);
_pokemon.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(_pokemon));
}
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (_pokemon?.BattleData?.Battle is null)
return;
// Remove the illusion when the Pokémon takes damage
_pokemon.SetDisplaySpecies(null, null);
_pokemon.BattleData.Battle.EventHook.Invoke(new AbilityTriggerEvent(_pokemon));
}
}

View File

@ -0,0 +1,21 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Immunity is an ability that prevents the Pokémon from being poisoned.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Immunity_(Ability)">Bulbapedia - Immunity</see>
/// </summary>
[Script(ScriptCategory.Ability, "immunity")]
public class Immunity : Script
{
/// <inheritdoc />
public override void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted,
ref bool preventStatus)
{
if (status == ScriptUtils.ResolveName<Status.Poisoned>() ||
status == ScriptUtils.ResolveName<Status.BadlyPoisoned>())
{
preventStatus = true;
}
}
}

View File

@ -0,0 +1,12 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Imposter is an ability that transforms the Pokémon into its opponent upon entering battle.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Imposter_(Ability)">Bulbapedia - Imposter</see>
/// </summary>
[Script(ScriptCategory.Ability, "imposter")]
public class Imposter : Script
{
// TODO: Implement Imposter effect.
}

View File

@ -0,0 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Infiltrator is an ability that allows the Pokémon's moves to ignore the opposing side's barriers and substitutes.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Infiltrator_(Ability)">Bulbapedia - Infiltrator</see>
/// </summary>
[Script(ScriptCategory.Ability, "infiltrator")]
public class Infiltrator : Script
{
/// <inheritdoc />
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? parameters)
{
if ((eventName != CustomTriggers.BypassProtection && eventName != CustomTriggers.BypassSubstitute) ||
parameters is null)
return;
parameters["bypass"] = true;
}
}

View File

@ -23,4 +23,8 @@ public static class CustomTriggers
public static readonly StringKey BypassChargeMove = "bypass_charge_move";
public static readonly StringKey ModifySleepTurns = "modify_sleep_turns";
public static readonly StringKey BypassProtection = "bypass_protection";
public static readonly StringKey BypassSubstitute = "bypass_subsitute";
}

View File

@ -1,3 +1,5 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
/// <summary>
@ -21,13 +23,18 @@ public class Autotomize : Script
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var user = move.User;
if (user.ChangeStatBoost(Statistic.Speed, 2, true, false) && user.ChangeWeightInKgBy(-100.0f))
{
var existingEffect = user.Volatile.Get<AutotomizeEffect>();
var stacks = existingEffect?.Stacks ?? 0;
if (!user.ChangeStatBoost(Statistic.Speed, 2, true, false) || !(user.WeightInKg - 100f * stacks >= 0.1f))
return;
user.Volatile.StackOrAdd(ScriptUtils.ResolveName<AutotomizeEffect>(), () => new AutotomizeEffect());
var battle = user.BattleData?.Battle;
battle?.EventHook.Invoke(new DialogEvent("pokemon_became_nimble", new Dictionary<string, object>
{
{ "pokemon", user },
}));
}
}
}

View File

@ -1,3 +1,4 @@
using PkmnLib.Plugin.Gen7.Scripts.Utils;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
@ -36,6 +37,8 @@ public class BatonPass : Script
foreach (var script in volatileScripts)
{
if (script is not IBatonPassException)
return;
toSwitch.Volatile.Add(script);
}
}

View File

@ -0,0 +1,21 @@
using PkmnLib.Plugin.Gen7.Scripts.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "autotomize")]
public class AutotomizeEffect : Script, IBatonPassException
{
public int Stacks { get; private set; } = 1;
/// <inheritdoc />
public override void Stack()
{
Stacks++;
}
/// <inheritdoc />
public override void ModifyWeight(ref float weight)
{
weight -= 100f * Stacks;
}
}

View File

@ -11,6 +11,18 @@ public class ProtectionEffectScript : Script
if (!executingMove.UseMove.HasFlag("protect"))
return;
var bypass = false;
var parameters = new Dictionary<StringKey, object?>
{
{ "target", target },
{ "move", executingMove },
{ "hitIndex", hitIndex },
{ "bypass", bypass },
};
executingMove.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassProtection, parameters));
bypass = parameters.GetValueOrDefault("bypass", false) as bool? ?? false;
if (bypass)
return;
block = true;
}

View File

@ -11,6 +11,19 @@ public class SubstituteEffect(uint health) : Script
if (executingMove.UseMove.HasFlag("ignore-substitute"))
return;
var bypass = false;
var parameters = new Dictionary<StringKey, object?>
{
{ "target", target },
{ "move", executingMove },
{ "hitIndex", hitIndex },
{ "bypass", bypass },
};
executingMove.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassProtection, parameters));
bypass = parameters.GetValueOrDefault("bypass", false) as bool? ?? false;
if (bypass)
return;
block = true;
var damage = executingMove.GetHitData(target, hitIndex).Damage;
if (damage >= _health)

View File

@ -0,0 +1,5 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Utils;
public interface IBatonPassException
{
}