Begin work on outlining dynamic side
This commit is contained in:
114
PkmnLib.Dynamic/Models/Battle.cs
Normal file
114
PkmnLib.Dynamic/Models/Battle.cs
Normal 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; }
|
||||
|
||||
|
||||
|
||||
}
|
||||
13
PkmnLib.Dynamic/Models/BattleChoiceQueue.cs
Normal file
13
PkmnLib.Dynamic/Models/BattleChoiceQueue.cs
Normal 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
|
||||
{
|
||||
}
|
||||
8
PkmnLib.Dynamic/Models/BattleParty.cs
Normal file
8
PkmnLib.Dynamic/Models/BattleParty.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace PkmnLib.Dynamic.Models;
|
||||
|
||||
public interface IBattleParty
|
||||
{
|
||||
IPokemonParty Party { get; }
|
||||
bool IsResponsibleForIndex(byte side, byte position);
|
||||
bool HasPokemonNotInField();
|
||||
}
|
||||
13
PkmnLib.Dynamic/Models/BattleRandom.cs
Normal file
13
PkmnLib.Dynamic/Models/BattleRandom.cs
Normal 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);
|
||||
}
|
||||
23
PkmnLib.Dynamic/Models/BattleResult.cs
Normal file
23
PkmnLib.Dynamic/Models/BattleResult.cs
Normal 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; }
|
||||
}
|
||||
120
PkmnLib.Dynamic/Models/BattleSide.cs
Normal file
120
PkmnLib.Dynamic/Models/BattleSide.cs
Normal 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();
|
||||
}
|
||||
6
PkmnLib.Dynamic/Models/Choices/FleeChoice.cs
Normal file
6
PkmnLib.Dynamic/Models/Choices/FleeChoice.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace PkmnLib.Dynamic.Models.Choices;
|
||||
|
||||
public interface IFleeChoice : ITurnChoice
|
||||
{
|
||||
|
||||
}
|
||||
6
PkmnLib.Dynamic/Models/Choices/ItemChoice.cs
Normal file
6
PkmnLib.Dynamic/Models/Choices/ItemChoice.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace PkmnLib.Dynamic.Models.Choices;
|
||||
|
||||
public interface IItemChoice : ITurnChoice
|
||||
{
|
||||
|
||||
}
|
||||
74
PkmnLib.Dynamic/Models/Choices/MoveChoice.cs
Normal file
74
PkmnLib.Dynamic/Models/Choices/MoveChoice.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
6
PkmnLib.Dynamic/Models/Choices/PassChoice.cs
Normal file
6
PkmnLib.Dynamic/Models/Choices/PassChoice.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace PkmnLib.Dynamic.Models.Choices;
|
||||
|
||||
public interface IPassChoice : ITurnChoice
|
||||
{
|
||||
|
||||
}
|
||||
6
PkmnLib.Dynamic/Models/Choices/SwitchChoice.cs
Normal file
6
PkmnLib.Dynamic/Models/Choices/SwitchChoice.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace PkmnLib.Dynamic.Models.Choices;
|
||||
|
||||
public interface ISwitchChoice : ITurnChoice
|
||||
{
|
||||
|
||||
}
|
||||
56
PkmnLib.Dynamic/Models/Choices/TurnChoice.cs
Normal file
56
PkmnLib.Dynamic/Models/Choices/TurnChoice.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
83
PkmnLib.Dynamic/Models/Choices/TurnChoiceComparer.cs
Normal file
83
PkmnLib.Dynamic/Models/Choices/TurnChoiceComparer.cs
Normal 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);
|
||||
}
|
||||
22
PkmnLib.Dynamic/Models/DamageSource.cs
Normal file
22
PkmnLib.Dynamic/Models/DamageSource.cs
Normal 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,
|
||||
}
|
||||
104
PkmnLib.Dynamic/Models/ExecutingMove.cs
Normal file
104
PkmnLib.Dynamic/Models/ExecutingMove.cs
Normal 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);
|
||||
}
|
||||
114
PkmnLib.Dynamic/Models/LearnedMove.cs
Normal file
114
PkmnLib.Dynamic/Models/LearnedMove.cs
Normal 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);
|
||||
}
|
||||
292
PkmnLib.Dynamic/Models/Pokemon.cs
Normal file
292
PkmnLib.Dynamic/Models/Pokemon.cs
Normal 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; }
|
||||
}
|
||||
59
PkmnLib.Dynamic/Models/PokemonParty.cs
Normal file
59
PkmnLib.Dynamic/Models/PokemonParty.cs
Normal 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];
|
||||
}
|
||||
Reference in New Issue
Block a user