Tweaks for setting weather

This commit is contained in:
Deukhoofd 2025-03-08 10:26:45 +01:00
parent a6c73a9c04
commit 8f262cb4a6
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
8 changed files with 114 additions and 28 deletions

View File

@ -106,9 +106,16 @@ public interface IBattle : IScriptSource, IDeepCloneable
bool TrySetChoice(ITurnChoice choice); bool TrySetChoice(ITurnChoice choice);
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
void SetWeather(StringKey? weatherName); IReadOnlyScriptContainer WeatherScript { get; }
/// <summary>
/// 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 <see cref="Script.ChangeWeatherDuration"/> script hook.
/// </summary>
bool SetWeather(StringKey? weatherName, int duration);
public IScriptSet Volatile { get; } public IScriptSet Volatile { get; }
@ -343,28 +350,37 @@ public class BattleImpl : ScriptSource, IBattle
EventHook.Invoke(new EndTurnEvent()); EventHook.Invoke(new EndTurnEvent());
} }
private readonly ScriptContainer _weatherScript = new(); private ScriptContainer _weatherScript = new();
public IReadOnlyScriptContainer WeatherScript => _weatherScript;
/// <inheritdoc /> /// <inheritdoc />
public void SetWeather(StringKey? weatherName) public bool SetWeather(StringKey? weatherName, int duration)
{ {
if (weatherName.HasValue) if (weatherName.HasValue)
{ {
if (!Library.ScriptResolver.TryResolve(ScriptCategory.Weather, weatherName.Value, null, out var script)) if (!Library.ScriptResolver.TryResolve(ScriptCategory.Weather, weatherName.Value, null, out var script))
throw new InvalidOperationException($"Weather script {weatherName} not found."); 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); _weatherScript.Set(script);
} }
else else
{ {
_weatherScript.Clear(); _weatherScript.Clear();
} }
return true;
// TODO: Trigger weather change script hooks // TODO: Trigger weather change script hooks
} }
public IScriptSet Volatile { get; } = new ScriptSet(); public IScriptSet Volatile { get; } = new ScriptSet();
/// <inheritdoc /> /// <inheritdoc />
public StringKey? WeatherName => _weatherScript.Script?.Name; public StringKey? WeatherName => WeatherScript.Script?.Name;
private readonly ScriptContainer _terrainScript = new(); private readonly ScriptContainer _terrainScript = new();
@ -415,7 +431,7 @@ public class BattleImpl : ScriptSource, IBattle
/// <inheritdoc /> /// <inheritdoc />
public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts) public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts)
{ {
scripts.Add(_weatherScript); scripts.Add(WeatherScript);
scripts.Add(_terrainScript); scripts.Add(_terrainScript);
scripts.Add(Volatile); scripts.Add(Volatile);
} }

View File

@ -814,6 +814,7 @@ public class PokemonImpl : ScriptSource, IPokemon
public void SuppressAbility() public void SuppressAbility()
{ {
AbilitySuppressed = true; AbilitySuppressed = true;
AbilityScript.Clear();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -1130,6 +1131,15 @@ public class PokemonImpl : ScriptSource, IPokemon
public void ChangeAbility(IAbility ability) public void ChangeAbility(IAbility ability)
{ {
OverrideAbility = ability; OverrideAbility = ability;
if (Library.ScriptResolver.TryResolve(ScriptCategory.Ability, ability.Name, ability.Parameters,
out var abilityScript))
{
AbilityScript.Set(abilityScript);
}
else
{
AbilityScript.Clear();
}
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -0,0 +1,6 @@
namespace PkmnLib.Dynamic.ScriptHandling;
public interface IWeatherScript
{
public void SetTurns(int turns);
}

View File

@ -559,4 +559,8 @@ public abstract class Script : IDeepCloneable
ref int modifiedAccuracy) ref int modifiedAccuracy)
{ {
} }
public virtual void ChangeWeatherDuration(StringKey weatherName, ref int duration)
{
}
} }

