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

This commit is contained in:
Deukhoofd 2025-06-09 18:16:29 +02:00
parent e68491e72a
commit 4326794611
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
26 changed files with 297 additions and 26 deletions

View File

@ -57,8 +57,7 @@ public static class MoveTurnExecutor
return;
}
var targetSide = battle.Sides[moveChoice.TargetSide];
targetSide.RunScriptHook(x => x.ChangeIncomingTargets(moveChoice, ref targets));
targets.WhereNotNull().RunScriptHook(x => x.ChangeIncomingTargets(moveChoice, ref targets));
byte numberOfHits = 1;
moveChoice.RunScriptHook(x => x.ChangeNumberOfHits(moveChoice, ref numberOfHits));
@ -154,6 +153,11 @@ public static class MoveTurnExecutor
break;
var useMove = executingMove.UseMove;
var isContact = useMove.HasFlag("contact");
executingMove.RunScriptHook(x => x.ModifyIsContact(executingMove, target, hitIndex, ref isContact));
hitData.IsContact = isContact;
var hitType = (TypeIdentifier?)useMove.MoveType;
executingMove.RunScriptHook(x => x.ChangeMoveType(executingMove, target, hitIndex, ref hitType));

View File

@ -37,6 +37,11 @@ public interface IHitData
/// </summary>
TypeIdentifier? Type { get; }
/// <summary>
/// Whether the hit is a contact hit. This is used to determine whether abilities that trigger on contact should be activated.
/// </summary>
bool IsContact { get; }
/// <summary>
/// Whether the hit has failed.
/// </summary>
@ -76,6 +81,9 @@ public record HitData : IHitData
/// <inheritdoc />
public TypeIdentifier? Type { get; internal set; }
/// <inheritdoc />
public bool IsContact { get; internal set; }
/// <inheritdoc />
public bool HasFailed { get; private set; }

View File

@ -769,4 +769,12 @@ public abstract class Script : IDeepCloneable
public virtual void ModifyWeight(ref float weight)
{
}
/// <summary>
/// Modifies whether a move is a contact move or not. This is used for abilities such as Long Reach.
/// </summary>
public virtual void ModifyIsContact(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref bool isContact)
{
}
}

View File

@ -315,15 +315,33 @@
"klutz": {
"effect": "klutz"
},
"leaf_guard": {},
"levitate": {},
"light_metal": {},
"lightning_rod": {},
"limber": {},
"liquid_ooze": {},
"liquid_voice": {},
"long_reach": {},
"magic_bounce": {},
"leaf_guard": {
"effect": "leaf_guard"
},
"levitate": {
"effect": "levitate"
},
"light_metal": {
"effect": "light_metal"
},
"lightning_rod": {
"effect": "lightning_rod"
},
"limber": {
"effect": "limber"
},
"liquid_ooze": {
"effect": "liquid_ooze"
},
"liquid_voice": {
"effect": "liquid_voice"
},
"long_reach": {
"effect": "long_reach"
},
"magic_bounce": {
"effect": "magic_bounce"
},
"magic_guard": {},
"magician": {},
"magma_armor": {},

View File

@ -14,7 +14,7 @@ public class Aftermath : Script
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
_lastAttack = move;
_lastAttack = !move.GetHitData(target, hit).IsContact ? move : null;
}
/// <inheritdoc />
@ -22,7 +22,7 @@ public class Aftermath : Script
{
if (source != DamageSource.MoveDamage)
return;
if (_lastAttack is null || !_lastAttack.UseMove.HasFlag("contact"))
if (_lastAttack is null)
return;
var user = _lastAttack.User;
if (!user.IsUsable)

View File

@ -17,7 +17,7 @@ public class CuteCharm : Script
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
// Only trigger on contact moves
if (!move.UseMove.HasFlag("contact"))
if (!move.GetHitData(target, hit).IsContact)
return;
// 30% chance to infatuate

View File

@ -19,7 +19,7 @@ public class EffectSpore : Script
return;
if (move.User.HasHeldItem("safety_goggles"))
return;
if (!move.UseMove.HasFlag("contact"))
if (!move.GetHitData(target, hit).IsContact)
return;
var rng = move.Battle.Random;

View File

@ -12,7 +12,7 @@ public class FlameBody : Script
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (!move.UseMove.HasFlag("contact"))
if (!move.GetHitData(target, hit).IsContact)
return;
var rng = move.Battle.Random;

View File

@ -16,7 +16,7 @@ public class Fluffy : Script
{
modifier *= 2f;
}
if (move.UseMove.HasFlag("contact"))
if (move.GetHitData(target, hit).IsContact)
{
modifier *= 0.5f;
}

View File

@ -11,7 +11,7 @@ public class Gooey : Script
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (!move.UseMove.HasFlag("contact"))
if (!move.GetHitData(target, hit).IsContact)
return;
move.User.ChangeStatBoost(Statistic.Speed, -1, false, false);
}

