Add changes for ZA DLC (Mega Dimension) (#407)

This commit is contained in:
Kurt
2025-12-19 15:13:38 -06:00
committed by GitHub
parent 579c33208c
commit 5bd7003a60
41 changed files with 1182 additions and 72 deletions

View File

@@ -0,0 +1,13 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table ItemTable {
ItemID:int;
PlibID:int;
}
table ItemTableArray (fs_serializer) {
Table:[ItemTable] (required);
}
root_type ItemTableArray;

View File

@@ -0,0 +1,18 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table ConditionRareDB {
Table:[ConditionRare] (required);
}
table ConditionRareDBArray (fs_serializer) {
Table:[ConditionRareDB] (required);
}
table ConditionRare {
Version:float;
TriggerID:int;
ComparisonOperatorType:int;
}
root_type ConditionRareDBArray;

View File

@@ -0,0 +1,22 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table BerryDetail {
ItemID:uint;
DonutIndex:uint;
IsHyperspace:bool;
Spicy:uint;
Fresh:uint;
Sweet:uint;
Bitter:uint;
Sour:uint;
LevelBoost:uint;
Calories:uint;
}
table BerryDetailArray (fs_serializer) {
Table:[BerryDetail] (required);
}
root_type BerryDetailArray;

View File

@@ -0,0 +1,17 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DonutDetail {
Index:uint;
FlavorDominant:uint;
BerryBase:uint;
Name:string (required);
Info:string (required);
}
table DonutDetailArray (fs_serializer) {
Table:[DonutDetail] (required);
}
root_type DonutDetailArray;

View File

@@ -0,0 +1,26 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DonutFlavor {
Flavor:string (required); // power granted
FlavorName:int;
UniqueEffect:int;
BoostArg:int; // type to boost, or which variant of the flavor
Field04:int;
Category2:int; // does this prevent multiple in same category?
IsBattleStatBoost:bool;
Level:int;
LevelVariance:int; // mostly matches level, besides shiny power (3) and legends (1)
IsSpecial:bool;
Name:string (required);
Info:string (required);
PrefixAdj:string (required); // based on the primary power
Detail:string (required);
}
table DonutFlavorArray (fs_serializer) {
Table:[DonutFlavor] (required);
}
root_type DonutFlavorArray;

View File

@@ -0,0 +1,16 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DonutFlavorLottery {
Count:uint;
Lv1:uint;
Lv2:uint;
Lv3:uint;
}
table DonutFlavorLotteryArray (fs_serializer) {
Table:[DonutFlavorLottery] (required);
}
root_type DonutFlavorLotteryArray;

View File

@@ -0,0 +1,16 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DonutFlavorParameter {
Index:uint;
Lv1:float;
Lv2:float;
Lv3:float;
}
table DonutFlavorParameterArray (fs_serializer) {
Table:[DonutFlavorParameter] (required);
}
root_type DonutFlavorParameterArray;

View File

@@ -0,0 +1,20 @@
include "Shared/ActivationCondition.fbs";
include "Shared/ActivationConditionElement.fbs";
include "Shared/ActivationConditionParam.fbs";
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DonutOneWord {
Text:string (required);
State:uint; // start=0, working=1, max=2, leave=3
Other:uint; // only used by oneword_max_*
Condition:[ActivationCondition] (required);
}
table DonutOneWordArray (fs_serializer) {
Table:[DonutOneWord] (required);
}
root_type DonutOneWordArray;

View File

@@ -0,0 +1,26 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
// special donut recipe
table DonutRecipe {
Index:uint;
SweetMin:uint;
SweetMax:uint;
SpicyMin:uint;
SpicyMax:uint;
SourMin:uint;
SourMax:uint;
BitterMin:uint;
BitterMax:uint;
FreshMin:uint;
FreshMax:uint;
DonutName:uint;
RecipeName:string (required);
}
table DonutRecipeArray (fs_serializer) {
Table:[DonutRecipe] (required);
}
root_type DonutRecipeArray;

View File

@@ -1,5 +1 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<Content Remove="Schemas\WeatherHappeningParamArray.fbs" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk" />

View File

@@ -54,6 +54,8 @@ public void AddDump(List<string> lines, PersonalTable9ZA table, ushort speciesIn
if (form != 0)
name += $"-{form}";
name += $" #{entry.DexIndex:000}";
// if (entry.DexIndex != 0) // every group is 0, don't bother emitting. The game does -232 to check >=1 for Hyperspace Dex.
// name += $" [{entry.DexGroup}]";
AddDump(lines, entry, index, name, speciesInternal, form);
}
@@ -366,6 +368,20 @@ public static class Plib9
{ 0094, 2403 }, // Unremarkable Teacup
{ 0095, 2404 }, // Masterpiece Teacup
{ 0096, 2402 }, // Syrupy Apple
{ 0097, 0000 },
{ 0098, 0000 },
{ 0099, 0000 },
{ 0100, 0000 },
{ 0101, 0000 },
{ 0102, 0765 }, // Prison Bottle
{ 0103, 0000 },
{ 0104, 0000 },
{ 0105, 0000 },
{ 0106, 0000 },
{ 0107, 0000 },
{ 0108, 0000 },
{ 0109, 0000 },
{ 0110, 0000 },
{ 0111, 0537 }, // Prism Scale
{ 0112, 0325 }, // Reaper Cloth
{ 0113, 0252 }, // Upgrade
@@ -375,5 +391,8 @@ public static class Plib9
{ 0117, 0321 }, // Protector
{ 0118, 0235 }, // Dragon Scale
{ 0119, 2482 }, // Metal Alloy
{ 0121, 0847 }, // Zygarde Cube
{ 0120, 0000 },
{ 1691, 1691 }, // Black Augurite -- yeah, they gave up having unique indexes
};
}

View File

@@ -53,10 +53,27 @@ public int Gender
public ushort Form { get => FB.Info.Form; set => FB.Info.Form = value; }
public ushort DexIndex
public int DexGroup
{
get => FB.Dex;
set => FB.Dex = value;
get => FB.Dex?.Group ?? 0;
set
{
if (value == 0)
FB.Dex = null;
else
(FB.Dex ??= new PersonalInfoDex()).Group = (byte)value;
}
}
public int DexIndex
{
get => FB.Dex?.Index ?? 0;
set
{
if (value == 0)
FB.Dex = null;
else
(FB.Dex ??= new PersonalInfoDex()).Index = (byte)value;
}
}
public int Item1 { get => 0; set { } }
@@ -98,7 +115,7 @@ public void Write(BinaryWriter bw)
bw.Write(FB.Info.Color);
bw.Write(FB.IsPresentInGame);
bw.Write((byte)0);
bw.Write((byte)DexGroup);
bw.Write((ushort)DexIndex);
bw.Write(FB.Info.Height);
bw.Write(FB.Info.Weight);
@@ -144,5 +161,12 @@ private static ushort GetEXP(int bst, byte evoStage, short add)
404, 369, 417, 430, 164, 528, 231, 191, 390, 399,
174, 605, 200, 018, 269, 056, 377, 127, 118, 441,
527, 411, 526, 394, 059, 087, 370,
// ZA DLC
004, 263, 886,
047, 491, 490, 488, 885, 006, 318, 325, 466, 246,
259, 206, 305, 706, 102, 443, 138, 402, 509, 451,
409, 458, 299, 814, 530, 815, 480, 524, 207, 330,
252, 660, 799, 813, 013, 130, 161, 503, 333, 410,
080, 669, 143, 090, 329, 800, 796, 307, 308, 338,
];
}

View File

