More abilities

This commit is contained in:
Deukhoofd 2025-06-15 13:20:58 +02:00
parent ec8681eaa9
commit cd6095455a
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
21 changed files with 251 additions and 29 deletions

View File

@ -235,7 +235,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// Changes the held item of the Pokemon. Returns the previously held item. /// Changes the held item of the Pokemon. Returns the previously held item.
/// </summary> /// </summary>
[MustUseReturnValue] [MustUseReturnValue]
IItem? SetHeldItem(IItem? item); IItem? ForceSetHeldItem(IItem? item);
/// <summary> /// <summary>
/// Removes the held item from the Pokemon. Returns the previously held item. /// Removes the held item from the Pokemon. Returns the previously held item.
@ -243,6 +243,8 @@ public interface IPokemon : IScriptSource, IDeepCloneable
[MustUseReturnValue] [MustUseReturnValue]
IItem? RemoveHeldItem(); IItem? RemoveHeldItem();
bool HasItemBeenRemovedForBattle { get; }
/// <summary> /// <summary>
/// Removes the held item from the Pokemon for the duration of the battle. Returns the previously held item. /// Removes the held item from the Pokemon for the duration of the battle. Returns the previously held item.
/// </summary> /// </summary>
@ -261,7 +263,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// <summary> /// <summary>
/// Restores the held item of a Pokémon if it was temporarily removed. /// Restores the held item of a Pokémon if it was temporarily removed.
/// </summary> /// </summary>
void RestoreStolenHeldItem(); void RestoreRemovedHeldItem();
/// <summary> /// <summary>
/// Makes the Pokemon uses its held item. Returns whether the item was consumed. /// Makes the Pokemon uses its held item. Returns whether the item was consumed.
@ -788,7 +790,7 @@ public class PokemonImpl : ScriptSource, IPokemon
public bool HasHeldItem(StringKey itemName) => HeldItem?.Name == itemName; public bool HasHeldItem(StringKey itemName) => HeldItem?.Name == itemName;
/// <inheritdoc /> /// <inheritdoc />
public IItem? SetHeldItem(IItem? item) public IItem? ForceSetHeldItem(IItem? item)
{ {
var previous = HeldItem; var previous = HeldItem;
HeldItem = item; HeldItem = item;
@ -812,12 +814,15 @@ public class PokemonImpl : ScriptSource, IPokemon
return previous; return previous;
} }
private IItem? _stolenHeldItem; /// <inheritdoc />
public bool HasItemBeenRemovedForBattle => _removedHeldItem is not null;
private IItem? _removedHeldItem;
/// <inheritdoc /> /// <inheritdoc />
public IItem? RemoveHeldItemForBattle() public IItem? RemoveHeldItemForBattle()
{ {
return _stolenHeldItem = RemoveHeldItem(); return _removedHeldItem = RemoveHeldItem();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -840,10 +845,10 @@ public class PokemonImpl : ScriptSource, IPokemon
} }
/// <inheritdoc /> /// <inheritdoc />
public void RestoreStolenHeldItem() public void RestoreRemovedHeldItem()
{ {
_ = SetHeldItem(_stolenHeldItem); _ = ForceSetHeldItem(_removedHeldItem);
_stolenHeldItem = null; _removedHeldItem = null;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -863,7 +868,7 @@ public class PokemonImpl : ScriptSource, IPokemon
BattleData.MarkItemAsConsumed(HeldItem); BattleData.MarkItemAsConsumed(HeldItem);
} }
UseItem(SetHeldItem(null)!); UseItem(ForceSetHeldItem(null)!);
return true; return true;
} }

View File

@ -82,4 +82,22 @@ public static class NumericHelpers
var result = value * multiplier; var result = value * multiplier;
return result > uint.MaxValue ? uint.MaxValue : (uint)result; return result > uint.MaxValue ? uint.MaxValue : (uint)result;
} }
/// <summary>
/// Multiplies two values. If this overflows, returns <see cref="uint.MaxValue"/>.
/// </summary>
public static int MultiplyOrMax(this int value, int multiplier)
{
var result = (long)value * multiplier;
return result > int.MaxValue ? int.MaxValue : (int)result;
}
/// <summary>
/// Multiplies two values. If this overflows, returns <see cref="uint.MaxValue"/>.
/// </summary>
public static int MultiplyOrMax(this int value, float multiplier)
{
var result = value * multiplier;
return result > int.MaxValue ? int.MaxValue : (int)result;
}
} }

