Add all missing docs

This commit is contained in:
Deukhoofd 2025-05-03 14:18:12 +02:00
parent 4d5dfd0342
commit 441f5dddaf
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
40 changed files with 400 additions and 21 deletions

View File

@ -1,3 +1,4 @@
using JetBrains.Annotations;
using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Static.Moves; using PkmnLib.Static.Moves;
@ -8,12 +9,13 @@ namespace PkmnLib.Dynamic.AI;
/// <summary> /// <summary>
/// The base class for implementing an AI for Pokémon. /// The base class for implementing an AI for Pokémon.
/// </summary> /// </summary>
[PublicAPI]
public abstract class PokemonAI public abstract class PokemonAI
{ {
/// <summary> /// <summary>
/// The name of the AI. /// The name of the AI.
/// </summary> /// </summary>
public StringKey Name { get; set; } public StringKey Name { get; }
/// <inheritdoc cref="PokemonAI" /> /// <inheritdoc cref="PokemonAI" />
protected PokemonAI(StringKey name) protected PokemonAI(StringKey name)
@ -26,9 +28,11 @@ public abstract class PokemonAI
/// </summary> /// </summary>
public abstract ITurnChoice GetChoice(IBattle battle, IPokemon pokemon); public abstract ITurnChoice GetChoice(IBattle battle, IPokemon pokemon);
/// <summary>
/// For a given user and move, returns the valid targets for that move.
/// </summary>
public IEnumerable<(byte side, byte position)> GetValidTargetsForMove(IPokemon user, ILearnedMove move) public IEnumerable<(byte side, byte position)> GetValidTargetsForMove(IPokemon user, ILearnedMove move)
{ {
byte GetOppositeSide(byte side) => side == 0 ? (byte)1 : (byte)0;
var userBattleData = user.BattleData!; var userBattleData = user.BattleData!;
switch (move.MoveData.Target) switch (move.MoveData.Target)
{ {
@ -90,5 +94,7 @@ public abstract class PokemonAI
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
yield break;
byte GetOppositeSide(byte side) => side == 0 ? (byte)1 : (byte)0;
} }
} }

View File

@ -3,15 +3,26 @@ using PkmnLib.Dynamic.Models;
namespace PkmnLib.Dynamic.Events; namespace PkmnLib.Dynamic.Events;
/// <summary>
/// Represents an event that occurs when a Pokémon capture attempt is made.
/// </summary>
public class CaptureAttemptEvent : IEventData public class CaptureAttemptEvent : IEventData
{ {
/// <inheritdoc cref="CaptureAttemptEvent"/>
public CaptureAttemptEvent(IPokemon target, CaptureResult result) public CaptureAttemptEvent(IPokemon target, CaptureResult result)
{ {
Target = target; Target = target;
Result = result; Result = result;
} }
/// <summary>
/// The Pokémon that is being captured.
/// </summary>
public IPokemon Target { get; init; } public IPokemon Target { get; init; }
/// <summary>
/// The result of the capture attempt.
/// </summary>
public CaptureResult Result { get; init; } public CaptureResult Result { get; init; }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,17 +1,27 @@
namespace PkmnLib.Dynamic.Events; namespace PkmnLib.Dynamic.Events;
/// <summary>
/// Represents an event that occurs when a dialog is displayed.
/// </summary>
public class DialogEvent : IEventData public class DialogEvent : IEventData
{ {
/// <inheritdoc /> /// <inheritdoc />
public EventBatchId BatchId { get; init; } = new(); public EventBatchId BatchId { get; init; } = new();
/// <inheritdoc cref="DialogEvent"/>
public DialogEvent(string message, Dictionary<string, object>? parameters = null) public DialogEvent(string message, Dictionary<string, object>? parameters = null)
{ {
Message = message; Message = message;
Parameters = parameters; Parameters = parameters;
} }
/// <summary>
/// The message to be displayed in the dialog. This is generally a key that needs to be localized.
/// </summary>
public string Message { get; set; } public string Message { get; set; }
/// <summary>
/// Optional parameters that can be used to format the message in a localized string.
/// </summary>
public Dictionary<string, object>? Parameters { get; set; } public Dictionary<string, object>? Parameters { get; set; }
} }

View File

@ -2,8 +2,12 @@ using PkmnLib.Dynamic.Models;
namespace PkmnLib.Dynamic.Events; namespace PkmnLib.Dynamic.Events;
/// <summary>
/// Represents an event that occurs when a Pokémon gains experience.
/// </summary>
public class ExperienceGainEvent : IEventData public class ExperienceGainEvent : IEventData
{ {
/// <inheritdoc cref="ExperienceGainEvent"/>
public ExperienceGainEvent(IPokemon pokemon, uint previousExperience, uint newExperience) public ExperienceGainEvent(IPokemon pokemon, uint previousExperience, uint newExperience)
{ {
Pokemon = pokemon; Pokemon = pokemon;
@ -11,8 +15,19 @@ public class ExperienceGainEvent : IEventData
NewExperience = newExperience; NewExperience = newExperience;
} }
/// <summary>
/// The Pokémon that gained experience.
/// </summary>
public IPokemon Pokemon { get; set; } public IPokemon Pokemon { get; set; }
/// <summary>
/// The amount of experience the Pokémon had before the gain.
/// </summary>
public uint PreviousExperience { get; } public uint PreviousExperience { get; }
/// <summary>
/// The amount of experience the Pokémon has after the gain.
/// </summary>
public uint NewExperience { get; } public uint NewExperience { get; }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -3,11 +3,22 @@ using PkmnLib.Static.Species;
namespace PkmnLib.Dynamic.Events; namespace PkmnLib.Dynamic.Events;
/// <summary>
/// Represents an event that occurs when a Pokémon changes its form.
/// </summary>
public class FormChangeEvent : IEventData public class FormChangeEvent : IEventData
{ {
/// <summary>
/// The Pokémon that changed its form.
/// </summary>
public IPokemon Pokemon { get; } public IPokemon Pokemon { get; }
/// <summary>
/// The new form of the Pokémon.
/// </summary>
public IForm Form { get; } public IForm Form { get; }
/// <inheritdoc cref="FormChangeEvent"/>
public FormChangeEvent(IPokemon pokemon, IForm form) public FormChangeEvent(IPokemon pokemon, IForm form)
{ {
Pokemon = pokemon; Pokemon = pokemon;

View File

@ -10,6 +10,7 @@ namespace PkmnLib.Dynamic.Events;
/// </remarks> /// </remarks>
public readonly record struct EventBatchId public readonly record struct EventBatchId
{ {
/// <inheritdoc cref="EventBatchId"/>
public EventBatchId() public EventBatchId()
{ {
Id = Guid.NewGuid(); Id = Guid.NewGuid();

View File

@ -1,3 +1,5 @@
using JetBrains.Annotations;
namespace PkmnLib.Dynamic.Events; namespace PkmnLib.Dynamic.Events;
/// <summary> /// <summary>
@ -5,6 +7,7 @@ namespace PkmnLib.Dynamic.Events;
/// display information about the battle to the user. This is the only way for the front-end 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. /// know what is happening in the battle.
/// </summary> /// </summary>
[PublicAPI]
public interface IEventData public interface IEventData
{ {
/// <summary> /// <summary>

View File

@ -2,8 +2,12 @@ using PkmnLib.Dynamic.Models;
namespace PkmnLib.Dynamic.Events; namespace PkmnLib.Dynamic.Events;
/// <summary>
/// Represents an event that occurs when a Pokémon gains enough experience points to level up.
/// </summary>
public class LevelUpEvent : IEventData public class LevelUpEvent : IEventData
{ {
/// <inheritdoc cref="LevelUpEvent"/>
public LevelUpEvent(IPokemon pokemon, int previousLevel, int newLevel) public LevelUpEvent(IPokemon pokemon, int previousLevel, int newLevel)
{ {
Pokemon = pokemon; Pokemon = pokemon;
@ -11,10 +15,19 @@ public class LevelUpEvent : IEventData
NewLevel = newLevel; NewLevel = newLevel;
} }
/// <summary>
/// The new level of the Pokémon after leveling up.
/// </summary>
public int NewLevel { get; set; } public int NewLevel { get; set; }
/// <summary>
/// The previous level of the Pokémon before leveling up.
/// </summary>
public int PreviousLevel { get; set; } public int PreviousLevel { get; set; }
/// <summary>
/// The Pokémon that leveled up.
/// </summary>
public IPokemon Pokemon { get; set; } public IPokemon Pokemon { get; set; }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -3,12 +3,27 @@ using PkmnLib.Static.Species;
namespace PkmnLib.Dynamic.Events; namespace PkmnLib.Dynamic.Events;
/// <summary>
/// Represents an event that occurs when a Pokémon changes to a different species.
/// </summary>
public class SpeciesChangeEvent : IEventData public class SpeciesChangeEvent : IEventData
{ {
/// <summary>
/// The Pokémon that changed species.
/// </summary>
public IPokemon Pokemon { get; } public IPokemon Pokemon { get; }
/// <summary>
/// The new species of the Pokémon.
/// </summary>
public ISpecies Species { get; } public ISpecies Species { get; }
/// <summary>
/// The new form of the Pokémon, if applicable.
/// </summary>
public IForm Form { get; } public IForm Form { get; }
/// <inheritdoc cref="SpeciesChangeEvent"/>
public SpeciesChangeEvent(IPokemon pokemon, ISpecies species, IForm form) public SpeciesChangeEvent(IPokemon pokemon, ISpecies species, IForm form)
{ {
Pokemon = pokemon; Pokemon = pokemon;

View File

@ -3,8 +3,12 @@ using PkmnLib.Static;
namespace PkmnLib.Dynamic.Libraries; namespace PkmnLib.Dynamic.Libraries;
/// <summary>
/// Represents the result of a capture attempt.
/// </summary>
public record struct CaptureResult public record struct CaptureResult
{ {
/// <inheritdoc cref="CaptureResult"/>
public CaptureResult(bool IsCaught, int Shakes, bool CriticalCapture) public CaptureResult(bool IsCaught, int Shakes, bool CriticalCapture)
{ {
this.IsCaught = IsCaught; this.IsCaught = IsCaught;
@ -12,14 +16,35 @@ public record struct CaptureResult
this.CriticalCapture = CriticalCapture; this.CriticalCapture = CriticalCapture;
} }
/// <summary>
/// Indicates whether the capture was successful.
/// </summary>
public bool IsCaught { get; init; } public bool IsCaught { get; init; }
/// <summary>
/// The number of shakes the Poké Ball made before the capture attempt was successful or failed.
/// </summary>
public int Shakes { get; init; } public int Shakes { get; init; }
/// <summary>
/// Indicates whether a critical capture occurred. A critical capture is a special case where the Poké Ball
/// shakes only once and then captures the Pokémon.
/// </summary>
public bool CriticalCapture { get; init; } public bool CriticalCapture { get; init; }
/// <summary>
/// Creates a <see cref="CaptureResult"/> indicating a failed capture attempt.
/// </summary>
public static CaptureResult Failed => new(false, 0, false); public static CaptureResult Failed => new(false, 0, false);
} }
/// <summary>
/// Interface for a library that handles Pokémon capture mechanics.
/// </summary>
public interface ICaptureLibrary public interface ICaptureLibrary
{ {
/// <summary>
/// Attempts to capture a Pokémon using a specified item (e.g., Poké Ball).
/// </summary>
CaptureResult TryCapture(IPokemon target, IItem captureItem, IBattleRandom random); CaptureResult TryCapture(IPokemon target, IItem captureItem, IBattleRandom random);
} }

View File

@ -20,5 +20,8 @@ public interface IMiscLibrary
/// </summary> /// </summary>
TimeOfDay GetTimeOfDay(); TimeOfDay GetTimeOfDay();
/// <summary>
/// Returns whether the given Pokemon can flee from the battle.
/// </summary>
bool CanFlee(IBattle battle, IFleeChoice fleeChoice); bool CanFlee(IBattle battle, IFleeChoice fleeChoice);
} }

View File

@ -93,6 +93,10 @@ public interface IBattle : IScriptSource, IDeepCloneable
/// </summary> /// </summary>
void ValidateBattleState(); void ValidateBattleState();
/// <summary>
/// Checks whether a Pokemon has a forced turn choice. If it does, this returns true and the choice
/// is set in the out parameter. If it does not, this returns false and the out parameter is null.
/// </summary>
bool HasForcedTurn(IPokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice); bool HasForcedTurn(IPokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice);
/// <summary> /// <summary>
@ -117,6 +121,9 @@ public interface IBattle : IScriptSource, IDeepCloneable
/// </summary> /// </summary>
bool SetWeather(StringKey? weatherName, int duration); bool SetWeather(StringKey? weatherName, int duration);
/// <summary>
/// Volatile scripts are scripts that are not permanent and can be removed by other scripts.
/// </summary>
public IScriptSet Volatile { get; } public IScriptSet Volatile { get; }
/// <summary> /// <summary>
@ -124,8 +131,15 @@ public interface IBattle : IScriptSource, IDeepCloneable
/// </summary> /// </summary>
StringKey? WeatherName { get; } StringKey? WeatherName { get; }
/// <summary>
/// Sets the current terrain for the battle. If null is passed, this clears the terrain.
/// </summary>
/// <param name="terrainName"></param>
void SetTerrain(StringKey? terrainName); void SetTerrain(StringKey? terrainName);
/// <summary>
/// Gets the current terrain of the battle. If no terrain is present, this returns null.
/// </summary>
StringKey? TerrainName { get; } StringKey? TerrainName { get; }
/// <summary> /// <summary>
@ -134,6 +148,9 @@ public interface IBattle : IScriptSource, IDeepCloneable
/// </summary> /// </summary>
IReadOnlyList<IReadOnlyList<ITurnChoice>> PreviousTurnChoices { get; } IReadOnlyList<IReadOnlyList<ITurnChoice>> PreviousTurnChoices { get; }
/// <summary>
/// Attempts to capture a Pokemon. This will use the current RNG to determine whether the capture is successful.
/// </summary>
CaptureResult AttempCapture(byte sideIndex, byte position, IItem item); CaptureResult AttempCapture(byte sideIndex, byte position, IItem item);
} }
@ -350,7 +367,11 @@ public class BattleImpl : ScriptSource, IBattle
EventHook.Invoke(new EndTurnEvent()); EventHook.Invoke(new EndTurnEvent());
} }
private ScriptContainer _weatherScript = new(); private readonly ScriptContainer _weatherScript = new();
/// <summary>
/// The script that handles the current weather of the battle.
/// </summary>
public IReadOnlyScriptContainer WeatherScript => _weatherScript; public IReadOnlyScriptContainer WeatherScript => _weatherScript;
/// <inheritdoc /> /// <inheritdoc />
@ -377,6 +398,7 @@ public class BattleImpl : ScriptSource, IBattle
// TODO: Trigger weather change script hooks // TODO: Trigger weather change script hooks
} }
/// <inheritdoc />
public IScriptSet Volatile { get; } = new ScriptSet(); public IScriptSet Volatile { get; } = new ScriptSet();
/// <inheritdoc /> /// <inheritdoc />

View File

@ -16,6 +16,10 @@ public class BattleChoiceQueue : IDeepCloneable
{ {
private readonly ITurnChoice?[] _choices; private readonly ITurnChoice?[] _choices;
private int _currentIndex; private int _currentIndex;
/// <summary>
/// Returns the last choice that was executed.
/// </summary>
public ITurnChoice? LastRanChoice { get; private set; } public ITurnChoice? LastRanChoice { get; private set; }
/// <inheritdoc cref="BattleChoiceQueue"/> /// <inheritdoc cref="BattleChoiceQueue"/>
@ -122,9 +126,15 @@ public class BattleChoiceQueue : IDeepCloneable
internal IReadOnlyList<ITurnChoice?> GetChoices() => _choices; internal IReadOnlyList<ITurnChoice?> GetChoices() => _choices;
/// <summary>
/// This returns the first choice that matches the predicate, or null if none was found.
/// </summary>
public ITurnChoice? FirstOrDefault(Func<ITurnChoice, bool> predicate) => public ITurnChoice? FirstOrDefault(Func<ITurnChoice, bool> predicate) =>
_choices.Skip(_currentIndex).WhereNotNull().FirstOrDefault(predicate); _choices.Skip(_currentIndex).WhereNotNull().FirstOrDefault(predicate);
/// <summary>
/// Removes a choice from the queue.
/// </summary>
public void Remove(ITurnChoice choice) public void Remove(ITurnChoice choice)
{ {
var index = Array.FindIndex(_choices, _currentIndex, x => x == choice); var index = Array.FindIndex(_choices, _currentIndex, x => x == choice);

View File

@ -19,10 +19,18 @@ public interface IBattleRandom : IRandom, IDeepCloneable
/// <inheritdoc cref="IBattleRandom"/> /// <inheritdoc cref="IBattleRandom"/>
public class BattleRandomImpl : RandomImpl, IBattleRandom public class BattleRandomImpl : RandomImpl, IBattleRandom
{ {
/// <inheritdoc cref="BattleRandomImpl"/>
/// <remarks>
/// This constructor is used to instantiate the class when no seed is provided. It uses a time-dependent default seed value.
/// </remarks>
public BattleRandomImpl() public BattleRandomImpl()
{ {
} }
/// <inheritdoc cref="BattleRandomImpl"/>
/// <remarks>
/// This constructor is used to instantiate the class with a specific seed value.
/// </remarks>
public BattleRandomImpl(int seed) : base(seed) public BattleRandomImpl(int seed) : base(seed)
{ {
} }

View File

@ -142,9 +142,19 @@ public interface IBattleSide : IScriptSource, IDeepCloneable
/// </summary> /// </summary>
IItem? GetLastConsumedItem(byte battleDataPosition); IItem? GetLastConsumedItem(byte battleDataPosition);
/// <summary>
/// Marks a Pokémon as fainted. This is used to track the last turn a Pokémon in a position fainted.
/// </summary>
void MarkFaint(byte position); void MarkFaint(byte position);
/// <summary>
/// Gets the last turn a Pokémon in a specific position fainted.
/// </summary>
uint? GetLastFaintTurn(byte position); uint? GetLastFaintTurn(byte position);
/// <summary>
/// Gets the last turn a Pokémon in any position fainted.
/// </summary>
uint? GetLastFaintTurn(); uint? GetLastFaintTurn();
} }

View File

@ -27,6 +27,7 @@ public interface IItemChoice : ITurnChoice
/// <inheritdoc cref="IItemChoice"/> /// <inheritdoc cref="IItemChoice"/>
public class ItemChoice : TurnChoice, IItemChoice public class ItemChoice : TurnChoice, IItemChoice
{ {
/// <inheritdoc cref="ItemChoice"/>
public ItemChoice(IPokemon user, IItem item, byte? targetSide, byte? targetPosition) : base(user) public ItemChoice(IPokemon user, IItem item, byte? targetSide, byte? targetPosition) : base(user)
{ {
Item = item; Item = item;
@ -34,6 +35,9 @@ public class ItemChoice : TurnChoice, IItemChoice
TargetPosition = targetPosition; TargetPosition = targetPosition;
} }
/// <summary>
/// The item that is used.
/// </summary>
public IItem Item { get; } public IItem Item { get; }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -9,8 +9,10 @@ public interface IPassChoice : ITurnChoice
{ {
} }
/// <inheritdoc cref="IPassChoice"/>
public class PassChoice : TurnChoice, IPassChoice public class PassChoice : TurnChoice, IPassChoice
{ {
/// <inheritdoc cref="PassChoice"/>
public PassChoice(IPokemon user) : base(user) public PassChoice(IPokemon user) : base(user)
{ {
} }

View File

@ -16,6 +16,7 @@ public interface ISwitchChoice : ITurnChoice
/// <inheritdoc cref="ISwitchChoice"/> /// <inheritdoc cref="ISwitchChoice"/>
public class SwitchChoice : TurnChoice, ISwitchChoice public class SwitchChoice : TurnChoice, ISwitchChoice
{ {
/// <inheritdoc cref="SwitchChoice"/>
public SwitchChoice(IPokemon user, IPokemon switchTo) : base(user) public SwitchChoice(IPokemon user, IPokemon switchTo) : base(user)
{ {
SwitchTo = switchTo; SwitchTo = switchTo;

View File

@ -140,8 +140,14 @@ public interface IExecutingMove : IScriptSource
/// </summary> /// </summary>
IMoveChoice MoveChoice { get; } IMoveChoice MoveChoice { get; }
/// <summary>
/// Returns all the hits of this move.
/// </summary>
IReadOnlyList<IHitData> Hits { get; } IReadOnlyList<IHitData> Hits { get; }
/// <summary>
/// The battle this move is being executed in.
/// </summary>
IBattle Battle { get; } IBattle Battle { get; }
} }
@ -189,6 +195,9 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove
/// <inheritdoc /> /// <inheritdoc />
public ScriptContainer Script => MoveChoice.Script; public ScriptContainer Script => MoveChoice.Script;
/// <summary>
/// The volatile scripts that are applicable to this move.
/// </summary>
public IScriptSet Volatile => MoveChoice.Volatile; public IScriptSet Volatile => MoveChoice.Volatile;
/// <inheritdoc /> /// <inheritdoc />

View File

@ -101,6 +101,7 @@ public class LearnedMoveImpl : ILearnedMove
CurrentPp = MaxPp; CurrentPp = MaxPp;
} }
/// <inheritdoc cref="LearnedMoveImpl" />
public LearnedMoveImpl(IMoveData moveData, MoveLearnMethod learnMethod, byte pp) : this(moveData, learnMethod) public LearnedMoveImpl(IMoveData moveData, MoveLearnMethod learnMethod, byte pp) : this(moveData, learnMethod)
{ {
CurrentPp = pp; CurrentPp = pp;

View File

@ -202,6 +202,9 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// </summary> /// </summary>
bool IsCaught { get; } bool IsCaught { get; }
/// <summary>
/// Marks the Pokemon as caught. This makes it so that the Pokemon is not considered valid in battle anymore.
/// </summary>
public void MarkAsCaught(); public void MarkAsCaught();
/// <summary> /// <summary>
@ -395,6 +398,9 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// </summary> /// </summary>
void SetTypes(IReadOnlyList<TypeIdentifier> types); void SetTypes(IReadOnlyList<TypeIdentifier> types);
/// <summary>
/// Changes the ability of the Pokémon.
/// </summary>
void ChangeAbility(IAbility ability); void ChangeAbility(IAbility ability);
/// <summary> /// <summary>
@ -449,8 +455,14 @@ public interface IPokemonBattleData : IDeepCloneable
/// </summary> /// </summary>
void MarkItemAsConsumed(IItem item); void MarkItemAsConsumed(IItem item);
/// <summary>
/// The turn the Pokémon switched in.
/// </summary>
uint SwitchInTurn { get; internal set; } uint SwitchInTurn { get; internal set; }
/// <summary>
/// The side the Pokémon is on.
/// </summary>
IBattleSide BattleSide { get; } IBattleSide BattleSide { get; }
} }
@ -483,6 +495,7 @@ public class PokemonImpl : ScriptSource, IPokemon
CurrentHealth = BoostedStats.Hp; CurrentHealth = BoostedStats.Hp;
} }
/// <inheritdoc cref="PokemonImpl"/>
public PokemonImpl(IDynamicLibrary library, SerializedPokemon serializedPokemon) public PokemonImpl(IDynamicLibrary library, SerializedPokemon serializedPokemon)
{ {
Library = library; Library = library;

View File

@ -8,7 +8,14 @@ namespace PkmnLib.Dynamic.Models;
/// </summary> /// </summary>
public interface IPokemonParty : IReadOnlyList<IPokemon?>, IDeepCloneable public interface IPokemonParty : IReadOnlyList<IPokemon?>, IDeepCloneable
{ {
/// <summary>
/// Event that is triggered when a Pokemon is swapped into the party.
/// </summary>
event EventHandler<(IPokemon?, int index)>? OnSwapInto; event EventHandler<(IPokemon?, int index)>? OnSwapInto;
/// <summary>
/// Event that is triggered when two Pokemon are swapped in the party.
/// </summary>
event EventHandler<(int index1, int index2)>? OnSwap; event EventHandler<(int index1, int index2)>? OnSwap;
/// <summary> /// <summary>

View File

@ -129,22 +129,23 @@ public record SerializedLearnedMove
public required byte CurrentPp { get; set; } public required byte CurrentPp { get; set; }
} }
/// <summary>
/// A serialized stats is a representation of a Pokémon's stats that can be easily serialized and deserialized.
/// </summary>
public record SerializedStats public record SerializedStats
{ {
/// <inheritdoc cref="SerializedStats"/>
public SerializedStats() public SerializedStats()
{ {
} }
public SerializedStats(ImmutableStatisticSet<byte> stats) /// <inheritdoc cref="SerializedStats"/>
public SerializedStats(ImmutableStatisticSet<byte> stats) : this(stats.Hp, stats.Attack, stats.Defense,
stats.SpecialAttack, stats.SpecialDefense, stats.Speed)
{ {
Hp = stats.Hp;
Attack = stats.Attack;
Defense = stats.Defense;
SpecialAttack = stats.SpecialAttack;
SpecialDefense = stats.SpecialDefense;
Speed = stats.Speed;
} }
/// <inheritdoc cref="SerializedStats"/>
public SerializedStats(long hp, long attack, long defense, long specialAttack, long specialDefense, long speed) public SerializedStats(long hp, long attack, long defense, long specialAttack, long specialDefense, long speed)
{ {
Hp = hp; Hp = hp;
@ -155,13 +156,39 @@ public record SerializedStats
Speed = speed; Speed = speed;
} }
/// <summary>
/// The health points stat value.
/// </summary>
public long Hp { get; set; } public long Hp { get; set; }
/// <summary>
/// The physical attack stat value.
/// </summary>
public long Attack { get; set; } public long Attack { get; set; }
/// <summary>
/// The physical defense stat value.
/// </summary>
public long Defense { get; set; } public long Defense { get; set; }
/// <summary>
/// The special attack stat value.
/// </summary>
public long SpecialAttack { get; set; } public long SpecialAttack { get; set; }
/// <summary>
/// The special defense stat value.
/// </summary>
public long SpecialDefense { get; set; } public long SpecialDefense { get; set; }
/// <summary>
/// The speed stat value.
/// </summary>
public long Speed { get; set; } public long Speed { get; set; }
/// <summary>
/// Converts the serialized stats to an <see cref="IndividualValueStatisticSet"/>.
/// </summary>
public IndividualValueStatisticSet ToIndividualValueStatisticSet() public IndividualValueStatisticSet ToIndividualValueStatisticSet()
{ {
if (Hp < 0 || Attack < 0 || Defense < 0 || SpecialAttack < 0 || SpecialDefense < 0 || Speed < 0) if (Hp < 0 || Attack < 0 || Defense < 0 || SpecialAttack < 0 || SpecialDefense < 0 || Speed < 0)
@ -174,6 +201,10 @@ public record SerializedStats
(byte)SpecialDefense, (byte)Speed); (byte)SpecialDefense, (byte)Speed);
} }
/// <summary>
/// Converts the serialized stats to an <see cref="EffortValueStatisticSet"/>.
/// </summary>
/// <returns></returns>
public EffortValueStatisticSet ToEffortValueStatisticSet() public EffortValueStatisticSet ToEffortValueStatisticSet()
{ {
if (Hp < 0 || Attack < 0 || Defense < 0 || SpecialAttack < 0 || SpecialDefense < 0 || Speed < 0) if (Hp < 0 || Attack < 0 || Defense < 0 || SpecialAttack < 0 || SpecialDefense < 0 || Speed < 0)

View File

@ -1,6 +1,12 @@
namespace PkmnLib.Dynamic.ScriptHandling; namespace PkmnLib.Dynamic.ScriptHandling;
/// <summary>
/// Helper interface for weather scripts.
/// </summary>
public interface IWeatherScript public interface IWeatherScript
{ {
/// <summary>
/// Sets the number of turns the weather will last.
/// </summary>
public void SetTurns(int turns); public void SetTurns(int turns);
} }

View File

@ -4,13 +4,20 @@ using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.ScriptHandling; namespace PkmnLib.Dynamic.ScriptHandling;
/// <summary>
/// Base class for item scripts.
/// </summary>
public abstract class ItemScript : IDeepCloneable public abstract class ItemScript : IDeepCloneable
{ {
/// <inheritdoc cref="ItemScript"/>
protected ItemScript(IItem item) protected ItemScript(IItem item)
{ {
Item = item; Item = item;
} }
/// <summary>
/// The item associated with this script.
/// </summary>
protected IItem Item { get; private set; } protected IItem Item { get; private set; }
/// <summary> /// <summary>

View File

@ -3,6 +3,9 @@ using PkmnLib.Static;
namespace PkmnLib.Dynamic.ScriptHandling; namespace PkmnLib.Dynamic.ScriptHandling;
/// <summary>
/// Base class for Pokéball scripts.
/// </summary>
public abstract class PokeballScript : ItemScript public abstract class PokeballScript : ItemScript
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -10,6 +13,9 @@ public abstract class PokeballScript : ItemScript
{ {
} }
/// <summary>
/// Returns the catch rate of the Pokéball against the given target Pokémon.
/// </summary>
public abstract byte GetCatchRate(IPokemon target); public abstract byte GetCatchRate(IPokemon target);
/// <inheritdoc /> /// <inheritdoc />

View File

@ -3,6 +3,9 @@ using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.ScriptHandling.Registry; namespace PkmnLib.Dynamic.ScriptHandling.Registry;
/// <summary>
/// Attribute to mark a class as an item script.
/// </summary>
[AttributeUsage(AttributeTargets.Class), MeansImplicitUse] [AttributeUsage(AttributeTargets.Class), MeansImplicitUse]
public class ItemScriptAttribute : Attribute public class ItemScriptAttribute : Attribute
{ {

View File

@ -8,10 +8,12 @@ namespace PkmnLib.Dynamic.ScriptHandling.Registry;
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)] [UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
public abstract class Plugin public abstract class Plugin
{ {
/// <inheritdoc cref="Plugin"/>
protected Plugin() protected Plugin()
{ {
} }
/// <inheritdoc cref="Plugin"/>
protected Plugin(PluginConfiguration configuration) protected Plugin(PluginConfiguration configuration)
{ {
} }
@ -33,6 +35,9 @@ public abstract class Plugin
public abstract void Register(ScriptRegistry registry); public abstract void Register(ScriptRegistry registry);
} }
/// <summary>
/// Base class for plugin configuration.
/// </summary>
public abstract class PluginConfiguration public abstract class PluginConfiguration
{ {
} }

View File

@ -18,6 +18,9 @@ public abstract class Script : IDeepCloneable
private int _suppressCount; private int _suppressCount;
/// <summary>
/// Remove the script from its owner.
/// </summary>
public void RemoveSelf() public void RemoveSelf()
{ {
OnRemoveEvent?.Invoke(this); OnRemoveEvent?.Invoke(this);
@ -62,6 +65,10 @@ public abstract class Script : IDeepCloneable
/// </summary> /// </summary>
public void Unsuppress() => _suppressCount--; public void Unsuppress() => _suppressCount--;
/// <summary>
/// This function is ran before any hook is invoked. This allows for suppressing certain categories
/// of scripts. This is useful for example to prevent certain effects from running.
/// </summary>
public virtual void OnBeforeAnyHookInvoked(ref List<ScriptCategory>? suppressedCategories) public virtual void OnBeforeAnyHookInvoked(ref List<ScriptCategory>? suppressedCategories)
{ {
} }
@ -95,6 +102,10 @@ public abstract class Script : IDeepCloneable
{ {
} }
/// <summary>
/// Force a certain move choice to be selected. If the choice is set, the Pokemon will be forced
/// to use it, and will not be able to select any other choice.
/// </summary>
public virtual void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice) public virtual void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice)
{ {
} }
@ -140,6 +151,9 @@ public abstract class Script : IDeepCloneable
{ {
} }
/// <summary>
/// This function allows you to change the targets of a move choice before the move starts.
/// </summary>
public virtual void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets) public virtual void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
{ {
} }
@ -230,6 +244,9 @@ public abstract class Script : IDeepCloneable
{ {
} }
/// <summary>
/// This function allows the script to override how effective a move is on a target.
/// </summary>
public virtual void ChangeIncomingEffectiveness(IExecutingMove executingMove, IPokemon target, byte hitIndex, public virtual void ChangeIncomingEffectiveness(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref float effectiveness) ref float effectiveness)
{ {
@ -504,6 +521,10 @@ public abstract class Script : IDeepCloneable
{ {
} }
/// <summary>
/// This function is triggered on a Pokemon and its parents when the given Pokemon switches out
/// of the battlefield.
/// </summary>
public virtual void OnSwitchOut(IPokemon oldPokemon, byte position) public virtual void OnSwitchOut(IPokemon oldPokemon, byte position)
{ {
} }
@ -559,10 +580,16 @@ public abstract class Script : IDeepCloneable
{ {
} }
/// <summary>
/// This function allows a script to block an incoming hit.
/// </summary>
public virtual void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) public virtual void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{ {
} }
/// <summary>
/// This function allows a script to block an outgoing hit.
/// </summary>
public virtual void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) public virtual void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{ {
} }
@ -582,23 +609,44 @@ public abstract class Script : IDeepCloneable
{ {
} }
/// <summary>
/// This function allows a script to prevent a held item from being consumed.
/// </summary>
public virtual void PreventHeldItemConsume(IPokemon pokemon, IItem heldItem, ref bool prevented) public virtual void PreventHeldItemConsume(IPokemon pokemon, IItem heldItem, ref bool prevented)
{ {
} }
/// <summary>
/// This function allows a script to change any kind of damage that is incoming.
/// </summary>
public virtual void ChangeIncomingDamage(IPokemon pokemon, DamageSource source, ref uint damage) public virtual void ChangeIncomingDamage(IPokemon pokemon, DamageSource source, ref uint damage)
{ {
} }
/// <summary>
/// This function allows a script to change the accuracy of a move used. The value for accuracy is in percentage.
/// A custom case goes when 255 is returned, in which case the entire accuracy check is skipped, and the move
/// will always hit.
/// </summary>
/// <param name="executingMove"></param>
/// <param name="target"></param>
/// <param name="hitIndex"></param>
/// <param name="modifiedAccuracy"></param>
public virtual void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, public virtual void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref int modifiedAccuracy) ref int modifiedAccuracy)
{ {
} }
/// <summary>
/// This function allows a script to change the weather duration of a weather effect.
/// </summary>
public virtual void ChangeWeatherDuration(StringKey weatherName, ref int duration) public virtual void ChangeWeatherDuration(StringKey weatherName, ref int duration)
{ {
} }
/// <summary>
/// This function allows a script to prevent a Pokemon from being healed.
/// </summary>
public virtual void PreventHeal(IPokemon pokemon, uint heal, bool allowRevive, ref bool prevented) public virtual void PreventHeal(IPokemon pokemon, uint heal, bool allowRevive, ref bool prevented)
{ {
} }

View File

@ -55,6 +55,9 @@ public enum ScriptCategory
/// </summary> /// </summary>
Weather = 7, Weather = 7,
/// <summary>
/// A special script for terrain, for use on battles.
/// </summary>
Terrain = 8, Terrain = 8,
/// <summary> /// <summary>

View File

@ -4,6 +4,10 @@ using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.ScriptHandling; namespace PkmnLib.Dynamic.ScriptHandling;
/// <summary>
/// A holder class for a script. This is used so we can cache a list of these, and iterate over them, even when
/// the underlying script changes.
/// </summary>
public interface IReadOnlyScriptContainer : IEnumerable<ScriptContainer>, IDeepCloneable public interface IReadOnlyScriptContainer : IEnumerable<ScriptContainer>, IDeepCloneable
{ {
/// <summary> /// <summary>
@ -17,10 +21,7 @@ public interface IReadOnlyScriptContainer : IEnumerable<ScriptContainer>, IDeepC
public Script? Script { get; } public Script? Script { get; }
} }
/// <summary> /// <inheritdoc cref="IReadOnlyScriptContainer"/>
/// A holder class for a script. This is used so we can cache a list of these, and iterate over them, even when
/// the underlying script changes.
/// </summary>
public class ScriptContainer : IReadOnlyScriptContainer public class ScriptContainer : IReadOnlyScriptContainer
{ {
/// <inheritdoc cref="ScriptContainer"/> /// <inheritdoc cref="ScriptContainer"/>
@ -73,6 +74,11 @@ public class ScriptContainer : IReadOnlyScriptContainer
return script; return script;
} }
/// <summary>
/// Removes the script from this container, but does not call <see cref="Script.OnRemove"/>.
/// Be very careful with this, as it can lead to unexpected behavior. An example of a valid use is Baton-Pass,
/// where scripts are being removed to be added to another Pokemon, so we want them to remain active.
/// </summary>
public void ClearWithoutRemoving() public void ClearWithoutRemoving()
{ {
Script = null; Script = null;

View File

@ -64,6 +64,9 @@ public static class ScriptExecution
} }
} }
/// <summary>
/// Executes a script on an item.
/// </summary>
public static void RunItemScript(this IItem item, ScriptResolver scriptResolver, IPokemon? target) public static void RunItemScript(this IItem item, ScriptResolver scriptResolver, IPokemon? target)
{ {
if (!scriptResolver.TryResolveBattleItemScript(item, out var itemScript)) if (!scriptResolver.TryResolveBattleItemScript(item, out var itemScript))

View File

@ -106,11 +106,25 @@ public interface IItem : INamedValue
/// </summary> /// </summary>
ImmutableHashSet<StringKey> Flags { get; } ImmutableHashSet<StringKey> Flags { get; }
/// <summary>
/// The effect of the item when used outside of battle.
/// </summary>
ISecondaryEffect? Effect { get; } ISecondaryEffect? Effect { get; }
/// <summary>
/// The effect of the item when used in battle.
/// </summary>
ISecondaryEffect? BattleEffect { get; } ISecondaryEffect? BattleEffect { get; }
/// <summary>
/// A set of arbitrary data that can be set on the item.
/// </summary>
IReadOnlyDictionary<StringKey, object?> AdditionalData { get; } IReadOnlyDictionary<StringKey, object?> AdditionalData { get; }
/// <summary>
/// Tries to get additional data from the item. If the data is not present, the value will be null.
/// If the data is present, but cannot be converted to the requested type, the value will be null.
/// </summary>
bool TryGetAdditionalData<T>(StringKey key, out T? value); bool TryGetAdditionalData<T>(StringKey key, out T? value);
/// <summary> /// <summary>

View File

@ -28,6 +28,9 @@ public interface IReadOnlyTypeLibrary
/// </summary> /// </summary>
float GetEffectiveness(TypeIdentifier attacking, IEnumerable<TypeIdentifier> defending); float GetEffectiveness(TypeIdentifier attacking, IEnumerable<TypeIdentifier> defending);
/// <summary>
/// Gets the effectiveness for a single attacking type against all defending types.
/// </summary>
IEnumerable<(TypeIdentifier type, float effectiveness)> GetAllEffectivenessFromAttacking(TypeIdentifier attacking); IEnumerable<(TypeIdentifier type, float effectiveness)> GetAllEffectivenessFromAttacking(TypeIdentifier attacking);
} }

View File

@ -19,6 +19,9 @@ public interface IAbility : INamedValue
/// </summary> /// </summary>
IReadOnlyDictionary<StringKey, object?> Parameters { get; } IReadOnlyDictionary<StringKey, object?> Parameters { get; }
/// <summary>
/// Checks whether the ability has a specific flag.
/// </summary>
bool HasFlag(StringKey key); bool HasFlag(StringKey key);
} }
@ -44,6 +47,9 @@ public class AbilityImpl : IAbility
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyDictionary<StringKey, object?> Parameters { get; } public IReadOnlyDictionary<StringKey, object?> Parameters { get; }
/// <summary>
/// A collection of arbitrary flags that can be used to mark the ability with specific properties.
/// </summary>
public ImmutableHashSet<StringKey> Flags; public ImmutableHashSet<StringKey> Flags;
/// <inheritdoc /> /// <inheritdoc />

View File

@ -51,6 +51,7 @@ public record ImmutableStatisticSet<T> where T : struct
Speed = speed; Speed = speed;
} }
/// <inheritdoc cref="ImmutableStatisticSet{T}"/>
public ImmutableStatisticSet(ImmutableStatisticSet<T> set) public ImmutableStatisticSet(ImmutableStatisticSet<T> set)
{ {
Hp = set.Hp; Hp = set.Hp;
@ -97,6 +98,7 @@ public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<(Statistic
{ {
} }
/// <inheritdoc cref="StatisticSet{T}"/>
public StatisticSet(StatisticSet<T> set) : base(set) public StatisticSet(StatisticSet<T> set) : base(set)
{ {
} }
@ -174,8 +176,16 @@ public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<(Statistic
return true; return true;
} }
/// <summary>
/// Gets a statistic that is not one of the standard statistics. This can be used for sets where there are
/// additional statistics, such as evasion or accuracy.
/// </summary>
protected virtual T GetUnknownStat(Statistic stat) => throw new ArgumentException($"Invalid statistic {stat}"); protected virtual T GetUnknownStat(Statistic stat) => throw new ArgumentException($"Invalid statistic {stat}");
/// <summary>
/// Sets a statistic that is not one of the standard statistics. This can be used for sets where there are
/// additional statistics, such as evasion or accuracy.
/// </summary>
protected virtual void SetUnknownStat(Statistic stat, T value) protected virtual void SetUnknownStat(Statistic stat, T value)
{ {
throw new ArgumentException($"Invalid statistic {stat}"); throw new ArgumentException($"Invalid statistic {stat}");
@ -249,10 +259,14 @@ public abstract record ClampedStatisticSet<T> : StatisticSet<T> where T : struct
Speed = Clamp(Speed, Min, Max); Speed = Clamp(Speed, Min, Max);
} }
/// <inheritdoc cref="ClampedStatisticSet{T}"/>
protected ClampedStatisticSet(ClampedStatisticSet<T> set) : base(set) protected ClampedStatisticSet(ClampedStatisticSet<T> set) : base(set)
{ {
} }
/// <summary>
/// Clamps a value to be between the minimum and maximum values.
/// </summary>
protected static T Clamp(T value, T min, T max) protected static T Clamp(T value, T min, T max)
{ {
if (value.CompareTo(min) < 0) if (value.CompareTo(min) < 0)
@ -314,6 +328,9 @@ public record StatBoostStatisticSet : ClampedStatisticSet<sbyte>
private sbyte _evasion; private sbyte _evasion;
/// <summary>
/// The evasion stat value.
/// </summary>
public sbyte Evasion public sbyte Evasion
{ {
get => _evasion; get => _evasion;
@ -322,6 +339,9 @@ public record StatBoostStatisticSet : ClampedStatisticSet<sbyte>
private sbyte _accuracy; private sbyte _accuracy;
/// <summary>
/// The accuracy stat value.
/// </summary>
public sbyte Accuracy public sbyte Accuracy
{ {
get => _accuracy; get => _accuracy;
@ -403,6 +423,7 @@ public record IndividualValueStatisticSet : ClampedStatisticSet<byte>
{ {
} }
/// <inheritdoc cref="IndividualValueStatisticSet"/>
public IndividualValueStatisticSet(IndividualValueStatisticSet ivs) : base(ivs) public IndividualValueStatisticSet(IndividualValueStatisticSet ivs) : base(ivs)
{ {
} }
@ -430,6 +451,7 @@ public record EffortValueStatisticSet : ClampedStatisticSet<byte>
{ {
} }
/// <inheritdoc cref="EffortValueStatisticSet"/>
public EffortValueStatisticSet(EffortValueStatisticSet evs) : base(evs) public EffortValueStatisticSet(EffortValueStatisticSet evs) : base(evs)
{ {
} }

View File

@ -1,12 +1,14 @@
namespace PkmnLib.Static.Utils; namespace PkmnLib.Static.Utils;
/// <summary>
/// Helpers for working with dictionaries.
/// </summary>
public static class DictionaryHelpers public static class DictionaryHelpers
{ {
/// <summary>
/// Gets the value for a key in a dictionary, or returns a default value if the key is not found.
/// </summary>
public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key,
TValue defaultValue) TValue defaultValue) =>
{ dictionary.TryGetValue(key, out var value) ? value : defaultValue;
if (dictionary.TryGetValue(key, out var value))
return value;
return defaultValue;
}
} }

View File

@ -34,6 +34,9 @@ public static class EnumerableHelpers
return -1; return -1;
} }
/// <summary>
/// Removes all elements from a list that match the given predicate.
/// </summary>
public static void RemoveAll<T>(this IList<T> list, Func<T, bool> predicate) public static void RemoveAll<T>(this IList<T> list, Func<T, bool> predicate)
{ {
for (var i = list.Count - 1; i >= 0; i--) for (var i = list.Count - 1; i >= 0; i--)

View File

@ -50,12 +50,18 @@ public static class NumericHelpers
return result > short.MaxValue ? short.MaxValue : (short)result; return result > short.MaxValue ? short.MaxValue : (short)result;
} }
/// <summary>
/// Multiplies two values. If this overflows, returns <see cref="uint.MaxValue"/>.
/// </summary>
public static uint MultiplyOrMax(this uint value, uint multiplier) public static uint MultiplyOrMax(this uint value, uint multiplier)
{ {
var result = (ulong)value * multiplier; var result = (ulong)value * multiplier;
return result > uint.MaxValue ? uint.MaxValue : (uint)result; return result > uint.MaxValue ? uint.MaxValue : (uint)result;
} }
/// <summary>
/// Multiplies two values. If this overflows, returns <see cref="uint.MaxValue"/>.
/// </summary>
public static uint MultiplyOrMax(this uint value, float multiplier) public static uint MultiplyOrMax(this uint value, float multiplier)
{ {
var result = value * multiplier; var result = value * multiplier;

View File

@ -4,9 +4,14 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
<NoWarn>
<!-- Don't show warning to make method static for our tests. -->
CA1822
</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>