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