Fixes and reworking of item use
All checks were successful
Build / Build (push) Successful in 57s

This commit is contained in:
2025-11-08 11:43:07 +01:00
parent fa05cdd773
commit 21ec4b28c7
11 changed files with 150 additions and 34 deletions

View File

@@ -179,13 +179,8 @@ public static class TurnRunner
var battleData = user.BattleData; var battleData = user.BattleData;
if (battleData == null) if (battleData == null)
return; return;
IPokemon? target = null; var target = itemChoice.GetTargetPokemon(battle);
if (itemChoice is { TargetSide: not null, TargetPosition: not null })
{
var side = battle.Sides[itemChoice.TargetSide.Value];
target = side.Pokemon[itemChoice.TargetPosition.Value];
}
battle.EventHook.Invoke(new ItemUseEvent(user, itemChoice.Item)); 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);
} }
} }

View File

@@ -351,6 +351,21 @@ public class BattleImpl : ScriptSource, IBattle
if (switchChoice.SwitchTo == switchChoice.User) if (switchChoice.SwitchTo == switchChoice.User)
return false; 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; return true;

View File

@@ -11,40 +11,67 @@ public interface IItemChoice : ITurnChoice
/// <summary> /// <summary>
/// The item that is used. /// The item that is used.
/// </summary> /// </summary>
public IItem Item { get; } IItem Item { get; }
/// <summary> /// <summary>
/// The side the move is targeted at. /// The target Pokémon of the item, if any.
/// </summary> /// </summary>
byte? TargetSide { get; } IPokemon? GetTargetPokemon(IBattle battle);
/// <summary>
/// The position the move is targeted at.
/// </summary>
byte? TargetPosition { get; }
} }
/// <inheritdoc cref="IItemChoice"/> /// <inheritdoc cref="IItemChoice"/>
public class ItemChoice : TurnChoice, IItemChoice public class ItemChoice : TurnChoice, IItemChoice
{ {
/// <inheritdoc cref="ItemChoice"/> /// <inheritdoc cref="ItemChoice"/>
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; Item = item;
TargetSide = targetSide; TargetSide = targetSide;
TargetPosition = targetPosition; 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);
/// <summary> /// <summary>
/// The item that is used. /// The item that is used.
/// </summary> /// </summary>
public IItem Item { get; } public IItem Item { get; }
/// <inheritdoc /> /// <inheritdoc />
public byte? TargetSide { get; } public IPokemon? GetTargetPokemon(IBattle battle)
{
if (TargetPokemon != null)
return TargetPokemon;
/// <inheritdoc /> if (!TargetSide.HasValue || !TargetPosition.HasValue)
public byte? TargetPosition { get; } return null;
var side = battle.Sides[TargetSide.Value];
return side.Pokemon[TargetPosition.Value];
}
/// <summary>
/// The target side of the item, if any.
/// </summary>
private byte? TargetSide { get; }
/// <summary>
/// The target position of the item, if any.
/// </summary>
private byte? TargetPosition { get; }
/// <summary>
/// The target Pokémon of the item, if any. This is used for party members.
/// </summary>
private IPokemon? TargetPokemon { get; }
/// <inheritdoc /> /// <inheritdoc />
public override int ScriptCount => User.ScriptCount; public override int ScriptCount => User.ScriptCount;

View File

@@ -0,0 +1,67 @@
namespace PkmnLib.Dynamic.Models;
/// <summary>
/// Types of targets an item can have.
/// </summary>
[Flags]
public enum ItemTargetType
{
/// <summary>
/// The item does not require a target.
/// </summary>
None = 0,
/// <summary>
/// The item targets Pokémon that are part of the user's own team.
/// </summary>
OwnPokemon = 1,
/// <summary>
/// The item targets allied Pokémon.
/// </summary>
AllyPokemon = 2,
/// <summary>
/// The item targets opposing Pokémon.
/// </summary>
FoePokemon = 4,
}
/// <summary>
/// Helper methods for ItemTargetType.
/// </summary>
public static class ItemTargetTypeHelpers
{
/// <summary>
/// Determines if the given target is valid based on the ItemTargetType.
/// </summary>
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;
}
}

View File

@@ -36,7 +36,7 @@ public abstract class ItemScript : IDeepCloneable
/// <summary> /// <summary>
/// Returns whether the item requires a target to be used. /// Returns whether the item requires a target to be used.
/// </summary> /// </summary>
public virtual bool RequiresTarget => false; public virtual ItemTargetType TargetType => ItemTargetType.None;
/// <summary> /// <summary>
/// Returns whether the item is usable on the given target. /// Returns whether the item is usable on the given target.

View File