View File

@ -11,7 +11,7 @@ public class IronBarbs : Script
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (move.UseMove.HasFlag("contact"))
if (move.GetHitData(target, hit).IsContact)
{
move.User.Damage(move.User.MaxHealth / 8, DamageSource.Misc);
}

View File

@ -0,0 +1,21 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Leaf Guard is an ability that prevents status conditions in harsh sunlight.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Leaf_Guard_(Ability)">Bulbapedia - Leaf Guard</see>
/// </summary>
[Script(ScriptCategory.Ability, "leaf_guard")]
public class LeafGuard : Script
{
/// <inheritdoc />
public override void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted,
ref bool preventStatus)
{
if (pokemon.BattleData?.Battle.WeatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>())
return;
if (selfInflicted)
return;
preventStatus = true;
}
}

View File

@ -0,0 +1,16 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Levitate is an ability that gives full immunity to all Ground-type moves.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Levitate_(Ability)">Bulbapedia - Levitate</see>
/// </summary>
[Script(ScriptCategory.Ability, "levitate")]
public class Levitate : Script
{
/// <inheritdoc />
public override void IsFloating(IPokemon pokemon, ref bool isFloating)
{
isFloating = true;
}
}

View File

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

View File

@ -0,0 +1,34 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Lightning Rod is an ability that draws all Electric-type moves to the Pokémon, raising its Special Attack.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Lightning_Rod_(Ability)">Bulbapedia - Lightning Rod</see>
/// </summary>
[Script(ScriptCategory.Ability, "lightning_rod")]
public class LightningRod : Script
{
/// <inheritdoc />
public override void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
{
if (moveChoice.ChosenMove.MoveData.MoveType.Name == "electric" && 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 != "electric")
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

@ -0,0 +1,25 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Limber is an ability that prevents the Pokémon from being paralyzed.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Limber_(Ability)">Bulbapedia - Limber</see>
/// </summary>
[Script(ScriptCategory.Ability, "limber")]
public class Limber : Script
{
/// <inheritdoc />
public override void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted,
ref bool preventStatus)
{
if (status != ScriptUtils.ResolveName<Status.Paralyzed>())
return;
// If the status is being inflicted by a move, we can also prevent the move from inflicting it
if (selfInflicted)
return;
pokemon.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon));
preventStatus = true;
}
}

View File

@ -0,0 +1,19 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Liquid Ooze is an ability that damages attackers using draining moves instead of healing them.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Liquid_Ooze_(Ability)">Bulbapedia - Liquid Ooze</see>
/// </summary>
[Script(ScriptCategory.Ability, "liquid_ooze")]
public class LiquidOoze : Script
{
/// <inheritdoc />
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? parameters)
{
if (eventName != CustomTriggers.ModifyDrain || parameters is null)
return;
parameters["invert"] = true;
}
}

View File

@ -0,0 +1,21 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Liquid Voice is an ability that makes all sound-based moves become Water-type.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Liquid_Voice_(Ability)">Bulbapedia - Liquid Voice</see>
/// </summary>
[Script(ScriptCategory.Ability, "liquid_voice")]
public class LiquidVoice : Script
{
/// <inheritdoc />
public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit,
ref TypeIdentifier? typeIdentifier)
{
if (move.UseMove.HasFlag("sound") &&
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("water", out var waterType))
{
typeIdentifier = waterType;
}
}
}

