diff --git a/src/ItemData.cc b/src/ItemData.cc index df1a29fb..d234d8ed 100644 --- a/src/ItemData.cc +++ b/src/ItemData.cc @@ -101,6 +101,14 @@ bool ItemData::empty() const { } uint32_t ItemData::primary_identifier() const { + // Primary identifiers are like: + // - 00TTSS00 = weapon (T = type, S = subtype; subtype is 0 for ES weapons) + // - 01TTSS00 = armor/shield/unit + // - 02TT0000 = mag + // - 0302ZZLL = tech disk (Z = tech number, L = level) + // - 03TTSS00 = tool + // - 04000000 = meseta + // The game treats any item starting with 04 as Meseta, and ignores the rest // of data1 (the value is in data2) if (this->data1[0] == 0x04) { diff --git a/src/ItemParameterTable.cc b/src/ItemParameterTable.cc index eb7d4e00..0a5250cb 100644 --- a/src/ItemParameterTable.cc +++ b/src/ItemParameterTable.cc @@ -1014,14 +1014,15 @@ uint8_t ItemParameterTable::get_item_base_stars(const ItemData& item) const { } } -uint8_t ItemParameterTable::get_item_adjusted_stars(const ItemData& item) const { +uint8_t ItemParameterTable::get_item_adjusted_stars(const ItemData& item, bool ignore_unidentified) const { uint8_t ret = this->get_item_base_stars(item); if (item.data1[0] == 0) { + bool is_unidentified = (!ignore_unidentified) && (item.data1[4] & 0x80); if (ret < 9) { - if (!(item.data1[4] & 0x80)) { + if (!is_unidentified) { ret += this->get_special_stars(item.data1[4]); } - } else if (item.data1[4] & 0x80) { + } else if (is_unidentified) { ret = 0; } } else if (item.data1[0] == 1) { diff --git a/src/ItemParameterTable.hh b/src/ItemParameterTable.hh index 50ed23d4..c5edd1a0 100644 --- a/src/ItemParameterTable.hh +++ b/src/ItemParameterTable.hh @@ -450,7 +450,7 @@ public: uint32_t get_item_id(const ItemData& item) const; uint32_t get_item_team_points(const ItemData& item) const; uint8_t get_item_base_stars(const ItemData& item) const; - uint8_t get_item_adjusted_stars(const ItemData& item) const; + uint8_t get_item_adjusted_stars(const ItemData& item, bool ignore_unidentified = false) const; bool is_item_rare(const ItemData& item) const; bool is_unsealable_item(uint8_t data1_0, uint8_t data1_1, uint8_t data1_2) const; bool is_unsealable_item(const ItemData& param_1) const; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 92a13750..87287adc 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1997,32 +1997,14 @@ static void on_pick_up_item_generic( } } - if ((fi->flags & 0x1000) && (fi->data.data1[0] < 0x04)) { - auto pmt = s->item_parameter_table(c->version()); - bool should_send_game_notif = false; - bool should_send_global_notif = false; - switch (fi->data.data1[0]) { - case 0x00: - case 0x01: { - uint8_t stars = pmt->get_item_adjusted_stars(fi->data); - should_send_game_notif = (stars >= s->game_rare_notif_min_stars); - should_send_global_notif = (stars >= s->global_rare_notif_min_stars); - break; - } - case 0x02: { - should_send_game_notif = s->game_rare_mag_notifs_enabled && pmt->is_item_rare(fi->data); - break; - } - case 0x03: { - should_send_game_notif = s->game_rare_tool_notifs_enabled && pmt->is_item_rare(fi->data); - break; - } - } - + if (fi->flags & 0x1000) { + uint32_t pi = fi->data.primary_identifier(); + bool should_send_game_notif = s->notify_game_for_item_primary_identifiers.count(pi); + bool should_send_global_notif = s->notify_server_for_item_primary_identifiers.count(pi); if (should_send_game_notif || should_send_global_notif) { string p_name = p->disp.name.decode(); string desc = s->describe_item(c->version(), fi->data, true); - string message = string_printf("$C6%s$C7 has found\n%s", p_name.c_str(), desc.c_str()); + string message = string_printf("$C6%s$C7 found\n%s", p_name.c_str(), desc.c_str()); if (should_send_global_notif) { for (auto& it : s->channel_to_client) { if (it.second->license && diff --git a/src/ServerState.cc b/src/ServerState.cc index 65911a8c..83a8bfc9 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -733,10 +733,6 @@ void ServerState::load_config_early() { this->default_rare_notifs_enabled_v3_v4 = this->default_rare_notifs_enabled_v1_v2; this->default_rare_notifs_enabled_v1_v2 = this->config_json->get_bool("RareNotificationsEnabledByDefaultV1V2", this->default_rare_notifs_enabled_v1_v2); this->default_rare_notifs_enabled_v3_v4 = this->config_json->get_bool("RareNotificationsEnabledByDefaultV3V4", this->default_rare_notifs_enabled_v3_v4); - this->game_rare_notif_min_stars = this->config_json->get_int("GameRareNotifMinStars", this->game_rare_notif_min_stars); - this->global_rare_notif_min_stars = this->config_json->get_int("GlobalRareNotifMinStars", this->global_rare_notif_min_stars); - this->game_rare_mag_notifs_enabled = this->config_json->get_bool("GameRareMagNotifsEnabled", this->game_rare_mag_notifs_enabled); - this->game_rare_tool_notifs_enabled = this->config_json->get_bool("GameRareToolNotifsEnabled", this->game_rare_tool_notifs_enabled); this->ep3_send_function_call_enabled = this->config_json->get_bool("EnableEpisode3SendFunctionCall", false); this->enable_v3_v4_protected_subcommands = this->config_json->get_bool("EnableV3V4ProtectedSubcommands", false); this->catch_handler_exceptions = this->config_json->get_bool("CatchHandlerExceptions", true); @@ -1203,6 +1199,31 @@ void ServerState::load_config_late() { } } catch (const out_of_range&) { } + + this->notify_game_for_item_primary_identifiers.clear(); + try { + for (const auto& pi_json : this->config_json->get_list("NotifyGameForItemPrimaryIdentifiers")) { + if (pi_json->is_int()) { + this->notify_game_for_item_primary_identifiers.emplace(pi_json->as_int()); + } else { + auto item = this->parse_item_description(Version::BB_V4, pi_json->as_string()); + this->notify_game_for_item_primary_identifiers.emplace(item.primary_identifier()); + } + } + } catch (const out_of_range&) { + } + this->notify_server_for_item_primary_identifiers.clear(); + try { + for (const auto& pi_json : this->config_json->get_list("NotifyServerForItemPrimaryIdentifiers")) { + if (pi_json->is_int()) { + this->notify_server_for_item_primary_identifiers.emplace(pi_json->as_int()); + } else { + auto item = this->parse_item_description(Version::BB_V4, pi_json->as_string()); + this->notify_server_for_item_primary_identifiers.emplace(item.primary_identifier()); + } + } + } catch (const out_of_range&) { + } } else { config_log.warning("BB item name index is missing; cannot load quest reward lists from config"); } diff --git a/src/ServerState.hh b/src/ServerState.hh index 9a6e807e..b207982d 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -128,10 +128,8 @@ struct ServerState : public std::enable_shared_from_this { bool use_game_creator_section_id = false; bool default_rare_notifs_enabled_v1_v2 = false; bool default_rare_notifs_enabled_v3_v4 = false; - uint8_t game_rare_notif_min_stars = 0xFF; - uint8_t global_rare_notif_min_stars = 0xFF; - bool game_rare_mag_notifs_enabled = false; - bool game_rare_tool_notifs_enabled = false; + std::unordered_set notify_game_for_item_primary_identifiers; + std::unordered_set notify_server_for_item_primary_identifiers; 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 80ca0b00..16ff2597 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -969,25 +969,37 @@ "RareNotificationsEnabledByDefaultV1V2": false, "RareNotificationsEnabledByDefaultV3V4": false, - // Conditions for when to broadcast rare notifications. These only apply to - // items dropped by boxes and enemies; items dropped by players or created - // with the $item command do not cause notifications when picked up. - - // If a weapon, armor, shield, or unit with at least this many stars is - // picked up, everyone in the game is notified. To disable these - // notifications, set this to 255. - "GameRareNotifMinStars": 10, - // If a weapon, armor, shield, or unit with at least this many stars is - // picked up, everyone on the server (except Episode 3 players) is notified. - // To disable these notifications, set this to 255. - "GlobalRareNotifMinStars": 12, - // If this option is on and a rare mag is picked up, everyone in the game is - // notified. Note that there are no rare mags in any of the default drop - // tables, so this has no effect unless you add rare mags to the drop tables. - "GameRareMagNotifsEnabled": true, - // If this option is on and a rare tool item (for example, a Photon Drop) is - // picked up, everyone in the game is notified. - "GameRareToolNotifsEnabled": true, + // Items for which rare notifications should be broadcast to the game or + // entire server. These notifications occur when the item is picked up. They + // only are generated from items dropped by boxes and enemies; items dropped + // by players or created with the $item command do not cause notifications + // when picked up. + // Entries in these lists are primary identifiers, which are similar to the + // usual 3-byte item codes but are slightly more expressive. In summary, + // primary identifiers go like this: + // 0x00TTSS00 = weapon (T = type, S = subtype; subtype is 0 for ES weapons) + // 0x01TTSS00 = armor/shield/unit + // 0x02TT0000 = mag + // 0x03TTSS00 = tool item (except tech disks) + // 0x0302ZZLL = tech disk (Z = tech number, L = level - 1) + // 0x04000000 = meseta + // For example (you can check these in names-v4.json): + // 0x00020700 = DRAGON SLAYER (any stats) + // 0x01012A00 = DF FIELD (any stats) + // 0x020B0000 = Tapas (any level) + // 0x03010200 = Trifluid (any amount) + // 0x0302061D = Disk:Zonde Lv.30 + // 0x04000000 = Meseta (any amount) + // As with most other places where you can specify items in this file, you + // can also put textual item descriptions in here (e.g. "DRAGON SLAYER"), but + // only data that would appear in the primary identifier will be used - that + // is, if you were to put something like "DRAGON SLAYER +5 5/0/0/5/0", the + // grind and bonuses would be ignored, and all DRAGON SLAYERs would result in + // notifications regardless of their stats. For textual descriptions, the + // items are parsed as they would be on BB, so certain V2-only items cannot + // be represented this way. + "NotifyGameForItemPrimaryIdentifiers": [], + "NotifyServerForItemPrimaryIdentifiers": [], // 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 diff --git a/tests/config.json b/tests/config.json index c2fd2a9a..1fe0542a 100644 --- a/tests/config.json +++ b/tests/config.json @@ -32,10 +32,8 @@ "CheatModeBehavior": "OnByDefault", "UnlockAllAreas": false, "RareNotificationsEnabledByDefault": false, - "GameRareNotifMinStars": 10, - "GlobalRareNotifMinStars": 12, - "GameRareMagNotifsEnabled": true, - "GameRareToolNotifsEnabled": true, + "NotifyGameForItemPrimaryIdentifiers": [], + "NotifyServerForItemPrimaryIdentifiers": [], "LocalAddress": "en0", "ExternalAddress": "en0",