diff --git a/README.md b/README.md index d368616b..9f849c9c 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ With that said, I offer no guarantees on how or when this project will advance. Current known issues / missing features / things to do: - Implement the rest of PSOBB. Major areas of work: - - Find any remaining mismatches in enemy IDs / experience (Episode 1 is mostly fixed now, except for Dark Falz) + - Find any remaining mismatches in enemy IDs / experience + - Sale prices for non-rare weapons with specials are computed incorrectly when buying/selling at shops - Replace enemy list, game episode, etc. with quest data when loading a quest - Implement trade window - Fix some edge cases on the BB proxy server (e.g. make sure Change Ship does the right thing, which is not the same as what it should do on other versions). diff --git a/src/EnemyType.cc b/src/EnemyType.cc index cd0a43ba..11cf3a64 100644 --- a/src/EnemyType.cc +++ b/src/EnemyType.cc @@ -26,6 +26,10 @@ const char* name_for_enum(EnemyType type) { return "BARBA_RAY"; case EnemyType::BARBAROUS_WOLF: return "BARBAROUS_WOLF"; + case EnemyType::BEE_L: + return "BEE_L"; + case EnemyType::BEE_R: + return "BEE_R"; case EnemyType::BOOMA: return "BOOMA"; case EnemyType::BOOTA: @@ -58,6 +62,10 @@ const char* name_for_enum(EnemyType type) { return "DARVANT"; case EnemyType::DE_ROL_LE: return "DE_ROL_LE"; + case EnemyType::DE_ROL_LE_BODY: + return "DE_ROL_LE_BODY"; + case EnemyType::DE_ROL_LE_MINE: + return "DE_ROL_LE_MINE"; case EnemyType::DEATH_GUNNER: return "DEATH_GUNNER"; case EnemyType::DEL_LILY: @@ -90,6 +98,8 @@ const char* name_for_enum(EnemyType type) { return "DUBWITCH"; case EnemyType::EGG_RAPPY: return "EGG_RAPPY"; + case EnemyType::EPSIGUARD: + return "EPSIGUARD"; case EnemyType::EPSILON: return "EPSILON"; case EnemyType::EVIL_SHARK: @@ -178,6 +188,8 @@ const char* name_for_enum(EnemyType type) { return "PAZUZU"; case EnemyType::PAZUZU_ALT: return "PAZUZU_ALT"; + case EnemyType::PIG_RAY: + return "PIG_RAY"; case EnemyType::POFUILLY_SLIME: return "POFUILLY_SLIME"; case EnemyType::POUILLY_SLIME: @@ -228,6 +240,14 @@ const char* name_for_enum(EnemyType type) { return "VOL_OPT_1"; case EnemyType::VOL_OPT_2: return "VOL_OPT_2"; + case EnemyType::VOL_OPT_AMP: + return "VOL_OPT_AMP"; + case EnemyType::VOL_OPT_CORE: + return "VOL_OPT_CORE"; + case EnemyType::VOL_OPT_MONITOR: + return "VOL_OPT_MONITOR"; + case EnemyType::VOL_OPT_PILLAR: + return "VOL_OPT_PILLAR"; case EnemyType::YOWIE: return "YOWIE"; case EnemyType::YOWIE_ALT: @@ -255,6 +275,8 @@ EnemyType enum_for_name(const char* name) { {"BA_BOOTA", EnemyType::BA_BOOTA}, {"BARBA_RAY", EnemyType::BARBA_RAY}, {"BARBAROUS_WOLF", EnemyType::BARBAROUS_WOLF}, + {"BEE_L", EnemyType::BEE_L}, + {"BEE_R", EnemyType::BEE_R}, {"BOOMA", EnemyType::BOOMA}, {"BOOTA", EnemyType::BOOTA}, {"BULCLAW", EnemyType::BULCLAW}, @@ -272,6 +294,8 @@ EnemyType enum_for_name(const char* name) { {"DARVANT", EnemyType::DARVANT}, {"DARVANT_ULTIMATE", EnemyType::DARVANT_ULTIMATE}, {"DE_ROL_LE", EnemyType::DE_ROL_LE}, + {"DE_ROL_LE_BODY", EnemyType::DE_ROL_LE_BODY}, + {"DE_ROL_LE_MINE", EnemyType::DE_ROL_LE_MINE}, {"DEATH_GUNNER", EnemyType::DEATH_GUNNER}, {"DEL_LILY", EnemyType::DEL_LILY}, {"DEL_RAPPY", EnemyType::DEL_RAPPY}, @@ -288,6 +312,7 @@ EnemyType enum_for_name(const char* name) { {"DUBCHIC", EnemyType::DUBCHIC}, {"DUBWITCH", EnemyType::DUBWITCH}, {"EGG_RAPPY", EnemyType::EGG_RAPPY}, + {"EPSIGUARD", EnemyType::EPSIGUARD}, {"EPSILON", EnemyType::EPSILON}, {"EVIL_SHARK", EnemyType::EVIL_SHARK}, {"GAEL", EnemyType::GAEL}, @@ -332,6 +357,7 @@ EnemyType enum_for_name(const char* name) { {"PAN_ARMS", EnemyType::PAN_ARMS}, {"PAZUZU", EnemyType::PAZUZU}, {"PAZUZU_ALT", EnemyType::PAZUZU_ALT}, + {"PIG_RAY", EnemyType::PIG_RAY}, {"POFUILLY_SLIME", EnemyType::POFUILLY_SLIME}, {"POUILLY_SLIME", EnemyType::POUILLY_SLIME}, {"POISON_LILY", EnemyType::POISON_LILY}, @@ -357,6 +383,10 @@ EnemyType enum_for_name(const char* name) { {"UL_GIBBON", EnemyType::UL_GIBBON}, {"VOL_OPT_1", EnemyType::VOL_OPT_1}, {"VOL_OPT_2", EnemyType::VOL_OPT_2}, + {"VOL_OPT_AMP", EnemyType::VOL_OPT_AMP}, + {"VOL_OPT_CORE", EnemyType::VOL_OPT_CORE}, + {"VOL_OPT_MONITOR", EnemyType::VOL_OPT_MONITOR}, + {"VOL_OPT_PILLAR", EnemyType::VOL_OPT_PILLAR}, {"YOWIE", EnemyType::YOWIE}, {"YOWIE_ALT", EnemyType::YOWIE_ALT}, {"ZE_BOOTA", EnemyType::ZE_BOOTA}, @@ -399,8 +429,6 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type) return 0x0E; case EnemyType::DE_ROL_LE: return 0x0F; - case EnemyType::DEATH_GUNNER: - throw runtime_error("DEATH_GUNNER entry is not specified"); case EnemyType::DRAGON: return 0x12; case EnemyType::SINOW_GOLD: @@ -413,8 +441,6 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type) return 0x1A; case EnemyType::DUBCHIC: return 0x1B; - case EnemyType::DUBWITCH: - throw runtime_error("no battle params for DUBWITCH"); case EnemyType::GILLCHIC: return 0x1C; case EnemyType::GARANZ: @@ -425,8 +451,6 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type) return 0x1F; case EnemyType::CLAW: return 0x20; - case EnemyType::VOL_OPT_1: - throw runtime_error("no battle params for VOL_OPT_1"); case EnemyType::VOL_OPT_2: return 0x25; case EnemyType::POUILLY_SLIME: @@ -476,7 +500,7 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type) case EnemyType::SO_DIMENIAN: return 0x55; default: - throw runtime_error("incorrect enemy type for Episode 1"); + throw runtime_error(string_printf("%s does not have battle parameters in Episode 1", name_for_enum(enemy_type))); } break; case Episode::EP2: @@ -520,8 +544,6 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type) return 0x1A; case EnemyType::DUBCHIC: return 0x1B; - case EnemyType::DUBWITCH: - throw runtime_error("no battle params for DUBWITCH"); case EnemyType::GILLCHIC: return 0x1C; case EnemyType::GARANZ: @@ -593,7 +615,7 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type) case EnemyType::SO_DIMENIAN: return 0x55; default: - throw runtime_error("incorrect enemy type for Episode 2"); + throw runtime_error(string_printf("%s does not have battle parameters in Episode 2", name_for_enum(enemy_type))); } break; case Episode::EP4: @@ -651,7 +673,7 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type) case EnemyType::KONDRIEU: return 0x22; default: - throw runtime_error("incorrect enemy type for Episode 4"); + throw runtime_error(string_printf("%s does not have battle parameters in Episode 4", name_for_enum(enemy_type))); } break; default: @@ -691,21 +713,14 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) { return 0x26; case EnemyType::DARK_BELRA: return 0x25; - case EnemyType::DARK_FALZ_1: - throw runtime_error("DARK_FALZ_1 does not have a rare table entry"); case EnemyType::DARK_FALZ_2: return 0x2F; case EnemyType::DARK_FALZ_3: return 0x2F; case EnemyType::DARK_GUNNER: return 0x22; - case EnemyType::DARVANT: - case EnemyType::DARVANT_ULTIMATE: - throw runtime_error("DARVANT and DARVANT_ULTIMATE do not have rare table entries"); case EnemyType::DE_ROL_LE: return 0x2D; - case EnemyType::DEATH_GUNNER: - throw runtime_error("DEATH_GUNNER does not have a rare table entry"); case EnemyType::DEL_LILY: return 0x53; case EnemyType::DEL_RAPPY: @@ -731,16 +746,12 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) { return 0x2C; case EnemyType::DUBCHIC: return 0x18; - case EnemyType::DUBWITCH: - throw runtime_error("DUBWITCH does not have a rare table entry"); case EnemyType::EGG_RAPPY: return 0x51; case EnemyType::EPSILON: return 0x54; case EnemyType::EVIL_SHARK: return 0x10; - case EnemyType::GAEL: - throw runtime_error("GAEL does not have a rare table entry"); case EnemyType::GAL_GRYPHON: return 0x4D; case EnemyType::GARANZ: @@ -811,8 +822,6 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) { return 0x0F; case EnemyType::NAR_LILY: return 0x0E; - case EnemyType::OLGA_FLOW_1: - throw runtime_error("OLGA_FLOW_1 does not have a rare table entry"); case EnemyType::OLGA_FLOW_2: return 0x4E; case EnemyType::PAL_SHARK: @@ -866,8 +875,6 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) { return 0x2B; case EnemyType::UL_GIBBON: return 0x3B; - case EnemyType::VOL_OPT_1: - throw runtime_error("VOL_OPT_1 does not have a rare table entry"); case EnemyType::VOL_OPT_2: return 0x2E; case EnemyType::YOWIE: @@ -881,6 +888,6 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) { case EnemyType::ZU_ALT: return 0x08; default: - throw logic_error("invalid enemy type"); + throw runtime_error(string_printf("%s does not have a rare table entry", name_for_enum(enemy_type))); } } diff --git a/src/EnemyType.hh b/src/EnemyType.hh index 4ea5f08d..7eae91da 100644 --- a/src/EnemyType.hh +++ b/src/EnemyType.hh @@ -14,6 +14,8 @@ enum class EnemyType { BA_BOOTA, BARBA_RAY, BARBAROUS_WOLF, + BEE_L, + BEE_R, BOOMA, BOOTA, BULCLAW, @@ -31,6 +33,8 @@ enum class EnemyType { DARVANT, DARVANT_ULTIMATE, DE_ROL_LE, + DE_ROL_LE_BODY, + DE_ROL_LE_MINE, DEATH_GUNNER, DEL_LILY, DEL_RAPPY, @@ -47,6 +51,7 @@ enum class EnemyType { DUBCHIC, DUBWITCH, // Has no entry in battle params EGG_RAPPY, + EPSIGUARD, EPSILON, EVIL_SHARK, GAEL, @@ -91,6 +96,7 @@ enum class EnemyType { PAN_ARMS, PAZUZU, PAZUZU_ALT, + PIG_RAY, POFUILLY_SLIME, POUILLY_SLIME, POISON_LILY, @@ -116,6 +122,10 @@ enum class EnemyType { UL_GIBBON, VOL_OPT_1, VOL_OPT_2, + VOL_OPT_AMP, + VOL_OPT_CORE, + VOL_OPT_MONITOR, + VOL_OPT_PILLAR, YOWIE, YOWIE_ALT, ZE_BOOTA, diff --git a/src/ItemCreator.cc b/src/ItemCreator.cc index 39247e1b..e73bc10d 100644 --- a/src/ItemCreator.cc +++ b/src/ItemCreator.cc @@ -235,11 +235,11 @@ ItemData ItemCreator::check_rare_specs_and_create_rare_box_item( for (const auto& spec : rare_specs) { item = this->check_rate_and_create_rare_item(spec); if (!item.empty()) { - this->log.info("Box spec %08" PRIX32 " => %02hhX%02hhX%02hhX produced an item", + this->log.info("Box spec %08" PRIX32 " produced item %02hhX%02hhX%02hhX", spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]); break; } - this->log.info("Box spec %08" PRIX32 " => %02hhX%02hhX%02hhX did not produce", + this->log.info("Box spec %08" PRIX32 " did not produce item %02hhX%02hhX%02hhX", spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]); } return item; @@ -289,11 +289,11 @@ ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item( for (const auto& spec : rare_specs) { item = this->check_rate_and_create_rare_item(spec); if (!item.empty()) { - this->log.info("Enemy spec %08" PRIX32 " => %02hhX%02hhX%02hhX did not produce", + this->log.info("Enemy spec %08" PRIX32 " produced item %02hhX%02hhX%02hhX", spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]); break; } - this->log.info("Enemy spec %08" PRIX32 " => %02hhX%02hhX%02hhX did not produce", + this->log.info("Enemy spec %08" PRIX32 " did not produce item %02hhX%02hhX%02hhX", spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]); } } diff --git a/src/Map.cc b/src/Map.cc index 70c79a30..623aa9bb 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -9,25 +9,23 @@ using namespace std; -uint64_t Map::Enemy::next_enemy_id = 1; - Map::Enemy::Enemy(EnemyType type) - : id(Map::Enemy::next_enemy_id++), - type(type), + : type(type), flags(0), last_hit_by_client_id(0) {} string Map::Enemy::str() const { - return string_printf("[Map::Enemy E-%" PRIX64 " type=%s flags=%02hhX last_hit_by_client_id=%hu]", - this->id, name_for_enum(this->type), this->flags, this->last_hit_by_client_id); + return string_printf("[Map::Enemy %s flags=%02hhX last_hit_by_client_id=%hu]", + name_for_enum(this->type), this->flags, this->last_hit_by_client_id); } struct EnemyEntry { - /* 00 */ le_uint32_t base; - /* 04 */ le_uint16_t unknown_a1; + /* 00 */ le_uint16_t base_type; + /* 02 */ le_uint16_t unknown_a0; // Overwritten by client at load time + /* 04 */ le_uint16_t enemy_index; // Overwritten by client at load time /* 06 */ le_uint16_t num_clones; /* 08 */ le_uint16_t area; - /* 0A */ le_uint16_t unknown_a2; + /* 0A */ le_uint16_t entity_id; // == enemy_index + 0x1000 /* 0C */ le_uint16_t section; /* 0E */ le_uint16_t wave_number; /* 10 */ le_uint32_t wave_number2; @@ -47,9 +45,9 @@ struct EnemyEntry { /* 48 */ string str() const { - return string_printf("EnemyEntry(base=%" PRIX32 ", a1=%hX, num_clones=%hX, area=%hX, a2=%hX, section=%hX, wave_number=%hX, wave_number2=%" PRIX32 ", x=%g, y=%g, z=%g, x_angle=%" PRIX32 ", y_angle=%" PRIX32 ", z_angle=%" PRIX32 ", a3=%" PRIX32 ", a4=%" PRIX32 ", a5=%" PRIX32 ", a6=%" PRIX32 ", a7=%" PRIX32 ", skin=%" PRIX32 ", a8=%" PRIX32 ")", - this->base.load(), this->unknown_a1.load(), this->num_clones.load(), this->area.load(), - this->unknown_a2.load(), this->section.load(), this->wave_number.load(), + return string_printf("EnemyEntry(base_type=%hX, a0=%hX, enemy_index=%hX, num_clones=%hX, area=%hX, entity_id=%hX, section=%hX, wave_number=%hX, wave_number2=%" PRIX32 ", x=%g, y=%g, z=%g, x_angle=%" PRIX32 ", y_angle=%" PRIX32 ", z_angle=%" PRIX32 ", a3=%" PRIX32 ", a4=%" PRIX32 ", a5=%" PRIX32 ", a6=%" PRIX32 ", a7=%" PRIX32 ", skin=%" PRIX32 ", a8=%" PRIX32 ")", + this->base_type.load(), this->unknown_a0.load(), this->enemy_index.load(), this->num_clones.load(), this->area.load(), + this->entity_id.load(), this->section.load(), this->wave_number.load(), this->wave_number2.load(), this->x.load(), this->y.load(), this->z.load(), this->x_angle.load(), this->y_angle.load(), this->z_angle.load(), this->unknown_a3.load(), this->unknown_a4.load(), this->unknown_a5.load(), this->unknown_a6.load(), this->unknown_a7.load(), this->skin.load(), @@ -58,7 +56,7 @@ struct EnemyEntry { } __attribute__((packed)); struct ObjectEntry { - /* 00 */ le_uint16_t type; + /* 00 */ le_uint16_t base_type; /* 02 */ le_uint16_t unknown_a1; /* 04 */ le_uint32_t unknown_a2; /* 08 */ le_uint16_t id; @@ -81,8 +79,8 @@ struct ObjectEntry { /* 44 */ string str() const { - return string_printf("ObjectEntry(type=%hX, a1=%hX, a2=%" PRIX32 ", id=%hX, group=%hX, section=%hX, a3=%hX, x=%g, y=%g, z=%g, x_angle=%" PRIX32 ", y_angle=%" PRIX32 ", z_angle=%" PRIX32 ", a3=%" PRIX32 ", a4=%" PRIX32 ", a5=%" PRIX32 ", a6=%" PRIX32 ", a7=%" PRIX32 ", a8=%" PRIX32 ", a9=%" PRIX32 ")", - this->type.load(), this->unknown_a1.load(), this->unknown_a2.load(), this->id.load(), this->group.load(), + return string_printf("ObjectEntry(base_type=%hX, a1=%hX, a2=%" PRIX32 ", id=%hX, group=%hX, section=%hX, a3=%hX, x=%g, y=%g, z=%g, x_angle=%" PRIX32 ", y_angle=%" PRIX32 ", z_angle=%" PRIX32 ", a3=%" PRIX32 ", a4=%" PRIX32 ", a5=%" PRIX32 ", a6=%" PRIX32 ", a7=%" PRIX32 ", a8=%" PRIX32 ", a9=%" PRIX32 ")", + this->base_type.load(), this->unknown_a1.load(), this->unknown_a2.load(), this->id.load(), this->group.load(), this->section.load(), this->unknown_a3.load(), this->x.load(), this->y.load(), this->z.load(), this->x_angle.load(), this->y_angle.load(), this->z_angle.load(), this->unknown_a3.load(), this->unknown_a4.load(), this->unknown_a5.load(), this->unknown_a6.load(), this->unknown_a7.load(), this->unknown_a8.load(), @@ -103,16 +101,13 @@ void Map::add_enemies_from_map_data( throw runtime_error("data size is not a multiple of entry size"); } - auto create_clones = [&](size_t count) { - for (; count > 0; count--) { - this->enemies.emplace_back(EnemyType::NONE); - } - }; - for (size_t y = 0; y < entry_count; y++) { const auto& e = map[y]; - switch (e.base) { + string hex = format_data_string(&e, sizeof(e)); + fprintf(stderr, "[%04zX] %s\n", y, hex.c_str()); + + switch (e.base_type) { case 0x40: enemies.emplace_back((e.skin & 0x01) ? EnemyType::HILDEBLUE : EnemyType::HILDEBEAR); break; @@ -123,18 +118,22 @@ void Map::add_enemies_from_map_data( enemies.emplace_back(is_rare ? EnemyType::AL_RAPPY : EnemyType::RAG_RAPPY); break; case Episode::EP2: - switch (event) { - case 0x01: - enemies.emplace_back(EnemyType::SAINT_RAPPY); - break; - case 0x04: - enemies.emplace_back(EnemyType::EGG_RAPPY); - break; - case 0x05: - enemies.emplace_back(EnemyType::HALLO_RAPPY); - break; - default: - enemies.emplace_back(EnemyType::LOVE_RAPPY); + if (is_rare) { + switch (event) { + case 0x01: + enemies.emplace_back(EnemyType::SAINT_RAPPY); + break; + case 0x04: + enemies.emplace_back(EnemyType::EGG_RAPPY); + break; + case 0x05: + enemies.emplace_back(EnemyType::HALLO_RAPPY); + break; + default: + enemies.emplace_back(EnemyType::LOVE_RAPPY); + } + } else { + enemies.emplace_back(EnemyType::RAG_RAPPY); } break; case Episode::EP4: @@ -149,14 +148,15 @@ void Map::add_enemies_from_map_data( } break; } - case 0x42: + case 0x42: { enemies.emplace_back(EnemyType::MONEST); - for (size_t x = 0; x < e.num_clones; x++) { - enemies.emplace_back((x < 30) ? EnemyType::MOTHMANT : EnemyType::UNKNOWN); + for (size_t x = 0; x < 30; x++) { + enemies.emplace_back(EnemyType::MOTHMANT); } break; + } case 0x43: { - enemies.emplace_back((e.unknown_a4 & 0x800000) ? EnemyType::BARBAROUS_WOLF : EnemyType::SAVAGE_WOLF); + enemies.emplace_back(e.unknown_a4 ? EnemyType::BARBAROUS_WOLF : EnemyType::SAVAGE_WOLF); break; } case 0x44: @@ -170,7 +170,7 @@ void Map::add_enemies_from_map_data( if ((episode == Episode::EP2) && (e.area > 0x0F)) { enemies.emplace_back(EnemyType::DEL_LILY); } else { - enemies.emplace_back((e.unknown_a4 & 0x800000) ? EnemyType::NAR_LILY : EnemyType::POISON_LILY); + enemies.emplace_back((e.skin & 1) ? EnemyType::NAR_LILY : EnemyType::POISON_LILY); } break; case 0x62: @@ -182,9 +182,9 @@ void Map::add_enemies_from_map_data( break; } case 0x64: { - bool is_common = ((e.unknown_a4 & 0x800000) ? true : false); + bool is_rare = (e.skin & 1); for (size_t x = 0; x < 5; x++) { // Main slime + 4 clones - enemies.emplace_back(is_common ? EnemyType::POFUILLY_SLIME : EnemyType::POUILLY_SLIME); + enemies.emplace_back(is_rare ? EnemyType::POFUILLY_SLIME : EnemyType::POUILLY_SLIME); } break; } @@ -199,19 +199,21 @@ void Map::add_enemies_from_map_data( case 0x81: enemies.emplace_back(EnemyType::GARANZ); break; - case 0x82: - enemies.emplace_back((e.unknown_a4 & 0x800000) ? EnemyType::SINOW_GOLD : EnemyType::SINOW_BEAT); - if (e.num_clones == 0) { - create_clones(4); + case 0x82: { + EnemyType type = e.unknown_a4 ? EnemyType::SINOW_GOLD : EnemyType::SINOW_BEAT; + size_t count = (e.num_clones == 0) ? 5 : (e.num_clones + 1); + for (size_t z = 0; z < count; z++) { + enemies.emplace_back(type); } break; + } case 0x83: enemies.emplace_back(EnemyType::CANADINE); break; case 0x84: enemies.emplace_back(EnemyType::CANANE); for (size_t x = 0; x < 8; x++) { - enemies.emplace_back(EnemyType::CANADINE); + enemies.emplace_back(EnemyType::CANADINE_GROUP); } break; case 0x85: @@ -222,7 +224,8 @@ void Map::add_enemies_from_map_data( break; case 0xA1: enemies.emplace_back(EnemyType::CHAOS_SORCERER); - create_clones(2); + enemies.emplace_back(EnemyType::BEE_R); + enemies.emplace_back(EnemyType::BEE_L); break; case 0xA2: enemies.emplace_back(EnemyType::DARK_GUNNER); @@ -261,9 +264,27 @@ void Map::add_enemies_from_map_data( break; case 0xC1: enemies.emplace_back(EnemyType::DE_ROL_LE); + for (size_t z = 0; z < 0x0A; z++) { + enemies.emplace_back(EnemyType::DE_ROL_LE_BODY); + } + for (size_t z = 0; z < 0x09; z++) { + enemies.emplace_back(EnemyType::DE_ROL_LE_MINE); + } break; case 0xC2: enemies.emplace_back(EnemyType::VOL_OPT_1); + for (size_t z = 0; z < 6; z++) { + enemies.emplace_back(EnemyType::VOL_OPT_PILLAR); + } + for (size_t z = 0; z < 24; z++) { + enemies.emplace_back(EnemyType::VOL_OPT_MONITOR); + } + for (size_t z = 0; z < 2; z++) { + enemies.emplace_back(EnemyType::NONE); + } + enemies.emplace_back(EnemyType::VOL_OPT_AMP); + enemies.emplace_back(EnemyType::VOL_OPT_CORE); + enemies.emplace_back(EnemyType::NONE); break; case 0xC5: enemies.emplace_back(EnemyType::VOL_OPT_2); @@ -274,26 +295,36 @@ void Map::add_enemies_from_map_data( } else { enemies.emplace_back(EnemyType::DARK_FALZ_2); } - for (size_t x = 0; x < 510; x++) { + for (size_t x = 0; x < 0x1FD; x++) { enemies.emplace_back(difficulty == 3 ? EnemyType::DARVANT_ULTIMATE : EnemyType::DARVANT); } + enemies.emplace_back(EnemyType::DARK_FALZ_3); + enemies.emplace_back(EnemyType::DARK_FALZ_2); + enemies.emplace_back(EnemyType::DARK_FALZ_1); break; case 0xCA: - enemies.emplace_back(EnemyType::OLGA_FLOW_2); - create_clones(0x200); + for (size_t z = 0; z < 0x201; z++) { + enemies.emplace_back(EnemyType::OLGA_FLOW_2); + } break; case 0xCB: enemies.emplace_back(EnemyType::BARBA_RAY); - create_clones(0x2F); + for (size_t z = 0; z < 0x2F; z++) { + enemies.emplace_back(EnemyType::PIG_RAY); + } break; case 0xCC: - enemies.emplace_back(EnemyType::GOL_DRAGON); - create_clones(5); + for (size_t z = 0; z < 6; z++) { + enemies.emplace_back(EnemyType::GOL_DRAGON); + } break; - case 0xD4: - enemies.emplace_back((e.unknown_a4 & 0x800000) ? EnemyType::SINOW_SPIGELL : EnemyType::SINOW_BERILL); - create_clones(4); + case 0xD4: { + EnemyType type = (e.skin & 1) ? EnemyType::SINOW_SPIGELL : EnemyType::SINOW_BERILL; + for (size_t z = 0; z < 5; z++) { + enemies.emplace_back(type); + } break; + } case 0xD5: enemies.emplace_back((e.skin & 0x01) ? EnemyType::MERILTAS : EnemyType::MERILLIA); break; @@ -337,7 +368,9 @@ void Map::add_enemies_from_map_data( case 0xE0: if ((episode == Episode::EP2) && (e.area > 0x0F)) { enemies.emplace_back(EnemyType::EPSILON); - create_clones(4); + for (size_t z = 0; z < 4; z++) { + enemies.emplace_back(EnemyType::EPSIGUARD); + } } else { enemies.emplace_back((e.skin & 0x01) ? EnemyType::SINOW_ZELE : EnemyType::SINOW_ZOA); } @@ -350,9 +383,9 @@ void Map::add_enemies_from_map_data( break; case 0x0111: if (e.area > 0x05) { - enemies.emplace_back((e.unknown_a4 & 0x800000) ? EnemyType::YOWIE_ALT : EnemyType::SATELLITE_LIZARD_ALT); + enemies.emplace_back(e.unknown_a4 ? EnemyType::YOWIE_ALT : EnemyType::SATELLITE_LIZARD_ALT); } else { - enemies.emplace_back((e.unknown_a4 & 0x800000) ? EnemyType::YOWIE : EnemyType::SATELLITE_LIZARD); + enemies.emplace_back(e.unknown_a4 ? EnemyType::YOWIE : EnemyType::SATELLITE_LIZARD); } break; case 0x0112: @@ -363,9 +396,9 @@ void Map::add_enemies_from_map_data( break; case 0x0114: if (e.area > 0x05) { - enemies.emplace_back((e.unknown_a4 & 0x800000) ? EnemyType::PAZUZU_ALT : EnemyType::ZU_ALT); + enemies.emplace_back((e.skin & 1) ? EnemyType::PAZUZU_ALT : EnemyType::ZU_ALT); } else { - enemies.emplace_back((e.unknown_a4 & 0x800000) ? EnemyType::PAZUZU : EnemyType::ZU); + enemies.emplace_back((e.skin & 1) ? EnemyType::PAZUZU : EnemyType::ZU); } break; case 0x0115: @@ -384,20 +417,21 @@ void Map::add_enemies_from_map_data( break; } case 0x0119: // Saint-Million, Shambertin, Kondrieu - if (e.unknown_a4 & 0x800000) { + if (e.unknown_a4) { enemies.emplace_back(EnemyType::KONDRIEU); } else { enemies.emplace_back((e.skin & 1) ? EnemyType::SHAMBERTIN : EnemyType::SAINT_MILLION); } break; default: - enemies.emplace_back(EnemyType::UNKNOWN); + for (size_t z = 0; z < e.num_clones + 1; z++) { + enemies.emplace_back(EnemyType::UNKNOWN); + } static_game_data_log.warning( - "(Entry %zu, offset %zX in file) Unknown enemy type %08" PRIX32 " %08" PRIX32, - y, y * sizeof(EnemyEntry), e.base.load(), e.skin.load()); + "(Entry %zu, offset %zX in file) Unknown enemy type %04hX", + y, y * sizeof(EnemyEntry), e.base_type.load()); break; } - create_clones(e.num_clones); } } diff --git a/src/Map.hh b/src/Map.hh index fbe7af3d..91a65610 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -15,8 +15,6 @@ struct Map { struct Enemy { - static uint64_t next_enemy_id; - enum Flag { HIT_BY_PLAYER0 = 0x01, HIT_BY_PLAYER1 = 0x02, @@ -25,7 +23,6 @@ struct Map { DEFEATED = 0x10, ITEM_DROPPED = 0x20, }; - uint64_t id; EnemyType type; uint8_t flags; uint8_t last_hit_by_client_id; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 2f1e2a79..3f245eac 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1237,10 +1237,10 @@ static void on_enemy_killed_bb(shared_ptr s, auto& e = l->map->enemies[cmd.enemy_id]; string e_str = e.str(); - c->log.info("Enemy killed: entry %hu => %s", cmd.enemy_id.load(), e_str.c_str()); + c->log.info("Enemy killed: E-%hX => %s", cmd.enemy_id.load(), e_str.c_str()); if (e.flags & Map::Enemy::Flag::DEFEATED) { if (c->options.debug) { - send_text_message_printf(c, "$C5E-%hX (already defeated)", cmd.enemy_id.load()); + send_text_message_printf(c, "$C5E-%hX __DEFEATED__", cmd.enemy_id.load()); } return; } @@ -1250,7 +1250,7 @@ static void on_enemy_killed_bb(shared_ptr s, experience = s->battle_params->get(l->mode == GameMode::SOLO, l->episode, l->difficulty, e.type).experience; } catch (const exception& e) { if (c->options.debug) { - send_text_message_printf(c, "$C5E-%hX (missing definition)\n%s", cmd.enemy_id.load(), e.what()); + send_text_message_printf(c, "$C5E-%hX __MISSING__\n%s", cmd.enemy_id.load(), e.what()); } else { send_text_message_printf(c, "$C4Unknown enemy type killed:\n%s", e.what()); } @@ -1279,7 +1279,7 @@ static void on_enemy_killed_bb(shared_ptr s, other_c->game_data.player()->disp.experience += player_exp; send_give_experience(l, other_c, player_exp); if (other_c->options.debug) { - send_text_message_printf(other_c, "$C5+%" PRIu32 " E-%hX (%s)", + send_text_message_printf(other_c, "$C5+%" PRIu32 " E-%hX %s", player_exp, cmd.enemy_id.load(), name_for_enum(e.type)); }