Adds a bunch more move scripts

This commit is contained in:
Deukhoofd 2024-11-02 12:59:55 +01:00
parent 6f2bd678a5
commit 44cd2ee03e
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
17 changed files with 492 additions and 1 deletions

View File

@ -0,0 +1,17 @@
namespace PkmnLib.Dynamic.Events;
public class DialogEvent : IEventData
{
/// <inheritdoc />
public EventBatchId BatchId { get; init; } = new();
public DialogEvent(string message, Dictionary<string, object>? parameters = null)
{
Message = message;
Parameters = parameters;
}
public string Message { get; set; }
public Dictionary<string, object>? Parameters { get; set; }
}

View File

@ -93,7 +93,14 @@ public interface IPokemon : IScriptSource
float WeightInKg { get; set; } float WeightInKg { get; set; }
/// <summary> /// <summary>
/// The height of the Pokemon in meters. /// Sets the weight of the Pokémon in kilograms. Returns whether the weight was changed.
/// </summary>
/// <param name="weightInKg">The new weight in kilograms</param>
/// <returns></returns>
public bool ChangeWeightInKgBy(float weightInKg);
/// <summary>
/// The height of the Pokémon in meters.
/// </summary> /// </summary>
float HeightInMeters { get; set; } float HeightInMeters { get; set; }
@ -543,6 +550,18 @@ public class PokemonImpl : ScriptSource, IPokemon
/// <inheritdoc /> /// <inheritdoc />
public float WeightInKg { get; set; } public float WeightInKg { get; set; }
/// <inheritdoc />
public bool ChangeWeightInKgBy(float weightInKg)
{
if (WeightInKg <= 0.1f)
return false;
var newWeight = WeightInKg + weightInKg;
if (newWeight <= 0.1f)
newWeight = 0.1f;
WeightInKg = newWeight;
return true;
}
/// <inheritdoc /> /// <inheritdoc />
public float HeightInMeters { get; set; } public float HeightInMeters { get; set; }

View File

@ -15,6 +15,8 @@ public static class ScriptUtils
/// </summary> /// </summary>
public static StringKey ResolveName(this Script script) => ResolveName(script.GetType()); public static StringKey ResolveName(this Script script) => ResolveName(script.GetType());
public static StringKey ResolveName<T>() where T : Script => ResolveName(typeof(T));
/// <summary> /// <summary>
/// Resolve name from the <see cref="ScriptAttribute"/> of the given type. /// Resolve name from the <see cref="ScriptAttribute"/> of the given type.
/// </summary> /// </summary>

View File

@ -14,8 +14,15 @@ namespace PkmnLib.Dynamic.ScriptHandling;
/// </summary> /// </summary>
public abstract class Script public abstract class Script
{ {
internal event Action<Script>? OnRemoveEvent;
private int _suppressCount; private int _suppressCount;
public void RemoveSelf()
{
OnRemoveEvent?.Invoke(this);
}
/// <summary> /// <summary>
/// The name of a script is its unique identifier. /// The name of a script is its unique identifier.
/// If not overridden, this will resolve the name from the <see cref="ScriptAttribute"/> of the /// If not overridden, this will resolve the name from the <see cref="ScriptAttribute"/> of the

View File

@ -79,6 +79,7 @@ public class ScriptSet : IScriptSet
return existing; return existing;
} }
script.OnRemoveEvent += s => Remove(s.Name);
var container = new ScriptContainer(script); var container = new ScriptContainer(script);
_scripts.Add(container); _scripts.Add(container);
return container; return container;
@ -97,6 +98,7 @@ public class ScriptSet : IScriptSet
var script = instantiation(); var script = instantiation();
if (script is null) if (script is null)
return null; return null;
script.OnRemoveEvent += s => Remove(s.Name);
var container = new ScriptContainer(script); var container = new ScriptContainer(script);
_scripts.Add(container); _scripts.Add(container);
return container; return container;
@ -111,6 +113,7 @@ public class ScriptSet : IScriptSet
var script = _scripts.FirstOrDefault(s => s.Script?.Name == scriptKey); var script = _scripts.FirstOrDefault(s => s.Script?.Name == scriptKey);
if (script is null) if (script is null)
return; return;
script.Script?.OnRemove();
_scripts.Remove(script); _scripts.Remove(script);
} }