@@ -11,10 +11,8 @@ attribute "fs_serializer";
table PersonalInfo {
Info:PersonalInfoDetail (required);
IsPresentInGame:bool;
Dex:ushort; // No need to group. Changed from SV.
// Dex:PersonalInfoDex; // not required
// KitakamiDex:ushort; // NOT USED IN DATA
// BlueberryDex:ushort; // NOT USED IN DATA
// Dex:ushort; // base game was just a ushort; extended to 2+1 struct in Mega Dimension DLC
Dex:PersonalInfoDex; // not required
Type1:ubyte;
Type2:ubyte;
Ability1:ushort;
@@ -40,6 +38,11 @@ table PersonalInfo {
Learnset:[PersonalInfoMove] (required);
}
struct PersonalInfoDex { // give me a class, not struct.
Index:ushort;
Group:ubyte;
}
table PersonalTable (fs_serializer) {
Table:[PersonalInfo] (required);
}

View File

@@ -31,4 +31,5 @@ table PokeDataBattle {
EffortValue:ParamSet (required);
RareType:RareType;
ScaleValue:short;
IsOybn:bool;
}

View File

@@ -68,6 +68,10 @@ enum ItemID : int
ITEMID_HIKARINOISI = 107,
ITEMID_YAMINOISI = 108,
ITEMID_MEZAMEISI = 109,
ITEMID_AKUAKASETTO = 116,
ITEMID_INAZUMAKASETTO = 117,
ITEMID_BUREIZUKASETTO = 118,
ITEMID_HURIIZUKASETTO = 119,
ITEMID_KURABONOMI = 149,
ITEMID_KAGONOMI = 150,
ITEMID_MOMONNOMI = 151,
@@ -145,7 +149,10 @@ enum ItemID : int
ITEMID_MOKUTAN = 249,
ITEMID_RYUUNOKIBA = 250,
ITEMID_SIRUKUNOSUKAAHU = 251,
ITEMID_APPUGUREEDO = 252,
ITEMID_KAIGARANOSUZU = 253,
ITEMID_HUTOIHONE = 258,
ITEMID_NAGANEGI = 259,
ITEMID_KOUKAKURENZU = 265,
ITEMID_TIKARANOHATIMAKI = 266,
ITEMID_MONOSIRIMEGANE = 267,
@@ -174,6 +181,7 @@ enum ItemID : int
ITEMID_KIREINANUKEGARA = 295,
ITEMID_OOKINANEKKO = 296,
ITEMID_KODAWARIMEGANE = 297,
ITEMID_AYASIIPATTI = 324,
ITEMID_WAZAMASIN01 = 328,
ITEMID_WAZAMASIN02 = 329,
ITEMID_WAZAMASIN03 = 330,
@@ -274,6 +282,9 @@ enum ItemID : int
ITEMID_HURENDOBOORU = 497,
ITEMID_MUUNBOORU = 498,
ITEMID_KONPEBOORU = 499,
ITEMID_BENIIRONOTAMA = 534,
ITEMID_AIIRONOTAMA = 535,
ITEMID_KIREINAUROKO = 537,
ITEMID_SINKANOKISEKI = 538,
ITEMID_KARUISI = 539,
ITEMID_GOTUGOTUMETTO = 540,
@@ -408,16 +419,20 @@ enum ItemID : int
ITEMID_YOUKIMINTO = 1249,
ITEMID_MUJYAKIMINTO = 1250,
ITEMID_MAZIMEMINTO = 1251,
ITEMID_ROTOMUNOKATAROGU = 1278,
ITEMID_GARANATUBURESU = 1582,
ITEMID_AKASINOOMAMORI = 1589,
ITEMID_GARANATURIISU = 1592,
ITEMID_TOKUSEIPATTI = 1606,
ITEMID_KURONOKISEKI = 1691,
ITEMID_NOROINOYOROI = 1861,
ITEMID_SENTOUBAFFA1 = 1881,
ITEMID_SENTOUBAFFA2 = 1882,
ITEMID_SENTOUBAFFA3 = 1883,
ITEMID_SENTOUBAFFA4 = 1884,
ITEMID_SENTOUBAFFA5 = 1885,
ITEMID_SENTOUBAFFA6 = 1886,
ITEMID_SOZAI182 = 2137,
ITEMID_WAZAMASIN100 = 2160,
ITEMID_WAZAMASIN101 = 2161,
ITEMID_WAZAMASIN102 = 2162,
@@ -490,6 +505,7 @@ enum ItemID : int
ITEMID_WAZAMASIN169 = 2229,
ITEMID_WAZAMASIN170 = 2230,
ITEMID_WAZAMASIN171 = 2231,
ITEMID_IWAINOYOROI = 2344,
ITEMID_YOUSEINOHANE = 2401,
ITEMID_KAIDENNOTANE = 2558,
ITEMID_PIKUSINAITO = 2559,
@@ -534,19 +550,19 @@ enum ItemID : int
ITEMID_OISHIIGOMI = 2598,
ITEMID_GENKINOKOEDA = 2599,
ITEMID_DEURONOWASUREMONO = 2600,
ITEMID_DAIZINAMONO14 = 2601,
ITEMID_DAIZINAMONO15 = 2602,
ITEMID_DAIZINAMONO16 = 2603,
ITEMID_DAIZINAMONO17 = 2604,
ITEMID_DAIZINAMONO18 = 2605,
ITEMID_DAIZINAMONO19 = 2606,
ITEMID_DAIZINAMONO20 = 2607,
ITEMID_DAIZINAMONO21 = 2608,
ITEMID_DAIZINAMONO22 = 2609,
ITEMID_DAIZINAMONO23 = 2610,
ITEMID_DAIZINAMONO24 = 2611,
ITEMID_DAIZINAMONO25 = 2612,
ITEMID_DAIZINAMONO26 = 2613,
ITEMID_BEIKINGUPAUDAA = 2601,
ITEMID_IJIGENPAUDAA1 = 2602,
ITEMID_IJIGENPAUDAA2 = 2603,
ITEMID_IJIGENPAUDAA3 = 2604,
ITEMID_IJIGENPAUDAA4 = 2605,
ITEMID_IJIGENPAUDAA5 = 2606,
ITEMID_HOUENNOARAJIO = 2607,
ITEMID_SUPAISUBURAKKU = 2608,
ITEMID_ORIIBUAOIRU = 2609,
ITEMID_PACHIPACHIKYANDII = 2610,
ITEMID_TAISETSUNATEGAMI = 2611,
ITEMID_OMOIDENOYUBIWA2 = 2612,
ITEMID_YOGORETASUKAAHU = 2613,
ITEMID_DAIZINAMONO27 = 2614,
ITEMID_DAIZINAMONO28 = 2615,
ITEMID_DAIZINAMONO29 = 2616,
@@ -568,4 +584,54 @@ enum ItemID : int
ITEMID_AONOKANARYINUI1 = 2632,
ITEMID_AONOKANARYINUI2 = 2633,
ITEMID_AONOKANARYINUI3 = 2634,
ITEMID_RAITYUUNAITOx = 2635,
ITEMID_RAITYUUNAITOy = 2636,
ITEMID_TIRIINNAITO = 2637,
ITEMID_ABUSORUNAITOz = 2638,
ITEMID_MUKUHOOKUNAITO = 2639,
ITEMID_GABURIASUNAITOz = 2640,
ITEMID_RUKARIONAITOz = 2641,
ITEMID_GORUUGUNAITO = 2642,
ITEMID_NYAONIKUSUNAITO = 2643,
ITEMID_KEKENKANINAITO = 2644,
ITEMID_GUSOKUMUSYANAITO = 2645,
ITEMID_MAGIANAITO = 2646,
ITEMID_SUKOBUIRANAITO = 2647,
ITEMID_SEGUREIBUNAITO = 2648,
ITEMID_SHARITATSUNAITO = 2649,
ITEMID_KIRAHURORUNAITO = 2650,
ITEMID_IJIGENKURABONOMI = 2651,
ITEMID_IJIGENKAGONOMI = 2652,
ITEMID_IJIGENMOMONNOMI = 2653,
ITEMID_IJIGENTIIGONOMI = 2654,
ITEMID_IJIGENNANASINOMI = 2655,
ITEMID_IJIGENORENNOMI = 2656,
ITEMID_IJIGENKIINOMI = 2657,
ITEMID_IJIGENRAMUNOMI = 2658,
ITEMID_IJIGENOBONNOMI = 2659,
ITEMID_IJIGENZAROKUNOMI = 2660,
ITEMID_IJIGENNEKOBUNOMI = 2661,
ITEMID_IJIGENTAPORUNOMI = 2662,
ITEMID_IJIGENROMENOMI = 2663,
ITEMID_IJIGENUBUNOMI = 2664,
ITEMID_IJIGENMATOMANOMI = 2665,
ITEMID_IJIGENOKKANOMI = 2666,
ITEMID_IJIGENITOKENOMI = 2667,
ITEMID_IJIGENSOKUNONOMI = 2668,
ITEMID_IJIGENRINDONOMI = 2669,
ITEMID_IJIGENYATHENOMI = 2670,
ITEMID_IJIGENYOPUNOMI = 2671,
ITEMID_IJIGENBIAANOMI = 2672,
ITEMID_IJIGENSYUKANOMI = 2673,
ITEMID_IJIGENBAKOUNOMI = 2674,
ITEMID_IJIGENUTANNOMI = 2675,
ITEMID_IJIGENTANGANOMI = 2676,
ITEMID_IJIGENYOROGINOMI = 2677,
ITEMID_IJIGENKASIBUNOMI = 2678,
ITEMID_IJIGENHABANNOMI = 2679,
ITEMID_IJIGENNAMONOMI = 2680,
ITEMID_IJIGENRIRIBANOMI = 2681,
ITEMID_IJIGENHOZUNOMI = 2682,
ITEMID_IJIGENROZERUNOMI = 2683,
ITEMID_KANARIIPAN = 2684,
}

View File

