use SetDataTable files as map indexes
This commit is contained in:
@@ -79,20 +79,13 @@ shared_ptr<const string> ThreadSafeFileCache::get(
|
||||
const string& name, std::function<shared_ptr<const string>(const std::string&)> generate) {
|
||||
try {
|
||||
shared_lock g(this->lock);
|
||||
auto ret = this->name_to_file.at(name);
|
||||
if (!ret) {
|
||||
throw cannot_open_file(name);
|
||||
}
|
||||
return ret;
|
||||
return this->name_to_file.at(name);
|
||||
} catch (const out_of_range&) {
|
||||
unique_lock g(this->lock);
|
||||
auto it = this->name_to_file.find(name);
|
||||
if (it == this->name_to_file.end()) {
|
||||
it = this->name_to_file.emplace(name, generate(name)).first;
|
||||
}
|
||||
if (!it->second) {
|
||||
throw cannot_open_file(name);
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
+54
-48
@@ -291,10 +291,30 @@ shared_ptr<Map> Lobby::load_maps(
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
shared_ptr<const SetDataTableBase> sdt,
|
||||
function<shared_ptr<const string>(Version, const string&)> get_file_data,
|
||||
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
shared_ptr<PSOLFGEncryption> random_crypt,
|
||||
const parray<le_uint32_t, 0x20>& variations) {
|
||||
const parray<le_uint32_t, 0x20>& variations,
|
||||
const PrefixedLogger* log) {
|
||||
auto enemy_filenames = sdt->map_filenames_for_variations(variations, episode, mode, true);
|
||||
auto object_filenames = sdt->map_filenames_for_variations(variations, episode, mode, false);
|
||||
return Lobby::load_maps(enemy_filenames, object_filenames, version, episode, mode, difficulty, event, lobby_id, get_file_data, rare_rates, random_crypt, log);
|
||||
}
|
||||
|
||||
shared_ptr<Map> Lobby::load_maps(
|
||||
const vector<string>& enemy_filenames,
|
||||
const vector<string>& object_filenames,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
function<shared_ptr<const string>(Version, const string&)> get_file_data,
|
||||
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
shared_ptr<PSOLFGEncryption> random_crypt,
|
||||
const PrefixedLogger* log) {
|
||||
auto map = make_shared<Map>(version, lobby_id, random_crypt);
|
||||
|
||||
// Don't load free-roam maps in Challenge mode, since players can't go to
|
||||
@@ -303,58 +323,42 @@ shared_ptr<Map> Lobby::load_maps(
|
||||
return map;
|
||||
}
|
||||
|
||||
for (size_t floor = 0; floor < 0x10; floor++) {
|
||||
auto enemy_filenames = map_filenames_for_variation(
|
||||
version,
|
||||
episode,
|
||||
mode,
|
||||
floor,
|
||||
variations[floor * 2],
|
||||
variations[floor * 2 + 1],
|
||||
true);
|
||||
if (!enemy_filenames.empty()) {
|
||||
bool any_map_loaded = false;
|
||||
for (const string& filename : enemy_filenames) {
|
||||
auto map_data = get_file_data(version, filename);
|
||||
if (map_data) {
|
||||
map->add_enemies_from_map_data(
|
||||
episode,
|
||||
difficulty,
|
||||
event,
|
||||
floor,
|
||||
map_data->data(),
|
||||
map_data->size(),
|
||||
rare_rates);
|
||||
any_map_loaded = true;
|
||||
break;
|
||||
for (size_t floor = 0; floor < 0x12; floor++) {
|
||||
const auto& floor_enemy_filename = enemy_filenames.at(floor);
|
||||
if (!floor_enemy_filename.empty()) {
|
||||
auto map_data = get_file_data(version, floor_enemy_filename);
|
||||
if (map_data) {
|
||||
map->add_enemies_from_map_data(
|
||||
episode,
|
||||
difficulty,
|
||||
event,
|
||||
floor,
|
||||
map_data->data(),
|
||||
map_data->size(),
|
||||
rare_rates);
|
||||
if (log) {
|
||||
log->info("Loaded enemies map %s for floor %02zX", floor_enemy_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("Enemies map %s for floor %02zX cannot be used; skipping", floor_enemy_filename.c_str(), floor);
|
||||
}
|
||||
if (!any_map_loaded) {
|
||||
throw runtime_error(string_printf("no enemy maps loaded for floor %zu", floor));
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("No enemies to load for floor %02zX", floor);
|
||||
}
|
||||
|
||||
auto object_filenames = map_filenames_for_variation(
|
||||
version,
|
||||
episode,
|
||||
mode,
|
||||
floor,
|
||||
variations[floor * 2],
|
||||
variations[floor * 2 + 1],
|
||||
false);
|
||||
if (!object_filenames.empty()) {
|
||||
bool any_map_loaded = false;
|
||||
for (const string& filename : object_filenames) {
|
||||
auto map_data = get_file_data(version, filename);
|
||||
if (map_data) {
|
||||
map->add_objects_from_map_data(floor, map_data->data(), map_data->size());
|
||||
any_map_loaded = true;
|
||||
break;
|
||||
const auto& floor_object_filename = object_filenames.at(floor);
|
||||
if (!floor_object_filename.empty()) {
|
||||
auto map_data = get_file_data(version, floor_object_filename);
|
||||
if (map_data) {
|
||||
map->add_objects_from_map_data(floor, map_data->data(), map_data->size());
|
||||
if (log) {
|
||||
log->info("Loaded objects map %s for floor %02zX", floor_object_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("Objects map %s for floor %02zX cannot be used; skipping", floor_object_filename.c_str(), floor);
|
||||
}
|
||||
if (!any_map_loaded) {
|
||||
throw runtime_error(string_printf("no object maps loaded for floor %zu", floor));
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("No objects to load for floor %02zX", floor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,10 +396,12 @@ void Lobby::load_maps() {
|
||||
this->difficulty,
|
||||
this->event,
|
||||
this->lobby_id,
|
||||
s->set_data_table(this->base_version, this->episode, this->mode, this->difficulty),
|
||||
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
||||
rare_rates,
|
||||
this->random_crypt,
|
||||
this->variations);
|
||||
this->variations,
|
||||
&this->log);
|
||||
|
||||
} else {
|
||||
this->map = make_shared<Map>(this->base_version, this->lobby_id, this->random_crypt);
|
||||
|
||||
+16
-1
@@ -211,10 +211,25 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::shared_ptr<const SetDataTableBase> sdt,
|
||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
std::shared_ptr<PSOLFGEncryption> random_crypt,
|
||||
const parray<le_uint32_t, 0x20>& variations);
|
||||
const parray<le_uint32_t, 0x20>& variations,
|
||||
const PrefixedLogger* log = nullptr);
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
const std::vector<std::string>& enemy_filenames,
|
||||
const std::vector<std::string>& object_filenames,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
std::shared_ptr<PSOLFGEncryption> random_crypt,
|
||||
const PrefixedLogger* log = nullptr);
|
||||
void load_maps();
|
||||
void create_ep3_server();
|
||||
|
||||
|
||||
+192
-1
@@ -1114,6 +1114,196 @@ Action a_disassemble_quest_map(
|
||||
string result = Map::disassemble_quest_data(data.data(), data.size());
|
||||
write_output_data(args, result.data(), result.size(), "txt");
|
||||
});
|
||||
Action a_disassemble_set_data_table(
|
||||
"disassemble-set-data-table", "\
|
||||
disassemble-set-data-table [INPUT-FILENAME]\n\
|
||||
Show the contents of a SetDataTable.rel file. A version option is required.\n",
|
||||
+[](Arguments& args) {
|
||||
Version version = get_cli_version(args);
|
||||
SetDataTable sdt(version, read_input_data(args));
|
||||
string str = sdt.str();
|
||||
write_output_data(args, str.data(), str.size(), "txt");
|
||||
});
|
||||
Action a_check_set_data_table(
|
||||
"check-set-data-tables", nullptr, +[](Arguments&) {
|
||||
ServerState s;
|
||||
s.load_objects_and_upstream_dependents("set_data_tables");
|
||||
static_game_data_log.min_level = LogLevel::DISABLED;
|
||||
|
||||
auto get_file_data = [&](Version version, const string& filename) -> shared_ptr<const string> {
|
||||
try {
|
||||
return s.load_map_file(version, filename);
|
||||
} catch (const cannot_open_file&) {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
auto check_filenames = [&](Version version, const string& sdt_filename, const vector<string>& ns_filenames) -> string {
|
||||
for (size_t z = 0; z < ns_filenames.size(); z++) {
|
||||
const auto& ns_filename = ns_filenames[z];
|
||||
auto data = get_file_data(version, ns_filename);
|
||||
if (data) {
|
||||
if (sdt_filename != ns_filename) {
|
||||
string ns_filenames_str = join(ns_filenames, ", ");
|
||||
return string_printf("SDT => %s, NS => [%s]", sdt_filename.c_str(), ns_filenames_str.c_str());
|
||||
}
|
||||
return "OK";
|
||||
}
|
||||
if (!data && (sdt_filename == ns_filename)) {
|
||||
string ns_filenames_str = join(ns_filenames, ", ");
|
||||
return string_printf("SDT => %s (missing)", sdt_filename.c_str());
|
||||
}
|
||||
}
|
||||
if (ns_filenames.empty() && sdt_filename.empty()) {
|
||||
return "OK (no files)";
|
||||
} else if (ns_filenames.empty()) {
|
||||
auto data = get_file_data(version, sdt_filename);
|
||||
if (data) {
|
||||
return string_printf("NS blank, SDT => %s", sdt_filename.c_str());
|
||||
} else {
|
||||
return string_printf("NS blank, SDT => %s (missing)", sdt_filename.c_str());
|
||||
}
|
||||
} else if (sdt_filename.empty()) {
|
||||
string ns_filenames_str = join(ns_filenames, ", ");
|
||||
return string_printf("SDT blank, NS => [%s] (all missing)", ns_filenames_str.c_str());
|
||||
} else {
|
||||
string ns_filenames_str = join(ns_filenames, ", ");
|
||||
return string_printf("SDT => %s (missing), NS => [%s] (all missing)", sdt_filename.c_str(), ns_filenames_str.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
size_t num_checks = 0;
|
||||
size_t num_errors = 0;
|
||||
auto check_table = [&](Version version) {
|
||||
vector<Episode> episodes({Episode::EP1});
|
||||
if (!is_v1_or_v2(version) || (version == Version::GC_NTE)) {
|
||||
episodes.emplace_back(Episode::EP2);
|
||||
if (is_v4(version)) {
|
||||
episodes.emplace_back(Episode::EP4);
|
||||
}
|
||||
}
|
||||
|
||||
vector<GameMode> modes({GameMode::NORMAL});
|
||||
if (!is_v1(version)) {
|
||||
modes.emplace_back(GameMode::BATTLE);
|
||||
modes.emplace_back(GameMode::CHALLENGE);
|
||||
}
|
||||
if (is_v4(version)) {
|
||||
modes.emplace_back(GameMode::SOLO);
|
||||
}
|
||||
|
||||
uint8_t max_difficulty = is_v1(version) ? 2 : 3;
|
||||
|
||||
for (Episode episode : episodes) {
|
||||
for (GameMode mode : modes) {
|
||||
for (uint8_t difficulty = 0; difficulty <= max_difficulty; difficulty++) {
|
||||
auto sdt = s.set_data_table(version, episode, mode, difficulty);
|
||||
auto ns_var_maxes = variation_maxes_deprecated(version, episode, (mode == GameMode::SOLO));
|
||||
size_t num_floors;
|
||||
if (episode == Episode::EP4) {
|
||||
num_floors = 0x0B;
|
||||
} else if (episode == Episode::EP2) {
|
||||
num_floors = 0x10;
|
||||
} else {
|
||||
num_floors = 0x0F;
|
||||
}
|
||||
for (size_t floor = 0; floor < num_floors; floor++) {
|
||||
auto sdt_var_avail = sdt->num_available_variations_for_floor(episode, floor);
|
||||
auto sdt_var_maxes = sdt->num_free_roam_variations_for_floor(episode, mode == GameMode::SOLO, floor);
|
||||
size_t sdt_var1_max_avail = sdt_var_avail.first - 1;
|
||||
size_t sdt_var2_max_avail = sdt_var_avail.second - 1;
|
||||
size_t sdt_var1_max = sdt_var_maxes.first - 1;
|
||||
size_t sdt_var2_max = sdt_var_maxes.second - 1;
|
||||
size_t ns_var1_max = ns_var_maxes[floor * 2];
|
||||
size_t ns_var2_max = ns_var_maxes[floor * 2 + 1];
|
||||
num_checks += 4;
|
||||
if (sdt_var1_max > sdt_var1_max_avail) {
|
||||
fprintf(stdout, "## %-8s %-10s %-10s %-10s %02zX VAR1:[SDT:%02zX SDTA:%02zX]\n",
|
||||
name_for_enum(version),
|
||||
name_for_episode(episode),
|
||||
name_for_mode(mode),
|
||||
name_for_difficulty(difficulty),
|
||||
floor,
|
||||
sdt_var1_max,
|
||||
sdt_var1_max_avail);
|
||||
num_errors++;
|
||||
}
|
||||
if (sdt_var2_max > sdt_var2_max_avail) {
|
||||
fprintf(stdout, "## %-8s %-10s %-10s %-10s %02zX VAR2:[SDT:%02zX SDTA:%02zX]\n",
|
||||
name_for_enum(version),
|
||||
name_for_episode(episode),
|
||||
name_for_mode(mode),
|
||||
name_for_difficulty(difficulty),
|
||||
floor,
|
||||
sdt_var2_max,
|
||||
sdt_var2_max_avail);
|
||||
num_errors++;
|
||||
}
|
||||
if (sdt_var1_max < ns_var1_max) {
|
||||
fprintf(stdout, "## %-8s %-10s %-10s %-10s %02zX VAR1:[SDT:%02zX NS:%02zX]\n",
|
||||
name_for_enum(version),
|
||||
name_for_episode(episode),
|
||||
name_for_mode(mode),
|
||||
name_for_difficulty(difficulty),
|
||||
floor,
|
||||
sdt_var1_max,
|
||||
ns_var1_max);
|
||||
num_errors++;
|
||||
}
|
||||
if (sdt_var2_max < ns_var2_max) {
|
||||
fprintf(stdout, "## %-8s %-10s %-10s %-10s %02zX VAR2:[SDT:%02zX NS:%02zX]\n",
|
||||
name_for_enum(version),
|
||||
name_for_episode(episode),
|
||||
name_for_mode(mode),
|
||||
name_for_difficulty(difficulty),
|
||||
floor,
|
||||
sdt_var2_max,
|
||||
ns_var2_max);
|
||||
num_errors++;
|
||||
}
|
||||
for (size_t var1 = 0; var1 <= ns_var1_max; var1++) {
|
||||
for (size_t var2 = 0; var2 <= ns_var2_max; var2++) {
|
||||
auto sdt_enemy_filename = sdt->map_filename_for_variation(floor, var1, var2, episode, mode, true);
|
||||
auto sdt_object_filename = sdt->map_filename_for_variation(floor, var1, var2, episode, mode, false);
|
||||
auto ns_enemy_filenames = map_filenames_for_variation_deprecated(floor, var1, var2, version, episode, mode, true);
|
||||
auto ns_object_filenames = map_filenames_for_variation_deprecated(floor, var1, var2, version, episode, mode, false);
|
||||
string enemies_error = check_filenames(version, sdt_enemy_filename, ns_enemy_filenames);
|
||||
string objects_error = check_filenames(version, sdt_object_filename, ns_object_filenames);
|
||||
num_checks += 2;
|
||||
num_errors += (enemies_error != "OK") + (objects_error != "OK");
|
||||
fprintf(stdout, "%s %-8s %-10s %-10s %-10s %02zX %02zX %02zX E:[%s] O:[%s] E:%-30s O:%-30s\n",
|
||||
((enemies_error != "OK") || (objects_error != "OK")) ? "##" : " ",
|
||||
name_for_enum(version),
|
||||
name_for_episode(episode),
|
||||
name_for_mode(mode),
|
||||
name_for_difficulty(difficulty),
|
||||
floor,
|
||||
var1,
|
||||
var2,
|
||||
enemies_error.c_str(),
|
||||
objects_error.c_str(),
|
||||
sdt_enemy_filename.c_str(),
|
||||
sdt_object_filename.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
check_table(Version::DC_NTE);
|
||||
check_table(Version::DC_V1_11_2000_PROTOTYPE);
|
||||
check_table(Version::DC_V1);
|
||||
check_table(Version::DC_V2);
|
||||
check_table(Version::PC_NTE);
|
||||
check_table(Version::PC_V2);
|
||||
check_table(Version::GC_NTE);
|
||||
check_table(Version::GC_V3);
|
||||
check_table(Version::XB_V3);
|
||||
check_table(Version::BB_V4);
|
||||
fprintf(stdout, "%zu/%zu errors\n", num_errors, num_checks);
|
||||
});
|
||||
|
||||
Action a_assemble_quest_script(
|
||||
"assemble-quest-script", "\
|
||||
@@ -1883,7 +2073,7 @@ Action a_find_rare_enemy_seeds(
|
||||
map = Lobby::load_maps(version, episode, difficulty, 0, 0, rare_rates, random_crypt, vq);
|
||||
|
||||
} else {
|
||||
generate_variations(variations, random_crypt, version, episode, (mode == GameMode::SOLO));
|
||||
generate_variations_deprecated(variations, random_crypt, version, episode, (mode == GameMode::SOLO));
|
||||
map = Lobby::load_maps(
|
||||
version,
|
||||
episode,
|
||||
@@ -1891,6 +2081,7 @@ Action a_find_rare_enemy_seeds(
|
||||
difficulty,
|
||||
0,
|
||||
0,
|
||||
s.set_data_table(version, episode, mode, difficulty),
|
||||
bind(&ServerState::load_map_file, &s, placeholders::_1, placeholders::_2),
|
||||
rare_rates,
|
||||
random_crypt,
|
||||
|
||||
+274
-61
@@ -1637,8 +1637,59 @@ string Map::disassemble_quest_data(const void* data, size_t size) {
|
||||
return join(ret, "\n") + "\n";
|
||||
}
|
||||
|
||||
SetDataTable::SetDataTable(shared_ptr<const string> data, bool big_endian) {
|
||||
if (big_endian) {
|
||||
SetDataTableBase::SetDataTableBase(Version version) : version(version) {}
|
||||
|
||||
parray<le_uint32_t, 0x20> SetDataTableBase::generate_variations(
|
||||
Episode episode, bool is_solo, std::shared_ptr<PSOLFGEncryption> random_crypt) const {
|
||||
parray<le_uint32_t, 0x20> ret;
|
||||
for (size_t floor = 0; floor < 0x10; floor++) {
|
||||
auto num_vars = this->num_free_roam_variations_for_floor(episode, is_solo, floor);
|
||||
ret[floor * 2] = (num_vars.first > 1) ? (random_crypt->next() % num_vars.first) : 0;
|
||||
ret[floor * 2 + 1] = (num_vars.second > 1) ? (random_crypt->next() % num_vars.second) : 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<string> SetDataTableBase::map_filenames_for_variations(
|
||||
const parray<le_uint32_t, 0x20>& variations, Episode episode, GameMode mode, bool is_enemies) const {
|
||||
vector<string> ret;
|
||||
for (uint8_t floor = 0; floor < 0x10; floor++) {
|
||||
ret.emplace_back(this->map_filename_for_variation(
|
||||
floor, variations[floor * 2], variations[floor * 2 + 1], episode, mode, is_enemies));
|
||||
}
|
||||
for (uint8_t floor = 0x10; floor < 0x12; floor++) {
|
||||
ret.emplace_back(this->map_filename_for_variation(floor, 0, 0, episode, mode, is_enemies));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t SetDataTableBase::default_area_for_floor(Episode episode, uint8_t floor) const {
|
||||
// For some inscrutable reason, Pioneer 2's area number in Episode 4 is
|
||||
// discontiguous with all the rest. Why, Sega??
|
||||
static const std::array<uint8_t, 0x12> areas_ep1 = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11};
|
||||
static const std::array<uint8_t, 0x12> areas_ep2_gc_nte = {
|
||||
0x00, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0xFF, 0xFF};
|
||||
static const std::array<uint8_t, 0x12> areas_ep2 = {
|
||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23};
|
||||
static const std::array<uint8_t, 0x12> areas_ep4 = {
|
||||
0x2D, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
return areas_ep1.at(floor);
|
||||
case Episode::EP2: {
|
||||
const auto& areas = ((this->version == Version::GC_NTE) ? areas_ep2_gc_nte : areas_ep2);
|
||||
return areas.at(floor);
|
||||
}
|
||||
case Episode::EP4:
|
||||
return areas_ep4.at(floor);
|
||||
default:
|
||||
throw logic_error("incorrect episode");
|
||||
}
|
||||
}
|
||||
|
||||
SetDataTable::SetDataTable(Version version, const string& data) : SetDataTableBase(version) {
|
||||
if (is_big_endian(this->version)) {
|
||||
this->load_table_t<true>(data);
|
||||
} else {
|
||||
this->load_table_t<false>(data);
|
||||
@@ -1646,10 +1697,10 @@ SetDataTable::SetDataTable(shared_ptr<const string> data, bool big_endian) {
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
void SetDataTable::load_table_t(shared_ptr<const string> data) {
|
||||
void SetDataTable::load_table_t(const string& data) {
|
||||
using U32T = typename conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
StringReader r(*data);
|
||||
StringReader r(data);
|
||||
|
||||
struct Footer {
|
||||
U32T table3_offset;
|
||||
@@ -1688,17 +1739,99 @@ void SetDataTable::load_table_t(shared_ptr<const string> data) {
|
||||
}
|
||||
}
|
||||
|
||||
void SetDataTable::print(FILE* stream) const {
|
||||
pair<uint32_t, uint32_t> SetDataTable::num_available_variations_for_floor(Episode episode, uint8_t floor) const {
|
||||
uint8_t area = this->default_area_for_floor(episode, floor);
|
||||
if (area == 0xFF) {
|
||||
return make_pair(1, 1);
|
||||
} else {
|
||||
const auto& e = this->entries.at(area);
|
||||
return make_pair(e.size(), e.at(0).size());
|
||||
}
|
||||
}
|
||||
|
||||
pair<uint32_t, uint32_t> SetDataTable::num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const {
|
||||
uint8_t area = this->default_area_for_floor(episode, floor);
|
||||
if (area == 0xFF) {
|
||||
return make_pair(1, 1);
|
||||
}
|
||||
static const array<uint32_t, 0x2F * 2> counts_on = {
|
||||
// Episode 1 (00-11)
|
||||
// P2 -F1-, -F2-, -C1-, -C2-, -C3-, -M1-, -M2-, -R1-, -R2-, -R3-, DRGN, DRL-, -VO-, -DF-, LOBBY, VS1-, VS2-,
|
||||
1, 1, 1, 5, 1, 5, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1,
|
||||
// Episode 2 (12-23)
|
||||
// P2 VRTA, VRTB, VRSA, VRSB, CCA-, -JN-, -JS-, MNTN, SEAS, SBU-, SBL-, -GG-, -OF-, -BR-, -GD-, SSN-, TWR-,
|
||||
1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 3, 1, 3, 1, 3, 2, 2, 1, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
// Episode 4 (24-2E)
|
||||
// CE -CW-, -CS-, -CN-, -CI-, DES1, DES2, DES3, SMIL, -P2-, TEST
|
||||
1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1};
|
||||
static const array<uint32_t, 0x2F * 2> counts_off = {
|
||||
// Episode 1 (00-11)
|
||||
// P2 -F1-, -F2-, -C1-, -C2-, -C3-, -M1-, -M2-, -R1-, -R2-, -R3-, DRGN, DRL-, -VO-, -DF-, LOBBY, VS1-, VS2-,
|
||||
1, 1, 1, 3, 1, 3, 3, 1, 3, 1, 3, 1, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1,
|
||||
// Episode 2 (12-23)
|
||||
// P2 VRTA, VRTB, VRSA, VRSB, CCA-, -JN-, -JS-, MNTN, SEAS, SBU-, SBL-, -GG-, -OF-, -BR-, -GD-, SSN-, TWR-,
|
||||
1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 3, 1, 3, 1, 3, 2, 2, 1, 3, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
// Episode 4 (24-2E)
|
||||
// CE -CW-, -CS-, -CN-, -CI-, DES1, DES2, DES3, SMIL, -P2-, TEST
|
||||
1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1};
|
||||
const auto& data = is_solo ? counts_off : counts_on;
|
||||
if ((floor * 2 + 1) < data.size()) {
|
||||
auto available = this->num_available_variations_for_floor(episode, floor);
|
||||
return make_pair(min<uint32_t>(available.first, data[area * 2]), min<uint32_t>(available.second, data[area * 2 + 1]));
|
||||
}
|
||||
throw runtime_error("invalid area");
|
||||
}
|
||||
|
||||
string SetDataTable::map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, bool is_enemies) const {
|
||||
uint8_t area = this->default_area_for_floor(episode, floor);
|
||||
if (area == 0xFF) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (area >= this->entries.size()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const auto& entry = this->entries.at(area).at(var1).at(var2);
|
||||
string filename = is_enemies ? entry.enemy_list_basename : entry.object_list_basename;
|
||||
|
||||
filename += (is_enemies ? "e" : "o");
|
||||
|
||||
switch ((floor != 0) ? GameMode::NORMAL : mode) {
|
||||
case GameMode::NORMAL:
|
||||
filename += ".dat";
|
||||
break;
|
||||
case GameMode::SOLO:
|
||||
filename += "_s.dat";
|
||||
break;
|
||||
case GameMode::CHALLENGE:
|
||||
filename += "_c1.dat";
|
||||
break;
|
||||
case GameMode::BATTLE:
|
||||
filename += "_d.dat";
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid game mode");
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
string SetDataTable::str() const {
|
||||
vector<string> lines;
|
||||
lines.emplace_back(string_printf("FL/V1/V2 => ----------------------OBJECT -----------------------ENEMY -----------------------EVENT\n"));
|
||||
for (size_t a = 0; a < this->entries.size(); a++) {
|
||||
const auto& v1_v = this->entries[a];
|
||||
for (size_t v1 = 0; v1 < v1_v.size(); v1++) {
|
||||
const auto& v2_v = v1_v[v1];
|
||||
for (size_t v2 = 0; v2 < v2_v.size(); v2++) {
|
||||
const auto& e = v2_v[v2];
|
||||
fprintf(stream, "[%02zX/%02zX/%02zX] %s %s %s\n", a, v1, v2, e.object_list_basename.c_str(), e.enemy_list_basename.c_str(), e.event_list_basename.c_str());
|
||||
lines.emplace_back(string_printf("%02zX/%02zX/%02zX => %28s %28s %28s\n", a, v1, v2, e.object_list_basename.c_str(), e.enemy_list_basename.c_str(), e.event_list_basename.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return join(lines, "");
|
||||
}
|
||||
|
||||
struct AreaMapFileInfo {
|
||||
@@ -1715,23 +1848,97 @@ struct AreaMapFileInfo {
|
||||
variation2_values(variation2_values) {}
|
||||
};
|
||||
|
||||
const array<vector<vector<string>>, 0x10> SetDataTableDCNTE::NAMES = {{
|
||||
/* 00 */ {{"map_city00_00"}},
|
||||
/* 01 */ {{"map_forest01_00", "map_forest01_01"}},
|
||||
/* 02 */ {{"map_forest02_00", "map_forest02_03"}},
|
||||
/* 03 */ {{"map_cave01_00_00", "map_cave01_00_01"}, {"map_cave01_01_00", "map_cave01_01_01"}},
|
||||
/* 04 */ {{"map_cave02_00_00", "map_cave02_00_01"}, {"map_cave02_01_00", "map_cave02_01_01"}},
|
||||
/* 05 */ {{"map_cave03_00_00", "map_cave03_00_01"}, {"map_cave03_01_00", "map_cave03_01_01"}},
|
||||
/* 06 */ {{"map_machine01_00_00", "map_machine01_00_01"}},
|
||||
/* 07 */ {{"map_machine02_00_00", "map_machine02_00_01"}},
|
||||
/* 08 */ {{"map_ancient01_00_00", "map_ancient01_00_01"}, {"map_ancient01_01_00", "map_ancient01_01_01"}},
|
||||
/* 09 */ {{"map_ancient02_00_00", "map_ancient02_00_01"}, {"map_ancient02_01_00", "map_ancient02_01_01"}},
|
||||
/* 0A */ {{"map_ancient03_00_00", "map_ancient03_00_01"}, {"map_ancient03_01_00", "map_ancient03_01_01"}},
|
||||
/* 0B */ {{"map_boss01"}},
|
||||
/* 0C */ {{"map_boss02"}},
|
||||
/* 0D */ {{"map_boss03"}},
|
||||
/* 0E */ {{"map_boss04"}},
|
||||
/* 0F */ {{"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}},
|
||||
}};
|
||||
|
||||
SetDataTableDCNTE::SetDataTableDCNTE() : SetDataTableBase(Version::DC_NTE) {}
|
||||
|
||||
pair<uint32_t, uint32_t> SetDataTableDCNTE::num_available_variations_for_floor(Episode, uint8_t floor) const {
|
||||
return make_pair(this->NAMES[floor].size(), this->NAMES[floor][0].size());
|
||||
}
|
||||
|
||||
pair<uint32_t, uint32_t> SetDataTableDCNTE::num_free_roam_variations_for_floor(Episode episode, bool, uint8_t floor) const {
|
||||
return this->num_available_variations_for_floor(episode, floor);
|
||||
}
|
||||
|
||||
string SetDataTableDCNTE::map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode, GameMode, bool is_enemies) const {
|
||||
if (floor >= this->NAMES.size()) {
|
||||
return "";
|
||||
}
|
||||
return this->NAMES.at(floor).at(var1).at(var2) + (is_enemies ? "e.dat" : "o.dat");
|
||||
}
|
||||
|
||||
const array<vector<vector<string>>, 0x10> SetDataTableDC112000::NAMES = {{
|
||||
/* 00 */ {{"map_city00_00"}},
|
||||
/* 01 */ {{"map_forest01_00", "map_forest01_01", "map_forest01_02", "map_forest01_03", "map_forest01_04"}},
|
||||
/* 02 */ {{"map_forest02_00", "map_forest02_01", "map_forest02_02", "map_forest02_03", "map_forest02_04"}},
|
||||
/* 03 */ {{"map_cave01_00_00", "map_cave01_00_01"}, {"map_cave01_01_00", "map_cave01_01_01"}, {"map_cave01_02_00", "map_cave01_02_01"}},
|
||||
/* 04 */ {{"map_cave02_00_00", "map_cave02_00_01"}, {"map_cave02_01_00", "map_cave02_01_01"}, {"map_cave02_02_00", "map_cave02_02_01"}},
|
||||
/* 05 */ {{"map_cave03_00_00", "map_cave03_00_01"}, {"map_cave03_01_00", "map_cave03_01_01"}, {"map_cave03_02_00", "map_cave03_02_01"}},
|
||||
/* 06 */ {{"map_machine01_00_00", "map_machine01_00_01"}, {"map_machine01_01_00", "map_machine01_01_01"}, {"map_machine01_02_00", "map_machine01_02_01"}},
|
||||
/* 07 */ {{"map_machine02_00_00", "map_machine02_00_01"}, {"map_machine02_01_00", "map_machine02_01_01"}, {"map_machine02_02_00", "map_machine02_02_01"}},
|
||||
/* 08 */ {{"map_ancient01_00_00", "map_ancient01_00_01"}, {"map_ancient01_01_00", "map_ancient01_01_01"}, {"map_ancient01_02_00", "map_ancient01_02_01"}},
|
||||
/* 09 */ {{"map_ancient02_00_00", "map_ancient02_00_01"}, {"map_ancient02_01_00", "map_ancient02_01_01"}, {"map_ancient02_02_00", "map_ancient02_02_01"}},
|
||||
/* 0A */ {{"map_ancient03_00_00", "map_ancient03_00_01"}, {"map_ancient03_01_00", "map_ancient03_01_01"}, {"map_ancient03_02_00", "map_ancient03_02_01"}},
|
||||
/* 0B */ {{"map_boss01"}},
|
||||
/* 0C */ {{"map_boss02"}},
|
||||
/* 0D */ {{"map_boss03"}},
|
||||
/* 0E */ {{"map_boss04"}},
|
||||
/* 0F */ {{"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}, {"map_visuallobby"}},
|
||||
}};
|
||||
|
||||
SetDataTableDC112000::SetDataTableDC112000() : SetDataTableBase(Version::DC_V1_11_2000_PROTOTYPE) {}
|
||||
|
||||
pair<uint32_t, uint32_t> SetDataTableDC112000::num_available_variations_for_floor(Episode, uint8_t floor) const {
|
||||
return make_pair(this->NAMES[floor].size(), this->NAMES[floor][0].size());
|
||||
}
|
||||
|
||||
pair<uint32_t, uint32_t> SetDataTableDC112000::num_free_roam_variations_for_floor(Episode episode, bool, uint8_t floor) const {
|
||||
return this->num_available_variations_for_floor(episode, floor);
|
||||
}
|
||||
|
||||
string SetDataTableDC112000::map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode, GameMode, bool is_enemies) const {
|
||||
if (floor >= this->NAMES.size()) {
|
||||
return "";
|
||||
}
|
||||
return this->NAMES.at(floor).at(var1).at(var2) + (is_enemies ? "e.dat" : "o.dat");
|
||||
}
|
||||
|
||||
static const vector<AreaMapFileInfo> map_file_info_dc_nte = {
|
||||
{"city00", {}, {0}},
|
||||
{"forest01", {}, {0, 1, 2, 3, 4}},
|
||||
{"forest02", {}, {0, 1, 2, 3, 4}},
|
||||
{"cave01", {0, 1, 2}, {0, 1}},
|
||||
{"cave02", {0, 1, 2}, {0, 1}},
|
||||
{"cave03", {0, 1, 2}, {0, 1}},
|
||||
{"machine01", {0, 1}, {0, 1}},
|
||||
{"machine02", {0, 1}, {0, 1}},
|
||||
{"ancient01", {0, 1}, {0, 1}},
|
||||
{"ancient02", {0, 1}, {0, 1}},
|
||||
{"ancient03", {0, 1}, {0, 1}},
|
||||
{"forest01", {}, {0, 1}},
|
||||
{"forest02", {}, {0, 3}},
|
||||
{"cave01", {0, 1}, {0, 1}},
|
||||
{"cave02", {0, 1}, {0, 1}},
|
||||
{"cave03", {0, 1}, {0, 1}},
|
||||
{"machine01", {0}, {0, 1}},
|
||||
{"machine02", {0}, {0, 1}},
|
||||
{"ancient01", {0}, {0, 1}},
|
||||
{"ancient02", {0}, {0, 1}},
|
||||
{"ancient03", {0}, {0, 1}},
|
||||
{"boss01", {}, {}},
|
||||
{"boss02", {}, {}},
|
||||
{"boss03", {}, {}},
|
||||
{"boss04", {}, {}},
|
||||
{nullptr, {}, {}},
|
||||
{"map_visuallobby", {}, {}},
|
||||
};
|
||||
|
||||
static const vector<vector<AreaMapFileInfo>> map_file_info_gc_nte = {
|
||||
@@ -1752,13 +1959,13 @@ static const vector<vector<AreaMapFileInfo>> map_file_info_gc_nte = {
|
||||
{"boss02", {}, {}},
|
||||
{"boss03", {}, {}},
|
||||
{"boss04", {}, {}},
|
||||
{nullptr, {}, {}},
|
||||
{"lobby_01", {}, {}},
|
||||
},
|
||||
{
|
||||
// Episode 2 Non-solo
|
||||
{"labo00", {}, {0}},
|
||||
{"ruins01", {0, 1}, {0}},
|
||||
{"ruins02", {0, 1}, {0}},
|
||||
{"ruins01", {0}, {0}},
|
||||
{"ruins02", {0}, {0}},
|
||||
{"space01", {0, 1}, {0}},
|
||||
{"space02", {0, 1}, {0}},
|
||||
{"jungle01", {}, {0, 1}},
|
||||
@@ -1766,8 +1973,8 @@ static const vector<vector<AreaMapFileInfo>> map_file_info_gc_nte = {
|
||||
{"jungle03", {}, {0, 1}},
|
||||
{"jungle04", {0, 1}, {0}},
|
||||
{"jungle05", {}, {0, 1}},
|
||||
{nullptr, {}, {}},
|
||||
{nullptr, {}, {}},
|
||||
{"seabed01", {0, 1}, {0}},
|
||||
{"seabed02", {0}, {0}},
|
||||
{"boss05", {}, {}},
|
||||
{"boss06", {}, {}},
|
||||
{"boss07", {}, {}},
|
||||
@@ -1796,7 +2003,7 @@ static const vector<vector<vector<AreaMapFileInfo>>> map_file_info = {
|
||||
{"boss02", {}, {}},
|
||||
{"boss03", {}, {}},
|
||||
{"boss04", {}, {}},
|
||||
{nullptr, {}, {}},
|
||||
{"lobby_01", {}, {}},
|
||||
},
|
||||
{
|
||||
// Solo
|
||||
@@ -1873,7 +2080,7 @@ static const vector<vector<vector<AreaMapFileInfo>>> map_file_info = {
|
||||
{"desert02", {0}, {0, 1, 2}},
|
||||
{"desert03", {0, 1, 2}, {0}},
|
||||
{"boss09", {0}, {0}},
|
||||
{nullptr, {}, {}},
|
||||
{"test01", {0}, {0}},
|
||||
{nullptr, {}, {}},
|
||||
{nullptr, {}, {}},
|
||||
{nullptr, {}, {}},
|
||||
@@ -1892,7 +2099,7 @@ static const vector<vector<vector<AreaMapFileInfo>>> map_file_info = {
|
||||
{"desert02", {0}, {0, 1, 2}},
|
||||
{"desert03", {0, 1, 2}, {0}},
|
||||
{"boss09", {0}, {0}},
|
||||
{nullptr, {}, {}},
|
||||
{"test01", {0}, {0}},
|
||||
{nullptr, {}, {}},
|
||||
{nullptr, {}, {}},
|
||||
{nullptr, {}, {}},
|
||||
@@ -1902,7 +2109,7 @@ static const vector<vector<vector<AreaMapFileInfo>>> map_file_info = {
|
||||
},
|
||||
};
|
||||
|
||||
const AreaMapFileInfo& file_info_for_variation(
|
||||
const AreaMapFileInfo& file_info_for_variation_deprecated(
|
||||
Version version, Episode episode, uint8_t area, bool is_solo) {
|
||||
const vector<AreaMapFileInfo>* multi_index = nullptr;
|
||||
const vector<AreaMapFileInfo>* solo_index = nullptr;
|
||||
@@ -1919,8 +2126,7 @@ const AreaMapFileInfo& file_info_for_variation(
|
||||
default:
|
||||
throw invalid_argument("episode has no maps");
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
multi_index = &map_file_info.at(0).at(0);
|
||||
@@ -1952,14 +2158,14 @@ const AreaMapFileInfo& file_info_for_variation(
|
||||
return multi_index->at(area);
|
||||
}
|
||||
|
||||
void generate_variations(
|
||||
void generate_variations_deprecated(
|
||||
parray<le_uint32_t, 0x20>& variations,
|
||||
shared_ptr<PSOLFGEncryption> random_crypt,
|
||||
Version version,
|
||||
Episode episode,
|
||||
bool is_solo) {
|
||||
for (size_t z = 0; z < 0x10; z++) {
|
||||
const auto& a = file_info_for_variation(version, episode, z, is_solo);
|
||||
const auto& a = file_info_for_variation_deprecated(version, episode, z, is_solo);
|
||||
if (!a.name_token) {
|
||||
variations[z * 2 + 0] = 0;
|
||||
variations[z * 2 + 1] = 0;
|
||||
@@ -1970,10 +2176,10 @@ void generate_variations(
|
||||
}
|
||||
}
|
||||
|
||||
vector<parray<le_uint32_t, 0x20>> generate_all_possible_variations(Version version, Episode episode, bool is_solo) {
|
||||
parray<uint32_t, 0x20> maxes;
|
||||
parray<le_uint32_t, 0x20> variation_maxes_deprecated(Version version, Episode episode, bool is_solo) {
|
||||
parray<le_uint32_t, 0x20> maxes;
|
||||
for (size_t z = 0; z < 0x10; z++) {
|
||||
const auto& a = file_info_for_variation(version, episode, z, is_solo);
|
||||
const auto& a = file_info_for_variation_deprecated(version, episode, z, is_solo);
|
||||
if (!a.name_token) {
|
||||
maxes[z * 2 + 0] = 0;
|
||||
maxes[z * 2 + 1] = 0;
|
||||
@@ -1982,38 +2188,32 @@ vector<parray<le_uint32_t, 0x20>> generate_all_possible_variations(Version versi
|
||||
maxes[z * 2 + 1] = (a.variation2_values.size() <= 1) ? 0 : (a.variation2_values.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
vector<parray<le_uint32_t, 0x20>> ret;
|
||||
parray<le_uint32_t, 0x20> current;
|
||||
for (;;) {
|
||||
ret.emplace_back(current);
|
||||
|
||||
// Increment current by 1 as if it were an 0x20-place integer, with each
|
||||
// "place" having a base of maxes[x] + 1
|
||||
ssize_t x;
|
||||
for (x = 0x1F; x >= 0; x--) {
|
||||
if (current[x] < maxes[x]) {
|
||||
current[x]++;
|
||||
break;
|
||||
} else {
|
||||
current[x] = 0;
|
||||
}
|
||||
}
|
||||
if (x < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return maxes;
|
||||
}
|
||||
|
||||
vector<string> map_filenames_for_variation(
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
bool next_variation_deprecated(parray<le_uint32_t, 0x20>& variations, Version version, Episode episode, bool is_solo) {
|
||||
auto maxes = variation_maxes_deprecated(version, episode, is_solo);
|
||||
|
||||
// Increment variations by 1 as if it were an 0x20-place integer, with each
|
||||
// "place" having a base of maxes[x] + 1
|
||||
for (ssize_t x = 0x1F; x >= 0; x--) {
|
||||
if (variations[x] < maxes[x]) {
|
||||
variations[x]++;
|
||||
return true;
|
||||
} else {
|
||||
variations[x] = 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
vector<string> map_filenames_for_variation_deprecated(
|
||||
uint8_t floor,
|
||||
uint32_t var1,
|
||||
uint32_t var2,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
bool is_enemies) {
|
||||
// Map filenames are like map_<name_token>_<VV>_<VV>(_off)?(e|o)(_s|_c1)?.dat
|
||||
// name_token comes from AreaMapFileInfo
|
||||
@@ -2024,7 +2224,7 @@ vector<string> map_filenames_for_variation(
|
||||
// _c1 is used for the city map in Challenge mode (which we don't load,
|
||||
// since it contains only NPCs and not enemies)
|
||||
// e|o specifies what kind of data: e = enemies, o = objects
|
||||
const auto& a = file_info_for_variation(version, episode, floor, mode == GameMode::SOLO);
|
||||
const auto& a = file_info_for_variation_deprecated(version, episode, floor, mode == GameMode::SOLO);
|
||||
if (!a.name_token) {
|
||||
return vector<string>();
|
||||
}
|
||||
@@ -2067,5 +2267,18 @@ vector<string> map_filenames_for_variation(
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<vector<string>> map_filenames_for_variations_deprecated(
|
||||
const parray<le_uint32_t, 0x20>& variations,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
bool is_enemies) {
|
||||
vector<vector<string>> ret;
|
||||
for (size_t z = 0; z < 0x10; z++) {
|
||||
ret.emplace_back(map_filenames_for_variation_deprecated(z, variations[z * 2], variations[z * 2 + 1], version, episode, mode, is_enemies));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const shared_ptr<const Map::RareEnemyRates> Map::NO_RARE_ENEMIES = make_shared<Map::RareEnemyRates>(0, 0);
|
||||
const shared_ptr<const Map::RareEnemyRates> Map::DEFAULT_RARE_ENEMIES = make_shared<Map::RareEnemyRates>(0x0083126E, 0x1999999A);
|
||||
|
||||
+70
-16
@@ -321,11 +321,28 @@ struct Map {
|
||||
std::vector<size_t> rare_enemy_indexes;
|
||||
};
|
||||
|
||||
// TODO: This class is currently unused. It would be nice if we could use this
|
||||
// to generate variations and link to the corresponding map filenames, but it
|
||||
// seems that SetDataTable.rel files link to map filenames that don't actually
|
||||
// exist in some cases, so we can't just directly use this data structure.
|
||||
class SetDataTable {
|
||||
class SetDataTableBase {
|
||||
public:
|
||||
virtual ~SetDataTableBase() = default;
|
||||
|
||||
parray<le_uint32_t, 0x20> generate_variations(Episode episode, bool is_solo, std::shared_ptr<PSOLFGEncryption> random_crypt) const;
|
||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const = 0;
|
||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const = 0;
|
||||
|
||||
virtual std::string map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, bool is_enemies) const = 0;
|
||||
std::vector<std::string> map_filenames_for_variations(
|
||||
const parray<le_uint32_t, 0x20>& variations, Episode episode, GameMode mode, bool is_enemies) const;
|
||||
|
||||
uint8_t default_area_for_floor(Episode episode, uint8_t floor) const;
|
||||
|
||||
protected:
|
||||
explicit SetDataTableBase(Version version);
|
||||
|
||||
Version version;
|
||||
};
|
||||
|
||||
class SetDataTable : public SetDataTableBase {
|
||||
public:
|
||||
struct SetEntry {
|
||||
std::string object_list_basename;
|
||||
@@ -333,31 +350,68 @@ public:
|
||||
std::string event_list_basename;
|
||||
};
|
||||
|
||||
SetDataTable(std::shared_ptr<const std::string> data, bool big_endian);
|
||||
SetDataTable(Version version, const std::string& data);
|
||||
virtual ~SetDataTable() = default;
|
||||
|
||||
inline const std::vector<std::vector<std::vector<SetEntry>>> get() const {
|
||||
return this->entries;
|
||||
}
|
||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
|
||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
|
||||
virtual std::string map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, bool is_enemies) const;
|
||||
|
||||
void print(FILE* stream) const;
|
||||
std::string str() const;
|
||||
|
||||
private:
|
||||
template <bool IsBigEndian>
|
||||
void load_table_t(std::shared_ptr<const std::string> data);
|
||||
void load_table_t(const std::string& data);
|
||||
|
||||
// Indexes are [floor][variation1][variation2]
|
||||
// floor is cumulative per episode, so Ep2 starts at floor=18.
|
||||
std::vector<std::vector<std::vector<SetEntry>>> entries;
|
||||
};
|
||||
|
||||
void generate_variations(
|
||||
class SetDataTableDCNTE : public SetDataTableBase {
|
||||
public:
|
||||
SetDataTableDCNTE();
|
||||
virtual ~SetDataTableDCNTE() = default;
|
||||
|
||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
|
||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
|
||||
virtual std::string map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, bool is_enemies) const;
|
||||
|
||||
private:
|
||||
static const std::array<std::vector<std::vector<std::string>>, 0x10> NAMES;
|
||||
};
|
||||
|
||||
class SetDataTableDC112000 : public SetDataTableBase {
|
||||
public:
|
||||
SetDataTableDC112000();
|
||||
virtual ~SetDataTableDC112000() = default;
|
||||
|
||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
|
||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
|
||||
virtual std::string map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, bool is_enemies) const;
|
||||
|
||||
private:
|
||||
static const std::array<std::vector<std::vector<std::string>>, 0x10> NAMES;
|
||||
};
|
||||
|
||||
void generate_variations_deprecated(
|
||||
parray<le_uint32_t, 0x20>& variations,
|
||||
std::shared_ptr<PSOLFGEncryption> random,
|
||||
Version version,
|
||||
Episode episode,
|
||||
bool is_solo);
|
||||
std::vector<parray<le_uint32_t, 0x20>> generate_all_possible_variations(
|
||||
Version version, Episode episode, bool is_solo);
|
||||
|
||||
std::vector<std::string> map_filenames_for_variation(
|
||||
Version version, Episode episode, GameMode mode, uint8_t floor, uint32_t var1, uint32_t var2, bool is_enemies);
|
||||
parray<le_uint32_t, 0x20> variation_maxes_deprecated(Version version, Episode episode, bool is_solo);
|
||||
bool next_variation_deprecated(parray<le_uint32_t, 0x20>& variations, Version version, Episode episode, bool is_solo);
|
||||
|
||||
std::vector<std::string> map_filenames_for_variation_deprecated(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Version version, Episode episode, GameMode mode, bool is_enemies);
|
||||
std::vector<std::vector<std::string>> map_filenames_for_variations_deprecated(
|
||||
const parray<le_uint32_t, 0x20>& variations,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
bool is_enemies);
|
||||
|
||||
+13
-2
@@ -4200,8 +4200,19 @@ shared_ptr<Lobby> create_game_generic(
|
||||
game->rare_enemy_rates = s->rare_enemy_rates_by_difficulty.at(game->difficulty);
|
||||
}
|
||||
|
||||
generate_variations(game->variations, game->random_crypt, game->base_version, game->episode, is_solo);
|
||||
game->load_maps();
|
||||
if (game->episode != Episode::EP3) {
|
||||
// GC NTE ignores the passed-in variations and always uses all zeroes
|
||||
if (game->base_version != Version::GC_NTE) {
|
||||
auto sdt = s->set_data_table(game->base_version, game->episode, game->mode, game->difficulty);
|
||||
game->variations = sdt->generate_variations(game->episode, is_solo, game->random_crypt);
|
||||
} else {
|
||||
game->variations.clear(0);
|
||||
}
|
||||
game->load_maps();
|
||||
} else {
|
||||
game->variations.clear(0);
|
||||
game->map = make_shared<Map>(game->base_version, game->lobby_id, game->random_crypt);
|
||||
}
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
+54
-22
@@ -31,8 +31,8 @@ ServerState::QuestF960Result::QuestF960Result(const JSON& json, std::shared_ptr<
|
||||
}
|
||||
|
||||
ServerState::ServerState(const string& config_filename)
|
||||
: creation_time(now()),
|
||||
config_filename(config_filename) {
|
||||
: creation_time(now()),
|
||||
config_filename(config_filename) {
|
||||
this->create_load_step_graph();
|
||||
}
|
||||
|
||||
@@ -371,6 +371,21 @@ void ServerState::dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<ServerState*>(ctx)->lobbies_to_destroy.clear();
|
||||
}
|
||||
|
||||
shared_ptr<const SetDataTableBase> ServerState::set_data_table(
|
||||
Version version, Episode episode, GameMode mode, uint8_t difficulty) const {
|
||||
bool use_ult_tables = ((episode == Episode::EP1) && (difficulty == 3) && !is_v1(version) && (version != Version::PC_NTE));
|
||||
if (mode == GameMode::SOLO && is_v4(version)) {
|
||||
return use_ult_tables ? this->bb_solo_set_data_table_ep1_ult : this->bb_solo_set_data_table;
|
||||
}
|
||||
|
||||
const auto& tables = use_ult_tables ? this->set_data_tables_ep1_ult : this->set_data_tables;
|
||||
auto ret = tables.at(static_cast<size_t>(version));
|
||||
if (ret == nullptr) {
|
||||
throw runtime_error("no set data table exists for this version");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<const ItemParameterTable> ServerState::item_parameter_table(Version version) const {
|
||||
auto ret = this->item_parameter_tables.at(static_cast<size_t>(version));
|
||||
if (ret == nullptr) {
|
||||
@@ -450,11 +465,8 @@ shared_ptr<const string> ServerState::load_bb_file(
|
||||
// First, look in the patch tree's data directory
|
||||
string patch_index_path = "./data/" + patch_index_filename;
|
||||
try {
|
||||
auto ret = this->bb_patch_file_index->get(patch_index_path)->load_data();
|
||||
static_game_data_log.info("Loaded %s from file in BB patch tree", patch_index_path.c_str());
|
||||
return ret;
|
||||
return this->bb_patch_file_index->get(patch_index_path)->load_data();
|
||||
} catch (const out_of_range&) {
|
||||
static_game_data_log.info("%s missing from BB patch tree", patch_index_path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,11 +476,8 @@ shared_ptr<const string> ServerState::load_bb_file(
|
||||
try {
|
||||
// TODO: It's kinda not great that we copy the data here; find a way to
|
||||
// avoid doing this (also in the below case)
|
||||
auto ret = make_shared<string>(this->bb_data_gsl->get_copy(effective_gsl_filename));
|
||||
static_game_data_log.info("Loaded %s from data.gsl in BB patch tree", effective_gsl_filename.c_str());
|
||||
return ret;
|
||||
return make_shared<string>(this->bb_data_gsl->get_copy(effective_gsl_filename));
|
||||
} catch (const out_of_range&) {
|
||||
static_game_data_log.info("%s missing from data.gsl in BB patch tree", effective_gsl_filename.c_str());
|
||||
}
|
||||
|
||||
// Third, look in data.gsl without the filename extension
|
||||
@@ -476,11 +485,8 @@ shared_ptr<const string> ServerState::load_bb_file(
|
||||
if (dot_offset != string::npos) {
|
||||
string no_ext_gsl_filename = effective_gsl_filename.substr(0, dot_offset);
|
||||
try {
|
||||
auto ret = make_shared<string>(this->bb_data_gsl->get_copy(no_ext_gsl_filename));
|
||||
static_game_data_log.info("Loaded %s from data.gsl in BB patch tree", no_ext_gsl_filename.c_str());
|
||||
return ret;
|
||||
return make_shared<string>(this->bb_data_gsl->get_copy(no_ext_gsl_filename));
|
||||
} catch (const out_of_range&) {
|
||||
static_game_data_log.info("%s missing from data.gsl in BB patch tree", no_ext_gsl_filename.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -490,11 +496,8 @@ shared_ptr<const string> ServerState::load_bb_file(
|
||||
static FileContentsCache cache(10 * 60 * 1000 * 1000); // 10 minutes
|
||||
try {
|
||||
auto ret = cache.get_or_load("system/blueburst/" + effective_bb_directory_filename);
|
||||
static_game_data_log.info("Loaded %s", effective_bb_directory_filename.c_str());
|
||||
return ret.file->data;
|
||||
} catch (const exception& e) {
|
||||
static_game_data_log.info("%s missing from system/blueburst", effective_bb_directory_filename.c_str());
|
||||
static_game_data_log.error("%s not found in any source", patch_index_filename.c_str());
|
||||
throw cannot_open_file(patch_index_filename);
|
||||
}
|
||||
}
|
||||
@@ -509,25 +512,20 @@ shared_ptr<const string> ServerState::load_map_file_uncached(Version version, co
|
||||
try {
|
||||
return this->load_bb_file(filename);
|
||||
} catch (const exception& e) {
|
||||
static_game_data_log.info("Failed to load %s from BB patch tree: %s", filename.c_str(), e.what());
|
||||
}
|
||||
} else if (version == Version::PC_V2) {
|
||||
try {
|
||||
string path = "system/patch-pc/Media/PSO/" + filename;
|
||||
auto ret = make_shared<string>(load_file(path));
|
||||
static_game_data_log.info("Loaded %s from PC patch tree", filename.c_str());
|
||||
return ret;
|
||||
} catch (const exception& e) {
|
||||
static_game_data_log.info("Failed to load %s from PC patch tree: %s", filename.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
try {
|
||||
string path = string_printf("system/maps/%s/%s", file_path_token_for_version(version), filename.c_str());
|
||||
auto ret = make_shared<string>(load_file(path));
|
||||
static_game_data_log.info("Loaded %s from default maps", filename.c_str());
|
||||
return ret;
|
||||
} catch (const exception& e) {
|
||||
static_game_data_log.info("Failed to load %s from default maps: %s", filename.c_str(), e.what());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1129,6 +1127,36 @@ void ServerState::clear_map_file_caches() {
|
||||
}
|
||||
}
|
||||
|
||||
void ServerState::load_set_data_tables() {
|
||||
config_log.info("Loading set data tables");
|
||||
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables;
|
||||
|
||||
auto load_table = [this](Version version) -> void {
|
||||
auto data = this->load_map_file(version, "SetDataTableOn.rel");
|
||||
this->set_data_tables[static_cast<size_t>(version)] = make_shared<SetDataTable>(version, *data);
|
||||
if (!is_v1(version) && (version != Version::PC_NTE)) {
|
||||
auto data_ep1_ult = this->load_map_file(version, "SetDataTableOnUlti.rel");
|
||||
this->set_data_tables_ep1_ult[static_cast<size_t>(version)] = make_shared<SetDataTable>(version, *data_ep1_ult);
|
||||
}
|
||||
};
|
||||
|
||||
this->set_data_tables[static_cast<size_t>(Version::DC_NTE)] = make_shared<SetDataTableDCNTE>();
|
||||
this->set_data_tables[static_cast<size_t>(Version::DC_V1_11_2000_PROTOTYPE)] = make_shared<SetDataTableDC112000>();
|
||||
load_table(Version::DC_V1);
|
||||
load_table(Version::DC_V2);
|
||||
load_table(Version::PC_NTE);
|
||||
load_table(Version::PC_V2);
|
||||
load_table(Version::GC_NTE);
|
||||
load_table(Version::GC_V3);
|
||||
load_table(Version::XB_V3);
|
||||
load_table(Version::BB_V4);
|
||||
|
||||
auto bb_solo_data = this->load_map_file(Version::BB_V4, "SetDataTableOff.rel");
|
||||
this->bb_solo_set_data_table = make_shared<SetDataTable>(Version::BB_V4, *bb_solo_data);
|
||||
auto bb_solo_data_ep1_ult = this->load_map_file(Version::BB_V4, "SetDataTableOffUlti.rel");
|
||||
this->bb_solo_set_data_table_ep1_ult = make_shared<SetDataTable>(Version::BB_V4, *bb_solo_data_ep1_ult);
|
||||
}
|
||||
|
||||
void ServerState::load_battle_params() {
|
||||
config_log.info("Loading battle parameters");
|
||||
this->battle_params = make_shared<BattleParamsIndex>(
|
||||
@@ -1505,6 +1533,10 @@ void ServerState::create_load_step_graph() {
|
||||
// Out: lobbies
|
||||
this->load_step_graph.add_step("lobbies", {"all"}, bind(&ServerState::create_default_lobbies, this));
|
||||
|
||||
// In: bb_patch_file_index
|
||||
// Out: set_data_tables
|
||||
this->load_step_graph.add_step("set_data_tables", {"all", "patch_indexes"}, bind(&ServerState::load_set_data_tables, this));
|
||||
|
||||
// In: bb_patch_file_index
|
||||
// Out: battle_params
|
||||
this->load_step_graph.add_step("battle_params", {"all", "patch_indexes"}, bind(&ServerState::load_battle_params, this));
|
||||
|
||||
@@ -148,6 +148,10 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<const TextIndex> text_index;
|
||||
std::array<std::shared_ptr<const ItemNameIndex>, NUM_VERSIONS> item_name_indexes;
|
||||
std::shared_ptr<const WordSelectTable> word_select_table;
|
||||
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables;
|
||||
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables_ep1_ult;
|
||||
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table;
|
||||
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table_ep1_ult;
|
||||
std::array<std::shared_ptr<const Map::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates_challenge;
|
||||
std::array<std::array<size_t, 4>, 3> min_levels_v4; // Indexed as [episode][difficulty]
|
||||
@@ -272,6 +276,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu(Version version) const;
|
||||
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations(Version version) const;
|
||||
|
||||
std::shared_ptr<const SetDataTableBase> set_data_table(Version version, Episode episode, GameMode mode, uint8_t difficulty) const;
|
||||
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table(Version version) const;
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_encode(Version version) const;
|
||||
void set_item_parameter_table(Version version, std::shared_ptr<const ItemParameterTable> table);
|
||||
@@ -314,6 +320,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
void load_item_name_indexes();
|
||||
void load_drop_tables();
|
||||
void load_item_definitions();
|
||||
void load_set_data_tables();
|
||||
void load_word_select_table();
|
||||
void load_ep3_data();
|
||||
void load_quest_index();
|
||||
|
||||
Reference in New Issue
Block a user