diff --git a/PkmnLib.Dynamic/Models/Battle.cs b/PkmnLib.Dynamic/Models/Battle.cs index 7acf24c..ba7db5a 100644 --- a/PkmnLib.Dynamic/Models/Battle.cs +++ b/PkmnLib.Dynamic/Models/Battle.cs @@ -106,9 +106,16 @@ public interface IBattle : IScriptSource, IDeepCloneable bool TrySetChoice(ITurnChoice choice); /// - /// Sets the current weather for the battle. If null is passed, this clears the weather. + /// The script that handles the current weather of the battle. /// - void SetWeather(StringKey? weatherName); + IReadOnlyScriptContainer WeatherScript { get; } + + /// + /// Sets the current weather for the battle. If null is passed, this clears the weather. + /// A duration can be passed to set the duration of the weather in turns. This duration can be modified by + /// other scripts before the weather is set through the script hook. + /// + bool SetWeather(StringKey? weatherName, int duration); public IScriptSet Volatile { get; } @@ -343,28 +350,37 @@ public class BattleImpl : ScriptSource, IBattle EventHook.Invoke(new EndTurnEvent()); } - private readonly ScriptContainer _weatherScript = new(); + private ScriptContainer _weatherScript = new(); + public IReadOnlyScriptContainer WeatherScript => _weatherScript; /// - public void SetWeather(StringKey? weatherName) + public bool SetWeather(StringKey? weatherName, int duration) { if (weatherName.HasValue) { if (!Library.ScriptResolver.TryResolve(ScriptCategory.Weather, weatherName.Value, null, out var script)) throw new InvalidOperationException($"Weather script {weatherName} not found."); + + if (script is IWeatherScript weatherScript) + { + this.RunScriptHook(x => x.ChangeWeatherDuration(weatherName.Value, ref duration)); + weatherScript.SetTurns(duration); + } + _weatherScript.Set(script); } else { _weatherScript.Clear(); } + return true; // TODO: Trigger weather change script hooks } public IScriptSet Volatile { get; } = new ScriptSet(); /// - public StringKey? WeatherName => _weatherScript.Script?.Name; + public StringKey? WeatherName => WeatherScript.Script?.Name; private readonly ScriptContainer _terrainScript = new(); @@ -415,7 +431,7 @@ public class BattleImpl : ScriptSource, IBattle /// public override void GetOwnScripts(List> scripts) { - scripts.Add(_weatherScript); + scripts.Add(WeatherScript); scripts.Add(_terrainScript); scripts.Add(Volatile); } diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs index 320082e..f02e0b0 100644 --- a/PkmnLib.Dynamic/Models/Pokemon.cs +++ b/PkmnLib.Dynamic/Models/Pokemon.cs @@ -814,6 +814,7 @@ public class PokemonImpl : ScriptSource, IPokemon public void SuppressAbility() { AbilitySuppressed = true; + AbilityScript.Clear(); } /// @@ -1130,6 +1131,15 @@ public class PokemonImpl : ScriptSource, IPokemon public void ChangeAbility(IAbility ability) { OverrideAbility = ability; + if (Library.ScriptResolver.TryResolve(ScriptCategory.Ability, ability.Name, ability.Parameters, + out var abilityScript)) + { + AbilityScript.Set(abilityScript); + } + else + { + AbilityScript.Clear(); + } } /// diff --git a/PkmnLib.Dynamic/ScriptHandling/IWeatherScript.cs b/PkmnLib.Dynamic/ScriptHandling/IWeatherScript.cs new file mode 100644 index 0000000..a701b30 --- /dev/null +++ b/PkmnLib.Dynamic/ScriptHandling/IWeatherScript.cs @@ -0,0 +1,6 @@ +namespace PkmnLib.Dynamic.ScriptHandling; + +public interface IWeatherScript +{ + public void SetTurns(int turns); +} \ No newline at end of file diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs index 1f4ca91..4377dce 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Script.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs @@ -559,4 +559,8 @@ public abstract class Script : IDeepCloneable ref int modifiedAccuracy) { } + + public virtual void ChangeWeatherDuration(StringKey weatherName, ref int duration) + { + } } \ No newline at end of file diff --git a/PkmnLib.Dynamic/ScriptHandling/ScriptContainer.cs b/PkmnLib.Dynamic/ScriptHandling/ScriptContainer.cs index 7592b8a..ad9f2e8 100644 --- a/PkmnLib.Dynamic/ScriptHandling/ScriptContainer.cs +++ b/PkmnLib.Dynamic/ScriptHandling/ScriptContainer.cs @@ -4,11 +4,24 @@ using PkmnLib.Static.Utils; namespace PkmnLib.Dynamic.ScriptHandling; +public interface IReadOnlyScriptContainer : IEnumerable, IDeepCloneable +{ + /// + /// Whether this container is empty. + /// + public bool IsEmpty { get; } + + /// + /// The script in this container. + /// + public Script? Script { get; } +} + /// /// A holder class for a script. This is used so we can cache a list of these, and iterate over them, even when /// the underlying script changes. /// -public class ScriptContainer : IEnumerable, IDeepCloneable +public class ScriptContainer : IReadOnlyScriptContainer { /// public ScriptContainer() @@ -21,15 +34,11 @@ public class ScriptContainer : IEnumerable, IDeepCloneable Script = script; } - /// - /// Whether this container is empty. - /// + /// [MemberNotNullWhen(false, nameof(ScriptHandling.Script))] public bool IsEmpty => Script is null; - /// - /// The script in this container. - /// + /// public Script? Script { get; private set; } /// diff --git a/PkmnLib.Tests/Data/Moves.json b/PkmnLib.Tests/Data/Moves.json index 89ddf51..48201d3 100755 --- a/PkmnLib.Tests/Data/Moves.json +++ b/PkmnLib.Tests/Data/Moves.json @@ -4766,7 +4766,13 @@ "priority": 0, "target": "All", "category": "status", - "flags": [] + "flags": [], + "effect": { + "name": "set_weather", + "parameters": { + "weather": "hail" + } + } }, { "name": "hammer_arm", diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/SetWeather.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/SetWeather.cs new file mode 100644 index 0000000..d42332c --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/SetWeather.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "set_weather")] +public class SetWeather : Script +{ + private string _weather = null!; + private int _defaultTurns = 5; + + /// + public override void OnInitialize(IReadOnlyDictionary? parameters) + { + if (!parameters!.TryGetValue("weather", out var weather) || weather is null) + { + throw new Exception("Weather not provided."); + } + _weather = weather!.ToString(); + if (parameters.TryGetValue("turns", out var turns) && turns is int turn) + { + _defaultTurns = turn; + } + } + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + target.BattleData?.Battle.SetWeather(_weather, _defaultTurns); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs index 83b383e..fbdc70b 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs @@ -6,20 +6,15 @@ using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Scripts.Weather; [Script(ScriptCategory.Weather, "hail")] -public class Hail : Script +public class Hail : Script, IWeatherScript { - public static void RegisterHailIgnoreAbility(StringKey abilityName) - { - _hailIgnoreAbilities.Add(abilityName); - } + private int? _duration; - private static readonly HashSet _hailIgnoreAbilities = - [ - "ice_body", - "magic_guard", - "overcoat", - "snow_cloak", - ]; + /// + public void SetTurns(int turns) + { + _duration = turns; + } /// public override void OnEndTurn(IBattle battle) @@ -36,14 +31,22 @@ public class Hail : Script continue; if (pokemon.Types.Contains(iceType)) continue; - if (pokemon.ActiveAbility != null && _hailIgnoreAbilities.Contains(pokemon.ActiveAbility.Name)) + var ignoresHail = false; + pokemon.RunScriptHook(x => x.CustomTrigger("ignores_hail", new Dictionary() + { + { "ignoresHail", ignoresHail }, + })); + if (ignoresHail) continue; var maxHealth = pokemon.BoostedStats.Hp; var damage = maxHealth / 16; - // TODO: Consider Safety Goggles. Handle it inside the Damage method? pokemon.Damage(damage, DamageSource.Weather, new EventBatchId()); } } + + _duration--; + if (_duration <= 0) + RemoveSelf(); } } \ No newline at end of file