Begin work on outlining dynamic side

This commit is contained in:
2024-07-27 16:26:45 +02:00
parent 1b501dee7e
commit a251913ebd
44 changed files with 2150 additions and 19 deletions

View File

@@ -0,0 +1,114 @@
using PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;
namespace PkmnLib.Dynamic.Models;
/// <summary>
/// A battle is a representation of a battle in the Pokemon games. It contains all the information needed
/// to simulate a battle, and can be used to simulate a battle between two parties.
/// </summary>
public interface IBattle : IScriptSource
{
/// <summary>
/// The library the battle uses for handling.
/// </summary>
IDynamicLibrary Library { get; }
/// <summary>
/// A list of all different parties in the battle.
/// </summary>
IReadOnlyList<IBattleParty> Parties { get; }
/// <summary>
/// Whether or not Pokemon can flee from the battle.
/// </summary>
bool CanFlee { get; }
/// <summary>
/// The number of sides in the battle. Typically 2.
/// </summary>
byte NumberOfSides { get; }
/// <summary>
/// The number of Pokemon that can be on each side.
/// </summary>
byte PositionsPerSide { get; }
/// <summary>
/// A list of all sides in the battle.
/// </summary>
IReadOnlyList<IBattleSide> Sides { get; }
/// <summary>
/// The RNG used for the battle.
/// </summary>
IBattleRandom Random { get; }
/// <summary>
/// Whether the battle has ended.
/// </summary>
bool HasEnded { get; }
/// <summary>
/// The result of the battle. If the battle has not ended, this is null.
/// </summary>
BattleResult? Result { get; }
/// <summary>
/// The handler to send all events to.
/// </summary>
EventHook EventHook { get; }
/// <summary>
/// The index of the current turn. Initially 0, until the first turn starts when all choices are made.
/// </summary>
uint CurrentTurnNumber { get; }
/// <summary>
/// A queue of the yet to be executed choices in a turn.
/// </summary>
BattleChoiceQueue ChoiceQueue { get; }
/// <summary>
/// Get a Pokemon on the battlefield, on a specific side and an index on that side.
/// </summary>
IPokemon GetPokemon(byte side, byte position);
/// <summary>
/// Returns whether a slot on the battlefield can still be filled. If no party is responsible
/// for that slot, or a party is responsible, but has no remaining Pokemon to throw out anymore,
/// this returns false.
/// </summary>
bool CanSlotBeFilled(byte side, byte position);
/// <summary>
/// Validates whether the battle is still in a non-ended state. If the battle has ended, this
/// properly sets who has won etc.
/// </summary>
void ValidateBattleState();
/// <summary>
/// Checks whether a choice is actually possible.
/// </summary>
void CanUse(ITurnChoice choice);
/// <summary>
/// Try and set the choice for the battle. If the choice is not valid, this returns false.
/// </summary>
bool TrySetChoice(ITurnChoice choice);
/// <summary>
/// Sets the current weather for the battle. If null is passed, this clears the weather.
/// </summary>
void SetWeather(string? weatherName);
/// <summary>
/// Gets the current weather of the battle. If no weather is present, this returns null.
/// </summary>
string? WeatherName { get; }
}

View File

@@ -0,0 +1,13 @@
namespace PkmnLib.Dynamic.Models;
/// <summary>
/// The ChoiceQueue is used to run choices one by one.
/// </summary>
/// <remarks>
/// It functions internally by holding a list of choices, and one by one setting items that have been returned to null,
/// It holds several helper functions to change the turn order while doing the execution. This is needed, as several
/// moves in Pokémon actively mess with this order.
/// </remarks>
public class BattleChoiceQueue
{
}

View File

@@ -0,0 +1,8 @@
namespace PkmnLib.Dynamic.Models;
public interface IBattleParty
{
IPokemonParty Party { get; }
bool IsResponsibleForIndex(byte side, byte position);
bool HasPokemonNotInField();
}

View File

@@ -0,0 +1,13 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.Models;
public interface IBattleRandom : IRandom
{
/// <summary>
/// Gets whether or not a move triggers its secondary effect. This takes its chance, and
/// 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);
}

View File

