More abilities, refactor IPokemon.SetStatus to pass pokemon that caused the status change
All checks were successful
Build / Build (push) Successful in 50s

This commit is contained in:
Deukhoofd 2025-06-15 12:29:13 +02:00
parent defb1349ca
commit 85d97cb9e6
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
37 changed files with 214 additions and 46 deletions

View File

@ -361,7 +361,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// <summary>
/// Adds a non-volatile status to the Pokemon.
/// </summary>
bool SetStatus(StringKey status, bool selfInflicted, EventBatchId batchId = default);
bool SetStatus(StringKey status, IPokemon? originPokemon, EventBatchId batchId = default);
/// <summary>
/// Removes the current non-volatile status from the Pokemon.
@ -1186,7 +1186,7 @@ public class PokemonImpl : ScriptSource, IPokemon
public bool HasStatus(StringKey status) => StatusScript.Script?.Name == status;
/// <inheritdoc />
public bool SetStatus(StringKey status, bool selfInflicted, EventBatchId batchId = default)
public bool SetStatus(StringKey status, IPokemon? originPokemon, EventBatchId batchId = default)
{
if (!Library.ScriptResolver.TryResolve(ScriptCategory.Status, status, null, out var statusScript))
throw new KeyNotFoundException($"Status script {status} not found");
@ -1194,6 +1194,7 @@ public class PokemonImpl : ScriptSource, IPokemon
if (!StatusScript.IsEmpty)
return false;
var oldStatus = StatusScript.Script?.Name;
var selfInflicted = originPokemon == this;
var preventStatus = false;
this.RunScriptHook(script => script.PreventStatusChange(this, status, selfInflicted, ref preventStatus));
@ -1206,6 +1207,7 @@ public class PokemonImpl : ScriptSource, IPokemon
{
BatchId = batchId,
});
this.RunScriptHook(script => script.OnAfterStatusChange(this, status, originPokemon));
return true;
}

View File

@ -767,6 +767,10 @@ public abstract class Script : IDeepCloneable
{
}
public virtual void OnAfterStatusChange(IPokemon pokemon, StringKey status, IPokemon? originPokemon)
{
}
/// <summary>
/// This function allows a script to prevent a Pokémon from being affected by a volatile status condition.
/// </summary>

View File

@ -36,13 +36,13 @@ public class EffectSpore : Script
switch (chance)
{
case < 9:
move.User.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), false, batchId);
move.User.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), move.User, batchId);
break;
case < 19:
move.User.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), false, batchId);
move.User.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), move.User, batchId);
break;
case < 30:
move.User.SetStatus(ScriptUtils.ResolveName<Status.Sleep>(), false, batchId);
move.User.SetStatus(ScriptUtils.ResolveName<Status.Sleep>(), move.User, batchId);
break;
}
}

View File

@ -20,6 +20,6 @@ public class FlameBody : Script
return;
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
move.User.SetStatus(ScriptUtils.ResolveName<Status.Burned>(), false);
move.User.SetStatus(ScriptUtils.ResolveName<Status.Burned>(), move.User);
}
}

View File

@ -12,6 +12,6 @@ public class PoisonPoint : Script
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (move.GetHitData(target, hit).IsContact)
move.User.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), false);
move.User.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), move.User);
}
}

View File

@ -14,6 +14,6 @@ public class PoisonTouch : Script
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (move.GetHitData(target, hit).IsContact && move.Battle.Random.GetInt(0, 100) < PoisonChance)
move.User.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), false);
move.User.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), target);
}
}

View File

@ -19,7 +19,7 @@ public class Static : Script
if (move.Battle.Random.GetInt(0, 100) < ChanceToParalyze)
{
EventBatchId batchId = new();
if (target.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), false, batchId))
if (target.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), target, batchId))
{
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)
{

View File

@ -0,0 +1,19 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Strong Jaw is an ability that boosts the power of biting moves.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Strong_Jaw_(Ability)">Bulbapedia - Strong Jaw</see>
/// </summary>
[Script(ScriptCategory.Ability, "strong_jaw")]
public class StrongJaw : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
{
if (move.UseMove.HasFlag("bite"))
{
basePower = basePower.MultiplyOrMax(1.5f);
}
}
}

View File

@ -0,0 +1,19 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Sturdy is an ability that allows the Pokémon to survive a one-hit KO attack with 1 HP.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Sturdy_(Ability)">Bulbapedia - Sturdy</see>
/// </summary>
[Script(ScriptCategory.Ability, "sturdy")]
public class Sturdy : Script
{
/// <inheritdoc />
public override void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
if (damage >= target.MaxHealth && target.CurrentHealth == target.MaxHealth)
{
damage = target.MaxHealth - 1;
}
}
}

View File

