PkmnLib.NET/PkmnLib.Dynamic/Models/BattleSide.cs

291 lines
8.3 KiB
C#
Raw Normal View History

using PkmnLib.Dynamic.Events;
2024-07-27 14:26:45 +00:00
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static.Utils;
2024-07-27 14:26:45 +00:00
namespace PkmnLib.Dynamic.Models;
/// <summary>
/// A side in a battle.
/// </summary>
public interface IBattleSide : IScriptSource
{
/// <summary>
/// The index of the side on the battle.
/// </summary>
byte Index { get; }
/// <summary>
/// The number of Pokémon that can be on the side.
/// </summary>
byte NumberOfPositions { get; }
/// <summary>
/// A list of Pokémon currently on the battlefield.
/// </summary>
IReadOnlyList<IPokemon?> Pokemon { get; }
/// <summary>
/// The currently set choices for all Pokémon on the battlefield. Cleared when the turn starts.
/// </summary>
IReadOnlyList<ITurnChoice?> SetChoices { get; }
/// <summary>
/// Whether every Pokémon on this side has its choices
/// </summary>
bool AllChoicesSet { get; }
/// <summary>
/// The slots on the side that can still be filled. Once all slots are set to false, this side
/// has lost the battle.
/// </summary>
IReadOnlyList<bool> FillablePositions { get; }
/// <summary>
/// A reference to the battle this side is in.
/// </summary>
IBattle Battle { get; }
/// <summary>
/// Whether this side has fled.
/// </summary>
bool HasFledBattle { get; }
/// <summary>
/// The volatile scripts that are attached to the side.
/// </summary>
IScriptSet VolatileScripts { get; }
/// <summary>
/// Returns true if there are slots that need to be filled with a new pokemon, that have parties
/// responsible for them. Returns false if all slots are filled with usable pokemon, or slots are
/// empty, but can't be filled by any party anymore.
/// </summary>
bool AllPositionsFilled();
2024-07-27 14:26:45 +00:00
/// <summary>
/// Sets a choice for a Pokémon on this side.
/// </summary>
void SetChoice(byte position, ITurnChoice choice);
/// <summary>
/// Resets all choices on this side.
/// </summary>
void ResetChoices();
2024-07-27 14:26:45 +00:00
/// <summary>
/// Forcibly removes a Pokémon from the field.
/// </summary>
/// <param name="index"></param>
void ForceClearPokemonFromField(byte index);
2024-07-27 14:26:45 +00:00
/// <summary>
/// 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);
/// <summary>
/// Swaps two Pokémon on the side.
/// </summary>
void SwapPokemon(byte position1, byte position2);
/// <summary>
/// Checks whether a Pokemon is on the field in this side.
/// </summary>
bool IsPokemonOnSide(IPokemon pokemon);
/// <summary>
/// Marks a slot as unfillable. This happens when no parties are able to fill the slot anymore.
/// If this happens, the slot can not be used again.
/// </summary>
void MarkPositionAsUnfillable(byte position);
/// <summary>
/// Checks whether a slot is fillable. If it is not, the slot can not be used anymore.
/// </summary>
bool IsPositionFillable(byte position);
/// <summary>
/// Checks whether the side has been defeated.
/// </summary>
bool IsDefeated();
/// <summary>
/// Mark the side as fled.
/// </summary>
void MarkAsFled();
/// <summary>
/// Gets a random Pokémon on the given side.
/// </summary>
byte GetRandomPosition();
}
/// <inheritdoc cref="IBattleSide"/>
public class BattleSideImpl : ScriptSource, IBattleSide
{
/// <inheritdoc cref="BattleSideImpl"/>
public BattleSideImpl(byte index, byte numberOfPositions, IBattle battle)
{
Index = index;
NumberOfPositions = numberOfPositions;
_pokemon = new IPokemon?[numberOfPositions];
_setChoices = new ITurnChoice?[numberOfPositions];
_fillablePositions = new bool[numberOfPositions];
2024-11-01 12:25:38 +00:00
for (byte i = 0; i < numberOfPositions; i++)
{
_fillablePositions[i] = true;
}
Battle = battle;
VolatileScripts = new ScriptSet();
}
/// <inheritdoc />
public byte Index { get; }
/// <inheritdoc />
public byte NumberOfPositions { get; }
private readonly IPokemon?[] _pokemon;
/// <inheritdoc />
public IReadOnlyList<IPokemon?> Pokemon => _pokemon;
private readonly ITurnChoice?[] _setChoices;
/// <inheritdoc />
public IReadOnlyList<ITurnChoice?> SetChoices => _setChoices;
/// <inheritdoc />
public bool AllChoicesSet => _setChoices.All(choice => choice is not null);
private readonly bool[] _fillablePositions;
/// <inheritdoc />
public IReadOnlyList<bool> FillablePositions => _fillablePositions;
/// <inheritdoc />
public IBattle Battle { get; }
/// <inheritdoc />
public bool HasFledBattle { get; private set; }
/// <inheritdoc />
public IScriptSet VolatileScripts { get; }
/// <inheritdoc />
public bool AllPositionsFilled()
{
for (byte i = 0; i < NumberOfPositions; i++)
{
var pokemon = Pokemon[i];
var isPokemonViable = pokemon is not null && pokemon.IsUsable;
// If the Pokémon is not valid, but the slot can be filled, return false.
if (!isPokemonViable && Battle.CanSlotBeFilled(Index, i))
{
return false;
}
}
return true;
}
/// <inheritdoc />
public void SetChoice(byte position, ITurnChoice choice)
{
_setChoices[position] = choice;
}
/// <inheritdoc />
public void ResetChoices()
{
for (byte i = 0; i < NumberOfPositions; i++)
{
_setChoices[i] = null;
}
}
/// <param name="index"></param>
/// <inheritdoc />
public void ForceClearPokemonFromField(byte index)
{
_pokemon[index] = null;
}
/// <inheritdoc />
public IPokemon? SwapPokemon(byte position, IPokemon? pokemon)
{
var oldPokemon = _pokemon[position];
if (oldPokemon is not null)
{
oldPokemon.RunScriptHook(script => script.OnRemove());
oldPokemon.SetOnBattlefield(false);
}
_pokemon[position] = pokemon;
if (pokemon is not null)
{
pokemon.SetBattleData(Battle, Index);
pokemon.SetOnBattlefield(true);
pokemon.SetBattleSidePosition(position);
foreach (var side in Battle.Sides)
{
if (side == this)
continue;
foreach (var opponent in side.Pokemon.WhereNotNull())
{
opponent.MarkOpponentAsSeen(pokemon);
pokemon.MarkOpponentAsSeen(opponent);
}
}
Battle.EventHook.Invoke(new SwitchEvent(Index, position, pokemon));
pokemon.RunScriptHook(script => script.OnSwitchIn(pokemon));
}
else
{
Battle.EventHook.Invoke(new SwitchEvent(Index, position, null));
}
return oldPokemon;
}
/// <inheritdoc />
public void SwapPokemon(byte position1, byte position2)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public bool IsPokemonOnSide(IPokemon pokemon) => _pokemon.Contains(pokemon);
/// <inheritdoc />
public void MarkPositionAsUnfillable(byte position) => _fillablePositions[position] = false;
/// <inheritdoc />
public bool IsPositionFillable(byte position) => _fillablePositions[position];
/// <inheritdoc />
public bool IsDefeated()
{
return _fillablePositions.All(fillable => !fillable);
}
/// <inheritdoc />
public void MarkAsFled() => HasFledBattle = true;
/// <inheritdoc />
public byte GetRandomPosition() => (byte)Battle.Random.GetInt(0, NumberOfPositions);
/// <inheritdoc />
public override int ScriptCount => 1 + Battle.ScriptCount;
/// <inheritdoc />
public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts)
{
scripts.Add(VolatileScripts);
}
/// <inheritdoc />
public override void CollectScripts(List<IEnumerable<ScriptContainer>> scripts)
{
scripts.Add(VolatileScripts);
Battle.CollectScripts(scripts);
}
2024-07-27 14:26:45 +00:00
}