View File

@ -695,18 +695,37 @@
"cant_be_copied" "cant_be_copied"
] ]
}, },
"triage": {}, "triage": {
"truant": { "effect": "triage"
"canBeChanged": false },
"truant": {
"canBeChanged": false,
"effect": "truant"
},
"turboblaze": {
"effect": "teravolt"
},
"unaware": {
"effect": "unaware"
},
"unburden": {
"effect": "unburden"
},
"unnerve": {
"effect": "unnerve"
},
"victory_star": {
"effect": "victory_star"
},
"vital_spirit": {
"effect": "vital_spirit"
},
"volt_absorb": {
"effect": "volt_absorb"
},
"water_absorb": {
"effect": "water_absorb"
}, },
"turboblaze": {},
"unaware": {},
"unburden": {},
"unnerve": {},
"victory_star": {},
"vital_spirit": {},
"volt_absorb": {},
"water_absorb": {},
"water_bubble": {}, "water_bubble": {},
"water_compaction": {}, "water_compaction": {},
"water_veil": {}, "water_veil": {},

View File

@ -33,7 +33,7 @@ public class Harvest : Script
if (battle.WeatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>() && rng.GetInt(1) != 0) if (battle.WeatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>() && rng.GetInt(1) != 0)
return; return;
battle.EventHook.Invoke(new AbilityTriggerEvent(_pokemon)); battle.EventHook.Invoke(new AbilityTriggerEvent(_pokemon));
_ = _pokemon.SetHeldItem(consumedBerry); _ = _pokemon.ForceSetHeldItem(consumedBerry);
} }
} }
} }

View File

@ -23,6 +23,6 @@ public class Magician : Script
return; return;
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)); move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
_ = move.User.SetHeldItem(item); _ = move.User.ForceSetHeldItem(item);
} }
} }

View File

@ -15,6 +15,6 @@ public class Pickpocket : Script
!move.User.TryStealHeldItem(out var item)) !move.User.TryStealHeldItem(out var item))
return; return;
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)); move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
_ = target.SetHeldItem(item); _ = target.ForceSetHeldItem(item);
} }
} }

View File

@ -0,0 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Triage is an ability that gives priority to healing moves.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Triage_(Ability)">Bulbapedia - Triage</see>
/// </summary>
[Script(ScriptCategory.Ability, "triage")]
public class Triage : Script
{
/// <inheritdoc />
public override void ChangePriority(IMoveChoice choice, ref sbyte priority)
{
if (!choice.ChosenMove.MoveData.HasFlag("heal"))
return;
if (priority == sbyte.MaxValue)
return;
priority++;
}
}

View File

@ -0,0 +1,18 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Truant is an ability that causes the Pokémon to only be able to move every other turn.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Truant_(Ability)">Bulbapedia - Truant</see>
/// </summary>
[Script(ScriptCategory.Ability, "truant")]
public class Truant : Script
{
/// <inheritdoc />
public override void OnAfterMove(IExecutingMove move)
{
move.User.Volatile.Add(new TruantEffect(move.User));
}
}

View File

@ -0,0 +1,24 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Unaware is an ability that ignores the opposing Pokémon's stat changes when attacking or defending.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Unaware_(Ability)">Bulbapedia - Unaware</see>
/// </summary>
[Script(ScriptCategory.Ability, "unaware")]
public class Unaware : Script
{
/// <inheritdoc />
public override void ChangeIncomingMoveOffensiveStatValue(IExecutingMove executingMove, IPokemon target,
byte hitNumber, uint defensiveStat, StatisticSet<uint> targetStats, Statistic offensive, ref uint offensiveStat)
{
offensiveStat = executingMove.User.FlatStats.GetStatistic(offensive);
}
/// <inheritdoc />
public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{
value = target.FlatStats.GetStatistic(stat);
}
}

View File

@ -0,0 +1,19 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Unburden is an ability that doubles the Pokémon's Speed if it loses its held item.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Unburden_(Ability)">Bulbapedia - Unburden</see>
/// </summary>
[Script(ScriptCategory.Ability, "unburden")]
public class Unburden : Script
{
/// <inheritdoc />
public override void ChangeSpeed(ITurnChoice choice, ref uint speed)
{
if (choice.User.HasItemBeenRemovedForBattle)
{
speed = speed.MultiplyOrMax(2);
}
}
}

View File