@ -0,0 +1,12 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Suction Cups is an ability that prevents the Pokémon from being forced to switch out.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Suction_Cups_(Ability)">Bulbapedia - Suction Cups</see>
/// </summary>
[Script(ScriptCategory.Ability, "suction_cups")]
public class SuctionCups : Script
{
// TODO: Implement Suction Cups ability logic
}

View File

@ -0,0 +1,18 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Super Luck is an ability that increases the critical hit ratio of the Pokémon's moves.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Super_Luck_(Ability)">Bulbapedia - Super Luck</see>
/// </summary>
[Script(ScriptCategory.Ability, "super_luck")]
public class SuperLuck : Script
{
/// <inheritdoc />
public override void ChangeCriticalStage(IExecutingMove move, IPokemon target, byte hit, ref byte stage)
{
if (stage == byte.MaxValue)
return;
stage++;
}
}

View File

@ -0,0 +1,19 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Surge Surfer is an ability that doubles the Pokémon's Speed while Electric Terrain is active.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Surge_Surfer_(Ability)">Bulbapedia - Surge Surfer</see>
/// </summary>
[Script(ScriptCategory.Ability, "surge_surfer")]
public class SurgeSurfer : Script
{
/// <inheritdoc />
public override void ChangeSpeed(ITurnChoice choice, ref uint speed)
{
if (choice.User.BattleData?.Battle.TerrainName == ScriptUtils.ResolveName<Terrain.ElectricTerrain>())
{
speed = speed.MultiplyOrMax(2);
}
}
}

View File

@ -0,0 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Swarm is an ability that powers up Bug-type moves when the Pokémon's HP is low.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Swarm_(Ability)">Bulbapedia - Swarm</see>
/// </summary>
[Script(ScriptCategory.Ability, "swarm")]
public class Swarm : 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 == "bug" && target.CurrentHealth <= target.MaxHealth / 3)
{
value = value.MultiplyOrMax(1.5f);
}
}
}

View File

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

View File

@ -0,0 +1,19 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Swift Swim is an ability that doubles the Pokémon's Speed in rain.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Swift_Swim_(Ability)">Bulbapedia - Swift Swim</see>
/// </summary>
[Script(ScriptCategory.Ability, "swift_swim")]
public class SwiftSwim : Script
{
/// <inheritdoc />
public override void ChangeSpeed(ITurnChoice choice, ref uint speed)
{
if (choice.User.BattleData?.Battle.WeatherName == ScriptUtils.ResolveName<Weather.Rain>())
{
speed = speed.MultiplyOrMax(2);
}
}
}

View File

@ -0,0 +1,12 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Symbiosis is an ability that passes the user's held item to an ally if the ally consumes its own held item.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Symbiosis_(Ability)">Bulbapedia - Symbiosis</see>
/// </summary>
[Script(ScriptCategory.Ability, "symbiosis")]
public class Symbiosis : Script
{
// TODO: Implement Symbiosis ability logic
}

View File

@ -0,0 +1,19 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Synchronize is an ability that passes major status conditions to the foe that inflicted them.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Synchronize_(Ability)">Bulbapedia - Synchronize</see>
/// </summary>
[Script(ScriptCategory.Ability, "synchronize")]
public class Synchronize : Script
{
/// <inheritdoc />
public override void OnAfterStatusChange(IPokemon pokemon, StringKey status, IPokemon? originPokemon)
{
if (originPokemon == null || pokemon == originPokemon)
return;
pokemon.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon));
originPokemon.SetStatus(status, pokemon);
}
}

View File

@ -34,7 +34,7 @@ public class Bounce : Script
var random = battle.Random;
if (random.EffectChance(30, move, target, hit))
{
target.SetStatus("paralyzed", false);
target.SetStatus("paralyzed", move.User);
}
}
}

View File

@ -14,7 +14,7 @@ public class FireFang : Script
var random = battleData.Battle.Random;
if (random.EffectChance(10, move, target, hit))
{
target.SetStatus("burned", false);
target.SetStatus("burned", move.User);
}
// It also has an independent 10% chance of causing the target to flinch, if the user attacks before the target.

View File

@ -28,7 +28,7 @@ public class FlameWheel : Script
if (move.Battle.Random.EffectChance(_burnChance, move, target, hit))
{
target.SetStatus("burned", false);
target.SetStatus("burned", move.User);
}
}
}

View File

@ -20,7 +20,7 @@ public class FlareBlitz : Script
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{
target.SetStatus("burned", false);
target.SetStatus("burned", move.User);
}
move.User.Damage(recoilDamage, DamageSource.Misc);

View File

@ -31,7 +31,7 @@ public class FreezeDry : Script
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{
target.SetStatus("frozen", false);
target.SetStatus("frozen", move.User);
}
}
}

View File

