diff --git a/src/Client.cc b/src/Client.cc index 68efb6e8..2c97d1ab 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -391,6 +391,16 @@ bool Client::can_play_quest(shared_ptr q, uint8_t event, uint8_t di return ret; } +bool Client::can_use_chat_commands() const { + if (!this->license) { + return false; + } + if (this->license->check_flag(License::Flag::ALWAYS_ENABLE_CHAT_COMMANDS)) { + return true; + } + return this->require_server_state()->enable_chat_commands; +} + void Client::dispatch_save_game_data(evutil_socket_t, short, void* ctx) { reinterpret_cast(ctx)->save_game_data(); } diff --git a/src/Client.hh b/src/Client.hh index eec79773..aba9db09 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -293,6 +293,8 @@ public: bool can_see_quest(std::shared_ptr q, uint8_t event, uint8_t difficulty, size_t num_players) const; bool can_play_quest(std::shared_ptr q, uint8_t event, uint8_t difficulty, size_t num_players) const; + bool can_use_chat_commands() const; + static void dispatch_save_game_data(evutil_socket_t, short, void* ctx); void save_game_data(); static void dispatch_send_ping(evutil_socket_t, short, void* ctx); diff --git a/src/License.hh b/src/License.hh index 9b8cf9e4..6470fe16 100644 --- a/src/License.hh +++ b/src/License.hh @@ -14,23 +14,23 @@ class License { public: enum class Flag : uint32_t { // clang-format off - KICK_USER = 0x00000001, - BAN_USER = 0x00000002, - SILENCE_USER = 0x00000004, - CHANGE_EVENT = 0x00000010, - ANNOUNCE = 0x00000020, - FREE_JOIN_GAMES = 0x00000040, - DEBUG = 0x01000000, - CHEAT_ANYWHERE = 0x02000000, - DISABLE_QUEST_REQUIREMENTS = 0x04000000, - MODERATOR = 0x00000007, - ADMINISTRATOR = 0x000000FF, - ROOT = 0x7FFFFFFF, - IS_SHARED_SERIAL = 0x80000000, + KICK_USER = 0x00000001, + BAN_USER = 0x00000002, + SILENCE_USER = 0x00000004, + CHANGE_EVENT = 0x00000010, + ANNOUNCE = 0x00000020, + FREE_JOIN_GAMES = 0x00000040, + DEBUG = 0x01000000, + CHEAT_ANYWHERE = 0x02000000, + DISABLE_QUEST_REQUIREMENTS = 0x04000000, + ALWAYS_ENABLE_CHAT_COMMANDS = 0x08000000, + MODERATOR = 0x00000007, + ADMINISTRATOR = 0x000000FF, + ROOT = 0x7FFFFFFF, + IS_SHARED_SERIAL = 0x80000000, // NOTE: When adding or changing license flags, don't forget to change the // documentation in the shell's help text. - - UNUSED_BITS = 0x78FFFF00, + UNUSED_BITS = 0x70FFFF00, // clang-format on }; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index c4f6317d..f18bc436 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -45,8 +45,10 @@ static shared_ptr proxy_options_menu_for_client(shared_ptrconfig.check_flag(flag), text, description); }; - add_flag_option(ProxyOptionsMenuItemID::CHAT_COMMANDS, Client::Flag::PROXY_CHAT_COMMANDS_ENABLED, - "Chat commands", "Enable chat\ncommands"); + if (c->can_use_chat_commands()) { + add_flag_option(ProxyOptionsMenuItemID::CHAT_COMMANDS, Client::Flag::PROXY_CHAT_COMMANDS_ENABLED, + "Chat commands", "Enable chat\ncommands"); + } add_flag_option(ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS, Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED, "Player notifs", "Show a message\nwhen other players\njoin or leave"); static const char* item_drop_notifs_description = "Enable item drop\nnotifications:\n- No: no notifs\n- Rare: rares only\n- Item: all items\nbut not Meseta\n- Every: everything"; @@ -133,6 +135,9 @@ void send_client_to_proxy_server(shared_ptr c) { s->proxy_server->delete_session(c->license->serial_number); auto ses = s->proxy_server->create_licensed_session(c->license, local_port, c->version(), c->config); + if (!c->can_use_chat_commands()) { + ses->config.clear_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED); + } if (!s->proxy_allow_save_files) { ses->config.clear_flag(Client::Flag::PROXY_SAVE_FILES); } @@ -2341,7 +2346,11 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { send_proxy_destinations_menu(c); break; case ProxyOptionsMenuItemID::CHAT_COMMANDS: - c->config.toggle_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED); + if (c->can_use_chat_commands()) { + c->config.toggle_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED); + } else { + c->config.clear_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED); + } goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS: c->config.toggle_flag(Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED); @@ -3301,7 +3310,7 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { } char command_sentinel = (c->version() == Version::DC_V1_11_2000_PROTOTYPE) ? '@' : '$'; - if (text[0] == command_sentinel) { + if ((text[0] == command_sentinel) && c->can_use_chat_commands()) { if (text[1] == command_sentinel) { text = text.substr(1); } else { diff --git a/src/ServerShell.cc b/src/ServerShell.cc index d63ee923..cfe08be9 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -297,6 +297,7 @@ CommandDefinition c_add_license( 01000000 = Can use debugging commands\n\ 02000000 = Can use cheat commands even if cheat mode is disabled\n\ 04000000 = Can play any quest without progression/flags restrictions\n\ + 08000000 = Can use chat commands even if disabled in config.json\n\ 80000000 = License is a shared serial (disables Access Key and password\n\ checks; players will get Guild Cards based on their player names)\n\ There are also shorthands for some general privilege levels:\n\ diff --git a/src/ServerState.cc b/src/ServerState.cc index 0036c1f1..ad41e412 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -886,6 +886,7 @@ void ServerState::load_config_early() { this->allow_dc_pc_games = this->config_json->get_bool("AllowDCPCGames", true); this->allow_gc_xb_games = this->config_json->get_bool("AllowGCXBGames", true); + this->enable_chat_commands = this->config_json->get_bool("EnableChatCommands", true); for (auto& order : this->public_lobby_search_orders) { order.clear(); diff --git a/src/ServerState.hh b/src/ServerState.hh index 118497be..a0a980f6 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -90,6 +90,7 @@ struct ServerState : public std::enable_shared_from_this { bool use_temp_licenses_for_prototypes = true; bool allow_dc_pc_games = true; bool allow_gc_xb_games = true; + bool enable_chat_commands = true; uint8_t allowed_drop_modes_v1_v2_normal = 0x1F; uint8_t allowed_drop_modes_v1_v2_battle = 0x07; uint8_t allowed_drop_modes_v1_v2_challenge = 0x07; diff --git a/system/config.example.json b/system/config.example.json index d57a331e..cb86c7a5 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -25,6 +25,12 @@ // corner of the screen while in lobbies. "ServerName": "newserv", + // User to run the server as. If present, newserv will attempt to switch to + // this user's permissions after loading its configuration and opening + // listening sockets. The special value $SUDO_USER causes newserv to look up + // the desired username in the $SUDO_USER environment variable instead. + // "User": "$SUDO_USER", + // Address to connect local clients to (IP address or interface name). This // is the address that newserv will expect clients on the same network as the // server to connect to. @@ -282,18 +288,18 @@ // still manually create permanent licenses for NTE players. "UseTemporaryLicensesForPrototypes": true, - // If this option is enabled, PC NTE users will be allowed to connect. This is - // the only version of the game that does not have any way to identify the + // If this option is enabled, PC NTE players will be allowed to connect. This + // is the only version of the game that does not have any way to identify the // player (no serial number, username, etc.), so PC NTE players receive random // Guild Card numbers every time they connect and cannot be banned by serial // number or username. "AllowPCNTE": true, - // User to run the server as. If present, newserv will attempt to switch to - // this user's permissions after loading its configuration and opening - // listening sockets. The special value $SUDO_USER causes newserv to look up - // the desired username in the $SUDO_USER environment variable instead. - // "User": "$SUDO_USER", + // Whether to enable chat commands for all players. If this is true, all + // players will be able to use chat commands as normal; if this is false, only + // players with the ALWAYS_ENABLE_CHAT_COMMANDS license flag will be able to + // use chat commands. + "EnableChatCommands": true, // Information menu contents. Each entry is a list containing [title, // short description, full contents]. In the short description and full diff --git a/tests/config.json b/tests/config.json index f4dc662f..0b15486e 100644 --- a/tests/config.json +++ b/tests/config.json @@ -140,6 +140,7 @@ "AllowPCNTE": true, "AllowDCPCGames": true, "AllowGCXBGames": true, + "EnableChatCommands": true, "InformationMenuContents": [ ["Text colors", "$C7Display color values", "These values can be used to color text in\nsome situations, with escape codes like %sC6.\n\n$C0Color 0$C7 - Black\n$C1Color 1$C7 - Blue\n$C2Color 2$C7 - Green\n$C3Color 3$C7 - Cyan\n$C4Color 4$C7 - Red\n$C5Color 5$C7 - Purple\n$C6Color 6$C7 - Yellow\n$C7Color 7$C7 - White\n$C8Color 8$C7 - Pink\n$C9Color 9$C7 - Violet\n$CGColor G$C7 - Orange Pulse"],