diff --git a/src/Lobby.cc b/src/Lobby.cc index 8ff904d9..d82c7930 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -618,6 +618,14 @@ Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr c, const s return JoinError::VERSION_CONFLICT; } if (this->is_game()) { + // Brutal Peeps rooms rely on version-specific BattleParam patching. + // BB Brutal rooms are BB-only; PC Brutal rooms are PC V2-only. + if ((this->brutal_peeps_tier >= 1) && + ((this->version_is_allowed(Version::BB_V4) && (c->version() != Version::BB_V4)) || + (this->version_is_allowed(Version::PC_V2) && (c->version() != Version::PC_V2)))) { + return JoinError::VERSION_CONFLICT; + } + if (this->check_flag(Flag::QUEST_SELECTION_IN_PROGRESS)) { return JoinError::QUEST_SELECTION_IN_PROGRESS; } diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index e1115f3d..79aeeb8e 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -2771,7 +2771,9 @@ static asio::awaitable on_10_main_menu(std::shared_ptr c, uint32_t co_await send_auto_patches_if_needed(c); if (c->version() == Version::BB_V4) { - co_await send_brutal_peeps_hp_patch_bb(c, tier); + // BB must patch all online BattleParam files before room creation/area load. + // PC V2 uses the delayed area-load retry path instead. + co_await send_brutal_peeps_hp_patch_bb(c, tier, true); } co_await enable_save_if_needed(c); send_lobby_list(c); @@ -2785,7 +2787,11 @@ static asio::awaitable on_10_main_menu(std::shared_ptr c, uint32_t case MainMenuItemID::GO_TO_LOBBY: { c->selected_brutal_peeps_tier = -1; co_await send_auto_patches_if_needed(c); - co_await send_brutal_peeps_hp_patch_bb(c, -1); + if (c->version() == Version::BB_V4) { + co_await send_brutal_peeps_hp_patch_bb(c, -1, true); + } else if (c->version() == Version::PC_V2) { + co_await send_brutal_peeps_hp_patch_bb(c, -1); + } co_await enable_save_if_needed(c); send_lobby_list(c); if (is_pre_v1(c->version())) { @@ -3664,6 +3670,27 @@ static void on_joinable_quest_loaded(std::shared_ptr c) { leader_c->expected_game_state_sync_commands.emplace(0x6C00 | (c->lobby_client_id)); leader_c->expected_game_state_sync_commands.emplace(0x6D00 | (c->lobby_client_id)); leader_c->expected_game_state_sync_commands.emplace(0x6E00 | (c->lobby_client_id)); + if (((c->version() == Version::BB_V4) || (c->version() == Version::PC_V2)) && l->is_game()) { + const int8_t room_brutal_peeps_tier = l->brutal_peeps_tier; + const int8_t client_brutal_peeps_tier = c->selected_brutal_peeps_tier; + + if ((room_brutal_peeps_tier >= 1) && (client_brutal_peeps_tier != room_brutal_peeps_tier)) { + send_message_box(c, std::format( + "$C6Must have Brutal Peeps +{} selected\nto join this room.\n\n" + "$C7Use Transfer Ship and select\nBrutal Peeps +{} first.", + static_cast(room_brutal_peeps_tier), + static_cast(room_brutal_peeps_tier))); + return; + } + + if ((room_brutal_peeps_tier < 1) && (client_brutal_peeps_tier >= 1)) { + send_message_box(c, + "$C6Disable Brutal Peeps before\njoining a normal room.\n\n" + "$C7Use Transfer Ship and select\nGo to lobby first."); + return; + } + } + c->log.info_f("Creating game join command queue"); c->game_join_command_queue = std::make_unique>(); } else { diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 1522112b..da074834 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -777,7 +777,8 @@ static std::string bb_stream_file_data_for_client(std::shared_ptr c) { static std::vector>>> send_brutal_peeps_hp_patch_bb_now( std::shared_ptr c, - int64_t tier) { + int64_t tier, + bool force_all_tables) { std::vector>>> promises; if (c->version() != Version::BB_V4) { @@ -831,25 +832,28 @@ static std::vector bp_filenames; - auto l = c->lobby.lock(); - if (l && l->is_game()) { - switch (l->episode) { - case Episode::EP1: - bp_filenames.emplace_back("BattleParamEntry_on.dat"); - break; - case Episode::EP2: - bp_filenames.emplace_back("BattleParamEntry_lab_on.dat"); - break; - case Episode::EP4: - bp_filenames.emplace_back("BattleParamEntry_ep4_on.dat"); - break; - default: - break; + + if (!force_all_tables) { + auto l = c->lobby.lock(); + if (l && l->is_game()) { + switch (l->episode) { + case Episode::EP1: + bp_filenames.emplace_back("BattleParamEntry_on.dat"); + break; + case Episode::EP2: + bp_filenames.emplace_back("BattleParamEntry_lab_on.dat"); + break; + case Episode::EP4: + bp_filenames.emplace_back("BattleParamEntry_ep4_on.dat"); + break; + default: + break; + } } } - // Before the room exists, we don't know which episode the player will pick. - // Patch all online BB BattleParam tables so EP2/EP4 HP is already scaled before enemies initialize. + // Before the room exists, or when explicitly requested from the BB ship-menu path, + // patch all online BB BattleParam tables before enemies initialize. if (bp_filenames.empty()) { bp_filenames.emplace_back("BattleParamEntry_on.dat"); bp_filenames.emplace_back("BattleParamEntry_lab_on.dat"); @@ -1251,7 +1255,7 @@ static std::vector send_brutal_peeps_hp_patch_bb(std::shared_ptr c, int64_t tier) { +asio::awaitable send_brutal_peeps_hp_patch_bb(std::shared_ptr c, int64_t tier, bool force_all_tables) { try { co_await prepare_client_for_patches(c); @@ -1261,7 +1265,7 @@ asio::awaitable send_brutal_peeps_hp_patch_bb(std::shared_ptr c, i for (size_t attempt = 1; attempt <= max_attempts; attempt++) { auto promises = is_pc_bp_patch ? send_brutal_peeps_hp_patch_pc_now(c, tier) - : send_brutal_peeps_hp_patch_bb_now(c, tier); + : send_brutal_peeps_hp_patch_bb_now(c, tier, force_all_tables); bool any_zero_return = false; bool any_success = false; @@ -2120,6 +2124,13 @@ void send_game_menu_t(std::shared_ptr c, bool is_spectator_team_list, bo (client_has_debug || (l->check_flag(Lobby::Flag::IS_CLIENT_CUSTOMIZATION) == c->check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION))) && (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) == is_spectator_team_list) && (!show_tournaments_only || l->tournament_match)) { + // Brutal Peeps rooms rely on version-specific BattleParam patching. + // BB Brutal rooms are BB-only; PC Brutal rooms are PC V2-only. + if ((l->brutal_peeps_tier >= 1) && + ((l->version_is_allowed(Version::BB_V4) && (c->version() != Version::BB_V4)) || + (l->version_is_allowed(Version::PC_V2) && (c->version() != Version::PC_V2)))) { + continue; + } games.emplace(l); } } @@ -2178,8 +2189,8 @@ void send_game_menu_t(std::shared_ptr c, bool is_spectator_team_list, bo default: throw std::logic_error("invalid game mode"); } - // On v2, render name in orange if v1 is not allowed - if (is_v2(c->version()) && !l->version_is_allowed(Version::DC_V1)) { + // On v2, render name in orange if v1 is not allowed, or if this is a Brutal Peeps room. + if (is_v2(c->version()) && (!l->version_is_allowed(Version::DC_V1) || (l->brutal_peeps_tier >= 1))) { e.flags |= 0x40; } // On BB, gray out games that can't be joined @@ -2187,7 +2198,12 @@ void send_game_menu_t(std::shared_ptr c, bool is_spectator_team_list, bo e.flags |= 0x04; } } - e.name.encode(l->name, c->language()); + + if ((c->version() == Version::BB_V4) && (l->brutal_peeps_tier >= 1)) { + e.name.encode(std::format("B+{} {}", static_cast(l->brutal_peeps_tier), l->name), c->language()); + } else { + e.name.encode(l->name, c->language()); + } } send_command_vt(c, is_spectator_team_list ? 0xE6 : 0x08, entries.size() - 1, entries); diff --git a/src/SendCommands.hh b/src/SendCommands.hh index b09c48cc..a54d3ed9 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -198,7 +198,7 @@ void send_guild_card_header_bb(std::shared_ptr c); void send_guild_card_chunk_bb(std::shared_ptr c, size_t chunk_index); void send_stream_file_index_bb(std::shared_ptr c); void send_stream_file_chunk_bb(std::shared_ptr c, uint32_t chunk_index); -asio::awaitable send_brutal_peeps_hp_patch_bb(std::shared_ptr c, int64_t tier); +asio::awaitable send_brutal_peeps_hp_patch_bb(std::shared_ptr c, int64_t tier, bool force_all_tables = false); void send_approve_player_choice_bb(std::shared_ptr c); void send_complete_player_bb(std::shared_ptr c);