diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index a251ebf4..35ebcbf7 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -2874,7 +2874,8 @@ struct S_TournamentList_Ep3_E0 { // used with the E3 command. See the E3 command for descriptions of what each // flag value means. -struct S_GameInformation_Ep3_E1 { +template +struct S_GameInformationBase_Ep3_E1 { /* 0004 */ pstring game_name; struct PlayerEntry { pstring name; // From disp.name @@ -2882,11 +2883,16 @@ struct S_GameInformation_Ep3_E1 { } __packed__; /* 0024 */ parray player_entries; /* 00E4 */ pstring map_name; - /* 0104 */ Episode3::Rules rules; + /* 0104 */ RulesT rules; /* 0118 */ parray spectator_entries; /* 0298 */ } __packed__; +struct S_GameInformation_Ep3NTE_E1 : S_GameInformationBase_Ep3_E1 { +} __packed__; +struct S_GameInformation_Ep3_E1 : S_GameInformationBase_Ep3_E1 { +} __packed__; + // E1 (S->C): System file created (BB) // This seems to take the place of 00E2 in certain cases. Perhaps it was used // when a client hadn't logged in before and didn't have a system file, so the @@ -2973,11 +2979,12 @@ struct S_TournamentEntryList_Ep3_E2 { // The 00, 01, and 04 cases don't really make sense, because the E1 command is // more appropriate for inspecting non-tournament games. -struct S_TournamentGameDetails_Ep3_E3 { +template +struct S_TournamentGameDetailsBase_Ep3_E3 { // These fields are used only if the Rules pane is shown /* 0004 */ pstring name; /* 0024 */ pstring map_name; - /* 0044 */ Episode3::Rules rules; + /* 0044 */ RulesT rules; // This field is used only if the bracket pane is shown struct BracketEntry { @@ -3009,6 +3016,11 @@ struct S_TournamentGameDetails_Ep3_E3 { /* 0740 */ } __packed__; +struct S_TournamentGameDetails_Ep3NTE_E3 : S_TournamentGameDetailsBase_Ep3_E3 { +} __packed__; +struct S_TournamentGameDetails_Ep3_E3 : S_TournamentGameDetailsBase_Ep3_E3 { +} __packed__; + // E3 (C->S): Player preview request (BB) struct C_PlayerPreviewRequest_BB_E3 { diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index f0ce998a..2135aa38 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -999,7 +999,7 @@ struct RulesTrial { /* 0C */ RulesTrial() = default; - explicit RulesTrial(const Rules&); + RulesTrial(const Rules&); operator Rules() const; } __attribute__((packed)); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index e941b943..2b4763db 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1332,12 +1332,19 @@ static bool start_ep3_battle_table_game_if_ready(shared_ptr l, int16_t ta // Figure out which clients are at this table. If any client has declined, we // never start a match, but we may start a match even if all clients have not // yet accepted (in case of a tournament match). + Version base_version = Version::UNKNOWN; unordered_map> table_clients; bool all_clients_accepted = true; for (const auto& c : l->clients) { if (!c || (c->card_battle_table_number != table_number)) { continue; } + // Prevent match from starting unless all players are on the same version + if (base_version == Version::UNKNOWN) { + base_version = c->version(); + } else if (base_version != c->version()) { + return false; + } if (c->card_battle_table_seat_number >= 4) { throw runtime_error("invalid seat number"); } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index b0ac9e16..0892dbc0 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2775,14 +2775,11 @@ void send_ep3_rank_update(shared_ptr c) { } void send_ep3_card_battle_table_state(shared_ptr l, uint16_t table_number) { - S_CardBattleTableState_Ep3_E4 cmd; - for (size_t z = 0; z < 4; z++) { - cmd.entries[z].state = 0; - cmd.entries[z].unknown_a1 = 0; - cmd.entries[z].guild_card_number = 0; - } + S_CardBattleTableState_Ep3_E4 cmd_nte; + S_CardBattleTableState_Ep3_E4 cmd_final; - set> clients; + set> clients_nte; + set> clients_final; for (const auto& c : l->clients) { if (!c) { continue; @@ -2791,17 +2788,23 @@ void send_ep3_card_battle_table_state(shared_ptr l, uint16_t table_number if (c->card_battle_table_seat_number > 3) { throw runtime_error("invalid battle table seat number"); } - auto& e = cmd.entries[c->card_battle_table_seat_number]; + + bool is_trial = (c->version() == Version::GC_EP3_NTE); + auto& e = is_trial ? cmd_nte.entries[c->card_battle_table_seat_number] : cmd_final.entries[c->card_battle_table_seat_number]; if (e.state == 0) { e.state = c->card_battle_table_seat_state; e.guild_card_number = c->license->serial_number; + auto& clients = is_trial ? clients_nte : clients_final; clients.emplace(c); } } } - for (const auto& c : clients) { - send_command_t(c, 0xE4, table_number, cmd); + for (const auto& c : clients_nte) { + send_command_t(c, 0xE4, table_number, cmd_nte); + } + for (const auto& c : clients_final) { + send_command_t(c, 0xE4, table_number, cmd_final); } } @@ -2911,10 +2914,11 @@ void send_ep3_tournament_entry_list( send_command_t(c, is_for_spectator_team_create ? 0xE7 : 0xE2, z, cmd); } -void send_ep3_tournament_details( +template +void send_ep3_tournament_details_t( shared_ptr c, shared_ptr tourn) { - S_TournamentGameDetails_Ep3_E3 cmd; + S_TournamentGameDetailsBase_Ep3_E3 cmd; auto vm = tourn->get_map()->version(c->language()); cmd.name.encode(tourn->get_name(), c->language()); cmd.map_name.encode(vm->map->name.decode(vm->language), c->language()); @@ -2930,6 +2934,16 @@ void send_ep3_tournament_details( send_command_t(c, 0xE3, 0x02, cmd); } +void send_ep3_tournament_details( + shared_ptr c, + shared_ptr tourn) { + if (c->version() == Version::GC_EP3_NTE) { + send_ep3_tournament_details_t(c, tourn); + } else { + send_ep3_tournament_details_t(c, tourn); + } +} + string ep3_description_for_client(shared_ptr c) { if (!is_ep3(c->version())) { throw runtime_error("client is not Episode 3"); @@ -2942,7 +2956,8 @@ string ep3_description_for_client(shared_ptr c) { char_for_language_code(p->inventory.language)); } -void send_ep3_game_details(shared_ptr c, shared_ptr l) { +template +void send_ep3_game_details_t(shared_ptr c, shared_ptr l) { shared_ptr primary_lobby; if (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) { @@ -2955,7 +2970,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { auto tourn = tourn_match ? tourn_match->tournament.lock() : nullptr; if (tourn) { - S_TournamentGameDetails_Ep3_E3 cmd; + S_TournamentGameDetailsBase_Ep3_E3 cmd; cmd.name.encode(l->name, c->language()); auto vm = tourn->get_map()->version(c->language()); @@ -2974,7 +2989,8 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { if (primary_lobby) { auto serial_number_to_client = primary_lobby->clients_by_serial_number(); - auto describe_team = [&](S_TournamentGameDetails_Ep3_E3::TeamEntry& team_entry, shared_ptr team) -> void { + using TeamEntryT = typename S_TournamentGameDetailsBase_Ep3_E3::TeamEntry; + auto describe_team = [&](TeamEntryT& team_entry, shared_ptr team) -> void { team_entry.team_name.encode(team->name, c->language()); for (size_t z = 0; z < team->players.size(); z++) { auto& entry = team_entry.players[z]; @@ -3014,7 +3030,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { send_command_t(c, 0xE3, flag, cmd); } else { - S_GameInformation_Ep3_E1 cmd; + S_GameInformationBase_Ep3_E1 cmd; cmd.game_name.encode(l->name, c->language()); if (primary_lobby) { size_t num_players = 0; @@ -3043,7 +3059,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { // spectator count in the info window object. To account for this, we send // a mostly-blank E3 to set the spectator count, followed by an E1 with // the correct data. - S_TournamentGameDetails_Ep3_E3 cmd_E3; + S_TournamentGameDetailsBase_Ep3_E3 cmd_E3; cmd_E3.num_spectators = num_spectators; send_command_t(c, 0xE3, 0x04, cmd_E3); @@ -3063,6 +3079,14 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { } } +void send_ep3_game_details(shared_ptr c, shared_ptr l) { + if (c->version() == Version::GC_EP3_NTE) { + send_ep3_game_details_t(c, l); + } else { + send_ep3_game_details_t(c, l); + } +} + template void send_ep3_set_tournament_player_decks_t(shared_ptr c) { auto s = c->require_server_state();