@@ -0,0 +1,23 @@
namespace PkmnLib.Dynamic.Models;
public record struct BattleResult
{
private BattleResult(bool conclusiveResult, byte? winningSide)
{
ConclusiveResult = conclusiveResult;
WinningSide = winningSide;
}
public static BattleResult Inconclusive => new(false, null);
public static BattleResult Conclusive(byte winningSide) => new(true, winningSide);
/// <summary>
/// Whether the battle has a conclusive result. If false, no side has won.
/// </summary>
public bool ConclusiveResult { get; }
/// <summary>
/// The side that won the battle. If null, no side has won.
/// </summary>
public byte? WinningSide { get; }
}

View File

@@ -0,0 +1,120 @@
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;
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>
void AllPositionsFilled();
/// <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();
/// <summary>
/// Forcibly removes a Pokémon from the field.
/// </summary>
void ForceClearPokemonFromField();
/// <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();
}

View File

@@ -0,0 +1,6 @@
namespace PkmnLib.Dynamic.Models.Choices;
public interface IFleeChoice : ITurnChoice
{
}

View File

@@ -0,0 +1,6 @@
namespace PkmnLib.Dynamic.Models.Choices;
public interface IItemChoice : ITurnChoice
{
}

View File

@@ -0,0 +1,74 @@
using PkmnLib.Dynamic.ScriptHandling;
namespace PkmnLib.Dynamic.Models.Choices;
/// <summary>
/// The choice of a Pokémon to use a move.
/// </summary>
public interface IMoveChoice : ITurnChoice
{
/// <summary>
/// The move that is used.
/// </summary>
ILearnedMove UsedMove { get; }
/// <summary>
/// The side the move is targeted at.
/// </summary>
byte TargetSide { get; }
/// <summary>
/// The position the move is targeted at.
/// </summary>
byte TargetPosition { get; }
/// <summary>
/// The priority of the move.
/// </summary>
sbyte Priority { get; set; }
/// <summary>
/// The underlying script of the move.
/// </summary>
ScriptContainer Script { get; set; }
}
/// <inheritdoc cref="IMoveChoice"/>
public class MoveChoice : TurnChoice, IMoveChoice
{
/// <inheritdoc cref="MoveChoice"/>
public MoveChoice(IPokemon user, ILearnedMove usedMove, byte targetSide, byte targetPosition) : base(user)
{
UsedMove = usedMove;
TargetSide = targetSide;
TargetPosition = targetPosition;
}
/// <inheritdoc />
public ILearnedMove UsedMove { get; }
/// <inheritdoc />
public byte TargetSide { get; }
/// <inheritdoc />
public byte TargetPosition { get; }
/// <inheritdoc />
public sbyte Priority { get; set; }
/// <inheritdoc />
public ScriptContainer Script { get; set; } = new();
/// <inheritdoc />
public override int ScriptCount => 1 + User.ScriptCount;
/// <inheritdoc />
public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts) => scripts.Add(Script);
/// <inheritdoc />
public override void CollectScripts(List<IEnumerable<ScriptContainer>> scripts)
{
GetOwnScripts(scripts);
User.CollectScripts(scripts);
}
}

View File

@@ -0,0 +1,6 @@
namespace PkmnLib.Dynamic.Models.Choices;
public interface IPassChoice : ITurnChoice
{
}

View File

@@ -0,0 +1,6 @@
namespace PkmnLib.Dynamic.Models.Choices;
public interface ISwitchChoice : ITurnChoice
{
}

View File

@@ -0,0 +1,56 @@
using PkmnLib.Dynamic.ScriptHandling;
namespace PkmnLib.Dynamic.Models.Choices;
public interface ITurnChoice : IScriptSource
{
/// <summary>
/// The user of the turn choice
/// </summary>
IPokemon User { get; }
/// <summary>
/// The speed of the user at the beginning of the turn.
/// </summary>
uint Speed { get; set; }
/// <summary>
/// This random value is set at the beginning of the turn. It is used for tie breaking of the
/// turn order in a predictable way, regardless of implementation and hardware.
/// </summary>
uint RandomValue { get; set; }
/// <summary>
/// Whether the choice has failed. A failed choice will stop running, and execute special
/// fail handling during turn execution.
/// </summary>
bool HasFailed { get; }
/// <summary>
/// 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();
}
public abstract class TurnChoice : ScriptSource, ITurnChoice
{
protected TurnChoice(IPokemon user)
{
User = user;
}
public IPokemon User { get; }
public uint Speed { get; set; }
public uint RandomValue { get; set; }
public bool HasFailed { get; private set; }
public void Fail()
{
HasFailed = true;
}
}

