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/.
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.
+4
View File
@@ -23,6 +23,10 @@ public:
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::string get_copy(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) {
using DropMode = ProxySession::DropMode;
if (a.text.empty()) {
switch (a.c->proxy_session->drop_mode) {
case DropMode::DISABLED:
case ProxyDropMode::DISABLED:
send_text_message(a.c, "Drop mode: disabled");
break;
case DropMode::PASSTHROUGH:
case ProxyDropMode::PASSTHROUGH:
send_text_message(a.c, "Drop mode: default");
break;
case DropMode::INTERCEPT:
case ProxyDropMode::INTERCEPT:
send_text_message(a.c, "Drop mode: proxy");
break;
}
} else {
DropMode new_mode;
ProxyDropMode new_mode;
if ((a.text == "none") || (a.text == "disabled")) {
new_mode = DropMode::DISABLED;
new_mode = ProxyDropMode::DISABLED;
} else if ((a.text == "default") || (a.text == "passthrough")) {
new_mode = DropMode::PASSTHROUGH;
new_mode = ProxyDropMode::PASSTHROUGH;
} else if ((a.text == "proxy") || (a.text == "intercept")) {
new_mode = DropMode::INTERCEPT;
new_mode = ProxyDropMode::INTERCEPT;
} else {
throw precondition_failed("Invalid drop 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) {
case DropMode::DISABLED:
case ProxyDropMode::DISABLED:
send_text_message(a.c->channel, "Item drops disabled");
break;
case DropMode::PASSTHROUGH:
case ProxyDropMode::PASSTHROUGH:
send_text_message(a.c->channel, "Item drops changed\nto default mode");
break;
case DropMode::INTERCEPT:
case ProxyDropMode::INTERCEPT:
send_text_message(a.c->channel, "Item drops changed\nto proxy mode");
break;
}
@@ -829,36 +828,36 @@ ChatCommandDefinition cc_dropmode(
auto l = a.c->require_lobby();
if (a.text.empty()) {
switch (l->drop_mode) {
case Lobby::DropMode::DISABLED:
case ServerDropMode::DISABLED:
send_text_message(a.c, "Drop mode: disabled");
break;
case Lobby::DropMode::CLIENT:
case ServerDropMode::CLIENT:
send_text_message(a.c, "Drop mode: client");
break;
case Lobby::DropMode::SERVER_SHARED:
case ServerDropMode::SERVER_SHARED:
send_text_message(a.c, "Drop mode: server\nshared");
break;
case Lobby::DropMode::SERVER_PRIVATE:
case ServerDropMode::SERVER_PRIVATE:
send_text_message(a.c, "Drop mode: server\nprivate");
break;
case Lobby::DropMode::SERVER_DUPLICATE:
case ServerDropMode::SERVER_DUPLICATE:
send_text_message(a.c, "Drop mode: server\nduplicate");
break;
}
} else {
a.check_is_leader();
Lobby::DropMode new_mode;
ServerDropMode new_mode;
if ((a.text == "none") || (a.text == "disabled")) {
new_mode = Lobby::DropMode::DISABLED;
new_mode = ServerDropMode::DISABLED;
} else if (a.text == "client") {
new_mode = Lobby::DropMode::CLIENT;
new_mode = ServerDropMode::CLIENT;
} 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")) {
new_mode = Lobby::DropMode::SERVER_PRIVATE;
new_mode = ServerDropMode::SERVER_PRIVATE;
} else if ((a.text == "duplicate") || (a.text == "dup")) {
new_mode = Lobby::DropMode::SERVER_DUPLICATE;
new_mode = ServerDropMode::SERVER_DUPLICATE;
} else {
throw precondition_failed("Invalid drop mode");
}
@@ -869,19 +868,19 @@ ChatCommandDefinition cc_dropmode(
l->drop_mode = new_mode;
switch (l->drop_mode) {
case Lobby::DropMode::DISABLED:
case ServerDropMode::DISABLED:
send_text_message(l, "Item drops disabled");
break;
case Lobby::DropMode::CLIENT:
case ServerDropMode::CLIENT:
send_text_message(l, "Item drops changed\nto client mode");
break;
case Lobby::DropMode::SERVER_SHARED:
case ServerDropMode::SERVER_SHARED:
send_text_message(l, "Item drops changed\nto server shared\nmode");
break;
case Lobby::DropMode::SERVER_PRIVATE:
case ServerDropMode::SERVER_PRIVATE:
send_text_message(l, "Item drops changed\nto server private\nmode");
break;
case Lobby::DropMode::SERVER_DUPLICATE:
case ServerDropMode::SERVER_DUPLICATE:
send_text_message(l, "Item drops changed\nto server duplicate\nmode");
break;
}
@@ -1262,7 +1261,7 @@ ChatCommandDefinition cc_item(
item = s->parse_item_description(a.c->version(), a.text);
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));
send_drop_stacked_item_to_channel(s, a.c->channel, item, a.c->floor, a.c->pos);
} else {
@@ -1452,19 +1451,19 @@ ChatCommandDefinition cc_lobby_info(
"$C7Section ID: $C6{}$C7", name_for_section_id(l->effective_section_id())));
switch (l->drop_mode) {
case Lobby::DropMode::DISABLED:
case ServerDropMode::DISABLED:
lines.emplace_back("Drops disabled");
break;
case Lobby::DropMode::CLIENT:
case ServerDropMode::CLIENT:
lines.emplace_back("Client item table");
break;
case Lobby::DropMode::SERVER_SHARED:
case ServerDropMode::SERVER_SHARED:
lines.emplace_back("Server item table");
break;
case Lobby::DropMode::SERVER_PRIVATE:
case ServerDropMode::SERVER_PRIVATE:
lines.emplace_back("Server indiv items");
break;
case Lobby::DropMode::SERVER_DUPLICATE:
case ServerDropMode::SERVER_DUPLICATE:
lines.emplace_back("Server dup items");
break;
default:
+9 -6
View File
@@ -435,9 +435,9 @@ struct C_LegacyLogin_BB_04 {
// 05 = Server down for maintenance (108)
// 06 = Incorrect password (127)
// Any other nonzero value = Generic failure (101)
// The client config field in this command is ignored by pre-V3 clients as well
// as Episodes 1&2 Trial Edition. All other V3 clients save it as opaque data to
// be returned in a 9E or 9F command later.
// The client config field in this command is ignored by all clients that never
// send 9E. Clients that do send 9E will save thie client config as opaque data
// to be returned in a 9E or 9F command later.
// 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
// 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;
} __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
// 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;
} __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 {
G_ClientIDHeader header;
le_uint32_t angle = 0; // In range [0x0000, 0xFFFF]
} __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 {
G_ClientIDHeader header;
+48 -24
View File
@@ -677,29 +677,49 @@ shared_ptr<const CommonItemSet::Table> CommonItemSet::get_table(
}
AFSV2CommonItemSet::AFSV2CommonItemSet(
std::shared_ptr<const std::string> pt_afs_data,
std::shared_ptr<const std::string> ct_afs_data) {
// ItemPT.afs has 40 entries; the first 10 are for Normal, then Hard, etc.
AFSArchive pt_afs(pt_afs_data);
for (size_t difficulty = 0; difficulty < 4; 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);
std::shared_ptr<const std::string> pt_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
// Hard, etc.
{
AFSArchive pt_afs(pt_afs_data);
size_t max_difficulty;
if (pt_afs.num_entries() >= 40) {
max_difficulty = 4;
} else if (pt_afs.num_entries() >= 30) {
max_difficulty = 3;
} else {
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
// used (section_id is ignored)
AFSArchive ct_afs(ct_afs_data);
for (size_t difficulty = 0; difficulty < 4; 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);
// ItemCT AFS files also have 40 entries, but only the 0th, 10th, 20th, and
// 30th are used (section_id is ignored)
if (ct_afs_data) {
AFSArchive ct_afs(ct_afs_data);
size_t max_difficulty;
if (ct_afs.num_entries() >= 40) {
max_difficulty = 4;
} 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) {
for (size_t difficulty = 0; difficulty < 4; difficulty++) {
auto r = gsl.get_reader(filename_for_table(episode, difficulty, 0, true));
auto table = make_shared<Table>(r, is_big_endian, true, episode);
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);
try {
auto r = gsl.get_reader(filename_for_table(episode, difficulty, 0, true));
auto table = make_shared<Table>(r, is_big_endian, true, episode);
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)},
});
switch (ses->drop_mode) {
case ProxySession::DropMode::DISABLED:
case ProxyDropMode::DISABLED:
ses_json.emplace("DropMode", "none");
break;
case ProxySession::DropMode::PASSTHROUGH:
case ProxyDropMode::PASSTHROUGH:
ses_json.emplace("DropMode", "default");
break;
case ProxySession::DropMode::INTERCEPT:
case ProxyDropMode::INTERCEPT:
ses_json.emplace("DropMode", "proxy");
break;
}
@@ -386,19 +386,19 @@ std::shared_ptr<phosg::JSON> HTTPServer::generate_lobby_json(
ret->emplace("EXPShareMultiplier", l->exp_share_multiplier);
ret->emplace("AllowedDropModes", l->allowed_drop_modes);
switch (l->drop_mode) {
case Lobby::DropMode::DISABLED:
case ServerDropMode::DISABLED:
ret->emplace("DropMode", "none");
break;
case Lobby::DropMode::CLIENT:
case ServerDropMode::CLIENT:
ret->emplace("DropMode", "client");
break;
case Lobby::DropMode::SERVER_SHARED:
case ServerDropMode::SERVER_SHARED:
ret->emplace("DropMode", "shared");
break;
case Lobby::DropMode::SERVER_PRIVATE:
case ServerDropMode::SERVER_PRIVATE:
ret->emplace("DropMode", "private");
break;
case Lobby::DropMode::SERVER_DUPLICATE:
case ServerDropMode::SERVER_DUPLICATE:
ret->emplace("DropMode", "duplicate");
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 {
auto v2_table = this->state->common_item_set_v2;
auto v3_v4_table = this->state->common_item_set_v3_v4;
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
return make_shared<phosg::JSON>(phosg::JSON::dict({{"v1_v2", v2_table->json()}, {"v3_v4", v3_v4_table->json()}}));
});
std::shared_ptr<phosg::JSON> HTTPServer::generate_common_table_list_json() const {
auto ret = make_shared<phosg::JSON>(phosg::JSON::list());
for (const auto& it : this->state->common_item_sets) {
ret->emplace_back(it.first);
}
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 {
@@ -794,7 +805,10 @@ asio::awaitable<std::unique_ptr<HTTPResponse>> HTTPServer::handle_request(shared
ret = co_await this->generate_ep3_cards_json(true);
} else if (req.path == "/y/data/common-tables") {
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") {
this->require_GET(req);
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;
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;
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_quest_list_json(std::shared_ptr<const QuestIndex> q);
+35
View File
@@ -4,6 +4,41 @@
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)
: version(version),
data(data),
+20
View File
@@ -16,6 +16,26 @@
#include "Types.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 {
public:
// 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),
random_seed(phosg::random_object<uint32_t>()),
rand_crypt(make_shared<DisabledRandomGenerator>()),
drop_mode(DropMode::CLIENT),
drop_mode(ServerDropMode::CLIENT),
event(0),
block(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;
}
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;
if (s->use_psov2_rand_crypt) {
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());
}
this->item_creator = make_shared<ItemCreator>(
common_item_set,
rare_item_set,
s->common_item_set(logic_version, this->quest),
s->rare_item_set(logic_version, this->quest),
s->armor_random_set,
s->tool_random_set,
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;
}
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,
// 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;
phosg::PrefixedLogger log;
@@ -136,7 +129,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
uint32_t random_seed;
std::shared_ptr<RandomGenerator> rand_crypt;
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
struct ChallengeParameters {
@@ -291,6 +284,6 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
};
template <>
Lobby::DropMode phosg::enum_for_name<Lobby::DropMode>(const char* name);
ServerDropMode phosg::enum_for_name<ServerDropMode>(const char* name);
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;
}
using DropMode = ProxySession::DropMode;
switch (c->proxy_session->drop_mode) {
case DropMode::DISABLED:
case ProxyDropMode::DISABLED:
co_return HandlerResult::SUPPRESS;
case DropMode::PASSTHROUGH:
case ProxyDropMode::PASSTHROUGH:
co_return HandlerResult::FORWARD;
case DropMode::INTERCEPT:
case ProxyDropMode::INTERCEPT:
break;
default:
throw logic_error("invalid drop mode");
+5 -38
View File
@@ -21,47 +21,14 @@ ProxySession::~ProxySession() {
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;
if (this->drop_mode == DropMode::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");
}
if (this->drop_mode == ProxyDropMode::INTERCEPT) {
auto rand_crypt = make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed);
this->item_creator = make_shared<ItemCreator>(
common_item_set,
rare_item_set,
s->common_item_set(version, nullptr),
s->rare_item_set(version, nullptr),
s->armor_random_set,
s->tool_random_set,
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;
parray<uint8_t, 0x28> remote_client_config_data;
enum class DropMode {
DISABLED = 0,
PASSTHROUGH,
INTERCEPT,
};
DropMode drop_mode = DropMode::PASSTHROUGH;
ProxyDropMode drop_mode = ProxyDropMode::PASSTHROUGH;
std::shared_ptr<std::string> quest_dat_data;
std::shared_ptr<ItemCreator> item_creator;
std::shared_ptr<MapState> map_state;
@@ -80,7 +75,7 @@ struct ProxySession {
};
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);
};
+70 -7
View File
@@ -221,6 +221,15 @@ void VersionedQuest::assert_valid() const {
if (!is_ep3(this->version) && !this->map_file) {
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 {
@@ -280,7 +289,13 @@ Quest::Quest(shared_ptr<const VersionedQuest> initial_version)
challenge_template_index(initial_version->challenge_template_index),
description_flag(initial_version->description_flag),
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);
}
@@ -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({
{"Number", this->quest_number},
{"CategoryID", this->category_id},
@@ -311,11 +322,15 @@ phosg::JSON Quest::json() const {
{"MaxPlayers", this->max_players},
{"LockStatusRegister", (this->lock_status_register >= 0) ? this->lock_status_register : phosg::JSON(nullptr)},
{"Name", this->name},
{"BattleRules", std::move(battle_rules_json)},
{"ChallengeTemplateIndex", std::move(challenge_template_index_json)},
{"BattleRules", this->battle_rules ? this->battle_rules->json() : phosg::JSON(nullptr)},
{"ChallengeTemplateIndex", (this->challenge_template_index >= 0) ? this->challenge_template_index : phosg::JSON(nullptr)},
{"DescriptionFlag", this->description_flag},
{"AvailableExpression", 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)},
});
}
@@ -406,6 +421,30 @@ void Quest::add_version(shared_ptr<const VersionedQuest> vq) {
"quest version has a different enabled expression (existing: {}, new: {})",
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);
}
@@ -482,6 +521,8 @@ shared_ptr<const VersionedQuest> Quest::version(Version v, uint8_t language) con
QuestIndex::QuestIndex(
const string& directory,
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)
: directory(directory),
category_index(category_index) {
@@ -914,6 +955,28 @@ QuestIndex::QuestIndex(
vq->lock_status_register = metadata_json.get_int("LockStatusRegister");
} 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();
+22 -1
View File
@@ -8,10 +8,13 @@
#include <unordered_map>
#include <vector>
#include "CommonItemSet.hh"
#include "IntegralExpression.hh"
#include "ItemParameterTable.hh"
#include "Map.hh"
#include "PlayerSubordinates.hh"
#include "QuestScript.hh"
#include "RareItemSet.hh"
#include "StaticGameData.hh"
#include "TeamIndex.hh"
@@ -87,6 +90,12 @@ struct VersionedQuest {
std::shared_ptr<const IntegralExpression> enabled_expression;
bool allow_start_from_chat_command = false;
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;
void assert_valid() const;
@@ -115,6 +124,13 @@ struct Quest {
uint8_t description_flag;
std::shared_ptr<const IntegralExpression> available_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;
Quest() = delete;
@@ -151,7 +167,12 @@ struct QuestIndex {
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;
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;
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) {
case Lobby::DropMode::DISABLED:
case ServerDropMode::DISABLED:
info += "$C6Drops disabled$C7\n";
break;
case Lobby::DropMode::CLIENT:
case ServerDropMode::CLIENT:
info += "$C6Client drops$C7\n";
break;
case Lobby::DropMode::SERVER_SHARED:
case ServerDropMode::SERVER_SHARED:
info += "$C6Server drops$C7\n";
break;
case Lobby::DropMode::SERVER_PRIVATE:
case ServerDropMode::SERVER_PRIVATE:
info += "$C6Private drops$C7\n";
break;
case Lobby::DropMode::SERVER_DUPLICATE:
case ServerDropMode::SERVER_DUPLICATE:
info += "$C6Duplicate drops$C7\n";
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) {
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();
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:
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));
break;
case Version::BB_V4:
@@ -4601,10 +4605,10 @@ shared_ptr<Lobby> create_game_generic(
game->allowed_drop_modes = s->allowed_drop_modes_v4_normal;
}
// 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");
}
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");
}
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");
}
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::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);
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) {
case Lobby::DropMode::DISABLED:
case ServerDropMode::DISABLED:
co_return;
case Lobby::DropMode::CLIENT: {
case ServerDropMode::CLIENT: {
// If the leader is BB, use SERVER_SHARED instead
// TODO: We should also use server drops if any clients have incompatible
// object lists, since they might generate incorrect IDs for items and we
// can't override them
auto leader = l->clients[l->leader_id];
if (leader && leader->version() == Version::BB_V4) {
drop_mode = Lobby::DropMode::SERVER_SHARED;
drop_mode = ServerDropMode::SERVER_SHARED;
break;
} else {
forward_subcommand(c, msg);
co_return;
}
}
case Lobby::DropMode::SERVER_SHARED:
case Lobby::DropMode::SERVER_DUPLICATE:
case Lobby::DropMode::SERVER_PRIVATE:
case ServerDropMode::SERVER_SHARED:
case ServerDropMode::SERVER_DUPLICATE:
case ServerDropMode::SERVER_PRIVATE:
break;
default:
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) {
case Lobby::DropMode::DISABLED:
case Lobby::DropMode::CLIENT:
case ServerDropMode::DISABLED:
case ServerDropMode::CLIENT:
throw logic_error("unhandled simple drop mode");
case Lobby::DropMode::SERVER_SHARED:
case Lobby::DropMode::SERVER_DUPLICATE: {
case ServerDropMode::SERVER_SHARED:
case ServerDropMode::SERVER_DUPLICATE: {
// TODO: In SERVER_DUPLICATE mode, should we reduce the rates for rare
// items? Maybe by a factor of l->count_clients()?
auto res = generate_item();
@@ -3029,7 +3029,7 @@ static asio::awaitable<void> on_entity_drop_item_request(shared_ptr<Client> c, S
} else {
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);
if (drop_mode == Lobby::DropMode::SERVER_DUPLICATE) {
if (drop_mode == ServerDropMode::SERVER_DUPLICATE) {
for (const auto& lc : l->clients) {
if (lc && (rec.obj_st || (lc->floor == cmd.floor))) {
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;
}
case Lobby::DropMode::SERVER_PRIVATE: {
case ServerDropMode::SERVER_PRIVATE: {
for (const auto& lc : l->clients) {
if (lc && (rec.obj_st || (lc->floor == cmd.floor))) {
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);
if (l->drop_mode != Lobby::DropMode::DISABLED) {
if (l->drop_mode != ServerDropMode::DISABLED) {
EnemyType boss_enemy_type = EnemyType::NONE;
bool is_ep2 = (l->episode == Episode::EP2);
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);
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);
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
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);
}
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) {
this->name_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_battle = this->config_json->get_int("AllowedDropModesV4Battle", 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_battle = this->config_json->get_enum("DefaultDropModeV1V2Battle", Lobby::DropMode::CLIENT);
this->default_drop_mode_v1_v2_challenge = this->config_json->get_enum("DefaultDropModeV1V2Challenge", Lobby::DropMode::CLIENT);
this->default_drop_mode_v3_normal = this->config_json->get_enum("DefaultDropModeV3Normal", Lobby::DropMode::CLIENT);
this->default_drop_mode_v3_battle = this->config_json->get_enum("DefaultDropModeV3Battle", Lobby::DropMode::CLIENT);
this->default_drop_mode_v3_challenge = this->config_json->get_enum("DefaultDropModeV3Challenge", Lobby::DropMode::CLIENT);
this->default_drop_mode_v4_normal = this->config_json->get_enum("DefaultDropModeV4Normal", Lobby::DropMode::SERVER_SHARED);
this->default_drop_mode_v4_battle = this->config_json->get_enum("DefaultDropModeV4Battle", Lobby::DropMode::SERVER_SHARED);
this->default_drop_mode_v4_challenge = this->config_json->get_enum("DefaultDropModeV4Challenge", Lobby::DropMode::SERVER_SHARED);
if ((this->default_drop_mode_v4_normal == Lobby::DropMode::CLIENT) ||
(this->default_drop_mode_v4_battle == Lobby::DropMode::CLIENT) ||
(this->default_drop_mode_v4_challenge == 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", ServerDropMode::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", ServerDropMode::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", ServerDropMode::CLIENT);
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", ServerDropMode::SERVER_SHARED);
this->default_drop_mode_v4_challenge = this->config_json->get_enum("DefaultDropModeV4Challenge", ServerDropMode::SERVER_SHARED);
if ((this->default_drop_mode_v4_normal == ServerDropMode::CLIENT) ||
(this->default_drop_mode_v4_battle == ServerDropMode::CLIENT) ||
(this->default_drop_mode_v4_challenge == ServerDropMode::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))) ||
(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)))) {
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>(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");
}
@@ -1940,61 +1969,103 @@ void ServerState::load_item_name_indexes() {
}
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")) {
string filename = item.path().filename().string();
if (!filename.starts_with("rare-table-")) {
continue;
}
string path = "system/item-tables/" + filename;
size_t ext_offset = filename.rfind('.');
string basename = (ext_offset == string::npos) ? filename : filename.substr(0, ext_offset);
if (filename.starts_with("common-table-") || filename.starts_with("ItemPT-")) {
string path = "system/item-tables/" + filename;
size_t ext_offset = filename.rfind('.');
string basename = (ext_offset == string::npos) ? filename : filename.substr(0, ext_offset);
if (filename.ends_with("-v1.json")) {
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)));
// AFSV2CommonItemSet(std::shared_ptr<const std::string> pt_afs_data, std::shared_ptr<const std::string> ct_afs_data);
} else if (filename.ends_with(".afs")) {
config_log.info_f("Loading AFS rare item table {}", filename);
auto data = make_shared<string>(phosg::load_file(path));
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(AFSArchive(data), false));
if (filename.ends_with(".json")) {
config_log.info_f("Loading JSON common item table {}", filename);
new_common_item_sets.emplace(basename, make_shared<JSONCommonItemSet>(phosg::JSON::parse(phosg::load_file(path))));
} 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")) {
config_log.info_f("Loading GSL rare item table {}", filename);
auto data = make_shared<string>(phosg::load_file(path));
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(GSLArchive(data, false), false));
} else if (filename.starts_with("rare-table-") || filename.starts_with("ItemRT-")) {
string path = "system/item-tables/" + filename;
size_t ext_offset = filename.rfind('.');
string basename = (ext_offset == string::npos) ? filename : filename.substr(0, ext_offset);
} 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));
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(GSLArchive(data, true), true));
shared_ptr<RareItemSet> rare_set;
if (filename.ends_with("-v1.json")) {
config_log.info_f("Loading v1 JSON rare item table {}", filename);
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")) {
config_log.info_f("Loading REL rare item table {}", filename);
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(phosg::load_file(path), true));
} else if (filename.ends_with(".afs")) {
config_log.info_f("Loading AFS rare item table {}", filename);
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");
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);
@@ -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 new_tekker_adjustment_set = make_shared<TekkerAdjustmentSet>(tekker_data);
if (this->server_global_drop_rate_multiplier != 1.0) {
for (auto& it : new_rare_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->rare_item_sets = std::move(new_rare_item_sets);
this->common_item_sets = std::move(new_common_item_sets);
this->armor_random_set = std::move(new_armor_random_set);
this->tool_random_set = std::move(new_tool_random_set);
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() {
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");
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() {
+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_battle = 0x05;
uint8_t allowed_drop_modes_v4_challenge = 0x05;
Lobby::DropMode default_drop_mode_v1_v2_normal = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_battle = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_challenge = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_normal = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_battle = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_challenge = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v4_normal = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_battle = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_challenge = Lobby::DropMode::SERVER_SHARED;
ServerDropMode default_drop_mode_v1_v2_normal = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v1_v2_battle = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v1_v2_challenge = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v3_normal = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v3_battle = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v3_challenge = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v4_normal = ServerDropMode::SERVER_SHARED;
ServerDropMode default_drop_mode_v4_battle = ServerDropMode::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_v3;
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 BattleParamsIndex> battle_params;
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::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 ToolRandomSet> tool_random_set;
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;
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;
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));
+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
// to use this option if either of those options are also used.
// "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",
}