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.Choices;
using PkmnLib.Static.Moves;
@ -8,12 +9,13 @@ namespace PkmnLib.Dynamic.AI;
/// <summary>
/// The base class for implementing an AI for Pokémon.
/// </summary>
[PublicAPI]
public abstract class PokemonAI
{
/// <summary>
/// The name of the AI.
/// </summary>
public StringKey Name { get; set; }
public StringKey Name { get; }
/// <inheritdoc cref="PokemonAI" />
protected PokemonAI(StringKey name)
@ -26,9 +28,11 @@ public abstract class PokemonAI
/// </summary>
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)
{
byte GetOppositeSide(byte side) => side == 0 ? (byte)1 : (byte)0;
var userBattleData = user.BattleData!;
switch (move.MoveData.Target)
{
@ -90,5 +94,7 @@ public abstract class PokemonAI
default:
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;
/// <summary>
/// Represents an event that occurs when a Pokémon capture attempt is made.
/// </summary>
public class CaptureAttemptEvent : IEventData
{
/// <inheritdoc cref="CaptureAttemptEvent"/>
public CaptureAttemptEvent(IPokemon target, CaptureResult result)
{
Target = target;
Result = result;
}
/// <summary>
/// The Pokémon that is being captured.
/// </summary>
public IPokemon Target { get; init; }
/// <summary>
/// The result of the capture attempt.
/// </summary>
public CaptureResult Result { get; init; }
/// <inheritdoc />

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
using JetBrains.Annotations;
namespace PkmnLib.Dynamic.Events;
/// <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
/// know what is happening in the battle.
/// </summary>
[PublicAPI]
public interface IEventData
{
/// <summary>

View File

@ -2,8 +2,12 @@ using PkmnLib.Dynamic.Models;
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
{
/// <inheritdoc cref="LevelUpEvent"/>
public LevelUpEvent(IPokemon pokemon, int previousLevel, int newLevel)
{
Pokemon = pokemon;
@ -11,10 +15,19 @@ public class LevelUpEvent : IEventData
NewLevel = newLevel;
}
/// <summary>
/// The new level of the Pokémon after leveling up.
/// </summary>
public int NewLevel { get; set; }
/// <summary>
/// The previous level of the Pokémon before leveling up.
/// </summary>
public int PreviousLevel { get; set; }
/// <summary>
/// The Pokémon that leveled up.
/// </summary>
public IPokemon Pokemon { get; set; }
/// <inheritdoc />

View File

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

View File

@ -3,8 +3,12 @@ using PkmnLib.Static;
namespace PkmnLib.Dynamic.Libraries;
/// <summary>
/// Represents the result of a capture attempt.
/// </summary>
public record struct CaptureResult
{
/// <inheritdoc cref="CaptureResult"/>
public CaptureResult(bool IsCaught, int Shakes, bool CriticalCapture)
{
this.IsCaught = IsCaught;
@ -12,14 +16,35 @@ public record struct CaptureResult
this.CriticalCapture = CriticalCapture;
}
/// <summary>
/// Indicates whether the capture was successful.
/// </summary>
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; }
/// <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; }
/// <summary>
/// Creates a <see cref="CaptureResult"/> indicating a failed capture attempt.
/// </summary>
public static CaptureResult Failed => new(false, 0, false);
}
/// <summary>
/// Interface for a library that handles Pokémon capture mechanics.
/// </summary>
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);
}

View File

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

View File

@ -93,6 +93,10 @@ public interface IBattle : IScriptSource, IDeepCloneable
/// </summary>
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);
/// <summary>
@ -117,6 +121,9 @@ public interface IBattle : IScriptSource, IDeepCloneable
/// </summary>
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; }
/// <summary>
@ -124,8 +131,15 @@ public interface IBattle : IScriptSource, IDeepCloneable
/// </summary>
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);
/// <summary>
/// Gets the current terrain of the battle. If no terrain is present, this returns null.
/// </summary>
StringKey? TerrainName { get; }
/// <summary>
@ -134,6 +148,9 @@ public interface IBattle : IScriptSource, IDeepCloneable
/// </summary>
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);
}
@ -350,7 +367,11 @@ public class BattleImpl : ScriptSource, IBattle
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;
/// <inheritdoc />
@ -377,6 +398,7 @@ public class BattleImpl : ScriptSource, IBattle
// TODO: Trigger weather change script hooks
}
/// <inheritdoc />
public IScriptSet Volatile { get; } = new ScriptSet();
/// <inheritdoc />

View File

