Skip to content

Commit

Permalink
Mix respawns and ghosts (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwsch authored Apr 2, 2022
1 parent 08e9b52 commit d3f61e9
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 23 deletions.
40 changes: 17 additions & 23 deletions PermuteMMO.Lib/Permuter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,24 @@ 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 };
}

/// <summary>
/// Iterates through all possible player actions with the starting <see cref="seed"/> and <see cref="spawner"/> details.
/// </summary>
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);
Expand All @@ -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);
}

Expand All @@ -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();
}
Expand Down Expand Up @@ -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();
}
Expand All @@ -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();
}
}
91 changes: 91 additions & 0 deletions PermuteMMO.Lib/Util/Calculations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using PKHeX.Core;

namespace PermuteMMO.Lib;

/// <summary>
/// Runs simple forwards calculations for seeds.
/// </summary>
public static class Calculations
{
/// <summary>
/// Gets the group seed value after the provided advance steps.
/// </summary>
public static ulong GetGroupSeed(in ulong groupSeed, IEnumerable<Advance> 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;
}

/// <summary>
/// Gets the group seed value after spawning the specified count of entities.
/// </summary>
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.
}

/// <summary>
/// Gets the slot generation seed (slot, entity seed) that re-spawns at a 1-based index.
/// </summary>
/// <param name="groupSeed">Group seed to spawn things for this regeneration round.</param>
/// <param name="spawnIndex">1-indexed (not 0) spawn index.</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
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));
}

/// <summary>
/// Gets the entity template seed (slot, entity seed) that re-spawns at a 1-based index.
/// </summary>
/// <param name="groupSeed">Group seed to spawn things for this regeneration round.</param>
/// <param name="spawnIndex">1-indexed (not 0) spawn index.</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
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));
}
}
33 changes: 33 additions & 0 deletions PermuteMMO.Tests/UtilTests.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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();
}
}

0 comments on commit d3f61e9

Please sign in to comment.