use constructor tables from GC and BB client in map parsing

This commit is contained in:
Martin Michelsen
2023-11-20 23:43:40 -08:00
parent 30ad83fa67
commit 1d8ae0b4d9
5 changed files with 765 additions and 213 deletions
File diff suppressed because one or more lines are too long
+3
View File
@@ -16,6 +16,8 @@ const char* name_for_enum<EnemyType>(EnemyType type) {
return "UNKNOWN"; return "UNKNOWN";
case EnemyType::NONE: case EnemyType::NONE:
return "NONE"; return "NONE";
case EnemyType::NON_ENEMY_NPC:
return "NON_ENEMY_NPC";
case EnemyType::AL_RAPPY: case EnemyType::AL_RAPPY:
return "AL_RAPPY"; return "AL_RAPPY";
case EnemyType::ASTARK: case EnemyType::ASTARK:
@@ -274,6 +276,7 @@ EnemyType enum_for_name<EnemyType>(const char* name) {
static const unordered_map<string, EnemyType> names({ static const unordered_map<string, EnemyType> names({
{"UNKNOWN", EnemyType::UNKNOWN}, {"UNKNOWN", EnemyType::UNKNOWN},
{"NONE", EnemyType::NONE}, {"NONE", EnemyType::NONE},
{"NON_ENEMY_NPC", EnemyType::NON_ENEMY_NPC},
{"AL_RAPPY", EnemyType::AL_RAPPY}, {"AL_RAPPY", EnemyType::AL_RAPPY},
{"ASTARK", EnemyType::ASTARK}, {"ASTARK", EnemyType::ASTARK},
{"BA_BOOTA", EnemyType::BA_BOOTA}, {"BA_BOOTA", EnemyType::BA_BOOTA},
+1
View File
@@ -9,6 +9,7 @@
enum class EnemyType { enum class EnemyType {
UNKNOWN = -1, UNKNOWN = -1,
NONE = 0, NONE = 0,
NON_ENEMY_NPC,
AL_RAPPY, AL_RAPPY,
ASTARK, ASTARK,
BA_BOOTA, BA_BOOTA,
+297 -211
View File
@@ -67,16 +67,17 @@ string Map::EnemyEntry::str() const {
this->unused.load()); this->unused.load());
} }
Map::Enemy::Enemy(uint8_t floor, EnemyType type) Map::Enemy::Enemy(size_t source_index, uint8_t floor, EnemyType type)
: type(type), : source_index(source_index),
type(type),
floor(floor), floor(floor),
flags(0), flags(0),
last_hit_by_client_id(0) { last_hit_by_client_id(0) {
} }
string Map::Enemy::str() const { string Map::Enemy::str() const {
return string_printf("[Map::Enemy %s flags=%02hhX last_hit_by_client_id=%hu]", return string_printf("[Map::Enemy source %zX %s flags=%02hhX last_hit_by_client_id=%hu]",
name_for_enum(this->type), this->flags, this->last_hit_by_client_id); this->source_index, name_for_enum(this->type), this->flags, this->last_hit_by_client_id);
} }
string Map::Object::str(shared_ptr<const ItemNameIndex> name_index) const { string Map::Object::str(shared_ptr<const ItemNameIndex> name_index) const {
@@ -88,11 +89,11 @@ string Map::Object::str(shared_ptr<const ItemNameIndex> name_index) const {
} catch (const exception& e) { } catch (const exception& e) {
item_name = string_printf("(failed: %s)", e.what()); item_name = string_printf("(failed: %s)", e.what());
} }
return string_printf("[Map::Object %04hX @%04hX p1=%g (specialized: %s) floor=%02hhX item_drop_checked=%s]", return string_printf("[Map::Object source %zX %04hX @%04hX p1=%g (specialized: %s) floor=%02hhX item_drop_checked=%s]",
this->base_type, this->section, this->param1, item_name.c_str(), this->floor, this->item_drop_checked ? "true" : "false"); this->source_index, this->base_type, this->section, this->param1, item_name.c_str(), this->floor, this->item_drop_checked ? "true" : "false");
} else { } else {
return string_printf("[Map::Object %04hX @%04hX p1=%g (generic) p456=[%08" PRIX32 " %08" PRIX32 " %08" PRIX32 "] floor=%02hhX item_drop_checked=%s]", return string_printf("[Map::Object source %zX %04hX @%04hX p1=%g (generic) p456=[%08" PRIX32 " %08" PRIX32 " %08" PRIX32 "] floor=%02hhX item_drop_checked=%s]",
this->base_type, this->section, this->param1, this->param4, this->param5, this->param6, this->source_index, this->base_type, this->section, this->param1, this->param4, this->param5, this->param6,
this->floor, this->item_drop_checked ? "true" : "false"); this->floor, this->item_drop_checked ? "true" : "false");
} }
} }
@@ -112,6 +113,7 @@ void Map::add_objects_from_map_data(uint8_t floor, const void* data, size_t size
const auto* objects = reinterpret_cast<const ObjectEntry*>(data); const auto* objects = reinterpret_cast<const ObjectEntry*>(data);
for (size_t z = 0; z < entry_count; z++) { for (size_t z = 0; z < entry_count; z++) {
this->objects.emplace_back(Object{ this->objects.emplace_back(Object{
.source_index = z,
.base_type = objects[z].base_type, .base_type = objects[z].base_type,
.section = objects[z].section, .section = objects[z].section,
.param1 = objects[z].param1, .param1 = objects[z].param1,
@@ -136,11 +138,6 @@ bool Map::check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate) {
return false; return false;
} }
void Map::add_enemy(uint8_t floor, EnemyType type) {
static_game_data_log.info("Adding enemy %02hhX:E-%zX => %s", floor, this->enemies.size(), name_for_enum(type));
this->enemies.emplace_back(floor, type);
}
void Map::add_enemy( void Map::add_enemy(
Episode episode, Episode episode,
uint8_t difficulty, uint8_t difficulty,
@@ -149,42 +146,112 @@ void Map::add_enemy(
size_t index, size_t index,
const EnemyEntry& e, const EnemyEntry& e,
const RareEnemyRates& rare_rates) { const RareEnemyRates& rare_rates) {
auto add = [&](EnemyType type) -> void {
this->enemies.emplace_back(index, floor, type);
};
EnemyType child_type = EnemyType::UNKNOWN;
ssize_t default_num_children = 0;
switch (e.base_type) { switch (e.base_type) {
case 0x40: { case 0x0001: // TObjNpcFemaleBase
bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.hildeblue); case 0x0002: // TObjNpcFemaleChild
this->add_enemy(floor, is_rare ? EnemyType::HILDEBLUE : EnemyType::HILDEBEAR); case 0x0003: // TObjNpcFemaleDwarf
case 0x0004: // TObjNpcFemaleFat
case 0x0005: // TObjNpcFemaleMacho
case 0x0006: // TObjNpcFemaleOld
case 0x0007: // TObjNpcFemaleTall
case 0x0008: // TObjNpcMaleBase
case 0x0009: // TObjNpcMaleChild
case 0x000A: // TObjNpcMaleDwarf
case 0x000B: // TObjNpcMaleFat
case 0x000C: // TObjNpcMaleMacho
case 0x000D: // TObjNpcMaleOld
case 0x000E: // TObjNpcMaleTall
case 0x0019: // TObjNpcSoldierBase
case 0x001A: // TObjNpcSoldierMacho
case 0x001B: // TObjNpcGovernorBase
case 0x001C: // TObjNpcConnoisseur
case 0x001D: // TObjNpcCloakroomBase
case 0x001E: // TObjNpcExpertBase
case 0x001F: // TObjNpcNurseBase
case 0x0020: // TObjNpcSecretaryBase
case 0x0021: // TObjNpcHHM00
case 0x0022: // TObjNpcNHW00
case 0x0024: // TObjNpcHRM00
case 0x0025: // TObjNpcARM00
case 0x0026: // TObjNpcARW00
case 0x0027: // TObjNpcHFW00
case 0x0028: // TObjNpcNFM00
case 0x0029: // TObjNpcNFW00
case 0x002B: // TObjNpcNHW01
case 0x002C: // TObjNpcAHM01
case 0x002D: // TObjNpcHRM01
case 0x0030: // TObjNpcHFW01
case 0x0031: // TObjNpcNFM01
case 0x0032: // TObjNpcNFW01
case 0x0033: // TObjNpcEnemy
case 0x0045: // TObjNpcLappy
case 0x0046: // TObjNpcMoja
case 0x00A9: // TObjNpcBringer
case 0x00D0: // TObjNpcKenkyu
case 0x00D1: // TObjNpcSoutokufu
case 0x00D2: // TObjNpcHosa
case 0x00D3: // TObjNpcKenkyuW
case 0x00F0: // TObjNpcHosa2
case 0x00F1: // TObjNpcKenkyu2
case 0x00F2: // TObjNpcNgcBase
case 0x00F3: // TObjNpcNgcBase
case 0x00F4: // TObjNpcNgcBase
case 0x00F5: // TObjNpcNgcBase
case 0x00F6: // TObjNpcNgcBase
case 0x00F7: // TObjNpcNgcBase
case 0x00F8: // TObjNpcNgcBase
case 0x00F9: // TObjNpcNgcBase
case 0x00FA: // TObjNpcNgcBase
case 0x00FB: // TObjNpcNgcBase
case 0x00FC: // TObjNpcNgcBase
case 0x00FD: // TObjNpcNgcBase
case 0x00FE: // TObjNpcNgcBase
case 0x00FF: // TObjNpcNgcBase
// All of these have a default child count of zero
add(EnemyType::NON_ENEMY_NPC);
break; break;
}
case 0x41: { case 0x0040: // TObjEneMoja
add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.hildeblue)
? EnemyType::HILDEBLUE
: EnemyType::HILDEBEAR);
break;
case 0x0041: { // TObjEneLappy
bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.rappy); bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.rappy);
switch (episode) { switch (episode) {
case Episode::EP1: case Episode::EP1:
this->add_enemy(floor, is_rare ? EnemyType::AL_RAPPY : EnemyType::RAG_RAPPY); add(is_rare ? EnemyType::AL_RAPPY : EnemyType::RAG_RAPPY);
break; break;
case Episode::EP2: case Episode::EP2:
if (is_rare) { if (is_rare) {
switch (event) { switch (event) {
case 0x01: case 0x01:
this->add_enemy(floor, EnemyType::SAINT_RAPPY); add(EnemyType::SAINT_RAPPY);
break; break;
case 0x04: case 0x04:
this->add_enemy(floor, EnemyType::EGG_RAPPY); add(EnemyType::EGG_RAPPY);
break; break;
case 0x05: case 0x05:
this->add_enemy(floor, EnemyType::HALLO_RAPPY); add(EnemyType::HALLO_RAPPY);
break; break;
default: default:
this->add_enemy(floor, EnemyType::LOVE_RAPPY); add(EnemyType::LOVE_RAPPY);
} }
} else { } else {
this->add_enemy(floor, EnemyType::RAG_RAPPY); add(EnemyType::RAG_RAPPY);
} }
break; break;
case Episode::EP4: case Episode::EP4:
if (e.floor > 0x05) { if (e.floor > 0x05) {
this->add_enemy(floor, is_rare ? EnemyType::DEL_RAPPY_ALT : EnemyType::SAND_RAPPY_ALT); add(is_rare ? EnemyType::DEL_RAPPY_ALT : EnemyType::SAND_RAPPY_ALT);
} else { } else {
this->add_enemy(floor, is_rare ? EnemyType::DEL_RAPPY : EnemyType::SAND_RAPPY); add(is_rare ? EnemyType::DEL_RAPPY : EnemyType::SAND_RAPPY);
} }
break; break;
default: default:
@@ -192,302 +259,321 @@ void Map::add_enemy(
} }
break; break;
} }
case 0x42: { case 0x0042: // TObjEneBm3FlyNest
this->add_enemy(floor, EnemyType::MONEST); add(EnemyType::MONEST);
for (size_t x = 0; x < 30; x++) { default_num_children = 30;
this->add_enemy(floor, EnemyType::MOTHMANT);
}
break; break;
} case 0x0043: // TObjEneBm5Wolf
case 0x43: { add(e.fparam2 ? EnemyType::BARBAROUS_WOLF : EnemyType::SAVAGE_WOLF);
this->add_enemy(floor, e.fparam2 ? EnemyType::BARBAROUS_WOLF : EnemyType::SAVAGE_WOLF);
break; break;
} case 0x0044: { // TObjEneBeast
case 0x44:
static const EnemyType types[3] = {EnemyType::BOOMA, EnemyType::GOBOOMA, EnemyType::GIGOBOOMA}; static const EnemyType types[3] = {EnemyType::BOOMA, EnemyType::GOBOOMA, EnemyType::GIGOBOOMA};
this->add_enemy(floor, types[e.uparam1 % 3]); add(types[e.uparam1 % 3]);
break; break;
case 0x60: }
this->add_enemy(floor, EnemyType::GRASS_ASSASSIN); case 0x0060: // TObjGrass
add(EnemyType::GRASS_ASSASSIN);
break; break;
case 0x61: case 0x0061: // TObjEneRe2Flower
if ((episode == Episode::EP2) && (e.floor > 0x0F)) { if ((episode == Episode::EP2) && (e.floor > 0x0F)) {
this->add_enemy(floor, EnemyType::DEL_LILY); add(EnemyType::DEL_LILY);
} else { } else {
bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.nar_lily); add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.nar_lily)
this->add_enemy(floor, is_rare ? EnemyType::NAR_LILY : EnemyType::POISON_LILY); ? EnemyType::NAR_LILY
: EnemyType::POISON_LILY);
} }
break; break;
case 0x62: case 0x0062: // TObjEneNanoDrago
this->add_enemy(floor, EnemyType::NANO_DRAGON); add(EnemyType::NANO_DRAGON);
break; break;
case 0x63: { case 0x0063: { // TObjEneShark
static const EnemyType types[3] = {EnemyType::EVIL_SHARK, EnemyType::PAL_SHARK, EnemyType::GUIL_SHARK}; static const EnemyType types[3] = {EnemyType::EVIL_SHARK, EnemyType::PAL_SHARK, EnemyType::GUIL_SHARK};
this->add_enemy(floor, types[e.uparam1 % 3]); add(types[e.uparam1 % 3]);
break; break;
} }
case 0x64: { case 0x0064: // TObjEneSlime
bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.pouilly_slime); add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.pouilly_slime)
for (size_t x = 0; x < 5; x++) { // Main slime + 4 clones ? EnemyType::POFUILLY_SLIME
this->add_enemy(floor, is_rare ? EnemyType::POFUILLY_SLIME : EnemyType::POUILLY_SLIME); : EnemyType::POUILLY_SLIME);
default_num_children = 4;
break;
case 0x0065: // TObjEnePanarms
if ((e.num_children != 0) && (e.num_children != 2)) {
static_game_data_log.warning("PAN_ARMS has an unusual num_children (0x%hX)", e.num_children.load());
} }
default_num_children = -1; // Skip adding children (because we do it here)
add(EnemyType::PAN_ARMS);
add(EnemyType::HIDOOM);
add(EnemyType::MIGIUM);
break; break;
} case 0x0080: // TObjEneDubchik
case 0x65: add((e.uparam1 & 0x01) ? EnemyType::GILLCHIC : EnemyType::DUBCHIC);
this->add_enemy(floor, EnemyType::PAN_ARMS);
this->add_enemy(floor, EnemyType::HIDOOM);
this->add_enemy(floor, EnemyType::MIGIUM);
break; break;
case 0x80: case 0x0081: // TObjEneGyaranzo
this->add_enemy(floor, (e.uparam1 & 0x01) ? EnemyType::GILLCHIC : EnemyType::DUBCHIC); add(EnemyType::GARANZ);
break; break;
case 0x81: case 0x0082: // TObjEneMe3ShinowaReal
this->add_enemy(floor, EnemyType::GARANZ); add(e.fparam2 ? EnemyType::SINOW_GOLD : EnemyType::SINOW_BEAT);
default_num_children = 4;
break; break;
case 0x82: { case 0x0083: // TObjEneMe1Canadin
EnemyType type = e.fparam2 ? EnemyType::SINOW_GOLD : EnemyType::SINOW_BEAT; add(EnemyType::CANADINE);
size_t count = (e.num_children == 0) ? 5 : (e.num_children + 1); break;
for (size_t z = 0; z < count; z++) { case 0x0084: // TObjEneMe1CanadinLeader
this->add_enemy(floor, type); add(EnemyType::CANANE);
child_type = EnemyType::CANADINE_GROUP;
default_num_children = 8;
break;
case 0x0085: // TOCtrlDubchik
add(EnemyType::DUBWITCH);
break;
case 0x00A0: // TObjEneSaver
add(EnemyType::DELSABER);
break;
case 0x00A1: // TObjEneRe4Sorcerer
if ((e.num_children != 0) && (e.num_children != 2)) {
static_game_data_log.warning("CHAOS_SORCERER has an unusual num_children (0x%hX)", e.num_children.load());
} }
default_num_children = -1; // Skip adding children (because we do it here)
add(EnemyType::CHAOS_SORCERER);
add(EnemyType::BEE_R);
add(EnemyType::BEE_L);
break; break;
} case 0x00A2: // TObjEneDarkGunner
case 0x83: add(EnemyType::DARK_GUNNER);
this->add_enemy(floor, EnemyType::CANADINE);
break; break;
case 0x84: case 0x00A3: // TObjEneDarkGunCenter
this->add_enemy(floor, EnemyType::CANANE); add(EnemyType::DEATH_GUNNER);
for (size_t x = 0; x < 8; x++) {
this->add_enemy(floor, EnemyType::CANADINE_GROUP);
}
break; break;
case 0x85: case 0x00A4: // TObjEneDf2Bringer
this->add_enemy(floor, EnemyType::DUBWITCH); add(EnemyType::CHAOS_BRINGER);
break; break;
case 0xA0: case 0x00A5: // TObjEneRe7Berura
this->add_enemy(floor, EnemyType::DELSABER); add(EnemyType::DARK_BELRA);
break; break;
case 0xA1: case 0x00A6: { // TObjEneDimedian
this->add_enemy(floor, EnemyType::CHAOS_SORCERER);
this->add_enemy(floor, EnemyType::BEE_R);
this->add_enemy(floor, EnemyType::BEE_L);
break;
case 0xA2:
this->add_enemy(floor, EnemyType::DARK_GUNNER);
break;
case 0xA3:
this->add_enemy(floor, EnemyType::DEATH_GUNNER);
break;
case 0xA4:
this->add_enemy(floor, EnemyType::CHAOS_BRINGER);
break;
case 0xA5:
this->add_enemy(floor, EnemyType::DARK_BELRA);
break;
case 0xA6: {
static const EnemyType types[3] = {EnemyType::DIMENIAN, EnemyType::LA_DIMENIAN, EnemyType::SO_DIMENIAN}; static const EnemyType types[3] = {EnemyType::DIMENIAN, EnemyType::LA_DIMENIAN, EnemyType::SO_DIMENIAN};
this->add_enemy(floor, types[e.uparam1 % 3]); add(types[e.uparam1 % 3]);
break; break;
} }
case 0xA7: case 0x00A7: // TObjEneBalClawBody
this->add_enemy(floor, EnemyType::BULCLAW); add(EnemyType::BULCLAW);
for (size_t x = 0; x < 4; x++) { child_type = EnemyType::CLAW;
this->add_enemy(floor, EnemyType::CLAW); default_num_children = 4;
}
break; break;
case 0xA8: case 0x00A8: // Unnamed subclass of TObjEneBalClawClaw
this->add_enemy(floor, EnemyType::CLAW); add(EnemyType::CLAW);
break; break;
case 0xC0: case 0x00C0: // TBoss1Dragon or TBoss5Gryphon
if (episode == Episode::EP1) { if (episode == Episode::EP1) {
this->add_enemy(floor, EnemyType::DRAGON); add(EnemyType::DRAGON);
} else if (episode == Episode::EP2) { } else if (episode == Episode::EP2) {
this->add_enemy(floor, EnemyType::GAL_GRYPHON); add(EnemyType::GAL_GRYPHON);
} else { } else {
throw runtime_error("DRAGON-type enemy placed outside of Episodes 1 or 2"); throw runtime_error("DRAGON placed outside of Episode 1 or 2");
} }
break; break;
case 0xC1: case 0x00C1: // TBoss2DeRolLe
this->add_enemy(floor, EnemyType::DE_ROL_LE); if ((e.num_children != 0) && (e.num_children != 0x13)) {
static_game_data_log.warning("DE_ROL_LE has an unusual num_children (0x%hX)", e.num_children.load());
}
default_num_children = -1; // Skip adding children (because we do it here)
add(EnemyType::DE_ROL_LE);
for (size_t z = 0; z < 0x0A; z++) { for (size_t z = 0; z < 0x0A; z++) {
this->add_enemy(floor, EnemyType::DE_ROL_LE_BODY); add(EnemyType::DE_ROL_LE_BODY);
} }
for (size_t z = 0; z < 0x09; z++) { for (size_t z = 0; z < 0x09; z++) {
this->add_enemy(floor, EnemyType::DE_ROL_LE_MINE); add(EnemyType::DE_ROL_LE_MINE);
} }
break; break;
case 0xC2: case 0x00C2: // TBoss3Volopt
this->add_enemy(floor, EnemyType::VOL_OPT_1); if ((e.num_children != 0) && (e.num_children != 0x23)) {
for (size_t z = 0; z < 6; z++) { static_game_data_log.warning("VOL_OPT has an unusual num_children (0x%hX)", e.num_children.load());
this->add_enemy(floor, EnemyType::VOL_OPT_PILLAR);
} }
for (size_t z = 0; z < 24; z++) { default_num_children = -1; // Skip adding children (because we do it here)
this->add_enemy(floor, EnemyType::VOL_OPT_MONITOR); add(EnemyType::VOL_OPT_1);
for (size_t z = 0; z < 0x06; z++) {
add(EnemyType::VOL_OPT_PILLAR);
} }
for (size_t z = 0; z < 2; z++) { for (size_t z = 0; z < 0x18; z++) {
this->add_enemy(floor, EnemyType::NONE); add(EnemyType::VOL_OPT_MONITOR);
} }
this->add_enemy(floor, EnemyType::VOL_OPT_AMP); for (size_t z = 0; z < 0x02; z++) {
this->add_enemy(floor, EnemyType::VOL_OPT_CORE); add(EnemyType::NONE);
this->add_enemy(floor, EnemyType::NONE); }
add(EnemyType::VOL_OPT_AMP);
add(EnemyType::VOL_OPT_CORE);
add(EnemyType::NONE);
break; break;
case 0xC5: case 0x00C5: // Unnamed subclass of TObjEnemyCustom
this->add_enemy(floor, EnemyType::VOL_OPT_2); add(EnemyType::VOL_OPT_2);
break; break;
case 0xC8: case 0x00C8: // TBoss4DarkFalz
if ((e.num_children != 0) && (e.num_children != 0x200)) {
static_game_data_log.warning("DARK_FALZ has an unusual num_children (0x%hX)", e.num_children.load());
}
default_num_children = -1; // Skip adding children (because we do it here)
if (difficulty) { if (difficulty) {
this->add_enemy(floor, EnemyType::DARK_FALZ_3); add(EnemyType::DARK_FALZ_3);
} else { } else {
this->add_enemy(floor, EnemyType::DARK_FALZ_2); add(EnemyType::DARK_FALZ_2);
} }
for (size_t x = 0; x < 0x1FD; x++) { for (size_t x = 0; x < 0x1FD; x++) {
this->add_enemy(floor, difficulty == 3 ? EnemyType::DARVANT_ULTIMATE : EnemyType::DARVANT); add(difficulty == 3 ? EnemyType::DARVANT_ULTIMATE : EnemyType::DARVANT);
} }
this->add_enemy(floor, EnemyType::DARK_FALZ_3); add(EnemyType::DARK_FALZ_3);
this->add_enemy(floor, EnemyType::DARK_FALZ_2); add(EnemyType::DARK_FALZ_2);
this->add_enemy(floor, EnemyType::DARK_FALZ_1); add(EnemyType::DARK_FALZ_1);
break; break;
case 0xCA: case 0x00CA: // TBoss6PlotFalz
for (size_t z = 0; z < 0x201; z++) { add(EnemyType::OLGA_FLOW_2);
this->add_enemy(floor, EnemyType::OLGA_FLOW_2); default_num_children = 0x200;
}
break; break;
case 0xCB: case 0x00CB: // TBoss7DeRolLeC
this->add_enemy(floor, EnemyType::BARBA_RAY); add(EnemyType::BARBA_RAY);
for (size_t z = 0; z < 0x2F; z++) { child_type = EnemyType::PIG_RAY;
this->add_enemy(floor, EnemyType::PIG_RAY); default_num_children = 0x2F;
}
break; break;
case 0xCC: case 0x00CC: // TBoss8Dragon
for (size_t z = 0; z < 6; z++) { add(EnemyType::GOL_DRAGON);
this->add_enemy(floor, EnemyType::GOL_DRAGON); default_num_children = 5;
}
break; break;
case 0xD4: { case 0x00D4: // TObjEneMe3StelthReal
EnemyType type = (e.uparam1 & 1) ? EnemyType::SINOW_SPIGELL : EnemyType::SINOW_BERILL; add((e.uparam1 & 1) ? EnemyType::SINOW_SPIGELL : EnemyType::SINOW_BERILL);
for (size_t z = 0; z < 5; z++) { default_num_children = 4;
this->add_enemy(floor, type);
}
break; break;
} case 0x00D5: // TObjEneMerillLia
case 0xD5: add((e.uparam1 & 0x01) ? EnemyType::MERILTAS : EnemyType::MERILLIA);
this->add_enemy(floor, (e.uparam1 & 0x01) ? EnemyType::MERILTAS : EnemyType::MERILLIA);
break; break;
case 0xD6: case 0x00D6: // TObjEneBm9Mericarol
if (e.uparam1 == 0) { if (e.uparam1 == 0) {
this->add_enemy(floor, EnemyType::MERICAROL); add(EnemyType::MERICAROL);
} else { } else {
this->add_enemy(floor, ((e.uparam1 % 3) == 2) ? EnemyType::MERICUS : EnemyType::MERIKLE); add(((e.uparam1 % 3) == 2) ? EnemyType::MERICUS : EnemyType::MERIKLE);
} }
break; break;
case 0xD7: case 0x00D7: // TObjEneBm5GibonU
this->add_enemy(floor, (e.uparam1 & 0x01) ? EnemyType::ZOL_GIBBON : EnemyType::UL_GIBBON); add((e.uparam1 & 0x01) ? EnemyType::ZOL_GIBBON : EnemyType::UL_GIBBON);
break; break;
case 0xD8: case 0x00D8: // TObjEneGibbles
this->add_enemy(floor, EnemyType::GIBBLES); add(EnemyType::GIBBLES);
break; break;
case 0xD9: case 0x00D9: // TObjEneMe1Gee
this->add_enemy(floor, EnemyType::GEE); add(EnemyType::GEE);
break; break;
case 0xDA: case 0x00DA: // TObjEneMe1GiGue
this->add_enemy(floor, EnemyType::GI_GUE); add(EnemyType::GI_GUE);
break; break;
case 0xDB: case 0x00DB: // TObjEneDelDepth
this->add_enemy(floor, EnemyType::DELDEPTH); add(EnemyType::DELDEPTH);
break; break;
case 0xDC: case 0x00DC: // TObjEneDellBiter
this->add_enemy(floor, EnemyType::DELBITER); add(EnemyType::DELBITER);
break; break;
case 0xDD: case 0x00DD: // TObjEneDolmOlm
this->add_enemy(floor, (e.uparam1 & 0x01) ? EnemyType::DOLMDARL : EnemyType::DOLMOLM); add(e.uparam1 ? EnemyType::DOLMDARL : EnemyType::DOLMOLM);
break; break;
case 0xDE: case 0x00DE: // TObjEneMorfos
this->add_enemy(floor, EnemyType::MORFOS); add(EnemyType::MORFOS);
break; break;
case 0xDF: case 0x00DF: // TObjEneRecobox
this->add_enemy(floor, EnemyType::RECOBOX); add(EnemyType::RECOBOX);
for (size_t x = 0; x < e.num_children; x++) { child_type = EnemyType::RECON;
this->add_enemy(floor, EnemyType::RECON);
}
break; break;
case 0xE0: case 0x00E0: // TObjEneMe3SinowZoaReal or TObjEneEpsilonBody
if ((episode == Episode::EP2) && (e.floor > 0x0F)) { if ((episode == Episode::EP2) && (e.floor > 0x0F)) {
this->add_enemy(floor, EnemyType::EPSILON); add(EnemyType::EPSILON);
for (size_t z = 0; z < 4; z++) { default_num_children = 4;
this->add_enemy(floor, EnemyType::EPSIGUARD); child_type = EnemyType::EPSIGUARD;
}
} else { } else {
EnemyType type = (e.uparam1 & 0x01) ? EnemyType::SINOW_ZELE : EnemyType::SINOW_ZOA; add((e.uparam1 & 0x01) ? EnemyType::SINOW_ZELE : EnemyType::SINOW_ZOA);
size_t count = e.num_children + 1;
for (size_t z = 0; z < count; z++) {
this->add_enemy(floor, type);
}
} }
break; break;
case 0xE1: case 0x00E1: // TObjEneIllGill
this->add_enemy(floor, EnemyType::ILL_GILL); add(EnemyType::ILL_GILL);
break; break;
case 0x0110: case 0x0110:
this->add_enemy(floor, EnemyType::ASTARK); add(EnemyType::ASTARK);
break; break;
case 0x0111: case 0x0111:
if (e.floor > 0x05) { if (e.floor > 0x05) {
this->add_enemy(floor, e.fparam2 ? EnemyType::YOWIE_ALT : EnemyType::SATELLITE_LIZARD_ALT); add(e.fparam2 ? EnemyType::YOWIE_ALT : EnemyType::SATELLITE_LIZARD_ALT);
} else { } else {
this->add_enemy(floor, e.fparam2 ? EnemyType::YOWIE : EnemyType::SATELLITE_LIZARD); add(e.fparam2 ? EnemyType::YOWIE : EnemyType::SATELLITE_LIZARD);
} }
break; break;
case 0x0112: { case 0x0112:
bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.merissa_aa); add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.merissa_aa)
this->add_enemy(floor, is_rare ? EnemyType::MERISSA_AA : EnemyType::MERISSA_A); ? EnemyType::MERISSA_AA
: EnemyType::MERISSA_A);
break; break;
}
case 0x0113: case 0x0113:
this->add_enemy(floor, EnemyType::GIRTABLULU); add(EnemyType::GIRTABLULU);
break; break;
case 0x0114: { case 0x0114: {
bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.pazuzu); bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.pazuzu);
if (e.floor > 0x05) { if (e.floor > 0x05) {
this->add_enemy(floor, is_rare ? EnemyType::PAZUZU_ALT : EnemyType::ZU_ALT); add(is_rare ? EnemyType::PAZUZU_ALT : EnemyType::ZU_ALT);
} else { } else {
this->add_enemy(floor, is_rare ? EnemyType::PAZUZU : EnemyType::ZU); add(is_rare ? EnemyType::PAZUZU : EnemyType::ZU);
} }
break; break;
} }
case 0x0115: case 0x0115:
if (e.uparam1 & 2) { if (e.uparam1 & 2) {
this->add_enemy(floor, EnemyType::BA_BOOTA); add(EnemyType::BA_BOOTA);
} else { } else {
this->add_enemy(floor, (e.uparam1 & 1) ? EnemyType::ZE_BOOTA : EnemyType::BOOTA); add((e.uparam1 & 1) ? EnemyType::ZE_BOOTA : EnemyType::BOOTA);
} }
break; break;
case 0x0116: { case 0x0116:
bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.dorphon_eclair); add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.dorphon_eclair)
this->add_enemy(floor, is_rare ? EnemyType::DORPHON_ECLAIR : EnemyType::DORPHON); ? EnemyType::DORPHON_ECLAIR
: EnemyType::DORPHON);
break; break;
}
case 0x0117: { case 0x0117: {
static const EnemyType types[3] = {EnemyType::GORAN, EnemyType::PYRO_GORAN, EnemyType::GORAN_DETONATOR}; static const EnemyType types[3] = {EnemyType::GORAN, EnemyType::PYRO_GORAN, EnemyType::GORAN_DETONATOR};
this->add_enemy(floor, types[e.uparam1 % 3]); add(types[e.uparam1 % 3]);
break; break;
} }
case 0x0119: { case 0x0119: {
bool is_rare = this->check_and_log_rare_enemy((e.fparam2 != 0.0f), rare_rates.kondrieu); bool is_rare = this->check_and_log_rare_enemy((e.fparam2 != 0.0f), rare_rates.kondrieu);
if (is_rare) { if (is_rare) {
this->add_enemy(floor, EnemyType::KONDRIEU); add(EnemyType::KONDRIEU);
} else { } else {
this->add_enemy(floor, (e.uparam1 & 1) ? EnemyType::SHAMBERTIN : EnemyType::SAINT_MILLION); add((e.uparam1 & 1) ? EnemyType::SHAMBERTIN : EnemyType::SAINT_MILLION);
} }
default_num_children = 0x18;
break; break;
} }
default:
for (size_t z = 0; z < static_cast<size_t>(e.num_children + 1); z++) { case 0x00C3: // TBoss3VoloptP01
this->add_enemy(floor, EnemyType::UNKNOWN); case 0x00C4: // TBoss3VoloptCore or subclass
} case 0x00C6: // TBoss3VoloptMonitor
case 0x00C7: // TBoss3VoloptHiraisin
case 0x0100:
add(EnemyType::UNKNOWN);
static_game_data_log.warning( static_game_data_log.warning(
"(Entry %zu, offset %zX in file) Unknown enemy type %04hX", "(Entry %zu, offset %zX in file) Unknown enemy type %04hX",
index, index * sizeof(EnemyEntry), e.base_type.load()); index, index * sizeof(EnemyEntry), e.base_type.load());
break; break;
default:
add(EnemyType::UNKNOWN);
static_game_data_log.warning(
"(Entry %zu, offset %zX in file) Invalid enemy type %04hX",
index, index * sizeof(EnemyEntry), e.base_type.load());
break;
}
if (default_num_children >= 0) {
size_t num_children = e.num_children ? e.num_children.load() : default_num_children;
if ((child_type == EnemyType::UNKNOWN) && !this->enemies.empty()) {
child_type = this->enemies.back().type;
}
for (size_t x = 0; x < num_children; x++) {
add(child_type);
}
} }
} }
+3 -2
View File
@@ -200,6 +200,7 @@ struct Map {
struct Object { struct Object {
// TODO: Add more fields in here if we ever care about them. Currently we // TODO: Add more fields in here if we ever care about them. Currently we
// only care about boxes with fixed item drops. // only care about boxes with fixed item drops.
size_t source_index;
uint16_t base_type; uint16_t base_type;
uint16_t section; uint16_t section;
float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6 float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
@@ -222,12 +223,13 @@ struct Map {
DEFEATED = 0x10, DEFEATED = 0x10,
ITEM_DROPPED = 0x20, ITEM_DROPPED = 0x20,
}; };
size_t source_index;
EnemyType type; EnemyType type;
uint8_t floor; uint8_t floor;
uint8_t flags; uint8_t flags;
uint8_t last_hit_by_client_id; uint8_t last_hit_by_client_id;
Enemy(uint8_t floor, EnemyType type); Enemy(size_t source_index, uint8_t floor, EnemyType type);
std::string str() const; std::string str() const;
} __attribute__((packed)); } __attribute__((packed));
@@ -241,7 +243,6 @@ struct Map {
void add_objects_from_map_data(uint8_t floor, const void* data, size_t size); void add_objects_from_map_data(uint8_t floor, const void* data, size_t size);
bool check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate); bool check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate);
void add_enemy(uint8_t floor, EnemyType type);
void add_enemy( void add_enemy(
Episode episode, Episode episode,
uint8_t difficulty, uint8_t difficulty,