@@ -10,10 +10,10 @@ table TrainerBattleDetail {
table TrainerBattleData {
Type:string (required);
TrainerBattleID:string;
NPCAssetID:string;
Field03:string;
Field04:string;
TrainerBattleID1:string;
NPCAssetID1:string;
TrainerBattleID2:string;
NPCAssetID2:string;
Field05:string;
Field06:string;
Field07:string;
@@ -28,7 +28,8 @@ table TrainerBattleData {
Field16:string;
Field17:string;
Field18:string;
Field19:[TrainerBattleDetail] (required);
Field19:string; // one field was inserted in 2.0.0, not sure which but it was after Field13
Field20:[TrainerBattleDetail] (required);
}
table TrainerBattleGlobalDBArray (fs_serializer) {

View File

@@ -17,6 +17,7 @@ table FieldAreaInfo {
ProhibitReadyBallThrow:bool;
ProhibitPartnerPutout:bool;
IsOptimizeWaterLight:bool;
CanEvolveToPoke0867:bool; // Runerigus, inserted in 2.0.0
DefaultStartPosition:PackedVec3f;
DefaultStartYaw:float;
Music:string;

View File

@@ -0,0 +1,33 @@
include "Shared/ActivationCondition.fbs";
include "Shared/ActivationConditionElement.fbs";
include "Shared/ActivationConditionParam.fbs";
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DimensionBossLottery {
BossLotteryId:string;
BattleId:string;
Rank:int;
EstimateLevel:int;
ExtensionLevel:int;
IsRematch:bool;
DevNo:int;
FormNo:int;
GateObjectType:int;
GateSpawnerId:string;
StartPointObjectName:string;
Condition:[ActivationCondition];
ReturnGateCondition:[ActivationCondition];
}
table DimensionBossLotteryDB {
Table:[DimensionBossLottery] (required);
}
table DimensionBossLotteryDBArray (fs_serializer) {
Table:[DimensionBossLotteryDB] (required);
}
root_type DimensionBossLotteryDBArray;

View File

@@ -0,0 +1,18 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DimensionConfig {
SpecialLotteryForceWinChainCount:int;
GateObjectStreamingActivateDistance:float;
GateObjectStreamingDeactivateDistance:float;
DummyZoneAreaId:string;
DummyZoneExtensionLevel:int;
BonusLotteryRandomThresholdDistance:float;
}
table DimensionConfigDB (fs_serializer) {
Table:DimensionConfig (required);
}
root_type DimensionConfigDB;

View File

@@ -0,0 +1,18 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DimensionGateObject {
GateObjectType:int;
ObjectTemplatePath:string;
}
table DimensionGateObjectDB {
Table:[DimensionGateObject] (required);
}
table DimensionGateObjectDBArray (fs_serializer) {
Table:[DimensionGateObjectDB] (required);
}
root_type DimensionGateObjectDBArray;

View File

@@ -0,0 +1,23 @@
include "Shared/ActivationCondition.fbs";
include "Shared/ActivationConditionElement.fbs";
include "Shared/ActivationConditionParam.fbs";
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DimensionGateSpawner {
GateSpawnerId:string;
IsDynamicCondition:bool;
Condition:[ActivationCondition];
}
table DimensionGateSpawnerDB {
Table:[DimensionGateSpawner] (required);
}
table DimensionGateSpawnerDBArray (fs_serializer) {
Table:[DimensionGateSpawnerDB] (required);
}
root_type DimensionGateSpawnerDBArray;

View File

@@ -0,0 +1,23 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table RankItem {
Rank:int;
ItemTableId:string (required);
}
table DimensionGimmickItem {
GimmickId:string;
RankItems:[RankItem] (required);
}
table DimensionGimmickItemDB {
Table:[DimensionGimmickItem] (required);
}
table DimensionGimmickItemDBArray (fs_serializer) {
Table:[DimensionGimmickItemDB] (required);
}
root_type DimensionGimmickItemDBArray;

View File

@@ -0,0 +1,26 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table StartPointItem {
ObjectName:string (required);
}
table DimensionMap {
MapId:string;
MapSize:int;
TaskRandomPopItemLimit:int;
WildTypeCount:int;
BattleTypeCount:int;
StartPointItems:[StartPointItem] (required);
}
table DimensionMapDB {
Table:[DimensionMap] (required);
}
table DimensionMapDBArray (fs_serializer) {
Table:[DimensionMapDB] (required);
}
root_type DimensionMapDBArray;

View File

@@ -0,0 +1,40 @@
include "Shared/ActivationCondition.fbs";
include "Shared/ActivationConditionElement.fbs";
include "Shared/ActivationConditionParam.fbs";
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DimensionProgressRankItem {
Rank:int;
LotteryWeight:int;
MinCount:int;
MaxCount:int;
}
table DimensionProgress {
ProgressId:string;
WildMinCount:int;
WildMaxCount:int;
BattleMinCount:int;
BattleMaxCount:int;
EventMinCount:int;
EventMaxCount:int;
AreaGateMinCount:int;
AreaGateMaxCount:int;
TotalGateMinCount:int;
TotalGateMaxCount:int;
RankItems:[DimensionProgressRankItem] (required);
Condition:[ActivationCondition];
}
table DimensionProgressDB {
Table:[DimensionProgress] (required);
}
table DimensionProgressDBArray (fs_serializer) {
Table:[DimensionProgressDB] (required);
}
root_type DimensionProgressDBArray;

View File

@@ -0,0 +1,32 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table PokeTypeItem {
PokeType:int;
LotteryWeight:int;
}
table MapItem {
MapId:string;
LotteryWeight:int;
}
table DimensionRank {
Rank:int;
ExtensionLevel:int;
EstimateLevel:int;
LimitReductionSpeed:float;
MapItems:[MapItem] (required);
BattlePokeTypeItems:[PokeTypeItem] (required);
}
table DimensionRankDB {
Table:[DimensionRank] (required);
}
table DimensionRankDBArray (fs_serializer) {
Table:[DimensionRankDB] (required);
}
root_type DimensionRankDBArray;

View File

@@ -0,0 +1,23 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DimensionSpecialLottery {
SpecialLotteryId:string (required);
Rank:int;
ZoneType:int;
MapId:string (required);
PatternSceneName:string; // not required
LotteryTableType:int;
LotteryWeight:int;
}
table DimensionSpecialLotteryDB {
Table:[DimensionSpecialLottery] (required);
}
table DimensionSpecialLotteryDBArray (fs_serializer) {
Table:[DimensionSpecialLotteryDB] (required);
}
root_type DimensionSpecialLotteryDBArray;

View File

@@ -0,0 +1,36 @@
include "Shared/ActivationCondition.fbs";
include "Shared/ActivationConditionElement.fbs";
include "Shared/ActivationConditionParam.fbs";
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DimensionUniqueLottery {
UniqueLotteryId:string;
UseLimit:bool;
Rank:int;
EstimateLevel:int;
MapId:string;
StartPointObjectName:string;
PatternSceneName:string;
ZoneType:int;
WildPokeType_0:int;
WildPokePatternNo_0:int;
WildPokeType_1:int;
WildPokePatternNo_1:int;
WildPokeType_2:int;
WildPokePatternNo_2:int;
GateSpawnerId:string;
Condition:[ActivationCondition];
}
table DimensionUniqueLotteryDB {
Table:[DimensionUniqueLottery] (required);
}
table DimensionUniqueLotteryDBArray (fs_serializer) {
Table:[DimensionUniqueLotteryDB] (required);
}
root_type DimensionUniqueLotteryDBArray;

View File

@@ -0,0 +1,34 @@
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DimensionWildSpawnerItem {
SpawnerDataId:string (required);
LotteryWeight:int;
}
table DimensionWildPatternItem {
PatternNo:int;
LotteryWeight:int;
SpawnerItems:[DimensionWildSpawnerItem];
}
table DimensionWildRankItem {
Rank:int;
PatternItems:[DimensionWildPatternItem];
}
table DimensionWildPokemon {
PokeType:int;
RankItems:[DimensionWildRankItem] (required);
}
table DimensionWildPokemonDB {
Table:[DimensionWildPokemon] (required);
}
table DimensionWildPokemonDBArray (fs_serializer) {
Table:[DimensionWildPokemonDB] (required);
}
root_type DimensionWildPokemonDBArray;

View File

@@ -0,0 +1,40 @@
include "Shared/ActivationCondition.fbs";
include "Shared/ActivationConditionElement.fbs";
include "Shared/ActivationConditionParam.fbs";
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table DimensionZoneEventItem {
EventId:string;
EventKind:int;
LotteryWeight:int;
EstimateLevel:int;
Condition:[ActivationCondition];
}
table DimensionZoneRankItem {
Rank:int;
WildLotteryWeight:int;
BattleLotteryWeight:int;
EventLotteryWeight:int;
WildSceneNames:[string];
BattleSceneNames:[string];
EventItems:[DimensionZoneEventItem];
}
table DimensionZoneLottery {
MapId:string;
RankItems:[DimensionZoneRankItem] (required);
}
table DimensionZoneLotteryDB {
Table:[DimensionZoneLottery] (required);
}
table DimensionZoneLotteryDBArray (fs_serializer) {
Table:[DimensionZoneLotteryDB] (required);
}
root_type DimensionZoneLotteryDBArray;

View File

@@ -0,0 +1,26 @@
include "Shared/ActivationCondition.fbs";
include "Shared/ActivationConditionElement.fbs";
include "Shared/ActivationConditionParam.fbs";
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table ItemTableInfo {
TableId:string;
Condition:[ActivationCondition];
}
table WazagimmickItemSpawner {
Id:string;
ObjectNameList:[string] (required);
MinCount:int;
MaxCount:int;
ItemTableInfoList:[ItemTableInfo] (required);
}
table WazagimmickItemSpawnerDB (fs_serializer) {
Table:[WazagimmickItemSpawner] (required);
}
root_type WazagimmickItemSpawnerDB;

View File

@@ -0,0 +1,30 @@
include "Shared/ActivationCondition.fbs";
include "Shared/ActivationConditionElement.fbs";
include "Shared/ActivationConditionParam.fbs";
namespace pkNX.Structures.FlatBuffers.ZA;
attribute "fs_serializer";
table ItemTableItemLotteryData {
ItemId:string (required);
Weight:int;
MaxCount:int;
Type:int;
Condition:[ActivationCondition];
}
table WazagimmickItemTable {
Id:string (required);
ItemLotteryDataList:[ItemTableItemLotteryData] (required);
}
table WazagimmickItemTableDB {
Table:[WazagimmickItemTable] (required);
}
table WazagimmickItemTableDBArray (fs_serializer) {
Table:[WazagimmickItemTableDB] (required);
}
root_type WazagimmickItemTableDBArray;

View File

@@ -23,10 +23,12 @@ The following games are supported:
* Sword / Shield
* Legends: Arceus
* Scarlet / Violet **(Only for dumping data, not to edit files!)**
* Legends: Z-A **(Only for dumping data, not to edit files!)**
pkNX operates under the assumption that your dumped ROM includes the latest available update data for the following games:
* Sword / Shield (Ver. 1.3.2)
* Scarlet / Violet (Ver. 3.0.1)
* Scarlet / Violet (Ver. 4.0.0)
* Legends: Z-A (Ver. 2.0.0)
## Features

View File

@@ -90,6 +90,7 @@ private static GameVersion GetGameFromPath(string romfs, string? exefs)
private const int FILECOUNT_SV_300 = 30; // Ver. 3.0.0 (Indigo Disk)
private const int FILECOUNT_ZA_100 = 22; // Ver. 1.0.0
private const int FILECOUNT_ZA_102 = 21; // Ver. 1.0.2
private const int FILECOUNT_ZA_200 = 22; // Ver. 2.0.0 (Mega Dimension)
private static ulong GetTitleID(string? exefs)
{
@@ -114,7 +115,7 @@ private static ulong GetTitleID(string? exefs)
FILECOUNT_SV or FILECOUNT_SV_101 or FILECOUNT_SV_120 or FILECOUNT_SV_130 or FILECOUNT_SV_201 or FILECOUNT_SV_300
=> DetectGen9(exefs),
FILECOUNT_ZA_100 or FILECOUNT_ZA_102
FILECOUNT_ZA_100 or FILECOUNT_ZA_102 or FILECOUNT_ZA_200
=> GameVersion.ZA,
_ => GameVersion.Invalid

View File

@@ -73,6 +73,8 @@ public enum EvolutionType : byte
Hisui = 61,
UseMoveBarbBarrage = 62, // ZA
UseItemFullMoon = 90, // Ursaluna
UseMoveAgileStyle = 91, // Wyrdeer
UseMoveStrongStyle = 92, // Overqwil
@@ -158,6 +160,7 @@ public static class EvolutionTypeExtensions
LevelUpRecoilDamageMale => true,
LevelUpRecoilDamageFemale => true,
Hisui => false,
UseMoveBarbBarrage => false,
UseItemFullMoon => false,
UseMoveAgileStyle => false,
@@ -237,6 +240,7 @@ public static class EvolutionTypeExtensions
[LevelUpKnowMoveECElse] = Moves, // Dudunsparce 0
[LevelUpKnowMoveEC25] = Moves, // Dudunsparce 1
[Hisui] = NoArg,
[UseMoveBarbBarrage] = NoArg,
};
public static EvolutionTypeArgumentType GetArgType(this EvolutionType t) => ArgType[t];

View File

@@ -51,13 +51,15 @@ private void InitializeComponent()
B_Misc = new System.Windows.Forms.Button();
B_Scrubbed = new System.Windows.Forms.Button();
B_DumpTriggers = new System.Windows.Forms.Button();
B_DumpConfig = new System.Windows.Forms.Button();
Tab_PKHeX = new System.Windows.Forms.TabPage();
flowLayoutPanel2 = new System.Windows.Forms.FlowLayoutPanel();
B_Locations = new System.Windows.Forms.Button();
B_DonutText = new System.Windows.Forms.Button();
Tab_Future = new System.Windows.Forms.TabPage();
flowLayoutPanel4 = new System.Windows.Forms.FlowLayoutPanel();
B_OpenFolder = new System.Windows.Forms.Button();
B_DumpConfig = new System.Windows.Forms.Button();
B_Dimension = new System.Windows.Forms.Button();
TC_Options.SuspendLayout();
Tab_General.SuspendLayout();
flowLayoutPanel1.SuspendLayout();
@@ -235,6 +237,7 @@ private void InitializeComponent()
flowLayoutPanel3.Controls.Add(B_Scrubbed);
flowLayoutPanel3.Controls.Add(B_DumpTriggers);
flowLayoutPanel3.Controls.Add(B_DumpConfig);
flowLayoutPanel3.Controls.Add(B_Dimension);
flowLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Fill;
flowLayoutPanel3.Location = new System.Drawing.Point(4, 3);
flowLayoutPanel3.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
@@ -341,6 +344,17 @@ private void InitializeComponent()
B_DumpTriggers.UseVisualStyleBackColor = true;
B_DumpTriggers.Click += B_Triggers_Click;
//
// B_DumpConfig
//
B_DumpConfig.Location = new System.Drawing.Point(4, 234);
B_DumpConfig.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
B_DumpConfig.Name = "B_DumpConfig";
B_DumpConfig.Size = new System.Drawing.Size(144, 71);
B_DumpConfig.TabIndex = 28;
B_DumpConfig.Text = "Config";
B_DumpConfig.UseVisualStyleBackColor = true;
B_DumpConfig.Click += B_DumpConfig_Click;
//
// Tab_PKHeX
//
Tab_PKHeX.Controls.Add(flowLayoutPanel2);
@@ -355,6 +369,7 @@ private void InitializeComponent()
// flowLayoutPanel2
//
flowLayoutPanel2.Controls.Add(B_Locations);
flowLayoutPanel2.Controls.Add(B_DonutText);
flowLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill;
flowLayoutPanel2.Location = new System.Drawing.Point(3, 3);
flowLayoutPanel2.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
@@ -373,6 +388,17 @@ private void InitializeComponent()
B_Locations.UseVisualStyleBackColor = true;
B_Locations.Click += B_Locations_Click;
//
// B_DonutText
//
B_DonutText.Location = new System.Drawing.Point(156, 3);
B_DonutText.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
B_DonutText.Name = "B_DonutText";
B_DonutText.Size = new System.Drawing.Size(144, 71);
B_DonutText.TabIndex = 22;
B_DonutText.Text = "Donut Text";
B_DonutText.UseVisualStyleBackColor = true;
B_DonutText.Click += B_DonutLocalization_Click;
//
// Tab_Future
//
Tab_Future.Controls.Add(flowLayoutPanel4);
@@ -405,16 +431,16 @@ private void InitializeComponent()
B_OpenFolder.UseVisualStyleBackColor = true;
B_OpenFolder.Click += B_OpenFolder_Click;
//
// B_DumpConfig
// B_Dimension
//
B_DumpConfig.Location = new System.Drawing.Point(4, 234);
B_DumpConfig.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
B_DumpConfig.Name = "B_DumpConfig";
B_DumpConfig.Size = new System.Drawing.Size(144, 71);
B_DumpConfig.TabIndex = 28;
B_DumpConfig.Text = "Config";
B_DumpConfig.UseVisualStyleBackColor = true;
B_DumpConfig.Click += B_DumpConfig_Click;
B_Dimension.Location = new System.Drawing.Point(156, 234);
B_Dimension.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
B_Dimension.Name = "B_Dimension";
B_Dimension.Size = new System.Drawing.Size(144, 71);
B_Dimension.TabIndex = 29;
B_Dimension.Text = "Dimension";
B_Dimension.UseVisualStyleBackColor = true;
B_Dimension.Click += B_DumpDimension_Click;
//
// Dumper9a
//
@@ -471,5 +497,7 @@ private void InitializeComponent()
private System.Windows.Forms.Button B_Scrubbed;
private System.Windows.Forms.Button B_DumpTriggers;
private System.Windows.Forms.Button B_DumpConfig;
private System.Windows.Forms.Button B_DonutText;
private System.Windows.Forms.Button B_Dimension;
}
}

View File

@@ -99,6 +99,12 @@ private void B_DumpMisc_Click(object sender, EventArgs e)
System.Media.SystemSounds.Asterisk.Play();
}
private void B_DumpDimension_Click(object sender, EventArgs e)
{
Dumper.DumpDimension();
System.Media.SystemSounds.Asterisk.Play();
}
private void B_DumpScrubbed_Click(object sender, EventArgs e)
{
Dumper.DumpScrubbedInfo();
@@ -190,4 +196,10 @@ private void B_Triggers_Click(object sender, EventArgs e)
Dumper.DumpEventTriggers();
System.Media.SystemSounds.Asterisk.Play();
}
private void B_DonutLocalization_Click(object sender, EventArgs e)
{
Dumper.DumpDonutData();
System.Media.SystemSounds.Asterisk.Play();
}
}

