From d3f61e98dbbd6d80c4e115630bd321f31ccc4402 Mon Sep 17 00:00:00 2001 From: Kurt Date: Sat, 2 Apr 2022 08:44:32 -0700 Subject: [PATCH] Mix respawns and ghosts (#3) --- PermuteMMO.Lib/Permuter.cs | 40 ++++++------- PermuteMMO.Lib/Util/Calculations.cs | 91 +++++++++++++++++++++++++++++ PermuteMMO.Tests/UtilTests.cs | 33 +++++++++++ 3 files changed, 141 insertions(+), 23 deletions(-) create mode 100644 PermuteMMO.Lib/Util/Calculations.cs diff --git a/PermuteMMO.Lib/Permuter.cs b/PermuteMMO.Lib/Permuter.cs index 7adcb65..3a63b7b 100644 --- a/PermuteMMO.Lib/Permuter.cs +++ b/PermuteMMO.Lib/Permuter.cs @@ -9,11 +9,16 @@ namespace PermuteMMO.Lib; public static class Permuter { private const int MaxAlive = 4; - private const int MaxKill = 4; + private const int MaxDead = 4; private const int MaxGhosts = 3; // State tracking - private readonly record struct SpawnState(in int Count, in int Alive = 0, in int Dead = 0, in int Ghost = 0); + private readonly record struct SpawnState(in int Count, in int Alive = 0, in int Dead = 0, in int Ghost = 0) + { + public SpawnState Knockout(int count) => this with { Alive = Alive - count, Dead = Dead + count }; + public SpawnState Generate(int count) => this with { Count = Count - count, Alive = Alive + count, Dead = Dead - count, Ghost = Dead - count }; + public SpawnState AddGhosts(int count) => this with { Alive = Alive - count, Dead = Dead + count, Ghost = count }; + } /// /// Iterates through all possible player actions with the starting and details. @@ -21,7 +26,7 @@ public static class Permuter public static PermuteMeta Permute(SpawnInfo spawner, ulong seed) { var info = new PermuteMeta(spawner); - var state = new SpawnState(spawner.BaseCount); + var state = new SpawnState(spawner.BaseCount) { Dead = MaxDead }; // Generate the encounters! PermuteRecursion(info, spawner.BaseTable, false, seed, state); @@ -41,12 +46,13 @@ private static void PermuteRecursion(PermuteMeta spawn, in ulong table, in bool private static void PermuteOutbreak(PermuteMeta meta, in ulong table, in bool isBonus, in ulong seed, in SpawnState state) { // Re-spawn to capacity - var respawn = Math.Min(state.Count, MaxAlive - state.Alive); + var emptySlots = state.Dead; + var respawn = Math.Min(state.Count, emptySlots); Debug.Assert(respawn != 0); - var reseed = GenerateSpawns(meta, table, isBonus, seed, respawn); + var reseed = GenerateSpawns(meta, table, isBonus, seed, emptySlots); // Update spawn state - var newState = state with { Count = state.Count - respawn, Alive = MaxAlive }; + var newState = state.Generate(respawn); ContinuePermute(meta, table, isBonus, reseed, newState); } @@ -60,12 +66,11 @@ private static void ContinuePermute(PermuteMeta meta, in ulong table, in bool is } // Permute our remaining options - int canKO = state.Count >= MaxAlive ? MaxKill : state.Count; - for (int i = 1; i <= canKO; i++) + for (int i = 1; i <= state.Alive; i++) { var step = (int)Advance.A1 + (i - 1); meta.Start((Advance)step); - var newState = state with { Alive = state.Alive - i, Dead = state.Dead + i }; + var newState = state.Knockout(i); PermuteRecursion(meta, table, isBonus, seed, newState); meta.End(); } @@ -98,7 +103,7 @@ private static void PermuteFinish(PermuteMeta meta, in ulong table, in ulong see private static void PermuteBonusTable(PermuteMeta meta, in ulong seed) { meta.Start(Advance.SB); - var state = new SpawnState(meta.Spawner.BonusCount); + var state = new SpawnState(meta.Spawner.BonusCount) { Dead = MaxDead }; PermuteOutbreak(meta, meta.Spawner.BonusTable, true, seed, state); meta.End(); } @@ -110,26 +115,15 @@ private static void PermuteAddGhosts(PermuteMeta meta, in ulong seed, in ulong t { // Get updated state with added ghosts var ghosts = state.Ghost + i; - var newState = state with { Count = 0, Alive = state.Alive - i, Dead = state.Dead + i, Ghost = ghosts }; + var newState = state.AddGhosts(ghosts); var step = (int)Advance.G1 + (i - 1); // Simulate ghost advancements via camp reset - var gSeed = GetGhostSeed(seed, ghosts); + var gSeed = Calculations.GetGroupSeed(seed, ghosts); meta.Start((Advance)step); PermuteRecursion(meta, table, false, gSeed, newState); meta.End(); } } - - private static ulong GetGhostSeed(in ulong seed, in int ghosts) - { - var rng = new Xoroshiro128Plus(seed); - for (int g = 0; g < ghosts; g++) - { - _ = rng.Next(); - _ = rng.Next(); - } - return rng.Next(); - } } diff --git a/PermuteMMO.Lib/Util/Calculations.cs b/PermuteMMO.Lib/Util/Calculations.cs new file mode 100644 index 0000000..8e372b6 --- /dev/null +++ b/PermuteMMO.Lib/Util/Calculations.cs @@ -0,0 +1,91 @@ +using PKHeX.Core; + +namespace PermuteMMO.Lib; + +/// +/// Runs simple forwards calculations for seeds. +/// +public static class Calculations +{ + /// + /// Gets the group seed value after the provided advance steps. + /// + public static ulong GetGroupSeed(in ulong groupSeed, IEnumerable advances) + { + ulong seed = GetGroupSeed(groupSeed, 4); + foreach (var advance in advances) + { + var count = advance.AdvanceCount(); + var rng = new Xoroshiro128Plus(seed); + for (int i = 0; i < count; i++) + { + _ = rng.Next(); + _ = rng.Next(); // Unknown + } + seed = rng.Next(); // Reset the seed for future spawns. + } + + return seed; + } + + /// + /// Gets the group seed value after spawning the specified count of entities. + /// + public static ulong GetGroupSeed(ulong seed, int count) + { + var rng = new Xoroshiro128Plus(seed); + for (int i = 0; i < count; i++) + { + _ = rng.Next(); + _ = rng.Next(); // Unknown + } + + return rng.Next(); // Reset the seed for future spawns. + } + + /// + /// Gets the slot generation seed (slot, entity seed) that re-spawns at a 1-based index. + /// + /// Group seed to spawn things for this regeneration round. + /// 1-indexed (not 0) spawn index. + /// + public static ulong GetGenerateSeed(in ulong groupSeed, in int spawnIndex) + { + var rng = new Xoroshiro128Plus(groupSeed); + for (int i = 1; i <= spawnIndex; i++) + { + var subSeed = rng.Next(); + _ = rng.Next(); // Unknown + + if (i == spawnIndex) + return subSeed; + } + + throw new ArgumentOutOfRangeException(nameof(spawnIndex)); + } + + /// + /// Gets the entity template seed (slot, entity seed) that re-spawns at a 1-based index. + /// + /// Group seed to spawn things for this regeneration round. + /// 1-indexed (not 0) spawn index. + /// + public static ulong GetEntitySeed(in ulong groupSeed, in int spawnIndex) + { + var rng = new Xoroshiro128Plus(groupSeed); + for (int i = 1; i <= spawnIndex; i++) + { + var subSeed = rng.Next(); + _ = rng.Next(); // Unknown + + if (i != spawnIndex) + continue; + + var poke = new Xoroshiro128Plus(subSeed); + _ = poke.Next(); // slot + return poke.Next(); + } + + throw new ArgumentOutOfRangeException(nameof(spawnIndex)); + } +} diff --git a/PermuteMMO.Tests/UtilTests.cs b/PermuteMMO.Tests/UtilTests.cs index 169eec5..f1807c2 100644 --- a/PermuteMMO.Tests/UtilTests.cs +++ b/PermuteMMO.Tests/UtilTests.cs @@ -1,10 +1,12 @@ using System; using System.Diagnostics; using System.IO; +using FluentAssertions; using Newtonsoft.Json; using PermuteMMO.Lib; using PKHeX.Core; using Xunit; +using static PermuteMMO.Lib.Advance; namespace PermuteMMO.Tests; @@ -31,4 +33,35 @@ public static void CreateJson() string argument = "/select, \"" + fileName + "\""; Process.Start("explorer.exe", argument); } + + [Fact] + public static void Garchomp() + { + var json = new UserEnteredSpawnInfo + { + Seed = "12880307074085126207", + Species = 444, + BaseCount = 9, + BaseTable = "0x85714105CF348588", + BonusCount = 7, + BonusTable = "0x8AE0881E5F939184", + }; + var spawn = json.GetSpawn(); + + ulong seed = json.GetSeed(); + var sequence = new[] { A2, A1, A3 }; + const int index = 2; + + var gs = Calculations.GetGroupSeed(seed, sequence); + var respawnSeed = Calculations.GetGenerateSeed(gs, index); + var entitySeed = Calculations.GetEntitySeed(gs, index); + var result = SpawnGenerator.Generate(respawnSeed, spawn.BonusTable, SpawnType.MMO); + result.IsShiny.Should().BeTrue(); + result.IsAlpha.Should().BeTrue(); + entitySeed.Should().Be(0xc50932b428a734fd); + + var permute = Permuter.Permute(spawn, seed); + var match = permute.Results.Find(z => z.Entity.Seed == respawnSeed); + match.Should().NotBeNull(); + } }