View File

@ -0,0 +1,52 @@
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Plugin.Gen7.Scripts.Side;
using PkmnLib.Plugin.Gen7.Scripts.Weather;
namespace PkmnLib.Plugin.Gen7.Moves;
/// <summary>
/// This move reduces damage from physical and special moves for five turns. This can be used only in a hailstorm.
/// </summary>
/// <remarks>
/// Aurora Veil reduces the damage done to the user by physical and special moves for five turns by half; in battles
/// beside Single Battles, Aurora Veil protects the user and all allies, but only reduces damage by (roughly) a third
/// rather than half (specifically, with a multiplier of 2732/4096). It does not reduce damage done by self-inflicted
/// confusion damage, critical hits, or moves that deal direct damage. Aurora Veil can only be used during hail or snow,
/// but the effect remains even after hail or snow ends; Cloud Nine and Air Lock will cause Aurora Veil to fail even if
/// it is hailing or snowing.
/// <br/>
/// While it can be active at the same time as Reflect and Light Screen, the damage reduction effects do not stack.
/// Aurora Veil is removed from a Pokémon's side of the field if it is hit by Brick Break, Defog, Psychic Fangs, or
/// Raging Bull, or if a Pokémon with Screen Cleaner is sent out. Pokémon with the Ability Infiltrator ignore the effects
/// of Aurora Veil when attacking.
/// <br/>
/// If a Light Clay is held when Aurora Veil is used, it will extend the duration of Aurora Veil from 5 to 8 turns.
/// </remarks>
[Script(ScriptCategory.Move, "aurora_veil")]
public class AuroraVeil : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battle = move.User.BattleData?.Battle;
if (battle == null)
return;
if (battle.WeatherName != ScriptUtils.ResolveName<Hail>())
{
move.GetHitData(target, hit).Fail();
return;
}
var side = battle.Sides[move.User.BattleData!.SideIndex];
var numberOfTurns = move.User.HasHeldItem("light_clay") ? 8 : 5;
var script = side.VolatileScripts.StackOrAdd(ScriptUtils.ResolveName<AuroraVeilEffect>(), () =>
{
var effect = new AuroraVeilEffect(numberOfTurns);
return effect;
});
((AuroraVeilEffect)script!.Script!).NumberOfTurns = numberOfTurns;
}
}

View File

@ -0,0 +1,41 @@
using System.Collections.Generic;
using PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Moves;
/// <summary>
/// The user sheds part of its body to make itself lighter and sharply raise its Speed stat.
/// </summary>
/// <remarks>
/// Autotomize raises the user's Speed stat by two stages and (if the user successfully changes its Speed) decreases its
/// weight by 220 lbs. (100 kg). If the user successfully changes its weight, the message "Pokémon became nimble!"
/// is displayed.
/// <br />
/// Autotomize cannot decrease the user's weight below the minimum 0.2 lbs (0.1 kg); if the user's weight would drop
/// below the minimum, it becomes the minimum instead. Weight loss from Autotomize stacks, so using it multiple times
/// will continue to decrease the user's weight accordingly until it reaches the minimum weight. Autotomize's weight
/// reduction cannot be transferred by Baton Pass or removed by Haze. A Pokémon's weight is reset if it changes form
/// (from Generation VI onward), switches out or faints, or the battle ends.
/// </remarks>
[Script(ScriptCategory.Move, "autotomize")]
public class Autotomize : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var user = move.User;
if (user.ChangeStatBoost(Statistic.Speed, 2, true) &&
user.ChangeWeightInKgBy(-100.0f))
{
var battle = user.BattleData?.Battle;
battle?.EventHook.Invoke(new DialogEvent("pokemon_became_nimble", new Dictionary<string, object>()
{
{ "pokemon", user }
}));
}
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.Events;
[Script(ScriptCategory.Move, "change_all_target_stats")]
public class ChangeAllTargetStats : Script
{
private sbyte _amount;
/// <inheritdoc />
public override void OnInitialize(IDynamicLibrary library, IReadOnlyDictionary<StringKey, object?>? parameters)
{
if (parameters == null)
{
throw new ArgumentNullException(nameof(parameters));
}
if (!parameters.TryGetValue("amount", out var amount) || amount == null)
{
throw new ArgumentException("Parameter 'amount' is required.");
}
_amount = (sbyte)amount;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.ChangeStatBoost(Statistic.Attack, _amount, target == move.User);
target.ChangeStatBoost(Statistic.Defense, _amount, target == move.User);
target.ChangeStatBoost(Statistic.SpecialAttack, _amount, target == move.User);
target.ChangeStatBoost(Statistic.SpecialDefense, _amount, target == move.User);
target.ChangeStatBoost(Statistic.Speed, _amount, target == move.User);
}
}

