support per-quest common and rare tables

This commit is contained in:
Martin Michelsen
2025-08-22 13:16:12 -07:00
parent e78f3142e3
commit 45824b46fe
27 changed files with 497 additions and 327 deletions
+4 -1
View File
@@ -307,7 +307,10 @@ For .dat files, the `LANGUAGE` token may be omitted. If it's present, then that
For example, the GameCube version of Lost HEAT SWORD is in two files named `q058-gc-e.bin` and `q058-gc.dat`. newserv knows these files are quests because they're in the system/quests/ directory, it knows they're for PSO GC because the filenames contain `-gc`, it knows this is the English version of the quest because the .bin filename ends with `-e` (even though the .dat filename does not), and it puts them in the Retrieval category because the files are within the retrieval/ directory within system/quests/. For example, the GameCube version of Lost HEAT SWORD is in two files named `q058-gc-e.bin` and `q058-gc.dat`. newserv knows these files are quests because they're in the system/quests/ directory, it knows they're for PSO GC because the filenames contain `-gc`, it knows this is the English version of the quest because the .bin filename ends with `-e` (even though the .dat filename does not), and it puts them in the Retrieval category because the files are within the retrieval/ directory within system/quests/.
Some quests (mostly battle and challenge mode quests) have additional JSON metadata files that describe how the server should handle them. These files include flags that can be used to hide the quest unless a preceding quest has been cleared, or to hide the quest unless purchased as a BB team reward. These metadata files are generally named similarly to their .bin and .dat counterparts, except the `VERSION` token may also be omitted if the metadata applies to all languages of the quest on all PSO versions. See system/quests/battle/b88001.json for documentation on the exact format of the JSON file. Some quests have additional JSON metadata files that describe how the server should handle them. These metadata files are generally named similarly to their .bin and .dat counterparts, except the `VERSION` token may also be omitted if the metadata applies to all languages of the quest on all PSO versions. See the comments in system/quests/battle/b88001.json for all of the available options and how to use them. Some of the options are:
- Disable or hide the quest if certain preceding quests aren't cleared or other conditions aren't met
- Enable the quest to be joined while in progress
- Override the common and/or rare item tables and set the allowed drop modes
Some quests may also include a .pvr file, which contains an image used in the quest. These files are named similarly to their .bin and .dat counterparts. Some quests may also include a .pvr file, which contains an image used in the quest. These files are named similarly to their .bin and .dat counterparts.
+4
View File
@@ -23,6 +23,10 @@ public:
return this->entries; return this->entries;
} }
inline size_t num_entries() const {
return this->entries.size();
}
std::pair<const void*, size_t> get(size_t index) const; std::pair<const void*, size_t> get(size_t index) const;
std::string get_copy(size_t index) const; std::string get_copy(size_t index) const;
phosg::StringReader get_reader(size_t index) const; phosg::StringReader get_reader(size_t index) const;
+32 -33
View File
@@ -785,41 +785,40 @@ ChatCommandDefinition cc_dropmode(
if (a.c->proxy_session) { if (a.c->proxy_session) {
using DropMode = ProxySession::DropMode;
if (a.text.empty()) { if (a.text.empty()) {
switch (a.c->proxy_session->drop_mode) { switch (a.c->proxy_session->drop_mode) {
case DropMode::DISABLED: case ProxyDropMode::DISABLED:
send_text_message(a.c, "Drop mode: disabled"); send_text_message(a.c, "Drop mode: disabled");
break; break;
case DropMode::PASSTHROUGH: case ProxyDropMode::PASSTHROUGH:
send_text_message(a.c, "Drop mode: default"); send_text_message(a.c, "Drop mode: default");
break; break;
case DropMode::INTERCEPT: case ProxyDropMode::INTERCEPT:
send_text_message(a.c, "Drop mode: proxy"); send_text_message(a.c, "Drop mode: proxy");
break; break;
} }
} else { } else {
DropMode new_mode; ProxyDropMode new_mode;
if ((a.text == "none") || (a.text == "disabled")) { if ((a.text == "none") || (a.text == "disabled")) {
new_mode = DropMode::DISABLED; new_mode = ProxyDropMode::DISABLED;
} else if ((a.text == "default") || (a.text == "passthrough")) { } else if ((a.text == "default") || (a.text == "passthrough")) {
new_mode = DropMode::PASSTHROUGH; new_mode = ProxyDropMode::PASSTHROUGH;
} else if ((a.text == "proxy") || (a.text == "intercept")) { } else if ((a.text == "proxy") || (a.text == "intercept")) {
new_mode = DropMode::INTERCEPT; new_mode = ProxyDropMode::INTERCEPT;
} else { } else {
throw precondition_failed("Invalid drop mode"); throw precondition_failed("Invalid drop mode");
} }
a.c->proxy_session->set_drop_mode(s, a.c->version(), a.c->override_random_seed, new_mode); a.c->proxy_session->set_drop_mode(s, a.c->version(), a.c->override_random_seed, new_mode);
switch (a.c->proxy_session->drop_mode) { switch (a.c->proxy_session->drop_mode) {
case DropMode::DISABLED: case ProxyDropMode::DISABLED:
send_text_message(a.c->channel, "Item drops disabled"); send_text_message(a.c->channel, "Item drops disabled");
break; break;
case DropMode::PASSTHROUGH: case ProxyDropMode::PASSTHROUGH:
send_text_message(a.c->channel, "Item drops changed\nto default mode"); send_text_message(a.c->channel, "Item drops changed\nto default mode");
break; break;
case DropMode::INTERCEPT: case ProxyDropMode::INTERCEPT:
send_text_message(a.c->channel, "Item drops changed\nto proxy mode"); send_text_message(a.c->channel, "Item drops changed\nto proxy mode");
break; break;
} }
@@ -829,36 +828,36 @@ ChatCommandDefinition cc_dropmode(
auto l = a.c->require_lobby(); auto l = a.c->require_lobby();
if (a.text.empty()) { if (a.text.empty()) {
switch (l->drop_mode) { switch (l->drop_mode) {
case Lobby::DropMode::DISABLED: case ServerDropMode::DISABLED:
send_text_message(a.c, "Drop mode: disabled"); send_text_message(a.c, "Drop mode: disabled");
break; break;
case Lobby::DropMode::CLIENT: case ServerDropMode::CLIENT:
send_text_message(a.c, "Drop mode: client"); send_text_message(a.c, "Drop mode: client");
break; break;
case Lobby::DropMode::SERVER_SHARED: case ServerDropMode::SERVER_SHARED:
send_text_message(a.c, "Drop mode: server\nshared"); send_text_message(a.c, "Drop mode: server\nshared");
break; break;
case Lobby::DropMode::SERVER_PRIVATE: case ServerDropMode::SERVER_PRIVATE:
send_text_message(a.c, "Drop mode: server\nprivate"); send_text_message(a.c, "Drop mode: server\nprivate");
break; break;
case Lobby::DropMode::SERVER_DUPLICATE: case ServerDropMode::SERVER_DUPLICATE:
send_text_message(a.c, "Drop mode: server\nduplicate"); send_text_message(a.c, "Drop mode: server\nduplicate");
break; break;
} }
} else { } else {
a.check_is_leader(); a.check_is_leader();
Lobby::DropMode new_mode; ServerDropMode new_mode;
if ((a.text == "none") || (a.text == "disabled")) { if ((a.text == "none") || (a.text == "disabled")) {
new_mode = Lobby::DropMode::DISABLED; new_mode = ServerDropMode::DISABLED;
} else if (a.text == "client") { } else if (a.text == "client") {
new_mode = Lobby::DropMode::CLIENT; new_mode = ServerDropMode::CLIENT;
} else if ((a.text == "shared") || (a.text == "server")) { } else if ((a.text == "shared") || (a.text == "server")) {
new_mode = Lobby::DropMode::SERVER_SHARED; new_mode = ServerDropMode::SERVER_SHARED;
} else if ((a.text == "private") || (a.text == "priv")) { } else if ((a.text == "private") || (a.text == "priv")) {
new_mode = Lobby::DropMode::SERVER_PRIVATE; new_mode = ServerDropMode::SERVER_PRIVATE;
} else if ((a.text == "duplicate") || (a.text == "dup")) { } else if ((a.text == "duplicate") || (a.text == "dup")) {
new_mode = Lobby::DropMode::SERVER_DUPLICATE; new_mode = ServerDropMode::SERVER_DUPLICATE;
} else { } else {
throw precondition_failed("Invalid drop mode"); throw precondition_failed("Invalid drop mode");
} }
@@ -869,19 +868,19 @@ ChatCommandDefinition cc_dropmode(
l->drop_mode = new_mode; l->drop_mode = new_mode;
switch (l->drop_mode) { switch (l->drop_mode) {
case Lobby::DropMode::DISABLED: case ServerDropMode::DISABLED:
send_text_message(l, "Item drops disabled"); send_text_message(l, "Item drops disabled");
break; break;
case Lobby::DropMode::CLIENT: case ServerDropMode::CLIENT:
send_text_message(l, "Item drops changed\nto client mode"); send_text_message(l, "Item drops changed\nto client mode");
break; break;
case Lobby::DropMode::SERVER_SHARED: case ServerDropMode::SERVER_SHARED:
send_text_message(l, "Item drops changed\nto server shared\nmode"); send_text_message(l, "Item drops changed\nto server shared\nmode");
break; break;
case Lobby::DropMode::SERVER_PRIVATE: case ServerDropMode::SERVER_PRIVATE:
send_text_message(l, "Item drops changed\nto server private\nmode"); send_text_message(l, "Item drops changed\nto server private\nmode");
break; break;
case Lobby::DropMode::SERVER_DUPLICATE: case ServerDropMode::SERVER_DUPLICATE:
send_text_message(l, "Item drops changed\nto server duplicate\nmode"); send_text_message(l, "Item drops changed\nto server duplicate\nmode");
break; break;
} }
@@ -1262,7 +1261,7 @@ ChatCommandDefinition cc_item(
item = s->parse_item_description(a.c->version(), a.text); item = s->parse_item_description(a.c->version(), a.text);
item.id = l->generate_item_id(a.c->lobby_client_id); item.id = l->generate_item_id(a.c->lobby_client_id);
if ((l->drop_mode == Lobby::DropMode::SERVER_PRIVATE) || (l->drop_mode == Lobby::DropMode::SERVER_DUPLICATE)) { if ((l->drop_mode == ServerDropMode::SERVER_PRIVATE) || (l->drop_mode == ServerDropMode::SERVER_DUPLICATE)) {
l->add_item(a.c->floor, item, a.c->pos, nullptr, nullptr, (1 << a.c->lobby_client_id)); l->add_item(a.c->floor, item, a.c->pos, nullptr, nullptr, (1 << a.c->lobby_client_id));
send_drop_stacked_item_to_channel(s, a.c->channel, item, a.c->floor, a.c->pos); send_drop_stacked_item_to_channel(s, a.c->channel, item, a.c->floor, a.c->pos);
} else { } else {
@@ -1452,19 +1451,19 @@ ChatCommandDefinition cc_lobby_info(
"$C7Section ID: $C6{}$C7", name_for_section_id(l->effective_section_id()))); "$C7Section ID: $C6{}$C7", name_for_section_id(l->effective_section_id())));
switch (l->drop_mode) { switch (l->drop_mode) {
case Lobby::DropMode::DISABLED: case ServerDropMode::DISABLED:
lines.emplace_back("Drops disabled"); lines.emplace_back("Drops disabled");
break; break;
case Lobby::DropMode::CLIENT: case ServerDropMode::CLIENT:
lines.emplace_back("Client item table"); lines.emplace_back("Client item table");
break; break;
case Lobby::DropMode::SERVER_SHARED: case ServerDropMode::SERVER_SHARED:
lines.emplace_back("Server item table"); lines.emplace_back("Server item table");
break; break;
case Lobby::DropMode::SERVER_PRIVATE: case ServerDropMode::SERVER_PRIVATE:
lines.emplace_back("Server indiv items"); lines.emplace_back("Server indiv items");
break; break;
case Lobby::DropMode::SERVER_DUPLICATE: case ServerDropMode::SERVER_DUPLICATE:
lines.emplace_back("Server dup items"); lines.emplace_back("Server dup items");
break; break;
default: default:
+9 -6
View File
@@ -435,9 +435,9 @@ struct C_LegacyLogin_BB_04 {
// 05 = Server down for maintenance (108) // 05 = Server down for maintenance (108)
// 06 = Incorrect password (127) // 06 = Incorrect password (127)
// Any other nonzero value = Generic failure (101) // Any other nonzero value = Generic failure (101)
// The client config field in this command is ignored by pre-V3 clients as well // The client config field in this command is ignored by all clients that never
// as Episodes 1&2 Trial Edition. All other V3 clients save it as opaque data to // send 9E. Clients that do send 9E will save thie client config as opaque data
// be returned in a 9E or 9F command later. // to be returned in a 9E or 9F command later.
// The client will respond with a 96 command, but only the first time it // The client will respond with a 96 command, but only the first time it
// receives this command - for later 04 commands, the client will still update // receives this command - for later 04 commands, the client will still update
// its client config but will not respond. Changing the security data at any // its client config but will not respond. Changing the security data at any
@@ -4298,7 +4298,8 @@ struct G_FeedMag_6x28 {
le_uint32_t fed_item_id = 0; le_uint32_t fed_item_id = 0;
} __packed_ws__(G_FeedMag_6x28, 0x0C); } __packed_ws__(G_FeedMag_6x28, 0x0C);
// 6x29: Delete inventory item (via bank deposit / sale / feeding MAG) (protected on V3 but not V4) // 6x29: Delete inventory item (via bank deposit / sale / feeding MAG)
// (protected on V3 but not on V4)
// This subcommand is also used for reducing the size of stacks - if amount is // This subcommand is also used for reducing the size of stacks - if amount is
// less than the stack count, the item is not deleted and its ID remains valid. // less than the stack count, the item is not deleted and its ID remains valid.
@@ -5739,14 +5740,16 @@ struct G_SetLobbyChairState_6xAE {
le_float unknown_a4 = 0; le_float unknown_a4 = 0;
} __packed_ws__(G_SetLobbyChairState_6xAE, 0x10); } __packed_ws__(G_SetLobbyChairState_6xAE, 0x10);
// 6xAF: Turn lobby chair (not valid on pre-V3 or GC Trial Edition) (protected on V3/V4) // 6xAF: Turn lobby chair (not valid on pre-V3 or GC Trial Edition) (protected
// on V3/V4)
struct G_TurnLobbyChair_6xAF { struct G_TurnLobbyChair_6xAF {
G_ClientIDHeader header; G_ClientIDHeader header;
le_uint32_t angle = 0; // In range [0x0000, 0xFFFF] le_uint32_t angle = 0; // In range [0x0000, 0xFFFF]
} __packed_ws__(G_TurnLobbyChair_6xAF, 8); } __packed_ws__(G_TurnLobbyChair_6xAF, 8);
// 6xB0: Move lobby chair (not valid on pre-V3 or GC Trial Edition) (protected on V3/V4) // 6xB0: Move lobby chair (not valid on pre-V3 or GC Trial Edition) (protected
// on V3/V4)
struct G_MoveLobbyChair_6xB0 { struct G_MoveLobbyChair_6xB0 {
G_ClientIDHeader header; G_ClientIDHeader header;
+48 -24
View File
@@ -677,29 +677,49 @@ shared_ptr<const CommonItemSet::Table> CommonItemSet::get_table(
} }
AFSV2CommonItemSet::AFSV2CommonItemSet( AFSV2CommonItemSet::AFSV2CommonItemSet(
std::shared_ptr<const std::string> pt_afs_data, std::shared_ptr<const std::string> pt_afs_data, std::shared_ptr<const std::string> ct_afs_data) {
std::shared_ptr<const std::string> ct_afs_data) { // Each AFS file has 40 entries (30 on v1); the first 10 are for Normal, then
// ItemPT.afs has 40 entries; the first 10 are for Normal, then Hard, etc. // Hard, etc.
AFSArchive pt_afs(pt_afs_data); {
for (size_t difficulty = 0; difficulty < 4; difficulty++) { AFSArchive pt_afs(pt_afs_data);
for (size_t section_id = 0; section_id < 10; section_id++) { size_t max_difficulty;
auto entry = pt_afs.get(difficulty * 10 + section_id); if (pt_afs.num_entries() >= 40) {
phosg::StringReader r(entry.first, entry.second); max_difficulty = 4;
auto table = make_shared<Table>(r, false, false, Episode::EP1); } else if (pt_afs.num_entries() >= 30) {
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id), table); max_difficulty = 3;
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::BATTLE, difficulty, section_id), table); } else {
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::SOLO, difficulty, section_id), table); throw std::runtime_error(std::format("PT AFS file has unexpected entry count ({})", pt_afs.num_entries()));
}
for (size_t difficulty = 0; difficulty < max_difficulty; difficulty++) {
for (size_t section_id = 0; section_id < 10; section_id++) {
auto entry = pt_afs.get(difficulty * 10 + section_id);
phosg::StringReader r(entry.first, entry.second);
auto table = make_shared<Table>(r, false, false, Episode::EP1);
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id), table);
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::BATTLE, difficulty, section_id), table);
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::SOLO, difficulty, section_id), table);
}
} }
} }
// ItemCT.afs also has 40 entries, but only the 0th, 10th, 20th, and 30th are // ItemCT AFS files also have 40 entries, but only the 0th, 10th, 20th, and
// used (section_id is ignored) // 30th are used (section_id is ignored)
AFSArchive ct_afs(ct_afs_data); if (ct_afs_data) {
for (size_t difficulty = 0; difficulty < 4; difficulty++) { AFSArchive ct_afs(ct_afs_data);
auto r = ct_afs.get_reader(difficulty * 10); size_t max_difficulty;
auto table = make_shared<Table>(r, false, false, Episode::EP1); if (ct_afs.num_entries() >= 40) {
for (size_t section_id = 0; section_id < 10; section_id++) { max_difficulty = 4;
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::CHALLENGE, difficulty, section_id), table); } else if (ct_afs.num_entries() >= 30) {
max_difficulty = 3;
} else {
throw std::runtime_error(std::format("CT AFS file has unexpected entry count ({})", ct_afs.num_entries()));
}
for (size_t difficulty = 0; difficulty < max_difficulty; difficulty++) {
auto r = ct_afs.get_reader(difficulty * 10);
auto table = make_shared<Table>(r, false, false, Episode::EP1);
for (size_t section_id = 0; section_id < 10; section_id++) {
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::CHALLENGE, difficulty, section_id), table);
}
} }
} }
} }
@@ -758,10 +778,14 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
if (episode != Episode::EP4) { if (episode != Episode::EP4) {
for (size_t difficulty = 0; difficulty < 4; difficulty++) { for (size_t difficulty = 0; difficulty < 4; difficulty++) {
auto r = gsl.get_reader(filename_for_table(episode, difficulty, 0, true)); try {
auto table = make_shared<Table>(r, is_big_endian, true, episode); auto r = gsl.get_reader(filename_for_table(episode, difficulty, 0, true));
for (size_t section_id = 0; section_id < 10; section_id++) { auto table = make_shared<Table>(r, is_big_endian, true, episode);
this->tables.emplace(this->key_for_table(episode, GameMode::CHALLENGE, difficulty, section_id), table); for (size_t section_id = 0; section_id < 10; section_id++) {
this->tables.emplace(this->key_for_table(episode, GameMode::CHALLENGE, difficulty, section_id), table);
}
} catch (const out_of_range&) {
// GC NTE doesn't have Ep2 challenge; just skip adding the table
} }
} }
} }
+29 -15
View File
@@ -328,13 +328,13 @@ std::shared_ptr<phosg::JSON> HTTPServer::generate_client_json(
{"LobbyPlayers", std::move(lobby_players_json)}, {"LobbyPlayers", std::move(lobby_players_json)},
}); });
switch (ses->drop_mode) { switch (ses->drop_mode) {
case ProxySession::DropMode::DISABLED: case ProxyDropMode::DISABLED:
ses_json.emplace("DropMode", "none"); ses_json.emplace("DropMode", "none");
break; break;
case ProxySession::DropMode::PASSTHROUGH: case ProxyDropMode::PASSTHROUGH:
ses_json.emplace("DropMode", "default"); ses_json.emplace("DropMode", "default");
break; break;
case ProxySession::DropMode::INTERCEPT: case ProxyDropMode::INTERCEPT:
ses_json.emplace("DropMode", "proxy"); ses_json.emplace("DropMode", "proxy");
break; break;
} }
@@ -386,19 +386,19 @@ std::shared_ptr<phosg::JSON> HTTPServer::generate_lobby_json(
ret->emplace("EXPShareMultiplier", l->exp_share_multiplier); ret->emplace("EXPShareMultiplier", l->exp_share_multiplier);
ret->emplace("AllowedDropModes", l->allowed_drop_modes); ret->emplace("AllowedDropModes", l->allowed_drop_modes);
switch (l->drop_mode) { switch (l->drop_mode) {
case Lobby::DropMode::DISABLED: case ServerDropMode::DISABLED:
ret->emplace("DropMode", "none"); ret->emplace("DropMode", "none");
break; break;
case Lobby::DropMode::CLIENT: case ServerDropMode::CLIENT:
ret->emplace("DropMode", "client"); ret->emplace("DropMode", "client");
break; break;
case Lobby::DropMode::SERVER_SHARED: case ServerDropMode::SERVER_SHARED:
ret->emplace("DropMode", "shared"); ret->emplace("DropMode", "shared");
break; break;
case Lobby::DropMode::SERVER_PRIVATE: case ServerDropMode::SERVER_PRIVATE:
ret->emplace("DropMode", "private"); ret->emplace("DropMode", "private");
break; break;
case Lobby::DropMode::SERVER_DUPLICATE: case ServerDropMode::SERVER_DUPLICATE:
ret->emplace("DropMode", "duplicate"); ret->emplace("DropMode", "duplicate");
break; break;
} }
@@ -669,12 +669,23 @@ asio::awaitable<std::shared_ptr<phosg::JSON>> HTTPServer::generate_ep3_cards_jso
}); });
} }
asio::awaitable<std::shared_ptr<phosg::JSON>> HTTPServer::generate_common_tables_json() const { std::shared_ptr<phosg::JSON> HTTPServer::generate_common_table_list_json() const {
auto v2_table = this->state->common_item_set_v2; auto ret = make_shared<phosg::JSON>(phosg::JSON::list());
auto v3_v4_table = this->state->common_item_set_v3_v4; for (const auto& it : this->state->common_item_sets) {
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> { ret->emplace_back(it.first);
return make_shared<phosg::JSON>(phosg::JSON::dict({{"v1_v2", v2_table->json()}, {"v3_v4", v3_v4_table->json()}})); }
}); return ret;
}
asio::awaitable<std::shared_ptr<phosg::JSON>> HTTPServer::generate_common_table_json(const std::string& table_name) const {
try {
const auto& table = this->state->common_item_sets.at(table_name);
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
return make_shared<phosg::JSON>(table->json());
});
} catch (const out_of_range&) {
throw HTTPError(404, "Table does not exist");
}
} }
std::shared_ptr<phosg::JSON> HTTPServer::generate_rare_table_list_json() const { std::shared_ptr<phosg::JSON> HTTPServer::generate_rare_table_list_json() const {
@@ -794,7 +805,10 @@ asio::awaitable<std::unique_ptr<HTTPResponse>> HTTPServer::handle_request(shared
ret = co_await this->generate_ep3_cards_json(true); ret = co_await this->generate_ep3_cards_json(true);
} else if (req.path == "/y/data/common-tables") { } else if (req.path == "/y/data/common-tables") {
this->require_GET(req); this->require_GET(req);
ret = co_await this->generate_common_tables_json(); ret = this->generate_common_table_list_json();
} else if (req.path.starts_with("/y/data/common-tables/")) {
this->require_GET(req);
ret = co_await this->generate_common_table_json(req.path.substr(22));
} else if (req.path == "/y/data/rare-tables") { } else if (req.path == "/y/data/rare-tables") {
this->require_GET(req); this->require_GET(req);
ret = this->generate_rare_table_list_json(); ret = this->generate_rare_table_list_json();
+2 -1
View File
@@ -37,8 +37,9 @@ protected:
std::shared_ptr<phosg::JSON> generate_all_json() const; std::shared_ptr<phosg::JSON> generate_all_json() const;
asio::awaitable<std::shared_ptr<phosg::JSON>> generate_ep3_cards_json(bool trial) const; asio::awaitable<std::shared_ptr<phosg::JSON>> generate_ep3_cards_json(bool trial) const;
asio::awaitable<std::shared_ptr<phosg::JSON>> generate_common_tables_json() const; std::shared_ptr<phosg::JSON> generate_common_table_list_json() const;
std::shared_ptr<phosg::JSON> generate_rare_table_list_json() const; std::shared_ptr<phosg::JSON> generate_rare_table_list_json() const;
asio::awaitable<std::shared_ptr<phosg::JSON>> generate_common_table_json(const std::string& table_name) const;
asio::awaitable<std::shared_ptr<phosg::JSON>> generate_rare_table_json(const std::string& table_name) const; asio::awaitable<std::shared_ptr<phosg::JSON>> generate_rare_table_json(const std::string& table_name) const;
asio::awaitable<std::shared_ptr<phosg::JSON>> generate_quest_list_json(std::shared_ptr<const QuestIndex> q); asio::awaitable<std::shared_ptr<phosg::JSON>> generate_quest_list_json(std::shared_ptr<const QuestIndex> q);
+35
View File
@@ -4,6 +4,41 @@
using namespace std; using namespace std;
template <>
ServerDropMode phosg::enum_for_name<ServerDropMode>(const char* name) {
if (!strcmp(name, "DISABLED")) {
return ServerDropMode::DISABLED;
} else if (!strcmp(name, "CLIENT")) {
return ServerDropMode::CLIENT;
} else if (!strcmp(name, "SERVER_SHARED")) {
return ServerDropMode::SERVER_SHARED;
} else if (!strcmp(name, "SERVER_PRIVATE")) {
return ServerDropMode::SERVER_PRIVATE;
} else if (!strcmp(name, "SERVER_DUPLICATE")) {
return ServerDropMode::SERVER_DUPLICATE;
} else {
throw runtime_error("invalid drop mode");
}
}
template <>
const char* phosg::name_for_enum<ServerDropMode>(ServerDropMode value) {
switch (value) {
case ServerDropMode::DISABLED:
return "DISABLED";
case ServerDropMode::CLIENT:
return "CLIENT";
case ServerDropMode::SERVER_SHARED:
return "SERVER_SHARED";
case ServerDropMode::SERVER_PRIVATE:
return "SERVER_PRIVATE";
case ServerDropMode::SERVER_DUPLICATE:
return "SERVER_DUPLICATE";
default:
throw runtime_error("invalid drop mode");
}
}
ItemParameterTable::ItemParameterTable(shared_ptr<const string> data, Version version) ItemParameterTable::ItemParameterTable(shared_ptr<const string> data, Version version)
: version(version), : version(version),
data(data), data(data),
+20
View File
@@ -16,6 +16,26 @@
#include "Types.hh" #include "Types.hh"
#include "Version.hh" #include "Version.hh"
// TODO: These don't really belong here, but putting them anywhere else creates
// annoying dependency cycles. Find or make a better place for these.
enum class ServerDropMode {
DISABLED = 0,
CLIENT = 1, // Not allowed for BB games
SERVER_SHARED = 2,
SERVER_PRIVATE = 3,
SERVER_DUPLICATE = 4,
};
enum class ProxyDropMode {
DISABLED = 0,
PASSTHROUGH,
INTERCEPT,
};
template <>
ServerDropMode phosg::enum_for_name<ServerDropMode>(const char* name);
template <>
const char* phosg::name_for_enum<ServerDropMode>(ServerDropMode value);
class ItemParameterTable { class ItemParameterTable {
public: public:
// TODO: This implementation is ugly. We should use real classes and virtual // TODO: This implementation is ugly. We should use real classes and virtual
+3 -73
View File
@@ -159,7 +159,7 @@ Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id, bool is_game)
challenge_exp_multiplier(1.0f), challenge_exp_multiplier(1.0f),
random_seed(phosg::random_object<uint32_t>()), random_seed(phosg::random_object<uint32_t>()),
rand_crypt(make_shared<DisabledRandomGenerator>()), rand_crypt(make_shared<DisabledRandomGenerator>()),
drop_mode(DropMode::CLIENT), drop_mode(ServerDropMode::CLIENT),
event(0), event(0),
block(0), block(0),
leader_id(0), leader_id(0),
@@ -214,41 +214,6 @@ void Lobby::create_item_creator(Version logic_version) {
logic_version = leader_c ? leader_c->version() : Version::BB_V4; logic_version = leader_c ? leader_c->version() : Version::BB_V4;
} }
shared_ptr<const RareItemSet> rare_item_set;
shared_ptr<const CommonItemSet> common_item_set;
switch (logic_version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
throw runtime_error("cannot create item creator for this base version");
case Version::DC_NTE:
case Version::DC_11_2000:
case Version::DC_V1:
// TODO: We should probably have a v1 common item set at some point too
common_item_set = s->common_item_set_v2;
rare_item_set = s->rare_item_sets.at("rare-table-v1");
break;
case Version::DC_V2:
case Version::PC_NTE:
case Version::PC_V2:
common_item_set = s->common_item_set_v2;
rare_item_set = s->rare_item_sets.at("rare-table-v2");
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::XB_V3:
common_item_set = s->common_item_set_v3_v4;
rare_item_set = s->rare_item_sets.at("rare-table-v3");
break;
case Version::BB_V4:
common_item_set = s->common_item_set_v3_v4;
rare_item_set = s->rare_item_sets.at("rare-table-v4");
break;
default:
throw logic_error("invalid lobby base version");
}
shared_ptr<RandomGenerator> rand_crypt; shared_ptr<RandomGenerator> rand_crypt;
if (s->use_psov2_rand_crypt) { if (s->use_psov2_rand_crypt) {
rand_crypt = make_shared<PSOV2Encryption>(this->rand_crypt->seed()); rand_crypt = make_shared<PSOV2Encryption>(this->rand_crypt->seed());
@@ -256,8 +221,8 @@ void Lobby::create_item_creator(Version logic_version) {
rand_crypt = make_shared<MT19937Generator>(this->rand_crypt->seed()); rand_crypt = make_shared<MT19937Generator>(this->rand_crypt->seed());
} }
this->item_creator = make_shared<ItemCreator>( this->item_creator = make_shared<ItemCreator>(
common_item_set, s->common_item_set(logic_version, this->quest),
rare_item_set, s->rare_item_set(logic_version, this->quest),
s->armor_random_set, s->armor_random_set,
s->tool_random_set, s->tool_random_set,
s->weapon_random_sets.at(this->difficulty), s->weapon_random_sets.at(this->difficulty),
@@ -884,38 +849,3 @@ bool Lobby::compare_shared(const shared_ptr<const Lobby>& a, const shared_ptr<co
return a->name < b->name; return a->name < b->name;
} }
template <>
Lobby::DropMode phosg::enum_for_name<Lobby::DropMode>(const char* name) {
if (!strcmp(name, "DISABLED")) {
return Lobby::DropMode::DISABLED;
} else if (!strcmp(name, "CLIENT")) {
return Lobby::DropMode::CLIENT;
} else if (!strcmp(name, "SERVER_SHARED")) {
return Lobby::DropMode::SERVER_SHARED;
} else if (!strcmp(name, "SERVER_PRIVATE")) {
return Lobby::DropMode::SERVER_PRIVATE;
} else if (!strcmp(name, "SERVER_DUPLICATE")) {
return Lobby::DropMode::SERVER_DUPLICATE;
} else {
throw runtime_error("invalid drop mode");
}
}
template <>
const char* phosg::name_for_enum<Lobby::DropMode>(Lobby::DropMode value) {
switch (value) {
case Lobby::DropMode::DISABLED:
return "DISABLED";
case Lobby::DropMode::CLIENT:
return "CLIENT";
case Lobby::DropMode::SERVER_SHARED:
return "SERVER_SHARED";
case Lobby::DropMode::SERVER_PRIVATE:
return "SERVER_PRIVATE";
case Lobby::DropMode::SERVER_DUPLICATE:
return "SERVER_DUPLICATE";
default:
throw runtime_error("invalid drop mode");
}
}
+3 -10
View File
@@ -90,13 +90,6 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
IS_OVERFLOW = 0x08000000, IS_OVERFLOW = 0x08000000,
// clang-format on // clang-format on
}; };
enum class DropMode {
DISABLED = 0,
CLIENT = 1, // Not allowed for BB games
SERVER_SHARED = 2,
SERVER_PRIVATE = 3,
SERVER_DUPLICATE = 4,
};
std::weak_ptr<ServerState> server_state; std::weak_ptr<ServerState> server_state;
phosg::PrefixedLogger log; phosg::PrefixedLogger log;
@@ -136,7 +129,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
uint32_t random_seed; uint32_t random_seed;
std::shared_ptr<RandomGenerator> rand_crypt; std::shared_ptr<RandomGenerator> rand_crypt;
uint8_t allowed_drop_modes; uint8_t allowed_drop_modes;
DropMode drop_mode; ServerDropMode drop_mode;
std::shared_ptr<ItemCreator> item_creator; // Always null for lobbies, never null for games std::shared_ptr<ItemCreator> item_creator; // Always null for lobbies, never null for games
struct ChallengeParameters { struct ChallengeParameters {
@@ -291,6 +284,6 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
}; };
template <> template <>
Lobby::DropMode phosg::enum_for_name<Lobby::DropMode>(const char* name); ServerDropMode phosg::enum_for_name<ServerDropMode>(const char* name);
template <> template <>
const char* phosg::name_for_enum<Lobby::DropMode>(Lobby::DropMode value); const char* phosg::name_for_enum<ServerDropMode>(ServerDropMode value);
+3 -4
View File
@@ -844,13 +844,12 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
co_return HandlerResult::FORWARD; co_return HandlerResult::FORWARD;
} }
using DropMode = ProxySession::DropMode;
switch (c->proxy_session->drop_mode) { switch (c->proxy_session->drop_mode) {
case DropMode::DISABLED: case ProxyDropMode::DISABLED:
co_return HandlerResult::SUPPRESS; co_return HandlerResult::SUPPRESS;
case DropMode::PASSTHROUGH: case ProxyDropMode::PASSTHROUGH:
co_return HandlerResult::FORWARD; co_return HandlerResult::FORWARD;
case DropMode::INTERCEPT: case ProxyDropMode::INTERCEPT:
break; break;
default: default:
throw logic_error("invalid drop mode"); throw logic_error("invalid drop mode");
+5 -38
View File
@@ -21,47 +21,14 @@ ProxySession::~ProxySession() {
this->num_proxy_sessions--; this->num_proxy_sessions--;
} }
void ProxySession::set_drop_mode(shared_ptr<ServerState> s, Version version, int64_t override_random_seed, DropMode new_mode) { void ProxySession::set_drop_mode(
shared_ptr<ServerState> s, Version version, int64_t override_random_seed, ProxyDropMode new_mode) {
this->drop_mode = new_mode; this->drop_mode = new_mode;
if (this->drop_mode == DropMode::INTERCEPT) { if (this->drop_mode == ProxyDropMode::INTERCEPT) {
shared_ptr<const RareItemSet> rare_item_set;
shared_ptr<const CommonItemSet> common_item_set;
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
throw runtime_error("cannot create item creator for this base version");
case Version::DC_NTE:
case Version::DC_11_2000:
case Version::DC_V1:
// TODO: We should probably have a v1 common item set at some point too
common_item_set = s->common_item_set_v2;
rare_item_set = s->rare_item_sets.at("rare-table-v1");
break;
case Version::DC_V2:
case Version::PC_NTE:
case Version::PC_V2:
common_item_set = s->common_item_set_v2;
rare_item_set = s->rare_item_sets.at("rare-table-v2");
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::XB_V3:
common_item_set = s->common_item_set_v3_v4;
rare_item_set = s->rare_item_sets.at("rare-table-v3");
break;
case Version::BB_V4:
common_item_set = s->common_item_set_v3_v4;
rare_item_set = s->rare_item_sets.at("rare-table-v4");
break;
default:
throw logic_error("invalid lobby base version");
}
auto rand_crypt = make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed); auto rand_crypt = make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed);
this->item_creator = make_shared<ItemCreator>( this->item_creator = make_shared<ItemCreator>(
common_item_set, s->common_item_set(version, nullptr),
rare_item_set, s->rare_item_set(version, nullptr),
s->armor_random_set, s->armor_random_set,
s->tool_random_set, s->tool_random_set,
s->weapon_random_sets.at(this->lobby_difficulty), s->weapon_random_sets.at(this->lobby_difficulty),
+2 -7
View File
@@ -47,12 +47,7 @@ struct ProxySession {
int64_t remote_guild_card_number = -1; int64_t remote_guild_card_number = -1;
parray<uint8_t, 0x28> remote_client_config_data; parray<uint8_t, 0x28> remote_client_config_data;
enum class DropMode { ProxyDropMode drop_mode = ProxyDropMode::PASSTHROUGH;
DISABLED = 0,
PASSTHROUGH,
INTERCEPT,
};
DropMode drop_mode = DropMode::PASSTHROUGH;
std::shared_ptr<std::string> quest_dat_data; std::shared_ptr<std::string> quest_dat_data;
std::shared_ptr<ItemCreator> item_creator; std::shared_ptr<ItemCreator> item_creator;
std::shared_ptr<MapState> map_state; std::shared_ptr<MapState> map_state;
@@ -80,7 +75,7 @@ struct ProxySession {
}; };
std::unordered_map<std::string, SavingFile> saving_files; std::unordered_map<std::string, SavingFile> saving_files;
void set_drop_mode(std::shared_ptr<ServerState> s, Version version, int64_t override_random_seed, DropMode new_mode); void set_drop_mode(std::shared_ptr<ServerState> s, Version version, int64_t override_random_seed, ProxyDropMode new_mode);
void clear_lobby_players(size_t num_slots); void clear_lobby_players(size_t num_slots);
}; };
+70 -7
View File
@@ -221,6 +221,15 @@ void VersionedQuest::assert_valid() const {
if (!is_ep3(this->version) && !this->map_file) { if (!is_ep3(this->version) && !this->map_file) {
throw runtime_error("parsed map file is missing"); throw runtime_error("parsed map file is missing");
} }
if (this->common_item_set_name.empty() != !this->common_item_set) {
throw runtime_error("common item set name/pointer mismatch");
}
if (this->rare_item_set_name.empty() != !this->rare_item_set) {
throw runtime_error("rare item set name/pointer mismatch");
}
if (this->allowed_drop_modes && !(this->allowed_drop_modes & (1 << static_cast<size_t>(this->default_drop_mode)))) {
throw runtime_error("default drop mode is not allowed");
}
} }
string VersionedQuest::bin_filename() const { string VersionedQuest::bin_filename() const {
@@ -280,7 +289,13 @@ Quest::Quest(shared_ptr<const VersionedQuest> initial_version)
challenge_template_index(initial_version->challenge_template_index), challenge_template_index(initial_version->challenge_template_index),
description_flag(initial_version->description_flag), description_flag(initial_version->description_flag),
available_expression(initial_version->available_expression), available_expression(initial_version->available_expression),
enabled_expression(initial_version->enabled_expression) { enabled_expression(initial_version->enabled_expression),
common_item_set_name(initial_version->common_item_set_name),
rare_item_set_name(initial_version->rare_item_set_name),
common_item_set(initial_version->common_item_set),
rare_item_set(initial_version->rare_item_set),
allowed_drop_modes(initial_version->allowed_drop_modes),
default_drop_mode(initial_version->default_drop_mode) {
this->add_version(initial_version); this->add_version(initial_version);
} }
@@ -298,10 +313,6 @@ phosg::JSON Quest::json() const {
})); }));
} }
auto battle_rules_json = this->battle_rules ? this->battle_rules->json() : nullptr;
auto challenge_template_index_json = (this->challenge_template_index >= 0)
? this->challenge_template_index
: phosg::JSON(nullptr);
return phosg::JSON::dict({ return phosg::JSON::dict({
{"Number", this->quest_number}, {"Number", this->quest_number},
{"CategoryID", this->category_id}, {"CategoryID", this->category_id},
@@ -311,11 +322,15 @@ phosg::JSON Quest::json() const {
{"MaxPlayers", this->max_players}, {"MaxPlayers", this->max_players},
{"LockStatusRegister", (this->lock_status_register >= 0) ? this->lock_status_register : phosg::JSON(nullptr)}, {"LockStatusRegister", (this->lock_status_register >= 0) ? this->lock_status_register : phosg::JSON(nullptr)},
{"Name", this->name}, {"Name", this->name},
{"BattleRules", std::move(battle_rules_json)}, {"BattleRules", this->battle_rules ? this->battle_rules->json() : phosg::JSON(nullptr)},
{"ChallengeTemplateIndex", std::move(challenge_template_index_json)}, {"ChallengeTemplateIndex", (this->challenge_template_index >= 0) ? this->challenge_template_index : phosg::JSON(nullptr)},
{"DescriptionFlag", this->description_flag}, {"DescriptionFlag", this->description_flag},
{"AvailableExpression", this->available_expression ? this->available_expression->str() : phosg::JSON(nullptr)}, {"AvailableExpression", this->available_expression ? this->available_expression->str() : phosg::JSON(nullptr)},
{"EnabledExpression", this->available_expression ? this->available_expression->str() : phosg::JSON(nullptr)}, {"EnabledExpression", this->available_expression ? this->available_expression->str() : phosg::JSON(nullptr)},
{"CommonItemSetName", this->common_item_set_name.empty() ? phosg::JSON(nullptr) : this->common_item_set_name},
{"RareItemSetName", this->rare_item_set_name.empty() ? phosg::JSON(nullptr) : this->rare_item_set_name},
{"AllowedDropModes", this->allowed_drop_modes},
{"DefaultDropMode", phosg::name_for_enum(this->default_drop_mode)},
{"Versions", std::move(versions_json)}, {"Versions", std::move(versions_json)},
}); });
} }
@@ -406,6 +421,30 @@ void Quest::add_version(shared_ptr<const VersionedQuest> vq) {
"quest version has a different enabled expression (existing: {}, new: {})", "quest version has a different enabled expression (existing: {}, new: {})",
existing_str, new_str)); existing_str, new_str));
} }
if (this->common_item_set_name != vq->common_item_set_name) {
throw runtime_error(std::format(
"quest version has different common table name (existing: {}, new: {})",
this->common_item_set_name, vq->common_item_set_name));
}
if (this->common_item_set != vq->common_item_set) {
throw runtime_error("quest version has different common table");
}
if (this->rare_item_set_name != vq->rare_item_set_name) {
throw runtime_error(std::format(
"quest version has different rare table name (existing: {}, new: {})",
this->rare_item_set_name, vq->rare_item_set_name));
}
if (this->rare_item_set != vq->rare_item_set) {
throw runtime_error("quest version has different rare table");
}
if (this->allowed_drop_modes != vq->allowed_drop_modes) {
throw runtime_error(format("quest version has different allowed drop modes (existing: {:02X}, new: {:02X})",
this->allowed_drop_modes, vq->allowed_drop_modes));
}
if (this->default_drop_mode != vq->default_drop_mode) {
throw runtime_error(format("quest version has different default drop mode (existing: {}, new: {})",
phosg::name_for_enum(this->default_drop_mode), phosg::name_for_enum(vq->default_drop_mode)));
}
this->versions.emplace(this->versions_key(vq->version, vq->language), vq); this->versions.emplace(this->versions_key(vq->version, vq->language), vq);
} }
@@ -482,6 +521,8 @@ shared_ptr<const VersionedQuest> Quest::version(Version v, uint8_t language) con
QuestIndex::QuestIndex( QuestIndex::QuestIndex(
const string& directory, const string& directory,
shared_ptr<const QuestCategoryIndex> category_index, shared_ptr<const QuestCategoryIndex> category_index,
const unordered_map<string, shared_ptr<const CommonItemSet>>& common_item_sets,
const unordered_map<string, shared_ptr<const RareItemSet>>& rare_item_sets,
bool is_ep3) bool is_ep3)
: directory(directory), : directory(directory),
category_index(category_index) { category_index(category_index) {
@@ -914,6 +955,28 @@ QuestIndex::QuestIndex(
vq->lock_status_register = metadata_json.get_int("LockStatusRegister"); vq->lock_status_register = metadata_json.get_int("LockStatusRegister");
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
try {
vq->common_item_set_name = metadata_json.at("CommonItemSetName").as_string();
} catch (const out_of_range&) {
}
if (!vq->common_item_set_name.empty()) {
vq->common_item_set = common_item_sets.at(vq->common_item_set_name);
}
try {
vq->rare_item_set_name = metadata_json.at("RareItemSetName").as_string();
} catch (const out_of_range&) {
}
if (!vq->rare_item_set_name.empty()) {
vq->rare_item_set = rare_item_sets.at(vq->rare_item_set_name);
}
try {
vq->allowed_drop_modes = metadata_json.at("AllowedDropModes").as_int();
} catch (const out_of_range&) {
}
try {
vq->default_drop_mode = phosg::enum_for_name<ServerDropMode>(metadata_json.at("DefaultDropMode").as_string());
} catch (const out_of_range&) {
}
} }
vq->assert_valid(); vq->assert_valid();
+22 -1
View File
@@ -8,10 +8,13 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "CommonItemSet.hh"
#include "IntegralExpression.hh" #include "IntegralExpression.hh"
#include "ItemParameterTable.hh"
#include "Map.hh" #include "Map.hh"
#include "PlayerSubordinates.hh" #include "PlayerSubordinates.hh"
#include "QuestScript.hh" #include "QuestScript.hh"
#include "RareItemSet.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
#include "TeamIndex.hh" #include "TeamIndex.hh"
@@ -87,6 +90,12 @@ struct VersionedQuest {
std::shared_ptr<const IntegralExpression> enabled_expression; std::shared_ptr<const IntegralExpression> enabled_expression;
bool allow_start_from_chat_command = false; bool allow_start_from_chat_command = false;
int16_t lock_status_register = -1; int16_t lock_status_register = -1;
std::string common_item_set_name;
std::string rare_item_set_name;
std::shared_ptr<const CommonItemSet> common_item_set;
std::shared_ptr<const RareItemSet> rare_item_set;
uint8_t allowed_drop_modes = 0x00; // 0 = use server default
ServerDropMode default_drop_mode = ServerDropMode::CLIENT; // Ignored if allowed_drop_modes == 0
bool is_dlq_encoded = false; bool is_dlq_encoded = false;
void assert_valid() const; void assert_valid() const;
@@ -115,6 +124,13 @@ struct Quest {
uint8_t description_flag; uint8_t description_flag;
std::shared_ptr<const IntegralExpression> available_expression; std::shared_ptr<const IntegralExpression> available_expression;
std::shared_ptr<const IntegralExpression> enabled_expression; std::shared_ptr<const IntegralExpression> enabled_expression;
std::string common_item_set_name;
std::string rare_item_set_name;
std::shared_ptr<const CommonItemSet> common_item_set;
std::shared_ptr<const RareItemSet> rare_item_set;
uint8_t allowed_drop_modes = 0x00; // 0 = use server default
ServerDropMode default_drop_mode = ServerDropMode::CLIENT; // Ignored if allowed_drop_modes == 0
std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions; std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions;
Quest() = delete; Quest() = delete;
@@ -151,7 +167,12 @@ struct QuestIndex {
std::map<std::string, std::shared_ptr<Quest>> quests_by_name; std::map<std::string, std::shared_ptr<Quest>> quests_by_name;
std::map<uint32_t, std::map<uint32_t, std::shared_ptr<Quest>>> quests_by_category_id_and_number; std::map<uint32_t, std::map<uint32_t, std::shared_ptr<Quest>>> quests_by_category_id_and_number;
QuestIndex(const std::string& directory, std::shared_ptr<const QuestCategoryIndex> category_index, bool is_ep3); QuestIndex(
const std::string& directory,
std::shared_ptr<const QuestCategoryIndex> category_index,
const std::unordered_map<std::string, std::shared_ptr<const CommonItemSet>>& common_item_sets,
const std::unordered_map<std::string, std::shared_ptr<const RareItemSet>>& rare_item_sets,
bool is_ep3);
phosg::JSON json() const; phosg::JSON json() const;
std::shared_ptr<const Quest> get(uint32_t quest_number) const; std::shared_ptr<const Quest> get(uint32_t quest_number) const;
+12 -8
View File
@@ -2252,19 +2252,19 @@ static asio::awaitable<void> on_09(shared_ptr<Client> c, Channel::Message& msg)
} }
switch (game->drop_mode) { switch (game->drop_mode) {
case Lobby::DropMode::DISABLED: case ServerDropMode::DISABLED:
info += "$C6Drops disabled$C7\n"; info += "$C6Drops disabled$C7\n";
break; break;
case Lobby::DropMode::CLIENT: case ServerDropMode::CLIENT:
info += "$C6Client drops$C7\n"; info += "$C6Client drops$C7\n";
break; break;
case Lobby::DropMode::SERVER_SHARED: case ServerDropMode::SERVER_SHARED:
info += "$C6Server drops$C7\n"; info += "$C6Server drops$C7\n";
break; break;
case Lobby::DropMode::SERVER_PRIVATE: case ServerDropMode::SERVER_PRIVATE:
info += "$C6Private drops$C7\n"; info += "$C6Private drops$C7\n";
break; break;
case Lobby::DropMode::SERVER_DUPLICATE: case ServerDropMode::SERVER_DUPLICATE:
info += "$C6Duplicate drops$C7\n"; info += "$C6Duplicate drops$C7\n";
break; break;
} }
@@ -2433,6 +2433,10 @@ void set_lobby_quest(shared_ptr<Lobby> l, shared_ptr<const Quest> q, bool substi
if (l->episode != Episode::EP3) { if (l->episode != Episode::EP3) {
l->episode = q->episode; l->episode = q->episode;
} }
if (l->quest->allowed_drop_modes) {
l->allowed_drop_modes = l->quest->allowed_drop_modes;
l->drop_mode = l->quest->default_drop_mode;
}
l->create_item_creator(); l->create_item_creator();
size_t num_clients_with_loading_flag = 0; size_t num_clients_with_loading_flag = 0;
@@ -4585,7 +4589,7 @@ shared_ptr<Lobby> create_game_generic(
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
quest_flag_rewrites = nullptr; quest_flag_rewrites = nullptr;
game->drop_mode = Lobby::DropMode::DISABLED; game->drop_mode = ServerDropMode::DISABLED;
game->allowed_drop_modes = (1 << static_cast<size_t>(game->drop_mode)); game->allowed_drop_modes = (1 << static_cast<size_t>(game->drop_mode));
break; break;
case Version::BB_V4: case Version::BB_V4:
@@ -4601,10 +4605,10 @@ shared_ptr<Lobby> create_game_generic(
game->allowed_drop_modes = s->allowed_drop_modes_v4_normal; game->allowed_drop_modes = s->allowed_drop_modes_v4_normal;
} }
// Disallow CLIENT mode on BB // Disallow CLIENT mode on BB
if (game->drop_mode == Lobby::DropMode::CLIENT) { if (game->drop_mode == ServerDropMode::CLIENT) {
throw logic_error("CLIENT mode not allowed on BB"); throw logic_error("CLIENT mode not allowed on BB");
} }
if (game->allowed_drop_modes & (1 << static_cast<size_t>(Lobby::DropMode::CLIENT))) { if (game->allowed_drop_modes & (1 << static_cast<size_t>(ServerDropMode::CLIENT))) {
throw logic_error("CLIENT mode not allowed on BB"); throw logic_error("CLIENT mode not allowed on BB");
} }
break; break;
+16 -16
View File
@@ -2186,7 +2186,7 @@ static void on_box_or_enemy_item_drop_t(shared_ptr<Client> c, SubcommandMessage&
throw runtime_error("BB client sent 6x5F command"); throw runtime_error("BB client sent 6x5F command");
} }
bool should_notify = s->rare_notifs_enabled_for_client_drops && (l->drop_mode == Lobby::DropMode::CLIENT); bool should_notify = s->rare_notifs_enabled_for_client_drops && (l->drop_mode == ServerDropMode::CLIENT);
shared_ptr<const MapState::EnemyState> ene_st; shared_ptr<const MapState::EnemyState> ene_st;
shared_ptr<const MapState::ObjectState> obj_st; shared_ptr<const MapState::ObjectState> obj_st;
@@ -2956,27 +2956,27 @@ static asio::awaitable<void> on_entity_drop_item_request(shared_ptr<Client> c, S
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(msg.data, msg.size); G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(msg.data, msg.size);
auto rec = reconcile_drop_request_with_map(c, cmd, l->episode, l->event, l->map_state, true); auto rec = reconcile_drop_request_with_map(c, cmd, l->episode, l->event, l->map_state, true);
Lobby::DropMode drop_mode = l->drop_mode; ServerDropMode drop_mode = l->drop_mode;
switch (drop_mode) { switch (drop_mode) {
case Lobby::DropMode::DISABLED: case ServerDropMode::DISABLED:
co_return; co_return;
case Lobby::DropMode::CLIENT: { case ServerDropMode::CLIENT: {
// If the leader is BB, use SERVER_SHARED instead // If the leader is BB, use SERVER_SHARED instead
// TODO: We should also use server drops if any clients have incompatible // TODO: We should also use server drops if any clients have incompatible
// object lists, since they might generate incorrect IDs for items and we // object lists, since they might generate incorrect IDs for items and we
// can't override them // can't override them
auto leader = l->clients[l->leader_id]; auto leader = l->clients[l->leader_id];
if (leader && leader->version() == Version::BB_V4) { if (leader && leader->version() == Version::BB_V4) {
drop_mode = Lobby::DropMode::SERVER_SHARED; drop_mode = ServerDropMode::SERVER_SHARED;
break; break;
} else { } else {
forward_subcommand(c, msg); forward_subcommand(c, msg);
co_return; co_return;
} }
} }
case Lobby::DropMode::SERVER_SHARED: case ServerDropMode::SERVER_SHARED:
case Lobby::DropMode::SERVER_DUPLICATE: case ServerDropMode::SERVER_DUPLICATE:
case Lobby::DropMode::SERVER_PRIVATE: case ServerDropMode::SERVER_PRIVATE:
break; break;
default: default:
throw logic_error("invalid drop mode"); throw logic_error("invalid drop mode");
@@ -3016,11 +3016,11 @@ static asio::awaitable<void> on_entity_drop_item_request(shared_ptr<Client> c, S
}; };
switch (drop_mode) { switch (drop_mode) {
case Lobby::DropMode::DISABLED: case ServerDropMode::DISABLED:
case Lobby::DropMode::CLIENT: case ServerDropMode::CLIENT:
throw logic_error("unhandled simple drop mode"); throw logic_error("unhandled simple drop mode");
case Lobby::DropMode::SERVER_SHARED: case ServerDropMode::SERVER_SHARED:
case Lobby::DropMode::SERVER_DUPLICATE: { case ServerDropMode::SERVER_DUPLICATE: {
// TODO: In SERVER_DUPLICATE mode, should we reduce the rates for rare // TODO: In SERVER_DUPLICATE mode, should we reduce the rates for rare
// items? Maybe by a factor of l->count_clients()? // items? Maybe by a factor of l->count_clients()?
auto res = generate_item(); auto res = generate_item();
@@ -3029,7 +3029,7 @@ static asio::awaitable<void> on_entity_drop_item_request(shared_ptr<Client> c, S
} else { } else {
string name = s->describe_item(c->version(), res.item); string name = s->describe_item(c->version(), res.item);
l->log.info_f("Entity {:04X} (area {:02X}) created item {}", cmd.entity_index, cmd.effective_area, name); l->log.info_f("Entity {:04X} (area {:02X}) created item {}", cmd.entity_index, cmd.effective_area, name);
if (drop_mode == Lobby::DropMode::SERVER_DUPLICATE) { if (drop_mode == ServerDropMode::SERVER_DUPLICATE) {
for (const auto& lc : l->clients) { for (const auto& lc : l->clients) {
if (lc && (rec.obj_st || (lc->floor == cmd.floor))) { if (lc && (rec.obj_st || (lc->floor == cmd.floor))) {
res.item.id = l->generate_item_id(0xFF); res.item.id = l->generate_item_id(0xFF);
@@ -3058,7 +3058,7 @@ static asio::awaitable<void> on_entity_drop_item_request(shared_ptr<Client> c, S
} }
break; break;
} }
case Lobby::DropMode::SERVER_PRIVATE: { case ServerDropMode::SERVER_PRIVATE: {
for (const auto& lc : l->clients) { for (const auto& lc : l->clients) {
if (lc && (rec.obj_st || (lc->floor == cmd.floor))) { if (lc && (rec.obj_st || (lc->floor == cmd.floor))) {
auto res = generate_item(); auto res = generate_item();
@@ -3135,7 +3135,7 @@ static asio::awaitable<void> on_set_quest_flag(shared_ptr<Client> c, SubcommandM
forward_subcommand(c, msg); forward_subcommand(c, msg);
if (l->drop_mode != Lobby::DropMode::DISABLED) { if (l->drop_mode != ServerDropMode::DISABLED) {
EnemyType boss_enemy_type = EnemyType::NONE; EnemyType boss_enemy_type = EnemyType::NONE;
bool is_ep2 = (l->episode == Episode::EP2); bool is_ep2 = (l->episode == Episode::EP2);
if ((l->episode == Episode::EP1) && (c->floor == 0x0E)) { if ((l->episode == Episode::EP1) && (c->floor == 0x0E)) {
@@ -4916,7 +4916,7 @@ static asio::awaitable<void> on_photon_crystal_exchange_bb(shared_ptr<Client> c,
size_t index = p->inventory.find_item_by_primary_identifier(0x03100200); size_t index = p->inventory.find_item_by_primary_identifier(0x03100200);
auto item = p->remove_item(p->inventory.items[index].data.id, 1, *s->item_stack_limits(c->version())); auto item = p->remove_item(p->inventory.items[index].data.id, 1, *s->item_stack_limits(c->version()));
send_destroy_item_to_lobby(c, item.id, 1); send_destroy_item_to_lobby(c, item.id, 1);
l->drop_mode = Lobby::DropMode::DISABLED; l->drop_mode = ServerDropMode::DISABLED;
l->allowed_drop_modes = (1 << static_cast<uint8_t>(l->drop_mode)); // DISABLED only l->allowed_drop_modes = (1 << static_cast<uint8_t>(l->drop_mode)); // DISABLED only
co_return; co_return;
} }
+137 -72
View File
@@ -512,6 +512,35 @@ ItemData ServerState::parse_item_description(Version version, const string& desc
return this->item_name_index(version)->parse_item_description(description); return this->item_name_index(version)->parse_item_description(description);
} }
shared_ptr<const CommonItemSet> ServerState::common_item_set(Version logic_version, shared_ptr<const Quest> q) const {
if (q && q->common_item_set) {
return q->common_item_set;
} else if (is_v1_or_v2(logic_version)) {
// TODO: We should probably have a v1 common item set at some point too
return this->common_item_sets.at("common-table-v1-v2");
} else if (is_v3(logic_version) || is_v4(logic_version)) {
return this->common_item_sets.at("common-table-v3-v4");
} else {
throw runtime_error(std::format("no default common item set is available for {}", phosg::name_for_enum(logic_version)));
}
}
shared_ptr<const RareItemSet> ServerState::rare_item_set(Version logic_version, shared_ptr<const Quest> q) const {
if (q && q->rare_item_set) {
return q->rare_item_set;
} else if (is_v1(logic_version)) {
return this->rare_item_sets.at("rare-table-v1");
} else if (is_v2(logic_version)) {
return this->rare_item_sets.at("rare-table-v2");
} else if (is_v3(logic_version)) {
return this->rare_item_sets.at("rare-table-v3");
} else if (is_v4(logic_version)) {
return this->rare_item_sets.at("rare-table-v4");
} else {
throw runtime_error(std::format("no default rare item set is available for {}", phosg::name_for_enum(logic_version)));
}
}
void ServerState::set_port_configuration(const vector<PortConfiguration>& port_configs) { void ServerState::set_port_configuration(const vector<PortConfiguration>& port_configs) {
this->name_to_port_config.clear(); this->name_to_port_config.clear();
this->number_to_port_config.clear(); this->number_to_port_config.clear();
@@ -835,22 +864,22 @@ void ServerState::load_config_early() {
this->allowed_drop_modes_v4_normal = this->config_json->get_int("AllowedDropModesV4Normal", 0x1D); this->allowed_drop_modes_v4_normal = this->config_json->get_int("AllowedDropModesV4Normal", 0x1D);
this->allowed_drop_modes_v4_battle = this->config_json->get_int("AllowedDropModesV4Battle", 0x05); this->allowed_drop_modes_v4_battle = this->config_json->get_int("AllowedDropModesV4Battle", 0x05);
this->allowed_drop_modes_v4_challenge = this->config_json->get_int("AllowedDropModesV4Challenge", 0x05); this->allowed_drop_modes_v4_challenge = this->config_json->get_int("AllowedDropModesV4Challenge", 0x05);
this->default_drop_mode_v1_v2_normal = this->config_json->get_enum("DefaultDropModeV1V2Normal", Lobby::DropMode::CLIENT); this->default_drop_mode_v1_v2_normal = this->config_json->get_enum("DefaultDropModeV1V2Normal", ServerDropMode::CLIENT);
this->default_drop_mode_v1_v2_battle = this->config_json->get_enum("DefaultDropModeV1V2Battle", Lobby::DropMode::CLIENT); this->default_drop_mode_v1_v2_battle = this->config_json->get_enum("DefaultDropModeV1V2Battle", ServerDropMode::CLIENT);
this->default_drop_mode_v1_v2_challenge = this->config_json->get_enum("DefaultDropModeV1V2Challenge", Lobby::DropMode::CLIENT); this->default_drop_mode_v1_v2_challenge = this->config_json->get_enum("DefaultDropModeV1V2Challenge", ServerDropMode::CLIENT);
this->default_drop_mode_v3_normal = this->config_json->get_enum("DefaultDropModeV3Normal", Lobby::DropMode::CLIENT); this->default_drop_mode_v3_normal = this->config_json->get_enum("DefaultDropModeV3Normal", ServerDropMode::CLIENT);
this->default_drop_mode_v3_battle = this->config_json->get_enum("DefaultDropModeV3Battle", Lobby::DropMode::CLIENT); this->default_drop_mode_v3_battle = this->config_json->get_enum("DefaultDropModeV3Battle", ServerDropMode::CLIENT);
this->default_drop_mode_v3_challenge = this->config_json->get_enum("DefaultDropModeV3Challenge", Lobby::DropMode::CLIENT); this->default_drop_mode_v3_challenge = this->config_json->get_enum("DefaultDropModeV3Challenge", ServerDropMode::CLIENT);
this->default_drop_mode_v4_normal = this->config_json->get_enum("DefaultDropModeV4Normal", Lobby::DropMode::SERVER_SHARED); this->default_drop_mode_v4_normal = this->config_json->get_enum("DefaultDropModeV4Normal", ServerDropMode::SERVER_SHARED);
this->default_drop_mode_v4_battle = this->config_json->get_enum("DefaultDropModeV4Battle", Lobby::DropMode::SERVER_SHARED); this->default_drop_mode_v4_battle = this->config_json->get_enum("DefaultDropModeV4Battle", ServerDropMode::SERVER_SHARED);
this->default_drop_mode_v4_challenge = this->config_json->get_enum("DefaultDropModeV4Challenge", Lobby::DropMode::SERVER_SHARED); this->default_drop_mode_v4_challenge = this->config_json->get_enum("DefaultDropModeV4Challenge", ServerDropMode::SERVER_SHARED);
if ((this->default_drop_mode_v4_normal == Lobby::DropMode::CLIENT) || if ((this->default_drop_mode_v4_normal == ServerDropMode::CLIENT) ||
(this->default_drop_mode_v4_battle == Lobby::DropMode::CLIENT) || (this->default_drop_mode_v4_battle == ServerDropMode::CLIENT) ||
(this->default_drop_mode_v4_challenge == Lobby::DropMode::CLIENT)) { (this->default_drop_mode_v4_challenge == ServerDropMode::CLIENT)) {
throw runtime_error("default V4 drop mode cannot be CLIENT"); throw runtime_error("default V4 drop mode cannot be CLIENT");
} }
if ((this->allowed_drop_modes_v4_normal & (1 << static_cast<size_t>(Lobby::DropMode::CLIENT))) || if ((this->allowed_drop_modes_v4_normal & (1 << static_cast<size_t>(ServerDropMode::CLIENT))) ||
(this->allowed_drop_modes_v4_battle & (1 << static_cast<size_t>(Lobby::DropMode::CLIENT))) || (this->allowed_drop_modes_v4_challenge & (1 << static_cast<size_t>(Lobby::DropMode::CLIENT)))) { (this->allowed_drop_modes_v4_battle & (1 << static_cast<size_t>(ServerDropMode::CLIENT))) || (this->allowed_drop_modes_v4_challenge & (1 << static_cast<size_t>(ServerDropMode::CLIENT)))) {
throw runtime_error("CLIENT drop mode cannot be allowed in V4"); throw runtime_error("CLIENT drop mode cannot be allowed in V4");
} }
@@ -1940,61 +1969,103 @@ void ServerState::load_item_name_indexes() {
} }
void ServerState::load_drop_tables() { void ServerState::load_drop_tables() {
config_log.info_f("Loading rare item sets"); config_log.info_f("Loading item sets");
unordered_map<string, shared_ptr<RareItemSet>> new_rare_item_sets; unordered_map<string, shared_ptr<const RareItemSet>> new_rare_item_sets;
unordered_map<string, shared_ptr<const CommonItemSet>> new_common_item_sets;
for (const auto& item : std::filesystem::directory_iterator("system/item-tables")) { for (const auto& item : std::filesystem::directory_iterator("system/item-tables")) {
string filename = item.path().filename().string(); string filename = item.path().filename().string();
if (!filename.starts_with("rare-table-")) {
continue;
}
string path = "system/item-tables/" + filename; if (filename.starts_with("common-table-") || filename.starts_with("ItemPT-")) {
size_t ext_offset = filename.rfind('.'); string path = "system/item-tables/" + filename;
string basename = (ext_offset == string::npos) ? filename : filename.substr(0, ext_offset); size_t ext_offset = filename.rfind('.');
string basename = (ext_offset == string::npos) ? filename : filename.substr(0, ext_offset);
if (filename.ends_with("-v1.json")) { // AFSV2CommonItemSet(std::shared_ptr<const std::string> pt_afs_data, std::shared_ptr<const std::string> ct_afs_data);
config_log.info_f("Loading v1 JSON rare item table {}", filename);
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(phosg::JSON::parse(phosg::load_file(path)), this->item_name_index(Version::DC_V1)));
} else if (filename.ends_with("-v2.json")) {
config_log.info_f("Loading v2 JSON rare item table {}", filename);
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(phosg::JSON::parse(phosg::load_file(path)), this->item_name_index(Version::PC_V2)));
} else if (filename.ends_with("-v3.json")) {
config_log.info_f("Loading v3 JSON rare item table {}", filename);
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(phosg::JSON::parse(phosg::load_file(path)), this->item_name_index(Version::GC_V3)));
} else if (filename.ends_with("-v4.json")) {
config_log.info_f("Loading v4 JSON rare item table {}", filename);
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(phosg::JSON::parse(phosg::load_file(path)), this->item_name_index(Version::BB_V4)));
} else if (filename.ends_with(".afs")) { if (filename.ends_with(".json")) {
config_log.info_f("Loading AFS rare item table {}", filename); config_log.info_f("Loading JSON common item table {}", filename);
auto data = make_shared<string>(phosg::load_file(path)); new_common_item_sets.emplace(basename, make_shared<JSONCommonItemSet>(phosg::JSON::parse(phosg::load_file(path))));
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(AFSArchive(data), false)); } else if (filename.ends_with(".afs")) {
string ct_filename;
if (filename.starts_with("ItemPT-")) {
ct_filename = "ItemCT-" + filename.substr(7);
} else if (filename.starts_with("common-table-")) {
ct_filename = "challenge-common-table-" + filename.substr(13);
} else {
throw std::runtime_error(std::format("cannot determine challenge table filename for common table file: {}", filename));
}
auto data = make_shared<string>(phosg::load_file(path));
shared_ptr<string> ct_data;
try {
string ct_path = "system/item-tables/" + ct_filename;
ct_data = make_shared<string>(phosg::load_file(ct_path));
config_log.info_f("Loading AFS common item table {} with challenge table {}", filename, ct_filename);
} catch (const phosg::cannot_open_file&) {
config_log.info_f("Loading AFS common item table {} without challenge table", filename);
}
new_common_item_sets.emplace(basename, make_shared<AFSV2CommonItemSet>(data, ct_data));
} else if (filename.ends_with(".gsl")) {
config_log.info_f("Loading little-endian GSL common item table {}", filename);
auto data = make_shared<string>(phosg::load_file(path));
new_common_item_sets.emplace(basename, make_shared<GSLV3V4CommonItemSet>(data, false));
} else if (filename.ends_with(".gslb")) {
config_log.info_f("Loading big-endian GSL common item table {}", filename);
auto data = make_shared<string>(phosg::load_file(path));
new_common_item_sets.emplace(basename, make_shared<GSLV3V4CommonItemSet>(data, true));
} else {
throw std::runtime_error(std::format("unknown format for common table file: {}", filename));
}
} else if (filename.ends_with(".gsl")) { } else if (filename.starts_with("rare-table-") || filename.starts_with("ItemRT-")) {
config_log.info_f("Loading GSL rare item table {}", filename); string path = "system/item-tables/" + filename;
auto data = make_shared<string>(phosg::load_file(path)); size_t ext_offset = filename.rfind('.');
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(GSLArchive(data, false), false)); string basename = (ext_offset == string::npos) ? filename : filename.substr(0, ext_offset);
} else if (filename.ends_with(".gslb")) { shared_ptr<RareItemSet> rare_set;
config_log.info_f("Loading GSL rare item table {}", filename); if (filename.ends_with("-v1.json")) {
auto data = make_shared<string>(phosg::load_file(path)); config_log.info_f("Loading v1 JSON rare item table {}", filename);
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(GSLArchive(data, true), true)); rare_set = make_shared<RareItemSet>(phosg::JSON::parse(phosg::load_file(path)), this->item_name_index(Version::DC_V1));
} else if (filename.ends_with("-v2.json")) {
config_log.info_f("Loading v2 JSON rare item table {}", filename);
rare_set = make_shared<RareItemSet>(phosg::JSON::parse(phosg::load_file(path)), this->item_name_index(Version::PC_V2));
} else if (filename.ends_with("-v3.json")) {
config_log.info_f("Loading v3 JSON rare item table {}", filename);
rare_set = make_shared<RareItemSet>(phosg::JSON::parse(phosg::load_file(path)), this->item_name_index(Version::GC_V3));
} else if (filename.ends_with("-v4.json")) {
config_log.info_f("Loading v4 JSON rare item table {}", filename);
rare_set = make_shared<RareItemSet>(phosg::JSON::parse(phosg::load_file(path)), this->item_name_index(Version::BB_V4));
} else if (filename.ends_with(".rel")) { } else if (filename.ends_with(".afs")) {
config_log.info_f("Loading REL rare item table {}", filename); config_log.info_f("Loading AFS rare item table {}", filename);
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(phosg::load_file(path), true)); auto data = make_shared<string>(phosg::load_file(path));
rare_set = make_shared<RareItemSet>(AFSArchive(data), false);
} else if (filename.ends_with(".gsl")) {
config_log.info_f("Loading GSL rare item table {}", filename);
auto data = make_shared<string>(phosg::load_file(path));
rare_set = make_shared<RareItemSet>(GSLArchive(data, false), false);
} else if (filename.ends_with(".gslb")) {
config_log.info_f("Loading GSL rare item table {}", filename);
auto data = make_shared<string>(phosg::load_file(path));
rare_set = make_shared<RareItemSet>(GSLArchive(data, true), true);
} else if (filename.ends_with(".rel")) {
config_log.info_f("Loading REL rare item table {}", filename);
rare_set = make_shared<RareItemSet>(phosg::load_file(path), true);
} else {
throw std::runtime_error(std::format("unknown format for rare table file: {}", filename));
}
if (this->server_global_drop_rate_multiplier != 1.0) {
rare_set->multiply_all_rates(this->server_global_drop_rate_multiplier);
}
new_rare_item_sets.emplace(basename, std::move(rare_set));
} }
} }
config_log.info_f("Loading v2 common item table");
auto ct_data_v2 = make_shared<string>(phosg::load_file("system/item-tables/ItemCT-pc-v2.afs"));
auto pt_data_v2 = make_shared<string>(phosg::load_file("system/item-tables/ItemPT-pc-v2.afs"));
auto new_common_item_set_v2 = make_shared<AFSV2CommonItemSet>(pt_data_v2, ct_data_v2);
config_log.info_f("Loading v3+v4 common item table");
auto pt_data_v3_v4 = make_shared<string>(phosg::load_file("system/item-tables/ItemPT-gc-v3.gsl"));
auto new_common_item_set_v3_v4 = make_shared<GSLV3V4CommonItemSet>(pt_data_v3_v4, true);
config_log.info_f("Loading armor table"); config_log.info_f("Loading armor table");
auto armor_data = make_shared<string>(phosg::load_file("system/item-tables/ArmorRandom-gc-v3.rel")); auto armor_data = make_shared<string>(phosg::load_file("system/item-tables/ArmorRandom-gc-v3.rel"));
auto new_armor_random_set = make_shared<ArmorRandomSet>(armor_data); auto new_armor_random_set = make_shared<ArmorRandomSet>(armor_data);
@@ -2020,19 +2091,8 @@ void ServerState::load_drop_tables() {
auto tekker_data = make_shared<string>(phosg::load_file("system/item-tables/JudgeItem-gc-v3.rel")); auto tekker_data = make_shared<string>(phosg::load_file("system/item-tables/JudgeItem-gc-v3.rel"));
auto new_tekker_adjustment_set = make_shared<TekkerAdjustmentSet>(tekker_data); auto new_tekker_adjustment_set = make_shared<TekkerAdjustmentSet>(tekker_data);
if (this->server_global_drop_rate_multiplier != 1.0) { this->rare_item_sets = std::move(new_rare_item_sets);
for (auto& it : new_rare_item_sets) { this->common_item_sets = std::move(new_common_item_sets);
it.second->multiply_all_rates(this->server_global_drop_rate_multiplier);
}
}
// We can't just std::move() new_rare_item_sets into place because its values are
// not const :(
this->rare_item_sets.clear();
for (auto& it : new_rare_item_sets) {
this->rare_item_sets.emplace(it.first, std::move(it.second));
}
this->common_item_set_v2 = std::move(new_common_item_set_v2);
this->common_item_set_v3_v4 = std::move(new_common_item_set_v3_v4);
this->armor_random_set = std::move(new_armor_random_set); this->armor_random_set = std::move(new_armor_random_set);
this->tool_random_set = std::move(new_tool_random_set); this->tool_random_set = std::move(new_tool_random_set);
this->weapon_random_sets = std::move(new_weapon_random_sets); this->weapon_random_sets = std::move(new_weapon_random_sets);
@@ -2107,9 +2167,14 @@ void ServerState::load_ep3_tournament_state() {
void ServerState::load_quest_index() { void ServerState::load_quest_index() {
config_log.info_f("Collecting quests"); config_log.info_f("Collecting quests");
this->default_quest_index = make_shared<QuestIndex>("system/quests", this->quest_category_index, false); this->default_quest_index = make_shared<QuestIndex>("system/quests", this->quest_category_index, this->common_item_sets, this->rare_item_sets, false);
config_log.info_f("Collecting Episode 3 download quests"); config_log.info_f("Collecting Episode 3 download quests");
this->ep3_download_quest_index = make_shared<QuestIndex>("system/ep3/maps-download", this->quest_category_index, true); this->ep3_download_quest_index = make_shared<QuestIndex>(
"system/ep3/maps-download",
this->quest_category_index,
unordered_map<string, shared_ptr<const CommonItemSet>>{},
unordered_map<string, shared_ptr<const RareItemSet>>{},
true);
} }
void ServerState::compile_functions() { void ServerState::compile_functions() {
+13 -11
View File
@@ -128,15 +128,15 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
uint8_t allowed_drop_modes_v4_normal = 0x1D; // CLIENT not allowed uint8_t allowed_drop_modes_v4_normal = 0x1D; // CLIENT not allowed
uint8_t allowed_drop_modes_v4_battle = 0x05; uint8_t allowed_drop_modes_v4_battle = 0x05;
uint8_t allowed_drop_modes_v4_challenge = 0x05; uint8_t allowed_drop_modes_v4_challenge = 0x05;
Lobby::DropMode default_drop_mode_v1_v2_normal = Lobby::DropMode::CLIENT; ServerDropMode default_drop_mode_v1_v2_normal = ServerDropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_battle = Lobby::DropMode::CLIENT; ServerDropMode default_drop_mode_v1_v2_battle = ServerDropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_challenge = Lobby::DropMode::CLIENT; ServerDropMode default_drop_mode_v1_v2_challenge = ServerDropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_normal = Lobby::DropMode::CLIENT; ServerDropMode default_drop_mode_v3_normal = ServerDropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_battle = Lobby::DropMode::CLIENT; ServerDropMode default_drop_mode_v3_battle = ServerDropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_challenge = Lobby::DropMode::CLIENT; ServerDropMode default_drop_mode_v3_challenge = ServerDropMode::CLIENT;
Lobby::DropMode default_drop_mode_v4_normal = Lobby::DropMode::SERVER_SHARED; ServerDropMode default_drop_mode_v4_normal = ServerDropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_battle = Lobby::DropMode::SERVER_SHARED; ServerDropMode default_drop_mode_v4_battle = ServerDropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_challenge = Lobby::DropMode::SERVER_SHARED; ServerDropMode default_drop_mode_v4_challenge = ServerDropMode::SERVER_SHARED;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v1_v2; std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v1_v2;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v3; std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v3;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v4; std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v4;
@@ -196,9 +196,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<const LevelTable> level_table_v4; std::shared_ptr<const LevelTable> level_table_v4;
std::shared_ptr<const BattleParamsIndex> battle_params; std::shared_ptr<const BattleParamsIndex> battle_params;
std::shared_ptr<const GSLArchive> bb_data_gsl; std::shared_ptr<const GSLArchive> bb_data_gsl;
std::unordered_map<std::string, std::shared_ptr<const CommonItemSet>> common_item_sets;
std::unordered_map<std::string, std::shared_ptr<const RareItemSet>> rare_item_sets; std::unordered_map<std::string, std::shared_ptr<const RareItemSet>> rare_item_sets;
std::shared_ptr<const CommonItemSet> common_item_set_v2;
std::shared_ptr<const CommonItemSet> common_item_set_v3_v4;
std::shared_ptr<const ArmorRandomSet> armor_random_set; std::shared_ptr<const ArmorRandomSet> armor_random_set;
std::shared_ptr<const ToolRandomSet> tool_random_set; std::shared_ptr<const ToolRandomSet> tool_random_set;
std::array<std::shared_ptr<const WeaponRandomSet>, 4> weapon_random_sets; std::array<std::shared_ptr<const WeaponRandomSet>, 4> weapon_random_sets;
@@ -357,6 +356,9 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::string describe_item(Version version, const ItemData& item, uint8_t flags = 0) const; std::string describe_item(Version version, const ItemData& item, uint8_t flags = 0) const;
ItemData parse_item_description(Version version, const std::string& description) const; ItemData parse_item_description(Version version, const std::string& description) const;
std::shared_ptr<const CommonItemSet> common_item_set(Version logic_version, std::shared_ptr<const Quest> q) const;
std::shared_ptr<const RareItemSet> rare_item_set(Version logic_version, std::shared_ptr<const Quest> q) const;
const std::vector<uint32_t>& public_lobby_search_order(Version version, bool is_client_customization) const; const std::vector<uint32_t>& public_lobby_search_order(Version version, bool is_client_customization) const;
inline const std::vector<uint32_t>& public_lobby_search_order(std::shared_ptr<const Client> c) const { inline const std::vector<uint32_t>& public_lobby_search_order(std::shared_ptr<const Client> c) const {
return this->public_lobby_search_order(c->version(), c->check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION)); return this->public_lobby_search_order(c->version(), c->check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
+1
View File
@@ -0,0 +1 @@
ItemCT-pc-v2.afs
+1
View File
@@ -0,0 +1 @@
ItemPT-pc-v2.afs
+1
View File
@@ -0,0 +1 @@
ItemPT-gc-v3.gslb
+25
View File
@@ -75,4 +75,29 @@
// via $quest, AvailableIf and EnabledIf are not checked, so it's inadvisable // via $quest, AvailableIf and EnabledIf are not checked, so it's inadvisable
// to use this option if either of those options are also used. // to use this option if either of those options are also used.
// "AllowStartFromChatCommand": true, // "AllowStartFromChatCommand": true,
// If this field is specified, it overrides the default common item table for
// the duration of the quest. The common item table name must begin with
// either "common-table-" or "ItemPT-", and the corresponding file should be
// in system/item-tables/ and should be in .json, .afs, .gsl, or .gslb
// format. The file extension (.json, etc.) should not be included here. If
// you use this, make sure to set AllowedDropModes appropriately below.
// "CommonItemSetName": "common-table-custom1",
// If this field is specified, it overrides the default rare item table for
// the duration of the quest. The rare item table name must begin with either
// "rare-table-" or "ItemRT-". As for common item sets, the rare table must
// be in system/item-tables/. If it's in JSON format, the table name must end
// with -v1, -v2, -v3, or -v4. If you use this, make sure to set
// AllowedDropModes appropriately below.
// "RareItemSetName": "rare-table-custom1",
// If these fields are specified, they override the allowed drop modes and
// the default drop mode for the game when the quest is loaded. These
// function analogously to the drop mode fields in config.json; see the
// comments there for more information. If a custom common or rare table is
// also specified above, the client drop mode should be disallowed here (by
// clearing the 0x02 bit of AllowedDropModes).
// "AllowedDropModes": 0x1D,
// "DefaultDropMode": "SERVER_PRIVATE",
} }