View File

@@ -0,0 +1,83 @@
namespace PkmnLib.Dynamic.Models.Choices;
/// <summary>
/// Comparer for turnchoices, to determine the order in which they should be executed.
/// </summary>
public class TurnChoiceComparer : IComparer<ITurnChoice>
{
private enum CompareValues
{
XEqualsY = 0,
XLessThanY = -1,
XGreaterThanY = 1
}
private CompareValues CompareForSameType(ITurnChoice x, ITurnChoice y)
{
// Higher speed goes first
var speedComparison = x.Speed.CompareTo(y.Speed);
if (speedComparison != 0)
return (CompareValues)speedComparison;
// If speed is equal, we use the random values we've given to each choice to tiebreak.
// This is to ensure that the order of choices is deterministic.
return (CompareValues)x.RandomValue.CompareTo(y.RandomValue);
}
private CompareValues CompareImpl(ITurnChoice? x, ITurnChoice? y)
{
// Deal with possible null values
switch (x)
{
case null when y is null:
return CompareValues.XEqualsY;
case null:
return CompareValues.XLessThanY;
}
if (y is null)
return CompareValues.XGreaterThanY;
switch (x)
{
case IMoveChoice moveX:
// Move choices go first
if (y is IMoveChoice moveY)
{
// Higher priority goes first
var priorityComparison = moveX.Priority.CompareTo(moveY.Priority);
if (priorityComparison != 0)
return (CompareValues)priorityComparison;
return CompareForSameType(moveX, moveY);
}
return CompareValues.XGreaterThanY;
case IItemChoice itemX:
// Item choices go second
return y switch
{
IMoveChoice => CompareValues.XLessThanY,
IItemChoice itemY => CompareForSameType(itemX, itemY),
_ => CompareValues.XGreaterThanY
};
case ISwitchChoice switchX:
// Switch choices go third
return y switch
{
IMoveChoice or IItemChoice => CompareValues.XLessThanY,
ISwitchChoice switchY => CompareForSameType(switchX, switchY),
_ => CompareValues.XGreaterThanY
};
case IPassChoice passX:
// Pass choices go last
return y switch
{
IMoveChoice or IItemChoice or ISwitchChoice => CompareValues.XLessThanY,
IPassChoice passY => CompareForSameType(passX, passY),
_ => CompareValues.XGreaterThanY
};
}
return CompareValues.XLessThanY;
}
/// <inheritdoc />
public int Compare(ITurnChoice? x, ITurnChoice? y) => (int) CompareImpl(x, y);
}

View File

@@ -0,0 +1,22 @@
namespace PkmnLib.Dynamic.Models;
/// <summary>
/// Where the damage comes from.
/// </summary>
public enum DamageSource
{
/// <summary>
/// The damage is done by a move.
/// </summary>
MoveDamage = 0,
/// <summary>
/// The damage is done by something else.
/// </summary>
Misc = 1,
/// <summary>
/// The damage is done because of struggling.
/// </summary>
Struggle = 2,
}

View File

@@ -0,0 +1,104 @@
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static;
using PkmnLib.Static.Moves;
namespace PkmnLib.Dynamic.Models;
/// <summary>
/// A hit data is the data for a single hit, on a single target.
/// </summary>
public record HitData
{
/// <summary>
/// Whether the hit is critical.
/// </summary>
public bool IsCritical { get; internal set; }
/// <summary>
/// The base power of the hit.
/// </summary>
public byte BasePower { get; internal set; }
/// <summary>
/// The effectiveness of the hit.
/// </summary>
public float Effectiveness { get; internal set; }
/// <summary>
/// The damage done by the hit.
/// </summary>
public uint Damage { get; internal set; }
/// <summary>
/// The type of the hit.
/// </summary>
public TypeIdentifier Type { get; internal set; }
/// <summary>
/// Whether the hit has failed.
/// </summary>
public bool HasFailed { get; private set; }
/// <summary>
/// Fails the hit.
/// </summary>
public void Fail() => HasFailed = true;
}
/// <summary>
/// An executing move is the data of the move for while it is executing.
/// </summary>
public interface IExecutingMove : IScriptSource
{
/// <summary>
/// The number of targets this move has.
/// </summary>
int TargetCount { get; }
/// <summary>
/// The number of hits this move has per target.
/// </summary>
byte NumberOfHits { get; }
/// <summary>
/// The user of the move.
/// </summary>
IPokemon User { get; }
/// <summary>
/// The move the user has actually chosen to do.
/// </summary>
ILearnedMove ChosenMove { get; }
/// <summary>
/// The move that the user is actually going to do. This can be different from the chosen move, for example
/// when metronome is used, in which case the chosen move will be metronome, and the movedata will be the
/// move that metronome has chosen.
/// </summary>
IMoveData UseMove { get; }
/// <summary>
/// The script of the move.
/// </summary>
ScriptContainer Script { get; }
/// <summary>
/// Gets a hit data for a target, with a specific index.
/// </summary>
HitData GetHitData(IPokemon target, byte hit);
/// <summary>
/// Checks whether a Pokémon is a target for this move.
/// </summary>
bool IsPokemonTarget(IPokemon target);
/// <summary>
/// Gets the index of the hits in this move where the hits for a specific target start.
/// </summary>
int GetTargetIndex(IPokemon target);
/// <summary>
/// Gets a hit based on its raw index.
/// </summary>
HitData GetDataFromRawIndex(int index);
}