@ -0,0 +1,12 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Unnerve is an ability that prevents opposing Pokémon from using their held Berries.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Unnerve_(Ability)">Bulbapedia - Unnerve</see>
/// </summary>
[Script(ScriptCategory.Ability, "unnerve")]
public class Unnerve : Script
{
// TODO: Implement Unnerve ability logic
}

View File

@ -0,0 +1,17 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Victory Star is an ability that boosts the accuracy of the Pokémon and its allies.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Victory_Star_(Ability)">Bulbapedia - Victory Star</see>
/// </summary>
[Script(ScriptCategory.Ability, "victory_star")]
public class VictoryStar : Script
{
/// <inheritdoc />
public override void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref int modifiedAccuracy)
{
modifiedAccuracy = modifiedAccuracy.MultiplyOrMax(1.1f);
}
}

View File

@ -0,0 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Vital Spirit is an ability that prevents the Pokémon from falling asleep.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Vital_Spirit_(Ability)">Bulbapedia - Vital Spirit</see>
/// </summary>
[Script(ScriptCategory.Ability, "vital_spirit")]
public class VitalSpirit : Script
{
/// <inheritdoc />
public override void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted,
ref bool preventStatus)
{
if (status == ScriptUtils.ResolveName<Status.Sleep>() && !selfInflicted)
{
preventStatus = true;
}
}
}

View File

@ -0,0 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Volt Absorb is an ability that heals the Pokémon when hit by an Electric-type move.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Volt_Absorb_(Ability)">Bulbapedia - Volt Absorb</see>
/// </summary>
[Script(ScriptCategory.Ability, "volt_absorb")]
public class VoltAbsorb : Script
{
/// <inheritdoc />
public override void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable)
{
if (move.GetHitData(target, 0).Type?.Name != "electric")
return;
invulnerable = true;
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
target.Heal(target.MaxHealth / 4);
}
}

View File

@ -0,0 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Water Absorb is an ability that heals the Pokémon when hit by a Water-type move.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Water_Absorb_(Ability)">Bulbapedia - Water Absorb</see>
/// </summary>
[Script(ScriptCategory.Ability, "water_absorb")]
public class WaterAbsorb : Script
{
/// <inheritdoc />
public override void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable)
{
if (move.GetHitData(target, 0).Type?.Name != "water")
return;
invulnerable = true;
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
target.Heal(target.MaxHealth / 4);
}
}

View File

@ -16,6 +16,6 @@ public class Bestow : Script
return; return;
} }
_ = target.SetHeldItem(userHeldItem); _ = target.ForceSetHeldItem(userHeldItem);
} }
} }

View File

@ -19,7 +19,7 @@ public class BugBite : Script
return; return;
} }
_ = target.SetHeldItem(null); _ = target.ForceSetHeldItem(null);
targetHeldItem.RunItemScript(battleData.Battle.Library.ScriptResolver, user, move.Battle.EventHook); targetHeldItem.RunItemScript(battleData.Battle.Library.ScriptResolver, user, move.Battle.EventHook);
} }
} }

View File

@ -10,6 +10,6 @@ public class Covet : Script
return; return;
if (!move.User.TryStealHeldItem(out var item)) if (!move.User.TryStealHeldItem(out var item))
return; return;
_ = move.User.SetHeldItem(item); _ = move.User.ForceSetHeldItem(item);
} }
} }

View File

@ -20,6 +20,6 @@ public class Recycle : Script
move.GetHitData(target, hit).Fail(); move.GetHitData(target, hit).Fail();
return; return;
} }
_ = move.User.SetHeldItem(lastItem); _ = move.User.ForceSetHeldItem(lastItem);
} }
} }

View File

@ -18,7 +18,7 @@ public class Switcheroo : Script
targetHeldItem = target.RemoveHeldItem(); targetHeldItem = target.RemoveHeldItem();
userHeldItem = move.User.RemoveHeldItem(); userHeldItem = move.User.RemoveHeldItem();
_ = target.SetHeldItem(userHeldItem); _ = target.ForceSetHeldItem(userHeldItem);
_ = move.User.SetHeldItem(targetHeldItem); _ = move.User.ForceSetHeldItem(targetHeldItem);
} }
} }

View File

@ -0,0 +1,10 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
public class TruantEffect(IPokemon owner) : Script
{
/// <inheritdoc />
public override void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice)
{
choice = new PassChoice(owner);
}
}