fix multi-area challenge enemy generation
This commit is contained in:
+28
@@ -1574,6 +1574,34 @@ Action a_show_ep3_maps(
|
||||
}
|
||||
});
|
||||
|
||||
Action a_show_battle_params(
|
||||
"show-battle-params", "\
|
||||
show-battle-params\n\
|
||||
Print the Blue Burst battle parameters from the system/blueburst directory\n\
|
||||
in a human-readable format.\n",
|
||||
+[](Arguments&) {
|
||||
BattleParamsIndex index(
|
||||
make_shared<string>(load_file("system/blueburst/BattleParamEntry_on.dat")),
|
||||
make_shared<string>(load_file("system/blueburst/BattleParamEntry_lab_on.dat")),
|
||||
make_shared<string>(load_file("system/blueburst/BattleParamEntry_ep4_on.dat")),
|
||||
make_shared<string>(load_file("system/blueburst/BattleParamEntry.dat")),
|
||||
make_shared<string>(load_file("system/blueburst/BattleParamEntry_lab.dat")),
|
||||
make_shared<string>(load_file("system/blueburst/BattleParamEntry_ep4.dat")));
|
||||
|
||||
fprintf(stdout, "Episode 1 multi\n");
|
||||
index.get_table(false, Episode::EP1).print(stdout);
|
||||
fprintf(stdout, "Episode 1 solo\n");
|
||||
index.get_table(true, Episode::EP1).print(stdout);
|
||||
fprintf(stdout, "Episode 2 multi\n");
|
||||
index.get_table(false, Episode::EP2).print(stdout);
|
||||
fprintf(stdout, "Episode 2 solo\n");
|
||||
index.get_table(true, Episode::EP2).print(stdout);
|
||||
fprintf(stdout, "Episode 4 multi\n");
|
||||
index.get_table(false, Episode::EP4).print(stdout);
|
||||
fprintf(stdout, "Episode 4 solo\n");
|
||||
index.get_table(true, Episode::EP4).print(stdout);
|
||||
});
|
||||
|
||||
Action a_parse_object_graph(
|
||||
"parse-object-graph", nullptr, +[](Arguments& args) {
|
||||
uint32_t root_object_address = args.get<uint32_t>("root", Arguments::IntFormat::HEX);
|
||||
|
||||
+71
-77
@@ -631,78 +631,70 @@ void Map::add_enemies_from_map_data(
|
||||
}
|
||||
}
|
||||
|
||||
struct DATParserRandomState {
|
||||
PSOV2Encryption random;
|
||||
PSOV2Encryption location_table_random;
|
||||
std::array<uint32_t, 0x20> location_index_table;
|
||||
uint32_t location_indexes_populated;
|
||||
uint32_t location_indexes_used;
|
||||
uint32_t location_entries_base_offset;
|
||||
Map::DATParserRandomState::DATParserRandomState(uint32_t rare_seed)
|
||||
: random(rare_seed),
|
||||
location_table_random(0),
|
||||
location_indexes_populated(0),
|
||||
location_indexes_used(0),
|
||||
location_entries_base_offset(0) {
|
||||
this->location_index_table.fill(0);
|
||||
}
|
||||
|
||||
DATParserRandomState(uint32_t rare_seed)
|
||||
: random(rare_seed),
|
||||
location_table_random(0),
|
||||
location_indexes_populated(0),
|
||||
location_indexes_used(0),
|
||||
location_entries_base_offset(0) {
|
||||
this->location_index_table.fill(0);
|
||||
size_t Map::DATParserRandomState::rand_int_biased(size_t min_v, size_t max_v) {
|
||||
float max_f = static_cast<float>(max_v + 1);
|
||||
uint32_t crypt_v = this->random.next();
|
||||
float det_f = static_cast<float>(crypt_v);
|
||||
return max<size_t>(floorf((max_f * det_f) / UINT32_MAX_AS_FLOAT), min_v);
|
||||
}
|
||||
|
||||
uint32_t Map::DATParserRandomState::next_location_index() {
|
||||
if (this->location_indexes_used < this->location_indexes_populated) {
|
||||
return this->location_index_table.at(this->location_indexes_used++);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Map::DATParserRandomState::generate_shuffled_location_table(
|
||||
const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section) {
|
||||
if (header.num_sections == 0) {
|
||||
throw runtime_error("no locations defined");
|
||||
}
|
||||
|
||||
size_t rand_int_biased(size_t min_v, size_t max_v) {
|
||||
float max_f = static_cast<float>(max_v + 1);
|
||||
uint32_t crypt_v = this->random.next();
|
||||
float det_f = static_cast<float>(crypt_v);
|
||||
return max<size_t>(floorf((max_f * det_f) / UINT32_MAX_AS_FLOAT), min_v);
|
||||
StringReader sections_r = r.sub(header.section_table_offset, header.num_sections * sizeof(Map::RandomEnemyLocationSection));
|
||||
|
||||
size_t bs_min = 0;
|
||||
size_t bs_max = header.num_sections - 1;
|
||||
do {
|
||||
size_t bs_mid = (bs_min + bs_max) / 2;
|
||||
if (sections_r.pget<Map::RandomEnemyLocationSection>(bs_mid * sizeof(Map::RandomEnemyLocationSection)).section < section) {
|
||||
bs_min = bs_mid + 1;
|
||||
} else {
|
||||
bs_max = bs_mid;
|
||||
}
|
||||
} while (bs_min < bs_max);
|
||||
|
||||
const auto& sec = sections_r.pget<Map::RandomEnemyLocationSection>(bs_min * sizeof(Map::RandomEnemyLocationSection));
|
||||
if (section != sec.section) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t next_location_index() {
|
||||
if (this->location_indexes_used < this->location_indexes_populated) {
|
||||
return this->location_index_table.at(this->location_indexes_used++);
|
||||
}
|
||||
return 0;
|
||||
this->location_indexes_populated = sec.count;
|
||||
this->location_indexes_used = 0;
|
||||
this->location_entries_base_offset = sec.offset;
|
||||
for (size_t z = 0; z < sec.count; z++) {
|
||||
this->location_index_table.at(z) = z;
|
||||
}
|
||||
|
||||
void generate_shuffled_location_table(const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section) {
|
||||
if (header.num_sections == 0) {
|
||||
throw runtime_error("no locations defined");
|
||||
}
|
||||
|
||||
StringReader sections_r = r.sub(header.section_table_offset, header.num_sections * sizeof(Map::RandomEnemyLocationSection));
|
||||
|
||||
size_t bs_min = 0;
|
||||
size_t bs_max = header.num_sections - 1;
|
||||
do {
|
||||
size_t bs_mid = (bs_min + bs_max) / 2;
|
||||
if (sections_r.pget<Map::RandomEnemyLocationSection>(bs_mid * sizeof(Map::RandomEnemyLocationSection)).section < section) {
|
||||
bs_min = bs_mid + 1;
|
||||
} else {
|
||||
bs_max = bs_mid;
|
||||
}
|
||||
} while (bs_min < bs_max);
|
||||
|
||||
const auto& sec = sections_r.pget<Map::RandomEnemyLocationSection>(bs_min * sizeof(Map::RandomEnemyLocationSection));
|
||||
if (section != sec.section) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->location_indexes_populated = sec.count;
|
||||
this->location_indexes_used = 0;
|
||||
this->location_entries_base_offset = sec.offset;
|
||||
for (size_t z = 0; z < sec.count; z++) {
|
||||
this->location_index_table.at(z) = z;
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
for (size_t x = 0; x < sec.count; x++) {
|
||||
uint32_t crypt_v = this->location_table_random.next();
|
||||
size_t choice = floorf((static_cast<float>(sec.count) * static_cast<float>(crypt_v)) / UINT32_MAX_AS_FLOAT);
|
||||
uint32_t t = this->location_index_table[x];
|
||||
this->location_index_table[x] = this->location_index_table[choice];
|
||||
this->location_index_table[choice] = t;
|
||||
}
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
for (size_t x = 0; x < sec.count; x++) {
|
||||
uint32_t crypt_v = this->location_table_random.next();
|
||||
size_t choice = floorf((static_cast<float>(sec.count) * static_cast<float>(crypt_v)) / UINT32_MAX_AS_FLOAT);
|
||||
uint32_t t = this->location_index_table[x];
|
||||
this->location_index_table[x] = this->location_index_table[choice];
|
||||
this->location_index_table[choice] = t;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void Map::add_random_enemies_from_map_data(
|
||||
Episode episode,
|
||||
@@ -712,7 +704,7 @@ void Map::add_random_enemies_from_map_data(
|
||||
StringReader wave_events_segment_r,
|
||||
StringReader locations_segment_r,
|
||||
StringReader definitions_segment_r,
|
||||
uint32_t rare_seed,
|
||||
std::shared_ptr<DATParserRandomState> random_state,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates) {
|
||||
|
||||
static const array<uint32_t, 41> rand_enemy_base_types = {
|
||||
@@ -736,14 +728,12 @@ void Map::add_random_enemies_from_map_data(
|
||||
definitions_header.weight_entries_offset,
|
||||
definitions_header.weight_entry_count * sizeof(RandomEnemyWeight));
|
||||
|
||||
DATParserRandomState random(rare_seed);
|
||||
|
||||
for (size_t wave_entry_index = 0; wave_entry_index < wave_events_header.entry_count; wave_entry_index++) {
|
||||
auto entry_log = static_game_data_log.sub(string_printf("(Entry %zu/%" PRIu32 ") ", wave_entry_index, wave_events_header.entry_count.load()));
|
||||
entry_log.info("Start");
|
||||
const auto& entry = wave_events_segment_r.get<Event2Entry>();
|
||||
|
||||
size_t remaining_waves = random.rand_int_biased(1, entry.max_waves);
|
||||
size_t remaining_waves = random_state->rand_int_biased(1, entry.max_waves);
|
||||
entry_log.info("Chose %zu waves (max=%hu)", remaining_waves, entry.max_waves.load());
|
||||
// Trace: at 0080E125 EAX is wave count
|
||||
|
||||
@@ -752,14 +742,14 @@ void Map::add_random_enemies_from_map_data(
|
||||
remaining_waves--;
|
||||
auto wave_log = entry_log.sub(string_printf("(Wave %zu) ", remaining_waves));
|
||||
|
||||
size_t remaining_enemies = random.rand_int_biased(entry.min_enemies, entry.max_enemies);
|
||||
size_t remaining_enemies = random_state->rand_int_biased(entry.min_enemies, entry.max_enemies);
|
||||
wave_log.info("Chose %zu enemies (range=[%hhu, %hhu])", remaining_enemies, entry.min_enemies, entry.max_enemies);
|
||||
// Trace: at 0080E208 EDI is enemy count
|
||||
|
||||
random.generate_shuffled_location_table(locations_header, locations_segment_r, entry.section);
|
||||
random_state->generate_shuffled_location_table(locations_header, locations_segment_r, entry.section);
|
||||
wave_log.info("Generated shuffled location table");
|
||||
for (size_t z = 0; z < random.location_indexes_populated; z++) {
|
||||
wave_log.info(" table[%zX] = %" PRIX32, z, random.location_index_table[z]);
|
||||
for (size_t z = 0; z < random_state->location_indexes_populated; z++) {
|
||||
wave_log.info(" table[%zX] = %" PRIX32, z, random_state->location_index_table[z]);
|
||||
}
|
||||
// Trace: at 0080EBB0 *(EBP + 4) points to table (0x20 uint32_ts)
|
||||
|
||||
@@ -775,7 +765,7 @@ void Map::add_random_enemies_from_map_data(
|
||||
}
|
||||
// Trace: at 0080E2C2 EBX is weight_total
|
||||
|
||||
size_t det = random.rand_int_biased(0, weight_total - 1);
|
||||
size_t det = random_state->rand_int_biased(0, weight_total - 1);
|
||||
enemy_log.info("weight_total=%zX, det=%zX", weight_total, det);
|
||||
// Trace: at 0080E300 EDX is det
|
||||
|
||||
@@ -813,13 +803,13 @@ void Map::add_random_enemies_from_map_data(
|
||||
e.fparam5 = def.fparam5;
|
||||
e.uparam1 = def.uparam1;
|
||||
e.uparam2 = def.uparam2;
|
||||
e.num_children = random.rand_int_biased(def.min_children, def.max_children);
|
||||
e.num_children = random_state->rand_int_biased(def.min_children, def.max_children);
|
||||
} else {
|
||||
throw runtime_error("random enemy definition not found");
|
||||
}
|
||||
|
||||
const auto& loc = locations_segment_r.pget<RandomEnemyLocationEntry>(
|
||||
locations_header.entries_offset + sizeof(RandomEnemyLocationEntry) * random.next_location_index());
|
||||
locations_header.entries_offset + sizeof(RandomEnemyLocationEntry) * random_state->next_location_index());
|
||||
e.x = loc.x;
|
||||
e.y = loc.y;
|
||||
e.z = loc.z;
|
||||
@@ -844,13 +834,13 @@ void Map::add_random_enemies_from_map_data(
|
||||
// doing so, it uses one value from random to determine the delay
|
||||
// parameter of the event. To keep our state in sync with what the
|
||||
// client would do, we skip a random value here.
|
||||
random.random.next();
|
||||
random_state->random.next();
|
||||
wave_number++;
|
||||
}
|
||||
}
|
||||
|
||||
// For the same reason as above, we need to skip another random value here.
|
||||
random.random.next();
|
||||
random_state->random.next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -928,6 +918,7 @@ void Map::add_enemies_and_objects_from_quest_data(
|
||||
auto all_floor_sections = this->collect_quest_map_data_sections(data, size);
|
||||
|
||||
StringReader r(data, size);
|
||||
shared_ptr<DATParserRandomState> random_state;
|
||||
for (size_t floor = 0; floor < all_floor_sections.size(); floor++) {
|
||||
const auto& floor_sections = all_floor_sections[floor];
|
||||
|
||||
@@ -962,6 +953,9 @@ void Map::add_enemies_and_objects_from_quest_data(
|
||||
const auto& wave_events_header = r.pget<SectionHeader>(floor_sections.wave_events);
|
||||
const auto& random_enemy_locations_header = r.pget<SectionHeader>(floor_sections.random_enemy_locations);
|
||||
const auto& random_enemy_definitions_header = r.pget<SectionHeader>(floor_sections.random_enemy_definitions);
|
||||
if (!random_state) {
|
||||
random_state = make_shared<DATParserRandomState>(rare_seed);
|
||||
}
|
||||
this->add_random_enemies_from_map_data(
|
||||
episode,
|
||||
difficulty,
|
||||
@@ -970,7 +964,7 @@ void Map::add_enemies_and_objects_from_quest_data(
|
||||
r.sub(floor_sections.wave_events + sizeof(SectionHeader), wave_events_header.data_size),
|
||||
r.sub(floor_sections.random_enemy_locations + sizeof(SectionHeader), random_enemy_locations_header.data_size),
|
||||
r.sub(floor_sections.random_enemy_definitions + sizeof(SectionHeader), random_enemy_definitions_header.data_size),
|
||||
rare_seed,
|
||||
random_state,
|
||||
rare_rates);
|
||||
}
|
||||
}
|
||||
|
||||
+15
-1
@@ -239,6 +239,20 @@ struct Map {
|
||||
std::string str() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct DATParserRandomState {
|
||||
PSOV2Encryption random;
|
||||
PSOV2Encryption location_table_random;
|
||||
std::array<uint32_t, 0x20> location_index_table;
|
||||
uint32_t location_indexes_populated;
|
||||
uint32_t location_indexes_used;
|
||||
uint32_t location_entries_base_offset;
|
||||
|
||||
DATParserRandomState(uint32_t rare_seed);
|
||||
size_t rand_int_biased(size_t min_v, size_t max_v);
|
||||
uint32_t next_location_index();
|
||||
void generate_shuffled_location_table(const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section);
|
||||
};
|
||||
|
||||
std::vector<Object> objects;
|
||||
std::vector<Enemy> enemies;
|
||||
std::vector<size_t> rare_enemy_indexes;
|
||||
@@ -272,7 +286,7 @@ struct Map {
|
||||
StringReader wave_events_r,
|
||||
StringReader random_enemy_locations_r,
|
||||
StringReader random_enemy_definitions_r,
|
||||
uint32_t rare_seed,
|
||||
std::shared_ptr<DATParserRandomState> random_state,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
|
||||
|
||||
struct DATSectionsForFloor {
|
||||
|
||||
Reference in New Issue
Block a user