diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 0f4b6097..90176423 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -1417,7 +1417,7 @@ struct S_LeaveLobby_66_69_Ep3_E9 { // 7E: Invalid command // 7F: Invalid command -// 80: Valid but ignored (all versions) +// 80: Valid but ignored (all versions except BB) // Internal names: RcvGenerateID and SndGenerateID // This command appears to be used to set the next item ID for the given player // slot. PSO V3 and later accept this command, but ignore it entirely. Notably, diff --git a/src/HTTPServer.cc b/src/HTTPServer.cc index 884b5c15..29c236f6 100644 --- a/src/HTTPServer.cc +++ b/src/HTTPServer.cc @@ -585,7 +585,7 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr l, shared_ptrx}, {"LocationZ", item->z}, {"DropNumber", item->drop_number}, - {"VisibilityFlags", item->visibility_flags}, + {"Flags", item->flags}, {"Data", item->data.hex()}, {"ItemID", item->data.id.load()}, }); diff --git a/src/Lobby.cc b/src/Lobby.cc index 3f84a569..811d70f7 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -12,7 +12,7 @@ using namespace std; bool Lobby::FloorItem::visible_to_client(uint8_t client_id) const { - return this->visibility_flags & (1 << client_id); + return this->flags & (1 << client_id); } Lobby::FloorItemManager::FloorItemManager(uint32_t lobby_id, uint8_t floor) @@ -27,18 +27,18 @@ shared_ptr Lobby::FloorItemManager::find(uint32_t item_id) con return this->items.at(item_id); } -void Lobby::FloorItemManager::add(const ItemData& item, float x, float z, uint16_t visibility_flags) { +void Lobby::FloorItemManager::add(const ItemData& item, float x, float z, uint16_t flags) { auto fi = make_shared(); fi->data = item; fi->x = x; fi->z = z; fi->drop_number = this->next_drop_number++; - fi->visibility_flags = visibility_flags & 0x0FFF; + fi->flags = flags; this->add(fi); } void Lobby::FloorItemManager::add(shared_ptr fi) { - if (fi->visibility_flags == 0) { + if (fi->flags == 0) { throw logic_error("floor item is not visible to any player"); } @@ -51,8 +51,8 @@ void Lobby::FloorItemManager::add(shared_ptr fi) { this->queue_for_client[z].emplace(fi->drop_number, fi); } } - this->log.info("Added floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " visible to %03hX", - fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->visibility_flags); + this->log.info("Added floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " with flags %03hX", + fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->flags); } std::shared_ptr Lobby::FloorItemManager::remove(uint32_t item_id, uint8_t client_id) { @@ -70,8 +70,8 @@ std::shared_ptr Lobby::FloorItemManager::remove(uint32_t item_ } } this->items.erase(item_it); - this->log.info("Removed floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " visible to %03hX", - fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->visibility_flags); + this->log.info("Removed floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " with flags %03hX", + fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->flags); return fi; } @@ -89,7 +89,7 @@ std::unordered_set> Lobby::FloorItemManager::e void Lobby::FloorItemManager::clear_inaccessible(uint16_t remaining_clients_mask) { unordered_set item_ids_to_delete; for (const auto& it : this->items) { - if ((it.second->visibility_flags & remaining_clients_mask) == 0) { + if ((it.second->flags & remaining_clients_mask) == 0) { item_ids_to_delete.emplace(it.first); } } @@ -102,7 +102,7 @@ void Lobby::FloorItemManager::clear_inaccessible(uint16_t remaining_clients_mask void Lobby::FloorItemManager::clear_private() { unordered_set item_ids_to_delete; for (const auto& it : this->items) { - if ((it.second->visibility_flags & 0x00F) != 0x00F) { + if ((it.second->flags & 0x00F) != 0x00F) { item_ids_to_delete.emplace(it.first); } } @@ -843,9 +843,9 @@ shared_ptr Lobby::find_item(uint8_t floor, uint32_t item_id) c return this->floor_item_managers.at(floor).find(item_id); } -void Lobby::add_item(uint8_t floor, const ItemData& data, float x, float z, uint16_t visibility_flags) { +void Lobby::add_item(uint8_t floor, const ItemData& data, float x, float z, uint16_t flags) { auto& m = this->floor_item_managers.at(floor); - m.add(data, x, z, visibility_flags); + m.add(data, x, z, flags); this->evict_items_from_floor(floor); } diff --git a/src/Lobby.hh b/src/Lobby.hh index 5674252b..9c3427d0 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -29,7 +29,13 @@ struct Lobby : public std::enable_shared_from_this { float x; float z; uint64_t drop_number; - uint16_t visibility_flags; + // The low 12 bits of flags are visibility flags, specifying which clients + // can see the item. (In practice, only the lowest 4 of these bits are used, + // but the game has fields for 12 players so we do too.) + // The 13th bit (0x1000) specifies whether a rare item notification should + // be sent to all players when the item is picked up. This has no effect for + // non-rare items. + uint16_t flags; bool visible_to_client(uint8_t client_id) const; }; @@ -46,7 +52,7 @@ struct Lobby : public std::enable_shared_from_this { bool exists(uint32_t item_id) const; std::shared_ptr find(uint32_t item_id) const; - void add(const ItemData& item, float x, float z, uint16_t visibility_flags); + void add(const ItemData& item, float x, float z, uint16_t flags); void add(std::shared_ptr fi); std::shared_ptr remove(uint32_t item_id, uint8_t client_id); std::unordered_set> evict(); @@ -290,7 +296,7 @@ struct Lobby : public std::enable_shared_from_this { bool item_exists(uint8_t floor, uint32_t item_id) const; std::shared_ptr find_item(uint8_t floor, uint32_t item_id) const; - void add_item(uint8_t floor, const ItemData& item, float x, float z, uint16_t visibility_flags); + void add_item(uint8_t floor, const ItemData& item, float x, float z, uint16_t flags); void add_item(uint8_t floor, std::shared_ptr); void evict_items_from_floor(uint8_t floor); std::shared_ptr remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 3a82eaa1..9c17fdaa 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1896,7 +1896,7 @@ static void on_box_or_enemy_item_drop_t(shared_ptr c, uint8_t command, u ItemData item = cmd.item.item; item.decode_for_version(c->version()); l->on_item_id_generated_externally(item.id); - l->add_item(cmd.item.floor, item, cmd.item.x, cmd.item.z, 0x00F); + l->add_item(cmd.item.floor, item, cmd.item.x, cmd.item.z, 0x100F); auto name = s->describe_item(c->version(), item, false); l->log.info("Player %hhu (leader) created floor item %08" PRIX32 " (%s) at %hhu:(%g, %g)", @@ -1993,6 +1993,47 @@ static void on_pick_up_item_generic( send_create_inventory_item_to_client(lc, client_id, fi->data); } } + + 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 (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()); + if (should_send_global_notif) { + for (auto& it : s->channel_to_client) { + if (it.second->license && + !is_patch(it.second->version()) && + !is_ep3(it.second->version()) && + it.second->lobby.lock()) { + send_text_message(it.second, message); + } + } + } else { + send_text_message(l, message); + } + } + } } } @@ -2605,7 +2646,7 @@ static void on_entity_drop_item_request(shared_ptr c, uint8_t command, u res.item.id = l->generate_item_id(0xFF); l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s", res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str()); - l->add_item(cmd.floor, res.item, cmd.x, cmd.z, (1 << lc->lobby_client_id)); + l->add_item(cmd.floor, res.item, cmd.x, cmd.z, 0x1000 | (1 << lc->lobby_client_id)); send_drop_item_to_channel(s, lc->channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id); send_item_notification_if_needed(s, lc->channel, lc->config, res.item, res.is_from_rare_table); } @@ -2615,7 +2656,7 @@ static void on_entity_drop_item_request(shared_ptr c, uint8_t command, u res.item.id = l->generate_item_id(0xFF); l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for all clients", res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load()); - l->add_item(cmd.floor, res.item, cmd.x, cmd.z, 0x00F); + l->add_item(cmd.floor, res.item, cmd.x, cmd.z, 0x100F); send_drop_item_to_lobby(l, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id); for (auto lc : l->clients) { if (lc) { @@ -2638,7 +2679,7 @@ static void on_entity_drop_item_request(shared_ptr c, uint8_t command, u res.item.id = l->generate_item_id(0xFF); l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s", res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str()); - l->add_item(cmd.floor, res.item, cmd.x, cmd.z, (1 << lc->lobby_client_id)); + l->add_item(cmd.floor, res.item, cmd.x, cmd.z, 0x1000 | (1 << lc->lobby_client_id)); send_drop_item_to_channel(s, lc->channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id); send_item_notification_if_needed(s, lc->channel, lc->config, res.item, res.is_from_rare_table); } @@ -3703,7 +3744,7 @@ static void on_request_challenge_grave_recovery_item_bb(shared_ptr c, ui }; ItemData item = items.at(cmd.item_type); item.id = l->generate_item_id(cmd.header.client_id); - l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x00F); + l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x100F); send_drop_stacked_item_to_lobby(l, item, cmd.floor, cmd.x, cmd.z); } } @@ -4072,7 +4113,7 @@ static void on_quest_F95E_result_bb(shared_ptr c, uint8_t, uint8_t, void } item.id = l->generate_item_id(0xFF); - l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x00F); + l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x100F); send_drop_stacked_item_to_lobby(l, item, cmd.floor, cmd.x, cmd.z); } diff --git a/src/ServerState.cc b/src/ServerState.cc index ecea29ff..65911a8c 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -733,6 +733,10 @@ 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); diff --git a/src/ServerState.hh b/src/ServerState.hh index 2e5ff957..9a6e807e 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -128,6 +128,10 @@ 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::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 c02c945e..80ca0b00 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -969,6 +969,26 @@ "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, + // 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 diff --git a/tests/config.json b/tests/config.json index 39f40c4a..c2fd2a9a 100644 --- a/tests/config.json +++ b/tests/config.json @@ -32,6 +32,10 @@ "CheatModeBehavior": "OnByDefault", "UnlockAllAreas": false, "RareNotificationsEnabledByDefault": false, + "GameRareNotifMinStars": 10, + "GlobalRareNotifMinStars": 12, + "GameRareMagNotifsEnabled": true, + "GameRareToolNotifsEnabled": true, "LocalAddress": "en0", "ExternalAddress": "en0",