From 7476eb62d3077dfe51b940b2cd4aa65eaf1ddd4d Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Wed, 25 Oct 2023 18:48:22 -0700 Subject: [PATCH] allow server item tables to be enabled by default --- src/ChatCommands.cc | 95 +++++++++++++++++++++++--------------- src/Client.cc | 1 - src/Client.hh | 1 - src/License.cc | 12 +++++ src/License.hh | 6 ++- src/Lobby.hh | 8 ++-- src/ReceiveCommands.cc | 84 +++++++++++++++++++++++++++------ src/ReceiveSubcommands.cc | 3 -- src/ServerState.cc | 42 ++++++++++------- src/ServerState.hh | 15 ++++-- system/config.example.json | 17 +++++-- tests/config.json | 3 ++ 12 files changed, 201 insertions(+), 86 deletions(-) diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index f1972c9f..874c0e57 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -70,11 +70,14 @@ static void check_is_ep3(shared_ptr c, bool is_ep3) { } } -static void check_cheats_enabled(shared_ptr s, shared_ptr l = nullptr) { - if (s->cheat_mode_behavior == ServerState::CheatModeBehavior::OFF) { - throw precondition_failed("$C6Cheats are disabled."); +static void check_cheats_enabled(shared_ptr l) { + if (!(l->flags & Lobby::Flag::CHEATS_ENABLED)) { + throw precondition_failed("$C6This command can\nonly be used in\ncheat mode."); } - if (l && !(l->flags & Lobby::Flag::CHEATS_ENABLED)) { +} + +static void check_proxy_cheats_enabled(shared_ptr s) { + if (s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) { throw precondition_failed("$C6This command can\nonly be used in\ncheat mode."); } } @@ -97,17 +100,26 @@ static void server_command_lobby_info(shared_ptr c, const std::string&) } else { if (l->is_game()) { - lines.emplace_back(string_printf("Game ID: $C6%08X$C7", l->lobby_id)); - if (!l->is_ep3()) { if (l->max_level == 0xFFFFFFFF) { - lines.emplace_back(string_printf("Levels: $C6%d+$C7", l->min_level + 1)); + lines.emplace_back(string_printf("$C6%08X$C7 L$C6%d+$C7", l->lobby_id, l->min_level + 1)); } else { - lines.emplace_back(string_printf("Levels: $C6%d-%d$C7", l->min_level + 1, l->max_level + 1)); + lines.emplace_back(string_printf("$C6%08X$C7 L$C6%d-%d$C7", l->lobby_id, l->min_level + 1, l->max_level + 1)); } - lines.emplace_back(string_printf("$C7Section ID: $C6%s$C7", name_for_section_id(l->section_id).c_str())); - lines.emplace_back(string_printf("$C7Cheat mode: $C6%s$C7", (l->flags & Lobby::Flag::CHEATS_ENABLED) ? "on" : "off")); + + if (l->flags & Lobby::Flag::DROPS_ENABLED) { + if (l->item_creator) { + lines.emplace_back("Server item table"); + } else { + lines.emplace_back("Client item table"); + } + } else { + lines.emplace_back("No item drops"); + } + if (l->flags & Lobby::Flag::CHEATS_ENABLED) { + lines.emplace_back("Cheats enabled"); + } } else { lines.emplace_back(string_printf("$C7State seed: $C6%08X$C7", l->random_seed)); @@ -459,14 +471,12 @@ static void server_command_cheat(shared_ptr c, const std::string&) { auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); - - if (s->cheat_mode_behavior == ServerState::CheatModeBehavior::OFF) { - send_text_message(c, "$C6Cheat mode cannot\nbe enabled on this\nserver"); - return; + if (l->flags & Lobby::Flag::CANNOT_CHANGE_CHEAT_MODE) { + send_text_message(c, "$C6Cheat mode cannot\nbe changed on this\nserver"); + } else { + l->flags ^= Lobby::Flag::CHEATS_ENABLED; + send_text_message_printf(l, "Cheat mode %s", (l->flags & Lobby::Flag::CHEATS_ENABLED) ? "enabled" : "disabled"); } - - l->flags ^= Lobby::Flag::CHEATS_ENABLED; - send_text_message_printf(l, "Cheat mode %s", (l->flags & Lobby::Flag::CHEATS_ENABLED) ? "enabled" : "disabled"); } static void server_command_lobby_event(shared_ptr c, const std::string& args) { @@ -1016,7 +1026,7 @@ static void server_command_warp(shared_ptr c, const std::string& args, b auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); - check_cheats_enabled(s, l); + check_cheats_enabled(l); uint32_t area = stoul(args, nullptr, 0); if (c->area == area) { @@ -1048,7 +1058,7 @@ static void server_command_warpall(shared_ptr c, const std::string& args static void proxy_command_warp(shared_ptr ses, const std::string& args, bool is_warpall) { auto s = ses->require_server_state(); - check_cheats_enabled(s); + check_proxy_cheats_enabled(s); if (!ses->is_in_game) { send_text_message(ses->client_channel, "$C6You must be in a\ngame to use this\ncommand"); return; @@ -1073,7 +1083,7 @@ static void server_command_next(shared_ptr c, const std::string&) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); - check_cheats_enabled(s, l); + check_cheats_enabled(l); size_t limit = area_limit_for_episode(l->episode); if (limit == 0) { @@ -1084,7 +1094,7 @@ static void server_command_next(shared_ptr c, const std::string&) { static void proxy_command_next(shared_ptr ses, const std::string&) { auto s = ses->require_server_state(); - check_cheats_enabled(s); + check_proxy_cheats_enabled(s); if (!ses->is_in_game) { send_text_message(ses->client_channel, "$C6You must be in a\ngame to use this\ncommand"); return; @@ -1149,7 +1159,7 @@ static void server_command_infinite_hp(shared_ptr c, const std::string&) auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); - check_cheats_enabled(s, l); + check_cheats_enabled(l); c->options.infinite_hp = !c->options.infinite_hp; send_text_message_printf(c, "$C6Infinite HP %s", c->options.infinite_hp ? "enabled" : "disabled"); @@ -1157,7 +1167,7 @@ static void server_command_infinite_hp(shared_ptr c, const std::string&) static void proxy_command_infinite_hp(shared_ptr ses, const std::string&) { auto s = ses->require_server_state(); - check_cheats_enabled(s); + check_proxy_cheats_enabled(s); ses->options.infinite_hp = !ses->options.infinite_hp; send_text_message_printf(ses->client_channel, "$C6Infinite HP %s", ses->options.infinite_hp ? "enabled" : "disabled"); } @@ -1166,7 +1176,7 @@ static void server_command_infinite_tp(shared_ptr c, const std::string&) auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); - check_cheats_enabled(s, l); + check_cheats_enabled(l); c->options.infinite_tp = !c->options.infinite_tp; send_text_message_printf(c, "$C6Infinite TP %s", @@ -1175,7 +1185,7 @@ static void server_command_infinite_tp(shared_ptr c, const std::string&) static void proxy_command_infinite_tp(shared_ptr ses, const std::string&) { auto s = ses->require_server_state(); - check_cheats_enabled(s); + check_proxy_cheats_enabled(s); ses->options.infinite_tp = !ses->options.infinite_tp; send_text_message_printf(ses->client_channel, "$C6Infinite TP %s", ses->options.infinite_tp ? "enabled" : "disabled"); @@ -1185,7 +1195,7 @@ static void server_command_switch_assist(shared_ptr c, const std::string auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); - check_cheats_enabled(s, l); + check_cheats_enabled(l); c->options.switch_assist = !c->options.switch_assist; send_text_message_printf(c, "$C6Switch assist %s", @@ -1194,7 +1204,7 @@ static void server_command_switch_assist(shared_ptr c, const std::string static void proxy_command_switch_assist(shared_ptr ses, const std::string&) { auto s = ses->require_server_state(); - check_cheats_enabled(s); + check_proxy_cheats_enabled(s); ses->options.switch_assist = !ses->options.switch_assist; send_text_message_printf(ses->client_channel, "$C6Switch assist %s", ses->options.switch_assist ? "enabled" : "disabled"); @@ -1204,22 +1214,31 @@ static void server_command_drop(shared_ptr c, const std::string&) { auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); - l->flags ^= Lobby::Flag::DROPS_ENABLED; - send_text_message_printf(l, "Drops %s", (l->flags & Lobby::Flag::DROPS_ENABLED) ? "enabled" : "disabled"); + if (l->flags & Lobby::Flag::CANNOT_CHANGE_DROPS_ENABLED) { + send_text_message(c, "Drop mode cannot\nbe changed on this\nserver"); + } else { + l->flags ^= Lobby::Flag::DROPS_ENABLED; + send_text_message_printf(l, "Drops %s", (l->flags & Lobby::Flag::DROPS_ENABLED) ? "enabled" : "disabled"); + } } -static void server_command_raretable(shared_ptr c, const std::string&) { +static void server_command_itemtable(shared_ptr c, const std::string&) { + auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); - if (l->base_version == GameVersion::BB) { - send_text_message_printf(c, "Cannot use client\nrare table on BB"); + if (l->flags & Lobby::Flag::CANNOT_CHANGE_ITEM_TABLE) { + send_text_message(c, "Cannot switch item\ntables on this\nserver"); + } else if (l->base_version == GameVersion::BB) { + send_text_message(c, "Cannot use client\nitem table on BB"); + } else if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { + send_text_message(c, "Cannot use server\nitem tables if item\ntracking is off"); } else if (l->item_creator) { l->item_creator.reset(); - send_text_message_printf(l, "Game switched to\nclient rare tables"); + send_text_message(l, "Game switched to\nclient item tables"); } else { l->create_item_creator(); - send_text_message_printf(l, "Game switched to\nserver rare tables"); + send_text_message(l, "Game switched to\nserver item tables"); } } @@ -1227,7 +1246,7 @@ static void server_command_item(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); - check_cheats_enabled(s, l); + check_cheats_enabled(l); ItemData item(args); item.id = l->generate_item_id(c->lobby_client_id); @@ -1241,7 +1260,7 @@ static void server_command_item(shared_ptr c, const std::string& args) { static void proxy_command_item(shared_ptr ses, const std::string& args) { auto s = ses->require_server_state(); - check_cheats_enabled(s); + check_proxy_cheats_enabled(s); if (ses->version() == GameVersion::BB) { send_text_message(ses->client_channel, "$C6This command cannot\nbe used on the proxy\nserver in BB games"); return; @@ -1380,7 +1399,7 @@ static void server_command_ep3_unset_field_character(shared_ptr c, const auto l = c->require_lobby(); check_is_game(l, true); check_is_ep3(c, true); - check_cheats_enabled(s, l); + check_cheats_enabled(l); if (l->episode != Episode::EP3) { throw logic_error("non-Ep3 client in Ep3 game"); @@ -1534,6 +1553,7 @@ static const unordered_map chat_commands({ {"$inftime", {server_command_ep3_infinite_time, nullptr}}, {"$inftp", {server_command_infinite_tp, proxy_command_infinite_tp}}, {"$item", {server_command_item, proxy_command_item}}, + {"$itemtable", {server_command_itemtable, nullptr}}, {"$i", {server_command_item, proxy_command_item}}, {"$kick", {server_command_kick, nullptr}}, {"$li", {server_command_lobby_info, proxy_command_lobby_info}}, @@ -1549,7 +1569,6 @@ static const unordered_map chat_commands({ {"$persist", {server_command_persist, nullptr}}, {"$playrec", {server_command_playrec, nullptr}}, {"$rand", {server_command_rand, proxy_command_rand}}, - {"$raretable", {server_command_raretable, nullptr}}, {"$saverec", {server_command_saverec, nullptr}}, {"$sc", {server_command_send_client, proxy_command_send_client}}, {"$secid", {server_command_secid, proxy_command_secid}}, diff --git a/src/Client.cc b/src/Client.cc index 4fcde953..03646084 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -87,7 +87,6 @@ Client::Client( card_battle_table_seat_state(0), next_exp_value(0), can_chat(true), - use_server_rare_tables(false), pending_bb_save_player_index(0), dol_base_addr(0) { this->last_switch_enabled_command.header.subcommand = 0; diff --git a/src/Client.hh b/src/Client.hh index 0a0d20e3..c4a79001 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -172,7 +172,6 @@ struct Client : public std::enable_shared_from_this { uint32_t next_exp_value; // next EXP value to give G_SwitchStateChanged_6x05 last_switch_enabled_command; bool can_chat; - bool use_server_rare_tables; std::string pending_bb_save_username; uint8_t pending_bb_save_player_index; std::deque> function_call_response_queue; diff --git a/src/License.cc b/src/License.cc index cad96223..24827815 100644 --- a/src/License.cc +++ b/src/License.cc @@ -167,6 +167,9 @@ void LicenseIndex::remove(uint32_t serial_number) { } shared_ptr LicenseIndex::verify_v1_v2(uint32_t serial_number, const string& access_key) const { + if (serial_number == 0) { + throw no_username(); + } try { auto& license = this->serial_number_to_license.at(serial_number); if (license->access_key.compare(0, 8, access_key) != 0) { @@ -182,6 +185,9 @@ shared_ptr LicenseIndex::verify_v1_v2(uint32_t serial_number, const str } shared_ptr LicenseIndex::verify_gc(uint32_t serial_number, const string& access_key) const { + if (serial_number == 0) { + throw no_username(); + } try { auto& license = this->serial_number_to_license.at(serial_number); if (license->access_key != access_key) { @@ -197,6 +203,9 @@ shared_ptr LicenseIndex::verify_gc(uint32_t serial_number, const string } shared_ptr LicenseIndex::verify_gc(uint32_t serial_number, const string& access_key, const string& password) const { + if (serial_number == 0) { + throw no_username(); + } try { auto& license = this->serial_number_to_license.at(serial_number); if (license->access_key != access_key) { @@ -215,6 +224,9 @@ shared_ptr LicenseIndex::verify_gc(uint32_t serial_number, const string } shared_ptr LicenseIndex::verify_bb(const string& username, const string& password) const { + if (username.empty() || password.empty()) { + throw no_username(); + } try { auto& license = this->bb_username_to_license.at(username); if (license->bb_password != password) { diff --git a/src/License.hh b/src/License.hh index c5584564..24cbebf1 100644 --- a/src/License.hh +++ b/src/License.hh @@ -54,16 +54,18 @@ struct License { class LicenseIndex { public: + class no_username : public std::invalid_argument { + public: + no_username() : invalid_argument("serial number is zero or username is missing") {} + }; class incorrect_password : public std::invalid_argument { public: incorrect_password() : invalid_argument("incorrect password") {} }; - class incorrect_access_key : public std::invalid_argument { public: incorrect_access_key() : invalid_argument("incorrect access key") {} }; - class missing_license : public std::invalid_argument { public: missing_license() : invalid_argument("missing license") {} diff --git a/src/Lobby.hh b/src/Lobby.hh index fc13f2cf..0937be8c 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -37,9 +37,11 @@ struct Lobby : public std::enable_shared_from_this { IS_SPECTATOR_TEAM = 0x00002000, // episode must be EP3 also SPECTATORS_FORBIDDEN = 0x00004000, START_BATTLE_PLAYER_IMMEDIATELY = 0x00008000, - DROPS_ENABLED = 0x00010000, // Does not affect BB - IS_EP3_TRIAL = 0x00020000, - USE_SERVER_RARE_TABLE = 0x00040000, // Does not affect BB + IS_EP3_TRIAL = 0x00010000, + DROPS_ENABLED = 0x00020000, + CANNOT_CHANGE_DROPS_ENABLED = 0x00040000, + CANNOT_CHANGE_ITEM_TABLE = 0x00080000, + CANNOT_CHANGE_CHEAT_MODE = 0x00100000, // Flags used only for lobbies PUBLIC = 0x01000000, diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 2ffa86a1..7b927ebe 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -50,7 +50,7 @@ static shared_ptr proxy_options_menu_for_client(shared_ptroptions.suppress_client_pings, "Block pings", "Block ping commands\nsent by the client"); - if (s->cheat_mode_behavior != ServerState::CheatModeBehavior::OFF) { + if (s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) { if (!(c->flags & Client::Flag::IS_EPISODE_3)) { add_option(ProxyOptionsMenuItemID::INFINITE_HP, c->options.infinite_hp, "Infinite HP", "Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills"); @@ -359,6 +359,11 @@ static void on_DB_V3(shared_ptr c, uint16_t, uint32_t, string& data) { c->set_license(l); send_command(c, 0x9A, 0x02); + } catch (const LicenseIndex::no_username& e) { + send_command(c, 0x9A, 0x03); + c->should_disconnect = true; + return; + } catch (const LicenseIndex::incorrect_access_key& e) { send_command(c, 0x9A, 0x03); c->should_disconnect = true; @@ -401,6 +406,11 @@ static void on_88_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) c->set_license(l); send_command(c, 0x88, 0x00); + } catch (const LicenseIndex::no_username& e) { + send_message_box(c, "Incorrect serial number"); + c->should_disconnect = true; + return; + } catch (const LicenseIndex::incorrect_access_key& e) { send_message_box(c, "Incorrect access key"); c->should_disconnect = true; @@ -436,6 +446,11 @@ static void on_8B_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); c->set_license(l); + } catch (const LicenseIndex::no_username& e) { + send_message_box(c, "Incorrect serial number"); + c->should_disconnect = true; + return; + } catch (const LicenseIndex::incorrect_access_key& e) { send_message_box(c, "Incorrect access key"); c->should_disconnect = true; @@ -482,6 +497,11 @@ static void on_90_DC(shared_ptr c, uint16_t, uint32_t, string& data) { c->set_license(l); send_command(c, 0x90, 0x02); + } catch (const LicenseIndex::no_username& e) { + send_command(c, 0x90, 0x03); + c->should_disconnect = true; + return; + } catch (const LicenseIndex::incorrect_access_key& e) { send_command(c, 0x90, 0x03); c->should_disconnect = true; @@ -525,6 +545,11 @@ static void on_93_DC(shared_ptr c, uint16_t, uint32_t, string& data) { shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); c->set_license(l); + } catch (const LicenseIndex::no_username& e) { + send_message_box(c, "Incorrect serial number"); + c->should_disconnect = true; + return; + } catch (const LicenseIndex::incorrect_access_key& e) { send_message_box(c, "Incorrect access key"); c->should_disconnect = true; @@ -595,6 +620,11 @@ static void on_9A(shared_ptr c, uint16_t, uint32_t, string& data) { c->set_license(l); send_command(c, 0x9A, 0x02); + } catch (const LicenseIndex::no_username& e) { + send_command(c, 0x9A, 0x03); + c->should_disconnect = true; + return; + } catch (const LicenseIndex::incorrect_access_key& e) { send_command(c, 0x9A, 0x03); c->should_disconnect = true; @@ -656,6 +686,11 @@ static void on_9C(shared_ptr c, uint16_t, uint32_t, string& data) { c->set_license(l); send_command(c, 0x9C, 0x01); + } catch (const LicenseIndex::no_username& e) { + send_message_box(c, "Incorrect serial number"); + c->should_disconnect = true; + return; + } catch (const LicenseIndex::incorrect_password& e) { send_command(c, 0x9C, 0x00); c->should_disconnect = true; @@ -774,6 +809,11 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, string& d } c->set_license(l); + } catch (const LicenseIndex::no_username& e) { + send_command(c, 0x04, 0x03); + c->should_disconnect = true; + return; + } catch (const LicenseIndex::incorrect_access_key& e) { send_command(c, 0x04, 0x03); c->should_disconnect = true; @@ -830,14 +870,19 @@ static void on_93_BB(shared_ptr c, uint16_t, uint32_t, string& data) { auto l = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode()); c->set_license(l); + } catch (const LicenseIndex::no_username& e) { + send_message_box(c, "Username is missing"); + c->should_disconnect = true; + return; + } catch (const LicenseIndex::incorrect_password& e) { - send_message_box(c, string_printf("Login failed: %s", e.what())); + send_message_box(c, "Incorrect login password"); c->should_disconnect = true; return; } catch (const LicenseIndex::missing_license& e) { if (!s->allow_unregistered_users) { - send_message_box(c, string_printf("Login failed: %s", e.what())); + send_message_box(c, "You are not registered on this server"); c->should_disconnect = true; return; } else { @@ -3336,18 +3381,29 @@ shared_ptr create_game_generic( // Only disable drops if the config flag is set and are playing regular // multi-mode. Drops are still enabled for battle and challenge modes. - bool drops_enabled = (s->drops_enabled || (mode != GameMode::NORMAL)); + bool drops_enabled = s->behavior_enabled(s->enable_drops_behavior) || (mode != GameMode::NORMAL); + bool use_server_item_table = item_tracking_enabled && s->behavior_enabled(s->use_server_item_tables_behavior); + bool cheat_mode_enabled = s->behavior_enabled(s->cheat_mode_behavior); - bool is_ep3_trial = (c->version() == GameVersion::GC) && (c->flags & Client::Flag::IS_EPISODE_3) && (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION); + bool cannot_change_cheat_mode = !s->behavior_can_be_overridden(s->cheat_mode_behavior); + bool cannot_change_item_table = !item_tracking_enabled || !s->behavior_can_be_overridden(s->use_server_item_tables_behavior); + bool cannot_change_drops_enabled = !s->behavior_can_be_overridden(s->enable_drops_behavior); + + bool is_ep3_trial = (c->version() == GameVersion::GC) && + (c->flags & Client::Flag::IS_EPISODE_3) && + (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION); shared_ptr game = s->create_lobby(); game->name = name; game->flags = flags | Lobby::Flag::GAME | + (is_ep3_trial ? Lobby::Flag::IS_EP3_TRIAL : 0) | (item_tracking_enabled ? Lobby::Flag::ITEM_TRACKING_ENABLED : 0) | (drops_enabled ? Lobby::Flag::DROPS_ENABLED : 0) | - (is_ep3_trial ? Lobby::Flag::IS_EP3_TRIAL : 0) | - ((s->cheat_mode_behavior == ServerState::CheatModeBehavior::ON_BY_DEFAULT) ? Lobby::Flag::CHEATS_ENABLED : 0); + (cheat_mode_enabled ? Lobby::Flag::CHEATS_ENABLED : 0) | + (cannot_change_drops_enabled ? Lobby::Flag::CANNOT_CHANGE_DROPS_ENABLED : 0) | + (cannot_change_item_table ? Lobby::Flag::CANNOT_CHANGE_ITEM_TABLE : 0) | + (cannot_change_cheat_mode ? Lobby::Flag::CANNOT_CHANGE_CHEAT_MODE : 0); game->password = password; game->base_version = c->version(); @@ -3412,10 +3468,15 @@ shared_ptr create_game_generic( game->battle_player = battle_player; battle_player->set_lobby(game); } - if ((game->base_version == GameVersion::BB) || (c->use_server_rare_tables)) { - // TODO: Use appropriate restrictions here if in battle mode + + for (size_t x = 0; x < 4; x++) { + game->next_item_id[x] = (0x00200000 * x) + 0x00010000; + } + game->next_game_item_id = 0x00810000; + if ((game->base_version == GameVersion::BB) || use_server_item_table) { game->create_item_creator(); } + game->event = Lobby::game_event_for_lobby_event(current_lobby->event); game->block = 0xFF; game->max_clients = (game->flags & Lobby::Flag::IS_SPECTATOR_TEAM) ? 12 : 4; @@ -3436,11 +3497,6 @@ shared_ptr create_game_generic( } if (game->base_version == GameVersion::BB) { - for (size_t x = 0; x < 4; x++) { - game->next_item_id[x] = (0x00200000 * x) + 0x00010000; - } - game->next_game_item_id = 0x00810000; - game->map.reset(new Map()); for (size_t area = 0; area < 0x10; area++) { c->log.info("[Map/%zu] Using variations %" PRIX32 ", %" PRIX32, diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 17596d20..e704e5ac 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1293,9 +1293,6 @@ static void on_entity_drop_item_request(shared_ptr c, uint8_t command, u if (!(l->flags & Lobby::Flag::DROPS_ENABLED)) { return; } - - // If there is no item creator (that is, the game is BB or has server rare - // tables disabled), then forward the request to the leader if (!l->item_creator) { forward_subcommand(c, command, flag, data, size); return; diff --git a/src/ServerState.cc b/src/ServerState.cc index 5e9ee7dc..16a24de2 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -27,7 +27,8 @@ ServerState::ServerState(const char* config_filename, bool is_replay) allow_dc_pc_games(false), allow_gc_xb_games(true), item_tracking_enabled(true), - drops_enabled(true), + enable_drops_behavior(BehaviorSwitch::ON_BY_DEFAULT), + use_server_item_tables_behavior(BehaviorSwitch::OFF_BY_DEFAULT), ep3_send_function_call_enabled(false), catch_handler_exceptions(true), ep3_infinite_meseta(false), @@ -37,7 +38,7 @@ ServerState::ServerState(const char* config_filename, bool is_replay) ep3_jukebox_is_free(false), ep3_behavior_flags(0), run_shell_behavior(RunShellBehavior::DEFAULT), - cheat_mode_behavior(CheatModeBehavior::OFF_BY_DEFAULT), + cheat_mode_behavior(BehaviorSwitch::OFF_BY_DEFAULT), ep3_card_auction_points(0), ep3_card_auction_min_size(0), ep3_card_auction_max_size(0), @@ -519,6 +520,25 @@ static vector parse_port_configuration(const JSON& json) { void ServerState::parse_config(const JSON& json, bool is_reload) { config_log.info("Parsing configuration"); + auto parse_behavior_switch = [&](const string& json_key, BehaviorSwitch default_value) -> ServerState::BehaviorSwitch { + try { + string behavior = json.get_string(json_key); + if (behavior == "Off") { + return ServerState::BehaviorSwitch::OFF; + } else if (behavior == "OffByDefault") { + return ServerState::BehaviorSwitch::OFF_BY_DEFAULT; + } else if (behavior == "OnByDefault") { + return ServerState::BehaviorSwitch::ON_BY_DEFAULT; + } else if (behavior == "On") { + return ServerState::BehaviorSwitch::ON; + } else { + throw runtime_error("invalid value for " + json_key); + } + } catch (const out_of_range&) { + return default_value; + } + }; + this->name = json.at("ServerName").as_string(); if (!is_reload) { @@ -577,7 +597,9 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { this->ip_stack_debug = json.get_bool("IPStackDebug", this->ip_stack_debug); this->allow_unregistered_users = json.get_bool("AllowUnregisteredUsers", this->allow_unregistered_users); this->item_tracking_enabled = json.get_bool("EnableItemTracking", this->item_tracking_enabled); - this->drops_enabled = json.get_bool("EnableDrops", this->drops_enabled); + this->enable_drops_behavior = parse_behavior_switch("ItemDropMode", this->enable_drops_behavior); + this->use_server_item_tables_behavior = parse_behavior_switch("UseServerItemTables", this->use_server_item_tables_behavior); + this->cheat_mode_behavior = parse_behavior_switch("CheatModeBehavior", this->cheat_mode_behavior); this->ep3_send_function_call_enabled = json.get_bool("EnableEpisode3SendFunctionCall", this->ep3_send_function_call_enabled); this->catch_handler_exceptions = json.get_bool("CatchHandlerExceptions", this->catch_handler_exceptions); @@ -701,20 +723,6 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { } } - try { - const string& behavior = json.at("CheatModeBehavior").as_string(); - if (behavior == "Off") { - this->cheat_mode_behavior = CheatModeBehavior::OFF; - } else if (behavior == "OffByDefault") { - this->cheat_mode_behavior = CheatModeBehavior::OFF_BY_DEFAULT; - } else if (behavior == "OnByDefault") { - this->cheat_mode_behavior = CheatModeBehavior::ON_BY_DEFAULT; - } else { - throw runtime_error("invalid value for CheatModeBehavior"); - } - } catch (const out_of_range&) { - } - this->allow_dc_pc_games = json.get_bool("AllowDCPCGames", this->allow_dc_pc_games); this->allow_gc_xb_games = json.get_bool("AllowGCXBGames", this->allow_gc_xb_games); diff --git a/src/ServerState.hh b/src/ServerState.hh index 26b87526..91aae008 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -40,12 +40,20 @@ struct ServerState : public std::enable_shared_from_this { ALWAYS, NEVER, }; - enum class CheatModeBehavior { + enum class BehaviorSwitch { OFF = 0, OFF_BY_DEFAULT, ON_BY_DEFAULT, + ON, }; + static inline bool behavior_enabled(BehaviorSwitch b) { + return (b == BehaviorSwitch::ON_BY_DEFAULT) || (b == BehaviorSwitch::ON); + } + static inline bool behavior_can_be_overridden(BehaviorSwitch b) { + return (b == BehaviorSwitch::OFF_BY_DEFAULT) || (b == BehaviorSwitch::ON_BY_DEFAULT); + } + std::string config_filename; bool is_replay; @@ -61,7 +69,8 @@ struct ServerState : public std::enable_shared_from_this { bool allow_dc_pc_games; bool allow_gc_xb_games; bool item_tracking_enabled; - bool drops_enabled; + BehaviorSwitch enable_drops_behavior; + BehaviorSwitch use_server_item_tables_behavior; bool ep3_send_function_call_enabled; bool catch_handler_exceptions; bool ep3_infinite_meseta; @@ -71,7 +80,7 @@ struct ServerState : public std::enable_shared_from_this { bool ep3_jukebox_is_free; uint32_t ep3_behavior_flags; RunShellBehavior run_shell_behavior; - CheatModeBehavior cheat_mode_behavior; + BehaviorSwitch cheat_mode_behavior; std::vector> bb_private_keys; std::shared_ptr function_code_index; std::shared_ptr pc_patch_file_index; diff --git a/system/config.example.json b/system/config.example.json index df758a66..e7166af8 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -549,10 +549,19 @@ // tracking is always enabled for them. "EnableItemTracking": true, - // Enable or disable drops by default in non-BB games (drops are always - // enabled in BB games). The leader can toggle drops in each game with the - // $drop command. - "EnableDrops": true, + // These options control the behavior of items dropped from boxes and enemies. + // ItemDropMode specifies whether any items drop at all; this setting applies + // to all versions. UseServerItemTables specifies whether the dropped items + // are generated by the client or by the server; this setting applies to all + // versions except BB. For BB, items are always generated by the server. + // Server item tables can only be used in non-BB games if item tracking is + // also enabled. + // Either option can be Off, On, OffByDefault, or OnByDefault. If the + // ByDefault values are used, the game leader can enable or disable drops with + // the $drop command, and can switch between server and client drop logic with + // the $itemtable command. + "ItemDropMode": "OnByDefault", + "UseServerItemTables": "OffByDefault", // Whether to enable certain exception handling. Disabling this causes // newserv to abort when any client causes an exception, which is generally diff --git a/tests/config.json b/tests/config.json index 76fe60d1..393e6707 100644 --- a/tests/config.json +++ b/tests/config.json @@ -10,6 +10,9 @@ "ServerName": "Alexandria", "CatchHandlerExceptions": false, + "ItemDropMode": "OnByDefault", + "UseServerItemTables": "OffByDefault", + "LocalAddress": "en0", "ExternalAddress": "en0",