This commit is contained in:
parent
af0126e413
commit
00005aa4bf
@ -8,16 +8,10 @@ namespace PkmnLib.Dynamic.Events;
|
|||||||
/// For example, when a Pokemon gets hurt by poison, we want to show the purple poison animation and the damage at the
|
/// For example, when a Pokemon gets hurt by poison, we want to show the purple poison animation and the damage at the
|
||||||
/// same time. This is done by batching the events together.
|
/// same time. This is done by batching the events together.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public readonly record struct EventBatchId
|
public readonly record struct EventBatchId()
|
||||||
{
|
{
|
||||||
/// <inheritdoc cref="EventBatchId"/>
|
|
||||||
public EventBatchId()
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The unique identifier for this batch of events.
|
/// The unique identifier for this batch of events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Guid Id { get; init; }
|
public Guid Id { get; init; } = Guid.NewGuid();
|
||||||
}
|
}
|
36
PkmnLib.Dynamic/Events/StatusChangeEvent.cs
Normal file
36
PkmnLib.Dynamic/Events/StatusChangeEvent.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using PkmnLib.Dynamic.Models;
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an event that occurs when a Pokémon's status changes.
|
||||||
|
/// </summary>
|
||||||
|
public record StatusChangeEvent : IEventData
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="StatusChangeEvent"/>
|
||||||
|
public StatusChangeEvent(IPokemon pokemon, StringKey? previousStatus, StringKey? newStatus)
|
||||||
|
{
|
||||||
|
Pokemon = pokemon;
|
||||||
|
PreviousStatus = previousStatus;
|
||||||
|
NewStatus = newStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Pokémon whose status has changed.
|
||||||
|
/// </summary>
|
||||||
|
public IPokemon Pokemon { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The new status of the Pokémon after the change.
|
||||||
|
/// </summary>
|
||||||
|
public StringKey? NewStatus { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The previous status of the Pokémon before the change.
|
||||||
|
/// </summary>
|
||||||
|
public StringKey? PreviousStatus { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public EventBatchId BatchId { get; init; }
|
||||||
|
}
|
18
PkmnLib.Dynamic/Events/TerrainChangeEvent.cs
Normal file
18
PkmnLib.Dynamic/Events/TerrainChangeEvent.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.Events;
|
||||||
|
|
||||||
|
public class TerrainChangeEvent : IEventData
|
||||||
|
{
|
||||||
|
public StringKey? OldTerrain { get; }
|
||||||
|
public StringKey? NewTerrain { get; }
|
||||||
|
|
||||||
|
public TerrainChangeEvent(StringKey? oldTerrain, StringKey? newTerrain)
|
||||||
|
{
|
||||||
|
OldTerrain = oldTerrain;
|
||||||
|
NewTerrain = newTerrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public EventBatchId BatchId { get; init; }
|
||||||
|
}
|
18
PkmnLib.Dynamic/Events/WeatherChangeEvent.cs
Normal file
18
PkmnLib.Dynamic/Events/WeatherChangeEvent.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.Events;
|
||||||
|
|
||||||
|
public class WeatherChangeEvent : IEventData
|
||||||
|
{
|
||||||
|
public StringKey? OldWeather { get; }
|
||||||
|
public StringKey? NewWeather { get; }
|
||||||
|
|
||||||
|
public WeatherChangeEvent(StringKey? oldWeather, StringKey? newWeather)
|
||||||
|
{
|
||||||
|
OldWeather = oldWeather;
|
||||||
|
NewWeather = newWeather;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public EventBatchId BatchId { get; init; }
|
||||||
|
}
|
@ -106,6 +106,11 @@ public class SerializedForm
|
|||||||
/// <inheritdoc cref="PkmnLib.Static.Species.IForm.Flags"/>
|
/// <inheritdoc cref="PkmnLib.Static.Species.IForm.Flags"/>
|
||||||
public string[] Flags { get; set; } = [];
|
public string[] Flags { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the form is a battle-only form, meaning it should return to its original form after the battle ends.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsBattleOnly { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Additional data that is not part of the standard form data.
|
/// Additional data that is not part of the standard form data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -108,7 +108,7 @@ public static class SpeciesDataLoader
|
|||||||
return new FormImpl(name, form.Height, form.Weight, form.BaseExp, types, DeserializeStats(form.BaseStats),
|
return new FormImpl(name, form.Height, form.Weight, form.BaseExp, types, DeserializeStats(form.BaseStats),
|
||||||
form.Abilities.Select(x => new StringKey(x)).ToList(),
|
form.Abilities.Select(x => new StringKey(x)).ToList(),
|
||||||
form.HiddenAbilities.Select(x => new StringKey(x)).ToList(), DeserializeMoves(form.Moves),
|
form.HiddenAbilities.Select(x => new StringKey(x)).ToList(), DeserializeMoves(form.Moves),
|
||||||
form.Flags.Select(x => new StringKey(x)).ToImmutableHashSet());
|
form.Flags.Select(x => new StringKey(x)).ToImmutableHashSet(), form.IsBattleOnly || form.IsMega);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ILearnableMoves DeserializeMoves(SerializedMoves moves)
|
private static ILearnableMoves DeserializeMoves(SerializedMoves moves)
|
||||||
|
@ -13,7 +13,7 @@ namespace PkmnLib.Dynamic.Models;
|
|||||||
/// A battle is a representation of a battle in the Pokemon games. It contains all the information needed
|
/// A battle is a representation of a battle in the Pokemon games. It contains all the information needed
|
||||||
/// to simulate a battle, and can be used to simulate a battle between two parties.
|
/// to simulate a battle, and can be used to simulate a battle between two parties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IBattle : IScriptSource, IDeepCloneable
|
public interface IBattle : IScriptSource, IDeepCloneable, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The library the battle uses for handling.
|
/// The library the battle uses for handling.
|
||||||
@ -40,6 +40,12 @@ public interface IBattle : IScriptSource, IDeepCloneable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
byte PositionsPerSide { get; }
|
byte PositionsPerSide { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this battle is a wild battle. In a wild battle, the player can catch the opposing Pokemon,
|
||||||
|
/// and moves like roar will end the battle instead of switching out the Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
bool IsWildBattle { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A list of all sides in the battle.
|
/// A list of all sides in the battle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -93,6 +99,11 @@ public interface IBattle : IScriptSource, IDeepCloneable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void ValidateBattleState();
|
void ValidateBattleState();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Forcefully ends the battle. This will set the result to inconclusive and set HasEnded to true.
|
||||||
|
/// </summary>
|
||||||
|
void ForceEndBattle();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks whether a Pokemon has a forced turn choice. If it does, this returns true and the choice
|
/// Checks whether a Pokemon has a forced turn choice. If it does, this returns true and the choice
|
||||||
/// is set in the out parameter. If it does not, this returns false and the out parameter is null.
|
/// is set in the out parameter. If it does not, this returns false and the out parameter is null.
|
||||||
@ -119,7 +130,7 @@ public interface IBattle : IScriptSource, IDeepCloneable
|
|||||||
/// A duration can be passed to set the duration of the weather in turns. This duration can be modified by
|
/// 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.
|
/// other scripts before the weather is set through the <see cref="Script.ChangeWeatherDuration"/> script hook.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool SetWeather(StringKey? weatherName, int duration);
|
bool SetWeather(StringKey? weatherName, int duration, EventBatchId batchId = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Volatile scripts are scripts that are not permanent and can be removed by other scripts.
|
/// Volatile scripts are scripts that are not permanent and can be removed by other scripts.
|
||||||
@ -134,8 +145,7 @@ public interface IBattle : IScriptSource, IDeepCloneable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the current terrain for the battle. If null is passed, this clears the terrain.
|
/// Sets the current terrain for the battle. If null is passed, this clears the terrain.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="terrainName"></param>
|
void SetTerrain(StringKey? terrainName, EventBatchId batchId = default);
|
||||||
void SetTerrain(StringKey? terrainName);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current terrain of the battle. If no terrain is present, this returns null.
|
/// Gets the current terrain of the battle. If no terrain is present, this returns null.
|
||||||
@ -165,13 +175,14 @@ public class BattleImpl : ScriptSource, IBattle
|
|||||||
/// <param name="positionsPerSide">The number of spots there are on each side for Pokémon. 1 for singles, 2 for doubles, etc.</param>
|
/// <param name="positionsPerSide">The number of spots there are on each side for Pokémon. 1 for singles, 2 for doubles, etc.</param>
|
||||||
/// <param name="randomSeed">The seed for the RNG. If null, this uses a time-dependent seed.</param>
|
/// <param name="randomSeed">The seed for the RNG. If null, this uses a time-dependent seed.</param>
|
||||||
public BattleImpl(IDynamicLibrary library, IReadOnlyList<IBattleParty> parties, bool canFlee, byte numberOfSides,
|
public BattleImpl(IDynamicLibrary library, IReadOnlyList<IBattleParty> parties, bool canFlee, byte numberOfSides,
|
||||||
byte positionsPerSide, int? randomSeed = null)
|
byte positionsPerSide, bool isWildBattle, int? randomSeed = null)
|
||||||
{
|
{
|
||||||
Library = library;
|
Library = library;
|
||||||
Parties = parties;
|
Parties = parties;
|
||||||
CanFlee = canFlee;
|
CanFlee = canFlee;
|
||||||
NumberOfSides = numberOfSides;
|
NumberOfSides = numberOfSides;
|
||||||
PositionsPerSide = positionsPerSide;
|
PositionsPerSide = positionsPerSide;
|
||||||
|
IsWildBattle = isWildBattle;
|
||||||
Volatile = new ScriptSet(this);
|
Volatile = new ScriptSet(this);
|
||||||
var sides = new IBattleSide[numberOfSides];
|
var sides = new IBattleSide[numberOfSides];
|
||||||
for (byte i = 0; i < numberOfSides; i++)
|
for (byte i = 0; i < numberOfSides; i++)
|
||||||
@ -196,6 +207,9 @@ public class BattleImpl : ScriptSource, IBattle
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public byte PositionsPerSide { get; }
|
public byte PositionsPerSide { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsWildBattle { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyList<IBattleSide> Sides { get; }
|
public IReadOnlyList<IBattleSide> Sides { get; }
|
||||||
|
|
||||||
@ -263,6 +277,13 @@ public class BattleImpl : ScriptSource, IBattle
|
|||||||
HasEnded = true;
|
HasEnded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void ForceEndBattle()
|
||||||
|
{
|
||||||
|
HasEnded = true;
|
||||||
|
Result = BattleResult.Inconclusive;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool HasForcedTurn(IPokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice)
|
public bool HasForcedTurn(IPokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice)
|
||||||
{
|
{
|
||||||
@ -376,10 +397,28 @@ public class BattleImpl : ScriptSource, IBattle
|
|||||||
public IReadOnlyScriptContainer WeatherScript => _weatherScript;
|
public IReadOnlyScriptContainer WeatherScript => _weatherScript;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool SetWeather(StringKey? weatherName, int duration)
|
public bool SetWeather(StringKey? weatherName, int duration, EventBatchId batchId = default)
|
||||||
{
|
{
|
||||||
|
var preventWeatherChange = false;
|
||||||
|
this.RunScriptHook(x => x.PreventWeatherChange(weatherName, ref preventWeatherChange));
|
||||||
|
if (preventWeatherChange)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var oldWeatherName = WeatherScript.Script?.Name;
|
||||||
if (weatherName.HasValue)
|
if (weatherName.HasValue)
|
||||||
{
|
{
|
||||||
|
if (weatherName == oldWeatherName)
|
||||||
|
{
|
||||||
|
// Extend duration of existing weather
|
||||||
|
if (_weatherScript.Script is ILimitedTurnsScript existingWeatherScript)
|
||||||
|
{
|
||||||
|
this.RunScriptHook(x => x.ChangeWeatherDuration(weatherName.Value, ref duration));
|
||||||
|
if (duration < existingWeatherScript.TurnsRemaining)
|
||||||
|
return true;
|
||||||
|
existingWeatherScript.SetTurns(duration);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
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.");
|
||||||
|
|
||||||
@ -396,8 +435,13 @@ public class BattleImpl : ScriptSource, IBattle
|
|||||||
{
|
{
|
||||||
_weatherScript.Clear();
|
_weatherScript.Clear();
|
||||||
}
|
}
|
||||||
|
EventHook.Invoke(new WeatherChangeEvent(oldWeatherName, weatherName)
|
||||||
|
{
|
||||||
|
BatchId = batchId,
|
||||||
|
});
|
||||||
|
Sides.SelectMany(x => x.Pokemon).WhereNotNull()
|
||||||
|
.RunScriptHook(x => x.OnWeatherChange(this, weatherName, oldWeatherName));
|
||||||
return true;
|
return true;
|
||||||
// TODO: Trigger weather change script hooks
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -409,12 +453,14 @@ public class BattleImpl : ScriptSource, IBattle
|
|||||||
private readonly ScriptContainer _terrainScript = new();
|
private readonly ScriptContainer _terrainScript = new();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void SetTerrain(StringKey? terrainName)
|
public void SetTerrain(StringKey? terrainName, EventBatchId batchId = default)
|
||||||
{
|
{
|
||||||
|
var oldTerrainName = TerrainName;
|
||||||
if (terrainName.HasValue)
|
if (terrainName.HasValue)
|
||||||
{
|
{
|
||||||
if (!Library.ScriptResolver.TryResolve(ScriptCategory.Terrain, terrainName.Value, null, out var script))
|
if (!Library.ScriptResolver.TryResolve(ScriptCategory.Terrain, terrainName.Value, null, out var script))
|
||||||
throw new InvalidOperationException($"Terrain script {terrainName} not found.");
|
throw new InvalidOperationException($"Terrain script {terrainName} not found.");
|
||||||
|
|
||||||
_terrainScript.Set(script);
|
_terrainScript.Set(script);
|
||||||
script.OnAddedToParent(this);
|
script.OnAddedToParent(this);
|
||||||
}
|
}
|
||||||
@ -422,6 +468,10 @@ public class BattleImpl : ScriptSource, IBattle
|
|||||||
{
|
{
|
||||||
_terrainScript.Clear();
|
_terrainScript.Clear();
|
||||||
}
|
}
|
||||||
|
EventHook.Invoke(new TerrainChangeEvent(oldTerrainName, terrainName)
|
||||||
|
{
|
||||||
|
BatchId = batchId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -463,4 +513,19 @@ public class BattleImpl : ScriptSource, IBattle
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void CollectScripts(List<IEnumerable<ScriptContainer>> scripts) => GetOwnScripts(scripts);
|
public override void CollectScripts(List<IEnumerable<ScriptContainer>> scripts) => GetOwnScripts(scripts);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var party in Parties)
|
||||||
|
{
|
||||||
|
foreach (var pokemon in party.Party.WhereNotNull())
|
||||||
|
{
|
||||||
|
pokemon.ClearBattleData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_weatherScript.Clear();
|
||||||
|
_terrainScript.Clear();
|
||||||
|
Volatile.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
@ -35,4 +35,9 @@ public enum DamageSource
|
|||||||
/// The damage is done because of a status condition.
|
/// The damage is done because of a status condition.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Status = 5,
|
Status = 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The damage is done due to the Pokémon being confused and hitting itself.
|
||||||
|
/// </summary>
|
||||||
|
Confusion = 6,
|
||||||
}
|
}
|
@ -354,12 +354,12 @@ public interface IPokemon : IScriptSource, IDeepCloneable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a non-volatile status to the Pokemon.
|
/// Adds a non-volatile status to the Pokemon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool SetStatus(StringKey status);
|
bool SetStatus(StringKey status, EventBatchId batchId = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the current non-volatile status from the Pokemon.
|
/// Removes the current non-volatile status from the Pokemon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void ClearStatus();
|
void ClearStatus(EventBatchId batchId = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Modifies the level by a certain amount
|
/// Modifies the level by a certain amount
|
||||||
@ -382,6 +382,11 @@ public interface IPokemon : IScriptSource, IDeepCloneable
|
|||||||
/// <param name="position"></param>
|
/// <param name="position"></param>
|
||||||
void SetBattleSidePosition(byte position);
|
void SetBattleSidePosition(byte position);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the battle data of the Pokémon. This is called when the battle ends.
|
||||||
|
/// </summary>
|
||||||
|
void ClearBattleData();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks a Pokemon as seen in the battle.
|
/// Marks a Pokemon as seen in the battle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -475,6 +480,16 @@ public interface IPokemonBattleData : IDeepCloneable
|
|||||||
/// The side the Pokémon is on.
|
/// The side the Pokémon is on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IBattleSide BattleSide { get; }
|
IBattleSide BattleSide { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The species of the Pokémon at the time it was sent out.
|
||||||
|
/// </summary>
|
||||||
|
ISpecies OriginalSpecies { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The form of the Pokémon at the time it was sent out.
|
||||||
|
/// </summary>
|
||||||
|
IForm OriginalForm { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="IPokemon"/>
|
/// <inheritdoc cref="IPokemon"/>
|
||||||
@ -928,7 +943,7 @@ public class PokemonImpl : ScriptSource, IPokemon
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void ChangeForm(IForm form, EventBatchId batchId)
|
public void ChangeForm(IForm form, EventBatchId batchId = default)
|
||||||
{
|
{
|
||||||
if (form == Form)
|
if (form == Form)
|
||||||
return;
|
return;
|
||||||
@ -1120,13 +1135,14 @@ public class PokemonImpl : ScriptSource, IPokemon
|
|||||||
public bool HasStatus(StringKey status) => StatusScript.Script?.Name == status;
|
public bool HasStatus(StringKey status) => StatusScript.Script?.Name == status;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool SetStatus(StringKey status)
|
public bool SetStatus(StringKey status, EventBatchId batchId = default)
|
||||||
{
|
{
|
||||||
if (!Library.ScriptResolver.TryResolve(ScriptCategory.Status, status, null, out var statusScript))
|
if (!Library.ScriptResolver.TryResolve(ScriptCategory.Status, status, null, out var statusScript))
|
||||||
throw new KeyNotFoundException($"Status script {status} not found");
|
throw new KeyNotFoundException($"Status script {status} not found");
|
||||||
|
|
||||||
if (!StatusScript.IsEmpty)
|
if (!StatusScript.IsEmpty)
|
||||||
return false;
|
return false;
|
||||||
|
var oldStatus = StatusScript.Script?.Name;
|
||||||
|
|
||||||
var preventStatus = false;
|
var preventStatus = false;
|
||||||
this.RunScriptHook(script => script.PreventStatusChange(this, status, ref preventStatus));
|
this.RunScriptHook(script => script.PreventStatusChange(this, status, ref preventStatus));
|
||||||
@ -1135,11 +1151,22 @@ public class PokemonImpl : ScriptSource, IPokemon
|
|||||||
|
|
||||||
StatusScript.Set(statusScript);
|
StatusScript.Set(statusScript);
|
||||||
statusScript.OnAddedToParent(this);
|
statusScript.OnAddedToParent(this);
|
||||||
|
BattleData?.Battle.EventHook.Invoke(new StatusChangeEvent(this, oldStatus, status)
|
||||||
|
{
|
||||||
|
BatchId = batchId,
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void ClearStatus() => StatusScript.Clear();
|
public void ClearStatus(EventBatchId batchId = default)
|
||||||
|
{
|
||||||
|
StatusScript.Clear();
|
||||||
|
BattleData?.Battle.EventHook.Invoke(new StatusChangeEvent(this, StatusScript.Script?.Name, null)
|
||||||
|
{
|
||||||
|
BatchId = batchId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void ChangeLevelBy(int change)
|
public void ChangeLevelBy(int change)
|
||||||
@ -1160,7 +1187,17 @@ public class PokemonImpl : ScriptSource, IPokemon
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
BattleData = new PokemonBattleDataImpl(battle, sideIndex, battle.CurrentTurnNumber);
|
BattleData = new PokemonBattleDataImpl(battle, sideIndex, battle.CurrentTurnNumber, Species, Form);
|
||||||
|
}
|
||||||
|
if (ActiveAbility != null && Library.ScriptResolver.TryResolve(ScriptCategory.Ability, ActiveAbility.Name,
|
||||||
|
ActiveAbility.Parameters, out var abilityScript))
|
||||||
|
{
|
||||||
|
AbilityScript.Set(abilityScript);
|
||||||
|
abilityScript.OnAddedToParent(this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AbilityScript.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1192,6 +1229,24 @@ public class PokemonImpl : ScriptSource, IPokemon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void ClearBattleData()
|
||||||
|
{
|
||||||
|
var battleData = BattleData;
|
||||||
|
BattleData = null;
|
||||||
|
Volatile.Clear();
|
||||||
|
WeightInKg = Form.Weight;
|
||||||
|
HeightInMeters = Form.Height;
|
||||||
|
Types = Form.Types;
|
||||||
|
OverrideAbility = null;
|
||||||
|
AbilitySuppressed = false;
|
||||||
|
StatBoost.Reset();
|
||||||
|
if (battleData != null && Form.IsBattleOnlyForm)
|
||||||
|
{
|
||||||
|
ChangeForm(battleData.OriginalSpecies == Species ? battleData.OriginalForm : Species.GetDefaultForm());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void MarkOpponentAsSeen(IPokemon pokemon) => BattleData?.MarkOpponentAsSeen(pokemon);
|
public void MarkOpponentAsSeen(IPokemon pokemon) => BattleData?.MarkOpponentAsSeen(pokemon);
|
||||||
|
|
||||||
@ -1295,11 +1350,14 @@ public class PokemonImpl : ScriptSource, IPokemon
|
|||||||
public class PokemonBattleDataImpl : IPokemonBattleData
|
public class PokemonBattleDataImpl : IPokemonBattleData
|
||||||
{
|
{
|
||||||
/// <inheritdoc cref="PokemonBattleDataImpl"/>
|
/// <inheritdoc cref="PokemonBattleDataImpl"/>
|
||||||
public PokemonBattleDataImpl(IBattle battle, byte sideIndex, uint switchInTurn)
|
public PokemonBattleDataImpl(IBattle battle, byte sideIndex, uint switchInTurn, ISpecies originalSpecies,
|
||||||
|
IForm originalForm)
|
||||||
{
|
{
|
||||||
Battle = battle;
|
Battle = battle;
|
||||||
SideIndex = sideIndex;
|
SideIndex = sideIndex;
|
||||||
SwitchInTurn = switchInTurn;
|
SwitchInTurn = switchInTurn;
|
||||||
|
OriginalSpecies = originalSpecies;
|
||||||
|
OriginalForm = originalForm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -1342,4 +1400,10 @@ public class PokemonBattleDataImpl : IPokemonBattleData
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IBattleSide BattleSide => Battle.Sides[SideIndex];
|
public IBattleSide BattleSide => Battle.Sides[SideIndex];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ISpecies OriginalSpecies { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IForm OriginalForm { get; }
|
||||||
}
|
}
|
@ -5,6 +5,11 @@ namespace PkmnLib.Dynamic.ScriptHandling;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ILimitedTurnsScript
|
public interface ILimitedTurnsScript
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of turns remaining for the script to last.
|
||||||
|
/// </summary>
|
||||||
|
public int TurnsRemaining { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the number of turns the script will last.
|
/// Sets the number of turns the script will last.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -371,6 +371,14 @@ public abstract class Script : IDeepCloneable
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This function allows a script to change the damage modifier of an incoming move.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void ChangeIncomingMoveDamageModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber,
|
||||||
|
ref float modifier)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This function allows a script to modify the outgoing damage done by a move.
|
/// This function allows a script to modify the outgoing damage done by a move.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -740,4 +748,16 @@ public abstract class Script : IDeepCloneable
|
|||||||
public virtual void IsFloating(IPokemon pokemon, ref bool isFloating)
|
public virtual void IsFloating(IPokemon pokemon, ref bool isFloating)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This function allows a script to prevent the weather from changing. This is used for abilities such as
|
||||||
|
/// Delta Stream, which prevent the weather from changing to anything other than strong winds.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void PreventWeatherChange(StringKey? weatherName, ref bool preventWeatherChange)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnWeatherChange(IBattle battle, StringKey? weatherName, StringKey? oldWeatherName)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
@ -89,6 +89,11 @@ public interface IForm : INamedValue
|
|||||||
/// Check if the form has a specific flag set.
|
/// Check if the form has a specific flag set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool HasFlag(string key);
|
bool HasFlag(string key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the form is a battle-only form, meaning it should return to its original form after the battle ends.
|
||||||
|
/// </summary>
|
||||||
|
bool IsBattleOnlyForm { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -97,7 +102,8 @@ public class FormImpl : IForm
|
|||||||
/// <inheritdoc cref="FormImpl" />
|
/// <inheritdoc cref="FormImpl" />
|
||||||
public FormImpl(StringKey name, float height, float weight, uint baseExperience, IEnumerable<TypeIdentifier> types,
|
public FormImpl(StringKey name, float height, float weight, uint baseExperience, IEnumerable<TypeIdentifier> types,
|
||||||
ImmutableStatisticSet<ushort> baseStats, IEnumerable<StringKey> abilities,
|
ImmutableStatisticSet<ushort> baseStats, IEnumerable<StringKey> abilities,
|
||||||
IEnumerable<StringKey> hiddenAbilities, ILearnableMoves moves, ImmutableHashSet<StringKey> flags)
|
IEnumerable<StringKey> hiddenAbilities, ILearnableMoves moves, ImmutableHashSet<StringKey> flags,
|
||||||
|
bool isBattleOnlyForm)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Height = height;
|
Height = height;
|
||||||
@ -109,6 +115,7 @@ public class FormImpl : IForm
|
|||||||
HiddenAbilities = [..hiddenAbilities];
|
HiddenAbilities = [..hiddenAbilities];
|
||||||
Moves = moves;
|
Moves = moves;
|
||||||
Flags = flags;
|
Flags = flags;
|
||||||
|
IsBattleOnlyForm = isBattleOnlyForm;
|
||||||
|
|
||||||
if (Types.Count == 0)
|
if (Types.Count == 0)
|
||||||
throw new ArgumentException("A form must have at least one type.");
|
throw new ArgumentException("A form must have at least one type.");
|
||||||
@ -202,4 +209,7 @@ public class FormImpl : IForm
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool HasFlag(string key) => Flags.Contains(key);
|
public bool HasFlag(string key) => Flags.Contains(key);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsBattleOnlyForm { get; }
|
||||||
}
|
}
|
@ -399,6 +399,21 @@ public record StatBoostStatisticSet : ClampedStatisticSet<sbyte>
|
|||||||
yield return (Statistic.Evasion, Evasion);
|
yield return (Statistic.Evasion, Evasion);
|
||||||
yield return (Statistic.Accuracy, Accuracy);
|
yield return (Statistic.Accuracy, Accuracy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets all statistics to 0.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
Hp = 0;
|
||||||
|
Attack = 0;
|
||||||
|
Defense = 0;
|
||||||
|
SpecialAttack = 0;
|
||||||
|
SpecialDefense = 0;
|
||||||
|
Speed = 0;
|
||||||
|
Evasion = 0;
|
||||||
|
Accuracy = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -86,8 +86,8 @@ public class IntegrationTestRunner
|
|||||||
|
|
||||||
return new BattlePartyImpl(party, x.Indices.Select(y => new ResponsibleIndex(y[0], y[1])).ToArray());
|
return new BattlePartyImpl(party, x.Indices.Select(y => new ResponsibleIndex(y[0], y[1])).ToArray());
|
||||||
}).ProcessOneAtATime().GetResultsAsync();
|
}).ProcessOneAtATime().GetResultsAsync();
|
||||||
var battle = new BattleImpl(library, parties, test.BattleSetup.CanFlee, test.BattleSetup.NumberOfSides,
|
using var battle = new BattleImpl(library, parties, test.BattleSetup.CanFlee, test.BattleSetup.NumberOfSides,
|
||||||
test.BattleSetup.PositionsPerSide, test.BattleSetup.Seed);
|
test.BattleSetup.PositionsPerSide, false, test.BattleSetup.Seed);
|
||||||
|
|
||||||
foreach (var action in test.Actions)
|
foreach (var action in test.Actions)
|
||||||
{
|
{
|
||||||
|
@ -108,7 +108,7 @@ public class DeepCloneTests
|
|||||||
new BattlePartyImpl(party1, [new ResponsibleIndex(0, 0)]),
|
new BattlePartyImpl(party1, [new ResponsibleIndex(0, 0)]),
|
||||||
new BattlePartyImpl(party2, [new ResponsibleIndex(1, 0)]),
|
new BattlePartyImpl(party2, [new ResponsibleIndex(1, 0)]),
|
||||||
};
|
};
|
||||||
var battle = new BattleImpl(library, parties, false, 2, 3, 0);
|
using var battle = new BattleImpl(library, parties, false, 2, 3, false, 0);
|
||||||
battle.Sides[0].SwapPokemon(0, party1[0]);
|
battle.Sides[0].SwapPokemon(0, party1[0]);
|
||||||
battle.Sides[1].SwapPokemon(0, party2[0]);
|
battle.Sides[1].SwapPokemon(0, party2[0]);
|
||||||
party1[0]!.ChangeStatBoost(Statistic.Defense, 2, true);
|
party1[0]!.ChangeStatBoost(Statistic.Defense, 2, true);
|
||||||
|
@ -77,7 +77,7 @@
|
|||||||
"chlorophyll": {
|
"chlorophyll": {
|
||||||
"effect": "speed_modifier_in_weather",
|
"effect": "speed_modifier_in_weather",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"weather": "sunny",
|
"weather": "harsh_sunlight",
|
||||||
"modifier": 2.0
|
"modifier": 2.0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -131,26 +131,55 @@
|
|||||||
"defiant": {
|
"defiant": {
|
||||||
"effect": "defiant"
|
"effect": "defiant"
|
||||||
},
|
},
|
||||||
"delta_stream": {},
|
"delta_stream": {
|
||||||
"desolate_land": {},
|
"effect": "delta_stream"
|
||||||
|
},
|
||||||
|
"desolate_land": {
|
||||||
|
"effect": "desolate_land"
|
||||||
|
},
|
||||||
"disguise": {
|
"disguise": {
|
||||||
|
"effect": "disguise",
|
||||||
"canBeChanged": false,
|
"canBeChanged": false,
|
||||||
"flags": [
|
"flags": [
|
||||||
"cant_be_copied"
|
"cant_be_copied"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"download": {},
|
"download": {
|
||||||
"drizzle": {},
|
"effect": "download"
|
||||||
"drought": {},
|
},
|
||||||
"dry_skin": {},
|
"drizzle": {
|
||||||
"early_bird": {},
|
"effect": "drizzle"
|
||||||
"effect_spore": {},
|
},
|
||||||
"electric_surge": {},
|
"drought": {
|
||||||
"emergency_exit": {},
|
"effect": "drought"
|
||||||
"fairy_aura": {},
|
},
|
||||||
"filter": {},
|
"dry_skin": {
|
||||||
"flame_body": {},
|
"effect": "dry_skin"
|
||||||
"flare_boost": {},
|
},
|
||||||
|
"early_bird": {
|
||||||
|
"effect": "early_bird"
|
||||||
|
},
|
||||||
|
"effect_spore": {
|
||||||
|
"effect": "effect_spore"
|
||||||
|
},
|
||||||
|
"electric_surge": {
|
||||||
|
"effect": "electric_surge"
|
||||||
|
},
|
||||||
|
"emergency_exit": {
|
||||||
|
"effect": "emergency_exit"
|
||||||
|
},
|
||||||
|
"fairy_aura": {
|
||||||
|
"effect": "fairy_aura"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"effect": "filter"
|
||||||
|
},
|
||||||
|
"flame_body": {
|
||||||
|
"effect": "flame_body"
|
||||||
|
},
|
||||||
|
"flare_boost": {
|
||||||
|
"effect": "flare_boost"
|
||||||
|
},
|
||||||
"flash_fire": {},
|
"flash_fire": {},
|
||||||
"flower_gift": {
|
"flower_gift": {
|
||||||
"flags": [
|
"flags": [
|
||||||
|
@ -4669,11 +4669,7 @@
|
|||||||
"snatch"
|
"snatch"
|
||||||
],
|
],
|
||||||
"effect": {
|
"effect": {
|
||||||
"name": "change_multiple_user_stat_boosts",
|
"name": "growth"
|
||||||
"parameters": {
|
|
||||||
"attack": 1,
|
|
||||||
"specialAttack": 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -11208,7 +11204,7 @@
|
|||||||
"effect": {
|
"effect": {
|
||||||
"name": "set_weather",
|
"name": "set_weather",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"weather": "sunny"
|
"weather": "harsh_sunlight"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -4,3 +4,4 @@ global using PkmnLib.Dynamic.Events;
|
|||||||
global using PkmnLib.Dynamic.Models;
|
global using PkmnLib.Dynamic.Models;
|
||||||
global using PkmnLib.Dynamic.Models.Choices;
|
global using PkmnLib.Dynamic.Models.Choices;
|
||||||
global using PkmnLib.Static;
|
global using PkmnLib.Static;
|
||||||
|
global using PkmnLib.Static.Utils;
|
@ -155,6 +155,8 @@ public class Gen7DamageCalculator(Gen7PluginConfiguration configuration) : IDama
|
|||||||
|
|
||||||
executingMove.RunScriptHook(script =>
|
executingMove.RunScriptHook(script =>
|
||||||
script.ChangeDamageModifier(executingMove, target, hitNumber, ref modifier));
|
script.ChangeDamageModifier(executingMove, target, hitNumber, ref modifier));
|
||||||
|
target.RunScriptHook(script =>
|
||||||
|
script.ChangeIncomingMoveDamageModifier(executingMove, target, hitNumber, ref modifier));
|
||||||
|
|
||||||
return modifier;
|
return modifier;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delta Stream is an ability that creates strong winds when the Pokémon enters battle.
|
||||||
|
/// These winds weaken the power of super-effective Flying-type moves and prevent other weather conditions.
|
||||||
|
/// This ability is exclusive to Mega Rayquaza.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Delta_Stream_(Ability)">Bulbapedia - Delta Stream</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "delta_stream")]
|
||||||
|
public class DeltaStreamAbility : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnSwitchIn(IPokemon pokemon, byte position)
|
||||||
|
{
|
||||||
|
var battle = pokemon.BattleData?.Battle;
|
||||||
|
if (battle == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
battle.SetWeather(ScriptUtils.ResolveName<Weather.StrongWinds>(), -1);
|
||||||
|
if (battle.WeatherName == ScriptUtils.ResolveName<Weather.StrongWinds>())
|
||||||
|
{
|
||||||
|
((Weather.StrongWinds)battle.WeatherScript.Script!).MarkAsPlaced(pokemon);
|
||||||
|
battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desolate Land is an ability that creates extremely harsh sunlight when the Pokémon enters battle.
|
||||||
|
/// This sunlight is so intense that it prevents other weather conditions and makes Water-type moves fail.
|
||||||
|
/// This ability is exclusive to Primal Groudon.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Desolate_Land_(Ability)">Bulbapedia - Desolate Land</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "desolate_land")]
|
||||||
|
public class DesolateLandAbility : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnSwitchIn(IPokemon pokemon, byte position)
|
||||||
|
{
|
||||||
|
var battle = pokemon.BattleData?.Battle;
|
||||||
|
if (battle == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
battle.SetWeather(ScriptUtils.ResolveName<Weather.DesolateLands>(), -1);
|
||||||
|
if (battle.WeatherName == ScriptUtils.ResolveName<Weather.DesolateLands>())
|
||||||
|
{
|
||||||
|
((Weather.DesolateLands)battle.WeatherScript.Script!).MarkAsPlaced(pokemon);
|
||||||
|
battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Disguise.cs
Normal file
50
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Disguise.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using PkmnLib.Static.Species;
|
||||||
|
|
||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disguise is an ability that allows the Pokémon to avoid damage from one attack.
|
||||||
|
/// After being hit, the disguise is broken and the Pokémon's appearance changes.
|
||||||
|
/// This ability is exclusive to Mimikyu.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Disguise_(Ability)">Bulbapedia - Disguise</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "disguise")]
|
||||||
|
public class Disguise : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeIncomingDamage(IPokemon pokemon, DamageSource source, ref uint damage)
|
||||||
|
{
|
||||||
|
if (pokemon.BattleData == null)
|
||||||
|
return;
|
||||||
|
if (source is not DamageSource.MoveDamage and not DamageSource.Confusion)
|
||||||
|
return;
|
||||||
|
if (pokemon.Form.Name == "busted" || pokemon.Form.Name == "totem-busted")
|
||||||
|
return;
|
||||||
|
IForm form;
|
||||||
|
if (pokemon.Form.Name == "default")
|
||||||
|
{
|
||||||
|
if (!pokemon.Species.TryGetForm("busted", out form!))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (pokemon.Form.Name == "totem-disguised")
|
||||||
|
{
|
||||||
|
if (!pokemon.Species.TryGetForm("totem-busted", out form!))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventBatchId batchId = new();
|
||||||
|
|
||||||
|
pokemon.BattleData.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon)
|
||||||
|
{
|
||||||
|
BatchId = batchId,
|
||||||
|
});
|
||||||
|
pokemon.ChangeForm(form, batchId);
|
||||||
|
|
||||||
|
damage = 0;
|
||||||
|
}
|
||||||
|
}
|
36
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Download.cs
Normal file
36
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Download.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Download is an ability that raises the Pokémon's Attack or Special Attack stat when it enters battle,
|
||||||
|
/// depending on which stat is lower on the opposing Pokémon.
|
||||||
|
/// This ability is commonly associated with Porygon and its evolutions.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Download_(Ability)">Bulbapedia - Download</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "download")]
|
||||||
|
public class Download : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnSwitchIn(IPokemon pokemon, byte position)
|
||||||
|
{
|
||||||
|
var battleData = pokemon.BattleData;
|
||||||
|
if (battleData == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var opponents = battleData.Battle.Sides.Where(x => x != battleData.BattleSide).SelectMany(x => x.Pokemon)
|
||||||
|
.WhereNotNull().ToArray();
|
||||||
|
if (opponents.Length == 0)
|
||||||
|
return;
|
||||||
|
var opponentAverageDefense = opponents.Average(x => x.BoostedStats.Defense);
|
||||||
|
var opponentAverageSpecialDefense = opponents.Average(x => x.BoostedStats.SpecialDefense);
|
||||||
|
|
||||||
|
EventBatchId batchId = new();
|
||||||
|
battleData.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon)
|
||||||
|
{
|
||||||
|
BatchId = batchId,
|
||||||
|
});
|
||||||
|
pokemon.ChangeStatBoost(
|
||||||
|
opponentAverageDefense < opponentAverageSpecialDefense ? Statistic.Attack : Statistic.SpecialAttack, 1,
|
||||||
|
true, batchId);
|
||||||
|
}
|
||||||
|
}
|
27
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Drizzle.cs
Normal file
27
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Drizzle.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drizzle is an ability that creates rain when the Pokémon enters battle.
|
||||||
|
/// This rain boosts Water-type moves and weakens Fire-type moves.
|
||||||
|
/// This ability is commonly associated with Kyogre and Politoed.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Drizzle_(Ability)">Bulbapedia - Drizzle</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "drizzle")]
|
||||||
|
public class Drizzle : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnSwitchIn(IPokemon pokemon, byte position)
|
||||||
|
{
|
||||||
|
var battleData = pokemon.BattleData;
|
||||||
|
if (battleData == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EventBatchId batchId = new();
|
||||||
|
battleData.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon)
|
||||||
|
{
|
||||||
|
BatchId = batchId,
|
||||||
|
});
|
||||||
|
battleData.Battle.SetWeather(ScriptUtils.ResolveName<Weather.Rain>(), 5, batchId);
|
||||||
|
}
|
||||||
|
}
|
27
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Drought.cs
Normal file
27
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Drought.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drought is an ability that creates harsh sunlight when the Pokémon enters battle.
|
||||||
|
/// This sunlight boosts Fire-type moves and weakens Water-type moves.
|
||||||
|
/// This ability is commonly associated with Groudon and Ninetales.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Drought_(Ability)">Bulbapedia - Drought</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "drought")]
|
||||||
|
public class Drought : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnSwitchIn(IPokemon pokemon, byte position)
|
||||||
|
{
|
||||||
|
var battleData = pokemon.BattleData;
|
||||||
|
if (battleData == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EventBatchId batchId = new();
|
||||||
|
battleData.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon)
|
||||||
|
{
|
||||||
|
BatchId = batchId,
|
||||||
|
});
|
||||||
|
battleData.Battle.SetWeather(ScriptUtils.ResolveName<Weather.HarshSunlight>(), 5, batchId);
|
||||||
|
}
|
||||||
|
}
|
58
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/DrySkin.cs
Normal file
58
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/DrySkin.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dry Skin is an ability that makes the Pokémon take more damage from Fire-type moves,
|
||||||
|
/// heal from Water-type moves, heal in rain, and take damage in harsh sunlight.
|
||||||
|
/// This ability is commonly associated with Paras and Toxicroak.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Dry_Skin_(Ability)">Bulbapedia - Dry Skin</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "dry_skin")]
|
||||||
|
public class DrySkin : Script
|
||||||
|
{
|
||||||
|
private IPokemon? _owningPokemon;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnAddedToParent(IScriptSource source)
|
||||||
|
{
|
||||||
|
if (source is not IPokemon pokemon)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("DrySkin script must be added to a Pokemon.", nameof(source));
|
||||||
|
}
|
||||||
|
_owningPokemon = pokemon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
|
||||||
|
{
|
||||||
|
var hitType = move.GetHitData(target, hit).Type;
|
||||||
|
if (hitType?.Name == "fire")
|
||||||
|
{
|
||||||
|
modifier *= 1.25f;
|
||||||
|
}
|
||||||
|
else if (hitType?.Name == "water")
|
||||||
|
{
|
||||||
|
modifier = 0;
|
||||||
|
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
|
||||||
|
target.Heal(target.MaxHealth / 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnEndTurn(IBattle battle)
|
||||||
|
{
|
||||||
|
if (_owningPokemon == null)
|
||||||
|
return;
|
||||||
|
var weather = battle.WeatherName;
|
||||||
|
if (weather == ScriptUtils.ResolveName<Weather.Rain>())
|
||||||
|
{
|
||||||
|
_owningPokemon.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(_owningPokemon));
|
||||||
|
_owningPokemon.Heal(_owningPokemon.MaxHealth / 8);
|
||||||
|
}
|
||||||
|
else if (weather == ScriptUtils.ResolveName<Weather.HarshSunlight>())
|
||||||
|
{
|
||||||
|
_owningPokemon.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(_owningPokemon));
|
||||||
|
_owningPokemon.Damage(_owningPokemon.MaxHealth / 8, DamageSource.Weather);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/EarlyBird.cs
Normal file
25
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/EarlyBird.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Early Bird is an ability that allows the Pokémon to wake up from sleep twice as fast.
|
||||||
|
/// This means the number of turns the Pokémon stays asleep is halved, rounded down.
|
||||||
|
/// This ability is commonly associated with Kangaskhan and Dodrio.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Early_Bird_(Ability)">Bulbapedia - Early Bird</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "early_bird")]
|
||||||
|
public class EarlyBird : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? parameters)
|
||||||
|
{
|
||||||
|
if (eventName != CustomTriggers.ModifySleepTurns)
|
||||||
|
return;
|
||||||
|
if (parameters == null)
|
||||||
|
return;
|
||||||
|
if (parameters.TryGetValue("turns", out var turnsObj) && turnsObj is int turns)
|
||||||
|
{
|
||||||
|
parameters["turns"] = turns / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/EffectSpore.cs
Normal file
49
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/EffectSpore.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Effect Spore is an ability that has a 30% chance of inflicting a status condition on the attacker
|
||||||
|
/// when hit by a contact move. The status condition can be poison (9%), paralysis (10%), or sleep (11%).
|
||||||
|
/// This ability is commonly associated with Paras and Shroomish.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Effect_Spore_(Ability)">Bulbapedia - Effect Spore</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "effect_spore")]
|
||||||
|
public class EffectSpore : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
|
||||||
|
{
|
||||||
|
if (move.User.Types.Any(x => x.Name == "grass"))
|
||||||
|
return;
|
||||||
|
if (move.User.ActiveAbility?.Name == "effect_spore")
|
||||||
|
return;
|
||||||
|
if (move.User.HasHeldItem("safety_goggles"))
|
||||||
|
return;
|
||||||
|
if (!move.UseMove.HasFlag("contact"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var rng = move.Battle.Random;
|
||||||
|
var chance = rng.GetInt(0, 100);
|
||||||
|
EventBatchId batchId = new();
|
||||||
|
if (chance < 30)
|
||||||
|
{
|
||||||
|
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)
|
||||||
|
{
|
||||||
|
BatchId = batchId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (chance)
|
||||||
|
{
|
||||||
|
case < 9:
|
||||||
|
move.User.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>(), batchId);
|
||||||
|
break;
|
||||||
|
case < 19:
|
||||||
|
move.User.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), batchId);
|
||||||
|
break;
|
||||||
|
case < 30:
|
||||||
|
move.User.SetStatus(ScriptUtils.ResolveName<Status.Sleep>(), batchId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Electric Surge is an ability that creates Electric Terrain when the Pokémon enters battle.
|
||||||
|
/// This terrain prevents grounded Pokémon from falling asleep and boosts Electric-type moves.
|
||||||
|
/// This ability is exclusive to Tapu Koko.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Electric_Surge_(Ability)">Bulbapedia - Electric Surge</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "electric_surge")]
|
||||||
|
public class ElectricSurge : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnSwitchIn(IPokemon pokemon, byte position)
|
||||||
|
{
|
||||||
|
var battleData = pokemon.BattleData;
|
||||||
|
if (battleData == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EventBatchId batchId = new();
|
||||||
|
battleData.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon)
|
||||||
|
{
|
||||||
|
BatchId = batchId,
|
||||||
|
});
|
||||||
|
battleData.Battle.SetTerrain(ScriptUtils.ResolveName<Terrain.ElectricTerrain>(), batchId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Emergency Exit is an ability that makes the Pokémon switch out when its HP drops below half.
|
||||||
|
/// This ability is similar to Wimp Out and is exclusive to Golisopod.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Emergency_Exit_(Ability)">Bulbapedia - Emergency Exit</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "emergency_exit")]
|
||||||
|
public class EmergencyExit : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnDamage(IPokemon pokemon, DamageSource source, uint oldHealth, uint newHealth)
|
||||||
|
{
|
||||||
|
if (pokemon.BattleData is null)
|
||||||
|
return;
|
||||||
|
if (source is DamageSource.Confusion)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var oldHealthFraction = (float)oldHealth / pokemon.MaxHealth;
|
||||||
|
var newHealthFraction = (float)newHealth / pokemon.MaxHealth;
|
||||||
|
if (!(oldHealthFraction >= 0.5f) || !(newHealthFraction < 0.5f))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (pokemon.BattleData.Battle.IsWildBattle)
|
||||||
|
{
|
||||||
|
pokemon.BattleData.Battle.ForceEndBattle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pokemon.BattleData.BattleSide.SwapPokemon(pokemon.BattleData.Position, null);
|
||||||
|
}
|
||||||
|
}
|
34
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/FairyAura.cs
Normal file
34
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/FairyAura.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fairy Aura is an ability that increases the power of Fairy-type moves by 33% for all Pokémon on the field.
|
||||||
|
/// The effect can be modified by other abilities (such as Aura Break) via a custom script hook.
|
||||||
|
/// This ability is exclusive to Xerneas.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Fairy_Aura_(Ability)">Bulbapedia - Fairy Aura</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "fairy_aura")]
|
||||||
|
public class FairyAura : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
|
||||||
|
{
|
||||||
|
if (move.GetHitData(target, hit).Type?.Name == "fairy")
|
||||||
|
{
|
||||||
|
var auraModifier = 5448f / 4096f;
|
||||||
|
var parameters = new Dictionary<StringKey, object?>
|
||||||
|
{
|
||||||
|
["aura_type"] = "fairy",
|
||||||
|
["modifier"] = auraModifier,
|
||||||
|
};
|
||||||
|
move.Battle.Sides.SelectMany(side => side.Pokemon).WhereNotNull()
|
||||||
|
.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyAuraEffect, parameters));
|
||||||
|
if (parameters.TryGetValue("modifier", out var modObj) && modObj is float modValue)
|
||||||
|
{
|
||||||
|
auraModifier = modValue;
|
||||||
|
}
|
||||||
|
modifier *= auraModifier;
|
||||||
|
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Filter.cs
Normal file
22
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Filter.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filter is an ability that reduces the damage taken from super-effective moves by 25%.
|
||||||
|
/// This ability is similar to Solid Rock, but is exclusive to certain Pokémon like Mr. Mime.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Filter_(Ability)">Bulbapedia - Filter</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "filter")]
|
||||||
|
public class Filter : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeIncomingMoveDamageModifier(IExecutingMove move, IPokemon target, byte hit,
|
||||||
|
ref float modifier)
|
||||||
|
{
|
||||||
|
if (move.GetHitData(target, hit).Effectiveness >= 2.0f)
|
||||||
|
{
|
||||||
|
modifier *= 0.75f;
|
||||||
|
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/FlameBody.cs
Normal file
25
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/FlameBody.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flame Body is an ability that has a 30% chance of burning the attacker when hit by a contact move.
|
||||||
|
/// This ability is similar to Effect Spore and Static, but inflicts burn instead of other status conditions.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Flame_Body_(Ability)">Bulbapedia - Flame Body</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "flame_body")]
|
||||||
|
public class FlameBody : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
|
||||||
|
{
|
||||||
|
if (!move.UseMove.HasFlag("contact"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var rng = move.Battle.Random;
|
||||||
|
if (rng.GetInt(0, 100) >= 30) // 30% chance
|
||||||
|
return;
|
||||||
|
|
||||||
|
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
|
||||||
|
move.User.SetStatus(ScriptUtils.ResolveName<Status.Burned>());
|
||||||
|
}
|
||||||
|
}
|
23
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/FlareBoost.cs
Normal file
23
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/FlareBoost.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using PkmnLib.Static.Moves;
|
||||||
|
|
||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flare Boost is an ability that increases the power of special moves by 50% when the Pokémon is burned.
|
||||||
|
/// This ability is exclusive to Drifblim.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Flare_Boost_(Ability)">Bulbapedia - Flare Boost</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "flare_boost")]
|
||||||
|
public class FlareBoost : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
|
||||||
|
{
|
||||||
|
if (!move.User.HasStatus(ScriptUtils.ResolveName<Status.Burned>()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (move.UseMove.Category == MoveCategory.Special)
|
||||||
|
modifier *= 1.5f;
|
||||||
|
}
|
||||||
|
}
|
29
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/FlashFire.cs
Normal file
29
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/FlashFire.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
|
||||||
|
|
||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flash Fire is an ability that makes the Pokémon immune to Fire-type moves.
|
||||||
|
/// When hit by a Fire-type move, the ability activates and increases the power of the Pokémon's Fire-type moves by 50%.
|
||||||
|
/// This ability is commonly associated with Ninetales and Arcanine.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Flash_Fire_(Ability)">Bulbapedia - Flash Fire</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Ability, "flash_fire")]
|
||||||
|
public class FlashFire : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeIncomingEffectiveness(IExecutingMove executingMove, IPokemon target, byte hitIndex,
|
||||||
|
ref float effectiveness)
|
||||||
|
{
|
||||||
|
if (executingMove.GetHitData(target, hitIndex).Type?.Name == "fire")
|
||||||
|
{
|
||||||
|
effectiveness = 0f;
|
||||||
|
|
||||||
|
if (target.Volatile.Contains<FlashFireEffect>())
|
||||||
|
return;
|
||||||
|
target.Volatile.Add(new FlashFireEffect());
|
||||||
|
executingMove.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,3 @@
|
|||||||
using PkmnLib.Static.Utils;
|
|
||||||
|
|
||||||
namespace PkmnLib.Plugin.Gen7.Scripts;
|
namespace PkmnLib.Plugin.Gen7.Scripts;
|
||||||
|
|
||||||
public static class CustomTriggers
|
public static class CustomTriggers
|
||||||
@ -21,4 +19,8 @@ public static class CustomTriggers
|
|||||||
public static readonly StringKey Whirlpool = "whirlpool";
|
public static readonly StringKey Whirlpool = "whirlpool";
|
||||||
|
|
||||||
public static readonly StringKey ModifyAuraEffect = "modify_aura_effect";
|
public static readonly StringKey ModifyAuraEffect = "modify_aura_effect";
|
||||||
|
|
||||||
|
public static readonly StringKey BypassChargeMove = "bypass_charge_move";
|
||||||
|
|
||||||
|
public static readonly StringKey ModifySleepTurns = "modify_sleep_turns";
|
||||||
}
|
}
|
@ -7,6 +7,14 @@ public class ChargeMove : Script
|
|||||||
{
|
{
|
||||||
public override void PreventMove(IExecutingMove move, ref bool prevent)
|
public override void PreventMove(IExecutingMove move, ref bool prevent)
|
||||||
{
|
{
|
||||||
|
var bypassCharge = false;
|
||||||
|
var parameters = new Dictionary<StringKey, object?>
|
||||||
|
{
|
||||||
|
{ "move", move },
|
||||||
|
{ "bypassCharge", bypassCharge },
|
||||||
|
};
|
||||||
|
move.RunScriptHook(script => script.CustomTrigger(CustomTriggers.BypassChargeMove, parameters));
|
||||||
|
|
||||||
var chargeMoveEffect = move.User.Volatile.Get<ChargeMoveEffect>();
|
var chargeMoveEffect = move.User.Volatile.Get<ChargeMoveEffect>();
|
||||||
if (chargeMoveEffect != null && chargeMoveEffect.MoveName == move.UseMove.Name)
|
if (chargeMoveEffect != null && chargeMoveEffect.MoveName == move.UseMove.Name)
|
||||||
return;
|
return;
|
||||||
|
18
Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Growth.cs
Normal file
18
Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Growth.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using PkmnLib.Plugin.Gen7.Scripts.Weather;
|
||||||
|
|
||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
|
||||||
|
|
||||||
|
[Script(ScriptCategory.Move, "growth")]
|
||||||
|
public class Growth : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
|
||||||
|
{
|
||||||
|
EventBatchId batchId = new();
|
||||||
|
sbyte amount = 1;
|
||||||
|
if (move.Battle.WeatherName == ScriptUtils.ResolveName<HarshSunlight>())
|
||||||
|
amount = 2;
|
||||||
|
move.User.ChangeStatBoost(Statistic.Attack, amount, true, batchId);
|
||||||
|
move.User.ChangeStatBoost(Statistic.SpecialAttack, amount, true, batchId);
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ public class Moonlight : Script
|
|||||||
|
|
||||||
var fraction = 0.5f;
|
var fraction = 0.5f;
|
||||||
var weather = battleData.Battle.WeatherName;
|
var weather = battleData.Battle.WeatherName;
|
||||||
if (weather == ScriptUtils.ResolveName<Sunny>())
|
if (weather == ScriptUtils.ResolveName<HarshSunlight>())
|
||||||
fraction = 2f / 3f;
|
fraction = 2f / 3f;
|
||||||
else if (weather == ScriptUtils.ResolveName<Rain>() || weather == ScriptUtils.ResolveName<Hail>() ||
|
else if (weather == ScriptUtils.ResolveName<Rain>() || weather == ScriptUtils.ResolveName<Hail>() ||
|
||||||
weather == ScriptUtils.ResolveName<Sandstorm>())
|
weather == ScriptUtils.ResolveName<Sandstorm>())
|
||||||
|
@ -8,7 +8,7 @@ public class Synthesis : Script
|
|||||||
{
|
{
|
||||||
var healModifier = 0.5f;
|
var healModifier = 0.5f;
|
||||||
var weatherName = target.BattleData?.Battle.WeatherName;
|
var weatherName = target.BattleData?.Battle.WeatherName;
|
||||||
if (weatherName == ScriptUtils.ResolveName<Weather.Sunny>())
|
if (weatherName == ScriptUtils.ResolveName<Weather.HarshSunlight>())
|
||||||
healModifier = 2 / 3f;
|
healModifier = 2 / 3f;
|
||||||
else if (weatherName == ScriptUtils.ResolveName<Weather.Rain>() ||
|
else if (weatherName == ScriptUtils.ResolveName<Weather.Rain>() ||
|
||||||
weatherName == ScriptUtils.ResolveName<Weather.Hail>() ||
|
weatherName == ScriptUtils.ResolveName<Weather.Hail>() ||
|
||||||
|
@ -23,7 +23,7 @@ public class WeatherBall : Script
|
|||||||
if (weather is null)
|
if (weather is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (weather == ScriptUtils.ResolveName<Weather.Sunny>() &&
|
if (weather == ScriptUtils.ResolveName<Weather.HarshSunlight>() &&
|
||||||
typeLibrary.TryGetTypeIdentifier("fire", out var fireType))
|
typeLibrary.TryGetTypeIdentifier("fire", out var fireType))
|
||||||
typeIdentifier = fireType;
|
typeIdentifier = fireType;
|
||||||
else if (weather == ScriptUtils.ResolveName<Weather.Rain>() &&
|
else if (weather == ScriptUtils.ResolveName<Weather.Rain>() &&
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
|
||||||
|
|
||||||
|
[Script(ScriptCategory.Pokemon, "flash_fire_effect")]
|
||||||
|
public class FlashFireEffect : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
|
||||||
|
ImmutableStatisticSet<uint> targetStats, ref uint value)
|
||||||
|
{
|
||||||
|
if (move.GetHitData(target, hit).Type?.Name == "fire")
|
||||||
|
{
|
||||||
|
value = value.MultiplyOrMax(1.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,13 @@ public class Sleep : Script
|
|||||||
{
|
{
|
||||||
// 1-3 turns of sleep
|
// 1-3 turns of sleep
|
||||||
Turns = battleData.Battle.Random.GetInt(1, 4);
|
Turns = battleData.Battle.Random.GetInt(1, 4);
|
||||||
|
source.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifySleepTurns,
|
||||||
|
new Dictionary<StringKey, object?>
|
||||||
|
{
|
||||||
|
{ "pokemon", pokemon },
|
||||||
|
{ "turns", Turns },
|
||||||
|
}));
|
||||||
|
Turns = Math.Max(1, Turns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/DesolateLands.cs
Normal file
36
Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/DesolateLands.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
|
||||||
|
|
||||||
|
[Script(ScriptCategory.Weather, "desolate_lands")]
|
||||||
|
public class DesolateLands : HarshSunlight
|
||||||
|
{
|
||||||
|
private readonly HashSet<IPokemon> _placers = [];
|
||||||
|
|
||||||
|
public void MarkAsPlaced(IPokemon pokemon)
|
||||||
|
{
|
||||||
|
_placers.Add(pokemon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnSwitchOut(IPokemon oldPokemon, byte position)
|
||||||
|
{
|
||||||
|
_placers.Remove(oldPokemon);
|
||||||
|
if (_placers.Count == 0)
|
||||||
|
oldPokemon.BattleData?.Battle.SetWeather(null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void PreventWeatherChange(StringKey? weatherName, ref bool preventWeatherChange)
|
||||||
|
{
|
||||||
|
if (weatherName == ScriptUtils.ResolveName<PrimordialSea>() ||
|
||||||
|
weatherName == ScriptUtils.ResolveName<StrongWinds>())
|
||||||
|
return;
|
||||||
|
preventWeatherChange = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void FailMove(IExecutingMove move, ref bool fail)
|
||||||
|
{
|
||||||
|
if (move.UseMove.MoveType.Name == "water")
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,3 @@
|
|||||||
using PkmnLib.Static.Utils;
|
|
||||||
|
|
||||||
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
|
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
|
||||||
|
|
||||||
[Script(ScriptCategory.Weather, "hail")]
|
[Script(ScriptCategory.Weather, "hail")]
|
||||||
@ -7,6 +5,9 @@ public class Hail : Script, ILimitedTurnsScript
|
|||||||
{
|
{
|
||||||
private int? _duration;
|
private int? _duration;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int TurnsRemaining => _duration ?? 0;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void SetTurns(int turns)
|
public void SetTurns(int turns)
|
||||||
{
|
{
|
||||||
|
59
Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/HarshSunlight.cs
Normal file
59
Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/HarshSunlight.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HarshSunlight is a weather condition that intensifies the effects of sunlight.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Harsh_sunlight">Bulbapedia - Harsh Sunlight</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Weather, "harsh_sunlight")]
|
||||||
|
public class HarshSunlight : Script
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
|
||||||
|
{
|
||||||
|
var hitType = move.GetHitData(target, hit).Type;
|
||||||
|
if (hitType?.Name == "fire" || move.UseMove.Name == "hydro_steam")
|
||||||
|
{
|
||||||
|
// Increase Fire-type move power by 50% in harsh sunlight
|
||||||
|
basePower = (ushort)(basePower * 1.5);
|
||||||
|
}
|
||||||
|
else if (hitType?.Name == "water")
|
||||||
|
{
|
||||||
|
basePower = (ushort)(basePower * 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? parameters)
|
||||||
|
{
|
||||||
|
if (eventName == CustomTriggers.BypassChargeMove)
|
||||||
|
{
|
||||||
|
if (parameters == null || !parameters.TryGetValue("move", out var moveObj) ||
|
||||||
|
moveObj is not IExecutingMove move)
|
||||||
|
return;
|
||||||
|
if (move.UseMove.Name == "solar_beam" || move.UseMove.Name == "solar_blade")
|
||||||
|
{
|
||||||
|
parameters["bypassCharge"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void PreventStatusChange(IPokemon pokemonImpl, StringKey status, ref bool preventStatus)
|
||||||
|
{
|
||||||
|
if (status == ScriptUtils.ResolveName<Status.Frozen>())
|
||||||
|
{
|
||||||
|
preventStatus = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex,
|
||||||
|
ref int modifiedAccuracy)
|
||||||
|
{
|
||||||
|
if (executingMove.UseMove.Name == "thunder" || executingMove.UseMove.Name == "hurricane")
|
||||||
|
{
|
||||||
|
modifiedAccuracy = (int)(modifiedAccuracy * 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/PrimordialSea.cs
Normal file
29
Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/PrimordialSea.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
|
||||||
|
|
||||||
|
[Script(ScriptCategory.Weather, "primordial_sea")]
|
||||||
|
public class PrimordialSea : Script
|
||||||
|
{
|
||||||
|
private HashSet<IPokemon> _placers = new();
|
||||||
|
|
||||||
|
public void MarkAsPlaced(IPokemon pokemon)
|
||||||
|
{
|
||||||
|
_placers.Add(pokemon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnSwitchOut(IPokemon oldPokemon, byte position)
|
||||||
|
{
|
||||||
|
_placers.Remove(oldPokemon);
|
||||||
|
if (_placers.Count == 0)
|
||||||
|
oldPokemon.BattleData?.Battle.SetWeather(null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void PreventWeatherChange(StringKey? weatherName, ref bool preventWeatherChange)
|
||||||
|
{
|
||||||
|
if (weatherName == ScriptUtils.ResolveName<DesolateLands>() ||
|
||||||
|
weatherName == ScriptUtils.ResolveName<StrongWinds>())
|
||||||
|
return;
|
||||||
|
preventWeatherChange = true;
|
||||||
|
}
|
||||||
|
}
|
50
Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/StrongWinds.cs
Normal file
50
Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/StrongWinds.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// StrongWinds is a weather condition that represents strong winds in battle.
|
||||||
|
///
|
||||||
|
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Strong_winds">Bulbapedia - Strong Winds</see>
|
||||||
|
/// </summary>
|
||||||
|
[Script(ScriptCategory.Weather, "strong_winds")]
|
||||||
|
public class StrongWinds : Script
|
||||||
|
{
|
||||||
|
private HashSet<IPokemon> _placers = new();
|
||||||
|
|
||||||
|
public void MarkAsPlaced(IPokemon pokemon)
|
||||||
|
{
|
||||||
|
_placers.Add(pokemon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnSwitchOut(IPokemon oldPokemon, byte position)
|
||||||
|
{
|
||||||
|
_placers.Remove(oldPokemon);
|
||||||
|
if (_placers.Count == 0)
|
||||||
|
oldPokemon.BattleData?.Battle.SetWeather(null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void PreventWeatherChange(StringKey? weatherName, ref bool preventWeatherChange)
|
||||||
|
{
|
||||||
|
if (weatherName == ScriptUtils.ResolveName<DesolateLands>() ||
|
||||||
|
weatherName == ScriptUtils.ResolveName<PrimordialSea>())
|
||||||
|
return;
|
||||||
|
preventWeatherChange = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeTypesForMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
|
||||||
|
IList<TypeIdentifier> types)
|
||||||
|
{
|
||||||
|
var flyingType = types.FirstOrDefault(x => x.Name == "flying");
|
||||||
|
if (flyingType != null)
|
||||||
|
{
|
||||||
|
var typeLibrary = executingMove.Battle.Library.StaticLibrary.Types;
|
||||||
|
var hitData = executingMove.GetHitData(target, hitIndex);
|
||||||
|
if (hitData.Type != null && typeLibrary.GetSingleEffectiveness(hitData.Type.Value, flyingType) > 1)
|
||||||
|
{
|
||||||
|
types.Remove(flyingType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
|
|
||||||
|
|
||||||
[Script(ScriptCategory.Weather, "sunny")]
|
|
||||||
public class Sunny : Script
|
|
||||||
{
|
|
||||||
// TODO: Implement Sunny
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user