View File

@@ -0,0 +1,114 @@
using PkmnLib.Static.Moves;
namespace PkmnLib.Dynamic.Models;
/// <summary>
/// The different ways a move can be learned.
/// </summary>
public enum MoveLearnMethod
{
/// <summary>
/// We do not know the learn method.
/// </summary>
Unknown,
/// <summary>
/// The move is learned by leveling up.
/// </summary>
LevelUp,
/// <summary>
/// The move is learned when the Pokémon is hatched from an egg.
/// </summary>
Egg,
/// <summary>
/// The move is learned by using a tutor in the game.
/// </summary>
Tutor,
/// <summary>
/// The move is learned by using a TM or HM.
/// </summary>
Machine,
/// <summary>
/// The move is learned when the Pokémon changes form.
/// </summary>
FormChange
}
/// <summary>
/// A learned move is the data attached to a Pokemon for a move it has learned. It has information
/// such as the remaining amount of users, how it has been learned, etc.
/// </summary>
public interface ILearnedMove
{
/// <summary>
/// The immutable move information of the move.
/// </summary>
IMoveData MoveData { get; }
/// <summary>
/// The maximal power points for this move.
/// </summary>
byte MaxPp { get; }
/// <summary>
/// The way the move has been learned.
/// </summary>
MoveLearnMethod LearnMethod { get; }
/// <summary>
/// Try and reduce the PP by a certain amount. If the amount is higher than the current uses,
/// return false. Otherwise, reduce the PP, and return true.
/// </summary>
bool TryUse(byte amount = 1);
/// <summary>
/// Set the remaining PP to the max amount of PP.
/// </summary>
void RestoreAllUses();
/// <summary>
/// Restore the remaining PP by a certain amount. Will prevent it from going above max PP.
/// </summary>
void RestoreUses(byte amount);
}
/// <inheritdoc />
public class LearnedMoveImpl : ILearnedMove
{
private byte _maxPpModification = 0;
public LearnedMoveImpl(IMoveData moveData, MoveLearnMethod learnMethod)
{
MoveData = moveData;
LearnMethod = learnMethod;
CurrentPp = MaxPp;
}
/// <inheritdoc />
public IMoveData MoveData { get; }
/// <inheritdoc />
public byte MaxPp => (byte)(MoveData.BaseUsages + _maxPpModification);
/// <inheritdoc />
public MoveLearnMethod LearnMethod { get; }
public byte CurrentPp { get; private set; }
public bool TryUse(byte amount = 1)
{
if (CurrentPp < amount)
return false;
CurrentPp -= amount;
return true;
}
public void RestoreAllUses() => CurrentPp = MaxPp;
public void RestoreUses(byte amount) => CurrentPp = (byte)Math.Min(CurrentPp + amount, MaxPp);
}

View File