View File

@ -4,11 +4,24 @@ using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.ScriptHandling; namespace PkmnLib.Dynamic.ScriptHandling;
public interface IReadOnlyScriptContainer : IEnumerable<ScriptContainer>, IDeepCloneable
{
/// <summary>
/// Whether this container is empty.
/// </summary>
public bool IsEmpty { get; }
/// <summary>
/// The script in this container.
/// </summary>
public Script? Script { get; }
}
/// <summary> /// <summary>
/// A holder class for a script. This is used so we can cache a list of these, and iterate over them, even when /// 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. /// the underlying script changes.
/// </summary> /// </summary>
public class ScriptContainer : IEnumerable<ScriptContainer>, IDeepCloneable public class ScriptContainer : IReadOnlyScriptContainer
{ {
/// <inheritdoc cref="ScriptContainer"/> /// <inheritdoc cref="ScriptContainer"/>
public ScriptContainer() public ScriptContainer()
@ -21,15 +34,11 @@ public class ScriptContainer : IEnumerable<ScriptContainer>, IDeepCloneable
Script = script; Script = script;
} }
/// <summary> /// <inheritdoc />
/// Whether this container is empty.
/// </summary>
[MemberNotNullWhen(false, nameof(ScriptHandling.Script))] [MemberNotNullWhen(false, nameof(ScriptHandling.Script))]
public bool IsEmpty => Script is null; public bool IsEmpty => Script is null;
/// <summary> /// <inheritdoc />
/// The script in this container.
/// </summary>
public Script? Script { get; private set; } public Script? Script { get; private set; }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -4766,7 +4766,13 @@
"priority": 0, "priority": 0,
"target": "All", "target": "All",
"category": "status", "category": "status",
"flags": [] "flags": [],
"effect": {
"name": "set_weather",
"parameters": {
"weather": "hail"
}
}
}, },
{ {
"name": "hammer_arm", "name": "hammer_arm",

View File

@ -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;
/// <inheritdoc />
public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? 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;
}
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.BattleData?.Battle.SetWeather(_weather, _defaultTurns);
}
}

View File

@ -6,20 +6,15 @@ using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Weather; namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
[Script(ScriptCategory.Weather, "hail")] [Script(ScriptCategory.Weather, "hail")]
public class Hail : Script public class Hail : Script, IWeatherScript
{ {
public static void RegisterHailIgnoreAbility(StringKey abilityName) private int? _duration;
{
_hailIgnoreAbilities.Add(abilityName);
}
private static readonly HashSet<StringKey> _hailIgnoreAbilities = /// <inheritdoc />
[ public void SetTurns(int turns)
"ice_body", {
"magic_guard", _duration = turns;
"overcoat", }
"snow_cloak",
];
/// <inheritdoc /> /// <inheritdoc />
public override void OnEndTurn(IBattle battle) public override void OnEndTurn(IBattle battle)
@ -36,14 +31,22 @@ public class Hail : Script
continue; continue;
if (pokemon.Types.Contains(iceType)) if (pokemon.Types.Contains(iceType))
continue; continue;
if (pokemon.ActiveAbility != null && _hailIgnoreAbilities.Contains(pokemon.ActiveAbility.Name)) var ignoresHail = false;
pokemon.RunScriptHook(x => x.CustomTrigger("ignores_hail", new Dictionary<StringKey, object?>()
{
{ "ignoresHail", ignoresHail },
}));
if (ignoresHail)
continue; continue;
var maxHealth = pokemon.BoostedStats.Hp; var maxHealth = pokemon.BoostedStats.Hp;
var damage = maxHealth / 16; var damage = maxHealth / 16;
// TODO: Consider Safety Goggles. Handle it inside the Damage method?
pokemon.Damage(damage, DamageSource.Weather, new EventBatchId()); pokemon.Damage(damage, DamageSource.Weather, new EventBatchId());
} }
} }
_duration--;
if (_duration <= 0)
RemoveSelf();
} }
} }