implement E1/E3 commands on NTE

This commit is contained in:
Martin Michelsen
2024-02-08 09:03:15 -08:00
parent dcea0e4520
commit 20f5a92d81
4 changed files with 65 additions and 22 deletions
+16 -4
View File
@@ -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 <typename RulesT>
struct S_GameInformationBase_Ep3_E1 {
/* 0004 */ pstring<TextEncoding::MARKED, 0x20> game_name;
struct PlayerEntry {
pstring<TextEncoding::ASCII, 0x10> name; // From disp.name
@@ -2882,11 +2883,16 @@ struct S_GameInformation_Ep3_E1 {
} __packed__;
/* 0024 */ parray<PlayerEntry, 4> player_entries;
/* 00E4 */ pstring<TextEncoding::MARKED, 0x20> map_name;
/* 0104 */ Episode3::Rules rules;
/* 0104 */ RulesT rules;
/* 0118 */ parray<PlayerEntry, 8> spectator_entries;
/* 0298 */
} __packed__;
struct S_GameInformation_Ep3NTE_E1 : S_GameInformationBase_Ep3_E1<Episode3::RulesTrial> {
} __packed__;
struct S_GameInformation_Ep3_E1 : S_GameInformationBase_Ep3_E1<Episode3::Rules> {
} __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 <typename RulesT>
struct S_TournamentGameDetailsBase_Ep3_E3 {
// These fields are used only if the Rules pane is shown
/* 0004 */ pstring<TextEncoding::MARKED, 0x20> name;
/* 0024 */ pstring<TextEncoding::MARKED, 0x20> 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<Episode3::RulesTrial> {
} __packed__;
struct S_TournamentGameDetails_Ep3_E3 : S_TournamentGameDetailsBase_Ep3_E3<Episode3::Rules> {
} __packed__;
// E3 (C->S): Player preview request (BB)
struct C_PlayerPreviewRequest_BB_E3 {
+1 -1
View File
@@ -999,7 +999,7 @@ struct RulesTrial {
/* 0C */
RulesTrial() = default;
explicit RulesTrial(const Rules&);
RulesTrial(const Rules&);
operator Rules() const;
} __attribute__((packed));
+7
View File
@@ -1332,12 +1332,19 @@ static bool start_ep3_battle_table_game_if_ready(shared_ptr<Lobby> 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<size_t, shared_ptr<Client>> 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");
}
+41 -17
View File
@@ -2775,14 +2775,11 @@ void send_ep3_rank_update(shared_ptr<Client> c) {
}
void send_ep3_card_battle_table_state(shared_ptr<Lobby> 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<shared_ptr<Client>> clients;
set<shared_ptr<Client>> clients_nte;
set<shared_ptr<Client>> clients_final;
for (const auto& c : l->clients) {
if (!c) {
continue;
@@ -2791,17 +2788,23 @@ void send_ep3_card_battle_table_state(shared_ptr<Lobby> 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 <typename RulesT>
void send_ep3_tournament_details_t(
shared_ptr<Client> c,
shared_ptr<const Episode3::Tournament> tourn) {
S_TournamentGameDetails_Ep3_E3 cmd;
S_TournamentGameDetailsBase_Ep3_E3<RulesT> 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<Client> c,
shared_ptr<const Episode3::Tournament> tourn) {
if (c->version() == Version::GC_EP3_NTE) {
send_ep3_tournament_details_t<Episode3::RulesTrial>(c, tourn);
} else {
send_ep3_tournament_details_t<Episode3::Rules>(c, tourn);
}
}
string ep3_description_for_client(shared_ptr<Client> 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<Client> c) {
char_for_language_code(p->inventory.language));
}
void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
template <typename RulesT>
void send_ep3_game_details_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
shared_ptr<Lobby> primary_lobby;
if (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) {
@@ -2955,7 +2970,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
auto tourn = tourn_match ? tourn_match->tournament.lock() : nullptr;
if (tourn) {
S_TournamentGameDetails_Ep3_E3 cmd;
S_TournamentGameDetailsBase_Ep3_E3<RulesT> 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<Client> c, shared_ptr<Lobby> 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<const Episode3::Tournament::Team> team) -> void {
using TeamEntryT = typename S_TournamentGameDetailsBase_Ep3_E3<RulesT>::TeamEntry;
auto describe_team = [&](TeamEntryT& team_entry, shared_ptr<const Episode3::Tournament::Team> 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<Client> c, shared_ptr<Lobby> l) {
send_command_t(c, 0xE3, flag, cmd);
} else {
S_GameInformation_Ep3_E1 cmd;
S_GameInformationBase_Ep3_E1<RulesT> 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<Client> c, shared_ptr<Lobby> 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<RulesT> 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<Client> c, shared_ptr<Lobby> l) {
}
}
void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
if (c->version() == Version::GC_EP3_NTE) {
send_ep3_game_details_t<Episode3::RulesTrial>(c, l);
} else {
send_ep3_game_details_t<Episode3::Rules>(c, l);
}
}
template <typename CmdT>
void send_ep3_set_tournament_player_decks_t(shared_ptr<Client> c) {
auto s = c->require_server_state();