@@ -0,0 +1,292 @@
using JetBrains.Annotations;
using PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static;
using PkmnLib.Static.Species;
namespace PkmnLib.Dynamic.Models;
/// <summary>
/// The data of a Pokemon.
/// </summary>
public interface IPokemon : IScriptSource
{
/// <summary>
/// The library data of the Pokemon.
/// </summary>
IDynamicLibrary Library { get; }
/// <summary>
/// The species of the Pokemon.
/// </summary>
ISpecies Species { get; }
/// <summary>
/// The form of the Pokemon.
/// </summary>
IForm Form { get; }
/// <summary>
/// An optional display species of the Pokemon. If this is set, the client should display this
/// species. An example of usage for this is the Illusion ability.
/// </summary>
ISpecies? DisplaySpecies { get; }
/// <summary>
/// An optional display form of the Pokemon. If this is set, the client should display this
/// form. An example of usage for this is the Illusion ability.
/// </summary>
IForm? DisplayForm { get; }
/// <summary>
/// The current level of the Pokemon.
/// </summary>
LevelInt Level { get; }
/// <summary>
/// The amount of experience of the Pokemon.
/// </summary>
uint Experience { get; }
/// <summary>
/// The personality value of the Pokemon.
/// </summary>
uint PersonalityValue { get; }
/// <summary>
/// The gender of the Pokemon.
/// </summary>
Gender Gender { get; }
/// <summary>
/// The coloring of the Pokemon. Value 0 is the default, value 1 means shiny. Other values are
/// currently not used, and can be used for other implementations.
/// </summary>
byte Coloring { get; }
/// <summary>
/// The held item of the Pokemon.
/// </summary>
IItem? HeldItem { get; }
/// <summary>
/// The remaining health points of the Pokemon.
/// </summary>
uint CurrentHealth { get; }
/// <summary>
/// The weight of the Pokemon in kilograms.
/// </summary>
float WeightInKm { get; set; }
/// <summary>
/// The height of the Pokemon in meters.
/// </summary>
float HeightInMeters { get; set; }
/// <summary>
/// The happiness of the Pokemon. Also known as friendship.
/// </summary>
byte Happiness { get; }
/// <summary>
/// The stats of the Pokemon when disregarding any stat boosts.
/// </summary>
StatisticSet<uint> FlatStats { get; }
/// <summary>
/// The statistics boosts of the Pokemon. Will prevent the value from going above 6, and below
/// -6.
/// </summary>
StatBoostStatisticSet StatBoost { get; }
/// <summary>
/// The stats of the Pokemon including the stat boosts
/// </summary>
StatisticSet<uint> BoostedStats { get; }
/// <summary>
/// The individual values of the Pokemon.
/// </summary>
IndividualValueStatisticSet IndividualValues { get; }
/// <summary>
/// The effort values of the Pokemon.
/// </summary>
EffortValueStatisticSet EffortValues { get; }
/// <summary>
/// The nature of the Pokemon.
/// </summary>
INature Nature { get; }
/// <summary>
/// An optional nickname of the Pokemon.
/// </summary>
string? Nickname { get; }
/// <summary>
/// An index of the ability to find the actual ability on the form.
/// </summary>
AbilityIndex AbilityIndex { get; }
/// <summary>
/// An ability can be overriden to an arbitrary ability. This is for example used for the Mummy
/// ability.
/// </summary>
IAbility? OverrideAbility { get; }
/// <summary>
/// If in battle, we have additional data.
/// </summary>
IPokemonBattleData? BattleData { get; }
/// <summary>
/// The moves the Pokemon has learned. This is of a set length of <see cref="Const.MovesCount"/>. Empty move slots
/// are null.
/// </summary>
IReadOnlyList<ILearnedMove?> Moves { get; }
/// <summary>
/// Whether or not the Pokemon is allowed to gain experience.
/// </summary>
bool AllowedExperience { get; }
/// <summary>
/// The current types of the Pokemon.
/// </summary>
IReadOnlyList<TypeIdentifier> Types { get; }
/// <summary>
/// Whether or not this Pokemon is an egg.
/// </summary>
bool IsEgg { get; }
/// <summary>
/// Whether or not this Pokemon was caught this battle.
/// </summary>
bool IsCaught { get; }
/// <summary>
/// The script for the held item.
/// </summary>
ScriptContainer HeldItemTriggerScript { get; }
/// <summary>
/// The script for the ability.
/// </summary>
ScriptContainer AbilityScript { get; }
/// <summary>
/// The script for the status.
/// </summary>
ScriptContainer StatusScript { get; }
/// <summary>
/// The volatile status scripts of the Pokemon.
/// </summary>
IScriptSet Volatile { get; }
/// <summary>
/// Checks whether the Pokemon is holding an item with a specific name.
/// </summary>
bool HasHeldItem(string itemName);
/// <summary>
/// Changes the held item of the Pokemon. Returns the previously held item.
/// </summary>
[MustUseReturnValue] IItem? SetHeldItem(IItem? item);
/// <summary>
/// Removes the held item from the Pokemon. Returns the previously held item.
/// </summary>
[MustUseReturnValue] IItem? RemoveHeldItem();
/// <summary>
/// Makes the Pokemon uses its held item. Returns whether the item was consumed.
/// </summary>
bool ConsumeHeldItem();
/// <summary>
/// Change a boosted stat by a certain amount.
/// </summary>
/// <param name="stat">The stat to be changed</param>
/// <param name="change">The amount to change the stat by</param>
/// <param name="selfInflicted">Whether the change was self-inflicted. This can be relevant in scripts.</param>
void ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted);
/// <summary>
/// Returns the currently active ability.
/// </summary>
IAbility ActiveAbility { get; }
/// <summary>
/// Calculates the flat stats on the Pokemon. This should be called when for example the base
/// stats, level, nature, IV, or EV changes. This has a side effect of recalculating the boosted
/// stats, as those depend on the flat stats.
/// </summary>
void RecalculateFlatStats();
/// <summary>
/// Calculates the boosted stats on the Pokemon, _without_ recalculating the flat stats.
/// This should be called when a stat boost changes.
/// </summary>
void RecalculateBoostedStats();
/// <summary>
/// Change the species of the Pokemon.
/// </summary>
void ChangeSpecies(ISpecies species, IForm form);
/// <summary>
/// Change the form of the Pokemon.
/// </summary>
void ChangeForm(IForm form);
/// <summary>
/// Whether the Pokemon is useable in a battle.
/// </summary>
bool IsUsable { get; }
/// <summary>
/// Whether the Pokemon is fainted.
/// </summary>
bool IsFainted { get; }
/// <summary>
/// Damages the Pokemon by a certain amount of damage, from a damage source.
/// </summary>
void Damage(uint damage, DamageSource source, EventBatchId batchId);
/// <summary>
/// Heals the Pokemon by a specific amount. Unless allow_revive is set to true, this will not
/// heal if the Pokemon has 0 health. If the amount healed is 0, this will return false.
/// </summary>
bool Heal(uint heal, bool allowRevive);
/// <summary>
/// Learn a move by name.
/// </summary>
void LearnMove(string moveName, MoveLearnMethod method, byte index);
/// <summary>
/// Removes the current non-volatile status from the Pokemon.
/// </summary>
void ClearStatus();
/// <summary>
/// Modifies the level by a certain amount
/// </summary>
void ChangeLevelBy(int change);
// TODO: (de)serialize
}
/// <summary>
/// The data of the Pokemon related to being in a battle.
/// </summary>
public interface IPokemonBattleData
{
IBattle? Battle { get; }
}