View File

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
public abstract class ChangeTargetStats : Script
{
private readonly Statistic _stat;
private sbyte _amount;
protected ChangeTargetStats(Statistic stat)
{
_stat = stat;
}
/// <inheritdoc />
public override void OnInitialize(IDynamicLibrary library, IReadOnlyDictionary<StringKey, object?>? parameters)
{
if (parameters == null)
{
throw new ArgumentNullException(nameof(parameters));
}
if (!parameters.TryGetValue("amount", out var amount) || amount == null)
{
throw new ArgumentException("Parameter 'amount' is required.");
}
_amount = (sbyte)amount;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.ChangeStatBoost(_stat, _amount, target == move.User);
}
}
[Script(ScriptCategory.Move, "change_target_attack")]
public class ChangeTargetAttack : ChangeTargetStats
{
public ChangeTargetAttack() : base(Statistic.Attack)
{
}
}
[Script(ScriptCategory.Move, "change_target_defense")]
public class ChangeTargetDefense : ChangeTargetStats
{
public ChangeTargetDefense() : base(Statistic.Defense)
{
}
}
[Script(ScriptCategory.Move, "change_target_special_attack")]
public class ChangeTargetSpecialAttack : ChangeTargetStats
{
public ChangeTargetSpecialAttack() : base(Statistic.SpecialAttack)
{
}
}
[Script(ScriptCategory.Move, "change_target_special_defense")]
public class ChangeTargetSpecialDefense : ChangeTargetStats
{
public ChangeTargetSpecialDefense() : base(Statistic.SpecialDefense)
{
}
}
[Script(ScriptCategory.Move, "change_target_speed")]
public class ChangeTargetSpeed : ChangeTargetStats
{
public ChangeTargetSpeed() : base(Statistic.Speed)
{
}
}

View File

@ -0,0 +1,27 @@
using System.Linq;
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "cure_party_status")]
public class CurePartyStatus : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var user = move.User;
var battle = user.BattleData?.Battle;
if (battle == null)
return;
var party = battle.Parties.FirstOrDefault(p => p.Party.Contains(user));
if (party == null)
return;
foreach (var pokemon in party.Party.WhereNotNull())
{
pokemon.ClearStatus();
}
}
}

View File

@ -0,0 +1,39 @@
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Plugin.Gen7.Scripts.Side;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "double_power_if_target_damaged_in_turn")]
public class DoublePowerIfTargetDamagedInTurn : Script
{
/// <inheritdoc />
public override void OnBeforeTurnStart(ITurnChoice choice)
{
if (choice is IMoveChoice moveChoice)
{
var battle = choice.User.BattleData?.Battle;
if (battle == null)
return;
var side = battle.Sides[moveChoice.TargetSide];
side.VolatileScripts.Add(new DoublePowerIfTargetDamagedInTurnData());
}
}
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower)
{
var battle = move.User.BattleData?.Battle;
if (battle == null)
return;
var side = battle.Sides[target.BattleData!.SideIndex];
var script = side.VolatileScripts.Get(ScriptUtils.ResolveName<DoublePowerIfTargetDamagedInTurnData>());
if (script?.Script == null)
return;
var data = (DoublePowerIfTargetDamagedInTurnData)script.Script;
if (data._hitPokemon.Contains(target))
basePower *= 2;
}
}

View File