@ -17,7 +17,7 @@ public class FreezeShock : BaseChargeMove<RequireChargeEffect>
if (battleData.Battle.Random.EffectChance(30, move, target, hit))
{
target.SetStatus("paralyzed", false);
target.SetStatus("paralyzed", move.User);
}
}
}

View File

@ -17,7 +17,7 @@ public class IceBurn : BaseChargeMove<RequireChargeEffect>
if (battleData.Battle.Random.EffectChance(30, move, target, hit))
{
target.SetStatus("burned", false);
target.SetStatus("burned", move.User);
}
}
}

View File

@ -14,7 +14,7 @@ public class IceFang : Script
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{
target.SetStatus("frozen", false);
target.SetStatus("frozen", move.User);
}
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{

View File

@ -22,7 +22,7 @@ public class PoisonTail : Script
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{
target.SetStatus(ScriptUtils.ResolveName<BadlyPoisoned>(), false);
target.SetStatus(ScriptUtils.ResolveName<BadlyPoisoned>(), move.User);
}
}
}

View File

@ -24,7 +24,8 @@ public class Rest : Script
return;
}
if (move.User.SetStatus(ScriptUtils.ResolveName<Sleep>(), true) && move.User.StatusScript.Script is Sleep sleep)
if (move.User.SetStatus(ScriptUtils.ResolveName<Sleep>(), move.User) &&
move.User.StatusScript.Script is Sleep sleep)
sleep.Turns = 2;
}
}

View File

@ -20,6 +20,6 @@ public class SetStatus : Script
{
if (_status == null)
throw new Exception("Missing required parameter 'status'");
target.SetStatus(_status, false);
target.SetStatus(_status, move.User);
}
}

View File

@ -12,7 +12,7 @@ public class ThunderFang : Script
var random = move.Battle.Random;
if (random.EffectChance(10, move, target, hit))
{
target.SetStatus(ScriptUtils.ResolveName<Paralyzed>(), false);
target.SetStatus(ScriptUtils.ResolveName<Paralyzed>(), move.User);
}
if (random.EffectChance(10, move, target, hit))
{

View File

@ -6,7 +6,7 @@ public class ToxicThread : Script
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), false);
target.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), move.User);
target.ChangeStatBoost(Statistic.Speed, -1, false, false);
}
}

View File

@ -14,6 +14,6 @@ public class TriAttack : Script
ScriptUtils.ResolveName<Status.Paralyzed>(),
ScriptUtils.ResolveName<Status.Frozen>(),
]);
target.SetStatus(status, false);
target.SetStatus(status, move.User);
}
}

View File

@ -11,7 +11,7 @@ public class Twineedle : Script
{
if (move.Battle.Random.EffectChance(20, move, target, hit))
{
target.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), false);
target.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), move.User);
}
}
}

View File

@ -12,7 +12,7 @@ public class VoltTackle : Script
if (move.Battle.Random.EffectChance(10, move, target, hit))
{
target.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), false);
target.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), move.User);
}
}
}

View File

@ -12,7 +12,7 @@ public class BanefulBunkerEffect : ProtectionEffectScript
if (executingMove.UseMove.Category != MoveCategory.Status &&
executingMove.GetHitData(target, hitIndex).IsContact)
{
executingMove.User.SetStatus("poisoned", false);
executingMove.User.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), executingMove.User);
}
}
}

View File

@ -8,7 +8,7 @@ public class BeakBlastEffect : Script
{
if (move.GetHitData(target, hit).IsContact)
{
move.User.SetStatus("burned", false);
move.User.SetStatus(ScriptUtils.ResolveName<Status.Burned>(), move.User);
}
}
}

View File

@ -3,34 +3,18 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "yawn")]
public class YawnEffect : Script
{
private IPokemon? _pokemon;
private bool _hasDoneFirstTurn;
/// <inheritdoc />
public override void OnAddedToParent(IScriptSource source)
{
if (source is IPokemon pokemon)
{
_pokemon = pokemon;
}
else
{
throw new InvalidOperationException("YawnEffect can only be added to a Pokemon.");
}
}
/// <inheritdoc />
public override void OnEndTurn(IScriptSource owner, IBattle battle)
{
if (_pokemon == null)
{
if (owner is not IPokemon pokemon)
return;
}
if (!_hasDoneFirstTurn)
{
_hasDoneFirstTurn = true;
return;
}
_pokemon.SetStatus(ScriptUtils.ResolveName<Status.Sleep>(), true);
pokemon.SetStatus(ScriptUtils.ResolveName<Status.Sleep>(), pokemon);
}
}

View File

@ -9,6 +9,6 @@ public class ToxicSpikesEffect : Script
if (pokemon.IsFloating)
return;
pokemon.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), false);
pokemon.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), null);
}
}