Begin work on outlining dynamic side
This commit is contained in:
parent
1b501dee7e
commit
a251913ebd
17
PkmnLib.Dynamic/Events/EventBatchId.cs
Normal file
17
PkmnLib.Dynamic/Events/EventBatchId.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace PkmnLib.Dynamic.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows for the batching of multiple events that should be shown at the same time.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is useful for when we send multiple events to the client in sequence, but we want to show them in one go.
|
||||||
|
/// For example, when a Pokemon gets hurt by poison, we want to show the purple poison animation and the damage at the
|
||||||
|
/// same time. This is done by batching the events together.
|
||||||
|
/// </remarks>
|
||||||
|
public record struct EventBatchId()
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The unique identifier for this batch of events.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; init; } = Guid.NewGuid();
|
||||||
|
}
|
11
PkmnLib.Dynamic/Events/EventData.cs
Normal file
11
PkmnLib.Dynamic/Events/EventData.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace PkmnLib.Dynamic.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event is something that happens during a battle. This can be used by front-end code to
|
||||||
|
/// display information about the battle to the user. This is the only way for the front-end to
|
||||||
|
/// know what is happening in the battle.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEventData
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
21
PkmnLib.Dynamic/Events/EventHook.cs
Normal file
21
PkmnLib.Dynamic/Events/EventHook.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
namespace PkmnLib.Dynamic.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The event hook of a battle. This is used to hook into the battle and get notified when something
|
||||||
|
/// happens that a front-end might want to know about.
|
||||||
|
/// </summary>
|
||||||
|
public class EventHook
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The event handler that is called when the event is triggered.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<IEventData>? Handler;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers the event, calling the handler with the given data.
|
||||||
|
/// </summary>
|
||||||
|
public void Invoke(IEventData data)
|
||||||
|
{
|
||||||
|
Handler?.Invoke(this, data);
|
||||||
|
}
|
||||||
|
}
|
6
PkmnLib.Dynamic/GlobalUsings.cs
Normal file
6
PkmnLib.Dynamic/GlobalUsings.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
global using LevelInt = byte;
|
||||||
|
|
||||||
|
public class Const
|
||||||
|
{
|
||||||
|
public const int MovesCount = 4;
|
||||||
|
}
|
30
PkmnLib.Dynamic/Libraries/BattleStatCalculator.cs
Normal file
30
PkmnLib.Dynamic/Libraries/BattleStatCalculator.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using PkmnLib.Dynamic.Models;
|
||||||
|
using PkmnLib.Static;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.Libraries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A battle stat calculator is used to calculate stats for a Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
public interface IBattleStatCalculator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate all the flat stats of a Pokemon, disregarding stat boosts.
|
||||||
|
/// </summary>
|
||||||
|
void CalculateFlatStats(IPokemon pokemon, StatisticSet<uint> stats);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate a single flat stat of a Pokemon, disregarding stat boosts.
|
||||||
|
/// </summary>
|
||||||
|
uint CalculateFlatStat(IPokemon pokemon, Statistic stat);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate all the boosted stats of a Pokemon, including stat boosts.
|
||||||
|
/// </summary>
|
||||||
|
void CalculateBoostedStats(IPokemon pokemon, StatisticSet<uint> stats);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate a single boosted stat of a Pokemon, including stat boosts.
|
||||||
|
/// </summary>
|
||||||
|
uint CalculateBoostedStat(IPokemon pokemon, Statistic stat);
|
||||||
|
}
|
24
PkmnLib.Dynamic/Libraries/DamageCalculator.cs
Normal file
24
PkmnLib.Dynamic/Libraries/DamageCalculator.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using PkmnLib.Dynamic.Models;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.Libraries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A damage library holds the functions related to the calculation of damage.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDamageCalculator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate the damage for a given hit on a Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
uint GetDamage(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate the base power for a given hit on a Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
byte GetBasePower(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether a specified hit should be critical or not.
|
||||||
|
/// </summary>
|
||||||
|
bool IsCritical(IBattle battle, IExecutingMove executingMove, IPokemon target, byte hitNumber);
|
||||||
|
}
|
55
PkmnLib.Dynamic/Libraries/DynamicLibrary.cs
Normal file
55
PkmnLib.Dynamic/Libraries/DynamicLibrary.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using PkmnLib.Static.Libraries;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.Libraries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The dynamic library stores a static data library, as well as holding different libraries and
|
||||||
|
/// calculators that might be customized between different generations and implementations.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDynamicLibrary
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The static data is the immutable storage data for this library.
|
||||||
|
/// </summary>
|
||||||
|
IStaticLibrary StaticLibrary { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The stat calculator deals with the calculation of flat and boosted stats, based on the
|
||||||
|
/// Pokémon's attributes.
|
||||||
|
/// </summary>
|
||||||
|
IBattleStatCalculator StatCalculator { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The damage calculator deals with the calculation of things relating to damage.
|
||||||
|
/// </summary>
|
||||||
|
IDamageCalculator DamageCalculator { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Misc Library holds minor functions that do not fall in any of the other libraries and
|
||||||
|
/// calculators.
|
||||||
|
/// </summary>
|
||||||
|
IMiscLibrary MiscLibrary { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DynamicLibraryImpl : IDynamicLibrary
|
||||||
|
{
|
||||||
|
public DynamicLibraryImpl(IStaticLibrary staticLibrary, IBattleStatCalculator statCalculator,
|
||||||
|
IDamageCalculator damageCalculator, IMiscLibrary miscLibrary)
|
||||||
|
{
|
||||||
|
StaticLibrary = staticLibrary;
|
||||||
|
StatCalculator = statCalculator;
|
||||||
|
DamageCalculator = damageCalculator;
|
||||||
|
MiscLibrary = miscLibrary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IStaticLibrary StaticLibrary { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IBattleStatCalculator StatCalculator { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IDamageCalculator DamageCalculator { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IMiscLibrary MiscLibrary { get; }
|
||||||
|
}
|
19
PkmnLib.Dynamic/Libraries/MiscLibrary.cs
Normal file
19
PkmnLib.Dynamic/Libraries/MiscLibrary.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using PkmnLib.Dynamic.Models;
|
||||||
|
using PkmnLib.Dynamic.Models.Choices;
|
||||||
|
using PkmnLib.Static;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.Libraries;
|
||||||
|
|
||||||
|
public interface IMiscLibrary
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the choice that's used when a Pokemon is unable to make the move choice it wants to, or when it has no
|
||||||
|
/// moves left, yet wants to make a move.
|
||||||
|
/// </summary>
|
||||||
|
ITurnChoice ReplacementChoice(IPokemon user, byte targetSide, byte targetPosition);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current time of day for the battle.
|
||||||
|
/// </summary>
|
||||||
|
TimeOfDay GetTimeOfDay();
|
||||||
|
}
|
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];
|
||||||
|
}
|
32
PkmnLib.Dynamic/PkmnLib.Dynamic.csproj
Normal file
32
PkmnLib.Dynamic/PkmnLib.Dynamic.csproj
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
|
<LangVersion>12</LangVersion>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<DocumentationFile>bin\Debug\netstandard2.1\PkmnLib.Dynamic.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<DocumentationFile>bin\Release\netstandard2.1\PkmnLib.Dynamic.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentResults" Version="3.16.0" />
|
||||||
|
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
||||||
|
<PackageReference Include="PolySharp" Version="1.14.1">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PkmnLib.Static\PkmnLib.Static.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
23
PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs
Normal file
23
PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace PkmnLib.Dynamic.ScriptHandling.Registry;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A plugin is a way to register scripts and other dynamic components to the script registry.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Plugin
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the plugin. Mostly used for debugging purposes.
|
||||||
|
/// </summary>
|
||||||
|
public abstract string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the plugin should be loaded. Lower values are loaded first.
|
||||||
|
/// 0 should be reserved for the core battle scripts.
|
||||||
|
/// </summary>
|
||||||
|
public abstract uint LoadOrder { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run the registration of the plugin when we're building the library.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Register(ScriptRegistry registry);
|
||||||
|
}
|
14
PkmnLib.Dynamic/ScriptHandling/Registry/ScriptAttribute.cs
Normal file
14
PkmnLib.Dynamic/ScriptHandling/Registry/ScriptAttribute.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class ScriptAttribute : Attribute
|
||||||
|
{
|
||||||
|
public ScriptCategory Category { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public ScriptAttribute(ScriptCategory category, string name)
|
||||||
|
{
|
||||||
|
Category = category;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
56
PkmnLib.Dynamic/ScriptHandling/Registry/ScriptRegistry.cs
Normal file
56
PkmnLib.Dynamic/ScriptHandling/Registry/ScriptRegistry.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using PkmnLib.Dynamic.Libraries;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||||
|
|
||||||
|
public class ScriptRegistry
|
||||||
|
{
|
||||||
|
private Dictionary<(ScriptCategory category, string name), Func<Script>> _scriptTypes = new();
|
||||||
|
private IBattleStatCalculator? _battleStatCalculator;
|
||||||
|
private IDamageCalculator? _damageCalculator;
|
||||||
|
private IMiscLibrary? _miscLibrary;
|
||||||
|
|
||||||
|
public void RegisterAssemblyScripts(Assembly assembly)
|
||||||
|
{
|
||||||
|
var baseType = typeof(Script);
|
||||||
|
foreach (var type in assembly.GetTypes().Where(t => baseType.IsAssignableFrom(t)))
|
||||||
|
{
|
||||||
|
var attribute = type.GetCustomAttribute<ScriptAttribute>();
|
||||||
|
if (attribute == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
RegisterScriptType(attribute.Category, attribute.Name, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterScriptType(ScriptCategory category, string name, Type type)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
if (type == null)
|
||||||
|
throw new ArgumentNullException(nameof(type));
|
||||||
|
|
||||||
|
var constructor = type.GetConstructor(Type.EmptyTypes);
|
||||||
|
if (constructor == null)
|
||||||
|
throw new ArgumentException("The type must have a parameterless constructor.");
|
||||||
|
|
||||||
|
// We create a lambda that creates a new instance of the script type.
|
||||||
|
// This is more performant than using Activator.CreateInstance.
|
||||||
|
_scriptTypes[(category, name)] = Expression.Lambda<Func<Script>>(Expression.New(constructor)).Compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterBattleStatCalculator<T>(T battleStatCalculator)
|
||||||
|
where T : IBattleStatCalculator => _battleStatCalculator = battleStatCalculator;
|
||||||
|
|
||||||
|
public void RegisterDamageCalculator<T>(T damageCalculator)
|
||||||
|
where T : IDamageCalculator => _damageCalculator = damageCalculator;
|
||||||
|
|
||||||
|
public void RegisterMiscLibrary<T>(T miscLibrary) where T : IMiscLibrary
|
||||||
|
=> _miscLibrary = miscLibrary;
|
||||||
|
|
||||||
|
internal Dictionary<(ScriptCategory category, string name), Func<Script>> ScriptTypes => _scriptTypes;
|
||||||
|
internal IBattleStatCalculator? BattleStatCalculator => _battleStatCalculator;
|
||||||
|
internal IDamageCalculator? DamageCalculator => _damageCalculator;
|
||||||
|
internal IMiscLibrary? MiscLibrary => _miscLibrary;
|
||||||
|
}
|
6
PkmnLib.Dynamic/ScriptHandling/Script.cs
Normal file
6
PkmnLib.Dynamic/ScriptHandling/Script.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||||
|
|
||||||
|
public abstract class Script
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
57
PkmnLib.Dynamic/ScriptHandling/ScriptCategory.cs
Normal file
57
PkmnLib.Dynamic/ScriptHandling/ScriptCategory.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using PkmnLib.Dynamic.Models;
|
||||||
|
using PkmnLib.Dynamic.Models.Choices;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A script category defines a sub-group of scripts. This can be used to have multiple scripts with
|
||||||
|
/// the same name, but a different script. It should be completely valid for a move to have the same
|
||||||
|
/// name as an ability, or more commonly: for a script attached to a Pokemon to have the same name as
|
||||||
|
/// a move that placed it there.
|
||||||
|
/// </summary>
|
||||||
|
public enum ScriptCategory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A script that belongs to a move. This generally is only the script that is attached to a
|
||||||
|
/// <see cref="IMoveChoice"/> and <see cref="IExecutingMove"/>
|
||||||
|
/// </summary>
|
||||||
|
Move = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An ability script. Scripts in this category are always abilities, and therefore always
|
||||||
|
/// attached to a Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
Ability = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A non volatile status script. Scripts in this category are always non volatile statuses, and
|
||||||
|
/// therefore always attached to a Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
Status = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A volatile status script. Scripts in this category are always volatile status effects, and
|
||||||
|
/// therefore always attached to a Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
Pokemon = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A script that can be attached to an entire side.
|
||||||
|
/// </summary>
|
||||||
|
Side = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A script that can be attached to the entire battle.
|
||||||
|
/// </summary>
|
||||||
|
Battle = 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A special script for weather, for use on battles.
|
||||||
|
/// </summary>
|
||||||
|
Weather = 6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A special script for held items. As they're part of a held item, they're attached to a Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
ItemBattleTrigger = 7,
|
||||||
|
}
|
22
PkmnLib.Dynamic/ScriptHandling/ScriptContainer.cs
Normal file
22
PkmnLib.Dynamic/ScriptHandling/ScriptContainer.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||||
|
|
||||||
|
public class ScriptContainer : IEnumerable<ScriptContainer>
|
||||||
|
{
|
||||||
|
private Script? _script = null;
|
||||||
|
|
||||||
|
public bool IsEmpty => _script is null;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerator<ScriptContainer> GetEnumerator()
|
||||||
|
{
|
||||||
|
yield return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
71
PkmnLib.Dynamic/ScriptHandling/ScriptIterator.cs
Normal file
71
PkmnLib.Dynamic/ScriptHandling/ScriptIterator.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||||
|
|
||||||
|
public class ScriptIterator : IEnumerable<ScriptContainer>
|
||||||
|
{
|
||||||
|
private readonly IReadOnlyList<IEnumerable<ScriptContainer>> _scripts;
|
||||||
|
private int _index = -1;
|
||||||
|
private int _setIndex = -1;
|
||||||
|
|
||||||
|
public ScriptIterator(IReadOnlyList<IEnumerable<ScriptContainer>> scripts)
|
||||||
|
{
|
||||||
|
_scripts = scripts;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IncrementToNext()
|
||||||
|
{
|
||||||
|
if (_index != -1)
|
||||||
|
{
|
||||||
|
var current = _scripts[_index];
|
||||||
|
if (current is IScriptSet)
|
||||||
|
{
|
||||||
|
_setIndex += 1;
|
||||||
|
if (_setIndex >= current.Count())
|
||||||
|
{
|
||||||
|
_setIndex = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_index += 1;
|
||||||
|
for (; _index < _scripts.Count; _index++)
|
||||||
|
{
|
||||||
|
switch (_scripts[_index])
|
||||||
|
{
|
||||||
|
case IScriptSet:
|
||||||
|
_setIndex = 0;
|
||||||
|
return true;
|
||||||
|
case ScriptContainer { IsEmpty: false }:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerator<ScriptContainer> GetEnumerator()
|
||||||
|
{
|
||||||
|
while (IncrementToNext())
|
||||||
|
{
|
||||||
|
var current = _scripts[_index];
|
||||||
|
yield return current switch
|
||||||
|
{
|
||||||
|
IScriptSet set => set.At(_setIndex),
|
||||||
|
ScriptContainer container => container,
|
||||||
|
_ => throw new InvalidOperationException("Invalid script type")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
16
PkmnLib.Dynamic/ScriptHandling/ScriptSet.cs
Normal file
16
PkmnLib.Dynamic/ScriptHandling/ScriptSet.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using FluentResults;
|
||||||
|
|
||||||
|
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||||
|
|
||||||
|
public interface IScriptSet : IEnumerable<ScriptContainer>
|
||||||
|
{
|
||||||
|
Result<ScriptContainer> Add(Script script);
|
||||||
|
Result<ScriptContainer?> Add(string scriptKey);
|
||||||
|
ScriptContainer? Get(string scriptKey);
|
||||||
|
void Remove(string scriptKey);
|
||||||
|
void Clear();
|
||||||
|
void Contains(string scriptKey);
|
||||||
|
ScriptContainer At(int index);
|
||||||
|
int Count { get; }
|
||||||
|
IEnumerable<string> GetScriptNames();
|
||||||
|
}
|
52
PkmnLib.Dynamic/ScriptHandling/ScriptSource.cs
Normal file
52
PkmnLib.Dynamic/ScriptHandling/ScriptSource.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
namespace PkmnLib.Dynamic.ScriptHandling;
|
||||||
|
|
||||||
|
public interface IScriptSource
|
||||||
|
{
|
||||||
|
ScriptIterator GetScripts();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of scripts that are expected to be relevant for this source. This generally is
|
||||||
|
/// The number of its own scripts + the number of scripts for any parents.
|
||||||
|
/// </summary>
|
||||||
|
int ScriptCount { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This should add all scripts belonging to this source to the scripts Vec, disregarding its
|
||||||
|
/// potential parents.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scripts"></param>
|
||||||
|
void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This should add all scripts that are relevant to the source the the scripts Vec, including
|
||||||
|
/// everything that belongs to its parents.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scripts"></param>
|
||||||
|
void CollectScripts(List<IEnumerable<ScriptContainer>> scripts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ScriptSource : IScriptSource
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ScriptIterator GetScripts()
|
||||||
|
{
|
||||||
|
if (_scripts == null)
|
||||||
|
{
|
||||||
|
_scripts = new List<IEnumerable<ScriptContainer>>(ScriptCount);
|
||||||
|
CollectScripts(_scripts);
|
||||||
|
}
|
||||||
|
return new ScriptIterator(_scripts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public abstract int ScriptCount { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public abstract void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public abstract void CollectScripts(List<IEnumerable<ScriptContainer>> scripts);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
private List<IEnumerable<ScriptContainer>>? _scripts;
|
||||||
|
}
|
15
PkmnLib.Dynamic/StaticHelpers.cs
Normal file
15
PkmnLib.Dynamic/StaticHelpers.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
namespace PkmnLib.Dynamic;
|
||||||
|
|
||||||
|
public static class StaticHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A function to get the current date and time. This can be replaced in cases where the date and time
|
||||||
|
/// may not be the same as the system time.
|
||||||
|
/// </summary>
|
||||||
|
public static Func<DateTime> DateTimeProvider { get; set; } = () => DateTime.Now;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the current date and time.
|
||||||
|
/// </summary>
|
||||||
|
public static DateTime GetCurrentDateTime() => DateTimeProvider();
|
||||||
|
}
|
@ -4,6 +4,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Static", "PkmnLib.S
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Tests", "PkmnLib.Tests\PkmnLib.Tests.csproj", "{42DE3095-0468-4827-AF5C-691C94BA7F92}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Tests", "PkmnLib.Tests\PkmnLib.Tests.csproj", "{42DE3095-0468-4827-AF5C-691C94BA7F92}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Dynamic", "PkmnLib.Dynamic\PkmnLib.Dynamic.csproj", "{D0CBA9A9-7288-41B4-B76B-CB4F20036AB2}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Scripts.Gen7", "PkmnLib.Scripts.Gen7\PkmnLib.Scripts.Gen7.csproj", "{FA5380F0-28CC-4AEC-8963-814B347A89BA}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -18,5 +22,13 @@ Global
|
|||||||
{42DE3095-0468-4827-AF5C-691C94BA7F92}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{42DE3095-0468-4827-AF5C-691C94BA7F92}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{42DE3095-0468-4827-AF5C-691C94BA7F92}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{42DE3095-0468-4827-AF5C-691C94BA7F92}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{42DE3095-0468-4827-AF5C-691C94BA7F92}.Release|Any CPU.Build.0 = Release|Any CPU
|
{42DE3095-0468-4827-AF5C-691C94BA7F92}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D0CBA9A9-7288-41B4-B76B-CB4F20036AB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D0CBA9A9-7288-41B4-B76B-CB4F20036AB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D0CBA9A9-7288-41B4-B76B-CB4F20036AB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D0CBA9A9-7288-41B4-B76B-CB4F20036AB2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{FA5380F0-28CC-4AEC-8963-814B347A89BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{FA5380F0-28CC-4AEC-8963-814B347A89BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{FA5380F0-28CC-4AEC-8963-814B347A89BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{FA5380F0-28CC-4AEC-8963-814B347A89BA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
23
PkmnLib.Scripts.Gen7/Gen7Plugin.cs
Normal file
23
PkmnLib.Scripts.Gen7/Gen7Plugin.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using PkmnLib.Dynamic.ScriptHandling;
|
||||||
|
using PkmnLib.Dynamic.ScriptHandling.Registry;
|
||||||
|
using PkmnLib.Scripts.Gen7.Libraries;
|
||||||
|
|
||||||
|
namespace PkmnLib.Scripts.Gen7;
|
||||||
|
|
||||||
|
public class Gen7Plugin : Plugin
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Name => "Gen7";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override uint LoadOrder => 0;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Register(ScriptRegistry registry)
|
||||||
|
{
|
||||||
|
registry.RegisterAssemblyScripts(typeof(Gen7Plugin).Assembly);
|
||||||
|
registry.RegisterBattleStatCalculator(new Gen7BattleStatCalculator());
|
||||||
|
registry.RegisterDamageCalculator(new Gen7DamageCalculator(true));
|
||||||
|
registry.RegisterMiscLibrary(new Gen7MiscLibrary());
|
||||||
|
}
|
||||||
|
}
|
96
PkmnLib.Scripts.Gen7/Libraries/Gen7BattleStatCalculator.cs
Normal file
96
PkmnLib.Scripts.Gen7/Libraries/Gen7BattleStatCalculator.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
using PkmnLib.Dynamic.Libraries;
|
||||||
|
using PkmnLib.Dynamic.Models;
|
||||||
|
using PkmnLib.Static;
|
||||||
|
|
||||||
|
namespace PkmnLib.Scripts.Gen7.Libraries;
|
||||||
|
|
||||||
|
public class Gen7BattleStatCalculator : IBattleStatCalculator
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void CalculateFlatStats(IPokemon pokemon, StatisticSet<uint> stats)
|
||||||
|
{
|
||||||
|
stats.SetStatistic(Statistic.Hp, CalculateHealthStat(pokemon));
|
||||||
|
stats.SetStatistic(Statistic.Attack, CalculateNormalStat(pokemon, Statistic.Attack));
|
||||||
|
stats.SetStatistic(Statistic.Defense, CalculateNormalStat(pokemon, Statistic.Defense));
|
||||||
|
stats.SetStatistic(Statistic.SpecialAttack, CalculateNormalStat(pokemon, Statistic.SpecialAttack));
|
||||||
|
stats.SetStatistic(Statistic.SpecialDefense, CalculateNormalStat(pokemon, Statistic.SpecialDefense));
|
||||||
|
stats.SetStatistic(Statistic.Speed, CalculateNormalStat(pokemon, Statistic.Speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public uint CalculateFlatStat(IPokemon pokemon, Statistic stat)
|
||||||
|
{
|
||||||
|
return stat switch
|
||||||
|
{
|
||||||
|
Statistic.Hp => CalculateHealthStat(pokemon),
|
||||||
|
_ => CalculateNormalStat(pokemon, stat)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void CalculateBoostedStats(IPokemon pokemon, StatisticSet<uint> stats)
|
||||||
|
{
|
||||||
|
stats.SetStatistic(Statistic.Hp, CalculateBoostedStat(pokemon, Statistic.Hp));
|
||||||
|
stats.SetStatistic(Statistic.Attack, CalculateBoostedStat(pokemon, Statistic.Attack));
|
||||||
|
stats.SetStatistic(Statistic.Defense, CalculateBoostedStat(pokemon, Statistic.Defense));
|
||||||
|
stats.SetStatistic(Statistic.SpecialAttack, CalculateBoostedStat(pokemon, Statistic.SpecialAttack));
|
||||||
|
stats.SetStatistic(Statistic.SpecialDefense, CalculateBoostedStat(pokemon, Statistic.SpecialDefense));
|
||||||
|
stats.SetStatistic(Statistic.Speed, CalculateBoostedStat(pokemon, Statistic.Speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public uint CalculateBoostedStat(IPokemon pokemon, Statistic stat)
|
||||||
|
{
|
||||||
|
var flatStat = CalculateFlatStat(pokemon, stat);
|
||||||
|
var boostModifier = GetStatBoostModifier(pokemon, stat);
|
||||||
|
var boostedStat = flatStat * boostModifier;
|
||||||
|
if (boostedStat > uint.MaxValue) boostedStat = uint.MaxValue;
|
||||||
|
return (uint)boostedStat;
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint CalculateHealthStat(IPokemon pokemon)
|
||||||
|
{
|
||||||
|
var baseValue = (ulong)pokemon.Form.BaseStats.Hp;
|
||||||
|
var iv = (ulong)pokemon.IndividualValues.Hp;
|
||||||
|
var ev = (ulong)pokemon.EffortValues.Hp;
|
||||||
|
var level = (ulong)pokemon.Level;
|
||||||
|
var health = (((2 * baseValue + iv + (ev / 4)) * level) / 100) + level + 10;
|
||||||
|
if (health > uint.MaxValue) health = uint.MaxValue;
|
||||||
|
return (uint)health;
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint CalculateNormalStat(IPokemon pokemon, Statistic statistic)
|
||||||
|
{
|
||||||
|
var baseValue = (ulong)pokemon.Form.BaseStats.GetStatistic(statistic);
|
||||||
|
var iv = (ulong)pokemon.IndividualValues.GetStatistic(statistic);
|
||||||
|
var ev = (ulong)pokemon.EffortValues.GetStatistic(statistic);
|
||||||
|
var level = (ulong)pokemon.Level;
|
||||||
|
var unmodified = (((2 * baseValue + iv + (ev / 4)) * level) / 100) + 5;
|
||||||
|
var natureModifier = pokemon.Nature.GetStatModifier(statistic);
|
||||||
|
var modified = (unmodified * natureModifier);
|
||||||
|
if (modified > uint.MaxValue) modified = uint.MaxValue;
|
||||||
|
return (uint)modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetStatBoostModifier(IPokemon pokemon, Statistic statistic)
|
||||||
|
{
|
||||||
|
var boost = pokemon.StatBoost.GetStatistic(statistic);
|
||||||
|
return boost switch
|
||||||
|
{
|
||||||
|
-6 => 2.0f / 8.0f,
|
||||||
|
-5 => 2.0f / 7.0f,
|
||||||
|
-4 => 2.0f / 6.0f,
|
||||||
|
-3 => 2.0f / 5.0f,
|
||||||
|
-2 => 2.0f / 4.0f,
|
||||||
|
-1 => 2.0f / 3.0f,
|
||||||
|
0 => 1.0f,
|
||||||
|
1 => 3.0f / 2.0f,
|
||||||
|
2 => 4.0f / 2.0f,
|
||||||
|
3 => 5.0f / 2.0f,
|
||||||
|
4 => 6.0f / 2.0f,
|
||||||
|
5 => 7.0f / 2.0f,
|
||||||
|
6 => 8.0f / 2.0f,
|
||||||
|
_ => throw new System.ArgumentException("Stat boost was out of expected range of -6 to 6")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
155
PkmnLib.Scripts.Gen7/Libraries/Gen7DamageCalculator.cs
Normal file
155
PkmnLib.Scripts.Gen7/Libraries/Gen7DamageCalculator.cs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using PkmnLib.Dynamic.Libraries;
|
||||||
|
using PkmnLib.Dynamic.Models;
|
||||||
|
using PkmnLib.Static;
|
||||||
|
using PkmnLib.Static.Moves;
|
||||||
|
|
||||||
|
namespace PkmnLib.Scripts.Gen7.Libraries;
|
||||||
|
|
||||||
|
public class Gen7DamageCalculator(bool hasRandomness) : IDamageCalculator
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public uint GetDamage(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData)
|
||||||
|
{
|
||||||
|
var category = executingMove.UseMove.Category;
|
||||||
|
if (category == MoveCategory.Status)
|
||||||
|
return 0;
|
||||||
|
if (hitData.Effectiveness == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var levelModifier = (2.0f * executingMove.User.Level) / 5.0f + 2.0f;
|
||||||
|
var basePower = (float)hitData.BasePower;
|
||||||
|
var statModifier = GetStatModifier(executingMove, target, hitNumber, hitData);
|
||||||
|
var damageModifier = GetDamageModifier(executingMove, target, hitNumber, hitData);
|
||||||
|
|
||||||
|
var floatDamage = MathF.Floor(levelModifier * basePower);
|
||||||
|
floatDamage = MathF.Floor(floatDamage * statModifier);
|
||||||
|
floatDamage = MathF.Floor(floatDamage / 50.0f) + 2.0f;
|
||||||
|
floatDamage = MathF.Floor(floatDamage * damageModifier);
|
||||||
|
if (executingMove.TargetCount > 1)
|
||||||
|
floatDamage = MathF.Floor(floatDamage * 0.75f);
|
||||||
|
|
||||||
|
if (hitData.IsCritical)
|
||||||
|
{
|
||||||
|
var critModifier = 1.5f;
|
||||||
|
// TODO: script hook to change the critical modifier
|
||||||
|
floatDamage = MathF.Floor(floatDamage * critModifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasRandomness)
|
||||||
|
{
|
||||||
|
var battle = target.BattleData?.Battle;
|
||||||
|
if (battle == null)
|
||||||
|
throw new InvalidOperationException("Randomness is enabled, but no battle is set.");
|
||||||
|
var random = battle.Random;
|
||||||
|
var randomFactor = random.GetInt(85, 101) / 100.0f;
|
||||||
|
floatDamage = MathF.Floor(floatDamage * randomFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (executingMove.User.Types.Contains(hitData.Type))
|
||||||
|
{
|
||||||
|
var stabModifier = 1.5f;
|
||||||
|
// TODO: script hook to change the STAB modifier
|
||||||
|
floatDamage = MathF.Floor(floatDamage * stabModifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
floatDamage = MathF.Floor(floatDamage * hitData.Effectiveness);
|
||||||
|
uint damage = floatDamage switch
|
||||||
|
{
|
||||||
|
> uint.MaxValue => uint.MaxValue,
|
||||||
|
< 1 => 1,
|
||||||
|
_ => (uint)floatDamage
|
||||||
|
};
|
||||||
|
// TODO: script hook to modify the damage
|
||||||
|
// TODO: script hook to modify incoming damage
|
||||||
|
|
||||||
|
return damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public byte GetBasePower(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData)
|
||||||
|
{
|
||||||
|
if (executingMove.UseMove.Category == MoveCategory.Status)
|
||||||
|
return 0;
|
||||||
|
var basePower = hitData.BasePower;
|
||||||
|
// TODO: script hook to modify the base power
|
||||||
|
return basePower;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[SuppressMessage("ReSharper", "UnreachableSwitchArmDueToIntegerAnalysis")] // disabled because of the TODO
|
||||||
|
public bool IsCritical(IBattle battle, IExecutingMove executingMove, IPokemon target, byte hitNumber)
|
||||||
|
{
|
||||||
|
if (executingMove.UseMove.Category == MoveCategory.Status)
|
||||||
|
return false;
|
||||||
|
byte critStage = 0;
|
||||||
|
// TODO: script hook to modify the crit stage
|
||||||
|
|
||||||
|
var random = battle.Random;
|
||||||
|
return critStage switch
|
||||||
|
{
|
||||||
|
0 => random.GetInt(24) == 0,
|
||||||
|
1 => random.GetInt(8) == 0,
|
||||||
|
2 => random.GetInt(2) == 0,
|
||||||
|
_ => true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetStatModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData)
|
||||||
|
{
|
||||||
|
var category = executingMove.UseMove.Category;
|
||||||
|
if (category == MoveCategory.Status)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
var (offensive, defensive) = category switch
|
||||||
|
{
|
||||||
|
MoveCategory.Physical => (Statistic.Attack, Statistic.Defense),
|
||||||
|
_ => (Statistic.SpecialAttack, Statistic.SpecialDefense),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if we can bypass the defensive stat boost on the target. We default to this if the
|
||||||
|
// move is critical, and the target has a defensive stat boost of > 0, but a script is
|
||||||
|
// allowed to change this.
|
||||||
|
var bypassDefense = hitData.IsCritical && target.StatBoost.GetStatistic(defensive) > 0;
|
||||||
|
// TODO: script hook
|
||||||
|
|
||||||
|
// Check if we can bypass the offensive stat boost on the user. We default to this if the
|
||||||
|
// move is critical, and the user has an offensive stat boost of < 0, but a script is
|
||||||
|
// allowed to change this.
|
||||||
|
var bypassOffense = hitData.IsCritical && executingMove.User.StatBoost.GetStatistic(offensive) < 0;
|
||||||
|
// TODO: script hook
|
||||||
|
|
||||||
|
var userStats = executingMove.User.BoostedStats;
|
||||||
|
if (bypassOffense)
|
||||||
|
userStats = executingMove.User.FlatStats;
|
||||||
|
var offensiveStat = userStats.GetStatistic(offensive);
|
||||||
|
|
||||||
|
var targetStats = target.BoostedStats;
|
||||||
|
if (bypassDefense)
|
||||||
|
targetStats = target.FlatStats;
|
||||||
|
var defensiveStat = targetStats.GetStatistic(defensive);
|
||||||
|
|
||||||
|
// TODO: script hook to modify the stats above
|
||||||
|
|
||||||
|
var modifier = (float)offensiveStat / defensiveStat;
|
||||||
|
// TODO: script hook to modify the modifier
|
||||||
|
|
||||||
|
return modifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the damage modifier. This is a value that defaults to 1.0, but can be modified by scripts
|
||||||
|
/// to apply a raw modifier to the damage.
|
||||||
|
/// </summary>
|
||||||
|
private static float GetDamageModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber,
|
||||||
|
HitData hitData)
|
||||||
|
{
|
||||||
|
var modifier = 1.0f;
|
||||||
|
|
||||||
|
// TODO: script hook to modify the modifier
|
||||||
|
|
||||||
|
return modifier;
|
||||||
|
}
|
||||||
|
}
|
38
PkmnLib.Scripts.Gen7/Libraries/Gen7MiscLibrary.cs
Normal file
38
PkmnLib.Scripts.Gen7/Libraries/Gen7MiscLibrary.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using PkmnLib.Dynamic;
|
||||||
|
using PkmnLib.Dynamic.Libraries;
|
||||||
|
using PkmnLib.Dynamic.Models;
|
||||||
|
using PkmnLib.Dynamic.Models.Choices;
|
||||||
|
using PkmnLib.Static;
|
||||||
|
using PkmnLib.Static.Moves;
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Scripts.Gen7.Libraries;
|
||||||
|
|
||||||
|
public class Gen7MiscLibrary : IMiscLibrary
|
||||||
|
{
|
||||||
|
private readonly IMoveData _struggleData = new MoveDataImpl("struggle", new TypeIdentifier(0),
|
||||||
|
MoveCategory.Physical, 50,
|
||||||
|
255, 255, MoveTarget.Any, 0,
|
||||||
|
new SecondaryEffectImpl(-1, "struggle", new Dictionary<StringKey, object>()), []);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ITurnChoice ReplacementChoice(IPokemon user, byte targetSide, byte targetPosition) =>
|
||||||
|
new MoveChoice(user, new LearnedMoveImpl(_struggleData, MoveLearnMethod.Unknown), targetSide,
|
||||||
|
targetPosition);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public TimeOfDay GetTimeOfDay()
|
||||||
|
{
|
||||||
|
var time = StaticHelpers.GetCurrentDateTime();
|
||||||
|
var hour = time.Hour;
|
||||||
|
return hour switch
|
||||||
|
{
|
||||||
|
>= 0 and <= 5 => TimeOfDay.Night,
|
||||||
|
>= 6 and <= 9 => TimeOfDay.Morning,
|
||||||
|
>= 10 and <= 16 => TimeOfDay.Day,
|
||||||
|
17 => TimeOfDay.Evening,
|
||||||
|
_ => TimeOfDay.Night
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
14
PkmnLib.Scripts.Gen7/PkmnLib.Scripts.Gen7.csproj
Normal file
14
PkmnLib.Scripts.Gen7/PkmnLib.Scripts.Gen7.csproj
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>12</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj" />
|
||||||
|
<ProjectReference Include="..\PkmnLib.Static\PkmnLib.Static.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -1,3 +1,5 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace PkmnLib.Static;
|
namespace PkmnLib.Static;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -79,10 +81,13 @@ public record StatisticSet<T> : ImmutableStatisticSet<T>
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected T Add(T a, T b) => (T)Convert.ChangeType(Convert.ToInt32(a) + Convert.ToInt32(b), typeof(T));
|
||||||
|
protected T Subtract(T a, T b) => (T)Convert.ChangeType(Convert.ToInt32(a) - Convert.ToInt32(b), typeof(T));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Modifies a statistic in the set.
|
/// Modifies a statistic in the set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetStatistic(Statistic stat, T value)
|
public virtual void SetStatistic(Statistic stat, T value)
|
||||||
{
|
{
|
||||||
switch (stat)
|
switch (stat)
|
||||||
{
|
{
|
||||||
@ -112,64 +117,186 @@ public record StatisticSet<T> : ImmutableStatisticSet<T>
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Increases a statistic in the set by a value.
|
/// Increases a statistic in the set by a value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void IncreaseStatistic(Statistic stat, T value)
|
public virtual bool IncreaseStatistic(Statistic stat, T value)
|
||||||
{
|
{
|
||||||
switch (stat)
|
switch (stat)
|
||||||
{
|
{
|
||||||
case Statistic.Hp:
|
case Statistic.Hp:
|
||||||
Hp = (T)Convert.ChangeType(Convert.ToInt32(Hp) + Convert.ToInt32(value), typeof(T));
|
Hp = Add(Hp, value);
|
||||||
break;
|
break;
|
||||||
case Statistic.Attack:
|
case Statistic.Attack:
|
||||||
Attack = (T)Convert.ChangeType(Convert.ToInt32(Attack) + Convert.ToInt32(value), typeof(T));
|
Attack = Add(Attack, value);
|
||||||
break;
|
break;
|
||||||
case Statistic.Defense:
|
case Statistic.Defense:
|
||||||
Defense = (T)Convert.ChangeType(Convert.ToInt32(Defense) + Convert.ToInt32(value), typeof(T));
|
Defense = Add(Defense, value);
|
||||||
break;
|
break;
|
||||||
case Statistic.SpecialAttack:
|
case Statistic.SpecialAttack:
|
||||||
SpecialAttack =
|
SpecialAttack = Add(SpecialAttack, value);
|
||||||
(T)Convert.ChangeType(Convert.ToInt32(SpecialAttack) + Convert.ToInt32(value), typeof(T));
|
|
||||||
break;
|
break;
|
||||||
case Statistic.SpecialDefense:
|
case Statistic.SpecialDefense:
|
||||||
SpecialDefense =
|
SpecialDefense = Add(SpecialDefense, value);
|
||||||
(T)Convert.ChangeType(Convert.ToInt32(SpecialDefense) + Convert.ToInt32(value), typeof(T));
|
|
||||||
break;
|
break;
|
||||||
case Statistic.Speed:
|
case Statistic.Speed:
|
||||||
Speed = (T)Convert.ChangeType(Convert.ToInt32(Speed) + Convert.ToInt32(value), typeof(T));
|
Speed = Add(Speed, value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException("Invalid statistic.");
|
throw new ArgumentException("Invalid statistic.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Decreases a statistic in the set by a value.
|
/// Decreases a statistic in the set by a value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DecreaseStatistic(Statistic stat, T value)
|
public virtual bool DecreaseStatistic(Statistic stat, T value)
|
||||||
{
|
{
|
||||||
switch (stat)
|
switch (stat)
|
||||||
{
|
{
|
||||||
case Statistic.Hp:
|
case Statistic.Hp:
|
||||||
Hp = (T)Convert.ChangeType(Convert.ToInt32(Hp) - Convert.ToInt32(value), typeof(T));
|
Hp = Subtract(Hp, value);
|
||||||
break;
|
break;
|
||||||
case Statistic.Attack:
|
case Statistic.Attack:
|
||||||
Attack = (T)Convert.ChangeType(Convert.ToInt32(Attack) - Convert.ToInt32(value), typeof(T));
|
Attack = Subtract(Attack, value);
|
||||||
break;
|
break;
|
||||||
case Statistic.Defense:
|
case Statistic.Defense:
|
||||||
Defense = (T)Convert.ChangeType(Convert.ToInt32(Defense) - Convert.ToInt32(value), typeof(T));
|
Defense = Subtract(Defense, value);
|
||||||
break;
|
break;
|
||||||
case Statistic.SpecialAttack:
|
case Statistic.SpecialAttack:
|
||||||
SpecialAttack =
|
SpecialAttack = Subtract(SpecialAttack, value);
|
||||||
(T)Convert.ChangeType(Convert.ToInt32(SpecialAttack) - Convert.ToInt32(value), typeof(T));
|
|
||||||
break;
|
break;
|
||||||
case Statistic.SpecialDefense:
|
case Statistic.SpecialDefense:
|
||||||
SpecialDefense =
|
SpecialDefense = Subtract(SpecialDefense, value);
|
||||||
(T)Convert.ChangeType(Convert.ToInt32(SpecialDefense) - Convert.ToInt32(value), typeof(T));
|
|
||||||
break;
|
break;
|
||||||
case Statistic.Speed:
|
case Statistic.Speed:
|
||||||
Speed = (T)Convert.ChangeType(Convert.ToInt32(Speed) - Convert.ToInt32(value), typeof(T));
|
Speed = Subtract(Speed, value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException("Invalid statistic.");
|
throw new ArgumentException("Invalid statistic.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A set of statistics that can be changed, but are clamped to a minimum and maximum value.
|
||||||
|
/// </summary>
|
||||||
|
public abstract record ClampedStatisticSet<T> : StatisticSet<T>
|
||||||
|
where T : struct, IComparable<T>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
[SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
|
||||||
|
protected ClampedStatisticSet(T hp, T attack, T defense, T specialAttack, T specialDefense, T speed) : base(hp,
|
||||||
|
attack, defense, specialAttack, specialDefense, speed)
|
||||||
|
{
|
||||||
|
if (Min.CompareTo(Max) > 0)
|
||||||
|
throw new ArgumentException("Minimum value must be less than or equal to maximum value.");
|
||||||
|
Hp = Clamp(Hp, Min, Max);
|
||||||
|
Attack = Clamp(Attack, Min, Max);
|
||||||
|
Defense = Clamp(Defense, Min, Max);
|
||||||
|
SpecialAttack = Clamp(SpecialAttack, Min, Max);
|
||||||
|
SpecialDefense = Clamp(SpecialDefense, Min, Max);
|
||||||
|
Speed = Clamp(Speed, Min, Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T Clamp(T value, T min, T max)
|
||||||
|
{
|
||||||
|
if (value.CompareTo(min) < 0)
|
||||||
|
return min;
|
||||||
|
if (value.CompareTo(max) > 0)
|
||||||
|
return max;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum value for the statistics.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract T Min { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum value for the statistics.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract T Max { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void SetStatistic(Statistic stat, T value) => base.SetStatistic(stat, Clamp(value, Min, Max));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool IncreaseStatistic(Statistic stat, T value)
|
||||||
|
{
|
||||||
|
var current = GetStatistic(stat);
|
||||||
|
var newValue = Add(current, value);
|
||||||
|
if (newValue.CompareTo(Max) > 0)
|
||||||
|
value = Subtract(Max, current);
|
||||||
|
if (value.CompareTo(default) == 0)
|
||||||
|
return false;
|
||||||
|
return base.IncreaseStatistic(stat, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool DecreaseStatistic(Statistic stat, T value)
|
||||||
|
{
|
||||||
|
var current = GetStatistic(stat);
|
||||||
|
var newValue = Subtract(current, value);
|
||||||
|
if (newValue.CompareTo(Min) < 0)
|
||||||
|
value = Subtract(current, Min);
|
||||||
|
if (value.CompareTo(default) == 0)
|
||||||
|
return false;
|
||||||
|
return base.DecreaseStatistic(stat, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A set of statistics that can be changed, but are clamped to a minimum and maximum value of -6 and 6.
|
||||||
|
/// </summary>
|
||||||
|
public record StatBoostStatisticSet : ClampedStatisticSet<sbyte>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override sbyte Min => -6;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override sbyte Max => 6;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="StatBoostStatisticSet"/>
|
||||||
|
public StatBoostStatisticSet(sbyte hp, sbyte attack, sbyte defense, sbyte specialAttack, sbyte specialDefense,
|
||||||
|
sbyte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A set of statistics that can be changed, but are clamped to a minimum and maximum value of 0 and 31.
|
||||||
|
/// </summary>
|
||||||
|
public record IndividualValueStatisticSet : ClampedStatisticSet<byte>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override byte Min => 0;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override byte Max => 31;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IndividualValueStatisticSet"/>
|
||||||
|
public IndividualValueStatisticSet(byte hp, byte attack, byte defense, byte specialAttack, byte specialDefense,
|
||||||
|
byte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A set of statistics that can be changed, but are clamped to a minimum and maximum value of 0 and 252.
|
||||||
|
/// </summary>
|
||||||
|
public record EffortValueStatisticSet : ClampedStatisticSet<byte>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override byte Min => 0;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override byte Max => 252;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EffortValueStatisticSet"/>
|
||||||
|
public EffortValueStatisticSet(byte hp, byte attack, byte defense, byte specialAttack, byte specialDefense,
|
||||||
|
byte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user