diff --git a/PkmnLib.Dynamic/Libraries/DynamicLibrary.cs b/PkmnLib.Dynamic/Libraries/DynamicLibrary.cs
index 970dc20..85ec479 100644
--- a/PkmnLib.Dynamic/Libraries/DynamicLibrary.cs
+++ b/PkmnLib.Dynamic/Libraries/DynamicLibrary.cs
@@ -50,7 +50,7 @@ public class DynamicLibraryImpl : IDynamicLibrary
/// Initializes a new instance of the class, with the given
/// plugins and static library.
///
- public static IDynamicLibrary Create(IEnumerable plugins)
+ public static IDynamicLibrary Create(IEnumerable plugins)
{
var load = LibraryLoader.LoadPlugins(plugins);
diff --git a/PkmnLib.Dynamic/Libraries/LibraryLoader.cs b/PkmnLib.Dynamic/Libraries/LibraryLoader.cs
index dcdb2b6..98ce651 100644
--- a/PkmnLib.Dynamic/Libraries/LibraryLoader.cs
+++ b/PkmnLib.Dynamic/Libraries/LibraryLoader.cs
@@ -18,7 +18,7 @@ public static class LibraryLoader
///
/// Loads plugins and creates a static library from them.
///
- public static LoadResult LoadPlugins(IEnumerable plugins)
+ public static LoadResult LoadPlugins(IEnumerable plugins)
{
var registry = new ScriptRegistry();
var orderedPlugins = plugins.OrderBy(x => x.LoadOrder).ToList();
@@ -39,7 +39,7 @@ public static class LibraryLoader
return new LoadResult(registry, scriptResolver, staticLibrary);
}
- private static StaticLibraryImpl CreateStaticLibrary(IReadOnlyList plugins)
+ private static StaticLibraryImpl CreateStaticLibrary(IReadOnlyList plugins)
{
var resourceProviders = plugins.OfType().ToList();
var settings = resourceProviders.Select(x => x.Settings).LastOrDefault(x => x != null);
diff --git a/PkmnLib.Dynamic/Models/LearnedMove.cs b/PkmnLib.Dynamic/Models/LearnedMove.cs
index 76dbff2..2cfb231 100644
--- a/PkmnLib.Dynamic/Models/LearnedMove.cs
+++ b/PkmnLib.Dynamic/Models/LearnedMove.cs
@@ -173,4 +173,7 @@ public class LearnedMoveImpl : ILearnedMove
{
CurrentPp = Math.Min(uses, MaxPp);
}
+
+ ///
+ public override string ToString() => MoveData.Name;
}
\ No newline at end of file
diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs
index f80f596..f7ffaf8 100644
--- a/PkmnLib.Dynamic/Models/Pokemon.cs
+++ b/PkmnLib.Dynamic/Models/Pokemon.cs
@@ -1261,6 +1261,14 @@ public class PokemonImpl : ScriptSource, IPokemon
side.CollectScripts(scripts);
}
}
+
+ ///
+ public override string ToString()
+ {
+ if (!string.IsNullOrEmpty(Nickname))
+ return $"{Nickname} ({Species.Name})";
+ return Species.Name;
+ }
}
///
diff --git a/PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs b/PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs
index c9da2ed..675f3ea 100644
--- a/PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs
+++ b/PkmnLib.Dynamic/ScriptHandling/Registry/Plugin.cs
@@ -2,20 +2,37 @@ using JetBrains.Annotations;
namespace PkmnLib.Dynamic.ScriptHandling.Registry;
+public interface IPlugin
+{
+ ///
+ /// The name of the plugin. Mostly used for debugging purposes.
+ ///
+ string Name { get; }
+
+ ///
+ /// When the plugin should be loaded. Lower values are loaded first.
+ /// 0 should be reserved for the core battle scripts.
+ ///
+ uint LoadOrder { get; }
+
+ ///
+ /// Run the registration of the plugin when we're building the library.
+ ///
+ void Register(ScriptRegistry registry);
+}
+
///
/// A plugin is a way to register scripts and other dynamic components to the script registry.
///
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
-public abstract class Plugin
+public abstract class Plugin : IPlugin where TConfiguration : IPluginConfiguration
{
- ///
- protected Plugin()
- {
- }
+ public TConfiguration Configuration { get; }
- ///
- protected Plugin(PluginConfiguration configuration)
+ ///
+ protected Plugin(TConfiguration configuration)
{
+ Configuration = configuration;
}
///
@@ -38,4 +55,4 @@ public abstract class Plugin
///
/// Base class for plugin configuration.
///
-public abstract class PluginConfiguration;
\ No newline at end of file
+public interface IPluginConfiguration;
\ No newline at end of file
diff --git a/PkmnLib.Tests/Integration/IntegrationTestRunner.cs b/PkmnLib.Tests/Integration/IntegrationTestRunner.cs
index d1a3ec0..a544878 100644
--- a/PkmnLib.Tests/Integration/IntegrationTestRunner.cs
+++ b/PkmnLib.Tests/Integration/IntegrationTestRunner.cs
@@ -1,6 +1,8 @@
using System.Text.Json;
using EnumerableAsyncProcessor.Extensions;
+using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models;
+using PkmnLib.Plugin.Gen7;
using PkmnLib.Static.Species;
using PkmnLib.Tests.Integration.Models;
@@ -10,7 +12,7 @@ public class IntegrationTestRunner
{
public static IEnumerable> TestCases()
{
- var files = Directory.GetFiles("Integration/Tests", "*.json");
+ var files = Directory.GetFiles("../../../../PkmnLib.Tests/Integration/Tests", "*.json");
var serializerOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
@@ -32,7 +34,13 @@ public class IntegrationTestRunner
[Test, MethodDataSource(nameof(TestCases))]
public async Task RunIntegrationTest(IntegrationTestModel test)
{
- var library = LibraryHelpers.LoadLibrary();
+ var library = DynamicLibraryImpl.Create([
+ new Gen7Plugin(new Gen7PluginConfiguration
+ {
+ DamageCalculatorHasRandomness = true,
+ }),
+ ]);
+
await TestContext.Current!.OutputWriter.WriteLineAsync("File: " + $"file://{test.FileName}");
TestContext.Current.AddArtifact(new Artifact
{
diff --git a/PkmnLib.Tests/Integration/Models/IntegrationTestAction.cs b/PkmnLib.Tests/Integration/Models/IntegrationTestAction.cs
index 4f4daf3..baf5bd3 100644
--- a/PkmnLib.Tests/Integration/Models/IntegrationTestAction.cs
+++ b/PkmnLib.Tests/Integration/Models/IntegrationTestAction.cs
@@ -3,6 +3,7 @@ using System.Text.Json.Serialization;
using CSPath;
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
+using TUnit.Core.Logging;
using JsonSerializer = System.Text.Json.JsonSerializer;
namespace PkmnLib.Tests.Integration.Models;
@@ -23,6 +24,7 @@ public class SetPokemonAction : IntegrationTestAction
{
var mon = battle.Parties[FromParty[0]].Party[FromParty[1]];
battle.Sides[Place[0]].SwapPokemon(Place[1], mon);
+ Console.WriteLine($"Set: {mon} to place {Place[0]}:{Place[1]}");
return Task.CompletedTask;
}
}
@@ -42,6 +44,8 @@ public class SetMoveChoiceAction : IntegrationTestAction
await Assert.That(move).IsNotNull();
var res = battle.TrySetChoice(new MoveChoice(user, move!, Target[0], Target[1]));
await Assert.That(res).IsTrue();
+ var target = battle.Sides[Target[0]].Pokemon[Target[1]];
+ Console.WriteLine($"Choice: {user} used {move} on {target} ({Target[0]}:{Target[1]})");
}
}
@@ -56,13 +60,14 @@ public class SetPassChoiceAction : IntegrationTestAction
await Assert.That(user).IsNotNull();
var res = battle.TrySetChoice(new PassChoice(user!));
await Assert.That(res).IsTrue();
+ Console.WriteLine($"Choice: {user} Pass");
}
}
public class AssertAction : IntegrationTestAction
{
- public string Value { get; set; } = null!;
- public JsonNode Expected { get; set; } = null!;
+ public string Value { get; init; } = null!;
+ public JsonNode Expected { get; init; } = null!;
///
public override async Task Execute(IBattle battle)
@@ -71,6 +76,9 @@ public class AssertAction : IntegrationTestAction
var value = list.Count == 1 ? list[0] : list;
var serialized = JsonSerializer.Serialize(value);
- await Assert.That(serialized).IsEqualTo(Expected.ToJsonString());
+#pragma warning disable TUnitAssertions0003
+ await Assert.That(serialized, Value).IsEqualTo(Expected.ToJsonString());
+#pragma warning restore TUnitAssertions0003
+ Console.WriteLine($"Assert: {Value} = {serialized}");
}
}
\ No newline at end of file
diff --git a/PkmnLib.Tests/Integration/Tests/BasicSingleTurn.json b/PkmnLib.Tests/Integration/Tests/BasicSingleTurn.json
index 1b4a16a..7ea3bf2 100644
--- a/PkmnLib.Tests/Integration/Tests/BasicSingleTurn.json
+++ b/PkmnLib.Tests/Integration/Tests/BasicSingleTurn.json
@@ -8,22 +8,36 @@
"positionsPerSide": 1,
"parties": [
{
- "indices": [[0, 0]],
+ "indices": [
+ [
+ 0,
+ 0
+ ]
+ ],
"pokemon": [
{
"species": "charizard",
"level": 50,
- "moves": ["ember"]
+ "moves": [
+ "ember"
+ ]
}
]
},
{
- "indices": [[1, 0]],
+ "indices": [
+ [
+ 1,
+ 0
+ ]
+ ],
"pokemon": [
{
"species": "venusaur",
"level": 50,
- "moves": ["vine_whip"]
+ "moves": [
+ "vine_whip"
+ ]
}
]
}
@@ -32,13 +46,25 @@
"actions": [
{
"$type": "setPokemon",
- "place": [0, 0],
- "fromParty": [0, 0]
+ "place": [
+ 0,
+ 0
+ ],
+ "fromParty": [
+ 0,
+ 0
+ ]
},
{
"$type": "setPokemon",
- "place": [1, 0],
- "fromParty": [1, 0]
+ "place": [
+ 1,
+ 0
+ ],
+ "fromParty": [
+ 1,
+ 0
+ ]
},
{
"$type": "assert",
@@ -47,18 +73,27 @@
},
{
"$type": "setMoveChoice",
- "place": [0, 0],
+ "place": [
+ 0,
+ 0
+ ],
"move": "ember",
- "target": [1, 0]
+ "target": [
+ 1,
+ 0
+ ]
},
{
"$type": "setPassChoice",
- "place": [1, 0]
+ "place": [
+ 1,
+ 0
+ ]
},
{
"$type": "assert",
"value": ".Sides[1].Pokemon[0].CurrentHealth",
- "expected": 78
+ "expected": 84
}
]
}
\ No newline at end of file
diff --git a/PkmnLib.Tests/PkmnLib.Tests.csproj b/PkmnLib.Tests/PkmnLib.Tests.csproj
index 73df3e9..08d006b 100644
--- a/PkmnLib.Tests/PkmnLib.Tests.csproj
+++ b/PkmnLib.Tests/PkmnLib.Tests.csproj
@@ -30,16 +30,7 @@
-
-
-
- Always
-
-
- Always
-
-
-
+
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs b/Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs
index 6dc7822..ae93c46 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Gen7Plugin.cs
@@ -1,27 +1,37 @@
using System;
using PkmnLib.Plugin.Gen7.Libraries.Battling;
using PkmnLib.Static.Libraries;
+using PkmnLib.Static.Species;
namespace PkmnLib.Plugin.Gen7;
-public class Gen7PluginConfiguration : PluginConfiguration
+public class Gen7PluginConfiguration : IPluginConfiguration
{
- public bool DamageCalculatorHasRandomness { get; set; } = true;
+ ///
+ /// Whether the damage calculator has randomness or not. If set to false, the damage calculator will always return
+ /// the same value for the same inputs. If set to true, the damage calculator will randomize the damage output
+ /// between 0.85 and 1.00 of the calculated damage.
+ ///
+ /// This should be set to true for most cases, as it simulates the actual damage calculation in the games. Only
+ /// set to false for testing purposes.
+ ///
+ public bool DamageCalculatorHasRandomness { get; init; } = true;
+
+ ///
+ /// The number of times a species has been caught. This is used for critical capture calculations.
+ ///
+ public Func TimesSpeciesCaught { get; init; } = _ => 0;
}
-public class Gen7Plugin : Dynamic.ScriptHandling.Registry.Plugin, IResourceProvider
+public class Gen7Plugin : Plugin, IResourceProvider
{
- private readonly Gen7PluginConfiguration _configuration;
-
- public Gen7Plugin()
+ public Gen7Plugin() : base(new Gen7PluginConfiguration())
{
- _configuration = new Gen7PluginConfiguration();
}
///
- public Gen7Plugin(PluginConfiguration configuration) : base(configuration)
+ public Gen7Plugin(Gen7PluginConfiguration configuration) : base(configuration)
{
- _configuration = (Gen7PluginConfiguration)configuration;
}
///
@@ -35,9 +45,9 @@ public class Gen7Plugin : Dynamic.ScriptHandling.Registry.Plugin, IResourceProvi
{
registry.RegisterAssemblyScripts(typeof(Gen7Plugin).Assembly);
registry.RegisterBattleStatCalculator(new Gen7BattleStatCalculator());
- registry.RegisterDamageCalculator(new Gen7DamageCalculator(_configuration.DamageCalculatorHasRandomness));
+ registry.RegisterDamageCalculator(new Gen7DamageCalculator(Configuration.DamageCalculatorHasRandomness));
registry.RegisterMiscLibrary(new Gen7MiscLibrary());
- registry.RegisterCaptureLibrary(new Gen7CaptureLibrary());
+ registry.RegisterCaptureLibrary(new Gen7CaptureLibrary(Configuration));
}
///
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7CaptureLibrary.cs b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7CaptureLibrary.cs
index 7f9c06f..481904a 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7CaptureLibrary.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7CaptureLibrary.cs
@@ -5,6 +5,13 @@ namespace PkmnLib.Plugin.Gen7.Libraries.Battling;
public class Gen7CaptureLibrary : ICaptureLibrary
{
+ private readonly Gen7PluginConfiguration _configuration;
+
+ public Gen7CaptureLibrary(Gen7PluginConfiguration configuration)
+ {
+ _configuration = configuration;
+ }
+
///
public CaptureResult TryCapture(IPokemon target, IItem captureItem, IBattleRandom random)
{
@@ -22,10 +29,10 @@ public class Gen7CaptureLibrary : ICaptureLibrary
byte bonusStatus = 1;
target.RunScriptHook(x => x.ChangeCatchRateBonus(target, captureItem, ref bonusStatus));
- var modifiedCatchRate = (3.0 * maxHealth - 2.0 * currentHealth) * catchRate * bonusBall / (3.0 * maxHealth);
+ var modifiedCatchRate = (3.0f * maxHealth - 2.0f * currentHealth) * catchRate * bonusBall / (3.0f * maxHealth);
modifiedCatchRate *= bonusStatus;
- var shakeProbability = 65536 / Math.Pow(255 / modifiedCatchRate, 0.1875);
+ var shakeProbability = 65536 / Math.Pow(255 / modifiedCatchRate, 0.1875f);
byte shakes = 0;
if (modifiedCatchRate >= 255)
{
@@ -33,7 +40,26 @@ public class Gen7CaptureLibrary : ICaptureLibrary
}
else
{
- // FIXME: Implement critical capture
+ var timesCaught = _configuration.TimesSpeciesCaught(target.Species);
+ if (timesCaught > 30)
+ {
+ var criticalCaptureModifier = timesCaught switch
+ {
+ > 600 => 2.5f,
+ > 450 => 2.0f,
+ > 300 => 1.5f,
+ > 150 => 1.0f,
+ > 30 => 0.5f,
+ // Default arm, should be heuristically unreachable (due to the timesCaught > 30 check above)
+ _ => throw new ArgumentOutOfRangeException(),
+ };
+ var criticalCaptureChance = (int)(modifiedCatchRate * criticalCaptureModifier / 6);
+ if (random.GetInt(0, 256) < criticalCaptureChance)
+ {
+ return new CaptureResult(true, 1, true);
+ }
+ }
+
for (var i = 0; i < 4; i++)
{
if (random.GetInt(0, 65536) < shakeProbability)