├── PermuteMMO.Lib
├── Generation
│ ├── SpawnType.cs
│ ├── SaveFileParameter.cs
│ ├── EntityResult.cs
│ └── SpawnGenerator.cs
├── Structure
│ ├── MassiveOutbreakSpawnerStatus.cs
│ ├── MassOutbreakSet8a.cs
│ ├── MassiveOutbreakSet8a.cs
│ ├── MassiveOutbreakArea8a.cs
│ ├── MassOutbreakSpawner8a.cs
│ └── MassiveOutbreakSpawner8a.cs
├── Util
│ ├── AreaUtil.cs
│ ├── PermuteDump.cs
│ ├── UserEnteredSpawnInfo.cs
│ ├── BehaviorUtil.cs
│ ├── JsonDecoder.cs
│ ├── Calculations.cs
│ └── SpawnInfo.cs
├── PermuteMMO.Lib.csproj
├── Permutation
│ ├── PermuteResult.cs
│ ├── PermuteMeta.cs
│ └── Advance.cs
├── Properties
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── ConsolePermuter.cs
├── SpawnState.cs
└── Permuter.cs
├── PermuteMMO.ConsoleApp
├── PermuteMMO.ConsoleApp.csproj
└── Program.cs
├── Directory.Build.props
├── .gitattributes
├── README.md
├── PermuteMMO.Tests
├── PermuteMMO.Tests.csproj
├── ForwardTests.cs
├── UtilTests.cs
└── MultiTests.cs
├── .editorconfig
├── PermuteMMO.sln
├── .gitignore
└── LICENSE
/PermuteMMO.Lib/Generation/SpawnType.cs:
--------------------------------------------------------------------------------
1 | namespace PermuteMMO.Lib;
2 |
3 | ///
4 | /// Type of encounter to generate (determining PID re-roll count)
5 | ///
6 | public enum SpawnType
7 | {
8 | Regular = 7 + 0,
9 | MMO = 7 + 12,
10 | Outbreak = 7 + 25,
11 | }
12 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Structure/MassiveOutbreakSpawnerStatus.cs:
--------------------------------------------------------------------------------
1 | namespace PermuteMMO.Lib;
2 |
3 | ///
4 | /// Status of a Massive Mass Outbreak to display on the map.
5 | ///
6 | public enum MassiveOutbreakSpawnerStatus : byte
7 | {
8 | None = 0,
9 | Unrevealed = 1,
10 | Normal = 2,
11 | Star = 3,
12 | Aguav = 4,
13 | }
14 |
--------------------------------------------------------------------------------
/PermuteMMO.ConsoleApp/PermuteMMO.ConsoleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | 12
5 | enable
6 | enable
7 | en
8 | $([System.DateTime]::UtcNow.ToString("yyMMddHHmmss"))
9 |
10 |
11 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Structure/MassOutbreakSet8a.cs:
--------------------------------------------------------------------------------
1 | namespace PermuteMMO.Lib;
2 |
3 | ///
4 | /// Overall block data for Mass Outbreaks, containing all areas and their spawner objects.
5 | ///
6 | public readonly ref struct MassOutbreakSet8a(Span data)
7 | {
8 | public const int SIZE = 0x190;
9 | public const int AreaCount = 5;
10 |
11 | private readonly Span Data = data;
12 |
13 | public MassOutbreakSpawner8a this[int index] => new(Data.Slice(MassOutbreakSpawner8a.SIZE * index, MassOutbreakSpawner8a.SIZE));
14 | }
15 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Structure/MassiveOutbreakSet8a.cs:
--------------------------------------------------------------------------------
1 | namespace PermuteMMO.Lib;
2 |
3 | ///
4 | /// Overall block data for Massive Mass Outbreaks, containing all areas and their spawner objects.
5 | ///
6 | public readonly ref struct MassiveOutbreakSet8a(Span data)
7 | {
8 | public const int SIZE = 0x3980;
9 | public const int AreaCount = 5;
10 |
11 | private readonly Span Data = data;
12 |
13 | public MassiveOutbreakArea8a this[int index] => new(Data.Slice(MassiveOutbreakArea8a.SIZE * index, MassiveOutbreakArea8a.SIZE));
14 | }
15 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Util/AreaUtil.cs:
--------------------------------------------------------------------------------
1 | namespace PermuteMMO.Lib;
2 |
3 | public static class AreaUtil
4 | {
5 | private const string NONE = "(Empty Area Detail)";
6 |
7 | public static string GetAreaName(ulong areaHash) => areaHash switch
8 | {
9 | 0 => NONE,
10 | 0xCBF29CE484222645 => NONE,
11 | 0xE3BBEF047A645A1D => "Obsidian Fieldlands",
12 | 0xE3BBEC047A645504 => "Crimson Mirelands",
13 | 0xE3BBED047A6456B7 => "Cobalt Coastlands",
14 | 0xE3BBEA047A64519E => "Coronet Highlands",
15 | 0xE3BBEB047A645351 => "Alabaster Icelands",
16 | _ => $"Unknown Area (0x{areaHash:X16})",
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/PermuteMMO.Lib.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | True
11 | True
12 | Resources.resx
13 |
14 |
15 |
16 |
17 |
18 | ResXFileCodeGenerator
19 | Resources.Designer.cs
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Structure/MassiveOutbreakArea8a.cs:
--------------------------------------------------------------------------------
1 | using static System.Buffers.Binary.BinaryPrimitives;
2 |
3 | namespace PermuteMMO.Lib;
4 |
5 | ///
6 | /// Massive Mass Outbreak data for a single area, containing multiple spawner objects and some metadata.
7 | ///
8 | public readonly ref struct MassiveOutbreakArea8a(Span data)
9 | {
10 | public const int SIZE = 0xB80;
11 | public const int SpawnerCount = 20;
12 |
13 | private readonly Span Data = data;
14 |
15 | public ulong AreaHash => ReadUInt64LittleEndian(Data);
16 | public bool IsActive => Data[0x8] == 1;
17 | public bool IsValid => AreaHash is not (0 or 0xCBF29CE484222645);
18 |
19 | public MassiveOutbreakSpawner8a this[int index] => new(Data.Slice(0x10 + (MassiveOutbreakSpawner8a.SIZE * index), MassiveOutbreakSpawner8a.SIZE));
20 | }
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PermuteMMO
2 |
3 | Permutes MMO data to find shinies.
4 |
5 | Requires [.NET 6.0](https://dotnet.microsoft.com/download/dotnet/6.0). The executable can be built with any compiler that supports C# 10.
6 |
7 | Usage:
8 | - Compile the ConsoleApp.
9 | - Pick from these options for inputs:
10 | - Put your `main` savedata next to the executable.
11 | - Put your `mmo.bin`/`mo.bin`/`combo.bin` block data (ripped from ram or savedata) next to the executable.
12 | - Fill out a `spawner.json` and put it next to the executable.
13 | - Run the executable, observe console output for steps to obtain.
14 |
15 | It's easy to change the criteria for emitting results (specific genders, specific height & weight, etc) by editing `Program.cs`.
16 |
17 | Refer to the [Wiki](https://github.com/kwsch/PermuteMMO/wiki) for more details on input modes and interpreting outputs.
18 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Util/PermuteDump.cs:
--------------------------------------------------------------------------------
1 | namespace PermuteMMO.Lib;
2 |
3 | public static class PermuteDump
4 | {
5 | public static IEnumerable Dump(PermuteMeta meta)
6 | {
7 | var results = meta.Results;
8 | var groups = results.GroupBy(z => z.Advances.Length);
9 | foreach (var g in groups)
10 | {
11 | var step = g.Key;
12 | var entities = g.ToArray();
13 | var first = entities[0];
14 | var adv = step == 0 ? Advance.RG : first.Advances[step - 1];
15 |
16 | foreach (var line in GetLines(step, adv, entities))
17 | yield return line;
18 | }
19 | }
20 |
21 | private static IEnumerable GetLines(int step, Advance adv, PermuteResult[] entities)
22 | {
23 | yield return "===================";
24 | yield return $"Step {step}: {adv}";
25 | yield return string.Join('|', entities[0].Advances);
26 | foreach (var entity in entities)
27 | {
28 | foreach (var line in entity.Entity.GetLines())
29 | yield return line;
30 | yield return "";
31 | }
32 | yield return "";
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/PermuteMMO.Tests/PermuteMMO.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 | all
17 |
18 |
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 | all
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Structure/MassOutbreakSpawner8a.cs:
--------------------------------------------------------------------------------
1 | using static System.Buffers.Binary.BinaryPrimitives;
2 |
3 | namespace PermuteMMO.Lib;
4 |
5 | ///
6 | /// Mass Outbreak data for an individual spawner, indicating all useful parameters for permutation / display.
7 | ///
8 | public readonly ref struct MassOutbreakSpawner8a(Span data)
9 | {
10 | public const int SIZE = 0x50;
11 |
12 | private readonly Span Data = data;
13 |
14 | public ushort DisplaySpecies => ReadUInt16LittleEndian(Data);
15 | public ushort DisplayForm => ReadUInt16LittleEndian(Data[4..]);
16 | public ulong AreaHash => ReadUInt64LittleEndian(Data[0x18..]);
17 | public float X => ReadSingleLittleEndian(Data[0x20..]);
18 | public float Y => ReadSingleLittleEndian(Data[0x24..]);
19 | public float Z => ReadSingleLittleEndian(Data[0x28..]);
20 | public ulong CountSeed => ReadUInt64LittleEndian(Data[0x30..]);
21 | public ulong GroupSeed => ReadUInt64LittleEndian(Data[0x38..]);
22 | public byte BaseCount => Data[0x40];
23 | public uint SpawnedCount => ReadUInt32LittleEndian(Data[0x44..]);
24 | public bool HasOutbreak => AreaHash is not (0 or 0xCBF29CE484222645);
25 | public bool IsValid => DisplaySpecies is not 0;
26 | }
27 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Util/UserEnteredSpawnInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 |
3 | namespace PermuteMMO.Lib;
4 |
5 | public sealed record UserEnteredSpawnInfo
6 | {
7 | public ushort Species { get; init; }
8 | public string Seed { get; init; } = string.Empty;
9 |
10 | public ulong GetSeed() => ulong.Parse(Seed);
11 |
12 | public int BaseCount { get; init; }
13 | public string BaseTable { get; init; } = string.Empty;
14 |
15 | public int BonusCount { get; init; }
16 | public string BonusTable { get; init; } = string.Empty;
17 |
18 | public SpawnInfo GetSpawn()
19 | {
20 | var table = Parse(BaseTable);
21 | var bonus = Parse(BonusTable);
22 | bool isOutbreak = !HashUtil.IsNonZeroHash(table) && !HashUtil.IsNonZeroHash(bonus);
23 | if (isOutbreak)
24 | {
25 | if (table < 1000)
26 | table = Species;
27 | return SpawnInfo.GetMO(table, BaseCount);
28 | }
29 | return SpawnInfo.GetMMO(table, BaseCount, bonus, BonusCount);
30 | }
31 |
32 | private static ulong Parse(string hex)
33 | {
34 | if (hex.StartsWith("0x"))
35 | hex = hex[2..];
36 | return ulong.Parse(hex, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | # All Files
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | # Solution Files
12 | [*.sln]
13 | indent_style = space
14 | indent_size = 4
15 | insert_final_newline = true
16 | trim_trailing_whitespace = true
17 |
18 | # XML Project Files
19 | [*.csproj]
20 | indent_style = space
21 | indent_size = 2
22 |
23 | # Code Files
24 | [*.cs]
25 | insert_final_newline = true
26 | trim_trailing_whitespace = true
27 | indent_style = space
28 | indent_size = 4
29 | tab_width = 4
30 | end_of_line = crlf
31 | csharp_prefer_braces = when_multiline:warning
32 | dotnet_diagnostic.IDE0047.severity = none
33 | dotnet_diagnostic.IDE0048.severity = none
34 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggest
35 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggest
36 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggest
37 | dotnet_style_parentheses_in_other_operators = always_for_clarity:suggest
38 |
39 | [*.{cs,vb}]
40 | #### Naming styles ####
41 |
42 | # Naming styles
43 |
44 | dotnet_naming_style.begins_with_i.required_prefix = I
45 | dotnet_naming_style.begins_with_i.capitalization = pascal_case
46 |
--------------------------------------------------------------------------------
/PermuteMMO.ConsoleApp/Program.cs:
--------------------------------------------------------------------------------
1 | using PermuteMMO.Lib;
2 |
3 | // Change the criteria for emitting matches here.
4 | PermuteMeta.SatisfyCriteria = (result, advances) => result.IsShiny;
5 |
6 | // If a spawner json exists, spawn from that instead
7 | const string json = "spawner.json";
8 | if (File.Exists(json))
9 | {
10 | var info = JsonDecoder.Deserialize(File.ReadAllText(json));
11 | var spawner = info.GetSpawn();
12 | ConsolePermuter.PermuteSingle(spawner, info.GetSeed(), info.Species);
13 |
14 | Console.WriteLine("Press [ENTER] to exit.");
15 | Console.ReadLine();
16 | return;
17 | }
18 |
19 | const string file = "combo.bin";
20 | Span data_mo, data_mmo;
21 | if (File.Exists(file))
22 | {
23 | Span data = File.ReadAllBytes(file);
24 | data_mo = data[..MassOutbreakSet8a.SIZE];
25 | data_mmo = data.Slice(MassOutbreakSet8a.SIZE, MassiveOutbreakSet8a.SIZE);
26 | }
27 | else
28 | {
29 | const string file_mo = "mo.bin";
30 | if (File.Exists(file_mo))
31 | data_mo = File.ReadAllBytes(file_mo);
32 | else
33 | data_mo = SaveFileParameter.GetMassOutbreakData();
34 |
35 | const string file_mmo = "mmo.bin";
36 | if (File.Exists(file_mmo))
37 | data_mmo = File.ReadAllBytes(file_mmo);
38 | else
39 | data_mmo = SaveFileParameter.GetMassiveMassOutbreakData();
40 | }
41 |
42 | // Compute and print.
43 | ConsolePermuter.PermuteMassiveMassOutbreak(data_mmo);
44 | Console.WriteLine();
45 | Console.WriteLine("==========");
46 | ConsolePermuter.PermuteBlockMassOutbreak(data_mo);
47 |
48 | Console.WriteLine("Press [ENTER] to exit.");
49 | Console.ReadLine();
50 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Structure/MassiveOutbreakSpawner8a.cs:
--------------------------------------------------------------------------------
1 | using static System.Buffers.Binary.BinaryPrimitives;
2 |
3 | namespace PermuteMMO.Lib;
4 |
5 | ///
6 | /// Massive Mass Outbreak data for an individual spawner, indicating all useful parameters for permutation / display.
7 | ///
8 | public readonly ref struct MassiveOutbreakSpawner8a(Span data)
9 | {
10 | public const int SIZE = 0x90;
11 |
12 | private readonly Span Data = data;
13 |
14 | public float X => ReadSingleLittleEndian(Data);
15 | public float Y => ReadSingleLittleEndian(Data[4..]);
16 | public float Z => ReadSingleLittleEndian(Data[8..]);
17 |
18 | public MassiveOutbreakSpawnerStatus Status => (MassiveOutbreakSpawnerStatus)Data[0x10];
19 | public ushort DisplaySpecies => ReadUInt16LittleEndian(Data[0x14..]);
20 | public ushort DisplayForm => ReadUInt16LittleEndian(Data[0x18..]);
21 | public ulong BaseTable => ReadUInt64LittleEndian(Data[0x38..]);
22 | public ulong BonusTable => ReadUInt64LittleEndian(Data[0x40..]);
23 | public ulong AguavSeed => ReadUInt64LittleEndian(Data[0x48..]);
24 | public ulong CountSeed => ReadUInt64LittleEndian(Data[0x50..]);
25 | public ulong GroupSeed => ReadUInt64LittleEndian(Data[0x58..]);
26 | public byte BaseCount => Data[0x60];
27 | public uint SpawnedCount => ReadUInt32LittleEndian(Data[0x64..]);
28 | public ulong SpawnerName => ReadUInt64LittleEndian(Data[0x68..]);
29 | public byte BonusCount => Data[0x74];
30 |
31 | public bool HasBase => BaseTable is not (0 or 0xCBF29CE484222645);
32 | public bool HasBonus => BonusTable is not (0 or 0xCBF29CE484222645);
33 | }
34 |
--------------------------------------------------------------------------------
/PermuteMMO.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.1.32210.238
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PermuteMMO.ConsoleApp", "PermuteMMO.ConsoleApp\PermuteMMO.ConsoleApp.csproj", "{D35AAC6B-C2BA-4573-8C2F-F3F135E0902F}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PermuteMMO.Lib", "PermuteMMO.Lib\PermuteMMO.Lib.csproj", "{91D75103-E981-4FCC-9D3F-F5086641B057}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PermuteMMO.Tests", "PermuteMMO.Tests\PermuteMMO.Tests.csproj", "{C72B86A5-539B-41D0-B18E-852BA72E1A66}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {D35AAC6B-C2BA-4573-8C2F-F3F135E0902F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {D35AAC6B-C2BA-4573-8C2F-F3F135E0902F}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {D35AAC6B-C2BA-4573-8C2F-F3F135E0902F}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {D35AAC6B-C2BA-4573-8C2F-F3F135E0902F}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {91D75103-E981-4FCC-9D3F-F5086641B057}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {91D75103-E981-4FCC-9D3F-F5086641B057}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {91D75103-E981-4FCC-9D3F-F5086641B057}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {91D75103-E981-4FCC-9D3F-F5086641B057}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {C72B86A5-539B-41D0-B18E-852BA72E1A66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {C72B86A5-539B-41D0-B18E-852BA72E1A66}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {C72B86A5-539B-41D0-B18E-852BA72E1A66}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {C72B86A5-539B-41D0-B18E-852BA72E1A66}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {A5BBB609-5402-45D3-A04E-D0F6779D4888}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Util/BehaviorUtil.cs:
--------------------------------------------------------------------------------
1 | using static PKHeX.Core.Species;
2 |
3 | namespace PermuteMMO.Lib;
4 |
5 | public static class BehaviorUtil
6 | {
7 | public static ReadOnlySpan Oblivious =>
8 | [
9 | (ushort)Cyndaquil, // Cyndaquil and Hippopotas can be scared, but the game seems to only
10 | (ushort)Hippopotas, // ever spawn 1 that will run away and 3 that attack you instead.
11 | (ushort)Lickilicky,
12 | (ushort)Lickitung,
13 | (ushort)Magikarp,
14 | (ushort)MrMime,
15 | ];
16 |
17 | public static ReadOnlySpan Skittish =>
18 | [
19 | (ushort)Abra,
20 | (ushort)Aipom,
21 | (ushort)Basculin,
22 | (ushort)Bidoof,
23 | (ushort)Blissey,
24 | (ushort)Bonsly,
25 | (ushort)Budew,
26 | (ushort)Buneary,
27 | (ushort)Chansey,
28 | (ushort)Chatot,
29 | (ushort)Cherubi,
30 | (ushort)Chimchar,
31 | (ushort)Chimecho,
32 | (ushort)Chingling,
33 | (ushort)Clefairy,
34 | (ushort)Cleffa,
35 | (ushort)Combee,
36 | (ushort)Eevee,
37 | (ushort)Finneon,
38 | (ushort)Froslass,
39 | (ushort)Gardevoir,
40 | (ushort)Glameow,
41 | (ushort)Goomy,
42 | (ushort)Happiny,
43 | (ushort)Kirlia,
44 | (ushort)Kricketot,
45 | (ushort)Kricketune,
46 | (ushort)Lickilicky,
47 | (ushort)Lickitung,
48 | (ushort)Lopunny,
49 | (ushort)Lumineon,
50 | (ushort)Magby,
51 | (ushort)Magikarp,
52 | (ushort)Mantine,
53 | (ushort)Mantyke,
54 | (ushort)MimeJr,
55 | (ushort)Misdreavus,
56 | (ushort)Mismagius,
57 | (ushort)MrMime,
58 | (ushort)Munchlax,
59 | (ushort)Pachirisu,
60 | (ushort)Petilil,
61 | (ushort)Pichu,
62 | (ushort)Piplup,
63 | (ushort)Ponyta,
64 | (ushort)Purugly,
65 | (ushort)Ralts,
66 | (ushort)Rowlet,
67 | (ushort)Shellos,
68 | (ushort)Sliggoo,
69 | (ushort)Snorunt,
70 | (ushort)Spheal,
71 | (ushort)Stantler,
72 | (ushort)Starly,
73 | (ushort)Sudowoodo,
74 | (ushort)Swinub,
75 | (ushort)Teddiursa,
76 | (ushort)Togepi,
77 | (ushort)Togetic,
78 | (ushort)Turtwig,
79 | (ushort)Unown,
80 | (ushort)Vulpix,
81 | (ushort)Wurmple,
82 | ];
83 | }
84 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Generation/SaveFileParameter.cs:
--------------------------------------------------------------------------------
1 | using PKHeX.Core;
2 |
3 | namespace PermuteMMO.Lib;
4 |
5 | ///
6 | /// Fetches environment specific values necessary for spawn generation.
7 | ///
8 | public static class SaveFileParameter
9 | {
10 | #region Public Mutable - Useful for DLL consumers
11 |
12 | public static SAV8LA SaveFile { get; set; } = GetFake();
13 | public static PokedexSave8a Pokedex => SaveFile.PokedexSave;
14 | public static byte[] BackingArray => SaveFile.Blocks.GetBlock(0x02168706).Data;
15 | public static bool HasCharm { get; set; } = true;
16 | public static bool UseSaveFileShinyRolls { get; set; }
17 |
18 | public static byte[] GetMassOutbreakData() => SaveFile.GetMassOutbreakData();
19 | public static byte[] GetMassiveMassOutbreakData() => SaveFile.GetMassiveMassOutbreakData();
20 |
21 | public static byte[] GetMassOutbreakData(this SAV8LA sav) => sav.Accessor.GetBlock(0x1E0F1BA3).Data;
22 | public static byte[] GetMassiveMassOutbreakData(this SAV8LA sav) => sav.Accessor.GetBlock(0x7799EB86).Data;
23 |
24 | #endregion
25 |
26 | private static SAV8LA GetFake()
27 | {
28 | var mainPath = AppDomain.CurrentDomain.BaseDirectory;
29 | mainPath = Path.Combine(mainPath, "main");
30 | if (File.Exists(mainPath))
31 | return GetFromFile(mainPath);
32 | return new SAV8LA();
33 | }
34 |
35 | private static SAV8LA GetFromFile(string mainPath)
36 | {
37 | var data = File.ReadAllBytes(mainPath);
38 | var sav = new SAV8LA(data);
39 | UseSaveFileShinyRolls = true;
40 | HasCharm = sav.Inventory.Any(z => z.Items.Any(IsShinyCharm));
41 | return sav;
42 | }
43 |
44 | private static bool IsShinyCharm(InventoryItem item) => item is { Index: 632, Count: not 0 };
45 |
46 | ///
47 | /// Gets the count of shiny rolls the player is permitted to have when rolling a .
48 | ///
49 | /// Encounter species
50 | /// Encounter Spawn type
51 | /// [1,X] iteration of PID rolls permitted
52 | public static int GetRerollCount(in ushort species, SpawnType type)
53 | {
54 | if (!UseSaveFileShinyRolls)
55 | return (int)type;
56 | bool perfect = Pokedex.IsPerfect(species);
57 | bool complete = Pokedex.IsComplete(species);
58 | return 1 + (complete ? 1 : 0) + (perfect ? 2 : 0) + (HasCharm ? 3 : 0) + (int)(type - 7);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Permutation/PermuteResult.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace PermuteMMO.Lib;
4 |
5 | ///
6 | /// wrapper with some utility logic to print to console.
7 | ///
8 | [DebuggerDisplay($"{{{nameof(StepSummary)},nq}}")]
9 | public sealed record PermuteResult(Advance[] Advances, EntityResult Entity)
10 | {
11 | private bool IsBonus => Array.IndexOf(Advances, Advance.CR) != -1;
12 | private int WaveIndex => Advances.Count(adv => adv == Advance.CR);
13 |
14 | public string GetLine(PermuteResult? prev, bool isActionMultiResult, bool hasChildChain)
15 | {
16 | var steps = GetSteps(prev);
17 | var feasibility = GetFeasibility(Advances);
18 | // 37 total characters for the steps:
19 | // 10+7 spawner has 6+(3)+3=12 max permutations, +"CR|", remove last |; (3*12+2)=37.
20 | var line = $"* {steps,-37} >>> {GetWaveIndicator()}Spawn{Entity.Index} = {Entity.GetSummary()}{feasibility}";
21 | if (prev != null || hasChildChain)
22 | line += " ~~ Chain result!";
23 | if (isActionMultiResult)
24 | line += " ~~ Spawns multiple results!";
25 | return line;
26 | }
27 |
28 | private string GetWaveIndicator()
29 | {
30 | if (!IsBonus)
31 | return " ";
32 | var waveIndex = WaveIndex;
33 | if (waveIndex == 1)
34 | return "Bonus ";
35 | return $"Wave {waveIndex}";
36 | }
37 |
38 | private string StepSummary => $"{Entity.Index} {Entity.GroupSeed:X16} " + GetSteps();
39 |
40 | public string GetSteps(PermuteResult? prev = null)
41 | {
42 | var steps = string.Join("|", Advances.Select(z => z.GetName()));
43 | if (prev is not { } p)
44 | return steps;
45 |
46 | var prevSeq = p.GetSteps();
47 | return string.Concat(Enumerable.Repeat("-> ", (prevSeq.Length + 2) / 3)) + steps[(prevSeq.Length + 1)..];
48 | }
49 |
50 | private static string GetFeasibility(ReadOnlySpan advances)
51 | {
52 | if (advances.IsAny(AdvanceExtensions.IsMultiScare))
53 | {
54 | if (advances.IsAny(AdvanceExtensions.IsMultiBeta))
55 | return " -- Skittish: Multi scaring with aggressive!";
56 | return " -- Skittish: Multi scaring!";
57 | }
58 | if (advances.IsAny(AdvanceExtensions.IsMultiBeta))
59 | return " -- Skittish: Aggressive!";
60 |
61 | if (advances.IsAny(z => z == Advance.B1))
62 | {
63 | if (!advances.IsAny(AdvanceExtensions.IsMultiAggressive))
64 | return " -- Skittish: Single advances!";
65 | return " -- Skittish: Mostly aggressive!";
66 | }
67 |
68 | if (advances.IsAny(AdvanceExtensions.IsMultiOblivious))
69 | return " -- Oblivious: Aggressive!";
70 |
71 | if (advances.IsAny(AdvanceExtensions.IsMultiAggressive))
72 | return string.Empty;
73 |
74 | return " -- Single advances!";
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/PermuteMMO.Tests/ForwardTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using PermuteMMO.Lib;
3 | using Xunit;
4 | using static PermuteMMO.Lib.Advance;
5 |
6 | namespace PermuteMMO.Tests;
7 |
8 | ///
9 | /// Aren't these nice to have? Examples??
10 | ///
11 | public sealed class ForwardTests
12 | {
13 | [Theory]
14 | [InlineData(0xA5D779D8831721FD, 10, 6)]
15 | public void First(in ulong seed, in int baseCount, in int bonusCount)
16 | {
17 | var spawner = SpawnInfo.GetMMO(0x7FA3A1DE69BD271E, baseCount, 0x44182B854CD3745D, bonusCount);
18 | var result = Permuter.Permute(spawner, seed);
19 | result.Results.Find(z => z.Entity.PID == 0x6f4edff0).Should().NotBeNull();
20 |
21 | var first = result.Results[0];
22 | var match = RunForwardsRegenerate(seed, result, first);
23 | Assert.NotNull(match);
24 | match.Entity.SlotSeed.Should().Be(first.Entity.SlotSeed);
25 | }
26 |
27 | private static PermuteResult? RunForwardsRegenerate(ulong seed, PermuteMeta result, PermuteResult first)
28 | {
29 | result = result.Copy(); // disassociate but keep same inputs
30 | result.Criteria = (_, _) => true;
31 | var (advances, entityResult) = first;
32 | var steps = AdvanceRemoval.RunForwards(result, advances, seed);
33 | steps.Count.Should().BeGreaterThan(0);
34 |
35 | return result.Results.Find(z => advances.SequenceEqual(z.Advances) && entityResult.Index == z.Entity.Index);
36 | }
37 |
38 | [Theory]
39 | [InlineData(1911689355633755303u, 9, 7)]
40 | public void TestForwards(in ulong seed, in int baseCount, in int bonusCount)
41 | {
42 | var spawner = SpawnInfo.GetMMO(0xECBF77B8F7302126, baseCount, 0x9D713CCF138FD43C, bonusCount);
43 | var result = Permuter.Permute(spawner, seed).Copy();
44 | Advance[] seq = [A1, A1, A2, A4, CR, A2, A2];
45 |
46 | _ = AdvanceRemoval.RunForwards(result, seq, seed);
47 | var expect = result.Results.Where(z => seq.SequenceEqual(z.Advances));
48 | expect.FirstOrDefault(z => z.Entity is { IsShiny: true, Index: 2 }).Should().NotBeNull();
49 | }
50 |
51 | [Theory]
52 | [InlineData(7345882244663053439u, 9, 7)]
53 | public void TestForwardsBeta(in ulong seed, in int baseCount, in int bonusCount)
54 | {
55 | var spawner = SpawnInfo.GetMMO(0x03B40EB53F427739, baseCount, 0xDF3114CC95FDCF22, bonusCount);
56 | var result = Permuter.Permute(spawner, seed).Copy();
57 | Advance[] seq = [B1, B1, B1, B1, B1, G1, CR, A1, A4];
58 |
59 | _ = AdvanceRemoval.RunForwards(result, seq, seed);
60 | var expect = result.Results.Where(z => seq.SequenceEqual(z.Advances));
61 | expect.FirstOrDefault(z => z.Entity is { IsShiny: true, Index: 4 }).Should().NotBeNull();
62 |
63 | var copy = Permuter.Permute(spawner, seed).Copy();
64 | copy.Criteria = (_, _) => true;
65 | var __ = AdvanceRemoval.RunForwards(copy, seq, seed);
66 | var lines = PermuteDump.Dump(copy);
67 | var msg = string.Join(Environment.NewLine, lines);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/PermuteMMO.Tests/UtilTests.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using FluentAssertions;
3 | using Newtonsoft.Json;
4 | using PermuteMMO.Lib;
5 | using PKHeX.Core;
6 | using Xunit;
7 | using static PermuteMMO.Lib.Advance;
8 |
9 | namespace PermuteMMO.Tests;
10 |
11 | public static class UtilTests
12 | {
13 | [Fact]
14 | public static void CreateJson()
15 | {
16 | const ulong bonusTable = 0;
17 | var obj = new UserEnteredSpawnInfo
18 | {
19 | Species = (int)Species.Diglett,
20 | Seed = 0xDEADBABE_BEEFCAFE.ToString(),
21 | BaseCount = 10,
22 | BaseTable = $"0x{0x1122_10F4_7DE9_8115:X16}",
23 | BonusCount = 0,
24 | BonusTable = $"0x{bonusTable:X16}",
25 | };
26 |
27 | var fileName = Path.Combine(Environment.CurrentDirectory, "spawner.json");
28 | var settings = new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate };
29 | var result = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
30 | File.WriteAllText(fileName, result);
31 |
32 | string argument = "/select, \"" + fileName + "\"";
33 | Process.Start("explorer.exe", argument);
34 | }
35 |
36 | [Fact]
37 | public static void Garchomp()
38 | {
39 | var spawn = SpawnInfo.GetMMO(0x85714105CF348588, 9, 0x8AE0881E5F939184, 7);
40 | const ulong seed = 12880307074085126207u;
41 | Advance[] sequence = [A2, A1, A3];
42 | const int index = 2;
43 |
44 | var gs = Calculations.GetGroupSeed(seed, sequence);
45 | var (genSeed, alphaSeed) = Calculations.GetGenerateSeed(gs, index);
46 | var entitySeed = Calculations.GetEntitySeed(gs, index);
47 | if (!spawn.GetNextWave(out var next))
48 | throw new Exception();
49 | var result = SpawnGenerator.Generate(seed, index, genSeed, alphaSeed, next.Set.Table, next.Type, false);
50 | if (result is null)
51 | throw new ArgumentNullException(nameof(result));
52 | result.IsShiny.Should().BeTrue();
53 | result.IsAlpha.Should().BeTrue();
54 | entitySeed.Should().Be(0xc50932b428a734fd);
55 |
56 | var permute = Permuter.Permute(spawn, seed);
57 | var match = permute.Results.Find(z => z.Entity.SlotSeed == genSeed);
58 | match.Should().NotBeNull();
59 | }
60 |
61 | [Fact]
62 | public static void Stantler()
63 | {
64 | var spawn = SpawnInfo.GetMMO(0x5BFA9CCA4ED8142B, 10, 0xC213942F6D31614C, 6);
65 | const ulong seed = 88514016295302425u;
66 |
67 | // Spawn 4 pokemon
68 | var entities = new List();
69 | for (int i = 1; i <= 4; i++)
70 | {
71 | var (genSeed, alphaSeed) = Calculations.GetGenerateSeed(seed, i);
72 | var entity = SpawnGenerator.Generate(seed, i, genSeed, alphaSeed, spawn.Set.Table, spawn.Type, false);
73 | if (entity is null)
74 | throw new ArgumentNullException(nameof(entity));
75 | entities.Add(entity);
76 | }
77 |
78 | var count = entities.Count(z => z.IsAggressive);
79 | count.Should().Be(2);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Visual Studio
3 | #################
4 |
5 | ## Ignore Visual Studio temporary files, build results, and
6 | ## files generated by popular Visual Studio add-ons.
7 |
8 | # User-specific files
9 | *.suo
10 | *.user
11 | *.sln.docstates
12 | *.vs
13 |
14 | # Build results
15 |
16 | [Dd]ebug/
17 | [Rr]elease/
18 | x64/
19 | build/
20 | [Bb]in/
21 | [Oo]bj/
22 |
23 | # MSTest test Results
24 | [Tt]est[Rr]esult*/
25 | [Bb]uild[Ll]og.*
26 |
27 | *_i.c
28 | *_p.c
29 | *.ilk
30 | *.meta
31 | *.obj
32 | *.pch
33 | *.pdb
34 | *.pgc
35 | *.pgd
36 | *.rsp
37 | *.sbr
38 | *.tlb
39 | *.tli
40 | *.tlh
41 | *.tmp
42 | *.tmp_proj
43 | *.log
44 | *.vspscc
45 | *.vssscc
46 | .builds
47 | *.pidb
48 | *.log
49 | *.scc
50 |
51 | # Visual C++ cache files
52 | ipch/
53 | *.aps
54 | *.ncb
55 | *.opensdf
56 | *.sdf
57 | *.cachefile
58 |
59 | # Visual Studio profiler
60 | *.psess
61 | *.vsp
62 | *.vspx
63 |
64 | # Guidance Automation Toolkit
65 | *.gpState
66 |
67 | # ReSharper is a .NET coding add-in
68 | _ReSharper*/
69 | *.[Rr]e[Ss]harper
70 |
71 | # TeamCity is a build add-in
72 | _TeamCity*
73 |
74 | # DotCover is a Code Coverage Tool
75 | *.dotCover
76 |
77 | # NCrunch
78 | *.ncrunch*
79 | .*crunch*.local.xml
80 |
81 | # Installshield output folder
82 | [Ee]xpress/
83 |
84 | # DocProject is a documentation generator add-in
85 | DocProject/buildhelp/
86 | DocProject/Help/*.HxT
87 | DocProject/Help/*.HxC
88 | DocProject/Help/*.hhc
89 | DocProject/Help/*.hhk
90 | DocProject/Help/*.hhp
91 | DocProject/Help/Html2
92 | DocProject/Help/html
93 |
94 | # Click-Once directory
95 | publish/
96 |
97 | # Publish Web Output
98 | *.Publish.xml
99 | *.pubxml
100 |
101 | # NuGet Packages Directory
102 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
103 | packages/
104 |
105 | # Windows Azure Build Output
106 | csx
107 | *.build.csdef
108 |
109 | # Windows Store app package directory
110 | AppPackages/
111 |
112 | # Others
113 | sql/
114 | *.Cache
115 | ClientBin/
116 | [Ss]tyle[Cc]op.*
117 | ~$*
118 | *~
119 | *.dbmdl
120 | *.[Pp]ublish.xml
121 | *.pfx
122 | *.publishsettings
123 |
124 | # RIA/Silverlight projects
125 | Generated_Code/
126 |
127 | # Backup & report files from converting an old project file to a newer
128 | # Visual Studio version. Backup files are not needed, because we have git ;-)
129 | _UpgradeReport_Files/
130 | Backup*/
131 | UpgradeLog*.XML
132 | UpgradeLog*.htm
133 |
134 | # SQL Server files
135 | App_Data/*.mdf
136 | App_Data/*.ldf
137 |
138 |
139 | #############
140 | ## Qt Creator
141 | #############
142 |
143 | *.save
144 | *.autosave
145 |
146 | #############
147 | ## GNU Emacs
148 | #############
149 |
150 | \#*
151 | .\#*
152 | *.elc
153 |
154 |
155 | #############
156 | ## Windows detritus
157 | #############
158 |
159 | # Windows image file caches
160 | Thumbs.db
161 | ehthumbs.db
162 |
163 | # Folder config file
164 | Desktop.ini
165 |
166 | # Recycle Bin used on file shares
167 | $RECYCLE.BIN/
168 |
169 |
170 | #############
171 | ## OS X
172 | #############
173 |
174 | .DS_Store
175 |
176 |
177 | #############
178 | ## C#
179 | #############
180 |
181 | *.resources
182 | pingme.txt
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Generation/EntityResult.cs:
--------------------------------------------------------------------------------
1 | using PKHeX.Core;
2 |
3 | namespace PermuteMMO.Lib;
4 |
5 | ///
6 | /// Spawned Pokémon Data that can be encountered.
7 | ///
8 | public sealed record EntityResult(SlotDetail Slot)
9 | {
10 | private const byte UNSET = byte.MaxValue;
11 | public readonly byte[] IVs = [UNSET, UNSET, UNSET, UNSET, UNSET, UNSET];
12 |
13 | public ulong GroupSeed { get; init; }
14 | public int Index { get; init; }
15 | public ulong SlotSeed { get; init; }
16 | public float SlotRoll { get; init; }
17 | public ulong GenSeed { get; init; }
18 | public ulong AlphaSeed { get; init; }
19 | public int Level { get; init; }
20 |
21 | public uint EC { get; set; }
22 | public uint FakeTID { get; set; }
23 | public uint PID { get; set; }
24 |
25 | public uint ShinyXor { get; set; }
26 | public int RollCountUsed { get; set; }
27 | public int RollCountAllowed { get; set; }
28 | public ushort Species { get; init; }
29 | public ushort Form { get; init; }
30 |
31 | public bool IsShiny { get; set; }
32 | public bool IsAlpha { get; init; }
33 | public byte Ability { get; set; }
34 | public byte Gender { get; set; }
35 | public byte Nature { get; set; }
36 | public byte Height { get; set; }
37 | public byte Weight { get; set; }
38 |
39 | public bool IsOblivious => BehaviorUtil.Oblivious.Contains(Species);
40 | public bool IsSkittish => BehaviorUtil.Skittish.Contains(Species);
41 | public bool IsAggressive => IsAlpha || !(IsSkittish || IsOblivious);
42 |
43 | public string GetSummary()
44 | {
45 | var shiny = IsShiny ? $" {RollCountUsed,2} {(ShinyXor == 0 ? '■' : '*')}" : "";
46 | var ivs = $" {IVs[0]:00}/{IVs[1]:00}/{IVs[2]:00}/{IVs[3]:00}/{IVs[4]:00}/{IVs[5]:00}";
47 | var nature = $" {GameInfo.GetStrings(1).Natures[Nature]}";
48 | var alpha = IsAlpha ? "α-" : " ";
49 | var notAlpha = !IsAlpha ? " -- NOT ALPHA" : "";
50 | var gender = Gender switch
51 | {
52 | 2 => "",
53 | 1 => " (F)",
54 | _ => " (M)",
55 | };
56 | return $"{alpha}{Slot.Name}{gender}:{shiny}{ivs}{nature,-8}{notAlpha}";
57 | }
58 |
59 | public IEnumerable GetLines()
60 | {
61 | var shiny = IsShiny ? $" {RollCountUsed,2} {(ShinyXor == 0 ? '■' : '*')}" : "";
62 | var s = GameInfo.GetStrings(1);
63 | var alpha = IsAlpha ? "α-" : "";
64 | yield return shiny + alpha + Slot.Name;
65 | yield return $"Group Seed: {GroupSeed:X16}";
66 | yield return $"Alpha Move Seed: {AlphaSeed:X16}";
67 | yield return $"Slot Seed: {SlotSeed:X16}";
68 | yield return $"Slot: {SlotRoll:F5}";
69 | yield return $"Level: {Level}";
70 | yield return $"Seed: {GenSeed:X16}";
71 | yield return $" EC: {EC:X8}";
72 | yield return $" PID: {PID:X8}";
73 | yield return $" Flawless IVs: {Slot.FlawlessIVs}";
74 | yield return $" IVs: {string.Join('/', IVs)}";
75 | yield return $" Ability: {Ability}";
76 | yield return $" Gender: {Gender switch { 0 => "M", 1 => "F", _ => "-" }}";
77 | yield return $" Nature: {s.Natures[Nature]}";
78 | yield return $" {Height} | {Weight}";
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Util/JsonDecoder.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using Newtonsoft.Json;
3 | using PKHeX.Core;
4 |
5 | namespace PermuteMMO.Lib;
6 |
7 | ///
8 | /// Decodes the community's json data (ripped from pkNX) with some conversion back to primitives instead of strings.
9 | ///
10 | public static class JsonDecoder
11 | {
12 | ///
13 | /// Wrapper to deserialize the json using whatever package this project is currently using.
14 | ///
15 | public static T Deserialize(string json) where T : class => JsonConvert.DeserializeObject(json)!;
16 |
17 | ///
18 | /// Converts the json string back to a usable dictionary.
19 | ///
20 | public static Dictionary GetDictionary(string json)
21 | {
22 | var obj = Deserialize>(json);
23 | var result = new Dictionary(obj.Count);
24 | foreach (var (key, value) in obj)
25 | {
26 | var hash = ulong.Parse(key[2..], NumberStyles.HexNumber, CultureInfo.InvariantCulture);
27 | foreach (var slot in value)
28 | slot.SetSpecies();
29 | result.Add(hash, value);
30 | }
31 | return result;
32 | }
33 | }
34 |
35 | ///
36 | /// Encounter slot detail.
37 | ///
38 | /// Weight factor used to determine how frequent the encounter is yielded.
39 | /// Community label name with Species-Form
40 | /// Indicates if it is an alpha
41 | /// Level range array
42 | /// Amount of flawless IVs
43 | public sealed record SlotDetail(
44 | [property: JsonProperty("slot")] int Rate,
45 | [property: JsonProperty("name")] string Name,
46 | [property: JsonProperty("alpha")] bool IsAlpha,
47 | [property: JsonProperty("level")] IReadOnlyList Level,
48 | [property: JsonProperty("ivs")] int FlawlessIVs
49 | )
50 | {
51 | public int LevelMin => Level[0];
52 | public int LevelMax => Level[1];
53 | public ushort Species { get; private set; }
54 | public byte Form { get; private set; }
55 | public bool IsSkittish => BehaviorUtil.Skittish.Contains(Species);
56 |
57 | ///
58 | /// Parses the string name into actual indexes.
59 | ///
60 | public void SetSpecies()
61 | {
62 | try
63 | {
64 | string species;
65 | var dash = Name.IndexOf('-');
66 | if (dash > 0)
67 | {
68 | Form = byte.Parse(Name.AsSpan(dash+1));
69 | species = Name[..dash];
70 | }
71 | else
72 | {
73 | species = Name;
74 | }
75 |
76 | if (species == "MimeJr.") // STOP FAKE NAMING SPECIES
77 | species = "Mime Jr.";
78 | if (species == "Mr.Mime") // STOP FAKE NAMING SPECIES
79 | species = "Mr. Mime";
80 |
81 | if (!SpeciesName.TryGetSpecies(species, (int)LanguageID.English, out var id))
82 | throw new Exception();
83 | Species = id;
84 | }
85 | catch (Exception e)
86 | {
87 | Console.WriteLine(e);
88 | throw;
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Util/Calculations.cs:
--------------------------------------------------------------------------------
1 | using PKHeX.Core;
2 |
3 | namespace PermuteMMO.Lib;
4 |
5 | ///
6 | /// Runs simple forwards calculations for seeds.
7 | ///
8 | public static class Calculations
9 | {
10 | ///
11 | /// Gets the group seed value after the provided advance steps.
12 | ///
13 | public static ulong GetGroupSeed(in ulong groupSeed, IEnumerable advances)
14 | {
15 | ulong seed = GetGroupSeed(groupSeed, 4);
16 | foreach (var advance in advances)
17 | {
18 | var count = advance.AdvanceCount();
19 | var rng = new Xoroshiro128Plus(seed);
20 | for (int i = 0; i < count; i++)
21 | {
22 | _ = rng.Next(); // generate/slot seed
23 | _ = rng.Next(); // alpha move
24 | }
25 | seed = rng.Next(); // Reset the seed for future spawns.
26 | }
27 |
28 | return seed;
29 | }
30 |
31 | ///
32 | /// Gets the group seed value after spawning the specified count of entities.
33 | ///
34 | public static ulong GetGroupSeed(ulong seed, int count)
35 | {
36 | var rng = new Xoroshiro128Plus(seed);
37 | for (int i = 0; i < count; i++)
38 | {
39 | _ = rng.Next(); // generate/slot seed
40 | _ = rng.Next(); // alpha move
41 | }
42 |
43 | return rng.Next(); // Reset the seed for future spawns.
44 | }
45 |
46 | ///
47 | /// Gets the slot generation seed (slot, entity seed) that re-spawns at a 1-based index.
48 | ///
49 | /// Group seed to spawn things for this regeneration round.
50 | /// 1-indexed (not 0) spawn index.
51 | ///
52 | public static (ulong Generate, ulong Alpha) GetGenerateSeed(in ulong groupSeed, in int spawnIndex)
53 | {
54 | var rng = new Xoroshiro128Plus(groupSeed);
55 | for (int i = 1; i <= spawnIndex; i++)
56 | {
57 | var subSeed = rng.Next(); // generate/slot seed
58 | var alpha = rng.Next(); // alpha move, don't care
59 |
60 | if (i == spawnIndex)
61 | return (subSeed, alpha);
62 | }
63 |
64 | throw new ArgumentOutOfRangeException(nameof(spawnIndex));
65 | }
66 |
67 | ///
68 | /// Gets the entity template seed (slot, entity seed) that re-spawns at a 1-based index.
69 | ///
70 | /// Group seed to spawn things for this regeneration round.
71 | /// 1-indexed (not 0) spawn index.
72 | ///
73 | public static ulong GetEntitySeed(in ulong groupSeed, in int spawnIndex)
74 | {
75 | var rng = new Xoroshiro128Plus(groupSeed);
76 | for (int i = 1; i <= spawnIndex; i++)
77 | {
78 | var subSeed = rng.Next(); // generate/slot seed
79 | _ = rng.Next(); // alpha move, don't care
80 |
81 | if (i != spawnIndex)
82 | continue;
83 |
84 | var poke = new Xoroshiro128Plus(subSeed);
85 | _ = poke.Next(); // slot
86 | return poke.Next();
87 | }
88 |
89 | throw new ArgumentOutOfRangeException(nameof(spawnIndex));
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Util/SpawnInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 | using PKHeX.Core;
3 |
4 | namespace PermuteMMO.Lib;
5 |
6 | ///
7 | /// Top-level spawner details to feed into the permutation logic.
8 | ///
9 | public sealed record SpawnInfo(SpawnCount Count, SpawnSet Set, SpawnType Type, SpawnInfo? Next = null)
10 | {
11 | private static readonly SpawnCount MMO = new(4, 4);
12 | private static readonly SpawnCount Outbreak = new(4, 4);
13 |
14 | private SpawnInfo? Next { get; set; } = Next;
15 | public bool NoMultiAlpha => Type is SpawnType.Regular or SpawnType.Outbreak;
16 | public bool AllowGhosts => Type is not SpawnType.Regular;
17 | public bool RetainExisting => Type is SpawnType.Regular;
18 |
19 | public string GetSummary(string prefix)
20 | {
21 | var summary = $"{prefix}{this}";
22 | if (Next is not { } x)
23 | return summary;
24 | if (ReferenceEquals(this, x))
25 | return summary + " REPEATING.";
26 | return summary + Environment.NewLine + x.GetSummary(prefix);
27 | }
28 |
29 | public bool GetNextWave([NotNullWhen(true)] out SpawnInfo? next) => (next = Next) != null;
30 |
31 | public SpawnInfo(MassiveOutbreakSpawner8a spawner) : this(MMO, new SpawnSet(spawner.BaseTable, spawner.BaseCount), SpawnType.MMO, GetBonusChain(spawner)) { }
32 | public SpawnInfo(MassOutbreakSpawner8a spawner) : this(Outbreak, new SpawnSet(spawner.DisplaySpecies, spawner.BaseCount), SpawnType.Outbreak) { }
33 |
34 | public static SpawnInfo GetMMO(ulong baseTable, in int baseCount, ulong bonusTable, in int bonusCount)
35 | {
36 | var child = new SpawnInfo(MMO, new SpawnSet(bonusTable, bonusCount), SpawnType.MMO);
37 | return new SpawnInfo(MMO, new SpawnSet(baseTable, baseCount), SpawnType.MMO, child);
38 | }
39 |
40 | public SpawnState GetStartingState()
41 | {
42 | if (Type is SpawnType.Regular)
43 | return SpawnState.Get(Count.GetNextCount());
44 | return SpawnState.Get(Set.Count, Count.MaxAlive);
45 | }
46 |
47 | private static SpawnInfo? GetBonusChain(MassiveOutbreakSpawner8a spawner)
48 | {
49 | if (!spawner.HasBonus)
50 | return null;
51 | return new SpawnInfo(MMO, new SpawnSet(spawner.BonusTable, spawner.BonusCount), SpawnType.MMO);
52 | }
53 |
54 | public static SpawnInfo GetMO(ulong table, int count) => new(Outbreak, new SpawnSet(table, count), SpawnType.Outbreak);
55 |
56 | public static SpawnInfo GetLoop(SpawnCount count, SpawnSet set, SpawnType type)
57 | {
58 | var result = new SpawnInfo(count, set, type);
59 | result.Next = result;
60 | return result;
61 | }
62 | }
63 |
64 | public readonly record struct SpawnSet(ulong Table, int Count);
65 |
66 | public record SpawnCount(int MaxAlive, int MinAlive = 0, ulong CountSeed = 0)
67 | {
68 | public ulong CountSeed { get; set; } = CountSeed;
69 |
70 | public bool IsFixedCount => MinAlive is 0 || MinAlive == MaxAlive;
71 |
72 | public int GetNextCount()
73 | {
74 | if (IsFixedCount)
75 | return MaxAlive;
76 | var rand = new Xoroshiro128Plus(CountSeed);
77 | var delta = MaxAlive - MinAlive;
78 | var result = MinAlive + (int)rand.NextInt((uint)delta + 1);
79 | CountSeed = rand.Next();
80 | return result;
81 | }
82 |
83 | private int PeekNextCount()
84 | {
85 | var rand = new Xoroshiro128Plus(CountSeed);
86 | var delta = MaxAlive - MinAlive;
87 | return MinAlive + (int)rand.NextInt((uint)delta + 1);
88 | }
89 |
90 | public bool CanSpawnMore(int currentMaxAlive)
91 | {
92 | if (IsFixedCount)
93 | return false;
94 |
95 | var nextMaxAlive = PeekNextCount();
96 | if (nextMaxAlive > currentMaxAlive)
97 | return true;
98 | return nextMaxAlive == currentMaxAlive && nextMaxAlive != MaxAlive;
99 | }
100 | }
101 |
102 | public static class HashUtil
103 | {
104 | public static bool IsNonZeroHash(in ulong hash) => hash is not (0 or 0xCBF29CE484222645);
105 | }
106 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace PermuteMMO.Lib.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PermuteMMO.Lib.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to {
65 | /// "0x7FA3A1DE69BD271E": [
66 | /// {
67 | /// "slot": 100,
68 | /// "name": "Pikachu",
69 | /// "alpha": false,
70 | /// "level": [
71 | /// 60,
72 | /// 62
73 | /// ],
74 | /// "ivs": 0
75 | /// },
76 | /// {
77 | /// "slot": 1,
78 | /// "name": "Pikachu",
79 | /// "alpha": true,
80 | /// "level": [
81 | /// 75,
82 | /// 77
83 | /// ],
84 | /// "ivs": 3
85 | /// },
86 | /// {
87 | /// "slot": 10,
88 | /// "name": "Raichu",
89 | /// "alpha": false,
90 | /// "level": [
91 | /// 62,
92 | /// 64
93 | /// ],
94 | /// "ivs": 0
95 | /// },
96 | /// {
97 | /// "slot": 1,
98 | /// "name": "Raichu",
99 | /// "alpha" [rest of string was truncated]";.
100 | ///
101 | internal static string mmo_es {
102 | get {
103 | return ResourceManager.GetString("mmo_es", resourceCulture);
104 | }
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Permutation/PermuteMeta.cs:
--------------------------------------------------------------------------------
1 | namespace PermuteMMO.Lib;
2 |
3 | ///
4 | /// Stores object-type references for cleaner passing internally. Only refer to when done.
5 | ///
6 | public sealed record PermuteMeta(SpawnInfo Spawner, int MaxDepth)
7 | {
8 | ///
9 | /// Global configuration for determining if a is a suitable result.
10 | ///
11 | public static Func, bool> SatisfyCriteria { get; set; } =
12 | (result, _) => result.IsShiny && result.IsAlpha;
13 |
14 | public Func, bool> Criteria { get; set; } = SatisfyCriteria;
15 |
16 | public readonly List Results = [];
17 | private readonly List Advances = new(MaxDepth);
18 |
19 | public PermuteMeta Copy() => new(Spawner, MaxDepth);
20 |
21 | public bool HasResults => Results.Count is not 0;
22 | public SpawnInfo Spawner { get; set; } = Spawner;
23 |
24 | public (bool CanContinue, SpawnInfo Next) AttemptNextWave()
25 | {
26 | if (Advances.Count < MaxDepth && Spawner.GetNextWave(out var next))
27 | return (true, next);
28 | return (false, Spawner);
29 | }
30 |
31 | ///
32 | /// Signals the start of a recursive permutation step.
33 | ///
34 | /// Step taken
35 | public void Start(Advance adv) => Advances.Add(adv); // add to end
36 |
37 | ///
38 | /// Signals the end of a recursive permutation step.
39 | ///
40 | public void End() => Advances.RemoveAt(Advances.Count - 1); // pop off end
41 |
42 | ///
43 | /// Stores a result.
44 | ///
45 | public void AddResult(EntityResult entity)
46 | {
47 | var steps = Advances.ToArray();
48 | var result = new PermuteResult(steps, entity);
49 | Results.Add(result);
50 | }
51 |
52 | ///
53 | /// Checks if the is a suitable result.
54 | ///
55 | public bool IsResult(EntityResult entity) => Criteria(entity, Advances);
56 |
57 | ///
58 | /// Calls for all objects in the result list.
59 | ///
60 | public IEnumerable GetLines()
61 | {
62 | for (var i = 0; i < Results.Count; i++)
63 | {
64 | var result = Results[i];
65 | var parent = FindNearestParentAdvanceResult(i, result.Advances);
66 | bool isActionMultiResult = IsActionMultiResult(i, result.Advances);
67 | bool hasChildChain = HasChildChain(i, result.Advances);
68 | yield return result.GetLine(parent, isActionMultiResult, hasChildChain);
69 | }
70 | }
71 |
72 | private bool HasChildChain(int index, ReadOnlySpan parent)
73 | {
74 | if (++index >= Results.Count)
75 | return false;
76 | return IsSubset(parent, Results[index].Advances);
77 | }
78 |
79 | private bool IsActionMultiResult(int index, ReadOnlySpan child)
80 | {
81 | int count = 0;
82 | // scan backwards until different
83 | for (int i = index - 1; i >= 0; i--)
84 | {
85 | if (child.SequenceEqual(Results[i].Advances))
86 | count++;
87 | else
88 | break;
89 | }
90 | // scan forwards until different
91 | for (int i = index + 1; i < Results.Count; i++)
92 | {
93 | if (child.SequenceEqual(Results[i].Advances))
94 | count++;
95 | else
96 | break;
97 | }
98 | return count != 0;
99 | }
100 |
101 | // Non-null indicates this is a chain of results the user might want to pick (compared to other results).
102 | private PermuteResult? FindNearestParentAdvanceResult(int index, ReadOnlySpan child)
103 | {
104 | var start = index - 1;
105 | if (start < 0)
106 | return null;
107 |
108 | // Due to how we depth-first search, previous results can contain overlapping advancement sequences.
109 | // Find nearest previous result with advancement sequence being a subset of our child's sequence.
110 | for (var i = start; i >= 0; i--)
111 | {
112 | if (IsSubset(Results[i].Advances, child))
113 | return Results[i];
114 | }
115 | return null;
116 | }
117 |
118 | private static bool IsSubset(ReadOnlySpan parent, ReadOnlySpan child)
119 | {
120 | // Some paths have no steps, so don't consider these part of a chain.
121 | if (parent.Length == 0)
122 | return false;
123 | // Check if parent sequence [0..n) matches child's [0..n)
124 | if (parent.Length >= child.Length)
125 | return false;
126 | for (var i = 0; i < parent.Length; i++)
127 | {
128 | if (parent[i] != child[i])
129 | return false;
130 | }
131 | return true;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/ConsolePermuter.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using PKHeX.Core;
3 |
4 | namespace PermuteMMO.Lib;
5 |
6 | ///
7 | /// Logic to permute spawner data.
8 | ///
9 | public static class ConsolePermuter
10 | {
11 | static ConsolePermuter() => Console.OutputEncoding = System.Text.Encoding.Unicode;
12 |
13 | ///
14 | /// Permutes all the areas to print out all possible spawns.
15 | ///
16 | public static void PermuteMassiveMassOutbreak(Span data)
17 | {
18 | var block = new MassiveOutbreakSet8a(data);
19 | for (int i = 0; i < MassiveOutbreakSet8a.AreaCount; i++)
20 | {
21 | var area = block[i];
22 | var areaName = AreaUtil.GetAreaName(area.AreaHash);
23 | if (!area.IsActive)
24 | {
25 | Console.WriteLine($"No outbreak in {areaName}.");
26 | continue;
27 | }
28 | Debug.Assert(area.IsValid);
29 |
30 | bool hasPrintedAreaMMO = false;
31 | for (int j = 0; j < MassiveOutbreakArea8a.SpawnerCount; j++)
32 | {
33 | var spawner = area[j];
34 | if (spawner.Status is MassiveOutbreakSpawnerStatus.None)
35 | continue;
36 |
37 | Debug.Assert(spawner.HasBase);
38 | var seed = spawner.GroupSeed;
39 | var spawn = new SpawnInfo(spawner);
40 |
41 | var result = Permuter.Permute(spawn, seed);
42 | if (!result.HasResults)
43 | continue;
44 |
45 | if (!hasPrintedAreaMMO)
46 | {
47 | Console.WriteLine($"Found paths for Massive Mass Outbreaks in {areaName}.");
48 | Console.WriteLine("==========");
49 | hasPrintedAreaMMO = true;
50 | }
51 |
52 | Console.WriteLine($"Spawner {j+1} at ({spawner.X:F1}, {spawner.Y:F1}, {spawner.Z}) shows {SpeciesName.GetSpeciesName(spawner.DisplaySpecies, 2)}");
53 | Console.WriteLine(spawn.GetSummary("Parameters: "));
54 | Console.WriteLine($"Seed: {seed}");
55 | var lines = result.GetLines();
56 | foreach (var line in lines)
57 | Console.WriteLine(line);
58 | Console.WriteLine();
59 | }
60 |
61 | if (!hasPrintedAreaMMO)
62 | {
63 | Console.WriteLine($"Found no results for any Massive Mass Outbreak in {areaName}.");
64 | }
65 | else
66 | {
67 | Console.WriteLine("Done permuting area.");
68 | Console.WriteLine("==========");
69 | }
70 | }
71 | }
72 |
73 | ///
74 | /// Permutes all the Mass Outbreaks to print out all possible spawns.
75 | ///
76 | public static void PermuteBlockMassOutbreak(Span data)
77 | {
78 | Console.WriteLine("Permuting Mass Outbreaks.");
79 | var block = new MassOutbreakSet8a(data);
80 | for (int i = 0; i < MassOutbreakSet8a.AreaCount; i++)
81 | {
82 | var spawner = block[i];
83 | var areaName = AreaUtil.GetAreaName(spawner.AreaHash);
84 | if (!spawner.HasOutbreak)
85 | {
86 | Console.WriteLine($"No outbreak in {areaName}.");
87 | continue;
88 | }
89 | Debug.Assert(spawner.IsValid);
90 |
91 | var seed = spawner.GroupSeed;
92 | var spawn = new SpawnInfo(spawner);
93 | var result = Permuter.Permute(spawn, seed);
94 | if (!result.HasResults)
95 | {
96 | Console.WriteLine($"Found no paths for {(Species)spawner.DisplaySpecies} Mass Outbreak in {areaName}.");
97 | continue;
98 | }
99 |
100 | Console.WriteLine($"Found paths for {(Species)spawner.DisplaySpecies} Mass Outbreak in {areaName}:");
101 | Console.WriteLine("==========");
102 | Console.WriteLine($"Spawner at ({spawner.X:F1}, {spawner.Y:F1}, {spawner.Z}) shows {SpeciesName.GetSpeciesName(spawner.DisplaySpecies, 2)}");
103 | Console.WriteLine(spawn.GetSummary("Parameters: "));
104 | Console.WriteLine($"Seed: {seed}");
105 | var lines = result.GetLines();
106 | foreach (var line in lines)
107 | Console.WriteLine(line);
108 | Console.WriteLine();
109 | }
110 | Console.WriteLine("Done permuting Mass Outbreaks.");
111 | Console.WriteLine("==========");
112 | }
113 |
114 | ///
115 | /// Permutes a single spawn with simple info.
116 | ///
117 | public static void PermuteSingle(SpawnInfo spawn, ulong seed, ushort species)
118 | {
119 | Console.WriteLine($"Permuting all possible paths for {seed:X16}.");
120 | Console.WriteLine($"Base Species: {SpeciesName.GetSpeciesName(species, 2)}");
121 | Console.WriteLine(spawn.GetSummary("Parameters: "));
122 | Console.WriteLine($"Seed: {seed}");
123 |
124 | var result = Permuter.Permute(spawn, seed);
125 | if (!result.HasResults)
126 | {
127 | Console.WriteLine("No results found. Try another outbreak! :(");
128 | }
129 | else
130 | {
131 | var lines = result.GetLines();
132 | foreach (var line in lines)
133 | Console.WriteLine(line);
134 | }
135 |
136 | Console.WriteLine();
137 | Console.WriteLine("Done.");
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/PermuteMMO.Tests/MultiTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using PermuteMMO.Lib;
3 | using PKHeX.Core;
4 | using Xunit;
5 |
6 | namespace PermuteMMO.Tests;
7 |
8 | ///
9 | /// Aren't these nice to have? Examples??
10 | ///
11 | public sealed class MultiTests
12 | {
13 | private static void SetFakeTable(SlotDetail[] slots, ulong key)
14 | {
15 | foreach (var s in slots)
16 | s.SetSpecies();
17 |
18 | SpawnGenerator.EncounterTables.Add(key, slots);
19 | }
20 |
21 | [Theory]
22 | [InlineData(0xB2204D9BA549D169u)]
23 | public void TestCombee22(in ulong seed)
24 | {
25 | const int count = 2; // 2-2 spawner
26 |
27 | const ulong key = 0x1337BABECAFEDEAD;
28 | var slots = new SlotDetail[]
29 | {
30 | new(100, "Combee", false, [17, 20], 0),
31 | new( 2, "Combee", true , [32, 35], 3),
32 | };
33 | SetFakeTable(slots, key);
34 |
35 | var details = new SpawnCount(count, count);
36 | var set = new SpawnSet(key, count);
37 | var spawner = SpawnInfo.GetLoop(details, set, SpawnType.Regular);
38 |
39 | var results = Permuter.Permute(spawner, seed, 20);
40 | var min = results.Results
41 | .Where(z => z.Entity is { Gender: 1, RollCountUsed: <= 5 })
42 | .MinBy(z => z.Advances.Length);
43 | min.Should().NotBeNull();
44 |
45 | var seq = min.Advances;
46 | var copy = results.Copy();
47 | _ = AdvanceRemoval.RunForwards(copy, seq, seed);
48 | var expect = copy.Results.Where(z => seq.SequenceEqual(z.Advances));
49 | expect.FirstOrDefault(z => z.Entity.IsShiny).Should().NotBeNull();
50 | }
51 |
52 | [Theory]
53 | [InlineData(0x9C1107A569F7681D)]
54 | public void TestEevee(in ulong seed)
55 | {
56 | const int count = 2; // 2-2 spawner
57 |
58 | const ulong key = 0x1337BABE12345678;
59 | var slots = new SlotDetail[]
60 | {
61 | new(100, "Bidoof", false, [ 3, 6], 0),
62 | new( 2, "Bidoof", true, [18, 21], 3),
63 | new( 20, "Eevee", false, [ 3, 6], 0),
64 | new( 1, "Eevee", true, [18, 21], 3),
65 | };
66 | SetFakeTable(slots, key);
67 |
68 | const int rolls = 5;
69 | static bool IsSatisfactory(PermuteResult z) => z.Entity is { Species: (int)Species.Eevee, Gender: 1, RollCountUsed: <= rolls };
70 |
71 | var details = new SpawnCount(count, count);
72 | var set = new SpawnSet(key, count);
73 | var spawner = SpawnInfo.GetLoop(details, set, SpawnType.Regular);
74 |
75 | var results = Permuter.Permute(spawner, seed, 20);
76 | var min = results.Results
77 | .Where(IsSatisfactory)
78 | .MinBy(z => z.Advances.Length);
79 | min.Should().NotBeNull();
80 |
81 | var seq = min.Advances;
82 | var copy = results.Copy();
83 | _ = AdvanceRemoval.RunForwards(copy, seq, seed);
84 | var expect = copy.Results.Where(z => seq.SequenceEqual(z.Advances));
85 | expect.FirstOrDefault(z => z.Entity.IsShiny).Should().NotBeNull();
86 | }
87 |
88 | [Theory]
89 | [InlineData(0xBE7A00B1CAF3C8DD)]
90 | public void TestBasculin(in ulong seed)
91 | {
92 | const int count = 2; // 2-2 spawner
93 |
94 | const ulong key = 0x123456B0; // Male gender lock
95 | var slots = new SlotDetail[]
96 | {
97 | new(100, "Basculin-2", false, [41, 44], 0),
98 | new( 1, "Basculin-2", true, [56, 59], 3),
99 | };
100 | SetFakeTable(slots, key);
101 |
102 | const int rolls = 5;
103 | static bool IsSatisfactory(PermuteResult z) => z.Entity is { Species: (int)Species.Basculin, Gender: 1, RollCountUsed: <= rolls };
104 |
105 | var details = new SpawnCount(count, count);
106 | var set = new SpawnSet(key, count);
107 | var spawner = SpawnInfo.GetLoop(details, set, SpawnType.Regular);
108 |
109 | var results = Permuter.Permute(spawner, seed, 20);
110 | var min = results.Results
111 | .Where(IsSatisfactory)
112 | .MinBy(z => z.Advances.Length);
113 | results.Should().NotBeNull();
114 | min.Should().BeNull();
115 | }
116 |
117 | [Theory]
118 | [InlineData(0xA0B404668A34A9E6u, 0x6B9EBA2AD7437ADEu)]
119 | public void TestUnown23(in ulong seed, in ulong countSeed)
120 | {
121 | const int minCount = 2;
122 | const int maxCount = 3;
123 |
124 | SlotDetail[] slots = new SlotDetail[28 * 2];
125 | const ulong key = 9489890319879407414;
126 | for (int i = 0; i < slots.Length/2; i++)
127 | {
128 | var name = $"Unown{(i == 0 ? "" : $"-{i}")}";
129 | slots[i] = new(100, name, false, [25, 25], 0);
130 | slots[i + 28] = new(001, name, true , [25, 25], 3);
131 | }
132 | SetFakeTable(slots, key);
133 |
134 | var details = new SpawnCount(maxCount, minCount, countSeed);
135 | var set = new SpawnSet(key, 0);
136 | var spawner = SpawnInfo.GetLoop(details, set, SpawnType.Regular);
137 |
138 | var results = Permuter.Permute(spawner, seed, 12);
139 | var min = results.Results
140 | .MinBy(z => z.Advances.Length);
141 | min.Should().NotBeNull();
142 |
143 | var seq = min.Advances;
144 | var copy = results.Copy();
145 | copy.Spawner.Count.CountSeed = countSeed;
146 | _ = AdvanceRemoval.RunForwards(copy, seq, seed);
147 | var expect = copy.Results.Where(z => seq.SequenceEqual(z.Advances));
148 | expect.FirstOrDefault(z => z.Entity.IsShiny).Should().NotBeNull();
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | ..\Resources\mmo_es.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252
123 |
124 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Generation/SpawnGenerator.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using PermuteMMO.Lib.Properties;
3 | using PKHeX.Core;
4 |
5 | namespace PermuteMMO.Lib;
6 |
7 | ///
8 | /// Generator logic for creating new objects.
9 | ///
10 | public static class SpawnGenerator
11 | {
12 | public static readonly IDictionary EncounterTables = JsonDecoder.GetDictionary(Resources.mmo_es);
13 |
14 | ///
15 | /// Generates an from the input and .
16 | ///
17 | public static EntityResult? Generate(in ulong groupseed, in int index, in ulong seed, in ulong alphaSeed, in ulong table, SpawnType type, bool noAlpha)
18 | {
19 | var slotrng = new Xoroshiro128Plus(seed);
20 |
21 | var slots = GetSlots(table);
22 | var slotSum = GetSlotSum(slots, noAlpha);
23 | if (slotSum == 0)
24 | return null;
25 |
26 | var slotroll = slotrng.NextFloat(slotSum);
27 | var slot = GetSlot(slots, slotroll, noAlpha);
28 | var genseed = slotrng.Next();
29 | var level = GetLevel(slot, slotrng);
30 |
31 | // Magic hashes to signify that this spawner is gender-locked, otherwise use the species default.
32 | // Should only apply to multispawners for Basculin-2, where we create fake tables.
33 | var gt = table switch
34 | {
35 | 0x123456B0 => PersonalInfo.RatioMagicMale,
36 | 0x123456B1 => PersonalInfo.RatioMagicFemale,
37 | _ => PersonalTable.LA.GetFormEntry(slot.Species, slot.Form).Gender,
38 | };
39 |
40 | // Get roll count from save file
41 | int shinyRolls = SaveFileParameter.GetRerollCount(slot.Species, type);
42 |
43 | var result = new EntityResult(slot)
44 | {
45 | Species = slot.Species,
46 | Form = slot.Form,
47 | Level = level,
48 | IsAlpha = slot.IsAlpha,
49 |
50 | GroupSeed = groupseed,
51 | Index = index,
52 | SlotSeed = seed,
53 | GenSeed = genseed,
54 |
55 | AlphaSeed = alphaSeed,
56 | SlotRoll = slotroll,
57 | };
58 |
59 | GeneratePokemon(result, genseed, shinyRolls, slot.FlawlessIVs, gt);
60 | return result;
61 | }
62 |
63 | private static readonly Dictionary Outbreaks = [];
64 | private static readonly int[] FakeLevels = [0, 1, 2];
65 |
66 | private static ReadOnlySpan GetSlots(in ulong table)
67 | {
68 | if (table > 1000)
69 | return EncounterTables[table];
70 |
71 | ushort species = (ushort)table;
72 | return GetFakeOutbreak(species);
73 | }
74 |
75 | private static SlotDetail[] GetFakeOutbreak(ushort species)
76 | {
77 | if (Outbreaks.TryGetValue(species, out var value))
78 | return value;
79 |
80 | var name = SpeciesName.GetSpeciesName(species, 2);
81 | if (species == (ushort)Species.Basculin)
82 | name = $"{name}-2";
83 | value =
84 | [
85 | new SlotDetail(100, name, false, FakeLevels, 0),
86 | new SlotDetail(001, name, true, FakeLevels, 3),
87 | ];
88 | foreach (var slot in value)
89 | slot.SetSpecies();
90 |
91 | return Outbreaks[species] = value;
92 | }
93 |
94 | private static int GetLevel(SlotDetail slot, Xoroshiro128Plus slotrng)
95 | {
96 | var min = slot.LevelMin;
97 | var max = slot.LevelMax;
98 | var level = min;
99 | var delta = max - min;
100 | if (delta != 0)
101 | level += (int)slotrng.NextInt((ulong)delta + 1);
102 | return level;
103 | }
104 |
105 | private static float GetSlotSum(ReadOnlySpan slots, bool noAlpha)
106 | {
107 | float total = 0;
108 | foreach (var slot in slots)
109 | {
110 | if (noAlpha && slot.IsAlpha)
111 | continue;
112 | total += slot.Rate;
113 | }
114 | return total;
115 | }
116 |
117 | private static SlotDetail GetSlot(ReadOnlySpan slots, float slotroll, bool noAlpha)
118 | {
119 | foreach (var slot in slots)
120 | {
121 | if (noAlpha && slot.IsAlpha)
122 | continue;
123 |
124 | slotroll -= slot.Rate;
125 | if (slotroll <= 0)
126 | return slot;
127 | }
128 | throw new ArgumentOutOfRangeException(nameof(slotroll));
129 | }
130 |
131 | public static void GeneratePokemon(EntityResult result, in ulong seed, in int shinyrolls, in int flawless, in int genderRatio)
132 | {
133 | var rng = new Xoroshiro128Plus(seed);
134 |
135 | // Encryption Constant
136 | result.EC = (uint)rng.NextInt();
137 | result.FakeTID = (uint)rng.NextInt();
138 |
139 | // PID
140 | uint pid;
141 | int ctr = 0;
142 | do
143 | {
144 | ++ctr;
145 | pid = (uint)rng.NextInt();
146 | var ShinyXor = GetShinyXor(pid, result.FakeTID);
147 | var isShiny = result.IsShiny = ShinyXor < 16;
148 | if (!isShiny)
149 | continue;
150 |
151 | result.ShinyXor = ShinyXor;
152 | result.RollCountUsed = ctr;
153 | result.RollCountAllowed = shinyrolls;
154 | break;
155 | } while (ctr < shinyrolls);
156 | result.PID = pid;
157 |
158 | const byte UNSET = byte.MaxValue;
159 | var ivs = result.IVs;
160 | const byte MAX = 31;
161 | for (int i = 0; i < flawless; i++)
162 | {
163 | int index;
164 | do { index = (int)rng.NextInt(6); }
165 | while (ivs[index] != UNSET);
166 |
167 | ivs[index] = MAX;
168 | }
169 |
170 | for (int i = 0; i < ivs.Length; i++)
171 | {
172 | if (ivs[i] == UNSET)
173 | ivs[i] = (byte)rng.NextInt(32);
174 | }
175 | result.Ability = (byte)rng.NextInt(2);
176 | result.Gender = genderRatio switch
177 | {
178 | PersonalInfo.RatioMagicGenderless => 2,
179 | PersonalInfo.RatioMagicFemale => 1,
180 | PersonalInfo.RatioMagicMale => 0,
181 | _ => (int)rng.NextInt(253) + 1 < genderRatio ? (byte)1: (byte)0,
182 | };
183 | result.Nature = (byte)rng.NextInt(25);
184 |
185 | (result.Height, result.Weight) = result.IsAlpha
186 | ? (byte.MaxValue, byte.MaxValue)
187 | : ((byte)((int)rng.NextInt(0x81) + (int)rng.NextInt(0x80)),
188 | (byte)((int)rng.NextInt(0x81) + (int)rng.NextInt(0x80)));
189 | }
190 |
191 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
192 | private static uint GetShinyXor(in uint pid, in uint oid)
193 | {
194 | var xor = pid ^ oid;
195 | return (xor ^ (xor >> 16)) & 0xFFFF;
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Permutation/Advance.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using static PermuteMMO.Lib.Advance;
3 |
4 | namespace PermuteMMO.Lib;
5 |
6 | ///
7 | /// Advancement step labels.
8 | ///
9 | public enum Advance : byte
10 | {
11 | RG,
12 | CR,
13 |
14 | A1, A2, A3, A4, // Aggressive
15 | B1, B2, B3, B4, // Beta
16 |
17 | O1, O2, O3, O4, // Oblivious
18 |
19 | // S1 is equivalent to B1
20 | S2, S3, S4,
21 |
22 | // G4 is equivalent to CR
23 | G1, G2, G3,
24 | }
25 |
26 | public static class AdvanceRemoval
27 | {
28 | public static (int Aggro, int Beta, int Oblivious) GetRemovals(this Advance advance)
29 | {
30 | var count = advance.AdvanceCount();
31 | if (advance.IsMultiAggressive() || advance is A1)
32 | return (count, 0, 0);
33 | if (advance.IsMultiBeta() || advance is B1)
34 | return (count - 1, 1, 0);
35 | if (advance.IsMultiScare())
36 | return (0, count, 0);
37 | if (advance.IsMultiOblivious() || advance is O1)
38 | return (count - 1, 0, 1);
39 |
40 | throw new ArgumentOutOfRangeException(nameof(advance));
41 | }
42 |
43 | public static SpawnState AdvanceState(this Advance advance, SpawnState state)
44 | {
45 | var (aggro, beta, oblivious) = advance.GetRemovals();
46 | return state.Remove(aggro, beta, oblivious);
47 | }
48 |
49 | [DebuggerDisplay($"{{{nameof(StepSummary)},nq}}")]
50 | public readonly record struct SpawnStep(Advance Step, SpawnState State, ulong Seed, ulong CountSeed)
51 | {
52 | public string StepSummary => $"{Step} {State.State} {State.Count} {Seed:X16} {CountSeed:X16}";
53 | }
54 |
55 | public static IReadOnlyList RunForwards(PermuteMeta meta, ReadOnlySpan advances, ulong seed)
56 | {
57 | List steps = [];
58 | var spawner = meta.Spawner;
59 | var state = spawner.GetStartingState();
60 | (seed, state) = Permuter.UpdateRespawn(meta, meta.Spawner.Set.Table, seed, state);
61 | steps.Add(new(RG, state, seed, meta.Spawner.Count.CountSeed));
62 | foreach (var adv in advances)
63 | {
64 | meta.Start(adv);
65 | if (meta.Spawner.RetainExisting)
66 | {
67 | var count = adv.AdvanceCount();
68 | if (count != 0)
69 | state = state.KnockoutAny(count);
70 |
71 | var newAlive = meta.Spawner.Count.GetNextCount();
72 | state = state.AdjustCount(newAlive);
73 | steps.Add(new(adv, state, seed, meta.Spawner.Count.CountSeed));
74 | }
75 | else if (adv == CR)
76 | {
77 | if (!meta.Spawner.GetNextWave(out var next))
78 | throw new ArgumentException(nameof(adv));
79 | meta.Spawner = next;
80 | state = next.GetStartingState();
81 | steps.Add(new(adv, state, seed, meta.Spawner.Count.CountSeed));
82 | }
83 | else if (adv >= G1)
84 | {
85 | var count = adv.AdvanceCount();
86 | state = state.AddGhosts(count);
87 | seed = Calculations.GetGroupSeed(seed, state.Ghost);
88 | steps.Add(new(adv, state, seed, meta.Spawner.Count.CountSeed));
89 | continue;
90 | }
91 | else
92 | {
93 | state = adv.AdvanceState(state);
94 | steps.Add(new(adv, state, seed, meta.Spawner.Count.CountSeed));
95 | }
96 |
97 | if (state.Count != 0)
98 | (seed, state) = Permuter.UpdateRespawn(meta, meta.Spawner.Set.Table, seed, state);
99 | steps.Add(new(RG, state, seed, meta.Spawner.Count.CountSeed));
100 | }
101 |
102 | return steps;
103 | }
104 | }
105 |
106 | public static class AdvanceExtensions
107 | {
108 | ///
109 | /// Option to just emit the result instead of a humanized string.
110 | ///
111 | public static bool Raw { get; set; } = true;
112 |
113 | ///
114 | /// Returns a string for indicating the value of the step.
115 | ///
116 | /// If undefined
117 | public static string GetName(this Advance advance) => Raw ? advance.ToString() : Humanize(advance);
118 |
119 | private static string Humanize(Advance advance) => advance switch
120 | {
121 | CR => "Clear Remaining",
122 |
123 | A1 => "1 Aggressive",
124 | A2 => "2 Aggressive",
125 | A3 => "3 Aggressive",
126 | A4 => "4 Aggressive",
127 |
128 | B1 => "1 Beta",
129 | B2 => "1 Beta + 1 Aggressive",
130 | B3 => "1 Beta + 2 Aggressive",
131 | B4 => "1 Beta + 3 Aggressive",
132 |
133 | O1 => "1 Oblivious",
134 | O2 => "1 Oblivious + 1 Aggressive",
135 | O3 => "1 Oblivious + 2 Aggressive",
136 | O4 => "1 Oblivious + 3 Aggressive",
137 |
138 | G1 => "De-spawn 1 + Leave",
139 | G2 => "De-spawn 2 + Leave",
140 | G3 => "De-spawn 3 + Leave",
141 |
142 | S2 => "Multi Scare 2 + Leave",
143 | S3 => "Multi Scare 3 + Leave",
144 | S4 => "Multi Scare 4 + Leave",
145 | _ => throw new ArgumentOutOfRangeException(nameof(advance), advance, null),
146 | };
147 |
148 | ///
149 | /// Gets the count of advances required.
150 | ///
151 | public static int AdvanceCount(this Advance advance) => advance switch
152 | {
153 | A1 or B1 or O1 or G1 => 1,
154 | A2 or B2 or O1 or S2 or G2 => 2,
155 | A3 or B3 or O1 or S3 or G3 => 3,
156 | A4 or B4 or O1 or S4 => 4,
157 | _ => 0,
158 | };
159 |
160 | ///
161 | /// Indicates if a multi-battle is required for this advancement.
162 | ///
163 | public static bool IsMultiAny(this Advance advance) => advance.IsMultiAggressive() || advance.IsMultiBeta() || advance.IsMultiScare() || advance.IsMultiOblivious();
164 |
165 | ///
166 | /// Indicates if a multi-battle is required for this advancement.
167 | ///
168 | public static bool IsMultiAggressive(this Advance advance) => advance is (A2 or A3 or A4);
169 |
170 | ///
171 | /// Indicates if a multi-battle is required for this advancement.
172 | ///
173 | public static bool IsMultiScare(this Advance advance) => advance is (S2 or S3 or S4);
174 |
175 | ///
176 | /// Indicates if a multi-battle is required for this advancement.
177 | ///
178 | public static bool IsMultiBeta(this Advance advance) => advance is (B2 or B3 or B4);
179 |
180 | ///
181 | /// Indicates if a multi-battle is required for this advancement.
182 | ///
183 | public static bool IsMultiOblivious(this Advance advance) => advance is (O2 or O3 or O4);
184 |
185 | public static bool IsAny(this ReadOnlySpan span, Func check)
186 | {
187 | foreach (var x in span)
188 | {
189 | if (check(x))
190 | return true;
191 | }
192 | return false;
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/SpawnState.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace PermuteMMO.Lib;
4 |
5 | ///
6 | /// Models the state of an Entity Spawner, indicating how it can be mutated by player actions.
7 | ///
8 | /// Total count of entities that can be spawned by the spawner.
9 | /// Maximum count of entities that can be alive at a given time.
10 | /// Current count of fake entities.
11 | /// Current count of aggressive entities alive.
12 | /// Current count of timid entities alive.
13 | /// Current count of oblivious entities alive.
14 | [DebuggerDisplay($"{{{nameof(State)},nq}}")]
15 | public readonly record struct SpawnState(in int Count, in int MaxAlive, in int Ghost = 0, in int AliveAlpha = 0, in int AliveAggressive = 0, in int AliveBeta = 0, in int AliveOblivious = 0)
16 | {
17 | /// Current count of unpopulated entities.
18 | public int Dead { get; init; } = MaxAlive;
19 |
20 | public int Alive => MaxAlive - Dead;
21 |
22 | /// Total count of entities that can exist as ghosts.
23 | /// Completely filling with ghost slots will start the next wave rather than add ghosts.
24 | private int MaxGhosts => MaxAlive - 1;
25 |
26 | /// Indicates if ghost entities can be added to the spawner.
27 | /// Only call this if is zero.
28 | public bool CanAddGhosts => Ghost != MaxGhosts;
29 |
30 | /// Indicates if ghost entities can be added to the spawner.
31 | /// Only call this if is zero.
32 | public int EmptyGhostSlots => MaxGhosts - Ghost;
33 |
34 | public static SpawnState Get(int count) => Get(count, count);
35 | public static SpawnState Get(int totalCount, int aliveCount) => new(totalCount, aliveCount);
36 |
37 | ///
38 | /// Returns a spawner state after knocking out existing entities.
39 | ///
40 | ///
41 | /// If is 1, this is the same as capturing a single Aggressive Entity out of battle.
42 | ///
43 | public SpawnState KnockoutAggressive(in int count) => Remove(aggro: count);
44 |
45 | ///
46 | /// Returns a spawner state after knocking out existing entities.
47 | ///
48 | ///
49 | /// If is 1, this is the same as capturing a single Beta Entity out of battle.
50 | ///
51 | public SpawnState KnockoutBeta(in int count) => Remove(aggro: count - 1, beta: 1);
52 |
53 | ///
54 | /// Returns a spawner state after knocking out existing entities.
55 | ///
56 | public SpawnState KnockoutOblivious(int count) => Remove(aggro: count - 1, oblivious: 1);
57 |
58 | public SpawnState KnockoutAny(int count)
59 | {
60 | int aggro = Math.Max(0, Math.Min(AliveAggressive, count));
61 | int beta = Math.Max(0, Math.Min(AliveBeta, count - aggro));
62 | int obli = Math.Max(0, Math.Min(AliveOblivious, count - aggro - beta));
63 | Debug.Assert(aggro + beta + obli == count);
64 | return Remove(aggro, beta, obli);
65 | }
66 |
67 | ///
68 | /// Returns a spawner state after scaring existing Beta entities away.
69 | ///
70 | public SpawnState Scare(in int count) => Remove(beta: count);
71 |
72 | ///
73 | /// Returns a spawner state after generating new entities.
74 | ///
75 | public SpawnState Add(in int count, in int alpha, in int aggro, in int beta, in int oblivious)
76 | {
77 | var nAlpha = AliveAlpha + alpha;
78 | var nAggro = AliveAggressive + aggro;
79 | var nBeta = AliveBeta + beta;
80 | var nOblivious = AliveOblivious + oblivious;
81 | Debug.Assert((uint)nAlpha <= MaxAlive);
82 | Debug.Assert((uint)nAggro <= MaxAlive);
83 | Debug.Assert((uint)nBeta <= MaxAlive);
84 | Debug.Assert((uint)nOblivious <= MaxAlive);
85 |
86 | var delta = (aggro + beta + oblivious);
87 | var nDead = Dead - count;
88 | var nGhost = nDead;
89 | Debug.Assert(delta > 0);
90 | Debug.Assert(count >= delta);
91 | Debug.Assert((uint)nDead < Dead);
92 | Debug.Assert((uint)nGhost < MaxAlive);
93 | Debug.Assert(nGhost <= Dead);
94 |
95 | return this with
96 | {
97 | Count = Count - count,
98 | AliveAlpha = nAlpha,
99 | AliveAggressive = nAggro,
100 | AliveBeta = nBeta,
101 | AliveOblivious = nOblivious,
102 | Dead = nDead,
103 | Ghost = nGhost,
104 | };
105 | }
106 |
107 | public SpawnState Remove(in int aggro = 0, in int beta = 0, in int oblivious = 0)
108 | {
109 | // Any aggressive should prefer removing alphas for regular spawners to prevent instant despawns of future alphas.
110 | var nAlpha = AliveAlpha - Math.Min(AliveAlpha, aggro);
111 | var nAggro = AliveAggressive - aggro;
112 | var nBeta = AliveBeta - beta;
113 | var nOblivious = AliveOblivious - oblivious;
114 | Debug.Assert((uint)nAlpha <= MaxAlive);
115 | Debug.Assert((uint)nAggro <= MaxAlive);
116 | Debug.Assert((uint)nBeta <= MaxAlive);
117 | Debug.Assert((uint)nOblivious <= MaxAlive);
118 |
119 | var delta = (aggro + beta + oblivious);
120 | var nDead = Dead + delta;
121 | Debug.Assert((uint)nDead <= MaxAlive);
122 | Debug.Assert(delta > 0);
123 |
124 | return this with
125 | {
126 | AliveAlpha = nAlpha,
127 | AliveAggressive = nAggro,
128 | AliveBeta = nBeta,
129 | AliveOblivious = nOblivious,
130 | Dead = nDead,
131 | };
132 | }
133 |
134 | public SpawnState AdjustCount(int newAlive)
135 | {
136 | var maxAlive = Math.Max(newAlive, Alive);
137 | var newCount = Math.Max(0, maxAlive - Alive);
138 | var newDead = maxAlive - Alive;
139 | return this with { MaxAlive = maxAlive, Count = newCount, Dead = newDead };
140 | }
141 |
142 | ///
143 | /// Returns a spawner state with additional ghosts added.
144 | ///
145 | /// Don't care about the alive breakdown; reaching here we only care about the amount of ghosts.
146 | public SpawnState AddGhosts(in int count) => new(Count, MaxAlive, Ghost + count) { Dead = Dead + count };
147 |
148 | ///
149 | /// Gets the counts of what to generate when regenerating a spawner.
150 | ///
151 | /// Only call this if is NOT zero.
152 | public (int Empty, int Respawn, int Ghosts) GetRespawnInfo()
153 | {
154 | var emptySlots = Dead;
155 | var respawn = Math.Min(Count, emptySlots);
156 | var ghosts = emptySlots - respawn;
157 |
158 | Debug.Assert(respawn != 0 || Dead == 0);
159 | return (emptySlots, respawn, ghosts);
160 | }
161 |
162 | public string State => GetState();
163 |
164 | private string GetState()
165 | {
166 | int ctr = 0;
167 | Span result = stackalloc char[MaxAlive];
168 | for (int i = 0; i < AliveAlpha; i++)
169 | result[ctr++] = 'a';
170 | for (int i = 0; i < AliveAggressive - AliveAlpha; i++)
171 | result[ctr++] = 'A';
172 | for (int i = 0; i < AliveBeta; i++)
173 | result[ctr++] = 'B';
174 | for (int i = 0; i < AliveOblivious; i++)
175 | result[ctr++] = 'O';
176 | for (int i = 0; i < Ghost; i++)
177 | result[ctr++] = '~';
178 | for (int i = 0; i < Dead - Ghost; i++)
179 | result[ctr++] = 'X';
180 | while (ctr != result.Length)
181 | result[ctr++] = '?'; // shouldn't hit here besides ghosts
182 | return new string(result[..ctr]);
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/PermuteMMO.Lib/Permuter.cs:
--------------------------------------------------------------------------------
1 | using PKHeX.Core;
2 |
3 | namespace PermuteMMO.Lib;
4 |
5 | ///
6 | /// Master iterator
7 | ///
8 | public static class Permuter
9 | {
10 | ///
11 | /// Iterates through all possible player actions with the starting and details.
12 | ///
13 | public static PermuteMeta Permute(SpawnInfo spawner, in ulong seed, int maxDepth = 15)
14 | {
15 | var info = new PermuteMeta(spawner, maxDepth);
16 | var state = spawner.GetStartingState();
17 |
18 | // Generate the encounters!
19 | PermuteRecursion(info, spawner.Set.Table, seed, state);
20 | return info;
21 | }
22 |
23 | private static void PermuteRecursion(PermuteMeta meta, in ulong table, in ulong seed, in SpawnState state)
24 | {
25 | // If the outbreak is not done, continue.
26 | if (state.Count != 0)
27 | {
28 | PermuteOutbreak(meta, table, seed, state);
29 | return;
30 | }
31 |
32 | var (canContinue, next) = meta.AttemptNextWave();
33 | if (!canContinue)
34 | return;
35 |
36 | // Try the next table before we try adding ghosts.
37 | PermuteNextTable(meta, next, seed, state);
38 |
39 | // Try adding ghost spawns if we haven't capped out yet.
40 | if (meta.Spawner.AllowGhosts && state.CanAddGhosts)
41 | PermuteAddGhosts(meta, seed, table, state);
42 |
43 | // Outbreak complete.
44 | }
45 |
46 | private static void PermuteOutbreak(PermuteMeta meta, in ulong table, in ulong seed, in SpawnState state)
47 | {
48 | // Re-spawn to capacity
49 | var (reseed, newState) = UpdateRespawn(meta, table, seed, state);
50 | ContinuePermute(meta, table, reseed, newState);
51 | }
52 |
53 | public static (ulong, SpawnState) UpdateRespawn(PermuteMeta meta, ulong table, ulong seed, SpawnState state)
54 | {
55 | if (state.Count == 0)
56 | return (seed, state);
57 | var (empty, respawn, ghosts) = state.GetRespawnInfo();
58 | var (reseed, alpha, aggro, beta, oblivious)
59 | = GenerateSpawns(meta, table, seed, empty, ghosts, state.AliveAlpha, meta.Spawner.NoMultiAlpha);
60 |
61 | // Update spawn state
62 | var newState = state.Add(respawn, alpha, aggro, beta, oblivious);
63 | return (reseed, newState);
64 | }
65 |
66 | private static void ContinuePermute(PermuteMeta meta, in ulong table, in ulong seed, in SpawnState state)
67 | {
68 | var spawner = meta.Spawner;
69 | // If our spawner loops (regular), handle differently.
70 | if (spawner.Type is SpawnType.Regular)
71 | {
72 | // Try KO none if a future state can spawn more.
73 | var countSeed = spawner.Count.CountSeed;
74 | if (spawner.Count.CanSpawnMore(state.Alive))
75 | {
76 | meta.Start(Advance.RG);
77 | PermuteRecursion(meta, table, seed, state);
78 | meta.End();
79 | spawner.Count.CountSeed = countSeed;
80 | }
81 |
82 | // Try all Knockout->Respawn steps.
83 | for (int i = 1; i <= state.Alive; i++)
84 | {
85 | var step = (int)Advance.A1 + (i - 1);
86 | meta.Start((Advance)step);
87 | var newState = state.KnockoutAny(i);
88 | PermuteRecursion(meta, table, seed, newState);
89 | meta.End();
90 | spawner.Count.CountSeed = countSeed;
91 | }
92 | return;
93 | }
94 |
95 | // Check if we're now out of possible re-spawns
96 | if (state.Count == 0)
97 | {
98 | PermuteRecursion(meta, table, seed, state);
99 | return;
100 | }
101 |
102 | // Depending on what spawns in future calls, the actions we take here can impact the options for future recursion.
103 | // We need to try out every single potential action the player can do, and target removals for specific behaviors.
104 |
105 | // De-spawn: Aggressive Only
106 | if (state.AliveAggressive != 0)
107 | {
108 | for (int i = 1; i <= state.AliveAggressive; i++)
109 | {
110 | var step = (int)Advance.A1 + (i - 1);
111 | meta.Start((Advance)step);
112 | var newState = state.KnockoutAggressive(i);
113 | PermuteRecursion(meta, table, seed, newState);
114 | meta.End();
115 | }
116 | }
117 |
118 | if (state.AliveOblivious != 0)
119 | {
120 | for (int i = 0; i <= state.AliveAggressive; i++)
121 | {
122 | var step = (int)Advance.O1 + i;
123 | meta.Start((Advance)step);
124 | var newState = state.KnockoutOblivious(i + 1);
125 | PermuteRecursion(meta, table, seed, newState);
126 | meta.End();
127 | }
128 | }
129 |
130 | // De-spawn: Single beta with aggressive(s) / none.
131 | if (state.AliveBeta != 0)
132 | {
133 | for (int i = 0; i <= state.AliveAggressive; i++)
134 | {
135 | var step = (int)Advance.B1 + i;
136 | meta.Start((Advance)step);
137 | var newState = state.KnockoutBeta(i + 1);
138 | PermuteRecursion(meta, table, seed, newState);
139 | meta.End();
140 | }
141 | }
142 |
143 | // De-spawn: Multiple betas (Scaring)
144 | for (int i = 2; i <= state.AliveBeta; i++)
145 | {
146 | var step = (int)Advance.S2 + (i - 2);
147 | meta.Start((Advance)step);
148 | var newState = state.Scare(i);
149 | PermuteRecursion(meta, table, seed, newState);
150 | meta.End();
151 | }
152 | }
153 |
154 | private static (ulong Seed, int Alpha, int Aggressive, int Skittish, int Oblivious)
155 | GenerateSpawns(PermuteMeta meta, in ulong table, in ulong seed, int count, in int ghosts, int currentAlpha, bool onlyOneAlpha)
156 | {
157 | int alpha = 0;
158 | int aggressive = 0;
159 | int beta = 0;
160 | int oblivious = 0;
161 | var rng = new Xoroshiro128Plus(seed);
162 | for (int i = 1; i <= count; i++)
163 | {
164 | var subSeed = rng.Next(); // generate/slot seed
165 | var alphaSeed = rng.Next(); // alpha move, don't care
166 |
167 | if (i <= ghosts)
168 | continue; // end of wave ghost -- ghosts spawn first!
169 |
170 | bool noAlpha = onlyOneAlpha && currentAlpha + alpha != 0;
171 | var generate = SpawnGenerator.Generate(seed, i, subSeed, alphaSeed, table, meta.Spawner.Type, noAlpha);
172 | if (generate is null) // only a consideration for spawners with 100% static alphas, qty >1, maybe some weather/time tables?
173 | continue; // empty ghost slot -- spawn failure.
174 |
175 | if (meta.IsResult(generate))
176 | meta.AddResult(generate);
177 |
178 | if (generate.IsAlpha) alpha++;
179 | else if (generate.IsOblivious) oblivious++;
180 | else if (generate.IsSkittish) beta++;
181 | else aggressive++;
182 | }
183 | var result = rng.Next(); // Reset the seed for future spawns.
184 | return (result, alpha, aggressive + alpha, beta, oblivious);
185 | }
186 |
187 | private static void PermuteNextTable(PermuteMeta meta, SpawnInfo next, in ulong seed, in SpawnState exist)
188 | {
189 | if (!next.RetainExisting)
190 | meta.Start(Advance.CR);
191 |
192 | var current = meta.Spawner;
193 | meta.Spawner = next;
194 | var newAlive = next.Count.GetNextCount();
195 |
196 | var state = next.RetainExisting ? exist.AdjustCount(newAlive) : next.GetStartingState();
197 | PermuteOutbreak(meta, next.Set.Table, seed, state);
198 |
199 | meta.Spawner = current;
200 | if (!next.RetainExisting)
201 | meta.End();
202 | }
203 |
204 | private static void PermuteAddGhosts(PermuteMeta meta, in ulong seed, in ulong table, in SpawnState state)
205 | {
206 | var remain = state.EmptyGhostSlots;
207 | for (int i = 1; i <= remain; i++)
208 | {
209 | var step = (int)Advance.G1 + (i - 1);
210 | meta.Start((Advance)step);
211 | var newState = state.AddGhosts(i);
212 | var gSeed = Calculations.GetGroupSeed(seed, newState.Ghost);
213 | PermuteRecursion(meta, table, gSeed, newState);
214 | meta.End();
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------