diff --git a/src/Client.cc b/src/Client.cc index 296b1622..0dccd0ac 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -35,6 +35,7 @@ ClientOptions::ClientOptions() override_lobby_number(-1), override_random_seed(-1), save_files(false), + enable_chat_commands(true), enable_chat_filter(true), suppress_remote_login(false), zero_remote_guild_card(false), diff --git a/src/Client.hh b/src/Client.hh index 268f0e49..cee501e3 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -37,6 +37,7 @@ struct ClientOptions { // Options used only on proxy server bool save_files; + bool enable_chat_commands; bool enable_chat_filter; bool suppress_remote_login; bool zero_remote_guild_card; diff --git a/src/Menu.hh b/src/Menu.hh index e5e48977..0e855be1 100644 --- a/src/Menu.hh +++ b/src/Menu.hh @@ -57,17 +57,18 @@ namespace PatchesMenuItemID { } namespace ProxyOptionsMenuItemID { - constexpr uint32_t GO_BACK = 0xAAFFFFAA; - constexpr uint32_t CHAT_FILTER = 0xAA0000AA; - constexpr uint32_t INFINITE_HP = 0xAA1111AA; - constexpr uint32_t INFINITE_TP = 0xAA2222AA; - constexpr uint32_t SWITCH_ASSIST = 0xAA3333AA; - constexpr uint32_t BLOCK_EVENTS = 0xAA4444AA; - constexpr uint32_t BLOCK_PATCHES = 0xAA5555AA; - constexpr uint32_t SAVE_FILES = 0xAA6666AA; - constexpr uint32_t SUPPRESS_LOGIN = 0xAA7777AA; - constexpr uint32_t SKIP_CARD = 0xAA8888AA; - constexpr uint32_t EP3_INFINITE_MESETA = 0xAA9999AA; + constexpr uint32_t GO_BACK = 0xAAFFFFAA; + constexpr uint32_t CHAT_COMMANDS = 0xAA0000AA; + constexpr uint32_t CHAT_FILTER = 0xAA1111AA; + constexpr uint32_t INFINITE_HP = 0xAA2222AA; + constexpr uint32_t INFINITE_TP = 0xAA3333AA; + constexpr uint32_t SWITCH_ASSIST = 0xAA4444AA; + constexpr uint32_t BLOCK_EVENTS = 0xAA5555AA; + constexpr uint32_t BLOCK_PATCHES = 0xAA6666AA; + constexpr uint32_t SAVE_FILES = 0xAA7777AA; + constexpr uint32_t SUPPRESS_LOGIN = 0xAA8888AA; + constexpr uint32_t SKIP_CARD = 0xAA9999AA; + constexpr uint32_t EP3_INFINITE_MESETA = 0xAAAAAAAA; } diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 1759412e..bce1c178 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -1357,31 +1357,36 @@ static HandlerResult C_98(shared_ptr, static HandlerResult C_06(shared_ptr s, ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { if (data.size() >= 12) { + const auto& cmd = check_size_t(data, sizeof(C_Chat_06), 0xFFFF); + u16string text; + uint8_t private_flags = 0; if (session.version == GameVersion::PC || session.version == GameVersion::BB) { - const auto& cmd = check_size_t(data, sizeof(C_Chat_06), 0xFFFF); text = u16string(cmd.text.pcbb, (data.size() - sizeof(C_Chat_06)) / sizeof(char16_t)); + + } else if ((cmd.text.dcv3[0] != '\t') && + (session.newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) { + private_flags = cmd.text.dcv3[0]; + text = decode_sjis(cmd.text.dcv3 + 1, data.size() - sizeof(C_Chat_06)); + } else { - const auto& cmd = check_size_t(data, sizeof(C_Chat_06), 0xFFFF); text = decode_sjis(cmd.text.dcv3, data.size() - sizeof(C_Chat_06)); } + strip_trailing_zeroes(text); if (text.empty()) { return HandlerResult::Type::SUPPRESS; } - // On Episode 3, spectator chats begin with an @ character bool is_command = (text[0] == '$') || - (text[0] == '@' && text[1] == '$') || - (text[0] == '\t' && text[1] != 'C' && text[2] == '$') || - (text[0] == '@' && text[1] == '\t' && text[2] != 'C' && text[3] == '$'); - if (is_command) { - size_t offset = text[0] == '@' ? 1 : 0; - offset += text[offset] == '$' ? 0 : 2; + (text[0] == '\t' && text[1] != 'C' && text[2] == '$'); + if (is_command && session.options.enable_chat_commands) { + size_t offset = ((text[0] & 0xF0) == 0x40) ? 1 : 0; + offset += (text[offset] == '$') ? 0 : 2; text = text.substr(offset); if (text.size() >= 2 && text[1] == '$') { - send_chat_message(session.server_channel, text.substr(1)); + send_chat_message(session.server_channel, text.substr(1), private_flags); return HandlerResult::Type::SUPPRESS; } else { on_chat_command(s, session, text); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index d082bdca..8edf32fd 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -72,7 +72,8 @@ vector quest_download_menu({ static const unordered_map proxy_options_menu_descriptions({ {ProxyOptionsMenuItemID::GO_BACK, u"Return to the\nproxy menu"}, - {ProxyOptionsMenuItemID::CHAT_FILTER, u"Enable escape\nsequences in chat\nmessages"}, + {ProxyOptionsMenuItemID::CHAT_COMMANDS, u"Enable chat\ncommands"}, + {ProxyOptionsMenuItemID::CHAT_FILTER, u"Enable escape\nsequences in\nchat messages"}, {ProxyOptionsMenuItemID::INFINITE_HP, u"Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills"}, {ProxyOptionsMenuItemID::INFINITE_TP, u"Enable automatic TP\nrestoration when\nyou cast any\ntechnique"}, {ProxyOptionsMenuItemID::SWITCH_ASSIST, u"Automatically try\nto unlock 2-player\ndoors when you step\non both switches\nsequentially"}, @@ -99,6 +100,7 @@ static vector proxy_options_menu_for_client( ret.emplace_back(item_id, option, u"", 0); }; + add_option(ProxyOptionsMenuItemID::CHAT_COMMANDS, c->options.enable_chat_commands, u"Chat commands"); add_option(ProxyOptionsMenuItemID::CHAT_FILTER, c->options.enable_chat_filter, u"Chat filter"); if (!(c->flags & Client::Flag::IS_EPISODE_3)) { add_option(ProxyOptionsMenuItemID::INFINITE_HP, c->options.infinite_hp, u"Infinite HP"); @@ -1792,6 +1794,9 @@ static void on_10(shared_ptr s, shared_ptr c, case ProxyOptionsMenuItemID::GO_BACK: send_proxy_destinations_menu(s, c); break; + case ProxyOptionsMenuItemID::CHAT_COMMANDS: + c->options.enable_chat_commands = !c->options.enable_chat_commands; + goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::CHAT_FILTER: c->options.enable_chat_filter = !c->options.enable_chat_filter; goto resend_proxy_options_menu; diff --git a/src/SendCommands.cc b/src/SendCommands.cc index f9a3d54e..7d88d348 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -749,8 +749,18 @@ u16string prepare_chat_message( return data; } -void send_chat_message(Channel& ch, const u16string& text) { - send_header_text(ch, 0x06, 0, text, false); +void send_chat_message(Channel& ch, const u16string& text, char private_flags) { + if (private_flags != 0) { + if (ch.version != GameVersion::GC) { + throw runtime_error("nonzero private_flags in non-GC chat message"); + } + u16string effective_text; + effective_text.push_back(static_cast(private_flags)); + effective_text += text; + send_header_text(ch, 0x06, 0, effective_text, false); + } else { + send_header_text(ch, 0x06, 0, text, false); + } } void send_chat_message(shared_ptr c, uint32_t from_guild_card_number, diff --git a/src/SendCommands.hh b/src/SendCommands.hh index ba556116..87745a4d 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -192,7 +192,10 @@ std::u16string prepare_chat_message( const std::u16string& from_name, const std::u16string& text, char private_flags); -void send_chat_message(Channel& ch, const std::u16string& text); +void send_chat_message( + Channel& ch, + const std::u16string& text, + char private_flags); void send_chat_message( std::shared_ptr c, uint32_t from_guild_card_number,