Initial commit
This commit is contained in:
commit
3845f91601
|
@ -0,0 +1,7 @@
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
/packages/
|
||||||
|
riderModule.iml
|
||||||
|
/_ReSharper.Caches/
|
||||||
|
.idea
|
||||||
|
*.DotSettings.user
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Static", "PkmnLib.Static\PkmnLib.Static.csproj", "{312782DA-1066-4490-BD0E-DF4DF8713B4A}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Tests", "PkmnLib.Tests\PkmnLib.Tests.csproj", "{42DE3095-0468-4827-AF5C-691C94BA7F92}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{312782DA-1066-4490-BD0E-DF4DF8713B4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{312782DA-1066-4490-BD0E-DF4DF8713B4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{312782DA-1066-4490-BD0E-DF4DF8713B4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{312782DA-1066-4490-BD0E-DF4DF8713B4A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{42DE3095-0468-4827-AF5C-691C94BA7F92}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1 @@
|
||||||
|
global using LevelInt = byte;
|
|
@ -0,0 +1,81 @@
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A growth rate defines how much experience is required per level.
|
||||||
|
/// </summary>
|
||||||
|
public interface IGrowthRate
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the growth rate.
|
||||||
|
/// </summary>
|
||||||
|
public StringKey Name { get; }
|
||||||
|
|
||||||
|
///<summary>
|
||||||
|
/// Calculate the level something with this growth rate would have at a certain experience.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="experience">The experience to calculate the level for.</param>
|
||||||
|
/// <returns>The level at the given experience.</returns>
|
||||||
|
LevelInt CalculateLevel(uint experience);
|
||||||
|
|
||||||
|
///<summary>
|
||||||
|
/// Calculate the experience something with this growth rate would have at a certain level.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level to calculate the experience for.</param>
|
||||||
|
/// <returns>The starting experience at the given level.</returns>
|
||||||
|
uint CalculateExperience(LevelInt level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An implementation of the growth rate that uses a lookup table for experience.
|
||||||
|
/// </summary>
|
||||||
|
public class LookupGrowthRate : IGrowthRate
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public StringKey Name { get; }
|
||||||
|
|
||||||
|
private readonly uint[] _experienceTable;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="LookupGrowthRate" />
|
||||||
|
public LookupGrowthRate(StringKey name, IEnumerable<uint> experienceTable)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
_experienceTable = experienceTable.ToArray();
|
||||||
|
if (_experienceTable.Length < 1)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Experience table must have at least one entry.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_experienceTable[0] != 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Experience table must start at 0.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_experienceTable.Length > LevelInt.MaxValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Experience table may have at most {LevelInt.MaxValue} entries.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public LevelInt CalculateLevel(uint experience)
|
||||||
|
{
|
||||||
|
for (LevelInt level = 0; level < _experienceTable.Length; level++)
|
||||||
|
{
|
||||||
|
if (_experienceTable[level] > experience)
|
||||||
|
{
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (LevelInt)(_experienceTable.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public uint CalculateExperience(LevelInt level)
|
||||||
|
{
|
||||||
|
if (level < 1) level = 1;
|
||||||
|
return level >= _experienceTable.Length ? _experienceTable[^1] : _experienceTable[level - 1];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An item category defines which bag slot items are stored in.
|
||||||
|
/// </summary>
|
||||||
|
public enum ItemCategory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is where most items should go.
|
||||||
|
/// </summary>
|
||||||
|
MiscItem,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pokeballs are used for capturing Pokemons.
|
||||||
|
/// </summary>
|
||||||
|
Pokeball,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Medicine is used for healing HP, PP, and status effects.
|
||||||
|
/// </summary>
|
||||||
|
Medicine,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Berry is used for all berries.
|
||||||
|
/// </summary>
|
||||||
|
Berry,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TMHM is used for Technical and Hidden Machines.
|
||||||
|
/// </summary>
|
||||||
|
TmHm,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Form Changer is used for items that change forms, such as mega stones.
|
||||||
|
/// </summary>
|
||||||
|
FormChanger,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key Items are single stored items, generally used for story progression.
|
||||||
|
/// </summary>
|
||||||
|
KeyItem,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mail is used for mail items.
|
||||||
|
/// </summary>
|
||||||
|
Mail,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A battle item category defines how the item is categorized when in battle.
|
||||||
|
/// </summary>
|
||||||
|
public enum BattleItemCategory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This item can't be used in battle.
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This item is used for healing Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
Healing,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This item is used for healing Pokemon from a status.
|
||||||
|
/// </summary>
|
||||||
|
StatusHealing,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This item is used for capturing Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
Pokeball,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This item does not belong in above categories, but is still a battle item.
|
||||||
|
/// </summary>
|
||||||
|
MiscBattleItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An item is an object which the player can pick up, keep in their Bag, and use in some manner.
|
||||||
|
/// </summary>
|
||||||
|
public interface IItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the item.
|
||||||
|
/// </summary>
|
||||||
|
StringKey Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Which bag slot items are stored in.
|
||||||
|
/// </summary>
|
||||||
|
ItemCategory Category { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How the item is categorized when in battle.
|
||||||
|
/// </summary>
|
||||||
|
BattleItemCategory BattleCategory { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buying value of the item.
|
||||||
|
/// </summary>
|
||||||
|
int Price { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A set of arbitrary flags that can be set on the item.
|
||||||
|
/// </summary>
|
||||||
|
ImmutableHashSet<StringKey> Flags { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the item has a specific flag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The flag to check for.</param>
|
||||||
|
/// <returns>True if the item has the flag, false otherwise.</returns>
|
||||||
|
bool HasFlag(string key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class ItemImpl : IItem
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="ItemImpl"/>
|
||||||
|
public ItemImpl(StringKey name, ItemCategory category, BattleItemCategory battleCategory, int price,
|
||||||
|
IEnumerable<StringKey> flags)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Category = category;
|
||||||
|
BattleCategory = battleCategory;
|
||||||
|
Price = price;
|
||||||
|
Flags = [..flags];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public StringKey Name { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ItemCategory Category { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public BattleItemCategory BattleCategory { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int Price { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ImmutableHashSet<StringKey> Flags { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool HasFlag(string key)
|
||||||
|
{
|
||||||
|
return Flags.Contains(key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static.Moves;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The move category defines what global kind of move this move is.
|
||||||
|
/// </summary>
|
||||||
|
public enum MoveCategory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A physical move uses the physical attack stats and physical defense stats to calculate damage.
|
||||||
|
/// </summary>
|
||||||
|
Physical = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A special move uses the special attack stats and special defense stats to calculate damage.
|
||||||
|
/// </summary>
|
||||||
|
Special = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A status move does not do damage, and only runs a secondary effect.
|
||||||
|
/// </summary>
|
||||||
|
Status = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The move target defines what kind of targets the move can touch.
|
||||||
|
/// </summary>
|
||||||
|
public enum MoveTarget
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adjacent allows a move to target any Pokemon that is either directly to the left or right of
|
||||||
|
/// the user, opposed to the user, or left or right of the slot that is opposing the user.
|
||||||
|
/// </summary>
|
||||||
|
Adjacent = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AdjacentAlly allows a move to target any Pokemon that is directly to the left or right of
|
||||||
|
/// the user.
|
||||||
|
/// </summary>
|
||||||
|
AdjacentAlly,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AdjacentAllySelf allows a move to target any Pokemon that is either directly to the left or
|
||||||
|
/// right of the user, or the user itself.
|
||||||
|
/// </summary>
|
||||||
|
AdjacentAllySelf,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AdjacentOpponent allows a move to target any Pokemon that is either the opponent, or directly
|
||||||
|
/// to the left or right of it.
|
||||||
|
/// </summary>
|
||||||
|
AdjacentOpponent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All makes the move target everything on the field.
|
||||||
|
/// </summary>
|
||||||
|
All,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AllAdjacent makes the move target everything adjacent on the field.
|
||||||
|
/// </summary>
|
||||||
|
AllAdjacent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AllAdjacentOpponent makes the move target everything adjacent to the opponent, and the opponent.
|
||||||
|
/// </summary>
|
||||||
|
AllAdjacentOpponent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AllAlly targets all Pokemon on the same side as the user.
|
||||||
|
/// </summary>
|
||||||
|
AllAlly,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AllOpponent targets all Pokemon on an opposing side from the user.
|
||||||
|
/// </summary>
|
||||||
|
AllOpponent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Any allows a move to target a single Pokemon, in any position.
|
||||||
|
/// </summary>
|
||||||
|
Any,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RandomOpponent allows a move to target a single Pokemon, in a random position.
|
||||||
|
/// </summary>
|
||||||
|
RandomOpponent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SelfUse makes the move target the user itself.
|
||||||
|
/// </summary>
|
||||||
|
SelfUse,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A move is the skill Pokémon primarily use in battle. This is the data related to that.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMoveData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the move.
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The attacking type of the move.
|
||||||
|
/// </summary>
|
||||||
|
TypeIdentifier MoveType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The category of the move.
|
||||||
|
/// </summary>
|
||||||
|
MoveCategory Category { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base power, not considering any modifiers, the move has.
|
||||||
|
/// </summary>
|
||||||
|
byte BasePower { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The accuracy of the move in percentage. Should be 255 for moves that always hit.
|
||||||
|
/// </summary>
|
||||||
|
byte Accuracy { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of times the move can be used. This can be modified on actually learned moves using PP-Ups
|
||||||
|
/// </summary>
|
||||||
|
byte BaseUsages { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How the move handles targets.
|
||||||
|
/// </summary>
|
||||||
|
MoveTarget Target { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The priority of the move. A higher priority means the move should go before other moves.
|
||||||
|
/// </summary>
|
||||||
|
sbyte Priority { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The optional secondary effect the move has.
|
||||||
|
/// </summary>
|
||||||
|
ISecondaryEffect? SecondaryEffect { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Arbitrary flags that can be applied to the move.
|
||||||
|
/// </summary>
|
||||||
|
bool HasFlag(string key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class MoveDataImpl : IMoveData
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="MoveDataImpl" />
|
||||||
|
public MoveDataImpl(string name, TypeIdentifier moveType, MoveCategory category, byte basePower, byte accuracy,
|
||||||
|
byte baseUsages, MoveTarget target, sbyte priority, ISecondaryEffect? secondaryEffect,
|
||||||
|
IEnumerable<StringKey> flags)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
MoveType = moveType;
|
||||||
|
Category = category;
|
||||||
|
BasePower = basePower;
|
||||||
|
Accuracy = accuracy;
|
||||||
|
BaseUsages = baseUsages;
|
||||||
|
Target = target;
|
||||||
|
Priority = priority;
|
||||||
|
SecondaryEffect = secondaryEffect;
|
||||||
|
_flags = [..flags];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public TypeIdentifier MoveType { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MoveCategory Category { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public byte BasePower { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public byte Accuracy { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public byte BaseUsages { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MoveTarget Target { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public sbyte Priority { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ISecondaryEffect? SecondaryEffect { get; }
|
||||||
|
|
||||||
|
private readonly ImmutableHashSet<StringKey> _flags;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool HasFlag(string key) => _flags.Contains(key);
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static.Moves;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A secondary effect is an effect on a move that happens after it hits.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISecondaryEffect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The chance in percentages that the effect triggers. When less than 0, the effect is always active.
|
||||||
|
/// </summary>
|
||||||
|
public float Chance { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the effect.
|
||||||
|
/// </summary>
|
||||||
|
public StringKey Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters for the effect.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<StringKey, object> Parameters { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class SecondaryEffectImpl : ISecondaryEffect
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="SecondaryEffectImpl" />
|
||||||
|
public SecondaryEffectImpl(float chance, StringKey name, IReadOnlyDictionary<StringKey, object> parameters)
|
||||||
|
{
|
||||||
|
Chance = chance;
|
||||||
|
Name = name;
|
||||||
|
Parameters = parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public float Chance { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public StringKey Name { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyDictionary<StringKey, object> Parameters { get; }
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A nature is an attribute on a Pokemon that modifies the effective base stats on a Pokemon. They
|
||||||
|
/// can have an increased statistic and a decreased statistic, or be neutral.
|
||||||
|
/// </summary>
|
||||||
|
public interface INature
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the nature.
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The stat that should receive the increased modifier.
|
||||||
|
/// </summary>
|
||||||
|
Statistic IncreasedStat { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The stat that should receive the decreased modifier.
|
||||||
|
/// </summary>
|
||||||
|
Statistic DecreasedStat { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount that the increased stat gets modified by.
|
||||||
|
/// </summary>
|
||||||
|
float IncreasedModifier { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount that the decreased stat gets modified by.
|
||||||
|
/// </summary>
|
||||||
|
float DecreasedModifier { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the modifier for a given stat. If it's the increased stat, returns the increased
|
||||||
|
/// modifier, if it's the decreased stat, returns the decreased modifier. Otherwise returns 1.0.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stat">The stat to calculate the modifier for.</param>
|
||||||
|
/// <returns>The calculated modifier.</returns>
|
||||||
|
float GetStatModifier(Statistic stat);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if two natures are equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The other nature to compare to.</param>
|
||||||
|
/// <returns>True if the natures are equal, false otherwise.</returns>
|
||||||
|
bool Equals(INature other);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class Nature(
|
||||||
|
string name,
|
||||||
|
Statistic increaseStat,
|
||||||
|
Statistic decreaseStat,
|
||||||
|
float increaseModifier,
|
||||||
|
float decreaseModifier)
|
||||||
|
: INature
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name { get; } = name;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Statistic IncreasedStat { get; } = increaseStat;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Statistic DecreasedStat { get; } = decreaseStat;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public float IncreasedModifier { get; } = increaseModifier;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public float DecreasedModifier { get; } = decreaseModifier;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public float GetStatModifier(Statistic stat)
|
||||||
|
{
|
||||||
|
if (stat == IncreasedStat && stat != DecreasedStat)
|
||||||
|
return IncreasedModifier;
|
||||||
|
if (stat == DecreasedStat && stat != IncreasedStat)
|
||||||
|
return DecreasedModifier;
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Equals(INature? other)
|
||||||
|
{
|
||||||
|
return other is not null && StringComparer.InvariantCultureIgnoreCase.Equals(Name, other.Name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.Static.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<DocumentationFile>bin\Release\netstandard2.1\PkmnLib.Static.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentResults" Version="3.16.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>
|
||||||
|
<Folder Include="Libraries\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,63 @@
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static.Species;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An ability is a passive effect in battle that is attached to a Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAbility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the ability.
|
||||||
|
/// </summary>
|
||||||
|
StringKey Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the script effect of the ability. This should refer to the name of the script that will be executed
|
||||||
|
/// when the ability is triggered.
|
||||||
|
/// </summary>
|
||||||
|
StringKey Effect { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The parameters for the script effect of the ability.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyDictionary<StringKey, object> Parameters { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class AbilityImpl : IAbility
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="AbilityImpl" />
|
||||||
|
public AbilityImpl(StringKey name, StringKey effect, IReadOnlyDictionary<StringKey, object> parameters)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Effect = effect;
|
||||||
|
Parameters = parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public StringKey Name { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public StringKey Effect { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyDictionary<StringKey, object> Parameters { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An ability index allows us to find an ability on a form. It combines a bool for whether the
|
||||||
|
/// ability is hidden or not, and then an index of the ability.
|
||||||
|
/// </summary>
|
||||||
|
public readonly record struct AbilityIndex
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the ability we're referring to is a hidden ability.
|
||||||
|
/// </summary>
|
||||||
|
public required bool IsHidden { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The index of the ability.
|
||||||
|
/// </summary>
|
||||||
|
public required byte Index { get; init; }
|
||||||
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static.Species;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data about how and into which Pokemon a species can evolve.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The species that the Pokemon evolves into.
|
||||||
|
/// </summary>
|
||||||
|
StringKey ToSpecies { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when a certain level is reached.
|
||||||
|
/// </summary>
|
||||||
|
public record LevelEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The level at which the Pokemon evolves.
|
||||||
|
/// </summary>
|
||||||
|
public required uint Level { get; init; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when a certain level is reached, and the Pokemon is a specific gender
|
||||||
|
/// </summary>
|
||||||
|
public record LevelGenderEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The level at which the Pokemon evolves.
|
||||||
|
/// </summary>
|
||||||
|
public required uint Level { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The gender the Pokemon needs to have to evolve
|
||||||
|
/// </summary>
|
||||||
|
public required Gender Gender { get; init; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when an item is used on the Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
public record ItemUseEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The item that needs to be used.
|
||||||
|
/// </summary>
|
||||||
|
public required StringKey Item { get; init; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when an item is used on the Pokemon, and the Pokemon is a specific gender
|
||||||
|
/// </summary>
|
||||||
|
public record ItemGenderEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The item that needs to be used.
|
||||||
|
/// </summary>
|
||||||
|
public required StringKey Item { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The gender the Pokemon needs to have to evolve
|
||||||
|
/// </summary>
|
||||||
|
public Gender Gender { get; init; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when an item is held by the Pokemon, and the Pokemon levels up.
|
||||||
|
/// </summary>
|
||||||
|
public record HoldItemEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The item that needs to be held.
|
||||||
|
/// </summary>
|
||||||
|
public required StringKey Item { get; init; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when an item is held by the Pokemon, and the Pokemon levels up, and it's day.
|
||||||
|
/// </summary>
|
||||||
|
public record DayHoldItemEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The item that needs to be held.
|
||||||
|
/// </summary>
|
||||||
|
public required StringKey Item { get; init; }
|
||||||
|
|
||||||
|
/// < inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when an item is held by the Pokemon, and the Pokemon levels up, and it's night.
|
||||||
|
/// </summary>
|
||||||
|
public record NightHoldItemEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The item that needs to be held.
|
||||||
|
/// </summary>
|
||||||
|
public required StringKey Item { get; init; }
|
||||||
|
|
||||||
|
/// < inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when the Pokemon knows a certain move, and the Pokemon levels up.
|
||||||
|
/// </summary>
|
||||||
|
public record HasMoveEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the move that needs to be known.
|
||||||
|
/// </summary>
|
||||||
|
public required StringKey MoveName { get; init; }
|
||||||
|
|
||||||
|
/// < inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when above a certain happiness level, and the Pokemon levels up.
|
||||||
|
/// </summary>
|
||||||
|
public record HappinessEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The happiness level that needs to be reached.
|
||||||
|
/// </summary>
|
||||||
|
public required byte Happiness { get; init; }
|
||||||
|
|
||||||
|
/// < inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when above a certain happiness level, and the Pokemon levels up, and it's day.
|
||||||
|
/// </summary>
|
||||||
|
public record HappinessDayEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The happiness level that needs to be reached.
|
||||||
|
/// </summary>
|
||||||
|
public required byte Happiness { get; init; }
|
||||||
|
|
||||||
|
/// < inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when above a certain happiness level, and the Pokemon levels up, and it's night.
|
||||||
|
/// </summary>
|
||||||
|
public record HappinessNightEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The happiness level that needs to be reached.
|
||||||
|
/// </summary>
|
||||||
|
public required byte Happiness { get; init; }
|
||||||
|
|
||||||
|
/// < inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when traded.
|
||||||
|
/// </summary>
|
||||||
|
public record TradeEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// < inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when traded with a certain species.
|
||||||
|
/// </summary>
|
||||||
|
public record TradeSpeciesEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The species that needs to be traded with.
|
||||||
|
/// </summary>
|
||||||
|
public required StringKey WithSpecies { get; init; }
|
||||||
|
|
||||||
|
/// < inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evolves when traded while it's holding a certain item.
|
||||||
|
/// </summary>
|
||||||
|
public record TradeItemEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The item that needs to be held.
|
||||||
|
/// </summary>
|
||||||
|
public required StringKey Item { get; init; }
|
||||||
|
|
||||||
|
/// < inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom evolution method, implemented by the user.
|
||||||
|
/// </summary>
|
||||||
|
public record CustomEvolution : IEvolution
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the custom evolution method. This should refer to the name of the script that will be executed.
|
||||||
|
/// </summary>
|
||||||
|
public required StringKey Name { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The parameters of the custom evolution method.
|
||||||
|
/// </summary>
|
||||||
|
public required IReadOnlyDictionary<StringKey, object> Parameters { get; init; }
|
||||||
|
|
||||||
|
/// < inheritdoc />
|
||||||
|
public required StringKey ToSpecies { get; init; }
|
||||||
|
}
|
|
@ -0,0 +1,218 @@
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using FluentResults;
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
using PkmnLib.Static.Utils.Errors;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static.Species;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A form is a variant of a specific species. A species always has at least one form, but can have
|
||||||
|
/// many more.
|
||||||
|
/// </summary>
|
||||||
|
public interface IForm
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the form.
|
||||||
|
/// </summary>
|
||||||
|
StringKey Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The height of the form in meters.
|
||||||
|
/// </summary>
|
||||||
|
float Height { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The weight of the form in kilograms.
|
||||||
|
/// </summary>
|
||||||
|
float Weight { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base amount of experience that is gained when beating a Pokemon with this form.
|
||||||
|
/// </summary>
|
||||||
|
uint BaseExperience { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The normal types a Pokemon with this form has.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyList<TypeIdentifier> Types { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The inherent values of a form of species that are used for the stats of a Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
StaticStatisticSet<ushort> BaseStats { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The possible abilities a Pokemon with this form can have.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyList<StringKey> Abilities { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The possible hidden abilities a Pokemon with this form can have.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyList<StringKey> HiddenAbilities { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The moves a Pokemon with this form can learn.
|
||||||
|
/// </summary>
|
||||||
|
ILearnableMoves Moves { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Arbitrary flags can be set on a form for scripting use.
|
||||||
|
/// </summary>
|
||||||
|
ImmutableHashSet<StringKey> Flags { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a type of the form at a certain index.
|
||||||
|
/// </summary>
|
||||||
|
Result<TypeIdentifier> GetType(int index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a single base stat value.
|
||||||
|
/// </summary>
|
||||||
|
ushort GetBaseStat(Statistic stat);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the index of an ability that can be on this form.
|
||||||
|
/// </summary>
|
||||||
|
AbilityIndex? FindAbilityIndex(IAbility ability);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an ability from the form.
|
||||||
|
/// </summary>
|
||||||
|
Result<StringKey> GetAbility(AbilityIndex index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a random ability from the form.
|
||||||
|
/// </summary>
|
||||||
|
StringKey GetRandomAbility(IRandom rand);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a random hidden ability from the form.
|
||||||
|
/// </summary>
|
||||||
|
StringKey GetRandomHiddenAbility(IRandom rand);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the form has a specific flag set.
|
||||||
|
/// </summary>
|
||||||
|
bool HasFlag(string key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class FormImpl : IForm
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="FormImpl" />
|
||||||
|
public FormImpl(StringKey name, float height, float weight, uint baseExperience,
|
||||||
|
IEnumerable<TypeIdentifier> types, StaticStatisticSet<ushort> baseStats, IEnumerable<StringKey> abilities,
|
||||||
|
IEnumerable<StringKey> hiddenAbilities, ILearnableMoves moves, ImmutableHashSet<StringKey> flags)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Height = height;
|
||||||
|
Weight = weight;
|
||||||
|
BaseExperience = baseExperience;
|
||||||
|
Types = [..types];
|
||||||
|
BaseStats = baseStats;
|
||||||
|
Abilities = [..abilities];
|
||||||
|
HiddenAbilities = [..hiddenAbilities];
|
||||||
|
Moves = moves;
|
||||||
|
Flags = flags;
|
||||||
|
|
||||||
|
if (Types.Count == 0)
|
||||||
|
throw new ArgumentException("A form must have at least one type.");
|
||||||
|
if (Abilities.Count == 0)
|
||||||
|
throw new ArgumentException("A form must have at least one ability.");
|
||||||
|
if (HiddenAbilities.Count == 0)
|
||||||
|
throw new ArgumentException("A form must have at least one hidden ability.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public StringKey Name { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public float Height { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public float Weight { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public uint BaseExperience { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<TypeIdentifier> Types { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public StaticStatisticSet<ushort> BaseStats { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<StringKey> Abilities { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<StringKey> HiddenAbilities { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ILearnableMoves Moves { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ImmutableHashSet<StringKey> Flags { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Result<TypeIdentifier> GetType(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= Types.Count)
|
||||||
|
return Result.Fail(new OutOfRange("Type", index, Types.Count));
|
||||||
|
return Types[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ushort GetBaseStat(Statistic stat) => BaseStats.GetStatistic(stat);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public AbilityIndex? FindAbilityIndex(IAbility ability)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Abilities.Count && i < 255; i++)
|
||||||
|
{
|
||||||
|
if (Abilities[i] == ability.Name)
|
||||||
|
return new AbilityIndex
|
||||||
|
{
|
||||||
|
IsHidden = false,
|
||||||
|
Index = (byte)i
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (var i = 0; i < HiddenAbilities.Count && i < 255; i++)
|
||||||
|
{
|
||||||
|
if (HiddenAbilities[i] == ability.Name)
|
||||||
|
return new AbilityIndex
|
||||||
|
{
|
||||||
|
IsHidden = true,
|
||||||
|
Index = (byte)i
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Result<StringKey> GetAbility(AbilityIndex index)
|
||||||
|
{
|
||||||
|
var array = index.IsHidden ? HiddenAbilities : Abilities;
|
||||||
|
if (index.Index >= array.Count)
|
||||||
|
return Result.Fail(new OutOfRange("Ability", index.Index, array.Count));
|
||||||
|
return array[index.Index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public StringKey GetRandomAbility(IRandom rand)
|
||||||
|
{
|
||||||
|
return Abilities[rand.GetInt(Abilities.Count)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public StringKey GetRandomHiddenAbility(IRandom rand)
|
||||||
|
{
|
||||||
|
return HiddenAbilities[rand.GetInt(HiddenAbilities.Count)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool HasFlag(string key)
|
||||||
|
{
|
||||||
|
return Flags.Contains(key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
namespace PkmnLib.Static;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gender is a Pokemon characteristic.
|
||||||
|
///
|
||||||
|
/// Required for standard pokemon functions, but somewhat controversial nowadays. Consider adding a feature
|
||||||
|
/// that allows for a more progressive gender system for those that want it?
|
||||||
|
/// </summary>
|
||||||
|
public enum Gender : byte
|
||||||
|
{
|
||||||
|
/// The Pokemon has no gender.
|
||||||
|
Genderless,
|
||||||
|
/// <summary>
|
||||||
|
/// The Pokemon is male.
|
||||||
|
/// </summary>
|
||||||
|
Male,
|
||||||
|
/// <summary>
|
||||||
|
/// The Pokemon is female.
|
||||||
|
/// </summary>
|
||||||
|
Female
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static.Species;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The storage of the moves a Pokemon can learn.
|
||||||
|
/// </summary>
|
||||||
|
public interface ILearnableMoves
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new level move the Pokemon can learn.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level the Pokemon learns the move at.</param>
|
||||||
|
/// <param name="move">The move the Pokemon learns.</param>
|
||||||
|
/// <returns>Whether the move was added successfully.</returns>
|
||||||
|
void AddLevelMove(LevelInt level, StringKey move);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all moves a Pokemon can learn when leveling up to a specific level.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level the Pokemon is learning moves at.</param>
|
||||||
|
/// <returns>The moves the Pokemon learns at that level.</returns>
|
||||||
|
IReadOnlyList<StringKey> GetLearnedByLevel(LevelInt level);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the distinct moves a Pokemon can learn through leveling up.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The moves the Pokemon can learn through leveling up.</returns>
|
||||||
|
IReadOnlyList<StringKey> GetDistinctLevelMoves();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LearnableMovesImpl : ILearnableMoves
|
||||||
|
{
|
||||||
|
private readonly Dictionary<LevelInt, List<StringKey>> _learnedByLevel = new();
|
||||||
|
private readonly HashSet<StringKey> _distinctLevelMoves = new();
|
||||||
|
|
||||||
|
|
||||||
|
public void AddLevelMove(LevelInt level, StringKey move)
|
||||||
|
{
|
||||||
|
if (!_learnedByLevel.TryGetValue(level, out var value))
|
||||||
|
_learnedByLevel[level] = [move];
|
||||||
|
else
|
||||||
|
value.Add(move);
|
||||||
|
_distinctLevelMoves.Add(move);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<StringKey> GetLearnedByLevel(LevelInt level)
|
||||||
|
{
|
||||||
|
if (!_learnedByLevel.TryGetValue(level, out var value))
|
||||||
|
return Array.Empty<StringKey>();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<StringKey> GetDistinctLevelMoves()
|
||||||
|
{
|
||||||
|
return _distinctLevelMoves.ToList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static.Species;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The data belonging to a Pokemon with certain characteristics.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISpecies
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The national dex identifier of the Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
ushort Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
StringKey Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The chance between 0.0 and 1.0 that a Pokemon is female. 0.0 means always male, 1.0 means always female.
|
||||||
|
/// If less than 0, the Pokemon is genderless.
|
||||||
|
/// </summary>
|
||||||
|
float GenderRate { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How much experience is required for a level.
|
||||||
|
/// </summary>
|
||||||
|
StringKey GrowthRate { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How hard it is to capture a Pokemon. 255 means this will be always caught, 0 means this is
|
||||||
|
/// uncatchable.
|
||||||
|
/// </summary>
|
||||||
|
byte CaptureRate { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base happiness of the Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
byte BaseHappiness { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The forms that belong to this Pokemon.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyDictionary<StringKey, IForm> Forms { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The arbitrary flags that can be set on a Pokemon for script use.
|
||||||
|
/// </summary>
|
||||||
|
ImmutableHashSet<StringKey> Flags { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a form by name.
|
||||||
|
/// </summary>
|
||||||
|
bool TryGetForm(StringKey id, [MaybeNullWhen(false)] out IForm form);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the form the Pokemon will have by default, if no other form is specified.
|
||||||
|
/// </summary>
|
||||||
|
IForm GetDefaultForm();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a random gender.
|
||||||
|
/// </summary>
|
||||||
|
Gender GetRandomGender(IRandom rand);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check whether the Pokemon has a specific flag set.
|
||||||
|
/// </summary>
|
||||||
|
bool HasFlag(string key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The data regarding into which Pokemon this species can evolve, and how.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyList<IEvolution> EvolutionData { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class SpeciesImpl : ISpecies
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="SpeciesImpl" />
|
||||||
|
public SpeciesImpl(ushort id, StringKey name, float genderRate, StringKey growthRate, byte captureRate,
|
||||||
|
byte baseHappiness, IReadOnlyDictionary<StringKey, IForm> forms, ImmutableHashSet<StringKey> flags,
|
||||||
|
IReadOnlyList<IEvolution> evolutionData)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
GenderRate = genderRate;
|
||||||
|
GrowthRate = growthRate;
|
||||||
|
CaptureRate = captureRate;
|
||||||
|
BaseHappiness = baseHappiness;
|
||||||
|
Forms = forms;
|
||||||
|
Flags = flags;
|
||||||
|
EvolutionData = evolutionData;
|
||||||
|
if (Forms.Count == 0)
|
||||||
|
throw new ArgumentException("Species must have at least one form.");
|
||||||
|
if (!Forms.ContainsKey("default"))
|
||||||
|
throw new ArgumentException("Species must have a default form.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ushort Id { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public StringKey Name { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public float GenderRate { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public StringKey GrowthRate { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public byte CaptureRate { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public byte BaseHappiness { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyDictionary<StringKey, IForm> Forms { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ImmutableHashSet<StringKey> Flags { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<IEvolution> EvolutionData { get; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool TryGetForm(StringKey id, [MaybeNullWhen(false)] out IForm form) => Forms.TryGetValue(id, out form);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IForm GetDefaultForm() => Forms["default"];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Gender GetRandomGender(IRandom rand)
|
||||||
|
{
|
||||||
|
if (GenderRate < 0.0f)
|
||||||
|
return Gender.Genderless;
|
||||||
|
var v = rand.GetFloat();
|
||||||
|
return v < GenderRate ? Gender.Female : Gender.Male;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool HasFlag(string key) => Flags.Contains(key);
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
namespace PkmnLib.Static;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stats are numerical values on Pokemon that are used in battle.
|
||||||
|
/// </summary>
|
||||||
|
public enum Statistic : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Health Points determine how much damage a Pokemon can receive before fainting.
|
||||||
|
/// </summary>
|
||||||
|
Hp,
|
||||||
|
/// <summary>
|
||||||
|
/// Attack determines how much damage a Pokemon deals when using a physical attack.
|
||||||
|
/// </summary>
|
||||||
|
Attack,
|
||||||
|
/// <summary>
|
||||||
|
/// Defense determines how much damage a Pokemon receives when it is hit by a physical attack.
|
||||||
|
/// </summary>
|
||||||
|
Defense,
|
||||||
|
/// <summary>
|
||||||
|
/// Special Attack determines how much damage a Pokemon deals when using a special attack.
|
||||||
|
/// </summary>
|
||||||
|
SpecialAttack,
|
||||||
|
/// <summary>
|
||||||
|
/// Special Defense determines how much damage a Pokemon receives when it is hit by a special attack.
|
||||||
|
/// </summary>
|
||||||
|
SpecialDefense,
|
||||||
|
/// <summary>
|
||||||
|
/// Speed determines the order that a Pokemon can act in battle.
|
||||||
|
/// </summary>
|
||||||
|
Speed
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static;
|
||||||
|
|
||||||
|
public record StaticStatisticSet<T>
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The health points stat value.
|
||||||
|
/// </summary>
|
||||||
|
public T Hp { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The physical attack stat value.
|
||||||
|
/// </summary>
|
||||||
|
public T Attack { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The physical defense stat value.
|
||||||
|
/// </summary>
|
||||||
|
public T Defense { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The special attack stat value.
|
||||||
|
/// </summary>
|
||||||
|
public T SpecialAttack { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The special defense stat value.
|
||||||
|
/// </summary>
|
||||||
|
public T SpecialDefense { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The speed stat value.
|
||||||
|
/// </summary>
|
||||||
|
public T Speed { get; protected set; }
|
||||||
|
|
||||||
|
public StaticStatisticSet(T hp, T attack, T defense, T specialAttack, T specialDefense, T speed)
|
||||||
|
{
|
||||||
|
Hp = hp;
|
||||||
|
Attack = attack;
|
||||||
|
Defense = defense;
|
||||||
|
SpecialAttack = specialAttack;
|
||||||
|
SpecialDefense = specialDefense;
|
||||||
|
Speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T GetStatistic(Statistic stat)
|
||||||
|
{
|
||||||
|
return stat switch
|
||||||
|
{
|
||||||
|
Statistic.Hp => Hp,
|
||||||
|
Statistic.Attack => Attack,
|
||||||
|
Statistic.Defense => Defense,
|
||||||
|
Statistic.SpecialAttack => SpecialAttack,
|
||||||
|
Statistic.SpecialDefense => SpecialDefense,
|
||||||
|
Statistic.Speed => Speed,
|
||||||
|
_ => throw new ArgumentException("Invalid statistic.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record StatisticSet<T> : StaticStatisticSet<T>
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
public StatisticSet(T hp, T attack, T defense, T specialAttack, T specialDefense, T speed) : base(hp, attack,
|
||||||
|
defense, specialAttack, specialDefense, speed)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetStatistic(Statistic stat, T value)
|
||||||
|
{
|
||||||
|
switch (stat)
|
||||||
|
{
|
||||||
|
case Statistic.Hp:
|
||||||
|
Hp = value;
|
||||||
|
break;
|
||||||
|
case Statistic.Attack:
|
||||||
|
Attack = value;
|
||||||
|
break;
|
||||||
|
case Statistic.Defense:
|
||||||
|
Defense = value;
|
||||||
|
break;
|
||||||
|
case Statistic.SpecialAttack:
|
||||||
|
SpecialAttack = value;
|
||||||
|
break;
|
||||||
|
case Statistic.SpecialDefense:
|
||||||
|
SpecialDefense = value;
|
||||||
|
break;
|
||||||
|
case Statistic.Speed:
|
||||||
|
Speed = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException("Invalid statistic.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void IncreaseStatistic(Statistic stat, T value)
|
||||||
|
{
|
||||||
|
switch (stat)
|
||||||
|
{
|
||||||
|
case Statistic.Hp:
|
||||||
|
Hp = (T)Convert.ChangeType(Convert.ToInt32(Hp) + Convert.ToInt32(value), typeof(T));
|
||||||
|
break;
|
||||||
|
case Statistic.Attack:
|
||||||
|
Attack = (T)Convert.ChangeType(Convert.ToInt32(Attack) + Convert.ToInt32(value), typeof(T));
|
||||||
|
break;
|
||||||
|
case Statistic.Defense:
|
||||||
|
Defense = (T)Convert.ChangeType(Convert.ToInt32(Defense) + Convert.ToInt32(value), typeof(T));
|
||||||
|
break;
|
||||||
|
case Statistic.SpecialAttack:
|
||||||
|
SpecialAttack =
|
||||||
|
(T)Convert.ChangeType(Convert.ToInt32(SpecialAttack) + Convert.ToInt32(value), typeof(T));
|
||||||
|
break;
|
||||||
|
case Statistic.SpecialDefense:
|
||||||
|
SpecialDefense =
|
||||||
|
(T)Convert.ChangeType(Convert.ToInt32(SpecialDefense) + Convert.ToInt32(value), typeof(T));
|
||||||
|
break;
|
||||||
|
case Statistic.Speed:
|
||||||
|
Speed = (T)Convert.ChangeType(Convert.ToInt32(Speed) + Convert.ToInt32(value), typeof(T));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException("Invalid statistic.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DecreaseStatistic(Statistic stat, T value)
|
||||||
|
{
|
||||||
|
switch (stat)
|
||||||
|
{
|
||||||
|
case Statistic.Hp:
|
||||||
|
Hp = (T)Convert.ChangeType(Convert.ToInt32(Hp) - Convert.ToInt32(value), typeof(T));
|
||||||
|
break;
|
||||||
|
case Statistic.Attack:
|
||||||
|
Attack = (T)Convert.ChangeType(Convert.ToInt32(Attack) - Convert.ToInt32(value), typeof(T));
|
||||||
|
break;
|
||||||
|
case Statistic.Defense:
|
||||||
|
Defense = (T)Convert.ChangeType(Convert.ToInt32(Defense) - Convert.ToInt32(value), typeof(T));
|
||||||
|
break;
|
||||||
|
case Statistic.SpecialAttack:
|
||||||
|
SpecialAttack =
|
||||||
|
(T)Convert.ChangeType(Convert.ToInt32(SpecialAttack) - Convert.ToInt32(value), typeof(T));
|
||||||
|
break;
|
||||||
|
case Statistic.SpecialDefense:
|
||||||
|
SpecialDefense =
|
||||||
|
(T)Convert.ChangeType(Convert.ToInt32(SpecialDefense) - Convert.ToInt32(value), typeof(T));
|
||||||
|
break;
|
||||||
|
case Statistic.Speed:
|
||||||
|
Speed = (T)Convert.ChangeType(Convert.ToInt32(Speed) - Convert.ToInt32(value), typeof(T));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException("Invalid statistic.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
namespace PkmnLib.Static;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time of day. These values are the 4 different groups of time of day in Pokemon games since
|
||||||
|
/// gen 5. The exact times these correspond to differ between games.
|
||||||
|
/// </summary>
|
||||||
|
public enum TimeOfDay : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The morning.
|
||||||
|
/// </summary>
|
||||||
|
Morning = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The day.
|
||||||
|
/// </summary>
|
||||||
|
Day = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The evening.
|
||||||
|
/// </summary>
|
||||||
|
Evening = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The night.
|
||||||
|
/// </summary>
|
||||||
|
Night = 3,
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace PkmnLib.Static;
|
||||||
|
|
||||||
|
public readonly record struct TypeIdentifier
|
||||||
|
{
|
||||||
|
private byte Value { get; init; }
|
||||||
|
|
||||||
|
public TypeIdentifier(byte value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator TypeIdentifier(byte value) => new(value);
|
||||||
|
|
||||||
|
public override int GetHashCode() => Value.GetHashCode();
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
using FluentResults;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static.Utils.Errors;
|
||||||
|
|
||||||
|
public class OutOfRange : Error
|
||||||
|
{
|
||||||
|
public OutOfRange(string hint, int index, int max)
|
||||||
|
: base($"{hint} index {index} is out of range. Must be between 0 and {max - 1}.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
namespace PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
public interface IRandom
|
||||||
|
{
|
||||||
|
public int GetInt(int min, int max);
|
||||||
|
public int GetInt(int max);
|
||||||
|
public int GetInt();
|
||||||
|
public float GetFloat();
|
||||||
|
public float GetFloat(float min, float max);
|
||||||
|
public bool GetBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RandomImpl : IRandom
|
||||||
|
{
|
||||||
|
private Random _random;
|
||||||
|
|
||||||
|
public RandomImpl(Random random)
|
||||||
|
{
|
||||||
|
_random = random;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RandomImpl(int seed)
|
||||||
|
{
|
||||||
|
_random = new Random(seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RandomImpl()
|
||||||
|
{
|
||||||
|
_random = new Random();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetInt(int min, int max) => _random.Next(min, max);
|
||||||
|
|
||||||
|
public int GetInt(int max) => _random.Next(max);
|
||||||
|
|
||||||
|
public int GetInt() => _random.Next();
|
||||||
|
|
||||||
|
public float GetFloat() => (float)_random.NextDouble();
|
||||||
|
|
||||||
|
public float GetFloat(float min, float max) => (float)(_random.NextDouble() * (max - min) + min);
|
||||||
|
|
||||||
|
public bool GetBool() => _random.Next(2) == 1;
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A case-insensitive string key. We use this class for things like looking up data from dictionaries, etc.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is a struct, as it's effectively just a wrapper around a single reference object. Heap allocation would be silly.
|
||||||
|
/// </remarks>
|
||||||
|
public readonly record struct StringKey
|
||||||
|
{
|
||||||
|
private readonly string _key;
|
||||||
|
|
||||||
|
public StringKey(string key)
|
||||||
|
{
|
||||||
|
_key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator string(StringKey key) => key._key;
|
||||||
|
public static implicit operator StringKey(string key) => new(key);
|
||||||
|
|
||||||
|
public override string ToString() => _key.ToLowerInvariant();
|
||||||
|
|
||||||
|
public bool Equals(StringKey other)
|
||||||
|
{
|
||||||
|
return string.Equals(_key, other._key, StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return StringComparer.InvariantCultureIgnoreCase.GetHashCode(_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
global using NUnit.Framework;
|
||||||
|
global using FluentAssertions;
|
||||||
|
|
||||||
|
global using LevelInt = byte;
|
|
@ -0,0 +1,25 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
|
||||||
|
<PackageReference Include="NUnit" Version="3.13.3"/>
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
|
||||||
|
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PkmnLib.Static\PkmnLib.Static.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,46 @@
|
||||||
|
using PkmnLib.Static;
|
||||||
|
|
||||||
|
namespace PkmnLib.Tests.Static;
|
||||||
|
|
||||||
|
public class GrowthRateTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void EmptyLookupGrowthRateTestShouldThrowArgumentException()
|
||||||
|
{
|
||||||
|
Action act = () => _ = new LookupGrowthRate("Test", []);
|
||||||
|
act.Should().Throw<ArgumentException>().WithMessage("Experience table must have at least one entry.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NonZeroLookupGrowthRateTestShouldThrowArgumentException()
|
||||||
|
{
|
||||||
|
Action act = () => _ = new LookupGrowthRate("Test", [1]);
|
||||||
|
act.Should().Throw<ArgumentException>().WithMessage("Experience table must start at 0.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TooLargeLookupGrowthRateTestShouldThrowArgumentException()
|
||||||
|
{
|
||||||
|
Action act = () => _ = new LookupGrowthRate("Test", Enumerable.Range(0, LevelInt.MaxValue + 1).Select(i => (uint)i));
|
||||||
|
act.Should().Throw<ArgumentException>().WithMessage($"Experience table may have at most {LevelInt.MaxValue} entries.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void LookupGrowthRateTest()
|
||||||
|
{
|
||||||
|
var growthRate = new LookupGrowthRate("Test", [0, 1, 2, 3, 4, 5]);
|
||||||
|
growthRate.CalculateLevel(0).Should().Be(1);
|
||||||
|
growthRate.CalculateLevel(1).Should().Be(2);
|
||||||
|
growthRate.CalculateLevel(2).Should().Be(3);
|
||||||
|
growthRate.CalculateLevel(3).Should().Be(4);
|
||||||
|
growthRate.CalculateLevel(4).Should().Be(5);
|
||||||
|
growthRate.CalculateLevel(5).Should().Be(6);
|
||||||
|
|
||||||
|
growthRate.CalculateExperience(1).Should().Be(0);
|
||||||
|
growthRate.CalculateExperience(2).Should().Be(1);
|
||||||
|
growthRate.CalculateExperience(3).Should().Be(2);
|
||||||
|
growthRate.CalculateExperience(4).Should().Be(3);
|
||||||
|
growthRate.CalculateExperience(5).Should().Be(4);
|
||||||
|
growthRate.CalculateExperience(6).Should().Be(5);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Collections;
|
||||||
|
using PkmnLib.Static.Utils;
|
||||||
|
|
||||||
|
namespace PkmnLib.Tests.Static;
|
||||||
|
|
||||||
|
public class StringKeyTests
|
||||||
|
{
|
||||||
|
private static IEnumerable StringKeyEqualityTestCases
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new TestCaseData("test", "test").Returns(true);
|
||||||
|
yield return new TestCaseData("test", "test2").Returns(false);
|
||||||
|
yield return new TestCaseData("test2", "test2").Returns(true);
|
||||||
|
yield return new TestCaseData("Test", "test").Returns(true);
|
||||||
|
yield return new TestCaseData("TeSt", "tesT").Returns(true);
|
||||||
|
yield return new TestCaseData("TeSt", "tesv").Returns(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCaseSource(nameof(StringKeyEqualityTestCases))]
|
||||||
|
public bool StringKeyEqualityTest(string k1, string k2)
|
||||||
|
{
|
||||||
|
var sk1 = new StringKey(k1);
|
||||||
|
var sk2 = new StringKey(k2);
|
||||||
|
return sk1 == sk2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCaseSource(nameof(StringKeyEqualityTestCases))]
|
||||||
|
public bool HashCodeEqualityTest(string k1, string k2)
|
||||||
|
{
|
||||||
|
var sk1 = new StringKey(k1);
|
||||||
|
var sk2 = new StringKey(k2);
|
||||||
|
return sk1.GetHashCode() == sk2.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCaseSource(nameof(StringKeyEqualityTestCases))]
|
||||||
|
public bool HashSetEqualityTest(string k1, string k2)
|
||||||
|
{
|
||||||
|
var sk1 = new StringKey(k1);
|
||||||
|
var sk2 = new StringKey(k2);
|
||||||
|
var hs = new HashSet<StringKey> { sk1 };
|
||||||
|
return hs.Contains(sk2);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue