diff --git a/.editorconfig b/.editorconfig index 8bd6b02..4f53419 100644 --- a/.editorconfig +++ b/.editorconfig @@ -516,7 +516,7 @@ ij_javascript_wrap_comments = false [{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,.ws-context,jest.config}] indent_size = 2 -ij_json_array_wrapping = split_into_lines +ij_json_array_wrapping = normal ij_json_keep_blank_lines_in_code = 0 ij_json_keep_indents_on_empty_lines = false ij_json_keep_line_breaks = true diff --git a/PkmnLib.Dataloader/TypeDataLoader.cs b/PkmnLib.Dataloader/TypeDataLoader.cs index 45641c7..dd0dcfb 100644 --- a/PkmnLib.Dataloader/TypeDataLoader.cs +++ b/PkmnLib.Dataloader/TypeDataLoader.cs @@ -26,7 +26,7 @@ public static class TypeDataLoader if (!types.Any()) throw new InvalidDataException("No types found in type data."); - foreach (var type in types) + foreach (var type in types.Skip(1)) library.RegisterType(type); while (!reader.EndOfStream) diff --git a/PkmnLib.Dynamic/Models/Battle.cs b/PkmnLib.Dynamic/Models/Battle.cs index 52288b7..861f8ce 100644 --- a/PkmnLib.Dynamic/Models/Battle.cs +++ b/PkmnLib.Dynamic/Models/Battle.cs @@ -223,7 +223,7 @@ public class BattleImpl : ScriptSource, IBattle /// public bool CanUse(ITurnChoice choice) { - if (choice.User.IsUsable) + if (!choice.User.IsUsable) return false; if (choice is IMoveChoice moveChoice) { diff --git a/PkmnLib.Dynamic/Models/Choices/PassChoice.cs b/PkmnLib.Dynamic/Models/Choices/PassChoice.cs index 495f14c..1cd5abd 100644 --- a/PkmnLib.Dynamic/Models/Choices/PassChoice.cs +++ b/PkmnLib.Dynamic/Models/Choices/PassChoice.cs @@ -1,3 +1,5 @@ +using PkmnLib.Dynamic.ScriptHandling; + namespace PkmnLib.Dynamic.Models.Choices; /// @@ -6,4 +8,25 @@ namespace PkmnLib.Dynamic.Models.Choices; public interface IPassChoice : ITurnChoice { +} + +public class PassChoice : TurnChoice, IPassChoice +{ + public PassChoice(IPokemon user) : base(user) + { + } + + /// + public override int ScriptCount => User.ScriptCount; + + /// + public override void GetOwnScripts(List> scripts) + { + } + + /// + public override void CollectScripts(List> scripts) + { + User.CollectScripts(scripts); + } } \ No newline at end of file diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs index 1f3f78d..b112731 100644 --- a/PkmnLib.Dynamic/Models/Pokemon.cs +++ b/PkmnLib.Dynamic/Models/Pokemon.cs @@ -271,7 +271,7 @@ public interface IPokemon : IScriptSource /// /// Learn a move by name. /// - void LearnMove(string moveName, MoveLearnMethod method, byte index); + void LearnMove(StringKey moveName, MoveLearnMethod method, byte index); /// /// Removes the current non-volatile status from the Pokemon. @@ -658,7 +658,7 @@ public class PokemonImpl : ScriptSource, IPokemon /// /// If the index is 255, it will try to find the first empty move slot. /// - public void LearnMove(string moveName, MoveLearnMethod method, byte index) + public void LearnMove(StringKey moveName, MoveLearnMethod method, byte index) { if (index == 255) { diff --git a/PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs b/PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs index 04ef606..4f17c53 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs @@ -8,6 +8,10 @@ namespace PkmnLib.Dynamic.ScriptHandling.Registry; [UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)] public abstract class Plugin { + protected Plugin(PluginConfiguration configuration) + { + } + /// /// The name of the plugin. Mostly used for debugging purposes. /// @@ -23,4 +27,8 @@ public abstract class Plugin /// Run the registration of the plugin when we're building the library. /// public abstract void Register(ScriptRegistry registry); +} + +public abstract class PluginConfiguration +{ } \ No newline at end of file diff --git a/PkmnLib.Tests/Dataloader/TypeDataloaderTests.cs b/PkmnLib.Tests/Dataloader/TypeDataloaderTests.cs index cf9f1dd..6b1e75f 100644 --- a/PkmnLib.Tests/Dataloader/TypeDataloaderTests.cs +++ b/PkmnLib.Tests/Dataloader/TypeDataloaderTests.cs @@ -10,5 +10,13 @@ public class TypeDataloaderTests using var file = File.Open("Data/Types.csv", FileMode.Open); var library = TypeDataLoader.LoadTypeLibrary(file); Assert.That(library, Is.Not.Null); + + var fire = library.TryGetTypeIdentifier("Fire", out var fireId); + Assert.That(fire, Is.True); + var grass = library.TryGetTypeIdentifier("Grass", out var grassId); + Assert.That(grass, Is.True); + + var fireEffectiveness = library.GetSingleEffectiveness(fireId, grassId); + Assert.That(fireEffectiveness, Is.EqualTo(2.0f)); } } \ No newline at end of file diff --git a/PkmnLib.Tests/Integration/IntegrationTestRunner.cs b/PkmnLib.Tests/Integration/IntegrationTestRunner.cs new file mode 100644 index 0000000..17aa80a --- /dev/null +++ b/PkmnLib.Tests/Integration/IntegrationTestRunner.cs @@ -0,0 +1,101 @@ +using System.Collections; +using System.Text.Json; +using PkmnLib.Dataloader; +using PkmnLib.Dynamic.Libraries; +using PkmnLib.Dynamic.Models; +using PkmnLib.Plugin.Gen7; +using PkmnLib.Static.Libraries; +using PkmnLib.Static.Species; +using PkmnLib.Tests.Integration.Models; + +namespace PkmnLib.Tests.Integration; + +public class IntegrationTestRunner +{ + private static IEnumerable TestCases + { + get + { + var files = Directory.GetFiles("Integration/Tests", "*.json"); + var serializerOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + foreach (var file in files) + { + var json = File.ReadAllText(file); + var test = JsonSerializer.Deserialize(json, serializerOptions)!; + yield return new TestCaseData(test).SetName(test.Name).SetDescription(test.Description); + } + } + } + + private static IDynamicLibrary LoadLibrary() + { + using var typesFile = File.Open("Data/Types.csv", FileMode.Open); + var types = TypeDataLoader.LoadTypeLibrary(typesFile); + using var naturesFile = File.Open("Data/Natures.csv", FileMode.Open); + var natures = NatureDataLoader.LoadNatureLibrary(naturesFile); + using var movesFile = File.Open("Data/Moves.json", FileMode.Open); + var moves = MoveDataLoader.LoadMoves(movesFile, types); + using var itemsFile = File.Open("Data/Items.json", FileMode.Open); + var items = ItemDataLoader.LoadItems(itemsFile); + using var abilitiesFile = File.Open("Data/Abilities.json", FileMode.Open); + var abilities = AbilityDataLoader.LoadAbilities(abilitiesFile); + using var growthRatesFile = File.Open("Data/GrowthRates.json", FileMode.Open); + var growthRates = GrowthRateDataLoader.LoadGrowthRates(growthRatesFile); + using var speciesFile = File.Open("Data/Pokemon.json", FileMode.Open); + var species = SpeciesDataLoader.LoadSpecies(speciesFile, types); + + var staticLibrary = new StaticLibraryImpl(new LibrarySettings() + { + MaxLevel = 100, + ShinyRate = 4096, + }, species, moves, abilities, types, natures, growthRates, items); + + var dynamicLibrary = DynamicLibraryImpl.Create(staticLibrary, [ + new Gen7Plugin(new Gen7PluginConfiguration() + { + DamageCalculatorHasRandomness = false, + }), + ]); + return dynamicLibrary; + } + + [TestCaseSource(nameof(TestCases))] + public void RunIntegrationTest(IntegrationTestModel test) + { + var library = LoadLibrary(); + + var parties = test.BattleSetup.Parties.Select(IBattleParty (x) => + { + var party = new PokemonParty(6); + for (var index = 0; index < x.Pokemon.Length; index++) + { + var pokemon = x.Pokemon[index]; + Assert.That(library.StaticLibrary.Species.TryGet(pokemon.Species, out var species), Is.True); + var mon = new PokemonImpl(library, species!, species!.GetDefaultForm(), new AbilityIndex + { + IsHidden = false, + Index = 0, + }, + pokemon.Level, 0, Gender.Genderless, 0, "hardy"); + foreach (var move in pokemon.Moves) + { + mon.LearnMove(move, MoveLearnMethod.Unknown, 255); + } + + party.SwapInto(mon, index); + } + + return new BattlePartyImpl(party, x.Indices.Select(y => new ResponsibleIndex(y[0], y[1])).ToArray()); + }).ToArray(); + var battle = new BattleImpl(library, parties, test.BattleSetup.CanFlee, test.BattleSetup.NumberOfSides, + test.BattleSetup.PositionsPerSide); + + foreach (var action in test.Actions) + { + action.Execute(battle); + } + } +} \ No newline at end of file diff --git a/PkmnLib.Tests/Integration/Models/IntegrationTestAction.cs b/PkmnLib.Tests/Integration/Models/IntegrationTestAction.cs new file mode 100644 index 0000000..c0df104 --- /dev/null +++ b/PkmnLib.Tests/Integration/Models/IntegrationTestAction.cs @@ -0,0 +1,79 @@ +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using CSPath; +using PkmnLib.Dynamic.Models; +using PkmnLib.Dynamic.Models.Choices; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace PkmnLib.Tests.Integration.Models; + + +[JsonDerivedType(typeof(SetPokemonAction), "setPokemon")] +[JsonDerivedType(typeof(SetMoveChoiceAction), "setMoveChoice")] +[JsonDerivedType(typeof(SetPassChoiceAction), "setPassChoice")] +[JsonDerivedType(typeof(AssertAction), "assert")] +public abstract class IntegrationTestAction +{ + public abstract void Execute(IBattle battle); +} + +public class SetPokemonAction : IntegrationTestAction +{ + public List Place { get; set; } = null!; + public List FromParty { get; set; } = null!; + + public override void Execute(IBattle battle) + { + var mon = battle.Parties[FromParty[0]].Party[FromParty[1]]; + battle.Sides[Place[0]].SwapPokemon(Place[1], mon); + } +} + +public class SetMoveChoiceAction : IntegrationTestAction +{ + public List Place { get; set; } = null!; + public string Move { get; set; } = null!; + public List Target { get; set; } = null!; + + + /// + public override void Execute(IBattle battle) + { + var user = battle.Sides[Place[0]].Pokemon[Place[1]]; + Assert.That(user, Is.Not.Null); + var move = user.Moves.First(m => m?.MoveData.Name == Move); + Assert.That(move, Is.Not.Null); + var res = battle.TrySetChoice(new MoveChoice(user, move, Target[0], Target[1])); + Assert.That(res, Is.True); + } +} + +public class SetPassChoiceAction : IntegrationTestAction +{ + public List Place { get; set; } = null!; + + /// + public override void Execute(IBattle battle) + { + var user = battle.Sides[Place[0]].Pokemon[Place[1]]; + Assert.That(user, Is.Not.Null); + var res = battle.TrySetChoice(new PassChoice(user)); + Assert.That(res, Is.True); + } +} + +public class AssertAction : IntegrationTestAction +{ + public string Value { get; set; } = null!; + public JsonNode Expected { get; set; } = null!; + + /// + public override void Execute(IBattle battle) + { + var list = battle.Path(Value).ToList(); + var value = list.Count == 1 ? list[0] : list; + + var serialized = JsonSerializer.Serialize(value); + Assert.That(serialized, Is.EqualTo(Expected.ToJsonString())); + } +} \ No newline at end of file diff --git a/PkmnLib.Tests/Integration/Models/IntegrationTestModel.cs b/PkmnLib.Tests/Integration/Models/IntegrationTestModel.cs new file mode 100644 index 0000000..7e38c64 --- /dev/null +++ b/PkmnLib.Tests/Integration/Models/IntegrationTestModel.cs @@ -0,0 +1,31 @@ +namespace PkmnLib.Tests.Integration.Models; + +public class IntegrationTestModel +{ + public string Name { get; set; } = null!; + public string Description { get; set; } = null!; + public IntegrationTestBattleSetup BattleSetup { get; set; } = null!; + public IntegrationTestAction[] Actions { get; set; } = null!; +} + +public class IntegrationTestBattleSetup +{ + public int Seed { get; set; } + public bool CanFlee { get; set; } + public byte NumberOfSides { get; set; } + public byte PositionsPerSide { get; set; } + public IntegrationTestParty[] Parties { get; set; } = null!; +} + +public class IntegrationTestParty +{ + public List> Indices { get; set; } = null!; + public IntegrationTestPokemon[] Pokemon { get; set; } = null!; +} + +public class IntegrationTestPokemon +{ + public string Species { get; set; } = null!; + public LevelInt Level { get; set; } + public string[] Moves { get; set; } = null!; +} \ No newline at end of file diff --git a/PkmnLib.Tests/Integration/Tests/BasicSingleTurn.json b/PkmnLib.Tests/Integration/Tests/BasicSingleTurn.json new file mode 100644 index 0000000..1b4a16a --- /dev/null +++ b/PkmnLib.Tests/Integration/Tests/BasicSingleTurn.json @@ -0,0 +1,64 @@ +{ + "name": "BasicSingleTurn", + "description": "An extremely basic test that checks if a single turn of a battle works correctly. This test has two Pokemon, a Charizard and a Venusaur, and the Charizard uses Ember on the Venusaur.", + "battleSetup": { + "seed": 10, + "canFlee": false, + "numberOfSides": 2, + "positionsPerSide": 1, + "parties": [ + { + "indices": [[0, 0]], + "pokemon": [ + { + "species": "charizard", + "level": 50, + "moves": ["ember"] + } + ] + }, + { + "indices": [[1, 0]], + "pokemon": [ + { + "species": "venusaur", + "level": 50, + "moves": ["vine_whip"] + } + ] + } + ] + }, + "actions": [ + { + "$type": "setPokemon", + "place": [0, 0], + "fromParty": [0, 0] + }, + { + "$type": "setPokemon", + "place": [1, 0], + "fromParty": [1, 0] + }, + { + "$type": "assert", + "value": ".Sides[1].Pokemon[0].CurrentHealth", + "expected": 140 + }, + { + "$type": "setMoveChoice", + "place": [0, 0], + "move": "ember", + "target": [1, 0] + }, + { + "$type": "setPassChoice", + "place": [1, 0] + }, + { + "$type": "assert", + "value": ".Sides[1].Pokemon[0].CurrentHealth", + "expected": 78 + } + ] +} \ No newline at end of file diff --git a/PkmnLib.Tests/PkmnLib.Tests.csproj b/PkmnLib.Tests/PkmnLib.Tests.csproj index 52c803f..c4ddbd3 100644 --- a/PkmnLib.Tests/PkmnLib.Tests.csproj +++ b/PkmnLib.Tests/PkmnLib.Tests.csproj @@ -10,7 +10,8 @@ - + + @@ -19,19 +20,23 @@ - - - + + + + - + - - Always - + + Always + + + Always + diff --git a/Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs b/Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs index 30d9234..65eeac9 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs @@ -3,8 +3,21 @@ using PkmnLib.Plugin.Gen7.Libraries; namespace PkmnLib.Plugin.Gen7; +public class Gen7PluginConfiguration : PluginConfiguration +{ + public bool DamageCalculatorHasRandomness { get; set; } +} + public class Gen7Plugin : Dynamic.ScriptHandling.Registry.Plugin { + private readonly Gen7PluginConfiguration _configuration; + + /// + public Gen7Plugin(PluginConfiguration configuration) : base(configuration) + { + _configuration = (Gen7PluginConfiguration)configuration; + } + /// public override string Name => "Gen7"; @@ -16,7 +29,7 @@ public class Gen7Plugin : Dynamic.ScriptHandling.Registry.Plugin { registry.RegisterAssemblyScripts(typeof(Gen7Plugin).Assembly); registry.RegisterBattleStatCalculator(new Gen7BattleStatCalculator()); - registry.RegisterDamageCalculator(new Gen7DamageCalculator(true)); + registry.RegisterDamageCalculator(new Gen7DamageCalculator(_configuration.DamageCalculatorHasRandomness)); registry.RegisterMiscLibrary(new Gen7MiscLibrary()); } } \ No newline at end of file