diff --git a/src/Map.cc b/src/Map.cc index a12558b6..f991e57d 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -3622,8 +3622,13 @@ string MapFile::Event2Entry::str() const { this->action_stream_offset); } -string MapFile::RandomEnemyLocationEntry::str() const { - return std::format("[RandomEnemyLocationEntry x={:g} y={:g} z={:g} x_angle={:08X} y_angle={:08X} z_angle={:08X}a9={:04X} a10={:04X}]", +string MapFile::RandomEnemyLocationSection::str() const { + return std::format("[RandomEnemyLocationSection room={:04X} count={:04X} offset={:08X} index={}]", + this->room, this->count, this->offset, this->offset / sizeof(RandomEnemyLocation)); +} + +string MapFile::RandomEnemyLocation::str() const { + return std::format("[RandomEnemyLocation x={:g} y={:g} z={:g} x_angle={:08X} y_angle={:08X} z_angle={:08X} a9={:04X} a10={:04X}]", this->pos.x, this->pos.y, this->pos.z, @@ -3649,8 +3654,14 @@ string MapFile::RandomEnemyDefinition::str() const { } string MapFile::RandomEnemyWeight::str() const { - return std::format("[RandomEnemyWeight base_type_index={:02X} def_entry_num={:02X} weight={:02X} a4={:02X}]", + string base_type_index_str; + try { + base_type_index_str = std::format("(->{:04X})", MapFile::RAND_ENEMY_BASE_TYPES.at(this->base_type_index)); + } catch (const std::out_of_range&) { + } + return std::format("[RandomEnemyWeight base_type_index={:02X}{} def_entry_num={} weight={:02X} a4={:02X}]", this->base_type_index, + base_type_index_str, this->def_entry_num, this->weight, this->unknown_a4); @@ -3723,6 +3734,12 @@ void MapFile::RandomState::generate_shuffled_location_table( } } +const array MapFile::RAND_ENEMY_BASE_TYPES = { + 0x44, 0x43, 0x41, 0x42, 0x40, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, + 0xA7, 0xA8, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, + 0xDE, 0xDF, 0xE0, 0xE0, 0xE1}; + MapFile::MapFile(std::shared_ptr data) { this->quest_data = data; this->link_data(data); @@ -3814,7 +3831,7 @@ void MapFile::set_enemy_sets_for_floor(uint8_t floor, size_t file_offset, const if (floor_sections.enemy_sets) { throw runtime_error("multiple enemy sets sections for same floor"); } - if (floor_sections.events2 || floor_sections.random_enemy_locations_data || floor_sections.random_enemy_definitions_data) { + if (floor_sections.events2 || floor_sections.random_enemy_definitions || floor_sections.random_enemy_locations) { throw runtime_error("floor already has random enemies and cannot also have fixed enemies"); } if (size % sizeof(EnemySetEntry)) { @@ -3870,6 +3887,25 @@ void MapFile::set_random_enemy_locations_for_floor(uint8_t floor, size_t file_of floor_sections.random_enemy_locations_file_offset = file_offset; floor_sections.random_enemy_locations_file_size = size; this->has_any_random_sections = true; + + phosg::StringReader r(data, size); + const auto& header = r.get(); + floor_sections.random_enemy_location_section_count = header.num_rooms; + floor_sections.random_enemy_location_sections = &r.pget( + header.room_table_offset, floor_sections.random_enemy_location_section_count * sizeof(RandomEnemyLocationSection)); + size_t max_offset = 0; + for (size_t z = 0; z < floor_sections.random_enemy_location_section_count; z++) { + const auto& sec = floor_sections.random_enemy_location_sections[z]; + if (sec.offset % sizeof(RandomEnemyLocation)) { + // TODO: We probably could support this if it's actually needed somewhere + throw std::runtime_error(std::format("random enemy location offset {:08X} is not a multiple of struct size {:08X}", + sec.offset, sizeof(RandomEnemyLocation))); + } + max_offset = std::max(max_offset, sec.offset + sec.count * sizeof(RandomEnemyLocation)); + } + floor_sections.random_enemy_location_count = max_offset / sizeof(RandomEnemyLocation); + floor_sections.random_enemy_locations = &r.pget( + header.entries_offset, floor_sections.random_enemy_location_count * sizeof(RandomEnemyLocation)); } void MapFile::set_random_enemy_definitions_for_floor(uint8_t floor, size_t file_offset, const void* data, size_t size) { @@ -3883,6 +3919,15 @@ void MapFile::set_random_enemy_definitions_for_floor(uint8_t floor, size_t file_ floor_sections.random_enemy_definitions_file_offset = file_offset; floor_sections.random_enemy_definitions_file_size = size; this->has_any_random_sections = true; + + phosg::StringReader r(data, size); + const auto& header = r.get(); + floor_sections.random_enemy_definition_count = header.entry_count; + floor_sections.random_enemy_definitions = &r.pget( + header.entries_offset, floor_sections.random_enemy_definition_count * sizeof(RandomEnemyDefinition)); + floor_sections.random_enemy_definition_weight_count = header.weight_entry_count; + floor_sections.random_enemy_definition_weights = &r.pget( + header.weight_entries_offset, floor_sections.random_enemy_definition_weight_count * sizeof(RandomEnemyWeight)); } std::shared_ptr MapFile::materialize_random_sections(uint32_t random_seed) const { @@ -3894,12 +3939,7 @@ std::shared_ptr MapFile::materialize_random_sections(uint32_t random_se return this->shared_from_this(); } - static const array rand_enemy_base_types = { - 0x44, 0x43, 0x41, 0x42, 0x40, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x80, - 0x81, 0x82, 0x83, 0x84, 0x85, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, - 0xA7, 0xA8, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, - 0xDE, 0xDF, 0xE0, 0xE0, 0xE1}; - + static_game_data_log.debug_f("Generating random enemies using seed {:08X}", random_seed); auto new_map = make_shared(random_seed); RandomState random_state(random_seed); @@ -3907,16 +3947,22 @@ std::shared_ptr MapFile::materialize_random_sections(uint32_t random_se const auto& this_sf = this->sections_for_floor[floor]; if (this_sf.object_sets) { + static_game_data_log.debug_f("(Floor {}) Using existing object sets", floor); new_map->set_object_sets_for_floor(floor, 0, this_sf.object_sets, this_sf.object_set_count * sizeof(ObjectSetEntry)); } if (this_sf.enemy_sets) { + static_game_data_log.debug_f("(Floor {}) Using existing enemy sets", floor); new_map->set_enemy_sets_for_floor(floor, 0, this_sf.enemy_sets, this_sf.enemy_set_count * sizeof(EnemySetEntry)); } if (this_sf.events1) { + static_game_data_log.debug_f("(Floor {}) Using existing events (format 1; fixed)", floor); new_map->set_events_for_floor(floor, 0, this_sf.events_data, this_sf.events_data_size, false); + } else if (this_sf.events2) { + static_game_data_log.debug_f("(Floor {}) Using existing events (format 2; random)", floor); + if (!this_sf.random_enemy_locations_data || !this_sf.random_enemy_definitions_data) { throw runtime_error("cannot materialize random enemies; evt2 section present but one or both random data sections are missing"); } @@ -3924,28 +3970,29 @@ std::shared_ptr MapFile::materialize_random_sections(uint32_t random_se throw runtime_error("cannot materialize random enemies; action stream is missing"); } - phosg::StringReader locations_sec_r( - this_sf.random_enemy_locations_data, this_sf.random_enemy_locations_data_size); - phosg::StringReader definitions_sec_r( - this_sf.random_enemy_definitions_data, this_sf.random_enemy_definitions_data_size); + phosg::StringReader locations_sec_r(this_sf.random_enemy_locations_data, this_sf.random_enemy_locations_data_size); + phosg::StringReader definitions_sec_r(this_sf.random_enemy_definitions_data, this_sf.random_enemy_definitions_data_size); const auto& locations_header = locations_sec_r.get(); const auto& definitions_header = definitions_sec_r.get(); auto definitions_r = definitions_sec_r.sub( - definitions_header.entries_offset, - definitions_header.entry_count * sizeof(RandomEnemyDefinition)); + definitions_header.entries_offset, definitions_header.entry_count * sizeof(RandomEnemyDefinition)); auto weights_r = definitions_sec_r.sub( - definitions_header.weight_entries_offset, - definitions_header.weight_entry_count * sizeof(RandomEnemyWeight)); + definitions_header.weight_entries_offset, definitions_header.weight_entry_count * sizeof(RandomEnemyWeight)); phosg::StringWriter enemy_sets_w; phosg::StringWriter events1_w; phosg::StringWriter action_stream_w; action_stream_w.write(this_sf.event_action_stream, this_sf.event_action_stream_bytes); + if (static_game_data_log.debug_f("(Floor {}) Added existing action stream data:", floor)) { + phosg::print_data(stderr, this_sf.event_action_stream, this_sf.event_action_stream_bytes); + } for (size_t source_event_index = 0; source_event_index < this_sf.event_count; source_event_index++) { const auto& source_event2 = this_sf.events2[source_event_index]; + static_game_data_log.debug_f("(Floor {} event {}) ... {}", floor, source_event_index, source_event2.str()); size_t remaining_waves = random_state.rand_int_biased(1, source_event2.max_waves); + static_game_data_log.debug_f("(Floor {} event {}) Chose {} waves", floor, source_event_index, remaining_waves); // Trace: at 0080E125 EAX is wave count le_uint32_t wave_next_event_id = source_event2.event_id; @@ -3954,6 +4001,8 @@ std::shared_ptr MapFile::materialize_random_sections(uint32_t random_se remaining_waves--; size_t remaining_enemies = random_state.rand_int_biased(source_event2.min_enemies, source_event2.max_enemies); + static_game_data_log.debug_f("(Floor {} event {} wave {}) Chose {} enemies", + floor, source_event_index, remaining_waves, remaining_enemies); // Trace: at 0080E208 EDI is enemy count random_state.generate_shuffled_location_table(locations_header, locations_sec_r, source_event2.room); @@ -3973,17 +4022,22 @@ std::shared_ptr MapFile::materialize_random_sections(uint32_t random_se size_t det = random_state.rand_int_biased(0, weight_total - 1); // Trace: at 0080E300 EDX is det + static_game_data_log.debug_f("(Floor {} event {} wave {} enemy {}) det={}, weight_total={}", + floor, source_event_index, remaining_waves, remaining_enemies, det, weight_total); + weights_r.go(0); while (!weights_r.eof()) { const auto& weight_entry = weights_r.get(); if (det < weight_entry.weight) { + static_game_data_log.debug_f("(Floor {} event {} wave {} enemy {}) This results in weight entry {}", + floor, source_event_index, remaining_waves, remaining_enemies, weight_entry.str()); if ((weight_entry.base_type_index != 0xFF) && (weight_entry.def_entry_num != 0xFF)) { if (definitions_header.entry_count == 0) { throw runtime_error("no available random enemy definitions"); } EnemySetEntry e; - e.base_type = rand_enemy_base_types.at(weight_entry.base_type_index); + e.base_type = this->RAND_ENEMY_BASE_TYPES.at(weight_entry.base_type_index); e.wave_number = wave_number; e.room = source_event2.room; e.floor = floor; @@ -4001,6 +4055,8 @@ std::shared_ptr MapFile::materialize_random_sections(uint32_t random_se const auto& def = definitions_r.pget(bs_min * sizeof(RandomEnemyDefinition)); if (def.entry_num == weight_entry.def_entry_num) { + static_game_data_log.debug_f("(Floor {} event {} wave {} enemy {}) Using parameters from {}", + floor, source_event_index, remaining_waves, remaining_enemies, def.str()); e.param1 = def.param1; e.param2 = def.param2; e.param3 = def.param3; @@ -4013,8 +4069,8 @@ std::shared_ptr MapFile::materialize_random_sections(uint32_t random_se throw runtime_error("random enemy definition not found"); } - const auto& loc = locations_sec_r.pget( - locations_header.entries_offset + sizeof(RandomEnemyLocationEntry) * random_state.next_location_index()); + const auto& loc = locations_sec_r.pget( + locations_header.entries_offset + sizeof(RandomEnemyLocation) * random_state.next_location_index()); e.pos = loc.pos; e.angle = loc.angle; @@ -4043,6 +4099,9 @@ std::shared_ptr MapFile::materialize_random_sections(uint32_t random_se action_stream_w.put_u32l(wave_next_event_id); action_stream_w.put_u8(0x01); // stop + static_game_data_log.debug_f("(Floor {} event {} wave {}) Added event {} which triggers {}", + floor, source_event_index, remaining_waves, event.str(), wave_next_event_id); + wave_number++; } } @@ -4057,6 +4116,9 @@ std::shared_ptr MapFile::materialize_random_sections(uint32_t random_se event.action_stream_offset = source_event2.action_stream_offset; events1_w.put(event); + static_game_data_log.debug_f("(Floor {} event {} wave {}) Added final event {}", + floor, source_event_index, remaining_waves, event.str()); + wave_number++; } @@ -4198,7 +4260,7 @@ string MapFile::disassemble(bool reassembly, Version version) const { if (reassembly) { ret.emplace_back(std::format(".object_sets {}", floor)); } else { - ret.emplace_back(std::format(".object_sets {} /* 0x{:X} in file; 0x{:X} bytes */", + ret.emplace_back(std::format(".object_sets {} /* offset 0x{:X} in file; 0x{:X} bytes */", floor, sf.object_sets_file_offset, sf.object_sets_file_size)); } for (size_t z = 0; z < sf.object_set_count; z++) { @@ -4206,15 +4268,17 @@ string MapFile::disassemble(bool reassembly, Version version) const { ret.emplace_back(sf.object_sets[z].str(version)); } else { size_t k_id = z + sf.first_object_set_index; - ret.emplace_back(std::format("/* K-{:03X} */ ", k_id) + sf.object_sets[z].str(version)); + ret.emplace_back(std::format("/* K-{:03X} */ {}", k_id, sf.object_sets[z].str(version))); } } + ret.emplace_back(); } + if (sf.enemy_sets) { if (reassembly) { ret.emplace_back(std::format(".enemy_sets {}", floor)); } else { - ret.emplace_back(std::format(".enemy_sets {} /* 0x{:X} in file; 0x{:X} bytes */", + ret.emplace_back(std::format(".enemy_sets {} /* offset 0x{:X} in file; 0x{:X} bytes */", floor, sf.enemy_sets_file_offset, sf.enemy_sets_file_size)); } for (size_t z = 0; z < sf.enemy_set_count; z++) { @@ -4222,15 +4286,17 @@ string MapFile::disassemble(bool reassembly, Version version) const { ret.emplace_back(sf.enemy_sets[z].str(version)); } else { size_t s_id = z + sf.first_enemy_set_index; - ret.emplace_back(std::format("/* S-{:03X} */ ", s_id) + sf.enemy_sets[z].str(version)); + ret.emplace_back(std::format("/* S-{:03X} */ {}", s_id, sf.enemy_sets[z].str(version))); } } + ret.emplace_back(); } + if (sf.events1) { if (reassembly) { ret.emplace_back(std::format(".events {}", floor)); } else { - ret.emplace_back(std::format(".events {} /* 0x{:X} in file; 0x{:X} bytes; 0x{:X} bytes in action stream */", + ret.emplace_back(std::format(".events {} /* offset 0x{:X} in file; 0x{:X} bytes; 0x{:X} bytes in action stream */", floor, sf.events_file_offset, sf.events_file_size, sf.event_action_stream_bytes)); } for (size_t z = 0; z < sf.event_count; z++) { @@ -4239,7 +4305,7 @@ string MapFile::disassemble(bool reassembly, Version version) const { ret.emplace_back(ev.str()); } else { size_t w_id = z + sf.first_event_set_index; - ret.emplace_back(std::format("/* W-{:03X} */ ", w_id) + ev.str()); + ret.emplace_back(std::format("/* W-{:03X} */ {}", w_id, ev.str())); } if (ev.action_stream_offset >= sf.event_action_stream_bytes) { ret.emplace_back(std::format( @@ -4249,13 +4315,15 @@ string MapFile::disassemble(bool reassembly, Version version) const { size_t as_size = as_r.size() - ev.action_stream_offset; ret.emplace_back(this->disassemble_action_stream(as_r.pgetv(ev.action_stream_offset, as_size), as_size)); } + ret.emplace_back(); } + if (sf.events2) { if (reassembly) { ret.emplace_back(std::format(".random_events {}", floor)); } else { ret.emplace_back(std::format( - ".random_events {} /* 0x{:X} in file; 0x{:X} bytes; 0x{:X} bytes in action stream */", + ".random_events {} /* offset 0x{:X} in file; 0x{:X} bytes; 0x{:X} bytes in action stream */", floor, sf.events_file_offset, sf.events_file_size, sf.event_action_stream_bytes)); } for (size_t z = 0; z < sf.event_count; z++) { @@ -4263,7 +4331,7 @@ string MapFile::disassemble(bool reassembly, Version version) const { if (reassembly) { ret.emplace_back(ev.str()); } else { - ret.emplace_back(std::format("/* index {} */", z) + ev.str()); + ret.emplace_back(std::format("/* index {} */ {}", z, ev.str())); } if (ev.action_stream_offset >= sf.event_action_stream_bytes) { ret.emplace_back(std::format( @@ -4273,26 +4341,62 @@ string MapFile::disassemble(bool reassembly, Version version) const { size_t as_size = as_r.size() - ev.action_stream_offset; ret.emplace_back(this->disassemble_action_stream(as_r.pgetv(ev.action_stream_offset, as_size), as_size)); } + ret.emplace_back(); } - if (sf.random_enemy_locations_data) { + + if (sf.random_enemy_locations_file_size > 0) { if (reassembly) { ret.emplace_back(std::format(".random_enemy_locations {}", floor)); } else { - ret.emplace_back(std::format(".random_enemy_locations {} /* 0x{:X} in file; 0x{:X} bytes */", + ret.emplace_back(std::format(".random_enemy_locations {} /* offset 0x{:X} in file; 0x{:X} bytes */", floor, sf.random_enemy_locations_file_offset, sf.random_enemy_locations_file_size)); } - ret.emplace_back(phosg::format_data(sf.random_enemy_locations_data, sf.random_enemy_locations_data_size)); + for (size_t z = 0; z < sf.random_enemy_location_section_count; z++) { + const auto& sec = sf.random_enemy_location_sections[z]; + if (reassembly) { + ret.emplace_back(sec.str()); + } else { + ret.emplace_back(std::format("/* section index {} */ {}", z, sec.str())); + } + } + for (size_t z = 0; z < sf.random_enemy_location_count; z++) { + const auto& ent = sf.random_enemy_locations[z]; + if (reassembly) { + ret.emplace_back(ent.str()); + } else { + ret.emplace_back(std::format("/* entry index {} */ {}", z, ent.str())); + } + } + ret.emplace_back(); } - if (sf.random_enemy_definitions_data) { + + if (sf.random_enemy_definitions_file_size > 0) { if (reassembly) { ret.emplace_back(std::format(".random_enemy_definitions {}", floor)); } else { - ret.emplace_back(std::format(".random_enemy_definitions {} /* 0x{:X} in file; 0x{:X} bytes */", + ret.emplace_back(std::format(".random_enemy_definitions {} /* offset 0x{:X} in file; 0x{:X} bytes */", floor, sf.random_enemy_definitions_file_offset, sf.random_enemy_definitions_file_size)); } - ret.emplace_back(phosg::format_data(sf.random_enemy_definitions_data, sf.random_enemy_definitions_data_size)); + for (size_t z = 0; z < sf.random_enemy_definition_count; z++) { + const auto& def = sf.random_enemy_definitions[z]; + if (reassembly) { + ret.emplace_back(def.str()); + } else { + ret.emplace_back(std::format("/* definition index {} */ {}", z, def.str())); + } + } + for (size_t z = 0; z < sf.random_enemy_definition_weight_count; z++) { + const auto& weight = sf.random_enemy_definition_weights[z]; + if (reassembly) { + ret.emplace_back(weight.str()); + } else { + ret.emplace_back(std::format("/* weight index {} */ {}", z, weight.str())); + } + } + ret.emplace_back(); } } + return phosg::join(ret, "\n"); } diff --git a/src/Map.hh b/src/Map.hh index a9ce457d..b7a23845 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -321,8 +321,8 @@ public: } __packed_ws__(Event2Entry, 0x18); struct RandomEnemyLocationsHeader { // Section type 4 (RANDOM_ENEMY_LOCATIONS) - /* 00 */ le_uint32_t room_table_offset; // Offset to RandomEnemyLocationSegment structs, from start of this struct - /* 04 */ le_uint32_t entries_offset; // Offset to RandomEnemyLocationEntry structs, from start of this struct + /* 00 */ le_uint32_t room_table_offset; // Offset to RandomEnemyLocationSection structs, from start of this struct + /* 04 */ le_uint32_t entries_offset; // Offset to RandomEnemyLocation structs, from start of this struct /* 08 */ le_uint32_t num_rooms; /* 0C */ } __packed_ws__(RandomEnemyLocationsHeader, 0x0C); @@ -330,11 +330,13 @@ public: struct RandomEnemyLocationSection { // Section type 4 (RANDOM_ENEMY_LOCATIONS) /* 00 */ le_uint16_t room; /* 02 */ le_uint16_t count; - /* 04 */ le_uint32_t offset; + /* 04 */ le_uint32_t offset; // Bytes from start of RandomEnemyLocation section /* 08 */ + + std::string str() const; } __packed_ws__(RandomEnemyLocationSection, 8); - struct RandomEnemyLocationEntry { // Section type 4 (RANDOM_ENEMY_LOCATIONS) + struct RandomEnemyLocation { // Section type 4 (RANDOM_ENEMY_LOCATIONS) /* 00 */ VectorXYZF pos; /* 0C */ VectorXYZI angle; /* 18 */ le_uint16_t unknown_a9; // TODO: Verify these are actually little-endian @@ -342,7 +344,7 @@ public: /* 1C */ std::string str() const; - } __packed_ws__(RandomEnemyLocationEntry, 0x1C); + } __packed_ws__(RandomEnemyLocation, 0x1C); struct RandomEnemyDefinitionsHeader { // Section type 5 (RANDOM_ENEMY_DEFINITIONS) /* 00 */ le_uint32_t entries_offset; // Offset to RandomEnemyDefinition structs, from start of this struct @@ -422,11 +424,19 @@ public: size_t random_enemy_locations_file_size = 0; const void* random_enemy_locations_data = nullptr; size_t random_enemy_locations_data_size = 0; + const RandomEnemyLocationSection* random_enemy_location_sections = nullptr; + size_t random_enemy_location_section_count = 0; + const RandomEnemyLocation* random_enemy_locations = nullptr; + size_t random_enemy_location_count = 0; size_t random_enemy_definitions_file_offset = 0; size_t random_enemy_definitions_file_size = 0; const void* random_enemy_definitions_data = nullptr; size_t random_enemy_definitions_data_size = 0; + const RandomEnemyDefinition* random_enemy_definitions = nullptr; + size_t random_enemy_definition_count = 0; + const RandomEnemyWeight* random_enemy_definition_weights = nullptr; + size_t random_enemy_definition_weight_count = 0; }; // Quest constructor @@ -478,6 +488,8 @@ public: std::string disassemble(bool reassembly = false, Version version = Version::UNKNOWN) const; protected: + static const std::array RAND_ENEMY_BASE_TYPES; + void link_data(std::shared_ptr data); void set_object_sets_for_floor(uint8_t floor, size_t file_offset, const void* data, size_t size); diff --git a/src/QuestScript.cc b/src/QuestScript.cc index c21ee586..27bce6b5 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -4268,7 +4268,7 @@ AssembledQuestScript assemble_quest_script( string include_path = include_dir + "/" + filename; if (std::filesystem::is_regular_file(include_path)) { found = true; - include_file(filename, phosg::load_file(filename), z); + include_file(filename, phosg::load_file(include_path), z); break; } }