diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 6ede8a45..e7a4fa23 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -70,8 +70,11 @@ static void check_is_ep3(shared_ptr c, bool is_ep3) { } } -static void check_cheats_enabled(shared_ptr l) { - if (!(l->flags & Lobby::Flag::CHEATS_ENABLED)) { +static void check_cheats_enabled(shared_ptr s, shared_ptr l = nullptr) { + if (s->cheat_mode_behavior == ServerState::CheatModeBehavior::OFF) { + throw precondition_failed(u"$C6Cheats are disabled."); + } + if (l && !(l->flags & Lobby::Flag::CHEATS_ENABLED)) { throw precondition_failed(u"$C6This command can\nonly be used in\ncheat mode."); } } @@ -438,16 +441,21 @@ static void proxy_command_send_server(shared_ptr, //////////////////////////////////////////////////////////////////////////////// // Lobby commands -static void server_command_cheat(shared_ptr, shared_ptr l, +static void server_command_cheat(shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string&) { check_is_game(l, true); check_is_leader(l, c); + if (s->cheat_mode_behavior == ServerState::CheatModeBehavior::OFF) { + send_text_message(c, u"$C6Cheat mode cannot\nbe enabled on this\nserver"); + return; + } + l->flags ^= Lobby::Flag::CHEATS_ENABLED; send_text_message_printf(l, "Cheat mode %s", (l->flags & Lobby::Flag::CHEATS_ENABLED) ? "enabled" : "disabled"); - // if cheat mode was disabled, turn off all the cheat features that were on + // If cheat mode was disabled, turn off all the cheat features that were on if (!(l->flags & Lobby::Flag::CHEATS_ENABLED)) { for (size_t x = 0; x < l->max_clients; x++) { auto c = l->clients[x]; @@ -964,9 +972,9 @@ static void server_command_ban(shared_ptr s, shared_ptr l, // Cheat commands static void server_command_warp( - shared_ptr l, shared_ptr c, const std::u16string& args, bool is_warpall) { + shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args, bool is_warpall) { check_is_game(l, true); - check_cheats_enabled(l); + check_cheats_enabled(s, l); uint32_t area = stoul(encode_sjis(args), nullptr, 0); if (c->area == area) { @@ -988,18 +996,19 @@ static void server_command_warp( } } -static void server_command_warpme(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string& args) { - server_command_warp(l, c, args, false); +static void server_command_warpme( + shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args) { + server_command_warp(s, l, c, args, false); } -static void server_command_warpall(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string& args) { - server_command_warp(l, c, args, true); +static void server_command_warpall( + shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args) { + server_command_warp(s, l, c, args, true); } static void proxy_command_warp( - ProxyServer::LinkedSession& session, const std::u16string& args, bool is_warpall) { + shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string& args, bool is_warpall) { + check_cheats_enabled(s); if (!session.is_in_game) { send_text_message(session.client_channel, u"$C6You must be in a\ngame to use this\ncommand"); return; @@ -1012,20 +1021,20 @@ static void proxy_command_warp( session.area = area; } -static void proxy_command_warpme(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& args) { - proxy_command_warp(session, args, false); +static void proxy_command_warpme( + shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string& args) { + proxy_command_warp(s, session, args, false); } -static void proxy_command_warpall(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& args) { - proxy_command_warp(session, args, true); +static void proxy_command_warpall( + shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string& args) { + proxy_command_warp(s, session, args, true); } -static void server_command_next(shared_ptr, shared_ptr l, +static void server_command_next(shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string&) { check_is_game(l, true); - check_cheats_enabled(l); + check_cheats_enabled(s, l); size_t limit = area_limit_for_episode(l->episode); if (limit == 0) { @@ -1034,8 +1043,9 @@ static void server_command_next(shared_ptr, shared_ptr l, send_warp(c, (c->area + 1) % limit, true); } -static void proxy_command_next(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string&) { +static void proxy_command_next( + shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string&) { + check_cheats_enabled(s); if (!session.is_in_game) { send_text_message(session.client_channel, u"$C6You must be in a\ngame to use this\ncommand"); return; @@ -1098,52 +1108,55 @@ static void proxy_command_song(shared_ptr, send_ep3_change_music(session.client_channel, song); } -static void server_command_infinite_hp(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string&) { +static void server_command_infinite_hp( + shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string&) { check_is_game(l, true); - check_cheats_enabled(l); + check_cheats_enabled(s, l); c->options.infinite_hp = !c->options.infinite_hp; send_text_message_printf(c, "$C6Infinite HP %s", c->options.infinite_hp ? "enabled" : "disabled"); } -static void proxy_command_infinite_hp(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string&) { +static void proxy_command_infinite_hp( + shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string&) { + check_cheats_enabled(s); session.options.infinite_hp = !session.options.infinite_hp; send_text_message_printf(session.client_channel, "$C6Infinite HP %s", session.options.infinite_hp ? "enabled" : "disabled"); } -static void server_command_infinite_tp(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string&) { +static void server_command_infinite_tp( + shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string&) { check_is_game(l, true); - check_cheats_enabled(l); + check_cheats_enabled(s, l); c->options.infinite_tp = !c->options.infinite_tp; send_text_message_printf(c, "$C6Infinite TP %s", c->options.infinite_tp ? "enabled" : "disabled"); } -static void proxy_command_infinite_tp(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string&) { +static void proxy_command_infinite_tp( + shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string&) { + check_cheats_enabled(s); session.options.infinite_tp = !session.options.infinite_tp; send_text_message_printf(session.client_channel, "$C6Infinite TP %s", session.options.infinite_tp ? "enabled" : "disabled"); } -static void server_command_switch_assist(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string&) { +static void server_command_switch_assist( + shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string&) { check_is_game(l, true); - check_cheats_enabled(l); + check_cheats_enabled(s, l); c->options.switch_assist = !c->options.switch_assist; send_text_message_printf(c, "$C6Switch assist %s", c->options.switch_assist ? "enabled" : "disabled"); } -static void proxy_command_switch_assist(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string&) { +static void proxy_command_switch_assist( + shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string&) { + check_cheats_enabled(s); session.options.switch_assist = !session.options.switch_assist; send_text_message_printf(session.client_channel, "$C6Switch assist %s", session.options.switch_assist ? "enabled" : "disabled"); @@ -1157,10 +1170,10 @@ static void server_command_drop(shared_ptr, shared_ptr l, send_text_message_printf(l, "Drops %s", (l->flags & Lobby::Flag::DROPS_ENABLED) ? "enabled" : "disabled"); } -static void server_command_item(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_item( + shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args) { check_is_game(l, true); - check_cheats_enabled(l); + check_cheats_enabled(s, l); string data = parse_data_string(encode_sjis(args)); if (data.size() < 2) { @@ -1188,8 +1201,9 @@ static void server_command_item(shared_ptr, shared_ptr l, send_text_message(c, u"$C7Item created:\n" + decode_sjis(name)); } -static void proxy_command_item(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& args) { +static void proxy_command_item( + shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string& args) { + check_cheats_enabled(s); if (session.version == GameVersion::BB) { send_text_message(session.client_channel, u"$C6This command cannot\nbe used on the proxy\nserver in BB games"); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 9afbc0f8..38439e66 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -53,17 +53,19 @@ static shared_ptr proxy_options_menu_for_client( u"Player notifs", u"Show a message\nwhen other players\njoin or leave"); add_option(ProxyOptionsMenuItemID::BLOCK_PINGS, c->options.suppress_client_pings, u"Block pings", u"Block ping commands\nsent by the client"); - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { - add_option(ProxyOptionsMenuItemID::INFINITE_HP, c->options.infinite_hp, - u"Infinite HP", u"Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills"); - add_option(ProxyOptionsMenuItemID::INFINITE_TP, c->options.infinite_tp, - u"Infinite TP", u"Enable automatic TP\nrestoration when\nyou cast any\ntechnique"); - add_option(ProxyOptionsMenuItemID::SWITCH_ASSIST, c->options.switch_assist, - u"Switch assist", u"Automatically try\nto unlock 2-player\ndoors when you step\non both switches\nsequentially"); - } else { - // Note: This option's text is the maximum possible length for any menu item - add_option(ProxyOptionsMenuItemID::EP3_INFINITE_MESETA, c->options.ep3_infinite_meseta, - u"Infinite Meseta", u"Fix Meseta value\nat 1,000,000"); + if (s->cheat_mode_behavior != ServerState::CheatModeBehavior::OFF) { + if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + add_option(ProxyOptionsMenuItemID::INFINITE_HP, c->options.infinite_hp, + u"Infinite HP", u"Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills"); + add_option(ProxyOptionsMenuItemID::INFINITE_TP, c->options.infinite_tp, + u"Infinite TP", u"Enable automatic TP\nrestoration when\nyou cast any\ntechnique"); + add_option(ProxyOptionsMenuItemID::SWITCH_ASSIST, c->options.switch_assist, + u"Switch assist", u"Automatically try\nto unlock 2-player\ndoors when you step\non both switches\nsequentially"); + } else { + // Note: This option's text is the maximum possible length for any menu item + add_option(ProxyOptionsMenuItemID::EP3_INFINITE_MESETA, c->options.ep3_infinite_meseta, + u"Infinite Meseta", u"Fix Meseta value\nat 1,000,000"); + } } add_option(ProxyOptionsMenuItemID::BLOCK_EVENTS, (c->options.override_lobby_event >= 0), u"Block events", u"Disable seasonal\nevents in the lobby\nand in games"); @@ -3150,7 +3152,7 @@ shared_ptr create_game_generic( bool item_tracking_enabled = (c->version() == GameVersion::BB) || (s->item_tracking_enabled && (mode == GameMode::NORMAL || mode == GameMode::SOLO)); - + // only disable drops if the config flag is set and are playing regualr multi-mode. // drops are still enabled for battle and challenge modes. bool drops_enabled = @@ -3159,9 +3161,10 @@ shared_ptr create_game_generic( shared_ptr game = s->create_lobby(); game->name = name; game->flags = flags | - Lobby::Flag::GAME | + Lobby::Flag::GAME | (item_tracking_enabled ? Lobby::Flag::ITEM_TRACKING_ENABLED : 0) | - (drops_enabled ? Lobby::Flag::DROPS_ENABLED : 0); + (drops_enabled ? Lobby::Flag::DROPS_ENABLED : 0) | + ((s->cheat_mode_behavior == ServerState::CheatModeBehavior::ON_BY_DEFAULT) ? Lobby::Flag::CHEATS_ENABLED : 0); game->password = password; game->version = c->version(); game->section_id = c->options.override_section_id >= 0 diff --git a/src/ServerState.cc b/src/ServerState.cc index 5c16efd2..03369601 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -29,6 +29,7 @@ ServerState::ServerState(const char* config_filename, bool is_replay) catch_handler_exceptions(true), ep3_behavior_flags(0), run_shell_behavior(RunShellBehavior::DEFAULT), + cheat_mode_behavior(CheatModeBehavior::OFF_BY_DEFAULT), ep3_card_auction_points(0), ep3_card_auction_min_size(0), ep3_card_auction_max_size(0), @@ -744,6 +745,20 @@ void ServerState::parse_config(shared_ptr config_json) { } catch (const out_of_range&) { } + try { + const string& behavior = d.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&) { + } + try { auto v = d.at("LobbyEvent"); uint8_t event = v->is_int() ? v->as_int() : event_for_name(v->as_string()); diff --git a/src/ServerState.hh b/src/ServerState.hh index 228f7a00..0b79fea7 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -40,6 +40,11 @@ struct ServerState { ALWAYS, NEVER, }; + enum class CheatModeBehavior { + OFF = 0, + OFF_BY_DEFAULT, + ON_BY_DEFAULT, + }; std::string config_filename; bool is_replay; @@ -60,6 +65,7 @@ struct ServerState { bool catch_handler_exceptions; uint32_t ep3_behavior_flags; RunShellBehavior run_shell_behavior; + CheatModeBehavior 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 3b3f3d5d..49b0ac4d 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -343,6 +343,15 @@ [0x40, "e", "", "Download", "$E$C6Quests to download\nto your Memory Card"], ], + // Cheat mode behavior. There are three values: + // "Off": Cheat mode is disabled on the entire server. This also disables + // cheat commands on the proxy server. + // "OffByDefault": Cheat mode is disabled on the entire server, but can be + // enabled in each game and proxy session. + // "OnByDefault": Cheat mode is enabled on the entire server, but can be + // disabled in each game and proxy session. + "CheatModeBehavior": "OnByDefault", + // Whether to enable patches on Episode 3 USA. This functionality depends on // exploiting a bug in Episode 3, and while it seems to work reliably on // Dolphin, it hasn't been tested on a real GameCube. So, newserv doesn't @@ -361,7 +370,7 @@ // be turned off here. This option has no effect on Blue Burst games - item // 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.