View File

@@ -121,7 +121,7 @@ public void DumpStrings()
{
var textConfig = new TextConfig(GameVersion.ZA);
ReadOnlySpan<string> groups = ["ik_messagedat"];
ReadOnlySpan<string> types = ["script", "common"];
ReadOnlySpan<string> types = ["script", "common", "sk"];
foreach (var group in groups)
{
foreach (var type in types)
@@ -512,6 +512,7 @@ public void DumpArchives()
public void DumpMisc()
{
Dump<ItemTableArray, ItemTable>("world/ik_data/battle/plib_item_conversion/plib_item_conversion_array.bfbs", z => z.Table);
Dump<RankBattleRewardArray, RankBattleReward>("world/ik_data/rank_battle/reward/reward_array.bfbs", z => z.Table);
Dump<PokedexBlackListMainArray, PokedexBlackListMain>("world/ik_data/ui/pokedex/black_list_main/black_list_main_array.bfbs", z => z.Table);
@@ -520,6 +521,7 @@ public void DumpMisc()
DumpJson<CaptureZARankData>("world/ik_data/capture/capture_zarank_data/capture_zarank_data.bin");
Dump<ZARewardItemDataArray, ZARewardItemData>("ik_event/bin/za_reward_item_data/za_reward_item_data.bin", z => z.Table);
Dump<NpcAssetDataDBArray, NpcAssetData>("world/ik_data/field/npc/npc_asset_data/npc_asset_data/npc_asset_data.bfbs", z => z.Table);
Dump<TrainerBattleGlobalDBArray, TrainerBattleData>("ik_event/bin/event_trainer_battle_data/trainer_btl_data_global.bin", z => z.Table);
Dump<ShopLineupArray, ShopLineup>("world/exl/shop/shop_item_lineup/shop_item_lineup.bin", z => z.Table);
Dump<ItemTableDataDBArray, ItemTableDataDB>("world/ik_data/field/random_pop_item/random_pop_item_table/random_pop_item_table_data/random_pop_item_table_data_array.bfbs", z => z.Table);
@@ -538,6 +540,27 @@ public void DumpMisc()
Dump<WeatherHappeningParamArray, WeatherHappeningParam>("world/ik_data/field/weather/weather_happening/weather_happening_array.bin", z => z.Table);
DumpDressUp();
const string pst = "world/ik_data/field/spawner_transform_data/placement_npc_spawner_transform/placement_npc_spawner_transform/placement_npc_spawner_transform_array.bfbs";
var spawnerT = Dump<SpawnerTransformDataDBArray, SpawnerTransformDataDB>(pst, z => z.Table);
for (var i = 0; i < spawnerT.Table.Count; i++)
{
var table = spawnerT.Table[i];
var lines = TableUtil.GetTable(table.Table);
var path = GetRawPath(pst);
var fileName = Path.ChangeExtension(path, $".{i:00}.txt");
File.WriteAllText(fileName, lines);
}
DumpJson<ConditionRareDBArray>("param_ai/data/ai/trigger/condition_rare/condition_rare_array.bin");
Dump<DonutFlavorArray, DonutFlavor>("world/exl/donut/flavor/flavor.bin", z => z.Table);
Dump<DonutRecipeArray, DonutRecipe>("world/exl/donut/donut_recipe/donut_recipe.bin", z => z.Table);
Dump<BerryDetailArray, BerryDetail>("world/exl/donut/berry/berry.bin", z => z.Table);
Dump<DonutDetailArray, DonutDetail>("world/exl/donut/donut/donut.bin", z => z.Table);
Dump<DonutFlavorLotteryArray, DonutFlavorLottery>("world/exl/donut/flavor_lottery/flavor_lottery.bin", z => z.Table);
Dump<DonutFlavorParameterArray, DonutFlavorParameter>("world/exl/donut/flavor_parameter/flavor_parameter.bin", z => z.Table);
Dump<DonutOneWordArray, DonutOneWord>("world/exl/donut/oneword/oneword.bin", z => z.Table);
}
private void DumpDressUp()
@@ -965,10 +988,18 @@ public void DumpPersonal()
var alphaMoves = Dump<OyabunWazaDB, OyabunWaza>(alphaMovesPath, z => z.Table)
.Table;
// Only acknowledge the first set of entries.
foreach (var alpha in alphaMoves)
{
foreach (var form in alpha.FormTableList)
pt[SpeciesConverterZA.GetNational9(alpha.DevNo), (byte)form.FormNo].AlphaMove = form.WazaNo;
{
var pi = pt[SpeciesConverterZA.GetNational9(alpha.DevNo), (byte)form.FormNo];
if (pi.AlphaMove == 0)
pi.AlphaMove = form.WazaNo;
// don't throw an exception if already non-zero -- Raichu's mega forms were incorrectly configured as 1/2 rather than 2/3.
// The Alpha Move search returns on first match.
// the only clash is Raichu-1, which needs to stay as Iron Tail (not Play Rough).
}
}
var bin = pt.Write();
@@ -1000,6 +1031,54 @@ public void DumpPersonal()
RipPersonal(pt, lang);
}
public void DumpDimension()
{
const string dimWild = "world/ik_data/field/dimension/dimension_wild_pokemon/dimension_wild_pokemon/dimension_wild_pokemon_array.bfbs";
Dump<DimensionWildPokemonDBArray, DimensionWildPokemonDB>(dimWild, z => z.Table);
DumpSel<DimensionWildPokemonDBArray, DimensionWildPokemon>(dimWild, z => z.Table.SelectMany(x => x.Table), "x");
const string dimSpecialLottery = "world/ik_data/field/dimension/dimension_special_lottery/dimension_special_lottery/dimension_special_lottery_array.bfbs";
Dump<DimensionSpecialLotteryDBArray, DimensionSpecialLotteryDB>(dimSpecialLottery, z => z.Table);
const string dimItem = "world/ik_data/field/dimension/dimension_gimmick_item/dimension_gimmick_item/dimension_gimmick_item_array.bfbs";
Dump<DimensionGimmickItemDBArray, DimensionGimmickItemDB>(dimItem, z => z.Table);
const string dimUniqueLottery = "world/ik_data/field/dimension/dimension_unique_lottery/dimension_unique_lottery/dimension_unique_lottery_array.bfbs";
Dump<DimensionUniqueLotteryDBArray, DimensionUniqueLotteryDB>(dimUniqueLottery, z => z.Table);
const string dimZoneLottery = "world/ik_data/field/dimension/dimension_zone_lottery/dimension_zone_lottery/dimension_zone_lottery_array.bfbs";
Dump<DimensionZoneLotteryDBArray, DimensionZoneLotteryDB>(dimZoneLottery, z => z.Table);
const string dimBossLottery = "world/ik_data/field/dimension/dimension_boss_lottery/dimension_boss_lottery/dimension_boss_lottery_array.bfbs";
Dump<DimensionBossLotteryDBArray, DimensionBossLotteryDB>(dimBossLottery, z => z.Table);
const string dimGateSpawner = "world/ik_data/field/dimension/dimension_gate_spawner/dimension_gate_spawner/dimension_gate_spawner_array.bfbs";
Dump<DimensionGateSpawnerDBArray, DimensionGateSpawnerDB>(dimGateSpawner, z => z.Table);
const string dimGateArray = "world/ik_data/field/dimension/dimension_gate_object/dimension_gate_object/dimension_gate_object_array.bfbs";
Dump<DimensionGateObjectDBArray, DimensionGateObjectDB>(dimGateArray, z => z.Table);
const string dimProgress = "world/ik_data/field/dimension/dimension_progress/dimension_progress/dimension_progress_array.bfbs";
Dump<DimensionProgressDBArray, DimensionProgressDB>(dimProgress, z => z.Table);
const string dimMapArray = "world/ik_data/field/dimension/dimension_map/dimension_map/dimension_map_array.bfbs";
Dump<DimensionMapDBArray, DimensionMapDB>(dimMapArray, z => z.Table);
const string dimRankArray = "world/ik_data/field/dimension/dimension_rank/dimension_rank/dimension_rank_array.bfbs";
Dump<DimensionRankDBArray, DimensionRankDB>(dimRankArray, z => z.Table);
const string dimConfig = "world/ik_data/field/dimension/dimension_config/dimension_config/dimension_config.bfbs";
DumpJson<DimensionConfigDB>(dimConfig);
const string wazaConfig = "world/ik_data/field/wazagimmick_item_spawner/wazagimmick_item_spawner/wazagimmick_item_spawner.bfbs";
Dump<WazagimmickItemSpawnerDB, WazagimmickItemSpawner>(wazaConfig, z => z.Table);
const string itemTable = "world/ik_data/field/wazagimmick_item_table/wazagimmick_item_table/wazagimmick_item_table_array.bfbs";
Dump<WazagimmickItemTableDBArray, WazagimmickItemTableDB>(itemTable, z => z.Table);
DumpDimensionGates();
}
private void RipPersonal(PersonalTable9ZA pt, string lang)
{
var cfg = new TextConfig(rom.Game);
@@ -1035,16 +1114,16 @@ private void RipPersonal(PersonalTable9ZA pt, string lang)
File.WriteAllLines(GetPath(lang, "MovePerPokemon.txt"), moveLines);
var dexOrder = pt.Table
.Where(z => z.FB.Dex != 0 && z.FB.IsPresentInGame)
.OrderBy(z => z.FB.Dex)
.Where(z => z.FB.Dex != null && z.FB.IsPresentInGame)
.OrderBy(z => z.FB.Dex!.Index)
.GroupBy(z => z.FB.Info.SpeciesNational)
.Select(z => z.First());
var pc = dexOrder.Select(z => $"{z.FB.Dex:000}\t{specNames[z.FB.Info.SpeciesInternal]}");
var pc = dexOrder.Select(z => $"{z.FB.Dex!.Index:000}\t{specNames[z.FB.Info.SpeciesInternal]}");
File.WriteAllText(GetPath(lang, "dex.txt"), string.Join(Environment.NewLine, pc));
var foreign = pt.Table
.Where(z => z.FB.Dex == 0 && z.IsPresentInGame)
.Where(z => z.FB.Dex == null && z.IsPresentInGame)
.GroupBy(z => z.FB.Info.SpeciesNational)
.Select(z => z.First());
@@ -1066,7 +1145,6 @@ private void RipPersonal(PersonalTable9ZA pt, string lang)
File.WriteAllText(GetPath(lang, "tm.txt"), string.Join(Environment.NewLine, tmNames));
}
private static byte[][] SerializeU16Pickle(PersonalTable9ZA pt, Func<PersonalInfo, IList<ushort>> sel)
{
var t = pt.Table;
@@ -1106,14 +1184,18 @@ static byte[] GetPickle(PersonalInfo9ZA e)
return Write(e.FB.Info.SpeciesNational, e.FB.Evolutions);
}
static byte[] Write(int species, IEnumerable<PersonalInfoEvolution> evos)
static byte[] Write(int species, IList<PersonalInfoEvolution> evos)
{
using var ms = new MemoryStream();
using var bw = new BinaryWriter(ms);
var list = evos.ToArray();
if (species == (int)Species.Crabrawler)
Array.Reverse(list); // put the levelup evo last.
var list = evos;
if (species is (int)Species.Crabrawler or (int)Species.Feebas)
{
var tmp = evos.ToArray();
Array.Reverse(tmp); // put the levelup evo last.
list = tmp;
}
foreach (var m in list) // just in case
{
@@ -1227,7 +1309,8 @@ public void DumpTrainers()
var classes = Get<TrainerTypeArray>(trt);
var npcs = Get<NpcAssetDataDBArray>(npc).Table;
var trBtlData = Get<TrainerBattleGlobalDBArray>(btl).Table;
List<(string TrainerBattleID, string NPCAssetID)> trBtls = trBtlData.Select(z => (z.TrainerBattleID, z.NPCAssetID)).ToList()!;
List<(string TrainerBattleID1, string NPCAssetID1)> trBtls1 = trBtlData.Select(z => (z.TrainerBattleID1, z.NPCAssetID1)).ToList()!;
List<(string TrainerBattleID2, string NPCAssetID2)> trBtls2 = trBtlData.Select(z => (z.TrainerBattleID2, z.NPCAssetID2)).ToList()!;
// pretty
List<string> result = [];
@@ -1276,6 +1359,7 @@ public void DumpTrainers()
var gender = pk.Sex is 0 ? string.Empty : pk.Sex is SexType.MALE ? " (♂)" : " (♀)";
var level = $" (Lv. {pk.Level})";
var alpha = pk.IsOybn ? " (Alpha)" : string.Empty;
var item = pk.Item is 0 ? string.Empty : $" (Held Item: {items[(int)pk.Item]})";
var rare = pk.RareType is RareType.RARE ? " ★" : string.Empty;
var move1 = PrettyDumpUtil.GetMoveText(pk.Waza1, moves);
@@ -1306,7 +1390,7 @@ public void DumpTrainers()
var eff = pk.EffortValue;
ReadOnlySpan<int> evs = [eff.HP, eff.ATK, eff.DEF, eff.SPA, eff.SPD, eff.SPE];
var textEVs = evs.ContainsAnyExcept(0) ? $" (EVs: {PrettyDumpUtil.GetEVSpread(evs)})" : string.Empty;
var line = $"- {name}{form}{gender}{rare}{level}{item}{ability}{nature}{ball}{moveOutput}{textIVs}{textEVs}";
var line = $"- {name}{form}{gender}{rare}{level}{alpha}{item}{ability}{nature}{ball}{moveOutput}{textIVs}{textEVs}";
result.Add(line);
}
@@ -1327,7 +1411,7 @@ static string GetTrainerNameFormatted(string type, string name, ushort flags)
var format = ((flags >> 7) & 0b11);
var display = format switch
{
0 when name is "Corbeau" or "Philippe" => "{1} of the {0}", // no clue how they differentiate these two from Grunts, hardcode it
0 when type is "Rust Syndicate" && name is "Corbeau" or "Philippe" => "{1} of the {0}", // no clue how they differentiate these two from Grunts, hardcode it
0 => "{0} {1}",
1 when article => "{1} of the {0}",
1 => "{1} of {0}",
@@ -1339,6 +1423,10 @@ static string GetTrainerNameFormatted(string type, string name, ushort flags)
if (name is "representative")
display = display.Insert(0, "The ");
// no unique name
if (type is "Alpha Trainer" or "Hyperspace Trainer")
return type;
return string.Format(display, type, name);
}
@@ -1361,21 +1449,51 @@ string GetTrainerName(string id)
return $"{rivalF}/{rivalM}";
}
var (trainerBattleId, npcAssetId) = trBtls.FirstOrDefault(z => z.TrainerBattleID == id);
if (trainerBattleId is null)
// no unique name
if (id.StartsWith("dim_rank_"))
return string.Empty;
var (trainerBattleId1, npcAssetId1) = trBtls1.FirstOrDefault(z => z.TrainerBattleID1 == id);
var (trainerBattleId2, npcAssetId2) = trBtls2.FirstOrDefault(z => z.TrainerBattleID2 == id);
var btlToCheck = trainerBattleId2 is null ? trainerBattleId1 : trainerBattleId2;
var npcToCheck = npcAssetId2 is null ? npcAssetId1 : npcAssetId2;
if (btlToCheck is null || id is "Ev_sub_123_010" || id.Contains("Ev_sub_124"))
{
// not in trname
if (id is "Ev_sub_145_010_multi")
return "Marcia";
var fallback = GetFallbackTrainerName(id);
return ahtbNames.GetString(FnvHash.HashFnv1a_64(fallback), trNames);
}
// special name format
if (npcAssetId.Contains("rest_trainer"))
if (npcToCheck.Contains("rest_trainer"))
{
var restName = ahtbNames.GetString(FnvHash.HashFnv1a_64(npcAssetId.Replace("_trainer", "")), trNames);
var restName = ahtbNames.GetString(FnvHash.HashFnv1a_64(npcToCheck.Replace("_trainer", "")), trNames);
return restName;
}
var target = npcs.FirstOrDefault(z => z.AssetId == npcAssetId)!;
if (npcToCheck.StartsWith("2on2_"))
npcToCheck = npcToCheck[5..];
var target = npcs.FirstOrDefault(z => z.AssetId == npcToCheck)!;
if (target is null)
{
var startQuest = npcToCheck.Length is 15 ? 8 : 7;
var startSub = npcToCheck.Length is 15 ? 12 : 11;
var questID = int.Parse(npcToCheck[startQuest..(startQuest + 3)]);
var subID = int.Parse(npcToCheck[startSub..(startSub + 3)]);
// this is off-by-two for some reason?
if (questID == 180)
subID += 2;
target = npcs.FirstOrDefault(z => z.AssetId == $"npc_sub{questID}_{subID:0000}")!;
}
var name = target.NpcInfoList.Where(z => !string.IsNullOrWhiteSpace(z.Name)).ToArray()[0].Name;
return ahtbNames.GetString(FnvHash.HashFnv1a_64(name), trNames);
}
@@ -1394,6 +1512,26 @@ string GetTrainerName(string id)
"Ev_m09_2400_multi_02" => "executive05", // Griselle
"Ev_sub_103_multi" => "alias01", // Andi
"Ev_sub_114_multi" => "battleg", // Josée
// DLC
"Ev_d02_0010" or "Ev_sub_161_010_multi" => "friend_01", // Naveen
"Ev_d00_2100_multi" or "Ev_d04_1100" or "Ev_sub_164_010_multi" => "friend_02", // Lida
"Ev_d01_3100" or "Ev_d02_2200" or "Ev_d03_1300" or "Ev_d04_2100" or "Ev_d05_1200" or "Ev_d06_1020" or "Ev_d07_1000" or "Ev_d07_1100" or "Ev_d07_2000" or "Ev_sub_162_010_multi" or "Ev_sub_203_010_multi_02" => "xyleader3_01", // Korrina
"Ev_d05_0100" => "boss03", // Corbeau
//"Ev_sub_145_010_multi" => "sub_145", // Marcia
"Ev_sub_148_020_client" => "sub_148", // Dixan
"Ev_sub_165_010_multi" or "Ev_sub_203_010_multi_01" => "detective", // Emma
"Ev_sub_166_010_multi" => "executive02", // Gwynn
"Ev_sub_167_010_multi" => "executive04", // Lebanne
"Ev_sub_168_010_multi" => "secretary", // Vinnie
"Ev_sub_169_010_multi" => "boss04", // Jacinthe
"Ev_sub_170_010_multi" => "executive03", // Philippe
// Some DLC Side Missions with NPCs from past Side Missions are, annoyingly, not mapped as such in the NPC db array
"Ev_sub_123_010" => "sub_002", // Trevelle
_ when id.Contains("megax") => "sub_001", // Tracie
_ when id.Contains("megay") => "sub_072", // Griddella
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
};
}
@@ -1511,6 +1649,8 @@ public void ScanPatterns()
("rnd_", true),
("TITLE_", true),
("itd_", true),
("donut", true),
("dim_", true),
("rso_", false), // royale card spawn
("t1_", false),
@@ -1559,7 +1699,7 @@ private void CreateSaveHashDump(IEnumerable<string> saveHashHits, string outPath
..FromFile<EventLabelArray>("ik_event/bin/flag/temp_flag.bin").Table.Select(z => z.Name),
..FromFile<EventLabelArray>("ik_event/bin/flag/temp_work.bin").Table.Select(z => z.Name),
..FromFile<EventLabelArray>("ik_event/bin/title/title.bin").Table.Select(z => z.Name),
..FromFile<TitleArray>("ik_event/bin/title/title.bin").Table.Select(z => z.Name),
// works to grab first field of object as string, even though this isn't the right schema to parse as
..FromFile<EventLabelArray>("ik_event/bin/momiji_count/momiji_count.bin").Table.Select(z => z.Name),
@@ -1838,7 +1978,7 @@ private void DumpPokemonPKHeX(PokemonDataDBArray pokemon)
var names = GetCommonText("monsname", lang, cfg);
var natures = GetCommonText("seikaku", lang, cfg);
ReadOnlySpan<string> Trades = ["sub_addpoke_araichu", "sub_tradepoke_riolu", "sub_addpoke_gyadon", "sub_tradepoke_heracros"];
ReadOnlySpan<string> Trades = ["sub_addpoke_araichu", "sub_tradepoke_riolu", "sub_addpoke_gyadon", "sub_tradepoke_heracros", "sub_tradepoke_poligon2"];
foreach (var enc in pokemon.Table)
{
@@ -1963,6 +2103,7 @@ public void DumpEncountData()
var outPath = GetPath("spawner");
var dumper = new SpawnRipper9a(rom, "English", outPath);
dumper.ExportRippedMaps();
dumper.ExportHyperspacePickle();
}
private void DumpEncountPretty(EncountDataDBArray pokemon, PokemonDropItemTableDataDBArray drops)
@@ -2098,13 +2239,19 @@ private void DumpEncountPretty(EncountDataDBArray pokemon, PokemonDropItemTableD
{
foreach (var slot in encDropData.Where(z => z.ItemTableId == i.Id))
{
float totalWeight = 0;
foreach (var dropped in i.ItemLotteryDataList!)
totalWeight += dropped.Weight;
foreach (var dropped in i.ItemLotteryDataList!)
{
var itemID = int.TryParse(dropped.ItemId, out var id);
var weight = dropped.Weight;
var maxCount = dropped.MaxCount;
var type = dropped.Type;
result.Add($"- {weight}% {items[id]} (×{maxCount})");
float rate = (float)(Math.Round((weight / totalWeight) * 100f, 2));
var countDisplay = maxCount == 1 ? $"×{maxCount}" : $"×1-{maxCount}";
result.Add($"- {rate:00.00}% {items[id]} ({countDisplay})");
}
}
}
@@ -2200,10 +2347,13 @@ private void DumpTradeNames()
result.Add(names[415]); // Riolouie (Riolu)
result.Add(names[416]); // Spicy (Slowpoke)
result.Add(names[417]); // Floffy (Raichu)
result.Add(names[496]); // Alias (Porygon)
result.Add(names[293]); // Tracie
result.Add(names[306]); // Bond
result.Add(names[310]); // Quille
result.Add(names[362]); // Griddella
result.Add(names[431]); // Trian
File.WriteAllLines(GetPath("pkhex", "trades", $"text_tradeza_{l}.txt"), result);
}
@@ -2444,7 +2594,7 @@ public void DumpEventTriggers()
private IEnumerable<string> GetTriggerFileList()
{
for (int i = 0; i <= 9; i++)
for (int i = 0; i <= 10; i++)
{
const string main = "ik_event/bin/trigger/main/trigger_main_{0:00}.bin";
var file = string.Format(main, i);
@@ -2458,6 +2608,106 @@ private IEnumerable<string> GetTriggerFileList()
if (rom.HasFile(file))
yield return file;
}
yield return "ik_event/bin/trigger/map/trigger_map_t2.bin";
yield return "ik_event/bin/trigger/global/trigger_global.bin";
yield return "ik_event/bin/trigger/trainer_battle/trigger_trainer_battle_global.bin";
yield return "ik_event/bin/trigger/wild_battle/trigger_wild_battle_global.bin";
yield return "ik_event/bin/trigger/last_battle/trigger_last_battle_global.bin";
yield return "ik_event/bin/trigger/boss_battle/trigger_boss_battle_global.bin";
yield return "ik_event/bin/trigger/athletic_time_attack/trigger_athletic_time_attack.bin";
}
public void DumpDonutData()
{
var textConfig = new TextConfig(GameVersion.ZA);
foreach (var language in LanguageEnglishNames)
{
var code = GetLanguageCode(language);
// Get Donut Names
// Skip index 0, only keep 203 entries
var donuts = GetText("donut_name", language).AsSpan(1, 203);
foreach (ref var name in donuts)
{
// remove string format. don't care for now.
name = name.Replace("[VAR 01D9(0001)][VAR 0133(0000)] ", ""); // english
name = name.Replace("[VAR 0133(0000)]", ""); // otherwise, before & after
name = name.Replace("[VAR 01D9(0001)]", "");
}
WriteText("donutName", code, donuts.ToArray());
// Get Flavor Names
var flavors = GetText("donut_flavor", language).AsSpan(205, 290);
// Need to skip over the unused entries.
// Skip 3,4,5 and 147-152
var flavorNames = new List<string>();
for (int i = 0; i < flavors.Length; i++)
{
if (i is 3 or 4 or 5 or >= 147 and <= 152)
continue;
flavorNames.Add(flavors[i]);
}
WriteText("donutFlavor", code, flavorNames);
}
return;
void WriteText(string name, string code, IEnumerable<string> lines)
{
var path = GetPath("pkhex", "donut");
Directory.CreateDirectory(path);
var fileName = $"text_{name}_{code}.txt";
File.WriteAllText(Path.Combine(path, fileName), string.Join('\n', lines));
}
string[] GetText(string name, string lang)
{
var data = rom.GetPackedFile($"{message}/dat/{lang}/sk/{name}.dat");
return new TextFile(data, textConfig).Lines;
}
}
public void DumpDimensionGates()
{
var files = new[]
{
"world/ik_scene/field/area/t1/sub_scene/t1_gimmick/t1_dim_gate_spawner/t1_dim_gate_spawner_blue_/t1_dim_gate_spawner_blue_0.trscn",
"world/ik_scene/field/area/t1/sub_scene/t1_gimmick/t1_dim_gate_spawner/t1_dim_gate_spawner_jaune_/t1_dim_gate_spawner_jaune_0.trscn",
"world/ik_scene/field/area/t1/sub_scene/t1_gimmick/t1_dim_gate_spawner/t1_dim_gate_spawner_rose_/t1_dim_gate_spawner_rose_0.trscn",
"world/ik_scene/field/area/t1/sub_scene/t1_gimmick/t1_dim_gate_spawner/t1_dim_gate_spawner_rouge_/t1_dim_gate_spawner_rouge_0.trscn",
"world/ik_scene/field/area/t1/sub_scene/t1_gimmick/t1_dim_gate_spawner/t1_dim_gate_spawner_veil_/t1_dim_gate_spawner_veil_0.trscn",
"world/ik_scene/field/area/t1_i011/sub_scene/t1_i011_gimmick/t1_i011_dim_gate_spawner_/t1_i011_dim_gate_spawner_0.trscn",
"world/ik_scene/field/area/t1_i012_1/sub_scene/t1_i012_1_gimmick/t1_i012_1_dim_gate_spawner_/t1_i012_1_dim_gate_spawner_0.trscn",
"world/ik_scene/field/area/t1_i012_2/sub_scene/t1_i012_2_gimmick/t1_i012_2_dim_gate_spawner_/t1_i012_2_dim_gate_spawner_0.trscn",
"world/ik_scene/field/area/t1_i014_1/sub_scene/t1_i014_1_gimmick/t1_i014_1_dim_gate_spawner_/t1_i014_1_dim_gate_spawner_0.trscn",
"world/ik_scene/field/area/t2/sub_scene/t2_gimmick/t2_dim_gate_spawner_/t2_dim_gate_spawner_0.trscn",
"world/ik_scene/field/area/t3/sub_scene/t3_gimmick/t3_dim_gate_spawner_/t3_dim_gate_spawner_0.trscn",
};
List<string> Result = [];
foreach (var t in files)
{
var data = rom.GetPackedFile(t);
var obj = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(data);
Result.Add(obj.ObjectTemplateName);
foreach (var o in obj.Objects.Where(z => z.Type is "trinity_ObjectTemplate"))
{
var sceneObject = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplateData>(o.Data);
var scenePoint = FlatBufferConverter.DeserializeFrom<TrinityScenePoint>(sceneObject.Data);
var dim = sceneObject.ObjectTemplateName;
var pos = scenePoint.Position;
Result.Add($"{dim}\t{pos.X}\t{pos.Y}\t{pos.Z}");
}
Result.Add("");
}
File.WriteAllLines(GetPath("dim_gate", "dim_gate.txt"), Result);
}
}