View File

@@ -0,0 +1,59 @@
using System.Collections;
namespace PkmnLib.Dynamic.Models;
public interface IPokemonParty : IReadOnlyList<IPokemon?>
{
/// <summary>
/// Sets the Pokemon at an index to a Pokemon, returning the old Pokemon.
/// </summary>
IPokemon? SwapInto(IPokemon pokemon, int index);
/// <summary>
/// Swaps two Pokemon in the party around.
/// </summary>
void Swap(int index1, int index2);
bool HasUsablePokemon();
}
public class PokemonParty : IPokemonParty
{
private readonly IPokemon?[] _pokemon;
public PokemonParty(int size)
{
_pokemon = new IPokemon[size];
}
/// <summary>
/// Sets the Pokemon at an index to a Pokemon, returning the old Pokemon.
/// </summary>
public IPokemon? SwapInto(IPokemon pokemon, int index)
{
var old = _pokemon[index];
_pokemon[index] = pokemon;
return old;
}
/// <summary>
/// Swaps two Pokemon in the party around.
/// </summary>
public void Swap(int index1, int index2) =>
(_pokemon[index1], _pokemon[index2]) = (_pokemon[index2], _pokemon[index1]);
public bool HasUsablePokemon() => _pokemon.Any(p => p != null && p.IsUsable);
/// <inheritdoc />
public IEnumerator<IPokemon?> GetEnumerator() => ((IEnumerable<IPokemon?>)_pokemon).GetEnumerator();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc />
public int Count => _pokemon.Length;
/// <inheritdoc />
public IPokemon? this[int index] => _pokemon[index];
}