mirror of
https://github.com/kwsch/pkNX.git
synced 2026-07-06 20:23:59 -05:00
Pre-work: clean up some encounter dump logic
Extract individual dumpers to separate classes & files. Too much was happening in that one file. Move to separate folders
This commit is contained in:
parent
31b45ee883
commit
a3f7daa178
|
|
@ -642,15 +642,21 @@ public void DumpBalloon()
|
|||
|
||||
public void DumpEncounters()
|
||||
{
|
||||
var dumper = new EncounterDumperSV(rom);
|
||||
var path = GetPath("encounters");
|
||||
const string language = "English";
|
||||
|
||||
var cfg = new TextConfig(GameVersion.SV);
|
||||
var specNamesInternal = GetCommonText("monsname", "English", cfg);
|
||||
var moveNames = GetCommonText("wazaname", "English", cfg);
|
||||
var place_names = GetCommonText("place_name", "English", cfg);
|
||||
var ahtb = GetCommonAHTB("place_name", "English");
|
||||
var nameDict = EncounterDumperSV.GetPlaceNameMap(place_names, ahtb);
|
||||
dumper.DumpTo(path, specNamesInternal, moveNames, nameDict);
|
||||
var ahtb = GetCommonAHTB("place_name", language);
|
||||
var place_names = GetCommonText("place_name", language, cfg);
|
||||
var nameDict = EncounterDumperSV.GetInternalStringLookup(place_names, ahtb);
|
||||
|
||||
var config = new EncounterDumpConfigSV
|
||||
{
|
||||
PlaceNameMap = nameDict,
|
||||
SpecNamesInternal = GetCommonText("monsname", language, cfg),
|
||||
MoveNames = GetCommonText("wazaname", language, cfg),
|
||||
Path = GetPath("encounters"),
|
||||
};
|
||||
EncounterDumperSV.Dump(rom, config);
|
||||
}
|
||||
|
||||
public void DumpRaid()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public static void DumpDeliveryOutbreaks(IFileInternal ROM, string path, string
|
|||
var place_names = GetCommonText(ROM, "place_name", "English", cfg);
|
||||
var data = ROM.GetPackedFile("message/dat/English/common/place_name.tbl");
|
||||
var ahtb = new AHTB(data);
|
||||
NameDict = EncounterDumperSV.GetPlaceNameMap(place_names, ahtb);
|
||||
NameDict = EncounterDumperSV.GetInternalStringLookup(place_names, ahtb);
|
||||
|
||||
var dirs = Directory.GetDirectories(path, "*", SearchOption.AllDirectories).Order();
|
||||
foreach (var dir in dirs)
|
||||
|
|
|
|||
18
pkNX.WinForms/Dumping/SV/Encounter/EncounterDumpConfigSV.cs
Normal file
18
pkNX.WinForms/Dumping/SV/Encounter/EncounterDumpConfigSV.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using System.Collections.Generic;
|
||||
using pkNX.Structures.FlatBuffers.SV;
|
||||
|
||||
namespace pkNX.Structures.FlatBuffers;
|
||||
|
||||
public record EncounterDumpConfigSV
|
||||
{
|
||||
public required string Path { get; init; }
|
||||
public required string[] SpecNamesInternal { get; init; }
|
||||
public required string[] MoveNames { get; init; }
|
||||
public required Dictionary<string, (string Name, int Index)> PlaceNameMap { get; init; }
|
||||
|
||||
public bool WriteText { get; init; } = true;
|
||||
public bool WritePickle { get; init; } = true;
|
||||
|
||||
public string this[WazaSet move] => MoveNames[(int)move.WazaId];
|
||||
public string this[DevID species] => SpecNamesInternal[(int)species];
|
||||
}
|
||||
142
pkNX.WinForms/Dumping/SV/Encounter/EncounterDumperSV.cs
Normal file
142
pkNX.WinForms/Dumping/SV/Encounter/EncounterDumperSV.cs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using pkNX.Containers;
|
||||
using pkNX.Structures.FlatBuffers.SV;
|
||||
|
||||
namespace pkNX.Structures.FlatBuffers;
|
||||
|
||||
public static class EncounterDumperSV
|
||||
{
|
||||
private const float tolX = 30f;
|
||||
private const float tolY = 30f;
|
||||
private const float tolZ = 30f;
|
||||
|
||||
public static bool IsContainedBy<T>(T collider, LocationPointDetail point) where T : IContainsV3f
|
||||
=> collider.ContainsPoint(point.X, point.Y, point.Z, tolX, tolY, tolZ);
|
||||
|
||||
public static bool IsContainedBy<T>(T collider, PackedVec3f point) where T : IContainsV3f
|
||||
=> collider.ContainsPoint(point.X, point.Y, point.Z, tolX, tolY, tolZ);
|
||||
|
||||
public static ReadOnlySpan<PaldeaFieldIndex> AllMaps =>
|
||||
[
|
||||
PaldeaFieldIndex.Paldea,
|
||||
PaldeaFieldIndex.Kitakami,
|
||||
PaldeaFieldIndex.Terarium,
|
||||
];
|
||||
|
||||
public static void Dump(IFileInternal rom, EncounterDumpConfigSV config)
|
||||
{
|
||||
if (!Directory.Exists(config.Path))
|
||||
Directory.CreateDirectory(config.Path);
|
||||
|
||||
var field = new PaldeaFieldModel(rom);
|
||||
var scene = new PaldeaSceneModel(rom, field);
|
||||
EncounterSlotDumper9.DumpSlots(rom, config, scene);
|
||||
FixedSymbolDumper9.DumpFixedSymbols(rom, config, scene);
|
||||
GimmighoulDump.DumpGimmighoul(rom, config, scene);
|
||||
|
||||
// Raw dumps for inspection
|
||||
DumpScene(scene, config.Path);
|
||||
DumpField(field, config.Path);
|
||||
}
|
||||
|
||||
public static bool TryGetPlaceName(ref string areaName, AreaInfo areaInfo,
|
||||
IContainsV3f collider, Dictionary<string, (string Name, int Index)> placeNameMap,
|
||||
IReadOnlyDictionary<string, AreaInfo> areas,
|
||||
PaldeaSceneModel scene, PaldeaFieldIndex fieldIndex, out string placeName)
|
||||
{
|
||||
placeName = areaInfo.LocationNameMain;
|
||||
if (!string.IsNullOrEmpty(placeName))
|
||||
return true;
|
||||
|
||||
// Maybe this is a sub-area? Try to get the parent area name.
|
||||
bool IsValidParentAreaName(string aName)
|
||||
{
|
||||
if (!areas.TryGetValue(aName, out var info))
|
||||
return false;
|
||||
var n = info.LocationNameMain;
|
||||
if (string.IsNullOrEmpty(n))
|
||||
return false;
|
||||
return placeNameMap.ContainsKey(n);
|
||||
}
|
||||
|
||||
if (!scene.TryGetParentAreaName(fieldIndex, areaName, collider, IsValidParentAreaName, out var parentAreaName))
|
||||
{
|
||||
Console.WriteLine($"No parent area for {areaName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
areaName = parentAreaName;
|
||||
placeName = areas[parentAreaName].LocationNameMain;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryGetPlaceName(ref string areaName, AreaInfo areaInfo,
|
||||
PackedVec3f point, Dictionary<string, (string Name, int Index)> placeNameMap,
|
||||
IReadOnlyDictionary<string, AreaInfo> areas,
|
||||
PaldeaSceneModel scene, PaldeaFieldIndex fieldIndex, out string placeName)
|
||||
{
|
||||
placeName = areaInfo.LocationNameMain;
|
||||
if (!string.IsNullOrEmpty(placeName))
|
||||
return true;
|
||||
|
||||
// Maybe this is a sub-area? Try to get the parent area name.
|
||||
bool IsValidParentAreaName(string aName)
|
||||
{
|
||||
if (!areas.TryGetValue(aName, out var info))
|
||||
return false;
|
||||
var n = info.LocationNameMain;
|
||||
if (string.IsNullOrEmpty(n))
|
||||
return false;
|
||||
return placeNameMap.ContainsKey(n);
|
||||
}
|
||||
|
||||
if (!scene.TryGetParentAreaName(fieldIndex, areaName, point, IsValidParentAreaName, out var parentAreaName))
|
||||
{
|
||||
Console.WriteLine($"No parent area for {areaName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
areaName = parentAreaName;
|
||||
placeName = areas[parentAreaName].LocationNameMain;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void DumpScene(PaldeaSceneModel scene, string path)
|
||||
{
|
||||
// Dump each property to json.
|
||||
var dest = Path.Combine(path, "paldea_scene.json");
|
||||
var json = JsonSerializer.Serialize(scene, new JsonSerializerOptions { WriteIndented = true, IncludeFields = true });
|
||||
File.WriteAllText(dest, json);
|
||||
}
|
||||
|
||||
private static void DumpField(PaldeaFieldModel field, string path)
|
||||
{
|
||||
// Dump each property to json.
|
||||
var dest = Path.Combine(path, "paldea_field.json");
|
||||
var json = JsonSerializer.Serialize(field, new JsonSerializerOptions { WriteIndented = true, IncludeFields = true });
|
||||
File.WriteAllText(dest, json);
|
||||
}
|
||||
|
||||
public static bool IsCrossoverAllowed(int MetLocation) => MetLocation switch
|
||||
{
|
||||
8 => false, // Mesagoza
|
||||
124 => false, // Area Zero
|
||||
_ => true,
|
||||
};
|
||||
|
||||
public static Dictionary<string, (string Name, int Index)> GetInternalStringLookup(ReadOnlySpan<string> text, AHTB ahtb)
|
||||
{
|
||||
var result = new Dictionary<string, (string Name, int Index)>();
|
||||
for (var i = 0; i < text.Length; i++)
|
||||
{
|
||||
var entry = ahtb.Entries[i];
|
||||
var name = entry.Name;
|
||||
var value = (text[i], i);
|
||||
result.Add(name, value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
65
pkNX.WinForms/Dumping/SV/Encounter/EncounterExtensions9.cs
Normal file
65
pkNX.WinForms/Dumping/SV/Encounter/EncounterExtensions9.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using pkNX.Structures.FlatBuffers.SV;
|
||||
|
||||
namespace pkNX.Structures.FlatBuffers;
|
||||
|
||||
internal static class EncounterExtensions9
|
||||
{
|
||||
public static string Humanize(this SizeType type, short value) => type switch
|
||||
{
|
||||
SizeType.RANDOM => "Random",
|
||||
SizeType.XS => "XS",
|
||||
SizeType.S => "S",
|
||||
SizeType.M => "M",
|
||||
SizeType.L => "L",
|
||||
SizeType.XL => "XL",
|
||||
SizeType.VALUE => value.ToString(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
|
||||
public static string Humanize(this RareType type) => type switch
|
||||
{
|
||||
RareType.DEFAULT => "Random",
|
||||
RareType.NO_RARE => "Never",
|
||||
RareType.RARE => "Always",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
|
||||
public static string Humanize(this SexType type) => type switch
|
||||
{
|
||||
SexType.DEFAULT => "Random",
|
||||
SexType.MALE => "Male",
|
||||
SexType.FEMALE => "Female",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
|
||||
public static string Humanize(this TokuseiType type) => type switch
|
||||
{
|
||||
TokuseiType.RANDOM_12 => "1/2",
|
||||
TokuseiType.RANDOM_123 => "1/2/H",
|
||||
TokuseiType.SET_1 => "1",
|
||||
TokuseiType.SET_2 => "2",
|
||||
TokuseiType.SET_3 => "H",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "Invalid ability index"),
|
||||
};
|
||||
|
||||
public static bool IsAvoidantAction(this PokemonActionID action) => action switch
|
||||
{
|
||||
PokemonActionID.FS_POP_PATH_RUN_SPEED_UP_1_NOT_COL_DELAY => true, // lighthouse Wingull (South) and Wattrel (East, West)
|
||||
PokemonActionID.FS_POP_PATH_RUN_SPEED_UP_1_NOT_COL_DESTROY => true, // fence Wingull (Cabo Poco)
|
||||
PokemonActionID.FS_POP_COMMON_FLIGHT_NOT_COL_DESTROY => true, // starting Fletchling (level 2)
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsStationary(this PokemonActionID action) => action switch
|
||||
{
|
||||
PokemonActionID.FS_POP_ALWAYS_GAZE_BIRD_TARGET_PLAYER => true,
|
||||
PokemonActionID.FS_POP_ALWAYS_GAZE_TARGET_PLAYER => true,
|
||||
PokemonActionID.ALWAYS_GAZE_BIRD_TARGET_PLAYER_LOOP => true,
|
||||
PokemonActionID.FS_POP_LAND_SLEEPING_CURRENT_LOCATION => true,
|
||||
PokemonActionID.FS_POP_LEVITATION_SLEEPING_TREE_BRANCH => true,
|
||||
PokemonActionID.FS_POP_AREA22_DRAGONITE => true, // Handle separately.
|
||||
_ => false,
|
||||
};
|
||||
|
||||
}
|
||||
277
pkNX.WinForms/Dumping/SV/Encounter/EncounterSlotDumper9.cs
Normal file
277
pkNX.WinForms/Dumping/SV/Encounter/EncounterSlotDumper9.cs
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using pkNX.Containers;
|
||||
using pkNX.Structures.FlatBuffers.SV;
|
||||
|
||||
namespace pkNX.Structures.FlatBuffers;
|
||||
|
||||
public static class EncounterSlotDumper9
|
||||
{
|
||||
public static void DumpSlots(IFileInternal rom, EncounterDumpConfigSV config, PaldeaSceneModel scene)
|
||||
{
|
||||
var spawns = new PaldeaSpawnModel(rom);
|
||||
var db = new LocationDatabase();
|
||||
|
||||
// Process all field indices
|
||||
foreach (var index in EncounterDumperSV.AllMaps) ProcessAreas(index, scene, db, spawns, config.PlaceNameMap);
|
||||
|
||||
// Each area and their local / crossover points have been aggregated.
|
||||
// Integrate the points' slots into a single list, and consolidate entries with same level ranges to as few objects as possible.
|
||||
foreach (var storage in db.Locations.Values)
|
||||
{
|
||||
// Consolidate encounters
|
||||
storage.Integrate();
|
||||
storage.Consolidate();
|
||||
if (storage.AreaName == "a_su0104")
|
||||
storage.Consolidate();
|
||||
}
|
||||
|
||||
// Output to stream if available.
|
||||
if (config.WriteText)
|
||||
{
|
||||
using var sw = File.CreateText(Path.Combine(config.Path, "titan_enc.txt"));
|
||||
foreach (var s in db.Locations.Values)
|
||||
WriteLocation(sw, config, s.AreaName, s.AreaInfo, s.Slots);
|
||||
using var tw = File.CreateText(Path.Combine(config.Path, "titan_loc_enc.txt"));
|
||||
WriteLocEncList(tw, config, db);
|
||||
|
||||
using var pw = File.CreateText(Path.Combine(config.Path, "titan_loc_point.txt"));
|
||||
WriteLocPointList(pw, config, db);
|
||||
}
|
||||
if (config.WritePickle)
|
||||
{
|
||||
string binPath = Path.Combine(config.Path, "encounter_wild_paldea.pkl");
|
||||
SerializeEncounters(binPath, db);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessAreas(PaldeaFieldIndex fieldIndex, PaldeaSceneModel scene, LocationDatabase db, PaldeaSpawnModel map, Dictionary<string, (string Name, int Index)> placeNameMap)
|
||||
{
|
||||
// Overall Logic Flow:
|
||||
// 1 - At every point, spawn everything that can exist at that point.
|
||||
// 2 - At each area, absorb native points into local points.
|
||||
// 3 - At each area, absorb crossover points into crossover points.
|
||||
// 4 - Consolidate the encounters from both point lists.
|
||||
// Just compute everything (big memory!) then crunch it all down.
|
||||
|
||||
// Fill the point lists for each area, then spawn everything into those points.
|
||||
var areaNames = scene.AreaNames[(int)fieldIndex];
|
||||
var areas = scene.AreaInfos[(int)fieldIndex];
|
||||
var types = scene.PaldeaType[(int)fieldIndex];
|
||||
for (var i = areaNames.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var areaName = areaNames[i];
|
||||
|
||||
// Determine potential spawners
|
||||
if (!scene.TryGetContainsCheck(fieldIndex, areaName, out var collider))
|
||||
{
|
||||
Console.WriteLine($"No collider for {areaName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var areaInfo = areas[areaName];
|
||||
// Areas without a location name can't be reversed into location ID.
|
||||
if (!EncounterDumperSV.TryGetPlaceName(ref areaName, areaInfo, collider, placeNameMap, areas, scene, fieldIndex, out var placeName))
|
||||
continue;
|
||||
|
||||
// Locations that do not spawn encounters can still have crossovers bleed into them.
|
||||
// We'll have empty local encounter lists for them.
|
||||
var storage = db.Get(placeNameMap[placeName].Index, fieldIndex, areaName, areas[areaName]);
|
||||
if (areaInfo.Tag is AreaTag.NG_Encount or AreaTag.NG_All)
|
||||
continue;
|
||||
|
||||
var type = types[areaName];
|
||||
var points = map.GetPoints(fieldIndex, type);
|
||||
storage.LoadPoints(points, collider, areaInfo.ActualMinLevel, areaInfo.ActualMaxLevel, areaInfo.AdjustEncLv);
|
||||
storage.GetEncounters(map.GetPokeData(fieldIndex), scene);
|
||||
}
|
||||
|
||||
// For each area, we need to peek at the other areas to see if they have any crossover points.
|
||||
// For each of those crossover points, we need to see if they are in the current area's collider.
|
||||
// If they are, we need to add them to the current area's list of crossover points.
|
||||
foreach (var areaName in areaNames)
|
||||
{
|
||||
// Same sanity checking as above iteration.
|
||||
var areaInfo = areas[areaName];
|
||||
|
||||
// Determine potential spawners
|
||||
if (!scene.TryGetContainsCheck(fieldIndex, areaName, out var collider))
|
||||
{
|
||||
Console.WriteLine($"No collider for {areaName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = areaInfo.LocationNameMain;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
continue;
|
||||
//if (areaInfo.Tag is AreaTag.NG_Encount or AreaTag.NG_All)
|
||||
// continue;
|
||||
|
||||
var storage = db.Get(placeNameMap[name].Index, fieldIndex, areaName, areaInfo);
|
||||
if (!IsCrossoverAllowed(storage))
|
||||
continue;
|
||||
|
||||
// Here's where the fun begins. Iterate over areas inside this loop so we can look for all possible adjacent areas.
|
||||
foreach (var otherName in areaNames)
|
||||
{
|
||||
// Skip self
|
||||
if (otherName == areaName)
|
||||
continue;
|
||||
// Skip areas that don't have a location name -- subzones were the initial spawn spot if so.
|
||||
var otherAreaInfo = areas[otherName];
|
||||
var otherNameMain = otherAreaInfo.LocationNameMain;
|
||||
if (string.IsNullOrEmpty(otherNameMain))
|
||||
continue;
|
||||
|
||||
// Skip areas that don't have a collider
|
||||
if (!scene.TryGetContainsCheck(fieldIndex, otherName, out _))
|
||||
continue;
|
||||
|
||||
// Iterate over all crossover points in the other area.
|
||||
var cross = db.Get(placeNameMap[otherNameMain].Index, fieldIndex, otherName, otherAreaInfo);
|
||||
if (!IsCrossoverAllowed(cross))
|
||||
continue;
|
||||
foreach (var point in cross.Local)
|
||||
{
|
||||
// If the crossover point is close enough to the current area's collider, add it to the current area's list of crossover points.
|
||||
if (EncounterDumperSV.IsContainedBy(collider, point))
|
||||
storage.Nearby.Add(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow encounters into and out of this location
|
||||
/// </summary>
|
||||
public static bool IsCrossoverAllowed(LocationStorage storage)
|
||||
{
|
||||
var loc = storage.Location;
|
||||
return EncounterDumperSV.IsCrossoverAllowed(loc);
|
||||
}
|
||||
|
||||
private static void SerializeEncounters(string binPath, LocationDatabase db)
|
||||
{
|
||||
var tables = db.Locations.Values;
|
||||
int ctr = 0;
|
||||
var result = new byte[tables.Count + tables.Sum(z => z.SlotsCrossover.Count)][];
|
||||
foreach (var x in tables)
|
||||
{
|
||||
result[ctr++] = SerializeLocationSet((ushort)x.Location, 0, x.Slots);
|
||||
foreach (var sub in x.SlotsCrossover)
|
||||
result[ctr++] = SerializeLocationSet((ushort)x.Location, (ushort)sub.Key, sub.Value.Slots);
|
||||
}
|
||||
var mini = MiniUtil.PackMini(result, "sv");
|
||||
File.WriteAllBytes(binPath, mini);
|
||||
}
|
||||
|
||||
private static byte[] SerializeLocationSet(ushort loc, ushort crossover, IReadOnlyList<PaldeaEncounter> slots)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using var bw = new BinaryWriter(ms);
|
||||
bw.Write(loc);
|
||||
bw.Write(crossover);
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
ushort species = slot.Species;
|
||||
byte form = slot.Species switch
|
||||
{
|
||||
(ushort)Species.Vivillon or (ushort)Species.Spewpa or (ushort)Species.Scatterbug => 30,
|
||||
(ushort)Species.Minior => 31,
|
||||
_ => slot.Form,
|
||||
};
|
||||
|
||||
// ReSharper disable RedundantCast
|
||||
bw.Write(species);
|
||||
bw.Write(form);
|
||||
bw.Write((byte)slot.Gender);
|
||||
|
||||
bw.Write((byte)slot.MinLevel);
|
||||
bw.Write((byte)slot.MaxLevel);
|
||||
bw.Write((byte)slot.Time);
|
||||
bw.Write((byte)0);
|
||||
// ReSharper restore RedundantCast
|
||||
}
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
#region Writing
|
||||
|
||||
private static void WriteLocation(TextWriter tw, EncounterDumpConfigSV config, string areaName, AreaInfo areaInfo, List<PaldeaEncounter> encounts)
|
||||
{
|
||||
var loc = areaInfo.LocationNameMain;
|
||||
if (!config.PlaceNameMap.TryGetValue(loc, out var value))
|
||||
return;
|
||||
var (name, index) = value;
|
||||
var heading = $"{areaName} - {loc} - {name} ({index})";
|
||||
WriteEncounts(tw, config.SpecNamesInternal, heading, encounts);
|
||||
}
|
||||
|
||||
private static void WriteLocEncList(TextWriter tw, EncounterDumpConfigSV config, LocationDatabase db)
|
||||
{
|
||||
var names = config.PlaceNameMap;
|
||||
foreach (var place in names.Keys.OrderBy(p => names[p].Index))
|
||||
{
|
||||
var (name, index) = names[place];
|
||||
if (!db.Locations.TryGetValue(index, out var encounts))
|
||||
continue;
|
||||
|
||||
var heading = $"{name} ({index})";
|
||||
WriteEncounts(tw, config.SpecNamesInternal, heading, encounts.Slots);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteEncounts(TextWriter tw, ReadOnlySpan<string> specNamesInternal, string heading, IEnumerable<PaldeaEncounter> encounts)
|
||||
{
|
||||
tw.WriteLine("===");
|
||||
tw.WriteLine(heading);
|
||||
tw.WriteLine("===");
|
||||
foreach (var e in encounts)
|
||||
tw.WriteLine($" - {e.GetEncountString(specNamesInternal)}");
|
||||
tw.WriteLine();
|
||||
}
|
||||
|
||||
private static void WriteLocPointList(TextWriter tw, EncounterDumpConfigSV config, LocationDatabase db)
|
||||
{
|
||||
var names = config.PlaceNameMap;
|
||||
foreach (var place in names.Keys.OrderBy(p => names[p].Index))
|
||||
{
|
||||
var (name, index) = names[place];
|
||||
if (!db.Locations.TryGetValue(index, out var loc))
|
||||
continue;
|
||||
|
||||
var heading = $"{name} ({index})";
|
||||
WriteBiomes(tw, heading, loc.Local);
|
||||
}
|
||||
|
||||
foreach (var place in names.Keys.OrderBy(p => names[p].Index))
|
||||
{
|
||||
var (name, index) = names[place];
|
||||
if (!db.Locations.TryGetValue(index, out var loc))
|
||||
continue;
|
||||
|
||||
var heading = $"{name} ({index})";
|
||||
WritePoints(tw, heading, loc.Local);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteBiomes(TextWriter tw, string heading, List<LocationPointDetail> points)
|
||||
{
|
||||
var biomes = points.Select(z => z.Point.Biome).Distinct().Select(z => z.ToString()).Order();
|
||||
var btext = string.Join(',', biomes);
|
||||
tw.WriteLine($"{heading}\t{btext}");
|
||||
}
|
||||
|
||||
private static void WritePoints(TextWriter tw, string heading, List<LocationPointDetail> points)
|
||||
{
|
||||
tw.WriteLine("===");
|
||||
tw.WriteLine(heading);
|
||||
tw.WriteLine("===");
|
||||
foreach (var e in points)
|
||||
tw.WriteLine($" - {e.GetString()}");
|
||||
tw.WriteLine();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
286
pkNX.WinForms/Dumping/SV/Encounter/FixedSymbolDumper9.cs
Normal file
286
pkNX.WinForms/Dumping/SV/Encounter/FixedSymbolDumper9.cs
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using pkNX.Containers;
|
||||
using pkNX.Structures.FlatBuffers.SV;
|
||||
|
||||
namespace pkNX.Structures.FlatBuffers;
|
||||
|
||||
public static class FixedSymbolDumper9
|
||||
{
|
||||
private record struct AppearTuple(string PlaceName, string AreaName, int Adjust, PackedVec3f Point);
|
||||
|
||||
public static void Dump(IFileInternal rom, EncounterDumpConfigSV config, PaldeaSceneModel scene)
|
||||
{
|
||||
List<byte[]> serialized = [];
|
||||
var fsym = new PaldeaFixedSymbolModel(rom);
|
||||
var array = rom.GetPackedFile("world/data/field/fixed_symbol/fixed_symbol_table/fixed_symbol_table_array.bin");
|
||||
var fsymData = FlatBufferConverter.DeserializeFrom<FixedSymbolTableArray>(array);
|
||||
foreach (var (game, gamePoints) in new[] { ("sl", points: fsym.PointsScarlet), ("vl", points: fsym.PointsViolet) })
|
||||
{
|
||||
using var gw = File.CreateText(Path.Combine(config.Path, $"titan_fixed_{game}.txt"));
|
||||
foreach (var fieldIndex in EncounterDumperSV.AllMaps)
|
||||
{
|
||||
var areaNames = scene.AreaNames[(int)fieldIndex];
|
||||
var areas = scene.AreaInfos[(int)fieldIndex];
|
||||
var atlantis = scene.PaldeaType[(int)fieldIndex];
|
||||
var allPoints = gamePoints[(int)fieldIndex];
|
||||
|
||||
if (fieldIndex is PaldeaFieldIndex.Kitakami)
|
||||
{
|
||||
areas = areas.Where(z => z.Value.AdjustEncLv != 0)
|
||||
.ToDictionary(z => z.Key, z => z.Value);
|
||||
areaNames = [.. areas.Keys];
|
||||
}
|
||||
|
||||
for (var i = 0; i < fsymData.Table.Count; i++)
|
||||
{
|
||||
var entry = fsymData.Table[i];
|
||||
var tableKey = entry.TableKey;
|
||||
if (tableKey.StartsWith("su2_w23d10_"))
|
||||
continue; // handle later, manually
|
||||
var points = allPoints.FindAll(p => p.TableKey == tableKey);
|
||||
if (points.Count == 0)
|
||||
continue;
|
||||
|
||||
var appearAreas = new List<AppearTuple>();
|
||||
|
||||
if (!FindArea(AreaType.Cave))
|
||||
FindArea(AreaType.Default);
|
||||
|
||||
bool FindArea(AreaType type)
|
||||
{
|
||||
for (var x = areaNames.Count - 1; x >= 0; x--)
|
||||
{
|
||||
var areaName = areaNames[x];
|
||||
if (atlantis[areaName] != PaldeaPointPivot.Overworld)
|
||||
continue;
|
||||
|
||||
var areaInfo = areas[areaName];
|
||||
if (type != AreaType.Default && areaInfo.Type != type)
|
||||
continue;
|
||||
if (areaInfo.Tag is AreaTag.NG_Encount)
|
||||
continue;
|
||||
if (!scene.TryGetContainsCheck(fieldIndex, areaName, out var collider))
|
||||
continue;
|
||||
for (int p = 0; p < points.Count; p++)
|
||||
{
|
||||
var point = points[p];
|
||||
var pt = point.Position;
|
||||
if (!collider.ContainsPoint(pt.X, pt.Y, pt.Z))
|
||||
continue;
|
||||
var tmp = areaName;
|
||||
if (!EncounterDumperSV.TryGetPlaceName(ref tmp, areaInfo, pt, config.PlaceNameMap, areas, scene, fieldIndex, out var placeName))
|
||||
continue;
|
||||
if (!appearAreas.Exists(z => z.Point == pt && z.AreaName == tmp))
|
||||
appearAreas.Add(new(placeName, tmp, areaInfo.AdjustEncLv, pt));
|
||||
points.RemoveAt(p);
|
||||
p--;
|
||||
}
|
||||
|
||||
if (points.Count == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteFixedSpawn(config, gw, tableKey, i, entry, appearAreas);
|
||||
|
||||
// Serialize
|
||||
if (entry.PokeAI.ActionId.IsAvoidantAction())
|
||||
continue; // not actually encounter-able, they're just for spectacle
|
||||
// var locs = appearAreas.Select(a => placeNameMap[a.PlaceName].Index);
|
||||
if (appearAreas.Count == 0)
|
||||
continue;
|
||||
|
||||
// If not stationary, allow some tolerance.
|
||||
var wanderAreas = new List<AppearTuple>(0);
|
||||
if (!entry.PokeAI.ActionId.IsStationary())
|
||||
{
|
||||
var allInfos = scene.AreaInfos[(int)fieldIndex];
|
||||
var allNames = scene.AreaNames[(int)fieldIndex];
|
||||
for (var a = 0; a < allNames.Count; a++)
|
||||
{
|
||||
var areaName = allNames[a];
|
||||
if (atlantis[areaName] != PaldeaPointPivot.Overworld)
|
||||
continue;
|
||||
|
||||
var areaInfo = allInfos[areaName];
|
||||
if (!scene.TryGetContainsCheck(fieldIndex, areaName, out var collider))
|
||||
continue;
|
||||
if (areaInfo.Tag is AreaTag.NG_Encount)
|
||||
continue;
|
||||
if (!TryGetBleedArea(collider, appearAreas, scene, fieldIndex, out var bledFrom))
|
||||
continue; // can't bleed into this area
|
||||
|
||||
// Bleeding from zones should start from the sub-zone, so we don't need to check other sub-zones.
|
||||
if (!EncounterDumperSV.TryGetPlaceName(ref areaName, areaInfo, collider, config.PlaceNameMap, allInfos, scene, fieldIndex, out var placeName))
|
||||
continue;
|
||||
wanderAreas.Add(bledFrom with { PlaceName = placeName, AreaName = allNames[a] });
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var area in appearAreas.GroupBy(z => z.Adjust))
|
||||
{
|
||||
var adjust = area.Key;
|
||||
var wander = wanderAreas.Where(z => z.Adjust == adjust);
|
||||
var allLocations = area.Concat(wander);
|
||||
var locationIDs = allLocations.Select(z => config.PlaceNameMap[z.PlaceName].Index).Distinct().ToList();
|
||||
if (fieldIndex == PaldeaFieldIndex.Paldea && entry.PokeAI.ActionId == PokemonActionID.FS_POP_AREA22_DRAGONITE) // Flies around not using tolerance.
|
||||
locationIDs.Add(46); // North Province (Area One)
|
||||
locationIDs.Sort();
|
||||
|
||||
WriteFixedSymbol(serialized, entry, locationIDs);
|
||||
if (adjust != 0)
|
||||
WriteFixedSymbol(serialized, entry, locationIDs, adjust);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var underDepths = Array.FindIndex(fsymData.Table.ToArray(), x => x.TableKey == "su2_w23d10_01");
|
||||
for (int i = underDepths; i < underDepths + 23; i++)
|
||||
{
|
||||
var entry = fsymData.Table[i];
|
||||
var tableKey = entry.TableKey;
|
||||
var appearAreas = new List<AppearTuple> { new("PLACENAME_a_w23_d10_01", "a_w23_d10_subarea", 0, new()) };
|
||||
WriteFixedSpawn(config, gw, tableKey, i, entry, appearAreas);
|
||||
WriteFixedSymbol(serialized, entry, [196]);
|
||||
}
|
||||
}
|
||||
|
||||
var pathPickle = Path.Combine(config.Path, "encounter_fixed_paldea.pkl");
|
||||
var ordered = serialized
|
||||
.OrderBy(z => BinaryPrimitives.ReadUInt16LittleEndian(z)) // Species
|
||||
.ThenBy(z => z[2]) // Form
|
||||
.ThenBy(z => z[3]) // Level
|
||||
;
|
||||
File.WriteAllBytes(pathPickle, ordered.SelectMany(z => z).ToArray());
|
||||
}
|
||||
|
||||
private static void WriteFixedSpawn(EncounterDumpConfigSV config, StreamWriter gw, string tableKey, int i, FixedSymbolTable entry, List<AppearTuple> appearAreas)
|
||||
{
|
||||
gw.WriteLine("===");
|
||||
gw.WriteLine($"{tableKey} - {i}");
|
||||
gw.WriteLine("===");
|
||||
gw.WriteLine(" PokeData:");
|
||||
var pd = entry.Symbol;
|
||||
gw.WriteLine($" Species: {config.SpecNamesInternal[(int)pd.DevId]}");
|
||||
gw.WriteLine($" Form: {pd.FormId}");
|
||||
gw.Write($" Level: {pd.Level}");
|
||||
foreach (var adj in appearAreas.Select(a => a.Adjust).Where(lv => lv != 0).Distinct().Order())
|
||||
{
|
||||
gw.Write($", {pd.Level + adj}");
|
||||
}
|
||||
gw.WriteLine();
|
||||
gw.WriteLine($" Sex: {pd.Sex.Humanize()}");
|
||||
gw.WriteLine($" Shiny: {pd.RareType.Humanize()}");
|
||||
|
||||
var talentStr = pd.TalentType switch
|
||||
{
|
||||
TalentType.RANDOM => "Random",
|
||||
TalentType.V_NUM => $"{pd.TalentVNum} Perfect",
|
||||
TalentType.VALUE => $"{pd.TalentValue.HP}/{pd.TalentValue.ATK}/{pd.TalentValue.DEF}/{pd.TalentValue.SPA}/{pd.TalentValue.SPD}/{pd.TalentValue.SPE}",
|
||||
_ => "Invalid",
|
||||
};
|
||||
gw.WriteLine($" IVs: {talentStr}");
|
||||
gw.WriteLine($" Ability: {pd.TokuseiIndex.Humanize()}");
|
||||
switch (pd.WazaType)
|
||||
{
|
||||
case WazaType.DEFAULT:
|
||||
gw.WriteLine(" Moves: Random");
|
||||
break;
|
||||
case WazaType.MANUAL:
|
||||
gw.WriteLine($" Moves: {config[pd.Waza1]}/{config[pd.Waza2]}/{config[pd.Waza3]}/{config[pd.Waza4]}");
|
||||
break;
|
||||
}
|
||||
|
||||
gw.WriteLine($" Scale: {pd.ScaleType.Humanize(pd.ScaleValue)}");
|
||||
gw.WriteLine($" GemType: {(int)pd.GemType}");
|
||||
|
||||
gw.WriteLine($" Probability: {entry.PokeGeneration.RepopProbability}");
|
||||
gw.WriteLine($" Pattern: {entry.PokeGeneration.GenerationPattern}");
|
||||
gw.WriteLine($" Scenario: {entry.PokeGeneration.RequireScenarioId}");
|
||||
gw.WriteLine($" Action: {entry.PokeAI.ActionId}");
|
||||
gw.WriteLine($" Trigger: {entry.PokeAI.TriggerActionId}");
|
||||
|
||||
gw.WriteLine(" Areas:");
|
||||
foreach (var area in appearAreas)
|
||||
{
|
||||
var loc = area.PlaceName;
|
||||
var (name, index) = config.PlaceNameMap[loc];
|
||||
gw.WriteLine($" - {area.PlaceName} - {loc} - {name} ({index})");
|
||||
var point = area.Point;
|
||||
gw.WriteLine($" @ ({point.X}, {point.Y}, {point.Z})");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetBleedArea(IContainsV3f collider, List<AppearTuple> appearAreas, PaldeaSceneModel scene, PaldeaFieldIndex fieldIndex, out AppearTuple bledFrom)
|
||||
{
|
||||
foreach (var a in appearAreas)
|
||||
{
|
||||
var p = a.Point;
|
||||
if (!EncounterDumperSV.IsContainedBy(collider, p))
|
||||
continue;
|
||||
// Get the original area it bled from.
|
||||
bledFrom = appearAreas.First(z =>
|
||||
scene.TryGetContainsCheck(fieldIndex, z.AreaName, out var c) &&
|
||||
c.ContainsPoint(p.X, p.Y, p.Z));
|
||||
return true;
|
||||
}
|
||||
bledFrom = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void WriteFixedSymbol(ICollection<byte[]> exist, FixedSymbolTable entry, IReadOnlyList<int> locs, int adjustLevel = 0)
|
||||
{
|
||||
var enc = entry.Symbol;
|
||||
using var ms = new MemoryStream();
|
||||
using var bw = new BinaryWriter(ms);
|
||||
|
||||
ushort species = SpeciesConverterSV.GetNational9((ushort)enc.DevId);
|
||||
byte form = (byte)enc.FormId;
|
||||
if (species == (int)Species.Minior)
|
||||
{
|
||||
// Not form random -- keep original form ID.
|
||||
// Encounter Slots themselves are form random, but the fixed symbols aren't.
|
||||
form += 7; // Out of battle form ID (without the rocky shields up)
|
||||
}
|
||||
|
||||
bw.Write(species);
|
||||
bw.Write(form);
|
||||
bw.Write((byte)(enc.Level + adjustLevel));
|
||||
|
||||
bw.Write(enc.TalentType == TalentType.RANDOM ? (byte)0 : (byte)enc.TalentVNum);
|
||||
bw.Write((byte)enc.GemType);
|
||||
bw.Write((byte)(enc.Sex - 1));
|
||||
bw.Write((byte)(enc.TokuseiIndex switch
|
||||
{
|
||||
TokuseiType.RANDOM_12 => PKHeX.Core.AbilityPermission.Any12,
|
||||
TokuseiType.RANDOM_123 => PKHeX.Core.AbilityPermission.Any12H,
|
||||
TokuseiType.SET_1 => PKHeX.Core.AbilityPermission.OnlyFirst,
|
||||
TokuseiType.SET_2 => PKHeX.Core.AbilityPermission.OnlySecond,
|
||||
TokuseiType.SET_3 => PKHeX.Core.AbilityPermission.OnlyHidden,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(TokuseiType), enc.TokuseiIndex, null),
|
||||
}));
|
||||
|
||||
bw.Write((ushort)enc.Waza1.WazaId);
|
||||
bw.Write((ushort)enc.Waza2.WazaId);
|
||||
|
||||
bw.Write((ushort)enc.Waza3.WazaId);
|
||||
bw.Write((ushort)enc.Waza4.WazaId);
|
||||
|
||||
// At most 3 locations, but just use 4 bytes.
|
||||
Span<byte> temp = stackalloc byte[4];
|
||||
if (locs.Count > temp.Length)
|
||||
throw new ArgumentException("Too many locations??", nameof(locs));
|
||||
for (int i = 0; i < locs.Count; i++)
|
||||
temp[i] = (byte)locs[i];
|
||||
bw.Write(temp);
|
||||
|
||||
var result = ms.ToArray();
|
||||
if (!exist.Any(x => x.SequenceEqual(result)))
|
||||
exist.Add(result);
|
||||
}
|
||||
}
|
||||
93
pkNX.WinForms/Dumping/SV/Encounter/GimmighoulDumper.cs
Normal file
93
pkNX.WinForms/Dumping/SV/Encounter/GimmighoulDumper.cs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using pkNX.Containers;
|
||||
using pkNX.Structures.FlatBuffers.SV;
|
||||
|
||||
namespace pkNX.Structures.FlatBuffers;
|
||||
|
||||
public static class GimmighoulDumper
|
||||
{
|
||||
public static void Dump(IFileInternal rom, EncounterDumpConfigSV config, PaldeaSceneModel scene)
|
||||
{
|
||||
var csym = new PaldeaCoinSymbolModel(rom);
|
||||
var eventBattle = FlatBufferConverter.DeserializeFrom<EventBattlePokemonArray>(rom.GetPackedFile("world/data/battle/eventBattlePokemon/eventBattlePokemon_array.bin"));
|
||||
Dump(config, csym, scene, eventBattle);
|
||||
}
|
||||
|
||||
private static void Dump(EncounterDumpConfigSV config, PaldeaCoinSymbolModel csym, PaldeaSceneModel scene, EventBattlePokemonArray eventBattle)
|
||||
{
|
||||
using var cw = File.CreateText(Path.Combine(config.Path, "titan_coin_symbol.txt"));
|
||||
foreach (var entry in csym.Points[(int)PaldeaFieldIndex.Paldea])
|
||||
{
|
||||
var areas = new List<string>();
|
||||
foreach (var areaName in scene.AreaNames[(int)PaldeaFieldIndex.Paldea])
|
||||
{
|
||||
if (scene.PaldeaType[(int)PaldeaFieldIndex.Paldea][areaName] != PaldeaPointPivot.Overworld)
|
||||
continue;
|
||||
|
||||
var areaInfo = scene.AreaInfos[(int)PaldeaFieldIndex.Paldea][areaName];
|
||||
var name = areaInfo.LocationNameMain;
|
||||
if (string.IsNullOrEmpty(name)) // Don't worry about subzones
|
||||
continue;
|
||||
if (areaInfo.Tag is AreaTag.NG_Encount or AreaTag.NG_All)
|
||||
continue;
|
||||
|
||||
if (scene.IsPointContained(PaldeaFieldIndex.Paldea, areaName, entry.Position.X, entry.Position.Y, entry.Position.Z))
|
||||
areas.Add(areaName);
|
||||
}
|
||||
|
||||
// var locs = areas.Select(a => placeNameMap[scene.AreaInfos[a].LocationNameMain].Index).Distinct().ToList();
|
||||
|
||||
cw.WriteLine("===");
|
||||
cw.WriteLine(entry.Name);
|
||||
cw.WriteLine("===");
|
||||
cw.WriteLine($" First Num: {entry.FirstNum}");
|
||||
cw.WriteLine($" Coordinates: ({entry.Position.X}, {entry.Position.Y}, {entry.Position.Z})");
|
||||
|
||||
if (entry.IsBox)
|
||||
{
|
||||
cw.WriteLine($" Box Label: {entry.BoxLabel}");
|
||||
cw.WriteLine(" PokeData:");
|
||||
var pd = eventBattle.Table.First(e => e.Label == entry.BoxLabel).PokeData;
|
||||
|
||||
cw.WriteLine($" Species: {config[pd.DevId]}");
|
||||
cw.WriteLine($" Form: {pd.FormId}");
|
||||
cw.WriteLine($" Level: {pd.Level}");
|
||||
cw.WriteLine($" Sex: {pd.Sex.Humanize()}");
|
||||
cw.WriteLine($" Shiny: {pd.RareType.Humanize()}");
|
||||
|
||||
var talentStr = pd.TalentType switch
|
||||
{
|
||||
TalentType.RANDOM => "Random",
|
||||
TalentType.V_NUM => $"{pd.TalentVnum} Perfect",
|
||||
TalentType.VALUE => $"{pd.TalentValue.HP}/{pd.TalentValue.ATK}/{pd.TalentValue.DEF}/{pd.TalentValue.SPA}/{pd.TalentValue.SPD}/{pd.TalentValue.SPE}",
|
||||
_ => "Invalid",
|
||||
};
|
||||
cw.WriteLine($" IVs: {talentStr}");
|
||||
cw.WriteLine($" Ability: {pd.Tokusei.Humanize()}");
|
||||
switch (pd.WazaType)
|
||||
{
|
||||
case WazaType.DEFAULT:
|
||||
cw.WriteLine(" Moves: Random");
|
||||
break;
|
||||
case WazaType.MANUAL:
|
||||
cw.WriteLine($" Moves: {config[pd.Waza1]}/{config[pd.Waza2]}/{config[pd.Waza3]}/{config[pd.Waza4]}");
|
||||
break;
|
||||
}
|
||||
|
||||
cw.WriteLine($" Scale: {pd.ScaleType.Humanize(pd.ScaleValue)}");
|
||||
cw.WriteLine($" GemType: {(int)pd.GemType}");
|
||||
}
|
||||
|
||||
cw.WriteLine(" Areas:");
|
||||
foreach (var areaName in areas)
|
||||
{
|
||||
var areaInfo = scene.AreaInfos[(int)PaldeaFieldIndex.Paldea][areaName];
|
||||
var loc = areaInfo.LocationNameMain;
|
||||
var (name, index) = config.PlaceNameMap[loc];
|
||||
cw.WriteLine($" - {areaName} - {loc} - {name} ({index})");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using pkNX.Structures.FlatBuffers.SV;
|
||||
|
||||
namespace pkNX.Structures.FlatBuffers;
|
||||
|
|
@ -39,7 +38,7 @@ public static PaldeaEncounter GetBand(EncountPokeData pd, PointData ep, int adju
|
|||
return new(SpeciesConverterSV.GetNational9((ushort)pd.BandPoke), (byte)pd.BandForm, (byte)pd.BandSex, min, max, time);
|
||||
}
|
||||
|
||||
public string GetEncountString(IReadOnlyList<string> specNamesInternal)
|
||||
public string GetEncountString(ReadOnlySpan<string> specNamesInternal)
|
||||
{
|
||||
var species = specNamesInternal[SpeciesConverterSV.GetInternal9(Species)];
|
||||
return GetString(species);
|
||||
|
|
@ -1,850 +0,0 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using pkNX.Containers;
|
||||
using pkNX.Structures;
|
||||
using pkNX.Structures.FlatBuffers;
|
||||
using pkNX.Structures.FlatBuffers.SV;
|
||||
|
||||
namespace pkNX.WinForms;
|
||||
|
||||
public class EncounterDumperSV(IFileInternal rom)
|
||||
{
|
||||
private const float tolX = 30f;
|
||||
private const float tolY = 30f;
|
||||
private const float tolZ = 30f;
|
||||
|
||||
private static ReadOnlySpan<PaldeaFieldIndex> AllMaps =>
|
||||
[
|
||||
PaldeaFieldIndex.Paldea,
|
||||
PaldeaFieldIndex.Kitakami,
|
||||
PaldeaFieldIndex.Terarium,
|
||||
];
|
||||
|
||||
public void DumpTo(string path, IReadOnlyList<string> specNamesInternal, IReadOnlyList<string> moveNames,
|
||||
Dictionary<string, (string Name, int Index)> placeNameMap,
|
||||
bool writeText = true, bool writePickle = true)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
var field = new PaldeaFieldModel(rom);
|
||||
var scene = new PaldeaSceneModel(rom, field);
|
||||
var fsym = new PaldeaFixedSymbolModel(rom);
|
||||
var csym = new PaldeaCoinSymbolModel(rom);
|
||||
var mlEncPoints = FlatBufferConverter.DeserializeFrom<PointDataArray>(rom.GetPackedFile("world/data/encount/point_data/point_data/encount_data_100000.bin"));
|
||||
var alEncPoints = FlatBufferConverter.DeserializeFrom<PointDataArray>(rom.GetPackedFile("world/data/encount/point_data/point_data/encount_data_atlantis.bin"));
|
||||
var su1EncPoints = FlatBufferConverter.DeserializeFrom<PointDataArray>(rom.GetPackedFile("world/data/encount/point_data/point_data/encount_data_su1.bin"));
|
||||
var su2EncPoints = FlatBufferConverter.DeserializeFrom<PointDataArray>(rom.GetPackedFile("world/data/encount/point_data/point_data/encount_data_su2.bin"));
|
||||
//var lcEncPoints = FlatBufferConverter.DeserializeFrom<PointDataArray>(ROM.GetPackedFile("world/data/encount/point_data/point_data/encount_data_lc.bin"));
|
||||
var pokeDataMain = FlatBufferConverter.DeserializeFrom<EncountPokeDataArray>(rom.GetPackedFile("world/data/encount/pokedata/pokedata/pokedata_array.bin"));
|
||||
var pokeDataSu1 = FlatBufferConverter.DeserializeFrom<EncountPokeDataArray>(rom.GetPackedFile("world/data/encount/pokedata/pokedata_su1/pokedata_su1_array.bin"));
|
||||
var pokeDataSu2 = FlatBufferConverter.DeserializeFrom<EncountPokeDataArray>(rom.GetPackedFile("world/data/encount/pokedata/pokedata_su2/pokedata_su2_array.bin"));
|
||||
//var pokeDataLc = FlatBufferConverter.DeserializeFrom<EncountPokeDataArray>(ROM.GetPackedFile("world/data/encount/pokedata/pokedata_lc/pokedata_lc_array.bfbs"));
|
||||
|
||||
var db = new LocationDatabase();
|
||||
|
||||
// Points can be used by multiple areas as crossover sources. Need to be able to "belong" to multiple areas, and indicate their parent area.
|
||||
var pointMain = ReformatPoints(mlEncPoints);
|
||||
var pointAtlantis = ReformatPoints(alEncPoints);
|
||||
var pointSu1 = ReformatPoints(su1EncPoints);
|
||||
var pointSu2 = ReformatPoints(su2EncPoints);
|
||||
//var lc = ReformatPoints(lcEncPoints);
|
||||
|
||||
// Process all field indices
|
||||
ProcessAreas(PaldeaFieldIndex.Paldea);
|
||||
ProcessAreas(PaldeaFieldIndex.Kitakami);
|
||||
ProcessAreas(PaldeaFieldIndex.Terarium);
|
||||
|
||||
// Each area and their local / crossover points have been aggregated.
|
||||
// Integrate the points' slots into a single list, and consolidate entries with same level ranges to as few objects as possible.
|
||||
foreach (var storage in db.Locations.Values)
|
||||
{
|
||||
// Consolidate encounters
|
||||
storage.Integrate();
|
||||
storage.Consolidate();
|
||||
if (storage.AreaName == "a_su0104")
|
||||
storage.Consolidate();
|
||||
}
|
||||
|
||||
// Output to stream if available.
|
||||
if (writeText)
|
||||
{
|
||||
using var sw = File.CreateText(Path.Combine(path, "titan_enc.txt"));
|
||||
foreach (var s in db.Locations.Values)
|
||||
WriteLocation(sw, specNamesInternal, placeNameMap, s.AreaName, s.AreaInfo, s.Slots);
|
||||
using var tw = File.CreateText(Path.Combine(path, "titan_loc_enc.txt"));
|
||||
WriteLocEncList(tw, specNamesInternal, placeNameMap, db);
|
||||
|
||||
using var pw = File.CreateText(Path.Combine(path, "titan_loc_point.txt"));
|
||||
WriteLocPointList(pw, placeNameMap, db);
|
||||
}
|
||||
if (writePickle)
|
||||
{
|
||||
string binPath = Path.Combine(path, "encounter_wild_paldea.pkl");
|
||||
SerializeEncounters(binPath, db);
|
||||
}
|
||||
|
||||
// Fixed symbols
|
||||
List<byte[]> serialized = [];
|
||||
var fsymData = FlatBufferConverter.DeserializeFrom<FixedSymbolTableArray>(rom.GetPackedFile("world/data/field/fixed_symbol/fixed_symbol_table/fixed_symbol_table_array.bin"));
|
||||
var eventBattle = FlatBufferConverter.DeserializeFrom<EventBattlePokemonArray>(rom.GetPackedFile("world/data/battle/eventBattlePokemon/eventBattlePokemon_array.bin"));
|
||||
foreach (var (game, gamePoints) in new[] { ("sl", fsym.scarletPoints), ("vl", fsym.violetPoints) })
|
||||
{
|
||||
using var gw = File.CreateText(Path.Combine(path, $"titan_fixed_{game}.txt"));
|
||||
foreach (var fieldIndex in AllMaps)
|
||||
{
|
||||
var areaNames = scene.AreaNames[(int)fieldIndex];
|
||||
var areas = scene.AreaInfos[(int)fieldIndex];
|
||||
var atlantis = scene.PaldeaType[(int)fieldIndex];
|
||||
var allPoints = gamePoints[(int)fieldIndex];
|
||||
|
||||
if (fieldIndex is PaldeaFieldIndex.Kitakami)
|
||||
{
|
||||
areas = areas.Where(z => z.Value.AdjustEncLv != 0)
|
||||
.ToDictionary(z => z.Key, z => z.Value);
|
||||
areaNames = [.. areas.Keys];
|
||||
}
|
||||
|
||||
for (var i = 0; i < fsymData.Table.Count; i++)
|
||||
{
|
||||
var entry = fsymData.Table[i];
|
||||
var tableKey = entry.TableKey;
|
||||
if (tableKey.StartsWith("su2_w23d10_"))
|
||||
continue; // handle later, manually
|
||||
var points = allPoints.FindAll(p => p.TableKey == tableKey);
|
||||
if (points.Count == 0)
|
||||
continue;
|
||||
|
||||
var appearAreas = new List<AppearTuple>();
|
||||
|
||||
if (!FindArea(AreaType.Cave))
|
||||
FindArea(AreaType.Default);
|
||||
|
||||
bool FindArea(AreaType type)
|
||||
{
|
||||
for (var x = areaNames.Count - 1; x >= 0; x--)
|
||||
{
|
||||
var areaName = areaNames[x];
|
||||
if (atlantis[areaName] != PaldeaPointPivot.Overworld)
|
||||
continue;
|
||||
|
||||
var areaInfo = areas[areaName];
|
||||
if (type != AreaType.Default && areaInfo.Type != type)
|
||||
continue;
|
||||
if (areaInfo.Tag is AreaTag.NG_Encount)
|
||||
continue;
|
||||
if (!scene.TryGetContainsCheck(fieldIndex, areaName, out var collider))
|
||||
continue;
|
||||
for (int p = 0; p < points.Count; p++)
|
||||
{
|
||||
var point = points[p];
|
||||
var pt = point.Position;
|
||||
if (!collider.ContainsPoint(pt.X, pt.Y, pt.Z))
|
||||
continue;
|
||||
var tmp = areaName;
|
||||
if (!TryGetPlaceName(ref tmp, areaInfo, pt, placeNameMap, areas, scene, fieldIndex, out var placeName))
|
||||
continue;
|
||||
if (!appearAreas.Exists(z => z.Point == pt && z.AreaName == tmp))
|
||||
appearAreas.Add(new(placeName, tmp, areaInfo.AdjustEncLv, pt));
|
||||
points.RemoveAt(p);
|
||||
p--;
|
||||
}
|
||||
|
||||
if (points.Count == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteFixedSpawn(specNamesInternal, moveNames, placeNameMap, gw, tableKey, i, entry, appearAreas);
|
||||
|
||||
// Serialize
|
||||
if (IsAvoidantAction(entry.PokeAI.ActionId))
|
||||
continue; // not actually encounter-able, they're just for spectacle
|
||||
// var locs = appearAreas.Select(a => placeNameMap[a.PlaceName].Index);
|
||||
if (appearAreas.Count == 0)
|
||||
continue;
|
||||
|
||||
// If not stationary, allow some tolerance.
|
||||
var aiStationary = GetIsStationary(entry.PokeAI.ActionId);
|
||||
var wanderAreas = new List<AppearTuple>(0);
|
||||
if (!aiStationary)
|
||||
{
|
||||
var allInfos = scene.AreaInfos[(int)fieldIndex];
|
||||
var allNames = scene.AreaNames[(int)fieldIndex];
|
||||
for (var a = 0; a < allNames.Count; a++)
|
||||
{
|
||||
var areaName = allNames[a];
|
||||
if (atlantis[areaName] != PaldeaPointPivot.Overworld)
|
||||
continue;
|
||||
|
||||
var areaInfo = allInfos[areaName];
|
||||
if (!scene.TryGetContainsCheck(fieldIndex, areaName, out var collider))
|
||||
continue;
|
||||
if (areaInfo.Tag is AreaTag.NG_Encount)
|
||||
continue;
|
||||
if (!TryGetBleedArea(collider, appearAreas, scene, fieldIndex, out var bledFrom))
|
||||
continue; // can't bleed into this area
|
||||
|
||||
// Bleeding from zones should start from the sub-zone, so we don't need to check other sub-zones.
|
||||
if (!TryGetPlaceName(ref areaName, areaInfo, collider, placeNameMap, allInfos, scene, fieldIndex, out var placeName))
|
||||
continue;
|
||||
wanderAreas.Add(bledFrom with { PlaceName = placeName, AreaName = allNames[a] });
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var area in appearAreas.GroupBy(z => z.Adjust))
|
||||
{
|
||||
var adjust = area.Key;
|
||||
var wander = wanderAreas.Where(z => z.Adjust == adjust);
|
||||
var allLocations = area.Concat(wander);
|
||||
var locationIDs = allLocations.Select(z => placeNameMap[z.PlaceName].Index).Distinct().ToList();
|
||||
if (fieldIndex == PaldeaFieldIndex.Paldea && entry.PokeAI.ActionId == PokemonActionID.FS_POP_AREA22_DRAGONITE) // Flies around not using tolerance.
|
||||
locationIDs.Add(46); // North Province (Area One)
|
||||
locationIDs.Sort();
|
||||
|
||||
WriteFixedSymbol(serialized, entry, locationIDs);
|
||||
if (adjust != 0)
|
||||
WriteFixedSymbol(serialized, entry, locationIDs, adjust);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var underDepths = Array.FindIndex(fsymData.Table.ToArray(), x => x.TableKey == "su2_w23d10_01");
|
||||
for (int i = underDepths; i < underDepths + 23; i++)
|
||||
{
|
||||
var entry = fsymData.Table[i];
|
||||
var tableKey = entry.TableKey;
|
||||
var appearAreas = new List<AppearTuple> { new("PLACENAME_a_w23_d10_01", "a_w23_d10_subarea", 0, new()) };
|
||||
WriteFixedSpawn(specNamesInternal, moveNames, placeNameMap, gw, tableKey, i, entry, appearAreas);
|
||||
WriteFixedSymbol(serialized, entry, [196]);
|
||||
}
|
||||
}
|
||||
|
||||
// Gimmighoul only in Paldea
|
||||
using var cw = File.CreateText(Path.Combine(path, "titan_coin_symbol.txt"));
|
||||
foreach (var entry in csym.Points[(int)PaldeaFieldIndex.Paldea])
|
||||
{
|
||||
var areas = new List<string>();
|
||||
foreach (var areaName in scene.AreaNames[(int)PaldeaFieldIndex.Paldea])
|
||||
{
|
||||
if (scene.PaldeaType[(int)PaldeaFieldIndex.Paldea][areaName] != PaldeaPointPivot.Overworld)
|
||||
continue;
|
||||
|
||||
var areaInfo = scene.AreaInfos[(int)PaldeaFieldIndex.Paldea][areaName];
|
||||
var name = areaInfo.LocationNameMain;
|
||||
if (string.IsNullOrEmpty(name)) // Don't worry about subzones
|
||||
continue;
|
||||
if (areaInfo.Tag is AreaTag.NG_Encount or AreaTag.NG_All)
|
||||
continue;
|
||||
|
||||
if (scene.IsPointContained(PaldeaFieldIndex.Paldea, areaName, entry.Position.X, entry.Position.Y, entry.Position.Z))
|
||||
areas.Add(areaName);
|
||||
}
|
||||
|
||||
// var locs = areas.Select(a => placeNameMap[scene.AreaInfos[a].LocationNameMain].Index).Distinct().ToList();
|
||||
|
||||
cw.WriteLine("===");
|
||||
cw.WriteLine(entry.Name);
|
||||
cw.WriteLine("===");
|
||||
cw.WriteLine($" First Num: {entry.FirstNum}");
|
||||
cw.WriteLine($" Coordinates: ({entry.Position.X}, {entry.Position.Y}, {entry.Position.Z})");
|
||||
|
||||
if (entry.IsBox)
|
||||
{
|
||||
cw.WriteLine($" Box Label: {entry.BoxLabel}");
|
||||
cw.WriteLine(" PokeData:");
|
||||
var pd = eventBattle.Table.First(e => e.Label == entry.BoxLabel).PokeData;
|
||||
|
||||
cw.WriteLine($" Species: {specNamesInternal[(int)pd.DevId]}");
|
||||
cw.WriteLine($" Form: {pd.FormId}");
|
||||
cw.WriteLine($" Level: {pd.Level}");
|
||||
cw.WriteLine($" Sex: {Humanize(pd.Sex)}");
|
||||
cw.WriteLine($" Shiny: {Humanize(pd.RareType)}");
|
||||
|
||||
var talentStr = pd.TalentType switch
|
||||
{
|
||||
TalentType.RANDOM => "Random",
|
||||
TalentType.V_NUM => $"{pd.TalentVnum} Perfect",
|
||||
TalentType.VALUE => $"{pd.TalentValue.HP}/{pd.TalentValue.ATK}/{pd.TalentValue.DEF}/{pd.TalentValue.SPA}/{pd.TalentValue.SPD}/{pd.TalentValue.SPE}",
|
||||
_ => "Invalid",
|
||||
};
|
||||
cw.WriteLine($" IVs: {talentStr}");
|
||||
cw.WriteLine($" Ability: {Humanize(pd.Tokusei)}");
|
||||
switch (pd.WazaType)
|
||||
{
|
||||
case WazaType.DEFAULT:
|
||||
cw.WriteLine(" Moves: Random");
|
||||
break;
|
||||
case WazaType.MANUAL:
|
||||
cw.WriteLine($" Moves: {moveNames[(int)pd.Waza1.WazaId]}/{moveNames[(int)pd.Waza2.WazaId]}/{moveNames[(int)pd.Waza3.WazaId]}/{moveNames[(int)pd.Waza4.WazaId]}");
|
||||
break;
|
||||
}
|
||||
|
||||
cw.WriteLine($" Scale: {Humanize(pd.ScaleType, pd.ScaleValue)}");
|
||||
cw.WriteLine($" GemType: {(int)pd.GemType}");
|
||||
}
|
||||
|
||||
cw.WriteLine(" Areas:");
|
||||
foreach (var areaName in areas)
|
||||
{
|
||||
var areaInfo = scene.AreaInfos[(int)PaldeaFieldIndex.Paldea][areaName];
|
||||
var loc = areaInfo.LocationNameMain;
|
||||
(string name, int index) = placeNameMap[loc];
|
||||
cw.WriteLine($" - {areaName} - {loc} - {name} ({index})");
|
||||
}
|
||||
}
|
||||
var pathPickle = Path.Combine(path, "encounter_fixed_paldea.pkl");
|
||||
var ordered = serialized
|
||||
.OrderBy(z => BinaryPrimitives.ReadUInt16LittleEndian(z)) // Species
|
||||
.ThenBy(z => z[2]) // Form
|
||||
.ThenBy(z => z[3]) // Level
|
||||
;
|
||||
File.WriteAllBytes(pathPickle, ordered.SelectMany(z => z).ToArray());
|
||||
DumpScene(scene, path);
|
||||
DumpField(field, path);
|
||||
return;
|
||||
|
||||
// HELPERS
|
||||
LocationPointDetail[] GetPoints(PaldeaFieldIndex fieldIndex, PaldeaPointPivot type) => fieldIndex switch
|
||||
{
|
||||
PaldeaFieldIndex.Paldea => type switch
|
||||
{
|
||||
PaldeaPointPivot.Overworld => pointMain,
|
||||
PaldeaPointPivot.AreaZero => pointAtlantis,
|
||||
_ => throw new ArgumentException($"Could not handle {type}"),
|
||||
},
|
||||
PaldeaFieldIndex.Kitakami => pointSu1,
|
||||
PaldeaFieldIndex.Terarium => pointSu2,
|
||||
_ => throw new ArgumentException($"Could not handle {fieldIndex}"),
|
||||
};
|
||||
|
||||
EncountPokeDataArray GetPokeData(PaldeaFieldIndex fieldIndex) => fieldIndex switch
|
||||
{
|
||||
PaldeaFieldIndex.Paldea => pokeDataMain,
|
||||
PaldeaFieldIndex.Kitakami => pokeDataSu1,
|
||||
PaldeaFieldIndex.Terarium => pokeDataSu2,
|
||||
_ => throw new ArgumentException($"Could not handle {fieldIndex}"),
|
||||
};
|
||||
|
||||
void ProcessAreas(PaldeaFieldIndex fieldIndex)
|
||||
{
|
||||
// Overall Logic Flow:
|
||||
// 1 - At every point, spawn everything that can exist at that point.
|
||||
// 2 - At each area, absorb native points into local points.
|
||||
// 3 - At each area, absorb crossover points into crossover points.
|
||||
// 4 - Consolidate the encounters from both point lists.
|
||||
// Just compute everything (big memory!) then crunch it all down.
|
||||
|
||||
// Fill the point lists for each area, then spawn everything into those points.
|
||||
var areaNames = scene.AreaNames[(int)fieldIndex];
|
||||
var areas = scene.AreaInfos[(int)fieldIndex];
|
||||
var types = scene.PaldeaType[(int)fieldIndex];
|
||||
for (var i = areaNames.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var areaName = areaNames[i];
|
||||
|
||||
// Determine potential spawners
|
||||
if (!scene.TryGetContainsCheck(fieldIndex, areaName, out var collider))
|
||||
{
|
||||
Console.WriteLine($"No collider for {areaName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var areaInfo = areas[areaName];
|
||||
// Areas without a location name can't be reversed into location ID.
|
||||
if (!TryGetPlaceName(ref areaName, areaInfo, collider, placeNameMap, areas, scene, fieldIndex, out var placeName))
|
||||
continue;
|
||||
|
||||
// Locations that do not spawn encounters can still have crossovers bleed into them.
|
||||
// We'll have empty local encounter lists for them.
|
||||
var storage = db.Get(placeNameMap[placeName].Index, fieldIndex, areaName, areas[areaName]);
|
||||
if (areaInfo.Tag is AreaTag.NG_Encount or AreaTag.NG_All)
|
||||
continue;
|
||||
|
||||
var type = types[areaName];
|
||||
var points = GetPoints(fieldIndex, type);
|
||||
storage.LoadPoints(points, collider, areaInfo.ActualMinLevel, areaInfo.ActualMaxLevel, areaInfo.AdjustEncLv);
|
||||
storage.GetEncounters(GetPokeData(fieldIndex), scene);
|
||||
}
|
||||
|
||||
// For each area, we need to peek at the other areas to see if they have any crossover points.
|
||||
// For each of those crossover points, we need to see if they are in the current area's collider.
|
||||
// If they are, we need to add them to the current area's list of crossover points.
|
||||
foreach (var areaName in areaNames)
|
||||
{
|
||||
// Same sanity checking as above iteration.
|
||||
var areaInfo = areas[areaName];
|
||||
|
||||
// Determine potential spawners
|
||||
if (!scene.TryGetContainsCheck(fieldIndex, areaName, out var collider))
|
||||
{
|
||||
Console.WriteLine($"No collider for {areaName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = areaInfo.LocationNameMain;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
continue;
|
||||
//if (areaInfo.Tag is AreaTag.NG_Encount or AreaTag.NG_All)
|
||||
// continue;
|
||||
|
||||
var storage = db.Get(placeNameMap[name].Index, fieldIndex, areaName, areaInfo);
|
||||
if (!IsCrossoverAllowed(storage))
|
||||
continue;
|
||||
|
||||
// Here's where the fun begins. Iterate over areas inside this loop so we can look for all possible adjacent areas.
|
||||
foreach (var otherName in areaNames)
|
||||
{
|
||||
// Skip self
|
||||
if (otherName == areaName)
|
||||
continue;
|
||||
// Skip areas that don't have a location name -- subzones were the initial spawn spot if so.
|
||||
var otherAreaInfo = areas[otherName];
|
||||
var otherNameMain = otherAreaInfo.LocationNameMain;
|
||||
if (string.IsNullOrEmpty(otherNameMain))
|
||||
continue;
|
||||
|
||||
// Skip areas that don't have a collider
|
||||
if (!scene.TryGetContainsCheck(fieldIndex, otherName, out _))
|
||||
continue;
|
||||
|
||||
// Iterate over all crossover points in the other area.
|
||||
var cross = db.Get(placeNameMap[otherNameMain].Index, fieldIndex, otherName, otherAreaInfo);
|
||||
if (!IsCrossoverAllowed(cross))
|
||||
continue;
|
||||
foreach (var point in cross.Local)
|
||||
{
|
||||
// If the crossover point is close enough to the current area's collider, add it to the current area's list of crossover points.
|
||||
if (collider.ContainsPoint(point.X, point.Y, point.Z, tolX, tolY, tolZ))
|
||||
storage.Nearby.Add(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteFixedSpawn(IReadOnlyList<string> specNamesInternal, IReadOnlyList<string> moveNames, Dictionary<string, (string Name, int Index)> placeNameMap,
|
||||
StreamWriter gw, string tableKey, int i, FixedSymbolTable entry, List<AppearTuple> appearAreas)
|
||||
{
|
||||
gw.WriteLine("===");
|
||||
gw.WriteLine($"{tableKey} - {i}");
|
||||
gw.WriteLine("===");
|
||||
gw.WriteLine(" PokeData:");
|
||||
var pd = entry.Symbol;
|
||||
gw.WriteLine($" Species: {specNamesInternal[(int)pd.DevId]}");
|
||||
gw.WriteLine($" Form: {pd.FormId}");
|
||||
gw.Write($" Level: {pd.Level}");
|
||||
foreach (var adj in appearAreas.Select(a => a.Adjust).Where(lv => lv != 0).Distinct().Order())
|
||||
{
|
||||
gw.Write($", {pd.Level + adj}");
|
||||
}
|
||||
gw.WriteLine();
|
||||
gw.WriteLine($" Sex: {Humanize(pd.Sex)}");
|
||||
gw.WriteLine($" Shiny: {Humanize(pd.RareType)}");
|
||||
|
||||
var talentStr = pd.TalentType switch
|
||||
{
|
||||
TalentType.RANDOM => "Random",
|
||||
TalentType.V_NUM => $"{pd.TalentVNum} Perfect",
|
||||
TalentType.VALUE => $"{pd.TalentValue.HP}/{pd.TalentValue.ATK}/{pd.TalentValue.DEF}/{pd.TalentValue.SPA}/{pd.TalentValue.SPD}/{pd.TalentValue.SPE}",
|
||||
_ => "Invalid",
|
||||
};
|
||||
gw.WriteLine($" IVs: {talentStr}");
|
||||
gw.WriteLine($" Ability: {Humanize(pd.TokuseiIndex)}");
|
||||
switch (pd.WazaType)
|
||||
{
|
||||
case WazaType.DEFAULT:
|
||||
gw.WriteLine(" Moves: Random");
|
||||
break;
|
||||
case WazaType.MANUAL:
|
||||
gw.WriteLine($" Moves: {moveNames[(int)pd.Waza1.WazaId]}/{moveNames[(int)pd.Waza2.WazaId]}/{moveNames[(int)pd.Waza3.WazaId]}/{moveNames[(int)pd.Waza4.WazaId]}");
|
||||
break;
|
||||
}
|
||||
|
||||
gw.WriteLine($" Scale: {Humanize(pd.ScaleType, pd.ScaleValue)}");
|
||||
gw.WriteLine($" GemType: {(int)pd.GemType}");
|
||||
|
||||
gw.WriteLine($" Probability: {entry.PokeGeneration.RepopProbability}");
|
||||
gw.WriteLine($" Pattern: {entry.PokeGeneration.GenerationPattern}");
|
||||
gw.WriteLine($" Scenario: {entry.PokeGeneration.RequireScenarioId}");
|
||||
gw.WriteLine($" Action: {entry.PokeAI.ActionId}");
|
||||
gw.WriteLine($" Trigger: {entry.PokeAI.TriggerActionId}");
|
||||
|
||||
gw.WriteLine(" Areas:");
|
||||
foreach (var area in appearAreas)
|
||||
{
|
||||
var loc = area.PlaceName;
|
||||
(string name, int index) = placeNameMap[loc];
|
||||
gw.WriteLine($" - {area.PlaceName} - {loc} - {name} ({index})");
|
||||
var point = area.Point;
|
||||
gw.WriteLine($" @ ({point.X}, {point.Y}, {point.Z})");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetBleedArea(IContainsV3f collider, List<AppearTuple> appearAreas, PaldeaSceneModel scene, PaldeaFieldIndex fieldIndex, out AppearTuple bledFrom)
|
||||
{
|
||||
foreach (var a in appearAreas)
|
||||
{
|
||||
var p = a.Point;
|
||||
if (!collider.ContainsPoint(p.X, p.Y, p.Z, tolX, tolY, tolZ))
|
||||
continue;
|
||||
// Get the original area it bled from.
|
||||
bledFrom = appearAreas.First(z =>
|
||||
scene.TryGetContainsCheck(fieldIndex, z.AreaName, out var c) &&
|
||||
c.ContainsPoint(p.X, p.Y, p.Z));
|
||||
return true;
|
||||
}
|
||||
bledFrom = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetPlaceName(ref string areaName, AreaInfo areaInfo,
|
||||
IContainsV3f collider, Dictionary<string, (string Name, int Index)> placeNameMap,
|
||||
IReadOnlyDictionary<string, AreaInfo> areas,
|
||||
PaldeaSceneModel scene, PaldeaFieldIndex fieldIndex, out string placeName)
|
||||
{
|
||||
placeName = areaInfo.LocationNameMain;
|
||||
if (!string.IsNullOrEmpty(placeName))
|
||||
return true;
|
||||
|
||||
// Maybe this is a sub-area? Try to get the parent area name.
|
||||
bool IsValidParentAreaName(string aName)
|
||||
{
|
||||
if (!areas.TryGetValue(aName, out var info))
|
||||
return false;
|
||||
var n = info.LocationNameMain;
|
||||
if (string.IsNullOrEmpty(n))
|
||||
return false;
|
||||
return placeNameMap.ContainsKey(n);
|
||||
}
|
||||
|
||||
if (!scene.TryGetParentAreaName(fieldIndex, areaName, collider, IsValidParentAreaName, out var parentAreaName))
|
||||
{
|
||||
Console.WriteLine($"No parent area for {areaName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
areaName = parentAreaName;
|
||||
placeName = areas[parentAreaName].LocationNameMain;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryGetPlaceName(ref string areaName, AreaInfo areaInfo,
|
||||
PackedVec3f point, Dictionary<string, (string Name, int Index)> placeNameMap,
|
||||
IReadOnlyDictionary<string, AreaInfo> areas,
|
||||
PaldeaSceneModel scene, PaldeaFieldIndex fieldIndex, out string placeName)
|
||||
{
|
||||
placeName = areaInfo.LocationNameMain;
|
||||
if (!string.IsNullOrEmpty(placeName))
|
||||
return true;
|
||||
|
||||
// Maybe this is a sub-area? Try to get the parent area name.
|
||||
bool IsValidParentAreaName(string aName)
|
||||
{
|
||||
if (!areas.TryGetValue(aName, out var info))
|
||||
return false;
|
||||
var n = info.LocationNameMain;
|
||||
if (string.IsNullOrEmpty(n))
|
||||
return false;
|
||||
return placeNameMap.ContainsKey(n);
|
||||
}
|
||||
|
||||
if (!scene.TryGetParentAreaName(fieldIndex, areaName, point, IsValidParentAreaName, out var parentAreaName))
|
||||
{
|
||||
Console.WriteLine($"No parent area for {areaName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
areaName = parentAreaName;
|
||||
placeName = areas[parentAreaName].LocationNameMain;
|
||||
return true;
|
||||
}
|
||||
|
||||
private record struct AppearTuple(string PlaceName, string AreaName, int Adjust, PackedVec3f Point);
|
||||
|
||||
private static string Humanize(SizeType type, short value) => type switch
|
||||
{
|
||||
SizeType.RANDOM => "Random",
|
||||
SizeType.XS => "XS",
|
||||
SizeType.S => "S",
|
||||
SizeType.M => "M",
|
||||
SizeType.L => "L",
|
||||
SizeType.XL => "XL",
|
||||
SizeType.VALUE => value.ToString(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
|
||||
private static string Humanize(RareType type) => type switch
|
||||
{
|
||||
RareType.DEFAULT => "Random",
|
||||
RareType.NO_RARE => "Never",
|
||||
RareType.RARE => "Always",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
|
||||
private static string Humanize(SexType type) => type switch
|
||||
{
|
||||
SexType.DEFAULT => "Random",
|
||||
SexType.MALE => "Male",
|
||||
SexType.FEMALE => "Female",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
|
||||
private static string Humanize(TokuseiType type) => type switch
|
||||
{
|
||||
TokuseiType.RANDOM_12 => "1/2",
|
||||
TokuseiType.RANDOM_123 => "1/2/H",
|
||||
TokuseiType.SET_1 => "1",
|
||||
TokuseiType.SET_2 => "2",
|
||||
TokuseiType.SET_3 => "H",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "Invalid ability index"),
|
||||
};
|
||||
|
||||
private static void DumpScene(PaldeaSceneModel scene, string path)
|
||||
{
|
||||
// Dump each property to json.
|
||||
var dest = Path.Combine(path, "paldea_scene.json");
|
||||
var json = JsonSerializer.Serialize(scene, new JsonSerializerOptions { WriteIndented = true, IncludeFields = true });
|
||||
File.WriteAllText(dest, json);
|
||||
}
|
||||
|
||||
private static void DumpField(PaldeaFieldModel field, string path)
|
||||
{
|
||||
// Dump each property to json.
|
||||
var dest = Path.Combine(path, "paldea_field.json");
|
||||
var json = JsonSerializer.Serialize(field, new JsonSerializerOptions { WriteIndented = true, IncludeFields = true });
|
||||
File.WriteAllText(dest, json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow encounters into and out of this location
|
||||
/// </summary>
|
||||
private static bool IsCrossoverAllowed(LocationStorage storage)
|
||||
{
|
||||
var loc = storage.Location;
|
||||
return IsCrossoverAllowed(loc);
|
||||
}
|
||||
|
||||
public static bool IsCrossoverAllowed(int MetLocation) => MetLocation switch
|
||||
{
|
||||
8 => false, // Mesagoza
|
||||
124 => false, // Area Zero
|
||||
_ => true,
|
||||
};
|
||||
|
||||
private static bool GetIsStationary(PokemonActionID action) => action switch
|
||||
{
|
||||
PokemonActionID.FS_POP_ALWAYS_GAZE_BIRD_TARGET_PLAYER => true,
|
||||
PokemonActionID.FS_POP_ALWAYS_GAZE_TARGET_PLAYER => true,
|
||||
PokemonActionID.ALWAYS_GAZE_BIRD_TARGET_PLAYER_LOOP => true,
|
||||
PokemonActionID.FS_POP_LAND_SLEEPING_CURRENT_LOCATION => true,
|
||||
PokemonActionID.FS_POP_LEVITATION_SLEEPING_TREE_BRANCH => true,
|
||||
PokemonActionID.FS_POP_AREA22_DRAGONITE => true, // Handle separately.
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private static LocationPointDetail[] ReformatPoints(PointDataArray all)
|
||||
{
|
||||
var arr = all.Table;
|
||||
var result = new LocationPointDetail[arr.Count];
|
||||
for (int i = 0; i < arr.Count; i++)
|
||||
result[i] = new LocationPointDetail(arr[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void WriteFixedSymbol(ICollection<byte[]> exist, FixedSymbolTable entry, IReadOnlyList<int> locs, int adjustLevel = 0)
|
||||
{
|
||||
var enc = entry.Symbol;
|
||||
using var ms = new MemoryStream();
|
||||
using var bw = new BinaryWriter(ms);
|
||||
|
||||
ushort species = SpeciesConverterSV.GetNational9((ushort)enc.DevId);
|
||||
byte form = (byte)enc.FormId;
|
||||
if (species == (int)Species.Minior)
|
||||
{
|
||||
// Not form random -- keep original form ID.
|
||||
// Encounter Slots themselves are form random, but the fixed symbols aren't.
|
||||
form += 7; // Out of battle form ID (without the rocky shields up)
|
||||
}
|
||||
|
||||
bw.Write(species);
|
||||
bw.Write(form);
|
||||
bw.Write((byte)(enc.Level + adjustLevel));
|
||||
|
||||
bw.Write(enc.TalentType == TalentType.RANDOM ? (byte)0 : (byte)enc.TalentVNum);
|
||||
bw.Write((byte)enc.GemType);
|
||||
bw.Write((byte)(enc.Sex - 1));
|
||||
bw.Write((byte)(enc.TokuseiIndex switch
|
||||
{
|
||||
TokuseiType.RANDOM_12 => PKHeX.Core.AbilityPermission.Any12,
|
||||
TokuseiType.RANDOM_123 => PKHeX.Core.AbilityPermission.Any12H,
|
||||
TokuseiType.SET_1 => PKHeX.Core.AbilityPermission.OnlyFirst,
|
||||
TokuseiType.SET_2 => PKHeX.Core.AbilityPermission.OnlySecond,
|
||||
TokuseiType.SET_3 => PKHeX.Core.AbilityPermission.OnlyHidden,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(TokuseiType), enc.TokuseiIndex, null),
|
||||
}));
|
||||
|
||||
bw.Write((ushort)enc.Waza1.WazaId);
|
||||
bw.Write((ushort)enc.Waza2.WazaId);
|
||||
|
||||
bw.Write((ushort)enc.Waza3.WazaId);
|
||||
bw.Write((ushort)enc.Waza4.WazaId);
|
||||
|
||||
// At most 3 locations, but just use 4 bytes.
|
||||
Span<byte> temp = stackalloc byte[4];
|
||||
if (locs.Count > temp.Length)
|
||||
throw new ArgumentException("Too many locations??", nameof(locs));
|
||||
for (int i = 0; i < locs.Count; i++)
|
||||
temp[i] = (byte)locs[i];
|
||||
bw.Write(temp);
|
||||
|
||||
var result = ms.ToArray();
|
||||
if (!exist.Any(x => x.SequenceEqual(result)))
|
||||
exist.Add(result);
|
||||
}
|
||||
|
||||
private static void SerializeEncounters(string binPath, LocationDatabase db)
|
||||
{
|
||||
var tables = db.Locations.Values;
|
||||
int ctr = 0;
|
||||
var result = new byte[tables.Count + tables.Sum(z => z.SlotsCrossover.Count)][];
|
||||
foreach (var x in tables)
|
||||
{
|
||||
result[ctr++] = SerializeLocationSet((ushort)x.Location, 0, x.Slots);
|
||||
foreach (var sub in x.SlotsCrossover)
|
||||
result[ctr++] = SerializeLocationSet((ushort)x.Location, (ushort)sub.Key, sub.Value.Slots);
|
||||
}
|
||||
var mini = MiniUtil.PackMini(result, "sv");
|
||||
File.WriteAllBytes(binPath, mini);
|
||||
}
|
||||
|
||||
private static byte[] SerializeLocationSet(ushort loc, ushort crossover, IReadOnlyList<PaldeaEncounter> slots)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using var bw = new BinaryWriter(ms);
|
||||
bw.Write(loc);
|
||||
bw.Write(crossover);
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
ushort species = slot.Species;
|
||||
byte form = slot.Species switch
|
||||
{
|
||||
(ushort)Species.Vivillon or (ushort)Species.Spewpa or (ushort)Species.Scatterbug => 30,
|
||||
(ushort)Species.Minior => 31,
|
||||
_ => slot.Form,
|
||||
};
|
||||
|
||||
// ReSharper disable RedundantCast
|
||||
bw.Write(species);
|
||||
bw.Write(form);
|
||||
bw.Write((byte)slot.Gender);
|
||||
|
||||
bw.Write((byte)slot.MinLevel);
|
||||
bw.Write((byte)slot.MaxLevel);
|
||||
bw.Write((byte)slot.Time);
|
||||
bw.Write((byte)0);
|
||||
// ReSharper restore RedundantCast
|
||||
}
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
private static void WriteLocation(TextWriter tw, IReadOnlyList<string> specNamesInternal,
|
||||
IReadOnlyDictionary<string, (string Name, int Index)> placeNameMap,
|
||||
string areaName, AreaInfo areaInfo, List<PaldeaEncounter> encounts)
|
||||
{
|
||||
var loc = areaInfo.LocationNameMain;
|
||||
if (!placeNameMap.ContainsKey(loc))
|
||||
return;
|
||||
(string name, int index) = placeNameMap[loc];
|
||||
var heading = $"{areaName} - {loc} - {name} ({index})";
|
||||
WriteEncounts(tw, specNamesInternal, heading, encounts);
|
||||
}
|
||||
|
||||
private static void WriteLocEncList(TextWriter tw, IReadOnlyList<string> specNamesInternal,
|
||||
IReadOnlyDictionary<string, (string Name, int Index)> placeNameMap,
|
||||
LocationDatabase db)
|
||||
{
|
||||
foreach (var place in placeNameMap.Keys.OrderBy(p => placeNameMap[p].Index))
|
||||
{
|
||||
(string name, int index) = placeNameMap[place];
|
||||
if (!db.Locations.TryGetValue(index, out var encounts))
|
||||
continue;
|
||||
|
||||
var heading = $"{name} ({index})";
|
||||
WriteEncounts(tw, specNamesInternal, heading, encounts.Slots);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteLocPointList(TextWriter tw,
|
||||
IReadOnlyDictionary<string, (string Name, int Index)> placeNameMap,
|
||||
LocationDatabase db)
|
||||
{
|
||||
foreach (var place in placeNameMap.Keys.OrderBy(p => placeNameMap[p].Index))
|
||||
{
|
||||
(string name, int index) = placeNameMap[place];
|
||||
if (!db.Locations.TryGetValue(index, out var loc))
|
||||
continue;
|
||||
|
||||
var heading = $"{name} ({index})";
|
||||
WriteBiomes(tw, heading, loc.Local);
|
||||
}
|
||||
|
||||
foreach (var place in placeNameMap.Keys.OrderBy(p => placeNameMap[p].Index))
|
||||
{
|
||||
(string name, int index) = placeNameMap[place];
|
||||
if (!db.Locations.TryGetValue(index, out var loc))
|
||||
continue;
|
||||
|
||||
var heading = $"{name} ({index})";
|
||||
WritePoints(tw, heading, loc.Local);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteBiomes(TextWriter tw, string heading, List<LocationPointDetail> points)
|
||||
{
|
||||
var biomes = points.Select(z => z.Point.Biome).Distinct().Select(z => z.ToString()).Order();
|
||||
var btext = string.Join(',', biomes);
|
||||
tw.WriteLine($"{heading}\t{btext}");
|
||||
}
|
||||
|
||||
private static void WritePoints(TextWriter tw, string heading, List<LocationPointDetail> points)
|
||||
{
|
||||
tw.WriteLine("===");
|
||||
tw.WriteLine(heading);
|
||||
tw.WriteLine("===");
|
||||
foreach (var e in points)
|
||||
tw.WriteLine($" - {e.GetString()}");
|
||||
tw.WriteLine();
|
||||
}
|
||||
|
||||
private static void WriteEncounts(TextWriter tw, IReadOnlyList<string> specNamesInternal, string heading, IEnumerable<PaldeaEncounter> encounts)
|
||||
{
|
||||
tw.WriteLine("===");
|
||||
tw.WriteLine(heading);
|
||||
tw.WriteLine("===");
|
||||
foreach (var e in encounts)
|
||||
tw.WriteLine($" - {e.GetEncountString(specNamesInternal)}");
|
||||
tw.WriteLine();
|
||||
}
|
||||
|
||||
public static Dictionary<string, (string Name, int Index)> GetPlaceNameMap(string[] text, AHTB ahtb)
|
||||
{
|
||||
var result = new Dictionary<string, (string Name, int Index)>();
|
||||
for (var i = 0; i < text.Length; i++)
|
||||
result[ahtb.Entries[i].Name] = (text[i], i);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool IsAvoidantAction(PokemonActionID action) => action switch
|
||||
{
|
||||
PokemonActionID.FS_POP_PATH_RUN_SPEED_UP_1_NOT_COL_DELAY => true, // lighthouse Wingull (South) and Wattrel (East, West)
|
||||
PokemonActionID.FS_POP_PATH_RUN_SPEED_UP_1_NOT_COL_DESTROY => true, // fence Wingull (Cabo Poco)
|
||||
PokemonActionID.FS_POP_COMMON_FLIGHT_NOT_COL_DESTROY => true, // starting Fletchling (level 2)
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using pkNX.Containers;
|
||||
using pkNX.Structures.FlatBuffers;
|
||||
using pkNX.Structures.FlatBuffers.SV.Trinity;
|
||||
|
||||
namespace pkNX.WinForms;
|
||||
namespace pkNX.Structures.FlatBuffers;
|
||||
|
||||
public class PaldeaCoinSymbolModel
|
||||
{
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
using pkNX.Structures.FlatBuffers;
|
||||
|
||||
namespace pkNX.WinForms;
|
||||
namespace pkNX.Structures.FlatBuffers;
|
||||
|
||||
public class PaldeaCoinSymbolPoint(string name, ulong num, string boxLabel, PackedVec3f pos)
|
||||
{
|
||||
157
pkNX.WinForms/Dumping/SV/Models/PaldeaFixedSymbolModel.cs
Normal file
157
pkNX.WinForms/Dumping/SV/Models/PaldeaFixedSymbolModel.cs
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
using pkNX.Containers;
|
||||
using pkNX.Structures.FlatBuffers.SV;
|
||||
using pkNX.Structures.FlatBuffers.SV.Trinity;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlatSharp;
|
||||
|
||||
namespace pkNX.Structures.FlatBuffers;
|
||||
|
||||
public class PaldeaFixedSymbolModel
|
||||
{
|
||||
public readonly List<PaldeaFixedSymbolPoint>[] PointsScarlet;
|
||||
public readonly List<PaldeaFixedSymbolPoint>[] PointsViolet;
|
||||
|
||||
public readonly Dictionary<string, string[]> MultiSpawner;
|
||||
|
||||
private static T Get<T>(Memory<byte> data) where T : class, IFlatBufferSerializable<T>
|
||||
=> FlatBufferConverter.DeserializeFrom<T>(data);
|
||||
|
||||
public PaldeaFixedSymbolModel(IFileInternal rom)
|
||||
{
|
||||
var raw = rom.GetPackedFile("world/data/field/fixed_symbol/gem_symbol_lottery_table/gem_symbol_lottery_table_array.bin");
|
||||
var gemLottery = Get<GemSymbolLotteryTableArray>(raw).Table;
|
||||
MultiSpawner = gemLottery.ToDictionary(z => z.LotteryKey, GetTableGetKeys);
|
||||
|
||||
PointsScarlet = GetSymbols(rom, 0);
|
||||
PointsViolet = GetSymbols(rom, 1);
|
||||
}
|
||||
|
||||
private List<PaldeaFixedSymbolPoint>[] GetSymbols(IFileInternal rom, int game) =>
|
||||
[
|
||||
[..GetSymbols(rom, game, "world")],
|
||||
[..GetSymbols(rom, game, "su1_world")],
|
||||
[..GetSymbols(rom, game, "su2_world")],
|
||||
];
|
||||
|
||||
private IEnumerable<PaldeaFixedSymbolPoint> GetSymbols(IFileInternal rom, int game, string mapName)
|
||||
{
|
||||
var data = rom.GetPackedFile($"world/scene/parts/field/streaming_event/{mapName}_fixed_placement_symbol_/{mapName}_fixed_placement_symbol_{game}.trscn");
|
||||
var template = Get<TrinitySceneObjectTemplate>(data);
|
||||
return GetObjectTemplateSymbolPoints(template);
|
||||
}
|
||||
|
||||
private static string[] GetTableGetKeys(GemSymbolLotteryTable entry)
|
||||
{
|
||||
var list = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey0)) list.Add(entry.TableKey0);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey1)) list.Add(entry.TableKey1);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey2)) list.Add(entry.TableKey2);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey3)) list.Add(entry.TableKey3);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey4)) list.Add(entry.TableKey4);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey5)) list.Add(entry.TableKey5);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey6)) list.Add(entry.TableKey6);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey7)) list.Add(entry.TableKey7);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey8)) list.Add(entry.TableKey8);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey9)) list.Add(entry.TableKey9);
|
||||
return [.. list];
|
||||
}
|
||||
|
||||
private IEnumerable<PaldeaFixedSymbolPoint> GetObjectTemplateSymbolPoints(TrinitySceneObjectTemplate template)
|
||||
{
|
||||
foreach (var obj in template.Objects)
|
||||
{
|
||||
switch (obj.Type)
|
||||
{
|
||||
case "trinity_ObjectTemplate":
|
||||
{
|
||||
var sObj = Get<TrinitySceneObjectTemplateData>(obj.Data);
|
||||
if (sObj.Type != "trinity_ScenePoint")
|
||||
break;
|
||||
var scenePoint = Get<TrinityScenePoint>(sObj.Data);
|
||||
|
||||
foreach (var f in GetScenePointSymbolPoints(scenePoint, obj.SubObjects))
|
||||
yield return f;
|
||||
break;
|
||||
}
|
||||
case "trinity_ScenePoint":
|
||||
{
|
||||
var scenePoint = Get<TrinityScenePoint>(obj.Data);
|
||||
|
||||
foreach (var f in GetScenePointSymbolPoints(scenePoint, obj.SubObjects))
|
||||
yield return f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<PaldeaFixedSymbolPoint> GetScenePointSymbolPoints(TrinityScenePoint scenePoint, IList<TrinitySceneObjectTemplateEntry> subObjects)
|
||||
{
|
||||
// Handle SubObjects
|
||||
foreach (var obj in subObjects)
|
||||
{
|
||||
switch (obj.Type)
|
||||
{
|
||||
case "trinity_PropertySheet":
|
||||
{
|
||||
var propSheet = Get<TrinityPropertySheet>(obj.Data);
|
||||
if (propSheet.Name != "fixed_symbol_point")
|
||||
break;
|
||||
|
||||
var tableKey = GetTableKey(propSheet);
|
||||
if (string.IsNullOrEmpty(tableKey))
|
||||
break;
|
||||
|
||||
var position = scenePoint.Position;
|
||||
if (MultiSpawner.TryGetValue(tableKey, out var others))
|
||||
{
|
||||
// Can spawn multiple fixed encounters.
|
||||
foreach (var other in others)
|
||||
yield return new PaldeaFixedSymbolPoint(other, position);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new PaldeaFixedSymbolPoint(tableKey, position);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "trinity_ScenePoint":
|
||||
{
|
||||
var subScenePoint = Get<TrinityScenePoint>(obj.Data);
|
||||
foreach (var f in GetScenePointSymbolPoints(subScenePoint, obj.SubObjects))
|
||||
yield return f;
|
||||
break;
|
||||
}
|
||||
case "trinity_ObjectTemplate":
|
||||
{
|
||||
var ssObj = Get<TrinitySceneObjectTemplateData>(obj.Data);
|
||||
if (ssObj.Type != "trinity_ScenePoint")
|
||||
break;
|
||||
var subScenePoint = Get<TrinityScenePoint>(ssObj.Data);
|
||||
|
||||
foreach (var f in GetScenePointSymbolPoints(subScenePoint, obj.SubObjects))
|
||||
yield return f;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentException($"Unknown SubObject {obj.Type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetTableKey(TrinityPropertySheet propSheet)
|
||||
{
|
||||
if (propSheet.Name != "fixed_symbol_point")
|
||||
throw new ArgumentException($"Invalid PropertySheet {propSheet.Name}");
|
||||
|
||||
if (propSheet.Properties[0].Fields[1].Name != "tableKey")
|
||||
throw new ArgumentException("Invalid PropertySheet field layout");
|
||||
|
||||
if (propSheet.Properties[0].Fields[1].Data.Item3 is not { } sv)
|
||||
throw new ArgumentException("Could not get PropertySheet Table Key");
|
||||
|
||||
return sv.Value;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
using pkNX.Structures.FlatBuffers;
|
||||
|
||||
namespace pkNX.WinForms;
|
||||
namespace pkNX.Structures.FlatBuffers;
|
||||
|
||||
public class PaldeaFixedSymbolPoint(string key, PackedVec3f pos)
|
||||
{
|
||||
66
pkNX.WinForms/Dumping/SV/Models/PaldeaSpawnModel.cs
Normal file
66
pkNX.WinForms/Dumping/SV/Models/PaldeaSpawnModel.cs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using pkNX.Containers;
|
||||
using pkNX.Structures.FlatBuffers.SV;
|
||||
|
||||
namespace pkNX.Structures.FlatBuffers;
|
||||
|
||||
public record PaldeaSpawnModel
|
||||
{
|
||||
public PaldeaSpawnSet Main { get; init; }
|
||||
public PaldeaSpawnSet Atlantis { get; init; }
|
||||
public PaldeaSpawnSet Kitakami { get; init; }
|
||||
public PaldeaSpawnSet Terarium { get; init; }
|
||||
|
||||
public PaldeaSpawnModel(IFileInternal rom)
|
||||
{
|
||||
|
||||
var mlEncPoints = FlatBufferConverter.DeserializeFrom<PointDataArray>(rom.GetPackedFile("world/data/encount/point_data/point_data/encount_data_100000.bin"));
|
||||
var alEncPoints = FlatBufferConverter.DeserializeFrom<PointDataArray>(rom.GetPackedFile("world/data/encount/point_data/point_data/encount_data_atlantis.bin"));
|
||||
var su1EncPoints = FlatBufferConverter.DeserializeFrom<PointDataArray>(rom.GetPackedFile("world/data/encount/point_data/point_data/encount_data_su1.bin"));
|
||||
var su2EncPoints = FlatBufferConverter.DeserializeFrom<PointDataArray>(rom.GetPackedFile("world/data/encount/point_data/point_data/encount_data_su2.bin"));
|
||||
//var lcEncPoints = FlatBufferConverter.DeserializeFrom<PointDataArray>(ROM.GetPackedFile("world/data/encount/point_data/point_data/encount_data_lc.bin"));
|
||||
var pokeDataMain = FlatBufferConverter.DeserializeFrom<EncountPokeDataArray>(rom.GetPackedFile("world/data/encount/pokedata/pokedata/pokedata_array.bin"));
|
||||
var pokeDataSu1 = FlatBufferConverter.DeserializeFrom<EncountPokeDataArray>(rom.GetPackedFile("world/data/encount/pokedata/pokedata_su1/pokedata_su1_array.bin"));
|
||||
var pokeDataSu2 = FlatBufferConverter.DeserializeFrom<EncountPokeDataArray>(rom.GetPackedFile("world/data/encount/pokedata/pokedata_su2/pokedata_su2_array.bin"));
|
||||
//var pokeDataLc = FlatBufferConverter.DeserializeFrom<EncountPokeDataArray>(ROM.GetPackedFile("world/data/encount/pokedata/pokedata_lc/pokedata_lc_array.bfbs"));
|
||||
|
||||
Main = new PaldeaSpawnSet(pokeDataMain, mlEncPoints);
|
||||
Atlantis = new PaldeaSpawnSet(pokeDataMain, alEncPoints);
|
||||
Kitakami = new PaldeaSpawnSet(pokeDataSu1, su1EncPoints);
|
||||
Terarium = new PaldeaSpawnSet(pokeDataSu2, su2EncPoints);
|
||||
//var lc = ReformatPoints(lcEncPoints);
|
||||
}
|
||||
|
||||
public PaldeaSpawnSet GetSet(PaldeaFieldIndex fieldIndex, PaldeaPointPivot type) => fieldIndex switch
|
||||
{
|
||||
PaldeaFieldIndex.Paldea => type switch
|
||||
{
|
||||
PaldeaPointPivot.Overworld => Main,
|
||||
PaldeaPointPivot.AreaZero => Atlantis,
|
||||
_ => throw new ArgumentException($"Could not handle {type}"),
|
||||
},
|
||||
PaldeaFieldIndex.Kitakami => Kitakami,
|
||||
PaldeaFieldIndex.Terarium => Terarium,
|
||||
_ => throw new ArgumentException($"Could not handle {fieldIndex}"),
|
||||
};
|
||||
|
||||
public LocationPointDetail[] GetPoints(PaldeaFieldIndex fieldIndex, PaldeaPointPivot type) => GetSet(fieldIndex, type).Points;
|
||||
public EncountPokeDataArray GetPokeData(PaldeaFieldIndex fieldIndex) => GetSet(fieldIndex, PaldeaPointPivot.Overworld).Criteria;
|
||||
|
||||
public record PaldeaSpawnSet(EncountPokeDataArray Criteria, LocationPointDetail[] Points)
|
||||
{
|
||||
public PaldeaSpawnSet(EncountPokeDataArray criteria, PointDataArray points) : this(criteria, ReformatPoints(points))
|
||||
{
|
||||
}
|
||||
|
||||
// Points can be used by multiple areas as crossover sources. Need to be able to "belong" to multiple areas, and indicate their parent area.
|
||||
private static LocationPointDetail[] ReformatPoints(PointDataArray all)
|
||||
{
|
||||
var arr = all.Table;
|
||||
var result = new LocationPointDetail[arr.Count];
|
||||
for (int i = 0; i < arr.Count; i++)
|
||||
result[i] = new LocationPointDetail(arr[i]);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using pkNX.Containers;
|
||||
using pkNX.Structures.FlatBuffers;
|
||||
using pkNX.Structures.FlatBuffers.SV;
|
||||
using pkNX.Structures.FlatBuffers.SV.Trinity;
|
||||
|
||||
namespace pkNX.WinForms;
|
||||
|
||||
public class PaldeaFixedSymbolModel
|
||||
{
|
||||
public readonly List<PaldeaFixedSymbolPoint>[] scarletPoints = [[], [], []];
|
||||
public readonly List<PaldeaFixedSymbolPoint>[] violetPoints = [[], [], []];
|
||||
|
||||
public readonly Dictionary<string, string[]> MultiSpawner;
|
||||
|
||||
public PaldeaFixedSymbolModel(IFileInternal ROM)
|
||||
{
|
||||
var gemLottery = FlatBufferConverter.DeserializeFrom<GemSymbolLotteryTableArray>(
|
||||
ROM.GetPackedFile("world/data/field/fixed_symbol/gem_symbol_lottery_table/gem_symbol_lottery_table_array.bin")).Table;
|
||||
MultiSpawner = gemLottery.ToDictionary(z => z.LotteryKey, GetTableGetKeys);
|
||||
|
||||
// Paldea
|
||||
var p0Data = ROM.GetPackedFile("world/scene/parts/field/streaming_event/world_fixed_placement_symbol_/world_fixed_placement_symbol_0.trscn");
|
||||
var p1Data = ROM.GetPackedFile("world/scene/parts/field/streaming_event/world_fixed_placement_symbol_/world_fixed_placement_symbol_1.trscn");
|
||||
|
||||
var p0 = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(p0Data);
|
||||
var p1 = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(p1Data);
|
||||
|
||||
scarletPoints[(int)PaldeaFieldIndex.Paldea].AddRange(GetObjectTemplateSymbolPoints(p0));
|
||||
violetPoints[(int)PaldeaFieldIndex.Paldea].AddRange(GetObjectTemplateSymbolPoints(p1));
|
||||
|
||||
// Kitakami
|
||||
var k0Data = ROM.GetPackedFile("world/scene/parts/field/streaming_event/su1_world_fixed_placement_symbol_/su1_world_fixed_placement_symbol_0.trscn");
|
||||
var k1Data = ROM.GetPackedFile("world/scene/parts/field/streaming_event/su1_world_fixed_placement_symbol_/su1_world_fixed_placement_symbol_1.trscn");
|
||||
|
||||
var k0 = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(k0Data);
|
||||
var k1 = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(k1Data);
|
||||
|
||||
scarletPoints[(int)PaldeaFieldIndex.Kitakami].AddRange(GetObjectTemplateSymbolPoints(k0));
|
||||
violetPoints[(int)PaldeaFieldIndex.Kitakami].AddRange(GetObjectTemplateSymbolPoints(k1));
|
||||
|
||||
// Terarium
|
||||
var b0Data = ROM.GetPackedFile("world/scene/parts/field/streaming_event/su2_world_fixed_placement_symbol_/su2_world_fixed_placement_symbol_0.trscn");
|
||||
var b1Data = ROM.GetPackedFile("world/scene/parts/field/streaming_event/su2_world_fixed_placement_symbol_/su2_world_fixed_placement_symbol_1.trscn");
|
||||
|
||||
var b0 = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(b0Data);
|
||||
var b1 = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(b1Data);
|
||||
|
||||
scarletPoints[(int)PaldeaFieldIndex.Terarium].AddRange(GetObjectTemplateSymbolPoints(b0));
|
||||
violetPoints[(int)PaldeaFieldIndex.Terarium].AddRange(GetObjectTemplateSymbolPoints(b1));
|
||||
|
||||
// Underdepths
|
||||
//var u0Data = ROM.GetPackedFile("world/scene/parts/field/room/a_w23_d10/a_w23_d10_event/a_w23_d10_fixed_placement_symbol_/a_w23_d10_fixed_placement_symbol_0.trscn");
|
||||
//var u1Data = ROM.GetPackedFile("world/scene/parts/field/room/a_w23_d10/a_w23_d10_event/a_w23_d10_fixed_placement_symbol_/a_w23_d10_fixed_placement_symbol_1.trscn");
|
||||
//var u0 = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(u0Data);
|
||||
//var u1 = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(u1Data);
|
||||
//
|
||||
//scarletPoints[(int)PaldeaFieldIndex.Paldea].AddRange(GetObjectTemplateSymbolPoints(u0));
|
||||
//violetPoints[(int)PaldeaFieldIndex.Paldea].AddRange(GetObjectTemplateSymbolPoints(u1));
|
||||
}
|
||||
|
||||
private static string[] GetTableGetKeys(GemSymbolLotteryTable entry)
|
||||
{
|
||||
var list = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey0)) list.Add(entry.TableKey0);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey1)) list.Add(entry.TableKey1);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey2)) list.Add(entry.TableKey2);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey3)) list.Add(entry.TableKey3);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey4)) list.Add(entry.TableKey4);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey5)) list.Add(entry.TableKey5);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey6)) list.Add(entry.TableKey6);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey7)) list.Add(entry.TableKey7);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey8)) list.Add(entry.TableKey8);
|
||||
if (!string.IsNullOrWhiteSpace(entry.TableKey9)) list.Add(entry.TableKey9);
|
||||
return [.. list];
|
||||
}
|
||||
|
||||
private IEnumerable<PaldeaFixedSymbolPoint> GetObjectTemplateSymbolPoints(TrinitySceneObjectTemplate template)
|
||||
{
|
||||
foreach (var obj in template.Objects)
|
||||
{
|
||||
switch (obj.Type)
|
||||
{
|
||||
case "trinity_ObjectTemplate":
|
||||
{
|
||||
var sObj = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplateData>(obj.Data);
|
||||
if (sObj.Type != "trinity_ScenePoint")
|
||||
continue;
|
||||
var scenePoint = FlatBufferConverter.DeserializeFrom<TrinityScenePoint>(sObj.Data);
|
||||
|
||||
foreach (var f in GetScenePointSymbolPoints(scenePoint, obj.SubObjects))
|
||||
yield return f;
|
||||
break;
|
||||
}
|
||||
case "trinity_ScenePoint":
|
||||
{
|
||||
var scenePoint = FlatBufferConverter.DeserializeFrom<TrinityScenePoint>(obj.Data);
|
||||
|
||||
foreach (var f in GetScenePointSymbolPoints(scenePoint, obj.SubObjects))
|
||||
yield return f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<PaldeaFixedSymbolPoint> GetScenePointSymbolPoints(TrinityScenePoint scenePoint, IList<TrinitySceneObjectTemplateEntry> subObjects)
|
||||
{
|
||||
// Handle SubObjects
|
||||
for (var i = 0; i < subObjects.Count; i++)
|
||||
{
|
||||
var sobj = subObjects[i];
|
||||
switch (sobj.Type)
|
||||
{
|
||||
case "trinity_PropertySheet":
|
||||
{
|
||||
var propSheet = FlatBufferConverter.DeserializeFrom<TrinityPropertySheet>(sobj.Data);
|
||||
if (propSheet.Name == "fixed_symbol_point")
|
||||
{
|
||||
var tableKey = GetTableKey(propSheet);
|
||||
if (!string.IsNullOrEmpty(tableKey))
|
||||
{
|
||||
if (MultiSpawner.TryGetValue(tableKey, out var others))
|
||||
{
|
||||
// Can spawn multiple fixed encounters.
|
||||
foreach (var other in others)
|
||||
yield return new PaldeaFixedSymbolPoint(other, scenePoint.Position);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new PaldeaFixedSymbolPoint(tableKey, scenePoint.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "trinity_ScenePoint":
|
||||
{
|
||||
var subScenePoint = FlatBufferConverter.DeserializeFrom<TrinityScenePoint>(sobj.Data);
|
||||
foreach (var f in GetScenePointSymbolPoints(subScenePoint, sobj.SubObjects))
|
||||
yield return f;
|
||||
break;
|
||||
}
|
||||
case "trinity_ObjectTemplate":
|
||||
{
|
||||
var ssObj = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplateData>(sobj.Data);
|
||||
if (ssObj.Type != "trinity_ScenePoint")
|
||||
continue;
|
||||
var subScenePoint = FlatBufferConverter.DeserializeFrom<TrinityScenePoint>(ssObj.Data);
|
||||
|
||||
foreach (var f in GetScenePointSymbolPoints(subScenePoint, sobj.SubObjects))
|
||||
yield return f;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentException($"Unknown SubObject {sobj.Type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetTableKey(TrinityPropertySheet propSheet)
|
||||
{
|
||||
if (propSheet.Name != "fixed_symbol_point")
|
||||
throw new ArgumentException($"Invalid PropertySheet {propSheet.Name}");
|
||||
|
||||
if (propSheet.Properties[0].Fields[1].Name != "tableKey")
|
||||
throw new ArgumentException("Invalid PropertySheet field layout");
|
||||
|
||||
if (propSheet.Properties[0].Fields[1].Data.Item3 is not { } sv)
|
||||
throw new ArgumentException("Could not get PropertySheet Table Key");
|
||||
|
||||
return sv.Value;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user