@@ -29,7 +29,7 @@ public abstract class PokeballScript : ItemScript
public override bool IsItemUsable => true; public override bool IsItemUsable => true;
/// <inheritdoc /> /// <inheritdoc />
public override bool RequiresTarget => true; public override ItemTargetType TargetType => ItemTargetType.FoePokemon;
/// <inheritdoc /> /// <inheritdoc />
public override bool IsTargetValid(IPokemon target) => public override bool IsTargetValid(IPokemon target) =>

View File

@@ -100,8 +100,8 @@ public static class ScriptExecution
/// <summary> /// <summary>
/// Executes a script on an item. /// Executes a script on an item.
/// </summary> /// </summary>
public static void RunItemScript(this IItem item, ScriptResolver scriptResolver, IPokemon? target, public static void RunItemScript(this IItem item, ScriptResolver scriptResolver, IPokemon? target, IPokemon user,
EventHook eventHook) IBattle battle, EventHook eventHook)
{ {
if (!scriptResolver.TryResolveBattleItemScript(item, out var itemScript)) if (!scriptResolver.TryResolveBattleItemScript(item, out var itemScript))
{ {
@@ -111,16 +111,20 @@ public static class ScriptExecution
return; return;
itemScript.OnInitialize(item.BattleEffect!.Parameters); itemScript.OnInitialize(item.BattleEffect!.Parameters);
var requiresTarget = itemScript.RequiresTarget; var targetType = itemScript.TargetType;
if (requiresTarget) if (targetType == ItemTargetType.None)
{
if (target == null)
throw new ArgumentNullException(nameof(target), "Item script requires a target.");
itemScript.OnUseWithTarget(target, eventHook);
}
else
{ {
itemScript.OnUse(eventHook); 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);
}
} }
} }

View File

@@ -2672,6 +2672,7 @@
{ {
"name": "hyper_potion", "name": "hyper_potion",
"itemType": "Medicine", "itemType": "Medicine",
"battleType": "Healing",
"flags": [], "flags": [],
"price": 1500, "price": 1500,
"additionalData": { "additionalData": {
@@ -3549,6 +3550,7 @@
{ {
"name": "max_potion", "name": "max_potion",
"itemType": "Medicine", "itemType": "Medicine",
"battleType": "Healing",
"flags": [], "flags": [],
"price": 2500, "price": 2500,
"additionalData": { "additionalData": {
@@ -3567,6 +3569,7 @@
{ {
"name": "max_revive", "name": "max_revive",
"itemType": "Medicine", "itemType": "Medicine",
"battleType": "Healing",
"flags": [], "flags": [],
"price": 4000, "price": 4000,
"additionalData": { "additionalData": {
@@ -4584,6 +4587,7 @@
{ {
"name": "potion", "name": "potion",
"itemType": "Medicine", "itemType": "Medicine",
"battleType": "Healing",
"flags": [], "flags": [],
"price": 200, "price": 200,
"additionalData": { "additionalData": {
@@ -5230,6 +5234,7 @@
{ {
"name": "revival_herb", "name": "revival_herb",
"itemType": "Medicine", "itemType": "Medicine",
"battleType": "Healing",
"flags": [], "flags": [],
"price": 2800, "price": 2800,
"additionalData": { "additionalData": {
@@ -5239,6 +5244,7 @@
{ {
"name": "revive", "name": "revive",
"itemType": "Medicine", "itemType": "Medicine",
"battleType": "Healing",
"flags": [], "flags": [],
"price": 2000, "price": 2000,
"additionalData": { "additionalData": {
@@ -6185,6 +6191,7 @@
{ {
"name": "super_potion", "name": "super_potion",
"itemType": "Medicine", "itemType": "Medicine",
"battleType": "Healing",
"flags": [], "flags": [],
"price": 700, "price": 700,
"additionalData": { "additionalData": {

View File

@@ -14,7 +14,7 @@ public class EvolutionItem : ItemScript
public override bool IsItemUsable => true; public override bool IsItemUsable => true;
/// <inheritdoc /> /// <inheritdoc />
public override bool RequiresTarget => true; public override ItemTargetType TargetType => ItemTargetType.OwnPokemon;
/// <inheritdoc /> /// <inheritdoc />
public override bool IsTargetValid(IPokemon target) public override bool IsTargetValid(IPokemon target)

View File

@@ -14,7 +14,7 @@ public class HealingItem : ItemScript
public override bool IsItemUsable => true; public override bool IsItemUsable => true;
/// <inheritdoc /> /// <inheritdoc />
public override bool RequiresTarget => true; public override ItemTargetType TargetType => ItemTargetType.OwnPokemon;
/// <inheritdoc /> /// <inheritdoc />
public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters) public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)

View File

@@ -20,6 +20,7 @@ public class BugBite : Script, IScriptOnSecondaryEffect
} }
_ = target.ForceSetHeldItem(null); _ = 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);
} }
} }