More abilities, refactor stealing held items
All checks were successful
Build / Build (push) Successful in 50s

This commit is contained in:
Deukhoofd 2025-06-15 11:49:15 +02:00
parent 1b9d137bb0
commit f5d18d7186
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
22 changed files with 326 additions and 21 deletions

View File

@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations; using JetBrains.Annotations;
using PkmnLib.Dynamic.Events; using PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Libraries;
@ -251,6 +252,12 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// </remarks> /// </remarks>
IItem? RemoveHeldItemForBattle(); IItem? RemoveHeldItemForBattle();
/// <summary>
/// Tries to steal the held item of the Pokémon. If successful, the item is removed from the Pokémon and returned.
/// If the Pokémon does not have a held item, or the item is a form changer, this will return false.
/// </summary>
bool TryStealHeldItem([NotNullWhen(true)] out IItem? item);
/// <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>
@ -812,6 +819,25 @@ public class PokemonImpl : ScriptSource, IPokemon
return _stolenHeldItem = RemoveHeldItem(); return _stolenHeldItem = RemoveHeldItem();
} }
/// <inheritdoc />
public bool TryStealHeldItem([NotNullWhen(true)] out IItem? item)
{
if (HeldItem is null || HeldItem.Category == ItemCategory.FormChanger)
{
item = null;
return false;
}
var prevent = false;
this.RunScriptHook(script => script.PreventHeldItemSteal(this, HeldItem, ref prevent));
if (prevent)
{
item = null;
return false;
}
item = RemoveHeldItemForBattle();
return item is not null;
}
/// <inheritdoc /> /// <inheritdoc />
public void RestoreStolenHeldItem() public void RestoreStolenHeldItem()
{ {

View File

@ -812,6 +812,13 @@ public abstract class Script : IDeepCloneable
{ {
} }
/// <summary>
/// This function allows a script to prevent a held item from being stolen by an effect such as Thief or Covet.
/// </summary>
public virtual void PreventHeldItemSteal(IPokemon pokemon, IItem heldItem, ref bool prevent)
{
}
/// <summary> /// <summary>
/// This function allows a script to run after a held item has changed. /// This function allows a script to run after a held item has changed.
/// </summary> /// </summary>

View File

@ -594,18 +594,37 @@
"speed_boost": { "speed_boost": {
"effect": "speed_boost" "effect": "speed_boost"
}, },
"stakeout": {}, "stakeout": {
"stall": {}, "effect": "stakeout"
"stamina": {}, },
"stall": {
"effect": "stall"
},
"stamina": {
"effect": "stamina"
},
"stance_change": { "stance_change": {
"effect": "stance_change",
"canBeChanged": false "canBeChanged": false
}, },
"static": {}, "static": {
"steadfast": {}, "effect": "static"
"steelworker": {}, },
"stench": {}, "steadfast": {
"sticky_hold": {}, "effect": "steadfast"
"storm_drain": {}, },
"steelworker": {
"effect": "steelworker"
},
"stench": {
"effect": "stench"
},
"sticky_hold": {
"effect": "sticky_hold"
},
"storm_drain": {
"effect": "storm_drain"
},
"strong_jaw": {}, "strong_jaw": {},
"sturdy": {}, "sturdy": {},
"suction_cups": {}, "suction_cups": {},

View File

@ -946,6 +946,7 @@
"flags": [], "flags": [],
"formes": { "formes": {
"blade": { "blade": {
"isBattleOnly": true,
"abilities": [ "abilities": [
"stance_change" "stance_change"
], ],

View File

@ -19,7 +19,10 @@ public class Magician : Script
if (move.User.HeldItem is not null || target.HeldItem is null) if (move.User.HeldItem is not null || target.HeldItem is null)
return; return;
if (!move.User.TryStealHeldItem(out var item))
return;
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)); move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
_ = move.User.SetHeldItem(target.RemoveHeldItemForBattle()); _ = move.User.SetHeldItem(item);
} }
} }

View File

@ -11,10 +11,10 @@ public class Pickpocket : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit) public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{ {
if (move.GetHitData(target, hit).IsContact && target.HeldItem is null && move.User.HeldItem is not null) if (!move.GetHitData(target, hit).IsContact || target.HeldItem is not null ||
{ !move.User.TryStealHeldItem(out var item))
return;
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)); move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
_ = target.SetHeldItem(move.User.RemoveHeldItemForBattle()); _ = target.SetHeldItem(item);
}
} }
} }

