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();