First couple abilities implemented
All checks were successful
Build / Build (push) Successful in 48s
All checks were successful
Build / Build (push) Successful in 48s
This commit is contained in:
parent
c1a7b806b1
commit
b090aa65f9
@ -263,6 +263,8 @@ public static class MoveTurnExecutor
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target.IsFainted)
|
||||
executingMove.RunScriptHook(x => x.OnOpponentFaints(executingMove, target, hitIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
PkmnLib.Dynamic/Events/AbilityTriggerEvent.cs
Normal file
20
PkmnLib.Dynamic/Events/AbilityTriggerEvent.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Static.Species;
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Dynamic.Events;
|
||||
|
||||
public record AbilityTriggerEvent : IEventData
|
||||
{
|
||||
public IPokemon Pokemon { get; }
|
||||
public IAbility? Ability { get; }
|
||||
|
||||
public AbilityTriggerEvent(IPokemon pokemon)
|
||||
{
|
||||
Pokemon = pokemon;
|
||||
Ability = pokemon.ActiveAbility;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public EventBatchId BatchId { get; init; }
|
||||
}
|
@ -59,7 +59,7 @@ public static class AbilityDataLoader
|
||||
|
||||
var flags = serialized.Flags.Select(x => new StringKey(x)).ToImmutableHashSet();
|
||||
|
||||
var ability = new AbilityImpl(name, effectName, parameters, flags);
|
||||
var ability = new AbilityImpl(name, effectName, parameters, flags, serialized.CanBeChanged ?? true);
|
||||
return ability;
|
||||
}
|
||||
}
|
@ -19,4 +19,6 @@ public class SerializedAbility
|
||||
/// A collection of arbitrary flags that can be used to mark the ability with specific properties.
|
||||
/// </summary>
|
||||
public string[] Flags { get; set; } = [];
|
||||
|
||||
public bool? CanBeChanged { get; set; }
|
||||
}
|
@ -271,18 +271,25 @@ public class BattleSideImpl : ScriptSource, IBattleSide
|
||||
pokemon.SetBattleData(Battle, Index);
|
||||
pokemon.SetOnBattlefield(true);
|
||||
pokemon.SetBattleSidePosition(position);
|
||||
Battle.EventHook.Invoke(new SwitchEvent(Index, position, pokemon));
|
||||
pokemon.RunScriptHook(script => script.OnSwitchIn(pokemon, position));
|
||||
|
||||
foreach (var side in Battle.Sides)
|
||||
{
|
||||
if (side == this)
|
||||
continue;
|
||||
var scripts = new List<IEnumerable<ScriptContainer>>(10);
|
||||
foreach (var opponent in side.Pokemon.WhereNotNull())
|
||||
{
|
||||
opponent.MarkOpponentAsSeen(pokemon);
|
||||
pokemon.MarkOpponentAsSeen(opponent);
|
||||
|
||||
scripts.Clear();
|
||||
opponent.GetOwnScripts(scripts);
|
||||
opponent.RunScriptHook(script => script.OnOpponentSwitchIn(pokemon, position));
|
||||
}
|
||||
side.RunScriptHook(script => script.OnOpponentSwitchIn(pokemon, position));
|
||||
}
|
||||
Battle.EventHook.Invoke(new SwitchEvent(Index, position, pokemon));
|
||||
pokemon.RunScriptHook(script => script.OnSwitchIn(pokemon, position));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -46,6 +46,16 @@ public interface IHitData
|
||||
/// Fails the hit.
|
||||
/// </summary>
|
||||
void Fail();
|
||||
|
||||
/// <summary>
|
||||
/// Sets a flag on the hit data. This is used to mark certain conditions or states
|
||||
/// </summary>
|
||||
void SetFlag(StringKey flag);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a flag is set on the hit data.
|
||||
/// </summary>
|
||||
bool HasFlag(StringKey flag);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -71,6 +81,18 @@ public record HitData : IHitData
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Fail() => HasFailed = true;
|
||||
|
||||
private HashSet<StringKey>? _flags;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetFlag(StringKey flag)
|
||||
{
|
||||
_flags ??= [];
|
||||
_flags.Add(flag);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasFlag(StringKey flag) => _flags != null && _flags.Contains(flag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -401,7 +401,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
|
||||
/// <summary>
|
||||
/// Changes the ability of the Pokémon.
|
||||
/// </summary>
|
||||
void ChangeAbility(IAbility ability);
|
||||
bool ChangeAbility(IAbility ability);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the Pokémon is levitating. This is used for moves like Magnet Rise, and abilities such as
|
||||
@ -1197,8 +1197,10 @@ public class PokemonImpl : ScriptSource, IPokemon
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeAbility(IAbility ability)
|
||||
public bool ChangeAbility(IAbility ability)
|
||||
{
|
||||
if (!ability.CanBeChanged)
|
||||
return false;
|
||||
OverrideAbility = ability;
|
||||
if (Library.ScriptResolver.TryResolve(ScriptCategory.Ability, ability.Name, ability.Parameters,
|
||||
out var abilityScript))
|
||||
@ -1210,6 +1212,7 @@ public class PokemonImpl : ScriptSource, IPokemon
|
||||
{
|
||||
AbilityScript.Clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -546,6 +546,13 @@ public abstract class Script : IDeepCloneable
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is triggered on a Pokemon and its parents when an opponent switches in.
|
||||
/// </summary>
|
||||
public virtual void OnOpponentSwitchIn(IPokemon pokemon, byte position)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is triggered on a Pokemon and its parents when the given Pokemon consumes the
|
||||
/// held item it had.
|
||||
|
@ -34,10 +34,7 @@ public class ScriptResolver
|
||||
}
|
||||
|
||||
script = scriptCtor();
|
||||
if (parameters != null)
|
||||
{
|
||||
script.OnInitialize(parameters);
|
||||
}
|
||||
script.OnInitialize(parameters);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ public interface IAbility : INamedValue
|
||||
/// Checks whether the ability has a specific flag.
|
||||
/// </summary>
|
||||
bool HasFlag(StringKey key);
|
||||
|
||||
bool CanBeChanged { get; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -30,12 +32,13 @@ public class AbilityImpl : IAbility
|
||||
{
|
||||
/// <inheritdoc cref="AbilityImpl" />
|
||||
public AbilityImpl(StringKey name, StringKey? effect, IReadOnlyDictionary<StringKey, object?> parameters,
|
||||
ImmutableHashSet<StringKey> flags)
|
||||
ImmutableHashSet<StringKey> flags, bool canBeChanged)
|
||||
{
|
||||
Name = name;
|
||||
Effect = effect;
|
||||
Parameters = parameters;
|
||||
Flags = flags;
|
||||
CanBeChanged = canBeChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -54,6 +57,9 @@ public class AbilityImpl : IAbility
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasFlag(StringKey key) => Flags.Contains(key);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanBeChanged { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -5,6 +5,12 @@ namespace PkmnLib.Static.Utils;
|
||||
/// </summary>
|
||||
public static class NumericHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if two floating-point values are approximately equal within a specified tolerance.
|
||||
/// </summary>
|
||||
public static bool IsApproximatelyEqualTo(this float value, float other, float tolerance = 0.0001f) =>
|
||||
MathF.Abs(value - other) <= tolerance;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two values. If this overflows, returns <see cref="byte.MaxValue"/>.
|
||||
/// </summary>
|
||||
|
@ -0,0 +1,56 @@
|
||||
using PkmnLib.Dynamic.Libraries;
|
||||
using PkmnLib.Dynamic.ScriptHandling;
|
||||
using PkmnLib.Static.Species;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Tests.DataTests;
|
||||
|
||||
public class AbilityDataTests
|
||||
{
|
||||
public record TestCaseData(IDynamicLibrary Library, IAbility Ability)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => Ability.Name + " has valid scripts";
|
||||
}
|
||||
|
||||
public static IEnumerable<Func<TestCaseData>> AllAbilitiesHaveValidScriptsData()
|
||||
{
|
||||
var library = LibraryHelpers.LoadLibrary();
|
||||
var abilityLibrary = library.StaticLibrary.Abilities;
|
||||
foreach (var ability in abilityLibrary)
|
||||
{
|
||||
if (ability.Effect is null)
|
||||
continue;
|
||||
yield return () => new TestCaseData(library, ability);
|
||||
}
|
||||
}
|
||||
|
||||
[Test, MethodDataSource(nameof(AllAbilitiesHaveValidScriptsData)), Explicit]
|
||||
public async Task AllAbilitiesEffectsHaveValidScripts(TestCaseData test)
|
||||
{
|
||||
var scriptName = test.Ability.Effect;
|
||||
if (scriptName is null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
await Assert.That(test.Library.ScriptResolver.TryResolve(ScriptCategory.Ability, scriptName.Value,
|
||||
test.Ability.Parameters, out _)).IsTrue();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Helper method to find the line number of the effect in the JSON file
|
||||
var file = Path.GetFullPath("../../../../Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.json");
|
||||
var json = await File.ReadAllLinesAsync(file);
|
||||
var moveLineNumber = json.Select((line, index) => new { line, index })
|
||||
.FirstOrDefault(x => x.line.Contains($"\"name\": \"{test.Ability.Effect}\""))?.index + 1;
|
||||
var effectLineNumber = moveLineNumber + json.Skip(moveLineNumber ?? 0)
|
||||
.Select((line, index) => new { line, index }).FirstOrDefault(x => x.line.Contains("effect"))
|
||||
?.index +
|
||||
1 ?? 0;
|
||||
|
||||
await TestContext.Current!.OutputWriter.WriteLineAsync("File: " + $"file://{file}:{effectLineNumber}");
|
||||
throw new AggregateException($"Failed to resolve script for move {test.Ability} with effect {scriptName}",
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,68 +1,75 @@
|
||||
{
|
||||
"adaptability": {
|
||||
"effect": "IncreasedStab"
|
||||
"effect": "increased_stab"
|
||||
},
|
||||
"aerilate": {
|
||||
"effect": "ChangeMoveType",
|
||||
"effect": "change_move_type",
|
||||
"parameters": {
|
||||
"from": "normal",
|
||||
"to": "flying"
|
||||
"from_type": "normal",
|
||||
"to_type": "flying"
|
||||
}
|
||||
},
|
||||
"aftermath": {
|
||||
"effect": "Aftermath"
|
||||
"effect": "aftermath"
|
||||
},
|
||||
"air_lock": {
|
||||
"effect": "SuppressWeather"
|
||||
"effect": "suppress_weather"
|
||||
},
|
||||
"analytic": {
|
||||
"effect": "Analytic"
|
||||
"effect": "analytic"
|
||||
},
|
||||
"anger_point": {
|
||||
"effect": "AngerPoint"
|
||||
"effect": "anger_point"
|
||||
},
|
||||
"anticipation": {
|
||||
"effect": "Anticipation"
|
||||
"effect": "anticipation"
|
||||
},
|
||||
"arena_trap": {
|
||||
"effect": "ArenaTrap"
|
||||
"effect": "arena_trap"
|
||||
},
|
||||
"aroma_veil": {
|
||||
"effect": "AromaVeil"
|
||||
"effect": "aroma_veil"
|
||||
},
|
||||
"aura_break": {
|
||||
"effect": "AuraBreal"
|
||||
"effect": "aura_break"
|
||||
},
|
||||
"bad_dreams": {
|
||||
"effect": "BadDreams"
|
||||
"effect": "bad_dreams"
|
||||
},
|
||||
"battery": {
|
||||
"effect": "Battery"
|
||||
"effect": "battery"
|
||||
},
|
||||
"battle_armor": {
|
||||
"effect": "PreventCritical"
|
||||
"effect": "prevent_critical"
|
||||
},
|
||||
"battle_bond": {
|
||||
"effect": "BattleBond",
|
||||
"flags": ["cant_be_changed"]
|
||||
"effect": "battle_bond",
|
||||
"canBeChanged": false,
|
||||
"flags": [
|
||||
"cant_be_copied"
|
||||
]
|
||||
},
|
||||
"beast_boost": {
|
||||
"effect": "BeastBoost"
|
||||
"effect": "beast_boost"
|
||||
},
|
||||
"berserk": {
|
||||
"effect": "Berserk"
|
||||
"effect": "berserk"
|
||||
},
|
||||
"big_pecks": {
|
||||
"effect": "PreventDefLowering"
|
||||
"effect": "prevent_stat_lowering",
|
||||
"parameters": {
|
||||
"stat": "defense"
|
||||
}
|
||||
},
|
||||
"blaze": {
|
||||
"effect": "PowerUpType",
|
||||
"effect": "power_up_type_at_low_health",
|
||||
"parameters": {
|
||||
"type": "fire"
|
||||
"type": "fire",
|
||||
"threshold": 0.33333
|
||||
}
|
||||
},
|
||||
"bulletproof": {
|
||||
"effect": "Bulletproof"
|
||||
"effect": "bulletproof"
|
||||
},
|
||||
"cheek_pouch": {
|
||||
"effect": "CheekPouch"
|
||||
@ -83,7 +90,7 @@
|
||||
"effect": "ColorChange"
|
||||
},
|
||||
"comatose": {
|
||||
"flags": ["cant_be_changed"]
|
||||
"canBeChanged": false
|
||||
},
|
||||
"competitive": {},
|
||||
"compound_eyes": {},
|
||||
@ -100,7 +107,10 @@
|
||||
"delta_stream": {},
|
||||
"desolate_land": {},
|
||||
"disguise": {
|
||||
"flags": ["cant_be_changed", "cant_be_copied"]
|
||||
"canBeChanged": false,
|
||||
"flags": [
|
||||
"cant_be_copied"
|
||||
]
|
||||
},
|
||||
"download": {},
|
||||
"drizzle": {},
|
||||
@ -116,12 +126,16 @@
|
||||
"flare_boost": {},
|
||||
"flash_fire": {},
|
||||
"flower_gift": {
|
||||
"flags": ["cant_be_copied"]
|
||||
"flags": [
|
||||
"cant_be_copied"
|
||||
]
|
||||
},
|
||||
"flower_veil": {},
|
||||
"fluffy": {},
|
||||
"forecast": {
|
||||
"flags": ["cant_be_copied"]
|
||||
"flags": [
|
||||
"cant_be_copied"
|
||||
]
|
||||
},
|
||||
"forewarn": {},
|
||||
"friend_guard": {},
|
||||
@ -147,11 +161,15 @@
|
||||
"ice_body": {},
|
||||
"illuminate": {},
|
||||
"illusion": {
|
||||
"flags": ["cant_be_copied"]
|
||||
"flags": [
|
||||
"cant_be_copied"
|
||||
]
|
||||
},
|
||||
"immunity": {},
|
||||
"imposter": {
|
||||
"flags": ["cant_be_copied"]
|
||||
"flags": [
|
||||
"cant_be_copied"
|
||||
]
|
||||
},
|
||||
"infiltrator": {},
|
||||
"innards_out": {},
|
||||
@ -187,7 +205,7 @@
|
||||
"moxie": {},
|
||||
"multiscale": {},
|
||||
"multitype": {
|
||||
"flags": ["cant_be_changed"]
|
||||
"canBeChanged": false
|
||||
},
|
||||
"mummy": {},
|
||||
"natural_cure": {},
|
||||
@ -206,10 +224,15 @@
|
||||
"poison_point": {},
|
||||
"poison_touch": {},
|
||||
"power_construct": {
|
||||
"flags": ["cant_be_changed", "cant_be_copied"]
|
||||
"canBeChanged": false,
|
||||
"flags": [
|
||||
"cant_be_copied"
|
||||
]
|
||||
},
|
||||
"power_of_alchemy": {
|
||||
"flags": ["cant_be_copied"]
|
||||
"flags": [
|
||||
"cant_be_copied"
|
||||
]
|
||||
},
|
||||
"prankster": {},
|
||||
"pressure": {},
|
||||
@ -223,14 +246,16 @@
|
||||
"rain_dish": {},
|
||||
"rattled": {},
|
||||
"receiver": {
|
||||
"flags": ["cant_be_copied"]
|
||||
"flags": [
|
||||
"cant_be_copied"
|
||||
]
|
||||
},
|
||||
"reckless": {},
|
||||
"refrigerate": {},
|
||||
"regenerator": {},
|
||||
"rivalry": {},
|
||||
"rks_system": {
|
||||
"flags": ["cant_be_changed"]
|
||||
"canBeChanged": false
|
||||
},
|
||||
"rock_head": {},
|
||||
"rough_skin": {},
|
||||
@ -241,7 +266,7 @@
|
||||
"sand_veil": {},
|
||||
"sap_sipper": {},
|
||||
"schooling": {
|
||||
"flags": ["cant_be_changed"]
|
||||
"canBeChanged": false
|
||||
},
|
||||
"scrappy": {},
|
||||
"serene_grace": {},
|
||||
@ -252,7 +277,7 @@
|
||||
"shell_armor": {},
|
||||
"shield_dust": {},
|
||||
"shields_down": {
|
||||
"flags": ["cant_be_changed"]
|
||||
"canBeChanged": false
|
||||
},
|
||||
"simple": {},
|
||||
"skill_link": {},
|
||||
@ -270,7 +295,7 @@
|
||||
"stall": {},
|
||||
"stamina": {},
|
||||
"stance_change": {
|
||||
"flags": ["cant_be_changed"]
|
||||
"canBeChanged": false
|
||||
},
|
||||
"static": {},
|
||||
"steadfast": {},
|
||||
@ -299,11 +324,13 @@
|
||||
"tough_claws": {},
|
||||
"toxic_boost": {},
|
||||
"trace": {
|
||||
"flags": ["cant_be_copied"]
|
||||
"flags": [
|
||||
"cant_be_copied"
|
||||
]
|
||||
},
|
||||
"triage": {},
|
||||
"truant": {
|
||||
"flags": ["cant_be_changed"]
|
||||
"canBeChanged": false
|
||||
},
|
||||
"turboblaze": {},
|
||||
"unaware": {},
|
||||
@ -322,6 +349,8 @@
|
||||
"wonder_guard": {},
|
||||
"wonder_skin": {},
|
||||
"zen_mode": {
|
||||
"flags": ["cant_be_copied"]
|
||||
"flags": [
|
||||
"cant_be_copied"
|
||||
]
|
||||
}
|
||||
}
|
33
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Aftermath.cs
Normal file
33
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Aftermath.cs
Normal file
@ -0,0 +1,33 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "aftermath")]
|
||||
public class Aftermath : Script
|
||||
{
|
||||
private IExecutingMove? _lastAttack;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
_lastAttack = move;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnFaint(IPokemon pokemon, DamageSource source)
|
||||
{
|
||||
if (source != DamageSource.MoveDamage)
|
||||
return;
|
||||
if (_lastAttack is null || !_lastAttack.UseMove.HasFlag("contact"))
|
||||
return;
|
||||
var user = _lastAttack.User;
|
||||
if (!user.IsUsable)
|
||||
return;
|
||||
if (user.BattleData is null)
|
||||
return;
|
||||
EventBatchId eventBatchId = new();
|
||||
user.BattleData.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon)
|
||||
{
|
||||
BatchId = eventBatchId,
|
||||
});
|
||||
user.Damage(user.MaxHealth / 4, DamageSource.Misc, eventBatchId);
|
||||
}
|
||||
}
|
15
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Analytic.cs
Normal file
15
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Analytic.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "analytic")]
|
||||
public class Analytic : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
|
||||
{
|
||||
if (move.Battle.ChoiceQueue?.HasNext() == false)
|
||||
{
|
||||
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
|
||||
modifier *= 1.3f;
|
||||
}
|
||||
}
|
||||
}
|
19
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/AngerPoint.cs
Normal file
19
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/AngerPoint.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "anger_point")]
|
||||
public class AngerPoint : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
if (move.GetHitData(target, hit).IsCritical)
|
||||
{
|
||||
EventBatchId batchId = new();
|
||||
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)
|
||||
{
|
||||
BatchId = batchId,
|
||||
});
|
||||
target.ChangeStatBoost(Statistic.Attack, 12, true, batchId);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "anticipation")]
|
||||
public class Anticipation : Script
|
||||
{
|
||||
private IPokemon? _owner;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnAddedToParent(IScriptSource source)
|
||||
{
|
||||
if (source is not IPokemon pokemon)
|
||||
throw new ArgumentException("Anticipation script can only be added to a Pokemon.", nameof(source));
|
||||
_owner = pokemon;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnOpponentSwitchIn(IPokemon pokemon, byte position)
|
||||
{
|
||||
if (_owner is null)
|
||||
return;
|
||||
var pokemonMoves = pokemon.Moves.WhereNotNull();
|
||||
var typeLibrary = pokemon.Library.StaticLibrary.Types;
|
||||
var relevantMoves = pokemonMoves.Any(move =>
|
||||
// Either the move is super effective against the owner or
|
||||
typeLibrary.GetEffectiveness(move.MoveData.MoveType, _owner.Types) > 1.0f ||
|
||||
// the move is a OHKO move
|
||||
move.MoveData.SecondaryEffect?.Name == "one_hit_ko" ||
|
||||
// the move is a self-destruct move
|
||||
move.MoveData.SecondaryEffect?.Name == "self_destruct" ||
|
||||
move.MoveData.SecondaryEffect?.Name == "explosion");
|
||||
|
||||
if (relevantMoves)
|
||||
{
|
||||
pokemon.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(_owner));
|
||||
}
|
||||
}
|
||||
}
|
35
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/ArenaTrap.cs
Normal file
35
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/ArenaTrap.cs
Normal file
@ -0,0 +1,35 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "arena_trap")]
|
||||
public class ArenaTrap : Script
|
||||
{
|
||||
private IPokemon? _owner;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnAddedToParent(IScriptSource source)
|
||||
{
|
||||
if (source is not IPokemon pokemon)
|
||||
throw new InvalidOperationException("ArenaTrap can only be added to a Pokemon.");
|
||||
_owner = pokemon;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PreventOpponentRunAway(IFleeChoice choice, ref bool prevent)
|
||||
{
|
||||
if (choice.User.IsFloating)
|
||||
return;
|
||||
if (_owner is not null)
|
||||
choice.User.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(_owner));
|
||||
prevent = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PreventOpponentSwitch(ISwitchChoice choice, ref bool prevent)
|
||||
{
|
||||
if (choice.User.IsFloating)
|
||||
return;
|
||||
if (_owner is not null)
|
||||
choice.User.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(_owner));
|
||||
prevent = true;
|
||||
}
|
||||
}
|
21
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/AromaVeil.cs
Normal file
21
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/AromaVeil.cs
Normal file
@ -0,0 +1,21 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "aroma_veil")]
|
||||
public class AromaVeil : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void OnSwitchIn(IPokemon pokemon, byte position)
|
||||
{
|
||||
var side = pokemon.BattleData?.BattleSide;
|
||||
var effect = side?.VolatileScripts.Add(new Side.AromaVeilEffect())?.Script as Side.AromaVeilEffect;
|
||||
effect?.PlacerActivated(pokemon);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSwitchOut(IPokemon oldPokemon, byte position)
|
||||
{
|
||||
var side = oldPokemon.BattleData?.BattleSide;
|
||||
var effect = side?.VolatileScripts.Get<Side.AromaVeilEffect>();
|
||||
effect?.PlacerDeactivated(oldPokemon);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "aura_break")]
|
||||
public class AuraBreak : Script
|
||||
{
|
||||
// FIXME: Implement together with Dark Aura and Fairy Aura.
|
||||
}
|
35
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/BadDreams.cs
Normal file
35
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/BadDreams.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "bad_dreams")]
|
||||
public class BadDreams : Script
|
||||
{
|
||||
private IPokemon? _owner;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnAddedToParent(IScriptSource source)
|
||||
{
|
||||
if (source is not IPokemon pokemon)
|
||||
throw new InvalidOperationException("Bad Dreams ability can only be added to a Pokemon.");
|
||||
_owner = pokemon;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnEndTurn(IBattle battle)
|
||||
{
|
||||
if (_owner is null)
|
||||
return;
|
||||
var opponents = battle.Sides.Where(x => x != _owner?.BattleData?.BattleSide).SelectMany(x => x.Pokemon)
|
||||
.WhereNotNull();
|
||||
|
||||
foreach (var opponent in opponents)
|
||||
{
|
||||
if (!opponent.HasStatus(ScriptUtils.ResolveName<Status.Sleep>()))
|
||||
continue;
|
||||
EventBatchId batchId = new();
|
||||
battle.EventHook.Invoke(new AbilityTriggerEvent(_owner));
|
||||
opponent.Damage(opponent.MaxHealth / 8, DamageSource.Misc, batchId);
|
||||
}
|
||||
}
|
||||
}
|
21
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Battery.cs
Normal file
21
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Battery.cs
Normal file
@ -0,0 +1,21 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "battery")]
|
||||
public class Battery : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void OnSwitchIn(IPokemon pokemon, byte position)
|
||||
{
|
||||
var side = pokemon.BattleData?.BattleSide;
|
||||
var effect = side?.VolatileScripts.Add(new Side.BatteryAbilityEffect())?.Script as Side.BatteryAbilityEffect;
|
||||
effect?.PlacerActivated(pokemon);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSwitchOut(IPokemon oldPokemon, byte position)
|
||||
{
|
||||
var side = oldPokemon.BattleData?.BattleSide;
|
||||
var effect = side?.VolatileScripts.Get<Side.BatteryAbilityEffect>();
|
||||
effect?.PlacerDeactivated(oldPokemon);
|
||||
}
|
||||
}
|
32
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/BattleBond.cs
Normal file
32
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/BattleBond.cs
Normal file
@ -0,0 +1,32 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "battle_bond")]
|
||||
public class BattleBond : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void OnOpponentFaints(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
if (move.User.Species.Name == "greninja" && move.User.Form.Name != "ash" &&
|
||||
move.User.Species.TryGetForm("ash", out var ashForm))
|
||||
{
|
||||
move.User.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
|
||||
move.User.ChangeForm(ashForm);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
|
||||
{
|
||||
if (move.UseMove.Name == "water_shuriken" && move.User.Form.Name == "ash")
|
||||
basePower = 20;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeNumberOfHits(IMoveChoice choice, ref byte numberOfHits)
|
||||
{
|
||||
if (choice.ChosenMove.MoveData.Name == "water_shuriken" && choice.User.Form.Name == "ash")
|
||||
{
|
||||
numberOfHits = 3;
|
||||
}
|
||||
}
|
||||
}
|
17
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/BeastBoost.cs
Normal file
17
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/BeastBoost.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "beast_boost")]
|
||||
public class BeastBoost : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void OnOpponentFaints(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
var highestStat = move.User.BoostedStats.OrderByDescending(x => x.value).First().statistic;
|
||||
EventBatchId batchId = new();
|
||||
move.User.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)
|
||||
{
|
||||
BatchId = batchId,
|
||||
});
|
||||
move.User.ChangeStatBoost(highestStat, 1, true, batchId);
|
||||
}
|
||||
}
|
21
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Berserk.cs
Normal file
21
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Berserk.cs
Normal file
@ -0,0 +1,21 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "berserk")]
|
||||
public class Berserk : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void OnDamage(IPokemon pokemon, DamageSource source, uint oldHealth, uint newHealth)
|
||||
{
|
||||
if (source is not DamageSource.MoveDamage)
|
||||
return;
|
||||
if (oldHealth > pokemon.MaxHealth / 2 || newHealth > pokemon.MaxHealth / 2)
|
||||
return;
|
||||
|
||||
EventBatchId batchId = new();
|
||||
pokemon.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon)
|
||||
{
|
||||
BatchId = batchId,
|
||||
});
|
||||
pokemon.ChangeStatBoost(Statistic.SpecialAttack, 1, true, batchId);
|
||||
}
|
||||
}
|
12
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Bulletproof.cs
Normal file
12
Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Bulletproof.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "bulletproof")]
|
||||
public class Bulletproof : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void FailIncomingMove(IExecutingMove move, IPokemon target, ref bool fail)
|
||||
{
|
||||
if (move.UseMove.HasFlag("ballistics"))
|
||||
fail = true;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "change_move_type")]
|
||||
public class ChangeMoveTypeAbility : Script
|
||||
{
|
||||
private StringKey _fromType;
|
||||
private StringKey _toType;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
|
||||
{
|
||||
if (parameters == null)
|
||||
throw new ArgumentNullException(nameof(parameters));
|
||||
|
||||
if (!parameters.TryGetValue("from_type", out var fromTypeObj) || fromTypeObj is not string fromType)
|
||||
throw new ArgumentException("Missing 'from_type' parameter.", nameof(parameters));
|
||||
if (!parameters.TryGetValue("to_type", out var toTypeObj) || toTypeObj is not string toType)
|
||||
throw new ArgumentException("Missing 'to_type' parameter.", nameof(parameters));
|
||||
_fromType = fromType;
|
||||
_toType = toType;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit,
|
||||
ref TypeIdentifier? typeIdentifier)
|
||||
{
|
||||
var typeLibrary = target.Library.StaticLibrary.Types;
|
||||
// Both types must be valid and the current type must match the from type
|
||||
if (!typeLibrary.TryGetTypeIdentifier(_fromType, out var fromType) ||
|
||||
!typeLibrary.TryGetTypeIdentifier(_toType, out var toType) || typeIdentifier != fromType)
|
||||
{
|
||||
return;
|
||||
}
|
||||
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
|
||||
typeIdentifier = toType;
|
||||
move.GetHitData(target, hit).SetFlag("change_move_type_ability");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
|
||||
{
|
||||
if (move.GetHitData(target, hit).HasFlag("change_move_type_ability"))
|
||||
basePower = basePower.MultiplyOrMax(1.3f);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "increased_stab")]
|
||||
public class IncreasedStab : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void ChangeStabModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber,
|
||||
ref float modifier)
|
||||
{
|
||||
if (modifier.IsApproximatelyEqualTo(1.5f))
|
||||
{
|
||||
executingMove.Battle.EventHook.Invoke(new AbilityTriggerEvent(executingMove.User));
|
||||
modifier = 2.0f;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "power_up_type_at_low_health")]
|
||||
public class PowerUpTypeAtLowHealth : Script
|
||||
{
|
||||
private StringKey _type;
|
||||
private float _threshold;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
|
||||
{
|
||||
if (parameters == null)
|
||||
throw new ArgumentNullException(nameof(parameters));
|
||||
|
||||
if (!parameters.TryGetValue("type", out var type) || type is not string typeName)
|
||||
throw new ArgumentException("Parameter 'type' is required and must be a string.", nameof(parameters));
|
||||
if (!parameters.TryGetValue("threshold", out var threshold) || threshold is not float thresholdValue)
|
||||
throw new ArgumentException("Parameter 'threshold' is required and must be a float.", nameof(parameters));
|
||||
|
||||
if (thresholdValue < 0 || thresholdValue > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(threshold), "Threshold must be between 0 and 1.");
|
||||
|
||||
_type = typeName;
|
||||
_threshold = thresholdValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
|
||||
{
|
||||
var currentHealthFraction = move.User.CurrentHealth / (float)move.User.MaxHealth;
|
||||
if (currentHealthFraction <= _threshold && move.GetHitData(target, hit).Type?.Name == _type)
|
||||
{
|
||||
modifier *= 1.5f;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "prevent_critical")]
|
||||
public class PreventCritical : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void BlockIncomingCriticalHit(IExecutingMove move, IPokemon target, byte hit, ref bool block) =>
|
||||
block = true;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "prevent_stat_lowering")]
|
||||
public class PreventStatLowering : Script
|
||||
{
|
||||
private Statistic _statistic;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
|
||||
{
|
||||
if (parameters is null)
|
||||
throw new ArgumentNullException(nameof(parameters), "Parameters cannot be null.");
|
||||
if (!parameters.TryGetValue("stat", out var statObj) || statObj is not string statStr)
|
||||
throw new ArgumentException("Parameter 'stat' is required and must be a string.", nameof(parameters));
|
||||
if (!Enum.TryParse(statStr, true, out Statistic stat))
|
||||
throw new ArgumentException($"Invalid statistic '{statStr}' provided.", nameof(statStr));
|
||||
|
||||
_statistic = stat;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted,
|
||||
ref bool prevent)
|
||||
{
|
||||
if (!selfInflicted)
|
||||
prevent = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
|
||||
[Script(ScriptCategory.Ability, "suppress_weather")]
|
||||
public class SuppressWeatherAbility : Script
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void OnBeforeAnyHookInvoked(ref List<ScriptCategory>? suppressedCategories)
|
||||
{
|
||||
suppressedCategories ??= [];
|
||||
suppressedCategories.Add(ScriptCategory.Weather);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSwitchIn(IPokemon pokemon, byte position)
|
||||
{
|
||||
pokemon.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon));
|
||||
}
|
||||
}
|
32
Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/AromaVeilEffect.cs
Normal file
32
Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/AromaVeilEffect.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using PkmnLib.Static.Moves;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
|
||||
|
||||
[Script(ScriptCategory.Side, "aroma_veil")]
|
||||
public class AromaVeilEffect : Script
|
||||
{
|
||||
private HashSet<IPokemon> _placers = new();
|
||||
|
||||
public void PlacerActivated(IPokemon placer) => _placers.Add(placer);
|
||||
|
||||
public void PlacerDeactivated(IPokemon placer)
|
||||
{
|
||||
_placers.Remove(placer);
|
||||
if (_placers.Count == 0)
|
||||
RemoveSelf();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void FailIncomingMove(IExecutingMove move, IPokemon target, ref bool fail)
|
||||
{
|
||||
if (move.UseMove.HasFlag("mental") && move.UseMove.Category == MoveCategory.Status)
|
||||
fail = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PreventSecondaryEffect(IExecutingMove move, IPokemon target, byte hit, ref bool prevent)
|
||||
{
|
||||
if (move.UseMove.HasFlag("mental"))
|
||||
prevent = true;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
using PkmnLib.Static.Moves;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
|
||||
|
||||
[Script(ScriptCategory.Side, "battery")]
|
||||
public class BatteryAbilityEffect : Script
|
||||
{
|
||||
private HashSet<IPokemon> _placers = new();
|
||||
|
||||
public void PlacerActivated(IPokemon placer) => _placers.Add(placer);
|
||||
|
||||
public void PlacerDeactivated(IPokemon placer)
|
||||
{
|
||||
_placers.Remove(placer);
|
||||
if (_placers.Count == 0)
|
||||
RemoveSelf();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
|
||||
{
|
||||
if (move.UseMove.Category == MoveCategory.Special)
|
||||
{
|
||||
modifier *= 5325f / 4096f; // ~1.3x
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user