@ -0,0 +1,36 @@
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "struggle")]
public class Struggle : Script
{
/// <inheritdoc />
public override void ChangeNumberOfHits(IMoveChoice choice, ref byte numberOfHits)
{
numberOfHits = 1;
}
/// <inheritdoc />
public override void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable)
{
invulnerable = false;
}
/// <inheritdoc />
public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
{
effectiveness = 1;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var damage = move.User.MaxHealth / 4;
if (damage == 0) damage = 1;
move.User.Damage(damage, DamageSource.Struggle, new());
}
}

View File

@ -0,0 +1,71 @@
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
/// <summary>
/// This move reduces damage from physical and special moves for five turns. This can be used only in a hailstorm.
/// </summary>
/// <remarks>
/// Aurora Veil reduces the damage done to the user by physical and special moves for five turns by half; in battles
/// beside Single Battles, Aurora Veil protects the user and all allies, but only reduces damage by (roughly) a third
/// rather than half (specifically, with a multiplier of 2732/4096). It does not reduce damage done by self-inflicted
/// confusion damage, critical hits, or moves that deal direct damage. Aurora Veil can only be used during hail or snow,
/// but the effect remains even after hail or snow ends; Cloud Nine and Air Lock will cause Aurora Veil to fail even if
/// it is hailing or snowing.
/// <br/>
/// While it can be active at the same time as Reflect and Light Screen, the damage reduction effects do not stack.
/// Aurora Veil is removed from a Pokémon's side of the field if it is hit by Brick Break, Defog, Psychic Fangs, or
/// Raging Bull, or if a Pokémon with Screen Cleaner is sent out. Pokémon with the Ability Infiltrator ignore the effects
/// of Aurora Veil when attacking.
/// <br/>
/// If a Light Clay is held when Aurora Veil is used, it will extend the duration of Aurora Veil from 5 to 8 turns.
/// </remarks>
[Script(ScriptCategory.Side, "aurora_veil")]
public class AuroraVeilEffect : Script
{
public int NumberOfTurns { get; set; }
public AuroraVeilEffect(int numberOfTurns)
{
NumberOfTurns = numberOfTurns;
}
/// <inheritdoc />
public override void OnEndTurn()
{
if (NumberOfTurns > 0)
NumberOfTurns--;
if (NumberOfTurns == 0)
RemoveSelf();
}
/// <inheritdoc />
public override void ChangeIncomingDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
var hitData = move.GetHitData(target, hit);
if (hitData.IsCritical)
return;
if (move.User.BattleData?.SideIndex == target.BattleData?.SideIndex)
return;
var targetSide = target.BattleData?.SideIndex;
if (targetSide == null)
return;
var side = move.User.BattleData!.Battle.Sides[targetSide.Value];
switch (move.UseMove.Category)
{
case MoveCategory.Physical when
side.VolatileScripts.Contains(ScriptUtils.ResolveName<ReflectEffect>()):
case MoveCategory.Special when
side.VolatileScripts.Contains(ScriptUtils.ResolveName<LightScreenEffect>()):
return;
}
var modifier = 0.5f;
if (target.BattleData!.Battle.PositionsPerSide > 1)
modifier = 2732f / 4096f;
damage = (uint)(damage * modifier);
}
}

View File

@ -0,0 +1,24 @@
using System.Collections.Generic;
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
[Script(ScriptCategory.Side, "double_power_if_target_damaged_in_turn_data")]
public class DoublePowerIfTargetDamagedInTurnData : Script
{
public HashSet<IPokemon> _hitPokemon = new();
/// <inheritdoc />
public override void OnEndTurn()
{
RemoveSelf();
}
/// <inheritdoc />
public override void OnDamage(IPokemon pokemon, DamageSource source, uint oldHealth, uint newHealth)
{
_hitPokemon.Add(pokemon);
}
}

View File

@ -0,0 +1,10 @@
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
[Script(ScriptCategory.Side, "light_screen")]
public class LightScreenEffect : Script
{
// TODO: Implement LightScreenEffect
}

View File

@ -0,0 +1,10 @@
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
[Script(ScriptCategory.Side, "reflect")]
public class ReflectEffect : Script
{
// TODO: Implement ReflectEffect
}

View File

@ -0,0 +1,10 @@
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
[Script(ScriptCategory.Weather, "hail")]
public class Hail : Script
{
// TODO: Implement Hail weather effect
}