Move all battle state from IPokemon to an ephemeral IBattlePokemon wrapper

This commit is contained in:
2026-08-28 15:20:26 +02:00
parent 942be8eaeb
commit 8a2733a0a9
859 changed files with 4408 additions and 5331 deletions

View File

@@ -92,7 +92,7 @@ public interface IBattle : IScriptSource, IDeepCloneable, IDisposable
/// <summary>
/// Get a Pokemon on the battlefield, on a specific side and an index on that side.
/// </summary>
IPokemon? GetPokemon(byte side, byte position);
IBattlePokemon? GetPokemon(byte side, byte position);
/// <summary>
/// Returns whether a slot on the battlefield can still be filled. If no party is responsible
@@ -116,7 +116,7 @@ public interface IBattle : IScriptSource, IDeepCloneable, IDisposable
/// Checks whether a Pokemon has a forced turn choice. If it does, this returns true and the choice
/// is set in the out parameter. If it does not, this returns false and the out parameter is null.
/// </summary>
bool HasForcedTurn(IPokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice);
bool HasForcedTurn(IBattlePokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice);
/// <summary>
/// Checks whether a choice is actually possible.
@@ -202,6 +202,8 @@ public class BattleImpl : ScriptSource, IBattle
Sides = sides;
Random = randomSeed.HasValue ? new BattleRandomImpl(randomSeed.Value) : new BattleRandomImpl();
EventHook = new EventHook();
foreach (var party in parties)
party.Initialize(this);
}
/// <inheritdoc />
@@ -248,12 +250,22 @@ public class BattleImpl : ScriptSource, IBattle
public BattleChoiceQueue? ChoiceQueue { get; private set; }
/// <inheritdoc />
public IPokemon? GetPokemon(byte side, byte position) => Sides[side].Pokemon[position];
public IBattlePokemon? GetPokemon(byte side, byte position) => Sides[side].Pokemon[position];
/// <inheritdoc />
public bool CanSlotBeFilled(byte side, byte position) => Parties.Any(x =>
x.IsResponsibleForIndex(new ResponsibleIndex(side, position)) && x.HasUsablePokemonNotInField());
private readonly List<IPokemon> _capturedPokemon = [];
/// <summary>
/// Attaches battle-wide result data, such as the captured Pokémon, to a result.
/// </summary>
private BattleResult FinalizeResult(BattleResult result) => result with
{
CapturedPokemon = _capturedPokemon.ToList(),
};
/// <inheritdoc />
public void ValidateBattleState()
{
@@ -265,7 +277,7 @@ public class BattleImpl : ScriptSource, IBattle
{
if (side.HasFledBattle)
{
Result = BattleResult.Inconclusive;
Result = FinalizeResult(BattleResult.Inconclusive);
HasEnded = true;
return;
}
@@ -283,13 +295,13 @@ public class BattleImpl : ScriptSource, IBattle
// If every side is defeated, the battle is a draw
if (!survivingSideExists)
{
Result = BattleResult.Inconclusive;
Result = FinalizeResult(BattleResult.Inconclusive);
HasEnded = true;
return;
}
// If only one side is left, that side has won
Result = BattleResult.Conclusive(survivingSide!.Index);
Result = FinalizeResult(BattleResult.Conclusive(survivingSide!.Index));
HasEnded = true;
}
@@ -297,22 +309,15 @@ public class BattleImpl : ScriptSource, IBattle
public void ForceEndBattle()
{
HasEnded = true;
Result = BattleResult.Inconclusive;
Result = FinalizeResult(BattleResult.Inconclusive);
}
/// <inheritdoc />
public bool HasForcedTurn(IPokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice)
public bool HasForcedTurn(IBattlePokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice)
{
var battleData = pokemon.BattleData;
if (battleData == null)
{
choice = null;
return false;
}
ITurnChoice? forcedChoice = null;
pokemon.RunScriptHook<IScriptForceTurnSelection>(script =>
script.ForceTurnSelection(this, battleData.SideIndex, battleData.Position, ref forcedChoice));
script.ForceTurnSelection(this, pokemon.SideIndex, pokemon.Position, ref forcedChoice));
choice = forcedChoice;
return choice != null;
}
@@ -346,7 +351,7 @@ public class BattleImpl : ScriptSource, IBattle
if (!switchChoice.SwitchTo.IsUsable)
return false;
// Can't switch to a Pokémon already on the field
if (switchChoice.SwitchTo.BattleData is { IsOnBattlefield: true })
if (switchChoice.SwitchTo.IsOnBattlefield)
return false;
if (switchChoice.SwitchTo == switchChoice.User)
return false;
@@ -389,10 +394,10 @@ public class BattleImpl : ScriptSource, IBattle
{
if (!CanUse(choice))
return false;
if (choice.User.BattleData?.IsOnBattlefield != true)
if (!choice.User.IsOnBattlefield)
return false;
var side = Sides[choice.User.BattleData!.SideIndex];
side.SetChoice(choice.User.BattleData!.Position, choice);
var side = Sides[choice.User.SideIndex];
side.SetChoice(choice.User.Position, choice);
CheckChoicesSetAndRun();
return true;
}
@@ -555,8 +560,8 @@ public class BattleImpl : ScriptSource, IBattle
if (attemptCapture.IsCaught)
{
target.MarkAsCaught();
var side = Sides[target.BattleData!.SideIndex];
side.ForceClearPokemonFromField(target.BattleData.Position);
_capturedPokemon.Add(target.UnderlyingPokemon);
target.BattleSide.ForceClearPokemonFromField(target.Position);
}
EventHook.Invoke(new CaptureAttemptEvent(target, attemptCapture, item));
@@ -592,9 +597,9 @@ public class BattleImpl : ScriptSource, IBattle
{
foreach (var party in Parties)
{
foreach (var pokemon in party.Party.WhereNotNull())
foreach (var pokemon in party.BattlePokemon.WhereNotNull())
{
pokemon.ClearBattleData();
pokemon.OnBattleEnd();
}
}
_weatherScript.Clear();

View File

@@ -87,7 +87,7 @@ public class BattleChoiceQueue : IDeepCloneable
/// <returns>
/// Returns true if the Pokémon was found and moved, false otherwise.
/// </returns>
public bool MovePokemonChoiceNext(IPokemon pokemon)
public bool MovePokemonChoiceNext(IBattlePokemon pokemon)
{
var index = Array.FindIndex(_choices, _currentIndex, choice => choice?.User == pokemon);
if (index == -1)
@@ -110,7 +110,7 @@ public class BattleChoiceQueue : IDeepCloneable
/// <returns>
/// Returns true if the Pokémon was found and moved, false otherwise.
/// </returns>
public bool MovePokemonChoiceLast(IPokemon pokemon)
public bool MovePokemonChoiceLast(IBattlePokemon pokemon)
{
var index = Array.FindIndex(_choices, _currentIndex, choice => choice?.User == pokemon);
if (index == -1)

View File

@@ -4,15 +4,35 @@ namespace PkmnLib.Dynamic.Models;
/// <summary>
/// A battle party is a wrapper around a Pokemon party that provides additional functionality for battles.
/// It indicates for which side and position the party is responsible.
/// It indicates for which side and position the party is responsible, and holds the battle-scoped
/// <see cref="IBattlePokemon"/> wrappers for the party's Pokémon.
/// </summary>
public interface IBattleParty : IDeepCloneable
{
/// <summary>
/// The backing Pokemon party.
/// The backing Pokemon party. Battle code should generally use <see cref="BattlePokemon"/> instead, as that
/// view contains the battle-scoped wrappers.
/// </summary>
IPokemonParty Party { get; }
/// <summary>
/// The battle-scoped view of the party. Index-aligned with <see cref="Party"/>. Only available after
/// <see cref="Initialize"/> has been called by the battle.
/// </summary>
IReadOnlyList<IBattlePokemon?> BattlePokemon { get; }
/// <summary>
/// Gets the battle-scoped wrapper for a Pokémon in this party. Returns null if the Pokémon is not in this
/// party. Accepts either the underlying Pokémon or the wrapper itself.
/// </summary>
IBattlePokemon? GetBattlePokemon(IPokemon pokemon);
/// <summary>
/// Initializes the battle-scoped wrappers for this party. This is called by the battle when it is created,
/// and should not be called by user code.
/// </summary>
void Initialize(IBattle battle);
/// <summary>
/// Whether the party is responsible for the specified side and position.
/// </summary>
@@ -26,7 +46,7 @@ public interface IBattleParty : IDeepCloneable
/// <summary>
/// Gets all usable Pokemon that are not currently in the field.
/// </summary>
IEnumerable<IPokemon> GetUsablePokemonNotInField();
IEnumerable<IBattlePokemon> GetUsablePokemonNotInField();
}
/// <summary>
@@ -39,6 +59,8 @@ public record struct ResponsibleIndex(byte Side, byte Position);
public class BattlePartyImpl : IBattleParty
{
private readonly ResponsibleIndex[] _responsibleIndices;
private IBattlePokemon?[] _battlePokemon = [];
private IBattle? _battle;
/// <inheritdoc cref="BattlePartyImpl"/>
public BattlePartyImpl(IPokemonParty party, ResponsibleIndex[] responsibleIndices)
@@ -50,14 +72,53 @@ public class BattlePartyImpl : IBattleParty
/// <inheritdoc />
public IPokemonParty Party { get; }
/// <inheritdoc />
public IReadOnlyList<IBattlePokemon?> BattlePokemon => _battlePokemon;
/// <inheritdoc />
public IBattlePokemon? GetBattlePokemon(IPokemon pokemon) =>
_battlePokemon.FirstOrDefault(x => x != null && (x == pokemon || x.UnderlyingPokemon == pokemon));
/// <inheritdoc />
public void Initialize(IBattle battle)
{
if (_battle != null)
throw new InvalidOperationException("This battle party has already been initialized for a battle.");
if (_responsibleIndices.Length == 0)
throw new InvalidOperationException("A battle party must be responsible for at least one position.");
var sideIndex = _responsibleIndices[0].Side;
if (_responsibleIndices.Any(x => x.Side != sideIndex))
throw new InvalidOperationException("A battle party can only be responsible for a single side.");
_battle = battle;
_battlePokemon = new IBattlePokemon?[Party.Count];
for (var i = 0; i < Party.Count; i++)
{
var pokemon = Party[i];
if (pokemon != null)
_battlePokemon[i] = new BattlePokemonImpl(pokemon, battle, sideIndex);
}
Party.OnSwapInto += (_, args) =>
{
var (pokemon, index) = args;
_battlePokemon[index] = pokemon == null ? null : new BattlePokemonImpl(pokemon, battle, sideIndex);
};
Party.OnSwap += (_, args) =>
{
var (index1, index2) = args;
(_battlePokemon[index1], _battlePokemon[index2]) = (_battlePokemon[index2], _battlePokemon[index1]);
};
}
/// <inheritdoc />
public bool IsResponsibleForIndex(ResponsibleIndex index) => _responsibleIndices.Contains(index);
/// <inheritdoc />
public bool HasUsablePokemonNotInField() =>
Party.WhereNotNull().Any(x => x.IsUsable && x.BattleData?.IsOnBattlefield != true);
_battlePokemon.WhereNotNull().Any(x => x.IsUsable && !x.IsOnBattlefield);
/// <inheritdoc />
public IEnumerable<IPokemon> GetUsablePokemonNotInField() =>
Party.WhereNotNull().Where(x => x.IsUsable && x.BattleData?.IsOnBattlefield != true);
public IEnumerable<IBattlePokemon> GetUsablePokemonNotInField() =>
_battlePokemon.WhereNotNull().Where(x => x.IsUsable && !x.IsOnBattlefield);
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ public interface IBattleRandom : IRandom, IDeepCloneable
/// rolls whether it triggers. As a side effect this run scripts to allow modifying this random
/// chance.
/// </summary>
bool EffectChance(float chance, IExecutingMove executingMove, IPokemon target, byte hitNumber);
bool EffectChance(float chance, IExecutingMove executingMove, IBattlePokemon target, byte hitNumber);
}
/// <inheritdoc cref="IBattleRandom"/>
@@ -36,7 +36,7 @@ public class BattleRandomImpl : RandomImpl, IBattleRandom
}
/// <inheritdoc />
public bool EffectChance(float chance, IExecutingMove executingMove, IPokemon target, byte hitNumber)
public bool EffectChance(float chance, IExecutingMove executingMove, IBattlePokemon target, byte hitNumber)
{
executingMove.RunScriptHook<IScriptChangeEffectChance>(script =>
script.ChangeEffectChance(executingMove, target, hitNumber, ref chance));

View File

@@ -30,4 +30,11 @@ public record struct BattleResult
/// The side that won the battle. If null, no side has won.
/// </summary>
public byte? WinningSide { get; }
/// <summary>
/// The Pokémon that were captured during the battle. These are the underlying Pokémon, so they remain
/// valid after the battle has been disposed. This is set for both conclusive and inconclusive results, as
/// captures complete the moment they happen.
/// </summary>
public IReadOnlyList<IPokemon> CapturedPokemon { get; init; } = [];
}

View File

@@ -24,7 +24,7 @@ public interface IBattleSide : IScriptSource, IDeepCloneable
/// <summary>
/// A list of Pokémon currently on the battlefield.
/// </summary>
IReadOnlyList<IPokemon?> Pokemon { get; }
IReadOnlyList<IBattlePokemon?> Pokemon { get; }
/// <summary>
/// The currently set choices for all Pokémon on the battlefield. Cleared when the turn starts.
@@ -84,7 +84,14 @@ public interface IBattleSide : IScriptSource, IDeepCloneable
/// Switches out a spot on the field for a different Pokémon. If null is passed, the spot is
/// cleared. Returns the Pokémon that was previously in the spot.
/// </summary>
IPokemon? SwapPokemon(byte position, IPokemon? pokemon);
IBattlePokemon? SwapPokemon(byte position, IBattlePokemon? pokemon);
/// <summary>
/// Sends out a Pokémon by its persistent representation. This resolves the battle-scoped wrapper for
/// the Pokémon from the parties in the battle, and swaps it into the given position. This is the
/// convenient entry point for hosts, which generally hold the persistent party Pokémon.
/// </summary>
IBattlePokemon? SendOut(byte position, IPokemon pokemon);
/// <summary>
/// Swaps two Pokémon on the side.
@@ -94,7 +101,7 @@ public interface IBattleSide : IScriptSource, IDeepCloneable
/// <summary>
/// Checks whether a Pokemon is on the field in this side.
/// </summary>
bool IsPokemonOnSide(IPokemon pokemon);
bool IsPokemonOnSide(IBattlePokemon pokemon);
/// <summary>
/// Marks a slot as unfillable. This happens when no parties are able to fill the slot anymore.
@@ -166,7 +173,7 @@ public class BattleSideImpl : ScriptSource, IBattleSide
{
Index = index;
NumberOfPositions = numberOfPositions;
_pokemon = new IPokemon?[numberOfPositions];
_pokemon = new IBattlePokemon?[numberOfPositions];
_setChoices = new ITurnChoice?[numberOfPositions];
_fillablePositions = new bool[numberOfPositions];
for (byte i = 0; i < numberOfPositions; i++)
@@ -183,10 +190,10 @@ public class BattleSideImpl : ScriptSource, IBattleSide
/// <inheritdoc />
public byte NumberOfPositions { get; }
private readonly IPokemon?[] _pokemon;
private readonly IBattlePokemon?[] _pokemon;
/// <inheritdoc />
public IReadOnlyList<IPokemon?> Pokemon => _pokemon;
public IReadOnlyList<IBattlePokemon?> Pokemon => _pokemon;
private readonly ITurnChoice?[] _setChoices;
@@ -249,28 +256,31 @@ public class BattleSideImpl : ScriptSource, IBattleSide
if (pokemon is not null)
{
pokemon.RunScriptHook<IScriptOnRemove>(script => script.OnRemove());
pokemon.SetOnBattlefield(false);
pokemon.OnSwitchedOut();
}
_pokemon[index] = null;
}
/// <inheritdoc />
public IPokemon? SwapPokemon(byte position, IPokemon? pokemon)
public IBattlePokemon? SwapPokemon(byte position, IBattlePokemon? pokemon)
{
var oldPokemon = _pokemon[position];
if (oldPokemon is not null)
{
oldPokemon.RunScriptHook<IScriptOnSwitchOut>(script => script.OnSwitchOut(oldPokemon, position));
oldPokemon.RunScriptHook<IScriptOnRemove>(script => script.OnRemove());
oldPokemon.SetOnBattlefield(false);
oldPokemon.OnSwitchedOut();
}
_pokemon[position] = pokemon;
if (pokemon is not null)
{
pokemon.SetBattleData(Battle, Index);
pokemon.SetOnBattlefield(true);
pokemon.SetBattleSidePosition(position);
if (pokemon.SideIndex != Index)
{
throw new InvalidOperationException(
"A battle Pokémon can only be sent out on the side its party is responsible for.");
}
pokemon.OnSwitchedIn(position);
Battle.EventHook.Invoke(new SwitchEvent(Index, position, pokemon));
pokemon.RunScriptHook<IScriptOnSwitchIn>(script => script.OnSwitchIn(pokemon, position));
@@ -300,6 +310,14 @@ public class BattleSideImpl : ScriptSource, IBattleSide
return oldPokemon;
}
/// <inheritdoc />
public IBattlePokemon? SendOut(byte position, IPokemon pokemon)
{
var wrapper = Battle.Parties.Select(p => p.GetBattlePokemon(pokemon)).FirstOrDefault(x => x != null) ??
throw new InvalidOperationException("The Pokémon does not belong to any party in this battle.");
return SwapPokemon(position, wrapper);
}
/// <inheritdoc />
public void SwapPokemon(byte position1, byte position2)
{
@@ -307,7 +325,7 @@ public class BattleSideImpl : ScriptSource, IBattleSide
}
/// <inheritdoc />
public bool IsPokemonOnSide(IPokemon pokemon) => _pokemon.Contains(pokemon);
public bool IsPokemonOnSide(IBattlePokemon pokemon) => _pokemon.Contains(pokemon);
/// <inheritdoc />
public void MarkPositionAsUnfillable(byte position) => _fillablePositions[position] = false;

View File

@@ -13,7 +13,7 @@ public interface IFleeChoice : ITurnChoice
public class FleeTurnChoice : TurnChoice, IFleeChoice
{
/// <inheritdoc />
public FleeTurnChoice(IPokemon user) : base(user)
public FleeTurnChoice(IBattlePokemon user) : base(user)
{
}

View File

@@ -16,15 +16,15 @@ public interface IItemChoice : ITurnChoice
/// <summary>
/// The target Pokémon of the item, if any.
/// </summary>
IPokemon? GetTargetPokemon(IBattle battle);
IBattlePokemon? GetTargetPokemon(IBattle battle);
}
/// <inheritdoc cref="IItemChoice"/>
public class ItemChoice : TurnChoice, IItemChoice
{
/// <inheritdoc cref="ItemChoice"/>
private ItemChoice(IPokemon user, IItem item, byte? targetSide, byte? targetPosition, IPokemon? targetPokemon) :
base(user)
private ItemChoice(IBattlePokemon user, IItem item, byte? targetSide, byte? targetPosition,
IBattlePokemon? targetPokemon) : base(user)
{
Item = item;
TargetSide = targetSide;
@@ -32,13 +32,13 @@ public class ItemChoice : TurnChoice, IItemChoice
TargetPokemon = targetPokemon;
}
public static ItemChoice CreateWithoutTarget(IPokemon user, IItem item) =>
public static ItemChoice CreateWithoutTarget(IBattlePokemon user, IItem item) =>
new(user, item, null, null, null);
public static ItemChoice CreateForOpponent(IPokemon user, IItem item, byte targetSide, byte targetPosition) =>
public static ItemChoice CreateForOpponent(IBattlePokemon user, IItem item, byte targetSide, byte targetPosition) =>
new(user, item, targetSide, targetPosition, null);
public static ItemChoice CreateForPartyMember(IPokemon user, IItem item, IPokemon targetPokemon) =>
public static ItemChoice CreateForPartyMember(IBattlePokemon user, IItem item, IBattlePokemon targetPokemon) =>
new(user, item, null, null, targetPokemon);
/// <summary>
@@ -47,7 +47,7 @@ public class ItemChoice : TurnChoice, IItemChoice
public IItem Item { get; }
/// <inheritdoc />
public IPokemon? GetTargetPokemon(IBattle battle)
public IBattlePokemon? GetTargetPokemon(IBattle battle)
{
if (TargetPokemon != null)
return TargetPokemon;
@@ -71,7 +71,7 @@ public class ItemChoice : TurnChoice, IItemChoice
/// <summary>
/// The target Pokémon of the item, if any. This is used for party members.
/// </summary>
private IPokemon? TargetPokemon { get; }
private IBattlePokemon? TargetPokemon { get; }
/// <inheritdoc />
public override int ScriptCount => User.ScriptCount;

View File

@@ -51,7 +51,7 @@ public interface IMoveChoice : ITurnChoice
public class MoveChoice : TurnChoice, IMoveChoice
{
/// <inheritdoc cref="MoveChoice"/>
public MoveChoice(IPokemon user, ILearnedMove usedMove, byte targetSide, byte targetPosition) : base(user)
public MoveChoice(IBattlePokemon user, ILearnedMove usedMove, byte targetSide, byte targetPosition) : base(user)
{
ChosenMove = usedMove;
TargetSide = targetSide;

View File

@@ -13,7 +13,7 @@ public interface IPassChoice : ITurnChoice
public class PassChoice : TurnChoice, IPassChoice
{
/// <inheritdoc cref="PassChoice"/>
public PassChoice(IPokemon user) : base(user)
public PassChoice(IBattlePokemon user) : base(user)
{
}

View File

@@ -10,20 +10,20 @@ public interface ISwitchChoice : ITurnChoice
/// <summary>
/// The Pokémon to switch to.
/// </summary>
IPokemon SwitchTo { get; }
IBattlePokemon SwitchTo { get; }
}
/// <inheritdoc cref="ISwitchChoice"/>
public class SwitchChoice : TurnChoice, ISwitchChoice
{
/// <inheritdoc cref="SwitchChoice"/>
public SwitchChoice(IPokemon user, IPokemon switchTo) : base(user)
public SwitchChoice(IBattlePokemon user, IBattlePokemon switchTo) : base(user)
{
SwitchTo = switchTo;
}
/// <inheritdoc />
public IPokemon SwitchTo { get; }
public IBattlePokemon SwitchTo { get; }
/// <inheritdoc />
public override int ScriptCount => User.ScriptCount;

View File

@@ -11,7 +11,7 @@ public interface ITurnChoice : IScriptSource, IDeepCloneable
/// <summary>
/// The user of the turn choice
/// </summary>
IPokemon User { get; }
IBattlePokemon User { get; }
/// <summary>
/// The speed of the user at the beginning of the turn.
@@ -34,7 +34,7 @@ public interface ITurnChoice : IScriptSource, IDeepCloneable
/// Fails the choice. This will prevent it from executing and run a specific fail handling during
/// execution. Note that this can not be undone.
/// </summary>
public void Fail();
void Fail();
}
/// <summary>
@@ -43,7 +43,7 @@ public interface ITurnChoice : IScriptSource, IDeepCloneable
public abstract class TurnChoice : ScriptSource, ITurnChoice
{
/// <inheritdoc cref="TurnChoice"/>
protected TurnChoice(IPokemon user)
protected TurnChoice(IBattlePokemon user)
{
User = user;
}
@@ -51,7 +51,7 @@ public abstract class TurnChoice : ScriptSource, ITurnChoice
/// <summary>
/// The Pokemon for which the choice is made.
/// </summary>
public IPokemon User { get; }
public IBattlePokemon User { get; }
/// <summary>
/// The speed of the user at the beginning of the turn.

View File

@@ -129,7 +129,7 @@ public interface IExecutingMove : IScriptSource
/// <summary>
/// The user of the move.
/// </summary>
IPokemon User { get; }
IBattlePokemon User { get; }
/// <summary>
/// The move the user has actually chosen to do.
@@ -151,17 +151,17 @@ public interface IExecutingMove : IScriptSource
/// <summary>
/// Gets a hit data for a target, with a specific index.
/// </summary>
IHitData GetHitData(IPokemon target, byte hit);
IHitData GetHitData(IBattlePokemon target, byte hit);
/// <summary>
/// Checks whether a Pokémon is a target for this move.
/// </summary>
bool IsPokemonTarget(IPokemon target);
bool IsPokemonTarget(IBattlePokemon target);
/// <summary>
/// Gets the index of the hits in this move where the hits for a specific target start.
/// </summary>
int GetTargetIndex(IPokemon target);
int GetTargetIndex(IBattlePokemon target);
/// <summary>
/// Gets a hit based on its raw index.
@@ -171,7 +171,7 @@ public interface IExecutingMove : IScriptSource
/// <summary>
/// Gets the targets of this move.
/// </summary>
IReadOnlyList<IPokemon?> Targets { get; }
IReadOnlyList<IBattlePokemon?> Targets { get; }
/// <summary>
/// The underlying move choice.
@@ -192,12 +192,12 @@ public interface IExecutingMove : IScriptSource
/// <inheritdoc cref="IExecutingMove"/>
public class ExecutingMoveImpl : ScriptSource, IExecutingMove
{
private readonly IReadOnlyList<IPokemon?> _targets;
private readonly IReadOnlyList<IBattlePokemon?> _targets;
private readonly IHitData[] _hits;
private readonly IBattle _battle;
/// <inheritdoc cref="ExecutingMoveImpl"/>
public ExecutingMoveImpl(IReadOnlyList<IPokemon?> targets, byte numberOfHits, ILearnedMove chosenMove,
public ExecutingMoveImpl(IReadOnlyList<IBattlePokemon?> targets, byte numberOfHits, ILearnedMove chosenMove,
IMoveData useMove, IMoveChoice moveChoice, IBattle battle)
{
_targets = targets;
@@ -222,7 +222,7 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove
public byte NumberOfHits { get; }
/// <inheritdoc />
public IPokemon User => MoveChoice.User;
public IBattlePokemon User => MoveChoice.User;
/// <inheritdoc />
public ILearnedMove ChosenMove { get; }
@@ -239,7 +239,7 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove
public IScriptSet Volatile => MoveChoice.Volatile;
/// <inheritdoc />
public IHitData GetHitData(IPokemon target, byte hit)
public IHitData GetHitData(IBattlePokemon target, byte hit)
{
var targetIndex = _targets.IndexOf(target);
if (targetIndex == -1)
@@ -252,10 +252,10 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove
}
/// <inheritdoc />
public bool IsPokemonTarget(IPokemon target) => _targets.Contains(target);
public bool IsPokemonTarget(IBattlePokemon target) => _targets.Contains(target);
/// <inheritdoc />
public int GetTargetIndex(IPokemon target)
public int GetTargetIndex(IBattlePokemon target)
{
var targetIndex = _targets.IndexOf(target);
if (targetIndex == -1)
@@ -273,7 +273,7 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove
}
/// <inheritdoc />
public IReadOnlyList<IPokemon?> Targets => _targets.ToList();
public IReadOnlyList<IBattlePokemon?> Targets => _targets.ToList();
/// <inheritdoc />
public IMoveChoice MoveChoice { get; }

View File

@@ -35,29 +35,30 @@ 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)
public static bool IsValidTarget(this ItemTargetType targetType, IBattle battle, IBattlePokemon user,
IBattlePokemon 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));
var userParty = battle.Parties.FirstOrDefault(x => x.BattlePokemon.Contains(user));
var targetParty = battle.Parties.FirstOrDefault(x => x.BattlePokemon.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)
if (user.BattleSide == target.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));
var userParty = battle.Parties.FirstOrDefault(x => x.BattlePokemon.Contains(user));
var targetParty = battle.Parties.FirstOrDefault(x => x.BattlePokemon.Contains(target));
if (userParty is not null && targetParty is not null && userParty != targetParty)
return true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@ public record SerializedPokemon
Nature = pokemon.Nature.Name;
Nickname = pokemon.Nickname;
Ability = pokemon.Form.GetAbility(pokemon.AbilityIndex);
Moves = pokemon.BaseMoves.Select(move =>
Moves = pokemon.Moves.Select(move =>
{
if (move == null)
return null;