View File

@ -0,0 +1,17 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Stakeout is an ability that doubles the damage dealt to Pokémon that have switched in this turn.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Stakeout_(Ability)">Bulbapedia - Stakeout</see>
/// </summary>
[Script(ScriptCategory.Ability, "stakeout")]
public class Stakeout : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
{
if (target.BattleData?.SwitchInTurn == move.Battle.CurrentTurnNumber)
basePower = basePower.MultiplyOrMax(2);
}
}

View File

@ -0,0 +1,16 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Stall is an ability that makes the Pokémon move last in its priority bracket.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Stall_(Ability)">Bulbapedia - Stall</see>
/// </summary>
[Script(ScriptCategory.Ability, "stall")]
public class Stall : Script
{
/// <inheritdoc />
public override void ChangeSpeed(ITurnChoice choice, ref uint speed)
{
speed = 0;
}
}

View File

@ -0,0 +1,23 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Stamina is an ability that raises the user's Defense by one stage when hit by an attack.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Stamina_(Ability)">Bulbapedia - Stamina</see>
/// </summary>
[Script(ScriptCategory.Ability, "stamina")]
public class Stamina : Script
{
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
EventBatchId batchId = new();
if (target.ChangeStatBoost(Statistic.Defense, 1, true, false, batchId))
{
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)
{
BatchId = batchId,
});
}
}
}

View File

@ -0,0 +1,30 @@
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Stance Change is an ability that changes Aegislash's form depending on the move used.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Stance_Change_(Ability)">Bulbapedia - Stance Change</see>
/// </summary>
[Script(ScriptCategory.Ability, "stance_change")]
public class StanceChange : Script
{
/// <inheritdoc />
public override void OnBeforeMove(IExecutingMove move)
{
if (move.User.Species.Name != "aegislash")
return;
if (move.UseMove.Category is not (MoveCategory.Physical or MoveCategory.Special) ||
move.User.Form.Name == "blade" || !move.User.Species.TryGetForm("blade", out var bladeForm))
return;
EventBatchId batchId = new();
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)
{
BatchId = batchId,
});
move.User.ChangeForm(bladeForm, batchId);
// Kings shield is handled in the move script.
}
}

View File

@ -0,0 +1,31 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Static is an ability that may paralyze attackers using contact moves.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Static_(Ability)">Bulbapedia - Static</see>
/// </summary>
[Script(ScriptCategory.Ability, "static")]
public class Static : Script
{
private const int ChanceToParalyze = 30;
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (!move.GetHitData(target, hit).IsContact)
return;
if (move.Battle.Random.GetInt(0, 100) < ChanceToParalyze)
{
EventBatchId batchId = new();
if (target.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), false, batchId))
{
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)
{
BatchId = batchId,
});
}
}
}
}

View File

@ -0,0 +1,26 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Steadfast is an ability that raises the user's Speed each time it flinches.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Steadfast_(Ability)">Bulbapedia - Steadfast</see>
/// </summary>
[Script(ScriptCategory.Ability, "steadfast")]
public class Steadfast : Script
{
/// <inheritdoc />
public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args)
{
if (eventName != CustomTriggers.OnFlinch)
return;
if (args is not CustomTriggers.OnFlinchArgs flinchArgs)
return;
EventBatchId batchId = new();
flinchArgs.Move.Battle.EventHook.Invoke(new AbilityTriggerEvent(flinchArgs.Move.User)
{
BatchId = batchId,
});
flinchArgs.Move.User.ChangeStatBoost(Statistic.Speed, 1, true, false, batchId);
}
}

View File

@ -0,0 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Steelworker is an ability that boosts the power of Steel-type moves.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Steelworker_(Ability)">Bulbapedia - Steelworker</see>
/// </summary>
[Script(ScriptCategory.Ability, "steelworker")]
public class Steelworker : Script
{
/// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
{
if (move.GetHitData(target, hit).Type?.Name == "steel")
{
value = value.MultiplyOrMax(1.5f);
}
}
}

View File

