diff --git a/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs b/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs index 1cea3ca..a1f4821 100644 --- a/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs +++ b/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs @@ -1,6 +1,7 @@ using PkmnLib.Dynamic.Events; using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Dynamic.ScriptHandling; +using PkmnLib.Static; using PkmnLib.Static.Moves; using PkmnLib.Static.Utils; @@ -55,7 +56,7 @@ internal static class MoveTurnExecutor return; } - var executingMove = new ExecutingMoveImpl(targets, numberOfHits, chosenMove, useMove, moveChoice); + var executingMove = new ExecutingMoveImpl(targets, numberOfHits, chosenMove, useMove, moveChoice, battle); var prevented = false; executingMove.RunScriptHook(x => x.PreventMove(executingMove, ref prevented)); @@ -124,7 +125,7 @@ internal static class MoveTurnExecutor executingMove.RunScriptHook(x => x.OnBeforeHit(executingMove, target, hitIndex)); var useMove = executingMove.UseMove; - var hitType = useMove.MoveType; + var hitType = (TypeIdentifier?)useMove.MoveType; executingMove.RunScriptHook(x => x.ChangeMoveType(executingMove, target, hitIndex, ref hitType)); var hitData = (HitData)executingMove.GetDataFromRawIndex(targetHitStat + i); @@ -134,7 +135,9 @@ internal static class MoveTurnExecutor executingMove.RunScriptHook(x => x.ChangeTypesForMove(executingMove, target, hitIndex, types)); target.RunScriptHook(x => x.ChangeTypesForIncomingMove(executingMove, target, hitIndex, types)); - var effectiveness = battle.Library.StaticLibrary.Types.GetEffectiveness(hitType, types); + var effectiveness = hitType == null + ? 1 + : battle.Library.StaticLibrary.Types.GetEffectiveness(hitType.Value, types); executingMove.RunScriptHook(x => x.ChangeEffectiveness(executingMove, target, hitIndex, ref effectiveness)); target.RunScriptHook(x => x.ChangeIncomingEffectiveness(executingMove, target, hitIndex, ref effectiveness)); diff --git a/PkmnLib.Dynamic/Models/BattleSide.cs b/PkmnLib.Dynamic/Models/BattleSide.cs index c18c9c2..a3365b1 100644 --- a/PkmnLib.Dynamic/Models/BattleSide.cs +++ b/PkmnLib.Dynamic/Models/BattleSide.cs @@ -1,6 +1,7 @@ using PkmnLib.Dynamic.Events; using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Dynamic.ScriptHandling; +using PkmnLib.Static; using PkmnLib.Static.Utils; namespace PkmnLib.Dynamic.Models; @@ -130,6 +131,21 @@ public interface IBattleSide : IScriptSource, IDeepCloneable /// Gets a random Pokémon on the given side. /// byte GetRandomPosition(); + + /// + /// Marks an item as consumed for a position. Can be used by moves such as Recycle to get the item back. + /// + void SetConsumedItem(byte battleDataPosition, IItem heldItem); + + /// + /// Gets the last consumed item for a position. Can be used by moves such as Recycle to get the item back. + /// + IItem? GetLastConsumedItem(byte battleDataPosition); + + void MarkFaint(byte position); + + uint? GetLastFaintTurn(byte position); + uint? GetLastFaintTurn(); } /// @@ -302,6 +318,42 @@ public class BattleSideImpl : ScriptSource, IBattleSide /// public byte GetRandomPosition() => (byte)Battle.Random.GetInt(0, NumberOfPositions); + private Dictionary? _lastConsumedItems; + + /// + public void SetConsumedItem(byte battleDataPosition, IItem heldItem) + { + _lastConsumedItems ??= new Dictionary(); + _lastConsumedItems[battleDataPosition] = heldItem; + } + + /// + public IItem? GetLastConsumedItem(byte battleDataPosition) => + _lastConsumedItems?.GetValueOrDefault(battleDataPosition); + + private Dictionary? _lastFaintTurn; + + /// + public void MarkFaint(byte position) + { + _lastFaintTurn ??= new Dictionary(); + _lastFaintTurn[position] = Battle.CurrentTurnNumber; + } + + /// + public uint? GetLastFaintTurn(byte position) + { + if (_lastFaintTurn is null) + return null; + if (_lastFaintTurn.TryGetValue(position, out var turn)) + return turn; + return null; + } + + /// + public uint? GetLastFaintTurn() => + _lastFaintTurn?.Values.Max() ?? null; + /// public override int ScriptCount => 1 + Battle.ScriptCount; diff --git a/PkmnLib.Dynamic/Models/ExecutingMove.cs b/PkmnLib.Dynamic/Models/ExecutingMove.cs index f0486e8..e440021 100644 --- a/PkmnLib.Dynamic/Models/ExecutingMove.cs +++ b/PkmnLib.Dynamic/Models/ExecutingMove.cs @@ -33,9 +33,9 @@ public interface IHitData uint Damage { get; } /// - /// The type of the hit. + /// The type of the hit. Null if the move is typeless. /// - TypeIdentifier Type { get; } + TypeIdentifier? Type { get; } /// /// Whether the hit has failed. @@ -64,7 +64,7 @@ public record HitData : IHitData public uint Damage { get; internal set; } /// - public TypeIdentifier Type { get; internal set; } + public TypeIdentifier? Type { get; internal set; } /// public bool HasFailed { get; private set; } @@ -141,6 +141,8 @@ public interface IExecutingMove : IScriptSource IMoveChoice MoveChoice { get; } IReadOnlyList Hits { get; } + + IBattle Battle { get; } } /// @@ -148,16 +150,18 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove { private readonly IReadOnlyList _targets; private readonly IHitData[] _hits; + private readonly IBattle _battle; /// public ExecutingMoveImpl(IReadOnlyList targets, byte numberOfHits, ILearnedMove chosenMove, - IMoveData useMove, IMoveChoice moveChoice) + IMoveData useMove, IMoveChoice moveChoice, IBattle battle) { _targets = targets; NumberOfHits = numberOfHits; ChosenMove = chosenMove; UseMove = useMove; MoveChoice = moveChoice; + _battle = battle; var totalHits = targets.Count * numberOfHits; _hits = new IHitData[totalHits]; @@ -230,6 +234,9 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove /// public IReadOnlyList Hits => _hits; + /// + public IBattle Battle => _battle; + /// public override int ScriptCount => 2 + User.ScriptCount; diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs index 5d4a26e..b8ef3f2 100644 --- a/PkmnLib.Dynamic/Models/Pokemon.cs +++ b/PkmnLib.Dynamic/Models/Pokemon.cs @@ -346,7 +346,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable /// /// Adds a non-volatile status to the Pokemon. /// - void SetStatus(StringKey status); + bool SetStatus(StringKey status); /// /// Removes the current non-volatile status from the Pokemon. @@ -447,7 +447,7 @@ public interface IPokemonBattleData : IDeepCloneable /// /// Marks an item as consumed. /// - void MarkItemAsConsumed(IItem itemName); + void MarkItemAsConsumed(IItem item); uint SwitchInTurn { get; internal set; } @@ -780,6 +780,7 @@ public class PokemonImpl : ScriptSource, IPokemon this.RunScriptHook(script => script.PreventHeldItemConsume(this, HeldItem, ref prevented)); if (prevented) return false; + BattleData.MarkItemAsConsumed(HeldItem); } // TODO: actually consume the item @@ -1007,6 +1008,7 @@ public class PokemonImpl : ScriptSource, IPokemon { BattleData.Battle.Sides[BattleData.SideIndex].MarkPositionAsUnfillable(BattleData.Position); } + BattleData.BattleSide.MarkFaint(BattleData.Position); // Validate the battle state to see if the battle is over. BattleData.Battle.ValidateBattleState(); @@ -1078,11 +1080,12 @@ public class PokemonImpl : ScriptSource, IPokemon public bool HasStatus(StringKey status) => StatusScript.Script?.Name == status; /// - public void SetStatus(StringKey status) + public bool SetStatus(StringKey status) { if (!Library.ScriptResolver.TryResolve(ScriptCategory.Status, status, null, out var statusScript)) throw new KeyNotFoundException($"Status script {status} not found"); StatusScript.Set(statusScript); + return true; } /// @@ -1255,9 +1258,10 @@ public class PokemonBattleDataImpl : IPokemonBattleData public IReadOnlyList ConsumedItems => _consumedItems; /// - public void MarkItemAsConsumed(IItem itemName) + public void MarkItemAsConsumed(IItem item) { - _consumedItems.Add(itemName); + _consumedItems.Add(item); + BattleSide.SetConsumedItem(Position, item); } /// diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs index 95816a1..4419f53 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Script.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs @@ -216,8 +216,10 @@ public abstract class Script : IDeepCloneable /// /// This function allows the script to change the actual type that is used for the move on a target. + /// If this is set to null, the move will be treated as a typeless move. /// - public virtual void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType) + public virtual void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, + ref TypeIdentifier? typeIdentifier) { } @@ -601,20 +603,35 @@ public abstract class Script : IDeepCloneable { } + /// + /// This function allows a script to change the types a target has. Multiple types can be set, and will be used + /// for the effectiveness calculation. + /// public virtual void ChangeTypesForMove(IExecutingMove executingMove, IPokemon target, byte hitIndex, IList types) { } + /// + /// This function allows a script to change the types a Pokemon has for a move that's incoming. Multiple types can + /// be set, and will be used for the effectiveness calculation. + /// public virtual void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex, IList types) { } + /// + /// This function allows a script to change the handling of the move category. This is used for moves that + /// are sometimes a status move, and sometimes a damaging move, such as pollen puff. + /// public virtual void ChangeCategory(IExecutingMove move, IPokemon target, byte hitIndex, ref MoveCategory category) { } + /// + /// Triggers first when we're about to hit a target. + /// public virtual void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex) { } diff --git a/PkmnLib.Tests/Data/Moves.jsonc b/PkmnLib.Tests/Data/Moves.jsonc index 7a8d07b..e065226 100755 --- a/PkmnLib.Tests/Data/Moves.jsonc +++ b/PkmnLib.Tests/Data/Moves.jsonc @@ -8763,7 +8763,10 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "recycle" + } }, { "name": "reflect", @@ -8776,7 +8779,10 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "reflect" + } }, { "name": "reflect_type", @@ -8790,7 +8796,10 @@ "flags": [ "protect", "ignore-substitute" - ] + ], + "effect": { + "name": "reflect_type" + } }, { "name": "refresh", @@ -8803,7 +8812,10 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "refresh" + } }, { "name": "relic_song", @@ -8819,7 +8831,14 @@ "mirror", "sound", "ignore-substitute" - ] + ], + "effect": { + "name": "set_status", + "chance": 10, + "parameters": { + "status": "sleep" + } + } }, { "name": "rest", @@ -8833,7 +8852,10 @@ "flags": [ "snatch", "heal" - ] + ], + "effect": { + "name": "rest" + } }, { "name": "retaliate", @@ -8848,7 +8870,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "retaliate" + } }, { "name": "return", @@ -8863,7 +8888,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "return" + } }, { "name": "revelation_dance", @@ -8878,7 +8906,10 @@ "protect", "mirror", "dance" - ] + ], + "effect": { + "name": "revelation_dance" + } }, { "name": "revenge", @@ -8893,7 +8924,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "double_power_if_target_damaged_in_turn" + } }, { "name": "reversal", @@ -8908,7 +8942,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "flail" + } }, { "name": "roar", @@ -8924,7 +8961,10 @@ "mirror", "sound", "ignore-substitute" - ] + ], + "effect": { + "name": "roar" + } }, { "name": "roar_of_time", @@ -8939,7 +8979,10 @@ "recharge", "protect", "mirror" - ] + ], + "effect": { + "name": "requires_recharge" + } }, { "name": "rock_blast", @@ -8953,7 +8996,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "2_5_hit_move" + } }, { "name": "rock_climb", @@ -8968,7 +9014,11 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "confuse", + "chance": 20 + } }, { "name": "rock_polish", @@ -8981,7 +9031,13 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "change_user_speed", + "parameters": { + "amount": 2 + } + } }, { "name": "rock_slide", @@ -8995,7 +9051,11 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "flinch", + "chance": 30 + } }, { "name": "rock_smash", @@ -9010,7 +9070,14 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "change_target_defense", + "chance": 50, + "parameters": { + "amount": -1 + } + } }, { "name": "rock_throw", @@ -9025,6 +9092,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "rock_tomb", @@ -9038,7 +9106,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "change_target_speed", + "chance": 100, + "parameters": { + "amount": -1 + } + } }, { "name": "rock_wrecker", @@ -9053,7 +9128,10 @@ "recharge", "protect", "mirror" - ] + ], + "effect": { + "name": "requires_recharge" + } }, { "name": "role_play", @@ -9066,7 +9144,10 @@ "category": "status", "flags": [ "ignore-substitute" - ] + ], + "effect": { + "name": "role_play" + } }, { "name": "rolling_kick", @@ -9081,7 +9162,11 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "flinch", + "chance": 30 + } }, { "name": "rollout", @@ -9096,7 +9181,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "ice_ball" + } }, { "name": "roost", @@ -9110,7 +9198,10 @@ "flags": [ "snatch", "heal" - ] + ], + "effect": { + "name": "roost" + } }, { "name": "rototiller", @@ -9124,7 +9215,10 @@ "flags": [ "distance", "nonskybattle" - ] + ], + "effect": { + "name": "rototiller" + } }, { "name": "round", diff --git a/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/HiddenPowerTests.cs b/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/HiddenPowerTests.cs index 0db770c..4758c46 100644 --- a/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/HiddenPowerTests.cs +++ b/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/HiddenPowerTests.cs @@ -59,12 +59,12 @@ public class HiddenPowerTests staticLibrary.Setup(x => x.Types).Returns(typeLibrary); dynamicLibrary.Setup(x => x.StaticLibrary).Returns(staticLibrary.Object); - var moveType = new TypeIdentifier(1, "normal"); + TypeIdentifier? moveType = new TypeIdentifier(1, "normal"); var hiddenPower = new HiddenPower(); hiddenPower.ChangeMoveType(executingMove.Object, target.Object, 0, ref moveType); - await Assert.That(moveType.Name).IsEqualTo(test.ExpectedType); + await Assert.That(moveType!.Value.Name).IsEqualTo(test.ExpectedType); } [Test, MethodDataSource(nameof(HiddenPowerTestData))] diff --git a/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/MultiAttackTests.cs b/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/MultiAttackTests.cs index e8712c3..d5a37c9 100644 --- a/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/MultiAttackTests.cs +++ b/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/MultiAttackTests.cs @@ -63,7 +63,7 @@ public class MultiAttackTests var move = new Mock(); var target = new Mock(); var user = new Mock(); - var typeIdentifier = new TypeIdentifier(1, "Normal"); + TypeIdentifier? typeIdentifier = new TypeIdentifier(1, "Normal"); var dynamicLibrary = new Mock(); var staticLibrary = new Mock(); var item = new Mock(); @@ -84,6 +84,6 @@ public class MultiAttackTests multiAttack.ChangeMoveType(move.Object, target.Object, 0, ref typeIdentifier); // Assert - await Assert.That(typeIdentifier.Name).IsEqualTo(test.ExpectedTypeName); + await Assert.That(typeIdentifier!.Value.Name).IsEqualTo(test.ExpectedTypeName); } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7DamageCalculator.cs b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7DamageCalculator.cs index ce56220..bebd1f6 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7DamageCalculator.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7DamageCalculator.cs @@ -47,7 +47,7 @@ public class Gen7DamageCalculator(bool hasRandomness) : IDamageCalculator floatDamage = MathF.Floor(floatDamage * randomFactor); } - if (executingMove.User.Types.Contains(hitData.Type)) + if (hitData.Type != null && executingMove.User.Types.Contains(hitData.Type.Value)) { var stabModifier = 1.5f; executingMove.RunScriptHook(script => diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/FutureSightEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/FutureSightEffect.cs index 14ad2fc..3ea758a 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/FutureSightEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/FutureSightEffect.cs @@ -25,7 +25,7 @@ public class FutureSightEffect : Script } var damageCalculator = battle.Library.DamageCalculator; var executingMove = new ExecutingMoveImpl([target], 1, _moveChoice.ChosenMove, - _moveChoice.ChosenMove.MoveData, _moveChoice); + _moveChoice.ChosenMove.MoveData, _moveChoice, battle); var hitData = executingMove.GetHitData(target, 0); var damage = damageCalculator.GetDamage(executingMove, target, 1, hitData); diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/IonDelugeEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/IonDelugeEffect.cs index b6edf72..d10121b 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/IonDelugeEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/IonDelugeEffect.cs @@ -6,9 +6,9 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Battle; public class IonDelugeEffect : Script { /// - public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType) + public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType) { - if (moveType.Name == "normal" && + if (moveType?.Name == "normal" && target.Library.StaticLibrary.Types.TryGetTypeIdentifier("electric", out var electricType)) { moveType = electricType; diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs index 392d3f8..75227d2 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs @@ -13,4 +13,6 @@ public static class CustomTriggers public static readonly StringKey IgnoreHail = "ignores_hail"; public static readonly StringKey LightScreenNumberOfTurns = "light_screen_number_of_turns"; + + public static readonly StringKey ReflectNumberOfTurns = "reflect_number_of_turns"; } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/MoveVolatile/ElectrifyEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/MoveVolatile/ElectrifyEffect.cs index 9447085..aaa012d 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/MoveVolatile/ElectrifyEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/MoveVolatile/ElectrifyEffect.cs @@ -6,7 +6,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.MoveVolatile; public class ElectrifyEffect : Script { /// - public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType) + public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType) { var battleData = target.BattleData; if (battleData == null) diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HiddenPower.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HiddenPower.cs index a2456ca..465b0b1 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HiddenPower.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HiddenPower.cs @@ -7,13 +7,14 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves; public class HiddenPower : Script { /// - public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType) + public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType) { var ivs = move.User.IndividualValues; var type = GetHiddenPowerValue(ivs, 0x00000001) * 15 / 63; - move.User.Library.StaticLibrary.Types.TryGetTypeIdentifierFromIndex((byte)(type + 2), out moveType); + if (move.User.Library.StaticLibrary.Types.TryGetTypeIdentifierFromIndex((byte)(type + 2), out var t)) + moveType = t; } /// diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Judgement.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Judgement.cs index afdb8de..f4158ff 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Judgement.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Judgement.cs @@ -6,7 +6,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves; public class Judgement : Script { /// - public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType) + public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType) { var heldItem = move.User.HeldItem; if (heldItem == null) diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MultiAttack.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MultiAttack.cs index 5f0c3c1..4de0b8a 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MultiAttack.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MultiAttack.cs @@ -6,7 +6,7 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves; public class MultiAttack : Script { /// - public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType) + public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType) { var item = move.User.HeldItem?.Name.ToString(); var typeLibrary = move.User.Library.StaticLibrary.Types; @@ -15,6 +15,7 @@ public class MultiAttack : Script return; var memoryType = item[..^7]; - typeLibrary.TryGetTypeIdentifier(memoryType, out moveType); + if (typeLibrary.TryGetTypeIdentifier(memoryType, out var t)) + moveType = t; } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/NaturalGift.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/NaturalGift.cs index bed9a42..b6fc112 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/NaturalGift.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/NaturalGift.cs @@ -20,7 +20,7 @@ public class NaturalGift : Script } /// - public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType) + public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType) { var naturalGiftData = GetNaturalGiftData(move.User.HeldItem); if (naturalGiftData == null) diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Recycle.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Recycle.cs new file mode 100644 index 0000000..92fed20 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Recycle.cs @@ -0,0 +1,25 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "recycle")] +public class Recycle : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + if (move.User.HeldItem is not null) + { + move.GetHitData(target, hit).Fail(); + return; + } + var battleData = move.User.BattleData; + if (battleData is null) + return; + var lastItem = battleData.BattleSide.GetLastConsumedItem(battleData.Position); + if (lastItem is null) + { + move.GetHitData(target, hit).Fail(); + return; + } + _ = move.User.SetHeldItem(lastItem); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Reflect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Reflect.cs new file mode 100644 index 0000000..3bb7e13 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Reflect.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "reflect")] +public class Reflect : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var battleData = move.User.BattleData; + if (battleData is null) + return; + var numberOfTurns = 5; + var dict = new Dictionary + { + { "duration", numberOfTurns }, + }; + move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ReflectNumberOfTurns, dict)); + numberOfTurns = (int)dict.GetOrDefault("duration", numberOfTurns)!; + + battleData.BattleSide.VolatileScripts.Add(new Side.ReflectEffect(numberOfTurns)); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ReflectType.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ReflectType.cs new file mode 100644 index 0000000..d83527e --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ReflectType.cs @@ -0,0 +1,12 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "reflect_type")] +public class ReflectType : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var targetTypes = target.Types; + move.User.SetTypes(targetTypes); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Refresh.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Refresh.cs new file mode 100644 index 0000000..ce55400 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Refresh.cs @@ -0,0 +1,23 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "refresh")] +public class Refresh : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var userStatus = move.User.StatusScript; + switch (userStatus.Script?.Name) + { + case "paralyzed": + case "burned": + case "poisoned": + case "badly_poisoned": + move.User.ClearStatus(); + break; + default: + move.GetHitData(target, hit).Fail(); + break; + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Rest.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Rest.cs new file mode 100644 index 0000000..95ce5bb --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Rest.cs @@ -0,0 +1,24 @@ +using PkmnLib.Plugin.Gen7.Scripts.Status; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "rest")] +public class Rest : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + if (move.User.HasStatus(ScriptUtils.ResolveName())) + { + move.GetHitData(target, hit).Fail(); + return; + } + if (!move.User.Heal(move.User.MaxHealth, false, forceHeal: false)) + { + move.GetHitData(target, hit).Fail(); + return; + } + move.User.SetStatus(ScriptUtils.ResolveName()); + ((Sleep)move.User.StatusScript.Script!).Turns = 2; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Retaliate.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Retaliate.cs new file mode 100644 index 0000000..85d558a --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Retaliate.cs @@ -0,0 +1,24 @@ +using System.Linq; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "retaliate")] +public class Retaliate : Script +{ + /// + public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) + { + var battleData = move.User.BattleData; + if (battleData is null) + return; + + var lastFaint = battleData.BattleSide.GetLastFaintTurn(); + if (lastFaint == null) + return; + if (lastFaint >= battleData.Battle.CurrentTurnNumber - 1) + { + basePower = basePower.MultiplyOrMax(2); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Return.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Return.cs new file mode 100644 index 0000000..bcc1222 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Return.cs @@ -0,0 +1,17 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "return")] +public class Return : Script +{ + /// + public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) + { + var friendship = move.User.Happiness; + var power = friendship * 2 / 5; + // The power is capped at 102, but as friendship is a byte, and thus max 255, this is already heuristically + // capped (255 * 2 / 5 = 102). + if (power < 1) + power = 1; + basePower = (byte)power; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RevelationDance.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RevelationDance.cs new file mode 100644 index 0000000..59c2198 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RevelationDance.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "revelation_dance")] +public class RevelationDance : Script +{ + /// + public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType) + { + // The type of the move is the same as the user's first type. + var user = move.User; + if (user.Types.Count > 0) + { + moveType = user.Types[0]; + } + // If the user has no types, the move is typeless. + else + { + moveType = null; + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Roar.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Roar.cs new file mode 100644 index 0000000..e3ead07 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Roar.cs @@ -0,0 +1,7 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "roar")] +public class Roar : Script +{ + // FIXME: Implement roar +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RolePlay.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RolePlay.cs new file mode 100644 index 0000000..1e612fb --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RolePlay.cs @@ -0,0 +1,17 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "role_play")] +public class RolePlay : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var ability = target.ActiveAbility; + if (ability is null || ability.HasFlag("cant_be_copied")) + { + move.GetHitData(target, hit).Fail(); + return; + } + move.User.ChangeAbility(ability); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Roost.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Roost.cs new file mode 100644 index 0000000..42d3184 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Roost.cs @@ -0,0 +1,14 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "roost")] +public class Roost : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + target.Heal(target.MaxHealth / 2); + target.Volatile.Add(new RoostEffect()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Rototiller.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Rototiller.cs new file mode 100644 index 0000000..9c25d01 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Rototiller.cs @@ -0,0 +1,22 @@ +using System.Linq; +using PkmnLib.Static; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "rototiller")] +public class Rototiller : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var pokemon = move.Battle.Sides.SelectMany(x => x.Pokemon).WhereNotNull() + .Where(x => x.Types.Any(y => y.Name == "grass")); + EventBatchId batchId = new(); + foreach (var pkmn in pokemon) + { + pkmn.ChangeStatBoost(Statistic.Attack, 1, pkmn == move.User, batchId); + pkmn.ChangeStatBoost(Statistic.SpecialAttack, 1, pkmn == move.User, batchId); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PowderEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PowderEffect.cs index 13faff8..8f2f754 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PowderEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PowderEffect.cs @@ -10,7 +10,7 @@ public class PowderEffect : Script public override void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) { var hit = executingMove.GetHitData(target, hitIndex); - if (hit.Type.Name == "fire") + if (hit.Type?.Name == "fire") { executingMove.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("powder_explodes", new Dictionary diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/RoostEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/RoostEffect.cs new file mode 100644 index 0000000..0a4fe5d --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/RoostEffect.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using PkmnLib.Static; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "roost_effect")] +public class RoostEffect : Script +{ + /// + public override void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex, + IList types) + { + types.RemoveAll(x => x.Name == "flying"); + } + + /// + public override void OnEndTurn(IBattle battle) => RemoveSelf(); +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/ReflectEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/ReflectEffect.cs index 84c28e0..92c0f8f 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/ReflectEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/ReflectEffect.cs @@ -1,7 +1,40 @@ +using PkmnLib.Static.Moves; + namespace PkmnLib.Plugin.Gen7.Scripts.Side; [Script(ScriptCategory.Side, "reflect")] -public class ReflectEffect : Script +public class ReflectEffect(int turns) : Script { - // TODO: Implement ReflectEffect + private int _turns = turns; + + /// + public override void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage) + { + var hitData = move.GetHitData(target, hit); + if (move.UseMove.Category != MoveCategory.Physical) + return; + if (hitData.IsCritical) + return; + switch (move.Battle.PositionsPerSide) + { + case 1: + damage /= 2; + break; + default: + damage *= 2 / 3; + break; + } + } + + /// + public override void OnEndTurn(IBattle battle) + { + if (_turns > 0) + { + _turns--; + return; + } + + RemoveSelf(); + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs index f07a0b9..2ac5990 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs @@ -3,4 +3,5 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Status; [Script(ScriptCategory.Status, "sleep")] public class Sleep : Script { + public int Turns { get; set; } } \ No newline at end of file