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();
|
||||
|
||||
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
map_city02_00_00e.dat
|
||||
@@ -0,0 +1 @@
|
||||
map_city02_00_00e.dat
|
||||
@@ -0,0 +1 @@
|
||||
map_city02_00_00o.dat
|
||||
@@ -0,0 +1 @@
|
||||
map_city02_00_00o.dat
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
map_labo00_00e.dat
|
||||
+1
@@ -0,0 +1 @@
|
||||
map_labo00_00e.dat
|
||||
@@ -0,0 +1 @@
|
||||
map_labo00_00o.dat
|
||||
+1
@@ -0,0 +1 @@
|
||||
map_labo00_00o.dat
|
||||
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user