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(
|
Action a_parse_object_graph(
|
||||||
"parse-object-graph", nullptr, +[](Arguments& args) {
|
"parse-object-graph", nullptr, +[](Arguments& args) {
|
||||||
uint32_t root_object_address = args.get<uint32_t>("root", Arguments::IntFormat::HEX);
|
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 {
|
Map::DATParserRandomState::DATParserRandomState(uint32_t rare_seed)
|
||||||
PSOV2Encryption random;
|
: random(rare_seed),
|
||||||
PSOV2Encryption location_table_random;
|
location_table_random(0),
|
||||||
std::array<uint32_t, 0x20> location_index_table;
|
location_indexes_populated(0),
|
||||||
uint32_t location_indexes_populated;
|
location_indexes_used(0),
|
||||||
uint32_t location_indexes_used;
|
location_entries_base_offset(0) {
|
||||||
uint32_t location_entries_base_offset;
|
this->location_index_table.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
DATParserRandomState(uint32_t rare_seed)
|
size_t Map::DATParserRandomState::rand_int_biased(size_t min_v, size_t max_v) {
|
||||||
: random(rare_seed),
|
float max_f = static_cast<float>(max_v + 1);
|
||||||
location_table_random(0),
|
uint32_t crypt_v = this->random.next();
|
||||||
location_indexes_populated(0),
|
float det_f = static_cast<float>(crypt_v);
|
||||||
location_indexes_used(0),
|
return max<size_t>(floorf((max_f * det_f) / UINT32_MAX_AS_FLOAT), min_v);
|
||||||
location_entries_base_offset(0) {
|
}
|
||||||
this->location_index_table.fill(0);
|
|
||||||
|
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) {
|
StringReader sections_r = r.sub(header.section_table_offset, header.num_sections * sizeof(Map::RandomEnemyLocationSection));
|
||||||
float max_f = static_cast<float>(max_v + 1);
|
|
||||||
uint32_t crypt_v = this->random.next();
|
size_t bs_min = 0;
|
||||||
float det_f = static_cast<float>(crypt_v);
|
size_t bs_max = header.num_sections - 1;
|
||||||
return max<size_t>(floorf((max_f * det_f) / UINT32_MAX_AS_FLOAT), min_v);
|
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() {
|
this->location_indexes_populated = sec.count;
|
||||||
if (this->location_indexes_used < this->location_indexes_populated) {
|
this->location_indexes_used = 0;
|
||||||
return this->location_index_table.at(this->location_indexes_used++);
|
this->location_entries_base_offset = sec.offset;
|
||||||
}
|
for (size_t z = 0; z < sec.count; z++) {
|
||||||
return 0;
|
this->location_index_table.at(z) = z;
|
||||||
}
|
}
|
||||||
|
|
||||||
void generate_shuffled_location_table(const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section) {
|
for (size_t z = 0; z < 4; z++) {
|
||||||
if (header.num_sections == 0) {
|
for (size_t x = 0; x < sec.count; x++) {
|
||||||
throw runtime_error("no locations defined");
|
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];
|
||||||
StringReader sections_r = r.sub(header.section_table_offset, header.num_sections * sizeof(Map::RandomEnemyLocationSection));
|
this->location_index_table[x] = this->location_index_table[choice];
|
||||||
|
this->location_index_table[choice] = t;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
void Map::add_random_enemies_from_map_data(
|
void Map::add_random_enemies_from_map_data(
|
||||||
Episode episode,
|
Episode episode,
|
||||||
@@ -712,7 +704,7 @@ void Map::add_random_enemies_from_map_data(
|
|||||||
StringReader wave_events_segment_r,
|
StringReader wave_events_segment_r,
|
||||||
StringReader locations_segment_r,
|
StringReader locations_segment_r,
|
||||||
StringReader definitions_segment_r,
|
StringReader definitions_segment_r,
|
||||||
uint32_t rare_seed,
|
std::shared_ptr<DATParserRandomState> random_state,
|
||||||
std::shared_ptr<const RareEnemyRates> rare_rates) {
|
std::shared_ptr<const RareEnemyRates> rare_rates) {
|
||||||
|
|
||||||
static const array<uint32_t, 41> rand_enemy_base_types = {
|
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_entries_offset,
|
||||||
definitions_header.weight_entry_count * sizeof(RandomEnemyWeight));
|
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++) {
|
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()));
|
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");
|
entry_log.info("Start");
|
||||||
const auto& entry = wave_events_segment_r.get<Event2Entry>();
|
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());
|
entry_log.info("Chose %zu waves (max=%hu)", remaining_waves, entry.max_waves.load());
|
||||||
// Trace: at 0080E125 EAX is wave count
|
// Trace: at 0080E125 EAX is wave count
|
||||||
|
|
||||||
@@ -752,14 +742,14 @@ void Map::add_random_enemies_from_map_data(
|
|||||||
remaining_waves--;
|
remaining_waves--;
|
||||||
auto wave_log = entry_log.sub(string_printf("(Wave %zu) ", 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);
|
wave_log.info("Chose %zu enemies (range=[%hhu, %hhu])", remaining_enemies, entry.min_enemies, entry.max_enemies);
|
||||||
// Trace: at 0080E208 EDI is enemy count
|
// 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");
|
wave_log.info("Generated shuffled location table");
|
||||||
for (size_t z = 0; z < random.location_indexes_populated; z++) {
|
for (size_t z = 0; z < random_state->location_indexes_populated; z++) {
|
||||||
wave_log.info(" table[%zX] = %" PRIX32, z, random.location_index_table[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)
|
// 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
|
// 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);
|
enemy_log.info("weight_total=%zX, det=%zX", weight_total, det);
|
||||||
// Trace: at 0080E300 EDX is det
|
// Trace: at 0080E300 EDX is det
|
||||||
|
|
||||||
@@ -813,13 +803,13 @@ void Map::add_random_enemies_from_map_data(
|
|||||||
e.fparam5 = def.fparam5;
|
e.fparam5 = def.fparam5;
|
||||||
e.uparam1 = def.uparam1;
|
e.uparam1 = def.uparam1;
|
||||||
e.uparam2 = def.uparam2;
|
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 {
|
} else {
|
||||||
throw runtime_error("random enemy definition not found");
|
throw runtime_error("random enemy definition not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& loc = locations_segment_r.pget<RandomEnemyLocationEntry>(
|
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.x = loc.x;
|
||||||
e.y = loc.y;
|
e.y = loc.y;
|
||||||
e.z = loc.z;
|
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
|
// 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
|
// parameter of the event. To keep our state in sync with what the
|
||||||
// client would do, we skip a random value here.
|
// client would do, we skip a random value here.
|
||||||
random.random.next();
|
random_state->random.next();
|
||||||
wave_number++;
|
wave_number++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the same reason as above, we need to skip another random value here.
|
// 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);
|
auto all_floor_sections = this->collect_quest_map_data_sections(data, size);
|
||||||
|
|
||||||
StringReader r(data, size);
|
StringReader r(data, size);
|
||||||
|
shared_ptr<DATParserRandomState> random_state;
|
||||||
for (size_t floor = 0; floor < all_floor_sections.size(); floor++) {
|
for (size_t floor = 0; floor < all_floor_sections.size(); floor++) {
|
||||||
const auto& floor_sections = all_floor_sections[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& 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_locations_header = r.pget<SectionHeader>(floor_sections.random_enemy_locations);
|
||||||
const auto& random_enemy_definitions_header = r.pget<SectionHeader>(floor_sections.random_enemy_definitions);
|
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(
|
this->add_random_enemies_from_map_data(
|
||||||
episode,
|
episode,
|
||||||
difficulty,
|
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.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_locations + sizeof(SectionHeader), random_enemy_locations_header.data_size),
|
||||||
r.sub(floor_sections.random_enemy_definitions + sizeof(SectionHeader), random_enemy_definitions_header.data_size),
|
r.sub(floor_sections.random_enemy_definitions + sizeof(SectionHeader), random_enemy_definitions_header.data_size),
|
||||||
rare_seed,
|
random_state,
|
||||||
rare_rates);
|
rare_rates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-1
@@ -239,6 +239,20 @@ struct Map {
|
|||||||
std::string str() const;
|
std::string str() const;
|
||||||
} __attribute__((packed));
|
} __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<Object> objects;
|
||||||
std::vector<Enemy> enemies;
|
std::vector<Enemy> enemies;
|
||||||
std::vector<size_t> rare_enemy_indexes;
|
std::vector<size_t> rare_enemy_indexes;
|
||||||
@@ -272,7 +286,7 @@ struct Map {
|
|||||||
StringReader wave_events_r,
|
StringReader wave_events_r,
|
||||||
StringReader random_enemy_locations_r,
|
StringReader random_enemy_locations_r,
|
||||||
StringReader random_enemy_definitions_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);
|
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
|
||||||
|
|
||||||
struct DATSectionsForFloor {
|
struct DATSectionsForFloor {
|
||||||
|
|||||||
Reference in New Issue
Block a user