View File

@ -0,0 +1,17 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Long Reach is an ability that allows the Pokémon to use contact moves without making contact.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Long_Reach_(Ability)">Bulbapedia - Long Reach</see>
/// </summary>
[Script(ScriptCategory.Ability, "long_reach")]
public class LongReach : Script
{
/// <inheritdoc />
public override void ModifyIsContact(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref bool isContact)
{
isContact = false;
}
}

View File

@ -0,0 +1,21 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Magic Bounce is an ability that reflects status moves back to the user.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Magic_Bounce_(Ability)">Bulbapedia - Magic Bounce</see>
/// </summary>
[Script(ScriptCategory.Ability, "magic_bounce")]
public class MagicBounce : Script
{
/// <inheritdoc />
public override void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
{
if (moveChoice.ChosenMove.MoveData.HasFlag("reflectable"))
{
var target = targets[0];
target?.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
targets = [moveChoice.User];
}
}
}

View File

@ -27,4 +27,6 @@ public static class CustomTriggers
public static readonly StringKey BypassProtection = "bypass_protection";
public static readonly StringKey BypassSubstitute = "bypass_subsitute";
public static readonly StringKey ModifyDrain = "modify_drain";
}

View File

@ -1,5 +1,3 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "drain")]
@ -24,6 +22,26 @@ public class Drain : Script
var healed = (uint)(damage * DrainModifier);
if (move.User.HasHeldItem("big_root"))
healed = (uint)(healed * 1.3f);
var invert = false;
var parameters = new Dictionary<StringKey, object?>
{
{ "user", user },
{ "target", target },
{ "damage", damage },
{ "healed", healed },
{ "invert", invert },
};
target.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyDrain, parameters));
if (parameters.TryGetValue("invert", out var invertObj) && invertObj is bool invertBool)
invert = invertBool;
if (invert)
{
user.Damage(damage, DamageSource.Misc);
}
else
{
user.Heal(healed);
}
}
}

View File

@ -18,6 +18,27 @@ public class DreamEater : Script
var healed = (uint)(damage * 0.5f);
if (move.User.HasHeldItem("big_root"))
healed = (uint)(healed * 1.3f);
var invert = false;
var parameters = new Dictionary<StringKey, object?>
{
{ "user", user },
{ "target", target },
{ "damage", damage },
{ "healed", healed },
{ "invert", invert },
};
target.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyDrain, parameters));
if (parameters.TryGetValue("invert", out var invertObj) && invertObj is bool invertBool)
invert = invertBool;
if (invert)
{
user.Damage(damage, DamageSource.Misc);
}
else
{
user.Heal(healed);
}
}
}

View File

@ -9,7 +9,8 @@ public class BanefulBunkerEffect : ProtectionEffectScript
public override void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{
base.BlockIncomingHit(executingMove, target, hitIndex, ref block);
if (executingMove.UseMove.Category != MoveCategory.Status && executingMove.UseMove.HasFlag("contact"))
if (executingMove.UseMove.Category != MoveCategory.Status &&
executingMove.GetHitData(target, hitIndex).IsContact)
{
executingMove.User.SetStatus("poisoned", false);
}

View File

@ -6,7 +6,7 @@ public class BeakBlastEffect : Script
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (move.UseMove.HasFlag("contact"))
if (move.GetHitData(target, hit).IsContact)
{
move.User.SetStatus("burned", false);
}

View File

@ -9,7 +9,8 @@ public class KingsShield : ProtectionEffectScript
public override void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{
base.BlockIncomingHit(executingMove, target, hitIndex, ref block);
if (executingMove.UseMove.Category != MoveCategory.Status && executingMove.UseMove.HasFlag("contact"))
if (executingMove.UseMove.Category != MoveCategory.Status &&
executingMove.GetHitData(target, hitIndex).IsContact)
{
executingMove.User.ChangeStatBoost(Statistic.Accuracy, -2, false, false);
}