Initial commit

This commit is contained in:
2024-07-20 13:51:52 +02:00
commit 3845f91601
26 changed files with 1822 additions and 0 deletions

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -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();
}
}

View File

@@ -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);
}