From 086b2d411a2d3df1caf3eb496806c71c0b2a3343 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 20 Oct 2024 16:05:29 -0700 Subject: [PATCH] add ability to disable rare announcements per account; closes #576 --- README.md | 1 + src/Account.cc | 36 ++++++++++++++++++++++++++---------- src/Account.hh | 17 +++++++++++++++++ src/ChatCommands.cc | 8 ++++++++ src/ReceiveSubcommands.cc | 3 ++- src/ServerShell.cc | 37 ++++++++++++++++++++++++++++++++++++- 6 files changed, 90 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 896cea7a..ee06f3ee 100644 --- a/README.md +++ b/README.md @@ -532,6 +532,7 @@ Some commands only work on the game server and not on the proxy server. The chat * `rare`: You are notified when a rare item drops. * `on`: You are notified when any item drops, except Meseta. * `every`: You are notified when any item drops, including Meseta. + * `$announcerares`: Enable or disable announcements for your rare item finds. This determines whether rare items you find will be announced to the game and server, not whether you will see announcements for others finding rare items. * `$what` (game server only): Show the type, name, and stats of the nearest item on the ground. * `$where` (game server only): Show your current floor number and coordinates. Mainly useful for debugging. * `$qfread ` (game server only): Show the value of a quest counter in your player data. The field names are defined in config.json. diff --git a/src/Account.cc b/src/Account.cc index e62aadac..4210c9e4 100644 --- a/src/Account.cc +++ b/src/Account.cc @@ -136,6 +136,7 @@ phosg::JSON BBLicense::json() const { Account::Account(const phosg::JSON& json) : account_id(0), flags(0), + user_flags(0), ban_end_time(0), ep3_current_meseta(0), ep3_total_meseta_earned(0), @@ -222,6 +223,7 @@ Account::Account(const phosg::JSON& json) } this->flags = json.get_int("Flags", 0); + this->user_flags = json.get_int("UserFlags", 0); this->ban_end_time = json.get_int("BanEndTime", 0); this->last_player_name = json.get_string("LastPlayerName", ""); this->auto_reply_message = json.get_string("AutoReplyMessage", ""); @@ -278,6 +280,7 @@ phosg::JSON Account::json() const { {"XBLicenses", std::move(xb_json)}, {"BBLicenses", std::move(bb_json)}, {"Flags", this->flags}, + {"UserFlags", this->user_flags}, {"BanEndTime", this->ban_end_time}, {"LastPlayerName", this->last_player_name}, {"AutoReplyMessage", this->auto_reply_message}, @@ -300,34 +303,34 @@ void Account::print(FILE* stream) const { } else if (this->flags == static_cast(Flag::MODERATOR)) { flags_str = "MODERATOR"; } else { - if (this->flags & static_cast(Flag::KICK_USER)) { + if (this->check_flag(Flag::KICK_USER)) { flags_str += "KICK_USER,"; } - if (this->flags & static_cast(Flag::BAN_USER)) { + if (this->check_flag(Flag::BAN_USER)) { flags_str += "BAN_USER,"; } - if (this->flags & static_cast(Flag::SILENCE_USER)) { + if (this->check_flag(Flag::SILENCE_USER)) { flags_str += "SILENCE_USER,"; } - if (this->flags & static_cast(Flag::CHANGE_EVENT)) { + if (this->check_flag(Flag::CHANGE_EVENT)) { flags_str += "CHANGE_EVENT,"; } - if (this->flags & static_cast(Flag::ANNOUNCE)) { + if (this->check_flag(Flag::ANNOUNCE)) { flags_str += "ANNOUNCE,"; } - if (this->flags & static_cast(Flag::FREE_JOIN_GAMES)) { + if (this->check_flag(Flag::FREE_JOIN_GAMES)) { flags_str += "FREE_JOIN_GAMES,"; } - if (this->flags & static_cast(Flag::DEBUG)) { + if (this->check_flag(Flag::DEBUG)) { flags_str += "DEBUG,"; } - if (this->flags & static_cast(Flag::CHEAT_ANYWHERE)) { + if (this->check_flag(Flag::CHEAT_ANYWHERE)) { flags_str += "CHEAT_ANYWHERE,"; } - if (this->flags & static_cast(Flag::DISABLE_QUEST_REQUIREMENTS)) { + if (this->check_flag(Flag::DISABLE_QUEST_REQUIREMENTS)) { flags_str += "ALWAYS_ENABLE_CHAT_COMMANDS,"; } - if (this->flags & static_cast(Flag::IS_SHARED_ACCOUNT)) { + if (this->check_flag(Flag::IS_SHARED_ACCOUNT)) { flags_str += "IS_SHARED_ACCOUNT,"; } } @@ -339,6 +342,19 @@ void Account::print(FILE* stream) const { fprintf(stream, " Flags: %08" PRIX32 " (%s)\n", this->flags, flags_str.c_str()); } + if (this->user_flags) { + string user_flags_str = ""; + if (this->check_user_flag(UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST)) { + user_flags_str += "DISABLE_DROP_NOTIFICATION_BROADCAST,"; + } + if (user_flags_str.empty()) { + user_flags_str = "none"; + } else if (phosg::ends_with(user_flags_str, ",")) { + user_flags_str.pop_back(); + } + fprintf(stream, " User flags: %08" PRIX32 " (%s)\n", this->user_flags, user_flags_str.c_str()); + } + if (this->ban_end_time) { string time_str = phosg::format_time(this->ban_end_time); fprintf(stream, " Banned until: %" PRIu64 " (%s)\n", this->ban_end_time, time_str.c_str()); diff --git a/src/Account.hh b/src/Account.hh index 64465a97..8413f5b0 100644 --- a/src/Account.hh +++ b/src/Account.hh @@ -76,11 +76,15 @@ struct Account { UNUSED_BITS = 0x70FFFF00, // clang-format on }; + enum class UserFlag : uint32_t { + DISABLE_DROP_NOTIFICATION_BROADCAST = 0x00000001, + }; // account_id is also the account's guild card number uint32_t account_id = 0; uint32_t flags = 0; + uint32_t user_flags = 0; uint64_t ban_end_time = 0; // 0 = not banned std::string last_player_name; std::string auto_reply_message; @@ -124,6 +128,19 @@ struct Account { this->flags = static_cast(mask); } + [[nodiscard]] inline bool check_user_flag(UserFlag flag) const { + return !!(this->user_flags & static_cast(flag)); + } + inline void set_user_flag(UserFlag flag) { + this->user_flags |= static_cast(flag); + } + inline void clear_user_flag(UserFlag flag) { + this->user_flags &= (~static_cast(flag)); + } + inline void toggle_user_flag(UserFlag flag) { + this->user_flags ^= static_cast(flag); + } + void print(FILE* stream) const; }; diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 159bb919..8e4265d5 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -2088,6 +2088,13 @@ static void proxy_command_switch_assist(shared_ptr s ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED) ? "enabled" : "disabled"); } +static void server_command_toggle_rare_announce(shared_ptr c, const std::string&) { + c->login->account->toggle_user_flag(Account::UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST); + c->login->account->save(); + send_text_message_printf(c, "$C6Rare announcements\n%s for your\nitems", + c->login->account->check_user_flag(Account::UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST) ? "disabled" : "enabled"); +} + static void server_command_dropmode(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); check_is_game(l, true); @@ -2580,6 +2587,7 @@ static const unordered_map chat_commands({ {"$allevent", {server_command_lobby_event_all, nullptr}}, {"$ann", {server_command_announce, nullptr}}, {"$ann!", {server_command_announce_mail, nullptr}}, + {"$announcerares", {server_command_toggle_rare_announce, nullptr}}, {"$arrow", {server_command_arrow, proxy_command_arrow}}, {"$auction", {server_command_auction, proxy_command_auction}}, {"$ax", {server_command_ax, nullptr}}, diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index c1e67e38..fc5483bf 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -2071,7 +2071,8 @@ static void on_pick_up_item_generic( } } - if (fi->flags & 0x1000) { + if (!c->login->account->check_user_flag(Account::UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST) && + (fi->flags & 0x1000)) { uint32_t pi = fi->data.primary_identifier(); bool should_send_game_notif, should_send_global_notif; if (is_v1_or_v2(c->version()) && (c->version() != Version::GC_NTE)) { diff --git a/src/ServerShell.cc b/src/ServerShell.cc index 67b03bae..c157149a 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -362,11 +362,37 @@ uint32_t parse_account_flags(const string& flags_str) { return ret; } +uint32_t parse_account_user_flags(const string& user_flags_str) { + try { + size_t end_pos = 0; + uint32_t ret = stoul(user_flags_str, &end_pos, 16); + if (end_pos == user_flags_str.size()) { + return ret; + } + } catch (const exception&) { + } + + uint32_t ret = 0; + auto tokens = phosg::split(user_flags_str, ','); + for (const auto& token : tokens) { + string token_upper = phosg::toupper(token); + if (token_upper == "NONE") { + // Nothing to do + } else if (token_upper == "DISABLE_DROP_NOTIFICATION_BROADCAST") { + ret |= static_cast(Account::UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST); + } else { + throw runtime_error("invalid user flag name: " + token_upper); + } + } + return ret; +} + CommandDefinition c_add_account( "add-account", "add-account [PARAMETERS...]\n\ Add an account to the server. is some subset of:\n\ id=ACCOUNT-ID: preferred account ID in hex (optional)\n\ flags=FLAGS: behaviors and permissions for the account (see below)\n\ + user-flags=FLAGS: user-set behaviors for the account\n\ ep3-current-meseta=MESETA: Episode 3 Meseta value\n\ ep3-total-meseta=MESETA: Episode 3 total Meseta ever earned\n\ temporary: marks the account as temporary; it is not saved to disk and\n\ @@ -405,6 +431,8 @@ CommandDefinition c_add_account( account->is_temporary = true; } else if (phosg::starts_with(token, "flags=")) { account->flags = parse_account_flags(token.substr(6)); + } else if (phosg::starts_with(token, "user-flags=")) { + account->user_flags = parse_account_user_flags(token.substr(11)); } else { throw invalid_argument("invalid account field: " + token); } @@ -418,7 +446,8 @@ CommandDefinition c_update_account( Update an existing license. ACCOUNT-ID (8 hex digits) specifies which\n\ account to update. The options are similar to the add-account command:\n\ flags=FLAGS: sets behaviors and permissions for the account (same as\n\ - with add-account)\n\ + add-account)\n\ + user-flags=FLAGS: sets behaviors for the account (same as add-account)\n\ ban-duration=DURATION: bans this account for the specified duration; the\n\ duration should be of the form 3d, 2w, 1mo, or 1y\n\ unban: clears any existing ban from this account\n\ @@ -441,6 +470,7 @@ CommandDefinition c_update_account( int64_t new_ep3_current_meseta = -1; int64_t new_ep3_total_meseta = -1; int64_t new_flags = -1; + int64_t new_user_flags = -1; uint8_t new_is_temporary = 0xFF; int64_t new_ban_duration = -1; for (const string& token : tokens) { @@ -454,6 +484,8 @@ CommandDefinition c_update_account( new_is_temporary = 0; } else if (phosg::starts_with(token, "flags=")) { new_flags = parse_account_flags(token.substr(6)); + } else if (phosg::starts_with(token, "user-flags=")) { + new_user_flags = parse_account_user_flags(token.substr(11)); } else if (token == "unban") { new_ban_duration = 0; } else if (phosg::starts_with(token, "ban-duration=")) { @@ -492,6 +524,9 @@ CommandDefinition c_update_account( if (new_flags >= 0) { account->flags = new_flags; } + if (new_user_flags >= 0) { + account->user_flags = new_user_flags; + } if (new_is_temporary != 0xFF) { account->is_temporary = new_is_temporary; }