diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index a95ee3d2..48f7bd41 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -4393,7 +4393,8 @@ struct G_StandardDropItemRequest_DC_6x60 { } __packed__; struct G_StandardDropItemRequest_PC_V3_BB_6x60 : G_StandardDropItemRequest_DC_6x60 { - le_float unknown_a2 = 0.0f; + uint8_t effective_area = 0; + parray unused; } __packed__; // 6x61: Activate MAG effect diff --git a/src/Map.cc b/src/Map.cc index 76dc9cf8..13c73052 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -71,9 +71,9 @@ struct ObjectEntry { /* 1C */ le_uint32_t x_angle; /* 20 */ le_uint32_t y_angle; /* 24 */ le_uint32_t z_angle; - /* 28 */ le_uint32_t unknown_a4; - /* 2C */ le_uint32_t unknown_a5; - /* 30 */ le_uint32_t unknown_a6; + /* 28 */ le_float unknown_a4; + /* 2C */ le_float unknown_a5; + /* 30 */ le_float unknown_a6; /* 34 */ le_uint32_t unknown_a7; /* 38 */ le_uint32_t unknown_a8; /* 3C */ le_uint32_t unknown_a9; @@ -81,12 +81,11 @@ struct ObjectEntry { /* 44 */ string str() const { - 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 ")", + 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 ", a4=%g, a5=%g, a6=%g, a7=%" PRIX32 ", a8=%" PRIX32 ", a9=%" 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(), - this->unknown_a9.load()); + this->y_angle.load(), this->z_angle.load(), this->unknown_a4.load(), this->unknown_a5.load(), this->unknown_a6.load(), + this->unknown_a7.load(), this->unknown_a8.load(), this->unknown_a9.load(), this->unknown_a9.load()); } } __attribute__((packed)); @@ -95,6 +94,20 @@ void Map::clear() { this->rare_enemy_indexes.clear(); } +void Map::add_objects_from_map_data(const void* data, size_t size) { + const auto* map = reinterpret_cast(data); + size_t entry_count = size / sizeof(ObjectEntry); + if (size != entry_count * sizeof(ObjectEntry)) { + throw runtime_error("data size is not a multiple of entry size"); + } + + for (size_t y = 0; y < entry_count; y++) { + const auto& e = map[y]; + string hex = format_data_string(&e, sizeof(e)); + fprintf(stderr, "[%04zX] %s\n", y, hex.c_str()); + } +} + void Map::add_enemies_from_map_data( Episode episode, uint8_t difficulty, @@ -487,7 +500,7 @@ struct DATSectionHeader { le_uint32_t data_size; } __attribute__((packed)); -void Map::add_enemies_from_quest_data( +void Map::add_enemies_and_objects_from_quest_data( Episode episode, uint8_t difficulty, uint8_t event, @@ -502,11 +515,19 @@ void Map::add_enemies_from_quest_data( if (header.section_size < sizeof(header)) { throw runtime_error(string_printf("quest layout has invalid section header at offset 0x%zX", r.where() - sizeof(header))); } - if (header.type == 2) { + + if (header.type == 1) { + if (header.data_size % sizeof(ObjectEntry)) { + throw runtime_error("quest layout object section size is not a multiple of object entry size"); + } + this->add_objects_from_map_data(r.getv(header.data_size), header.data_size); + + } else if (header.type == 2) { if (header.data_size % sizeof(EnemyEntry)) { throw runtime_error("quest layout enemy section size is not a multiple of enemy entry size"); } this->add_enemies_from_map_data(episode, difficulty, event, r.getv(header.data_size), header.data_size); + } else { r.skip(header.section_size - sizeof(header)); } @@ -756,7 +777,7 @@ void generate_variations( } vector map_filenames_for_variation( - Episode episode, bool is_solo, uint8_t area, uint32_t var1, uint32_t var2) { + Episode episode, bool is_solo, uint8_t area, uint32_t var1, uint32_t var2, bool is_enemies) { // Map filenames are like map_[_VV][_VV][_off][_s].dat // name_token comes from AreaMapFileIndex // _VV are the values from the variation<1|2>_values vector (in contrast to @@ -785,13 +806,21 @@ vector map_filenames_for_variation( filename += string_printf("_%02" PRIX32, a->variation2_values.at(var2)); } + // Try both _off.dat and _s.dat suffixes first before falling back + // to non-solo version vector ret; - if (is_solo) { - // Try both _offe.dat and e_s.dat suffixes first before falling back to - // non-solo version - ret.emplace_back(filename + "_offe.dat"); - ret.emplace_back(filename + "e_s.dat"); + if (is_enemies) { + if (is_solo) { + ret.emplace_back(filename + "_offe.dat"); + ret.emplace_back(filename + "e_s.dat"); + } + ret.emplace_back(filename + "e.dat"); + } else { + if (is_solo) { + ret.emplace_back(filename + "_offo.dat"); + ret.emplace_back(filename + "o_s.dat"); + } + ret.emplace_back(filename + "o.dat"); } - ret.emplace_back(filename + "e.dat"); return ret; } diff --git a/src/Map.hh b/src/Map.hh index 73a16fea..976577a8 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -47,6 +47,7 @@ struct Map { std::vector rare_enemy_indexes; void clear(); + void add_objects_from_map_data(const void* data, size_t size); void add_enemies_from_map_data( Episode episode, uint8_t difficulty, @@ -54,7 +55,7 @@ struct Map { const void* data, size_t size, const RareEnemyRates* rare_rates = nullptr); - void add_enemies_from_quest_data( + void add_enemies_and_objects_from_quest_data( Episode episode, uint8_t difficulty, uint8_t event, @@ -97,5 +98,5 @@ void generate_variations( Episode episode, bool is_solo); std::vector map_filenames_for_variation( - Episode episode, bool is_solo, uint8_t area, uint32_t var1, uint32_t var2); + Episode episode, bool is_solo, uint8_t area, uint32_t var1, uint32_t var2, bool is_enemies); void load_map_files(); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index ce98ef49..090f20e3 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -2516,7 +2516,7 @@ static void on_AC_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) auto vq = l->quest->version(QuestScriptVersion::BB_V4, c->language()); auto dat_contents = prs_decompress(*vq->dat_contents); l->map->clear(); - l->map->add_enemies_from_quest_data(l->episode, l->difficulty, l->event, dat_contents.data(), dat_contents.size()); + l->map->add_enemies_and_objects_from_quest_data(l->episode, l->difficulty, l->event, dat_contents.data(), dat_contents.size()); c->log.info("Replaced enemies list with quest layout (%zu entries)", l->map->enemies.size()); for (size_t z = 0; z < l->map->enemies.size(); z++) { @@ -3472,43 +3472,44 @@ shared_ptr create_game_generic( for (size_t area = 0; area < 0x10; area++) { c->log.info("[Map/%zu] Using variations %" PRIX32 ", %" PRIX32, area, game->variations[area * 2].load(), game->variations[area * 2 + 1].load()); - auto filenames = map_filenames_for_variation( + + auto enemy_filenames = map_filenames_for_variation( game->episode, is_solo, area, game->variations[area * 2], - game->variations[area * 2 + 1]); - - if (filenames.empty()) { - c->log.info("[Map/%zu] No file to load", area); - continue; - } - bool any_map_loaded = false; - for (const string& filename : filenames) { - try { - auto map_data = s->load_bb_file(filename, "", "map/" + filename); - size_t start_offset = game->map->enemies.size(); - game->map->add_enemies_from_map_data( - game->episode, game->difficulty, game->event, map_data->data(), map_data->size()); - size_t entries_loaded = game->map->enemies.size() - start_offset; - c->log.info("[Map/%zu] Loaded %s (%zu entries)", - area, filename.c_str(), entries_loaded); - for (size_t z = start_offset; z < game->map->enemies.size(); z++) { - string e_str = game->map->enemies[z].str(); - static_game_data_log.info("(Entry %zX) %s", z, e_str.c_str()); + game->variations[area * 2 + 1], + true); + if (enemy_filenames.empty()) { + c->log.info("[Map/%zu:e] No file to load", area); + } else { + bool any_map_loaded = false; + for (const string& filename : enemy_filenames) { + try { + auto map_data = s->load_bb_file(filename, "", "map/" + filename); + size_t start_offset = game->map->enemies.size(); + game->map->add_enemies_from_map_data( + game->episode, game->difficulty, game->event, map_data->data(), map_data->size()); + size_t entries_loaded = game->map->enemies.size() - start_offset; + c->log.info("[Map/%zu:e] Loaded %s (%zu entries)", + area, filename.c_str(), entries_loaded); + for (size_t z = start_offset; z < game->map->enemies.size(); z++) { + string e_str = game->map->enemies[z].str(); + static_game_data_log.info("(Entry %zX) %s", z, e_str.c_str()); + } + any_map_loaded = true; + break; + } catch (const exception& e) { + c->log.info("[Map/%zu:e] Failed to load %s: %s", area, filename.c_str(), e.what()); } - any_map_loaded = true; - break; - } catch (const exception& e) { - c->log.info("[Map/%zu] Failed to load %s: %s", area, filename.c_str(), e.what()); } - } - if (!any_map_loaded) { - throw runtime_error(string_printf("no maps loaded for area %zu", area)); + if (!any_map_loaded) { + throw runtime_error(string_printf("no enemy maps loaded for area %zu", area)); + } } } - c->log.info("Loaded maps contain %zu entries overall (%zu as rares)", + c->log.info("Loaded maps contain %zu enemy entries overall (%zu as rares)", game->map->enemies.size(), game->map->rare_enemy_indexes.size()); } return game; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 2c6f86cd..e9c2db4f 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1320,12 +1320,12 @@ static void on_entity_drop_item_request(shared_ptr c, uint8_t command, u if (size == sizeof(G_SpecializableItemDropRequest_6xA2)) { cmd = check_size_t(data, size); if (cmd.header.subcommand != 0xA2) { - throw runtime_error("item drop request has specializable size but non-specializable command"); + throw runtime_error("item drop request has incorrect subcommand"); } - } else { - const auto& in_cmd = check_size_t(data, size, 0xFFFF); + } else if (size == sizeof(G_StandardDropItemRequest_PC_V3_BB_6x60)) { + const auto& in_cmd = check_size_t(data, size); if (in_cmd.header.subcommand != 0x60) { - throw runtime_error("item drop request has non-specializable size but specializable command"); + throw runtime_error("item drop request has incorrect subcommand"); } cmd.entity_id = in_cmd.entity_id; cmd.area = in_cmd.area; @@ -1333,12 +1333,25 @@ static void on_entity_drop_item_request(shared_ptr c, uint8_t command, u cmd.x = in_cmd.x; cmd.z = in_cmd.z; cmd.ignore_def = true; + cmd.effective_area = in_cmd.effective_area; + } else { + const auto& in_cmd = check_size_t(data, size); + if (in_cmd.header.subcommand != 0x60) { + throw runtime_error("item drop request has incorrect subcommand"); + } + cmd.entity_id = in_cmd.entity_id; + cmd.area = in_cmd.area; + cmd.rt_index = in_cmd.rt_index; + cmd.x = in_cmd.x; + cmd.z = in_cmd.z; + cmd.ignore_def = true; + cmd.effective_area = in_cmd.area; } ItemData item; if (cmd.rt_index == 0x30) { if (cmd.ignore_def) { - item = l->item_creator->on_box_item_drop(cmd.entity_id, cmd.area); + item = l->item_creator->on_box_item_drop(cmd.entity_id, cmd.effective_area); } else { item = l->item_creator->on_specialized_box_item_drop(cmd.entity_id, cmd.def[0], cmd.def[1], cmd.def[2]); } @@ -1351,7 +1364,7 @@ static void on_entity_drop_item_request(shared_ptr c, uint8_t command, u cmd.rt_index, expected_rt_index); } } - item = l->item_creator->on_monster_item_drop(cmd.entity_id, cmd.rt_index, cmd.area); + item = l->item_creator->on_monster_item_drop(cmd.entity_id, cmd.rt_index, cmd.effective_area); } item.id = l->generate_item_id(0xFF); @@ -1429,8 +1442,8 @@ static void on_set_quest_flag(shared_ptr c, uint8_t command, uint8_t fla 2, 0, }, - 0xE0AEDC01, - }; + 0x01, + {}}; send_command_t(c, 0x62, l->leader_id, req); } }