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 PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Libraries;
@ -251,6 +252,12 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// </remarks>
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>
/// Restores the held item of a Pokémon if it was temporarily removed.
/// </summary>
@ -812,6 +819,25 @@ public class PokemonImpl : ScriptSource, IPokemon
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 />
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>
/// This function allows a script to run after a held item has changed.
/// </summary>

View File

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

View File

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

View File

@ -19,7 +19,10 @@ public class Magician : Script
if (move.User.HeldItem is not null || target.HeldItem is null)
return;
if (!move.User.TryStealHeldItem(out var item))
return;
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 />
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)
{
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
_ = target.SetHeldItem(move.User.RemoveHeldItemForBattle());
}
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));
_ = 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 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;
if (targetHeldItem is not { Category: ItemCategory.Berry })
if (targetHeldItem is not { Category: ItemCategory.Berry } || !target.TryStealHeldItem(out targetHeldItem))
{
move.GetHitData(target, hit).Fail();
return;
}
_ = target.SetHeldItem(null);
targetHeldItem.RunItemScript(battleData.Battle.Library.ScriptResolver, user);
}

View File

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

View File

@ -6,9 +6,11 @@ public class Incinerate : Script
/// <inheritdoc />
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 />
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();
}

View File

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