@ -0,0 +1,21 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Stench is an ability that may cause the target to flinch when hit by a damaging move.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Stench_(Ability)">Bulbapedia - Stench</see>
/// </summary>
[Script(ScriptCategory.Ability, "stench")]
public class Stench : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (move.Battle.Random.GetInt(100) >= 10)
return;
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
target.Volatile.Add(new FlinchEffect());
}
}

View File

@ -0,0 +1,16 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Sticky Hold is an ability that prevents the Pokémon's held item from being taken.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Sticky_Hold_(Ability)">Bulbapedia - Sticky Hold</see>
/// </summary>
[Script(ScriptCategory.Ability, "sticky_hold")]
public class StickyHold : Script
{
/// <inheritdoc />
public override void PreventHeldItemSteal(IPokemon pokemon, IItem heldItem, ref bool prevent)
{
prevent = true;
}
}

View File

@ -0,0 +1,34 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Storm Drain is an ability that draws in all Water-type moves to up its Special Attack.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Storm_Drain_(Ability)">Bulbapedia - Storm Drain</see>
/// </summary>
[Script(ScriptCategory.Ability, "storm_drain")]
public class StormDrain : Script
{
/// <inheritdoc />
public override void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
{
if (moveChoice.ChosenMove.MoveData.MoveType.Name == "water" && targets.Count == 1)
{
targets = [moveChoice.User];
}
}
/// <inheritdoc />
public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
{
if (move.GetHitData(target, hit).Type?.Name != "water")
return;
effectiveness = 0f;
EventBatchId batchId = new();
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)
{
BatchId = batchId,
});
move.User.ChangeStatBoost(Statistic.SpecialAttack, 1, true, true, batchId);
}
}

View File

@ -132,4 +132,11 @@ public static class CustomTriggers
{ {
public byte NumberOfHits { get; set; } = NumberOfHits; public byte NumberOfHits { get; set; } = NumberOfHits;
} }
public static readonly StringKey OnFlinch = "on_flinch";
public record OnFlinchArgs(IExecutingMove Move) : ICustomTriggerArgs
{
public bool Prevent { get; set; } = false;
}
} }

View File

@ -13,11 +13,12 @@ public class BugBite : Script
var targetHeldItem = target.HeldItem; var targetHeldItem = target.HeldItem;
if (targetHeldItem is not { Category: ItemCategory.Berry }) if (targetHeldItem is not { Category: ItemCategory.Berry } || !target.TryStealHeldItem(out targetHeldItem))
{ {
move.GetHitData(target, hit).Fail(); move.GetHitData(target, hit).Fail();
return; return;
} }
_ = target.SetHeldItem(null); _ = target.SetHeldItem(null);
targetHeldItem.RunItemScript(battleData.Battle.Library.ScriptResolver, user); targetHeldItem.RunItemScript(battleData.Battle.Library.ScriptResolver, user);
} }

View File

@ -8,8 +8,8 @@ public class Covet : Script
{ {
if (target.HeldItem == null) if (target.HeldItem == null)
return; return;
if (move.User.HeldItem != null) if (!move.User.TryStealHeldItem(out var item))
return; return;
_ = move.User.SetHeldItem(target.RemoveHeldItemForBattle()); _ = move.User.SetHeldItem(item);
} }
} }

View File

@ -6,9 +6,11 @@ public class Incinerate : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
if (target.HeldItem is { Category: ItemCategory.Berry }) if (target.HeldItem is not { Category: ItemCategory.Berry } || !target.TryStealHeldItem(out _))
{ {
target.RemoveHeldItemForBattle(); move.GetHitData(target, hit).Fail();
} return;
}
// TODO: Add message for item incineration
} }
} }

View File

@ -6,7 +6,7 @@ public class KnockOff : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
if (target.RemoveHeldItemForBattle() is null) if (!target.TryStealHeldItem(out var item))
{ {
move.GetHitData(target, hit).Fail(); move.GetHitData(target, hit).Fail();
} }

View File

@ -7,6 +7,11 @@ public class FlinchEffect : Script
public override void PreventMove(IExecutingMove move, ref bool prevent) public override void PreventMove(IExecutingMove move, ref bool prevent)
{ {
prevent = true; prevent = true;
var args = new CustomTriggers.OnFlinchArgs(move);
move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.OnFlinch, args));
if (args.Prevent)
return;
RemoveSelf(); RemoveSelf();
} }
} }