@ -16,6 +16,10 @@ public class BattleChoiceQueue : IDeepCloneable
{
private readonly ITurnChoice?[] _choices;
private int _currentIndex;
/// <summary>
/// Returns the last choice that was executed.
/// </summary>
public ITurnChoice? LastRanChoice { get; private set; }
/// <inheritdoc cref="BattleChoiceQueue"/>
@ -122,9 +126,15 @@ public class BattleChoiceQueue : IDeepCloneable
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) =>
_choices.Skip(_currentIndex).WhereNotNull().FirstOrDefault(predicate);
/// <summary>
/// Removes a choice from the queue.
/// </summary>
public void Remove(ITurnChoice choice)
{
var index = Array.FindIndex(_choices, _currentIndex, x => x == choice);

View File

@ -19,10 +19,18 @@ public interface IBattleRandom : IRandom, IDeepCloneable
/// <inheritdoc cref="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()
{
}
/// <inheritdoc cref="BattleRandomImpl"/>
/// <remarks>
/// This constructor is used to instantiate the class with a specific seed value.
/// </remarks>
public BattleRandomImpl(int seed) : base(seed)
{
}

View File

@ -142,9 +142,19 @@ public interface IBattleSide : IScriptSource, IDeepCloneable
/// </summary>
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);
/// <summary>
/// Gets the last turn a Pokémon in a specific position fainted.
/// </summary>
uint? GetLastFaintTurn(byte position);
/// <summary>
/// Gets the last turn a Pokémon in any position fainted.
/// </summary>
uint? GetLastFaintTurn();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -202,6 +202,9 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// </summary>
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();
/// <summary>
@ -395,6 +398,9 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// </summary>
void SetTypes(IReadOnlyList<TypeIdentifier> types);
/// <summary>
/// Changes the ability of the Pokémon.
/// </summary>
void ChangeAbility(IAbility ability);
/// <summary>
@ -449,8 +455,14 @@ public interface IPokemonBattleData : IDeepCloneable
/// </summary>
void MarkItemAsConsumed(IItem item);
/// <summary>
/// The turn the Pokémon switched in.
/// </summary>
uint SwitchInTurn { get; internal set; }
/// <summary>
/// The side the Pokémon is on.
/// </summary>
IBattleSide BattleSide { get; }
}
@ -483,6 +495,7 @@ public class PokemonImpl : ScriptSource, IPokemon
CurrentHealth = BoostedStats.Hp;
}
/// <inheritdoc cref="PokemonImpl"/>
public PokemonImpl(IDynamicLibrary library, SerializedPokemon serializedPokemon)
{
Library = library;

View File

@ -8,7 +8,14 @@ namespace PkmnLib.Dynamic.Models;
/// </summary>
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;
/// <summary>
/// Event that is triggered when two Pokemon are swapped in the party.
/// </summary>
event EventHandler<(int index1, int index2)>? OnSwap;
/// <summary>

View File

@ -129,22 +129,23 @@ public record SerializedLearnedMove
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
{
/// <inheritdoc cref="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)
{
Hp = hp;
@ -155,13 +156,39 @@ public record SerializedStats
Speed = speed;
}
/// <summary>
/// The health points stat value.
/// </summary>
public long Hp { get; set; }
/// <summary>
/// The physical attack stat value.
/// </summary>
public long Attack { get; set; }
/// <summary>
/// The physical defense stat value.
/// </summary>
public long Defense { get; set; }
/// <summary>
/// The special attack stat value.
/// </summary>
public long SpecialAttack { get; set; }
/// <summary>
/// The special defense stat value.
/// </summary>
public long SpecialDefense { get; set; }
/// <summary>
/// The speed stat value.
/// </summary>
public long Speed { get; set; }
/// <summary>
/// Converts the serialized stats to an <see cref="IndividualValueStatisticSet"/>.
/// </summary>
public IndividualValueStatisticSet ToIndividualValueStatisticSet()
{
if (Hp < 0 || Attack < 0 || Defense < 0 || SpecialAttack < 0 || SpecialDefense < 0 || Speed < 0)
@ -174,6 +201,10 @@ public record SerializedStats
(byte)SpecialDefense, (byte)Speed);
}
/// <summary>
/// Converts the serialized stats to an <see cref="EffortValueStatisticSet"/>.
/// </summary>
/// <returns></returns>
public EffortValueStatisticSet ToEffortValueStatisticSet()
{
if (Hp < 0 || Attack < 0 || Defense < 0 || SpecialAttack < 0 || SpecialDefense < 0 || Speed < 0)

View File

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

View File

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

View File

@ -3,6 +3,9 @@ using PkmnLib.Static;
namespace PkmnLib.Dynamic.ScriptHandling;
/// <summary>
/// Base class for Pokéball scripts.
/// </summary>
public abstract class PokeballScript : ItemScript
{
/// <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);
/// <inheritdoc />

View File

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

View File

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

View File

@ -18,6 +18,9 @@ public abstract class Script : IDeepCloneable
private int _suppressCount;
/// <summary>
/// Remove the script from its owner.
/// </summary>
public void RemoveSelf()
{
OnRemoveEvent?.Invoke(this);
@ -62,6 +65,10 @@ public abstract class Script : IDeepCloneable
/// </summary>
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)
{
}
@ -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)
{
}
@ -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)
{
}
@ -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,
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)
{
}
@ -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)
{
}
/// <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)
{
}
@ -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)
{
}
/// <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)
{
}
/// <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,
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)
{
}
/// <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)
{
}

