From 656c208e5f1419a38412ac1f859df46eb6096807 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Tue, 3 Sep 2024 09:48:18 +0200 Subject: [PATCH] Fixes for serialization --- PkmnLib.Dynamic/Models/Pokemon.cs | 4 +- .../Models/Serialized/SerializedPokemon.cs | 135 ++++++++++++++++-- PkmnLib.Static/StatisticSet.cs | 2 +- PkmnLib.Tests/Dynamic/SerializationTests.cs | 71 ++++++++- 4 files changed, 198 insertions(+), 14 deletions(-) diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs index a9c21b9..d32af67 100644 --- a/PkmnLib.Dynamic/Models/Pokemon.cs +++ b/PkmnLib.Dynamic/Models/Pokemon.cs @@ -400,8 +400,8 @@ public class PokemonImpl : ScriptSource, IPokemon WeightInKg = form.Weight; HeightInMeters = form.Height; Happiness = serializedPokemon.Happiness; - IndividualValues = new IndividualValueStatisticSet(serializedPokemon.IndividualValues); - EffortValues = new EffortValueStatisticSet(serializedPokemon.EffortValues); + IndividualValues = serializedPokemon.IndividualValues.ToIndividualValueStatisticSet(); + EffortValues = serializedPokemon.EffortValues.ToEffortValueStatisticSet(); if (!library.StaticLibrary.Natures.TryGet(serializedPokemon.Nature, out var nature)) throw new KeyNotFoundException($"Nature {serializedPokemon.Nature} not found."); Nature = nature; diff --git a/PkmnLib.Dynamic/Models/Serialized/SerializedPokemon.cs b/PkmnLib.Dynamic/Models/Serialized/SerializedPokemon.cs index afb4d8a..c9f4e79 100644 --- a/PkmnLib.Dynamic/Models/Serialized/SerializedPokemon.cs +++ b/PkmnLib.Dynamic/Models/Serialized/SerializedPokemon.cs @@ -4,10 +4,17 @@ using PkmnLib.Static.Species; namespace PkmnLib.Dynamic.Models.Serialized; -public class SerializedPokemon +/// +/// A serialized Pokémon is a representation of a Pokémon that can be easily serialized and deserialized. +/// +public record SerializedPokemon { - public SerializedPokemon(){} - + /// + public SerializedPokemon() + { + } + + /// [SetsRequiredMembers] public SerializedPokemon(IPokemon pokemon) { @@ -21,8 +28,8 @@ public class SerializedPokemon HeldItem = pokemon.HeldItem?.Name; CurrentHealth = pokemon.CurrentHealth; Happiness = pokemon.Happiness; - IndividualValues = new IndividualValueStatisticSet(pokemon.IndividualValues); - EffortValues = new EffortValueStatisticSet(pokemon.EffortValues); + IndividualValues = new SerializedStats(pokemon.IndividualValues); + EffortValues = new SerializedStats(pokemon.EffortValues); Nature = pokemon.Nature.Name; Nickname = pokemon.Nickname; Ability = pokemon.Form.GetAbility(pokemon.AbilityIndex); @@ -41,31 +48,141 @@ public class SerializedPokemon IsEgg = pokemon.IsEgg; Status = pokemon.StatusScript.Script?.Name; } - + + /// public required string Species { get; set; } + + /// public required string Form { get; set; } + + /// public LevelInt Level { get; set; } + + /// public uint Experience { get; set; } + + /// public uint PersonalityValue { get; set; } + + /// public Gender Gender { get; set; } + + /// public byte Coloring { get; set; } + + /// public string? HeldItem { get; set; } + + /// public uint CurrentHealth { get; set; } + + /// public byte Happiness { get; set; } - public required IndividualValueStatisticSet IndividualValues { get; set; } - public required EffortValueStatisticSet EffortValues { get; set; } + + /// + public required SerializedStats IndividualValues { get; set; } + + /// + public required SerializedStats EffortValues { get; set; } + + /// public required string Nature { get; set; } + + /// public string? Nickname { get; set; } + + /// + /// The ability of the Pokémon. Note that this is the ability name, not the ability index, as the ability index might + /// change if the order of abilities changes in data. + /// public required string Ability { get; set; } + + /// public required SerializedLearnedMove?[] Moves { get; set; } + + /// public bool AllowedExperience { get; set; } + + /// public bool IsEgg { get; set; } + + /// + /// The status of the Pokémon. This is the name of the status script, if any exists. + /// public string? Status { get; set; } } -public class SerializedLearnedMove +/// +/// A serialized learned move is a representation of a learned move that can be easily serialized and deserialized. +/// +public record SerializedLearnedMove { + /// + /// The name of the move. + /// public required string MoveName { get; set; } + + /// public required MoveLearnMethod LearnMethod { get; set; } + + /// public required byte CurrentPp { get; set; } +} + +public record SerializedStats +{ + public SerializedStats() + { + } + + public SerializedStats(ImmutableStatisticSet stats) + { + Hp = stats.Hp; + Attack = stats.Attack; + Defense = stats.Defense; + SpecialAttack = stats.SpecialAttack; + SpecialDefense = stats.SpecialDefense; + Speed = stats.Speed; + } + + public SerializedStats(long hp, long attack, long defense, long specialAttack, long specialDefense, long speed) + { + Hp = hp; + Attack = attack; + Defense = defense; + SpecialAttack = specialAttack; + SpecialDefense = specialDefense; + Speed = speed; + } + + public long Hp { get; set; } + public long Attack { get; set; } + public long Defense { get; set; } + public long SpecialAttack { get; set; } + public long SpecialDefense { get; set; } + public long Speed { get; set; } + + public IndividualValueStatisticSet ToIndividualValueStatisticSet() + { + if (Hp < 0 || Attack < 0 || Defense < 0 || SpecialAttack < 0 || SpecialDefense < 0 || Speed < 0) + throw new InvalidOperationException("Stats cannot be negative."); + if (Hp > byte.MaxValue || Attack > byte.MaxValue || Defense > byte.MaxValue || SpecialAttack > byte.MaxValue || + SpecialDefense > byte.MaxValue || Speed > byte.MaxValue) + throw new InvalidOperationException("Stats cannot be higher than 255."); + + return new IndividualValueStatisticSet((byte)Hp, (byte)Attack, (byte)Defense, (byte)SpecialAttack, + (byte)SpecialDefense, (byte)Speed); + } + + public EffortValueStatisticSet ToEffortValueStatisticSet() + { + if (Hp < 0 || Attack < 0 || Defense < 0 || SpecialAttack < 0 || SpecialDefense < 0 || Speed < 0) + throw new InvalidOperationException("Stats cannot be negative."); + if (Hp > byte.MaxValue || Attack > byte.MaxValue || Defense > byte.MaxValue || SpecialAttack > byte.MaxValue || + SpecialDefense > byte.MaxValue || Speed > byte.MaxValue) + throw new InvalidOperationException("Stats cannot be higher than 255."); + + return new EffortValueStatisticSet((byte)Hp, (byte)Attack, (byte)Defense, (byte)SpecialAttack, + (byte)SpecialDefense, (byte)Speed); + } } \ No newline at end of file diff --git a/PkmnLib.Static/StatisticSet.cs b/PkmnLib.Static/StatisticSet.cs index f25a878..32fe807 100644 --- a/PkmnLib.Static/StatisticSet.cs +++ b/PkmnLib.Static/StatisticSet.cs @@ -334,7 +334,7 @@ public record IndividualValueStatisticSet : ClampedStatisticSet public IndividualValueStatisticSet() : base(0, 0, 0, 0, 0, 0) { } - + /// public IndividualValueStatisticSet(byte hp, byte attack, byte defense, byte specialAttack, byte specialDefense, byte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed) diff --git a/PkmnLib.Tests/Dynamic/SerializationTests.cs b/PkmnLib.Tests/Dynamic/SerializationTests.cs index 8c14698..37d6cca 100644 --- a/PkmnLib.Tests/Dynamic/SerializationTests.cs +++ b/PkmnLib.Tests/Dynamic/SerializationTests.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models.Serialized; using PkmnLib.Static; @@ -65,8 +66,8 @@ public class SerializationTests HeldItem = null, CurrentHealth = 29, Happiness = 70, - IndividualValues = new IndividualValueStatisticSet(20, 20, 20, 20, 20, 20), - EffortValues = new EffortValueStatisticSet(0, 0, 0, 0, 0, 0), + IndividualValues = new SerializedStats(20, 20, 20, 20, 20, 20), + EffortValues = new SerializedStats(0, 0, 0, 0, 0, 0), Nature = "hardy", Nickname = "foo", Moves = new[] @@ -112,4 +113,70 @@ public class SerializationTests }); } + [Test] + public void SerializedPokemonToJson() + { + var data = new SerializedPokemon + { + Species = "bulbasaur", + Form = "default", + Ability = "overgrow", + Level = 10, + Experience = 560, + PersonalityValue = 1000, + Gender = Gender.Male, + Coloring = 0, + HeldItem = null, + CurrentHealth = 29, + Happiness = 70, + IndividualValues = new SerializedStats(20, 20, 20, 20, 20, 20), + EffortValues = new SerializedStats(0, 0, 0, 0, 0, 0), + Nature = "hardy", + Nickname = "foo", + Moves = new[] + { + new SerializedLearnedMove + { + MoveName = "tackle", + LearnMethod = MoveLearnMethod.LevelUp, + CurrentPp = 23, + }, + null, + null, + null, + }, + }; + + var json = JsonSerializer.Serialize(data); + var deserialized = JsonSerializer.Deserialize(json); + Assert.That(deserialized, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserialized!.Species, Is.EqualTo("bulbasaur")); + Assert.That(deserialized!.Form, Is.EqualTo("default")); + Assert.That(deserialized!.Ability, Is.EqualTo("overgrow")); + Assert.That(deserialized!.Level, Is.EqualTo(10)); + Assert.That(deserialized!.Experience, Is.EqualTo(560)); + Assert.That(deserialized!.PersonalityValue, Is.EqualTo(1000)); + Assert.That(deserialized!.Gender, Is.EqualTo(Gender.Male)); + Assert.That(deserialized!.Coloring, Is.EqualTo(0)); + Assert.That(deserialized!.HeldItem, Is.Null); + Assert.That(deserialized!.CurrentHealth, Is.EqualTo(29)); + Assert.That(deserialized!.Happiness, Is.EqualTo(70)); + Assert.That(deserialized!.IndividualValues, Is.EqualTo(new SerializedStats(20, 20, 20, 20, 20, 20))); + Assert.That(deserialized!.EffortValues, Is.EqualTo(new SerializedStats(0, 0, 0, 0, 0, 0))); + Assert.That(deserialized!.Nature, Is.EqualTo("hardy")); + Assert.That(deserialized!.Nickname, Is.EqualTo("foo")); + }); + + Assert.That(deserialized!.Moves, Has.Length.EqualTo(4)); + Assert.That(deserialized!.Moves[0], Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(deserialized!.Moves[0]!.MoveName, Is.EqualTo("tackle")); + Assert.That(deserialized!.Moves[0]!.LearnMethod, Is.EqualTo(MoveLearnMethod.LevelUp)); + Assert.That(deserialized!.Moves[0]!.CurrentPp, Is.EqualTo(23)); + }); + } + } \ No newline at end of file