View File

@@ -12,7 +12,8 @@ public sealed class SpawnGlobalInfo
private readonly GameManager9a _rom;
public readonly Dictionary<string, EncountData> Encounters;
public readonly Dictionary<string, SpawnerTransformData> SpawnerTransforms;
public readonly Dictionary<string, List<SpawnerTransformData>> SpawnerTransforms;
public readonly List<string> HyperspaceSpawnSets;
public readonly PokemonSpawnerDataDBArray SpawnerDatabase;
@@ -26,10 +27,45 @@ public SpawnGlobalInfo(GameManager9a rom)
const string pst = "world/ik_data/field/spawner_transform_data/pokemon_spawner_transform/pokemon_spawner_transform/pokemon_spawner_transform_array.bin";
var spawnerT = Get<SpawnerTransformDataDBArray>(pst);
SpawnerTransforms = spawnerT.Table.SelectMany(z => z.Table).ToDictionary(z => z.Name ?? "", z => z);
// Build multimap of Name -> List<SpawnerTransformData>; skip null/empty names to avoid collisions.
SpawnerTransforms = spawnerT.Table
.SelectMany(z => z.Table)
.Where(x => !string.IsNullOrEmpty(x.Name))
.GroupBy(x => x.Name)
.ToDictionary(g => g.Key!, g => g.ToList());
const string psp = "world/ik_data/field/pokemon_spawner/pokemon_spawner_data/pokemon_spawner_data_array.bin";
SpawnerDatabase = Get<PokemonSpawnerDataDBArray>(psp);
const string dimWild = "world/ik_data/field/dimension/dimension_wild_pokemon/dimension_wild_pokemon/dimension_wild_pokemon_array.bin";
var dim = Get<DimensionWildPokemonDBArray>(dimWild);
// Since we don't care about the star count requirements (since distortions can be 1-5 regardless of game progress), we can lump them all together.
HyperspaceSpawnSets = GetHyperspaceSpawnSets(dim);
}
private static List<string> GetHyperspaceSpawnSets(DimensionWildPokemonDBArray dim)
{
List<string> result = [];
foreach (var a in dim.Table)
{
foreach (var b in a.Table)
{
foreach (var c in b.RankItems)
{
if (c.PatternItems is null) // no encounters for this type-set
continue;
foreach (var set in c.PatternItems)
{
if (set.SpawnerItems is not { } si)
continue;
result.AddRange(si.Select(x => x.SpawnerDataId));
}
}
}
}
return result;
}
private T Get<T>(string path) where T : class, IFlatBufferSerializable<T>