View File

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

View File

@ -4,6 +4,10 @@ using PkmnLib.Static.Utils;
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
{
/// <summary>
@ -17,10 +21,7 @@ public interface IReadOnlyScriptContainer : IEnumerable<ScriptContainer>, IDeepC
public Script? Script { get; }
}
/// <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>
/// <inheritdoc cref="IReadOnlyScriptContainer"/>
public class ScriptContainer : IReadOnlyScriptContainer
{
/// <inheritdoc cref="ScriptContainer"/>
@ -73,6 +74,11 @@ public class ScriptContainer : IReadOnlyScriptContainer
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()
{
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)
{
if (!scriptResolver.TryResolveBattleItemScript(item, out var itemScript))

View File

@ -106,11 +106,25 @@ public interface IItem : INamedValue
/// </summary>
ImmutableHashSet<StringKey> Flags { get; }
/// <summary>
/// The effect of the item when used outside of battle.
/// </summary>
ISecondaryEffect? Effect { get; }
/// <summary>
/// The effect of the item when used in battle.
/// </summary>
ISecondaryEffect? BattleEffect { get; }
/// <summary>
/// A set of arbitrary data that can be set on the item.
/// </summary>
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);
/// <summary>

View File

@ -28,6 +28,9 @@ public interface IReadOnlyTypeLibrary
/// </summary>
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);
}

View File

@ -19,6 +19,9 @@ public interface IAbility : INamedValue
/// </summary>
IReadOnlyDictionary<StringKey, object?> Parameters { get; }
/// <summary>
/// Checks whether the ability has a specific flag.
/// </summary>
bool HasFlag(StringKey key);
}
@ -44,6 +47,9 @@ public class AbilityImpl : IAbility
/// <inheritdoc />
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;
/// <inheritdoc />

View File

@ -51,6 +51,7 @@ public record ImmutableStatisticSet<T> where T : struct
Speed = speed;
}
/// <inheritdoc cref="ImmutableStatisticSet{T}"/>
public ImmutableStatisticSet(ImmutableStatisticSet<T> set)
{
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)
{
}
@ -174,8 +176,16 @@ public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<(Statistic
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}");
/// <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)
{
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);
}
/// <inheritdoc cref="ClampedStatisticSet{T}"/>
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)
{
if (value.CompareTo(min) < 0)
@ -314,6 +328,9 @@ public record StatBoostStatisticSet : ClampedStatisticSet<sbyte>
private sbyte _evasion;
/// <summary>
/// The evasion stat value.
/// </summary>
public sbyte Evasion
{
get => _evasion;
@ -322,6 +339,9 @@ public record StatBoostStatisticSet : ClampedStatisticSet<sbyte>
private sbyte _accuracy;
/// <summary>
/// The accuracy stat value.
/// </summary>
public sbyte Accuracy
{
get => _accuracy;
@ -403,6 +423,7 @@ public record IndividualValueStatisticSet : ClampedStatisticSet<byte>
{
}
/// <inheritdoc cref="IndividualValueStatisticSet"/>
public IndividualValueStatisticSet(IndividualValueStatisticSet ivs) : base(ivs)
{
}
@ -430,6 +451,7 @@ public record EffortValueStatisticSet : ClampedStatisticSet<byte>
{
}
/// <inheritdoc cref="EffortValueStatisticSet"/>
public EffortValueStatisticSet(EffortValueStatisticSet evs) : base(evs)
{
}

View File

@ -1,12 +1,14 @@
namespace PkmnLib.Static.Utils;
/// <summary>
/// Helpers for working with dictionaries.
/// </summary>
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,
TValue defaultValue)
{
if (dictionary.TryGetValue(key, out var value))
return value;
return defaultValue;
}
TValue defaultValue) =>
dictionary.TryGetValue(key, out var value) ? value : defaultValue;
}

View File

@ -34,6 +34,9 @@ public static class EnumerableHelpers
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)
{
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;
}
/// <summary>
/// Multiplies two values. If this overflows, returns <see cref="uint.MaxValue"/>.
/// </summary>
public static uint MultiplyOrMax(this uint value, uint multiplier)
{
var result = (ulong)value * multiplier;
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)
{
var result = value * multiplier;

View File

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