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