diff --git a/PkmnLib.Dynamic/BattleFlow/TurnRunner.cs b/PkmnLib.Dynamic/BattleFlow/TurnRunner.cs
index f8f5a4c..63a3aed 100644
--- a/PkmnLib.Dynamic/BattleFlow/TurnRunner.cs
+++ b/PkmnLib.Dynamic/BattleFlow/TurnRunner.cs
@@ -179,13 +179,8 @@ public static class TurnRunner
var battleData = user.BattleData;
if (battleData == null)
return;
- IPokemon? target = null;
- if (itemChoice is { TargetSide: not null, TargetPosition: not null })
- {
- var side = battle.Sides[itemChoice.TargetSide.Value];
- target = side.Pokemon[itemChoice.TargetPosition.Value];
- }
+ var target = itemChoice.GetTargetPokemon(battle);
battle.EventHook.Invoke(new ItemUseEvent(user, itemChoice.Item));
- itemChoice.Item.RunItemScript(battle.Library.ScriptResolver, target ?? user, battle.EventHook);
+ itemChoice.Item.RunItemScript(battle.Library.ScriptResolver, target ?? user, user, battle, battle.EventHook);
}
}
\ No newline at end of file
diff --git a/PkmnLib.Dynamic/Models/Battle.cs b/PkmnLib.Dynamic/Models/Battle.cs
index 7c260af..4ea6c15 100644
--- a/PkmnLib.Dynamic/Models/Battle.cs
+++ b/PkmnLib.Dynamic/Models/Battle.cs
@@ -351,6 +351,21 @@ public class BattleImpl : ScriptSource, IBattle
if (switchChoice.SwitchTo == switchChoice.User)
return false;
}
+ else if (choice is IItemChoice itemChoice)
+ {
+ if (!Library.ScriptResolver.TryResolveBattleItemScript(itemChoice.Item, out var itemScript))
+ return false;
+ if (!itemScript.IsItemUsable)
+ return false;
+ if (itemScript.TargetType != ItemTargetType.None)
+ {
+ var target = itemChoice.GetTargetPokemon(this);
+ if (target is null || !itemScript.IsTargetValid(target))
+ return false;
+ if (!itemScript.TargetType.IsValidTarget(this, choice.User, target))
+ return false;
+ }
+ }
return true;
diff --git a/PkmnLib.Dynamic/Models/Choices/ItemChoice.cs b/PkmnLib.Dynamic/Models/Choices/ItemChoice.cs
index e166833..4396cb7 100644
--- a/PkmnLib.Dynamic/Models/Choices/ItemChoice.cs
+++ b/PkmnLib.Dynamic/Models/Choices/ItemChoice.cs
@@ -11,40 +11,67 @@ public interface IItemChoice : ITurnChoice
///
/// The item that is used.
///
- public IItem Item { get; }
+ IItem Item { get; }
///
- /// The side the move is targeted at.
+ /// The target Pokémon of the item, if any.
///
- byte? TargetSide { get; }
-
- ///
- /// The position the move is targeted at.
- ///
- byte? TargetPosition { get; }
+ IPokemon? GetTargetPokemon(IBattle battle);
}
///
public class ItemChoice : TurnChoice, IItemChoice
{
///
- public ItemChoice(IPokemon user, IItem item, byte? targetSide, byte? targetPosition) : base(user)
+ private ItemChoice(IPokemon user, IItem item, byte? targetSide, byte? targetPosition, IPokemon? targetPokemon) :
+ base(user)
{
Item = item;
TargetSide = targetSide;
TargetPosition = targetPosition;
+ TargetPokemon = targetPokemon;
}
+ public static ItemChoice CreateWithoutTarget(IPokemon user, IItem item) =>
+ new(user, item, null, null, null);
+
+ public static ItemChoice CreateForOpponent(IPokemon user, IItem item, byte targetSide, byte targetPosition) =>
+ new(user, item, targetSide, targetPosition, null);
+
+ public static ItemChoice CreateForPartyMember(IPokemon user, IItem item, IPokemon targetPokemon) =>
+ new(user, item, null, null, targetPokemon);
+
///
/// The item that is used.
///
public IItem Item { get; }
///
- public byte? TargetSide { get; }
+ public IPokemon? GetTargetPokemon(IBattle battle)
+ {
+ if (TargetPokemon != null)
+ return TargetPokemon;
- ///
- public byte? TargetPosition { get; }
+ if (!TargetSide.HasValue || !TargetPosition.HasValue)
+ return null;
+ var side = battle.Sides[TargetSide.Value];
+ return side.Pokemon[TargetPosition.Value];
+ }
+
+ ///
+ /// The target side of the item, if any.
+ ///
+ private byte? TargetSide { get; }
+
+ ///
+ /// The target position of the item, if any.
+ ///
+ private byte? TargetPosition { get; }
+
+ ///
+ /// The target Pokémon of the item, if any. This is used for party members.
+ ///
+ private IPokemon? TargetPokemon { get; }
///
public override int ScriptCount => User.ScriptCount;
diff --git a/PkmnLib.Dynamic/Models/ItemTargetType.cs b/PkmnLib.Dynamic/Models/ItemTargetType.cs
new file mode 100644
index 0000000..ac21259
--- /dev/null
+++ b/PkmnLib.Dynamic/Models/ItemTargetType.cs
@@ -0,0 +1,67 @@
+namespace PkmnLib.Dynamic.Models;
+
+///
+/// Types of targets an item can have.
+///
+[Flags]
+public enum ItemTargetType
+{
+ ///
+ /// The item does not require a target.
+ ///
+ None = 0,
+
+ ///
+ /// The item targets Pokémon that are part of the user's own team.
+ ///
+ OwnPokemon = 1,
+
+ ///
+ /// The item targets allied Pokémon.
+ ///
+ AllyPokemon = 2,
+
+ ///
+ /// The item targets opposing Pokémon.
+ ///
+ FoePokemon = 4,
+}
+
+///
+/// Helper methods for ItemTargetType.
+///
+public static class ItemTargetTypeHelpers
+{
+ ///
+ /// Determines if the given target is valid based on the ItemTargetType.
+ ///
+ public static bool IsValidTarget(this ItemTargetType targetType, IBattle battle, IPokemon user, IPokemon target)
+ {
+ if (targetType == ItemTargetType.None)
+ return true;
+
+ if (targetType.HasFlag(ItemTargetType.OwnPokemon))
+ {
+ var userParty = battle.Parties.FirstOrDefault(x => x.Party.Contains(user));
+ var targetParty = battle.Parties.FirstOrDefault(x => x.Party.Contains(target));
+ if (userParty is not null && targetParty is not null && userParty == targetParty)
+ return true;
+ }
+
+ if (targetType.HasFlag(ItemTargetType.AllyPokemon))
+ {
+ if (user.BattleData?.BattleSide == target.BattleData?.BattleSide)
+ return true;
+ }
+
+ if (targetType.HasFlag(ItemTargetType.FoePokemon))
+ {
+ var userParty = battle.Parties.FirstOrDefault(x => x.Party.Contains(user));
+ var targetParty = battle.Parties.FirstOrDefault(x => x.Party.Contains(target));
+ if (userParty is not null && targetParty is not null && userParty != targetParty)
+ return true;
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/PkmnLib.Dynamic/ScriptHandling/ItemScript.cs b/PkmnLib.Dynamic/ScriptHandling/ItemScript.cs
index 4f8aa4f..5b2786f 100644
--- a/PkmnLib.Dynamic/ScriptHandling/ItemScript.cs
+++ b/PkmnLib.Dynamic/ScriptHandling/ItemScript.cs
@@ -36,7 +36,7 @@ public abstract class ItemScript : IDeepCloneable
///
/// Returns whether the item requires a target to be used.
///
- public virtual bool RequiresTarget => false;
+ public virtual ItemTargetType TargetType => ItemTargetType.None;
///
/// Returns whether the item is usable on the given target.
diff --git a/PkmnLib.Dynamic/ScriptHandling/PokeballScript.cs b/PkmnLib.Dynamic/ScriptHandling/PokeballScript.cs
index bb81689..4fdc006 100644
--- a/PkmnLib.Dynamic/ScriptHandling/PokeballScript.cs
+++ b/PkmnLib.Dynamic/ScriptHandling/PokeballScript.cs
@@ -29,7 +29,7 @@ public abstract class PokeballScript : ItemScript
public override bool IsItemUsable => true;
///
- public override bool RequiresTarget => true;
+ public override ItemTargetType TargetType => ItemTargetType.FoePokemon;
///
public override bool IsTargetValid(IPokemon target) =>
diff --git a/PkmnLib.Dynamic/ScriptHandling/ScriptExecution.cs b/PkmnLib.Dynamic/ScriptHandling/ScriptExecution.cs
index 31fb07c..1d9d216 100644
--- a/PkmnLib.Dynamic/ScriptHandling/ScriptExecution.cs
+++ b/PkmnLib.Dynamic/ScriptHandling/ScriptExecution.cs
@@ -100,8 +100,8 @@ public static class ScriptExecution
///
/// Executes a script on an item.
///
- public static void RunItemScript(this IItem item, ScriptResolver scriptResolver, IPokemon? target,
- EventHook eventHook)
+ public static void RunItemScript(this IItem item, ScriptResolver scriptResolver, IPokemon? target, IPokemon user,
+ IBattle battle, EventHook eventHook)
{
if (!scriptResolver.TryResolveBattleItemScript(item, out var itemScript))
{
@@ -111,16 +111,20 @@ public static class ScriptExecution
return;
itemScript.OnInitialize(item.BattleEffect!.Parameters);
- var requiresTarget = itemScript.RequiresTarget;
- if (requiresTarget)
- {
- if (target == null)
- throw new ArgumentNullException(nameof(target), "Item script requires a target.");
- itemScript.OnUseWithTarget(target, eventHook);
- }
- else
+ var targetType = itemScript.TargetType;
+ if (targetType == ItemTargetType.None)
{
itemScript.OnUse(eventHook);
}
+ else
+ {
+ if (target == null)
+ throw new ArgumentNullException(nameof(target), "Item script requires a target.");
+ if (!targetType.IsValidTarget(battle, user, target))
+ throw new InvalidOperationException("Item script target is not valid for the required target type.");
+ if (!itemScript.IsTargetValid(target))
+ throw new InvalidOperationException("Item script target is not valid.");
+ itemScript.OnUseWithTarget(target, eventHook);
+ }
}
}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Data/Items.json b/Plugins/PkmnLib.Plugin.Gen7/Data/Items.json
index 04a236d..d6825df 100755
--- a/Plugins/PkmnLib.Plugin.Gen7/Data/Items.json
+++ b/Plugins/PkmnLib.Plugin.Gen7/Data/Items.json
@@ -2672,6 +2672,7 @@
{
"name": "hyper_potion",
"itemType": "Medicine",
+ "battleType": "Healing",
"flags": [],
"price": 1500,
"additionalData": {
@@ -3549,6 +3550,7 @@
{
"name": "max_potion",
"itemType": "Medicine",
+ "battleType": "Healing",
"flags": [],
"price": 2500,
"additionalData": {
@@ -3567,6 +3569,7 @@
{
"name": "max_revive",
"itemType": "Medicine",
+ "battleType": "Healing",
"flags": [],
"price": 4000,
"additionalData": {
@@ -4584,6 +4587,7 @@
{
"name": "potion",
"itemType": "Medicine",
+ "battleType": "Healing",
"flags": [],
"price": 200,
"additionalData": {
@@ -5230,6 +5234,7 @@
{
"name": "revival_herb",
"itemType": "Medicine",
+ "battleType": "Healing",
"flags": [],
"price": 2800,
"additionalData": {
@@ -5239,6 +5244,7 @@
{
"name": "revive",
"itemType": "Medicine",
+ "battleType": "Healing",
"flags": [],
"price": 2000,
"additionalData": {
@@ -6185,6 +6191,7 @@
{
"name": "super_potion",
"itemType": "Medicine",
+ "battleType": "Healing",
"flags": [],
"price": 700,
"additionalData": {
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/EvolutionItem.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/EvolutionItem.cs
index 94522dd..e5d98ae 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/EvolutionItem.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/EvolutionItem.cs
@@ -14,7 +14,7 @@ public class EvolutionItem : ItemScript
public override bool IsItemUsable => true;
///
- public override bool RequiresTarget => true;
+ public override ItemTargetType TargetType => ItemTargetType.OwnPokemon;
///
public override bool IsTargetValid(IPokemon target)
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/HealingItem.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/HealingItem.cs
index 0647789..860f460 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/HealingItem.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/HealingItem.cs
@@ -14,7 +14,7 @@ public class HealingItem : ItemScript
public override bool IsItemUsable => true;
///
- public override bool RequiresTarget => true;
+ public override ItemTargetType TargetType => ItemTargetType.OwnPokemon;
///
public override void OnInitialize(IReadOnlyDictionary? parameters)
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BugBite.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BugBite.cs
index 26b9e28..8088d57 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BugBite.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BugBite.cs
@@ -20,6 +20,7 @@ public class BugBite : Script, IScriptOnSecondaryEffect
}
_ = target.ForceSetHeldItem(null);
- targetHeldItem.RunItemScript(battleData.Battle.Library.ScriptResolver, user, move.Battle.EventHook);
+ targetHeldItem.RunItemScript(battleData.Battle.Library.ScriptResolver, user, user, battleData.Battle,
+ move.Battle.EventHook);
}
}
\ No newline at end of file