View File

@@ -80,11 +80,11 @@ public static void WritePickle(IReadOnlyList<EncounterArea9a> slotSets, string p
File.WriteAllBytes(path, mini);
}
public static void AddSlots(List<EncounterSlot9a> list, EncountData slot, EncountDataInfo enc)
public static void AddSlots(List<EncounterSlot9a> list, EncountData slot, EncountDataInfo enc, bool hyperspaceBoost = false)
{
var species = SpeciesConverterZA.GetNational9((ushort)slot.DevNo);
var form = (byte)slot.FormNo;
const int boost = 0; // enc.AdditionalLevel; // field is unused; assumed to be similar to Kitakami post-game level boost, but there is none applicable in Z-A.
int boost = !hyperspaceBoost ? 0 : enc.AdditionalLevel; // level boost used in hyperspace, applicable in Z-A.
var minLevel = (byte)(slot.MinLevel + boost);
var maxLevel = (byte)(slot.MaxLevel + boost);
var gender = (sbyte)slot.Sex;

View File

@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using pkNX.Structures.FlatBuffers.ZA;
namespace pkNX.WinForms;
@@ -6,4 +8,18 @@ namespace pkNX.WinForms;
public sealed class MapSpawnerSet
{
public required Dictionary<string, PokemonSpawnerData> SpawnerInfo { get; init; }
public bool TryGetSpawnerFromEncounter(string encId, [NotNullWhen(true)] out PokemonSpawnerData? spawnerData)
{
foreach (var (key, value) in SpawnerInfo)
{
if (value.EncountDataInfoList.All(x => x.EncountDataId != encId))
continue;
spawnerData = value;
return true;
}
spawnerData = null;
return false;
}
}

View File

@@ -6,6 +6,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using pkNX.Structures;
namespace pkNX.WinForms;
@@ -19,6 +20,7 @@ public sealed class SpawnRipper9a
public readonly LumioseMap Overworld;
public const byte LocationLabs = 234;
public const byte LocationSewer = 235;
public const ushort LocationHyperspace = 273;
public readonly SpawnNameResolver Resolver;
public readonly SpawnGlobalInfo Shared;
@@ -60,7 +62,6 @@ private void FindAllPointsOnMaps()
RippedMaps.Add(SimulateScene(SceneInfo9a.ScenePathsT3, "t3", _ => LocationSewer));
}
public void DumpSceneToPickle(List<SimulatedAreaSet> rip)
{
var sets = rip.SelectMany(z => z.ToAreas()).ToArray();
@@ -106,7 +107,7 @@ public void ExportRippedMaps()
foreach (var obj in spawn.Table)
{
foreach (var sub in obj.AppearanceSpawnerObjectInfoList)
result.Add(sub.ObjectName, obj);
result.TryAdd(sub.ObjectName, obj);
}
}
return result;
@@ -136,6 +137,79 @@ private SpawnSceneSimulation SimulateScene(string[] scenePaths, string map, Func
public string GetLocationName(int locationIndex) => Resolver.LocationNames[locationIndex];
public bool TryGetSpawner(string name, [NotNullWhen(true)] out PokemonSpawnerData? spawnerData) => RipInfo.SpawnerInfo.TryGetValue(name, out spawnerData);
public bool TryGetEncounter(string id, [NotNullWhen(true)] out EncountData? slot) => Shared.Encounters.TryGetValue(id, out slot);
public void ExportHyperspacePickle()
{
var list = new List<EncounterSlot9a>();
var locationTable = new EncounterArea9a[] { new() { Location = LocationHyperspace, Slots = list } };
// Random set of spawns can appear at any spawner. All we care about is the encounters within that set.
AddRandomSpawns(list);
// Special Distortion boss spawners come with a pack of encounters to act as nuisance. We need them.
// ect_zdm40X_sp0Y_Z
// bosses are the special spawn with fixed IVs. Don't need them.
var band = Shared.Encounters.Select(z => z.Value)
.Where(z => IsHyperspaceBandSp(z.Id));
foreach (var enc in band)
{
// Need to get the spawner that has this encounter, for the relevant level boost.
if (!RipInfo.TryGetSpawnerFromEncounter(enc.Id, out var spawner))
{
System.Diagnostics.Debug.WriteLine($"Could not find spawner for hyperspace band encounter {enc.Id} - {(Species)enc.DevNo}");
continue;
}
AddSpawner(list, spawner);
}
foreach (var set in locationTable)
set.CondenseList();
var path = Path.Combine(_outDir, "encounter_hyperspace_za.pkl");
EncounterSlot9a.WritePickle(locationTable, path);
// Write a concatenated array of slots for visual pattern inspection.
EncounterSlot9a.WritePickleWithoutLocations(locationTable.SelectMany(z => z.Slots), path);
}
private static bool IsHyperspaceBandSp(string name) => name.StartsWith("ect_zdm40") && !name.EndsWith("_sp");
private void AddRandomSpawns(List<EncounterSlot9a> list)
{
foreach (var set in Shared.HyperspaceSpawnSets)
{
foreach (var info in RipInfo.SpawnerInfo)
{
if (info.Value.Id != set)
continue;
AddSpawner(list, info.Value);
break;
}
}
}
private void AddSpawner(List<EncounterSlot9a> list, PokemonSpawnerData spawner)
{
foreach (var info in spawner.AppearanceSpawnerObjectInfoList)
{
var appearInfo = info.Appearance;
if (appearInfo.MinCount == 0)
continue;
var encounters = spawner.EncountDataInfoList;
// don't care about randomness of the list of encounters. just add them.
foreach (var enc in encounters)
{
if (enc.EncountDataId is not { } id)
continue;
if (!TryGetEncounter(id, out var slot))
continue;
EncounterSlot9a.AddSlots(list, slot, enc, hyperspaceBoost: true);
}
}
}
}
public record PointDump