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
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));