make tournaments work with multiple human players

This commit is contained in:
Martin Michelsen
2022-12-13 21:40:09 -08:00
parent d52b882679
commit 5bcd16b6f2
9 changed files with 354 additions and 187 deletions
+109 -39
View File
@@ -11,6 +11,23 @@ namespace Episode3 {
Tournament::PlayerEntry::PlayerEntry(uint32_t serial_number)
: serial_number(serial_number), com_deck() { }
Tournament::PlayerEntry::PlayerEntry(
shared_ptr<const COMDeckDefinition> com_deck)
: serial_number(0), com_deck(com_deck) { }
bool Tournament::PlayerEntry::is_com() const {
return (this->com_deck != nullptr);
}
bool Tournament::PlayerEntry::is_human() const {
return (this->serial_number != 0);
}
Tournament::Team::Team(
shared_ptr<Tournament> tournament, size_t index, size_t max_players)
: tournament(tournament),
@@ -22,12 +39,21 @@ Tournament::Team::Team(
is_active(true) { }
string Tournament::Team::str() const {
string ret = string_printf("[Team/%zu %s %zu/%zuP name=%s pass=%s rounds=%zu",
size_t num_human_players = 0;
size_t num_com_players = 0;
for (const auto& player : this->players) {
num_human_players += player.is_human();
num_com_players += player.is_com();
}
string ret = string_printf("[Team/%zu %s %zuH/%zuC/%zuP name=%s pass=%s rounds=%zu",
this->index, this->is_active ? "active" : "inactive",
this->player_serial_numbers.size(), this->max_players, this->name.c_str(),
num_human_players, num_com_players, this->max_players, this->name.c_str(),
this->password.c_str(), this->num_rounds_cleared);
for (uint32_t serial_number : this->player_serial_numbers) {
ret += string_printf(" %08" PRIX32, serial_number);
for (const auto& player : this->players) {
if (player.is_human()) {
ret += string_printf(" %08" PRIX32, player.serial_number);
}
}
return ret + "]";
}
@@ -36,7 +62,7 @@ void Tournament::Team::register_player(
uint32_t serial_number,
const string& team_name,
const string& password) {
if (this->player_serial_numbers.size() >= this->max_players) {
if (this->players.size() >= this->max_players) {
throw runtime_error("team is full");
}
@@ -52,10 +78,14 @@ void Tournament::Team::register_player(
throw runtime_error("player already registered in same tournament");
}
if (!this->player_serial_numbers.emplace(serial_number).second) {
throw logic_error("player already registered in team but not in tournament");
for (const auto& player : this->players) {
if (player.is_human() && (player.serial_number == serial_number)) {
throw logic_error("player already registered in team but not in tournament");
}
}
this->players.emplace_back(serial_number);
if (this->name.empty()) {
this->name = team_name;
this->password = password;
@@ -63,8 +93,18 @@ void Tournament::Team::register_player(
}
bool Tournament::Team::unregister_player(uint32_t serial_number) {
if (this->player_serial_numbers.erase(serial_number)) {
if (this->player_serial_numbers.empty()) {
size_t index;
for (index = 0; index < this->players.size(); index++) {
if (this->players[index].is_human() &&
(this->players[index].serial_number == serial_number)) {
break;
}
}
if (index < this->players.size()) {
this->players.erase(this->players.begin() + index);
if (this->players.empty()) {
this->name.clear();
this->password.clear();
}
@@ -108,6 +148,31 @@ bool Tournament::Team::unregister_player(uint32_t serial_number) {
}
}
bool Tournament::Team::has_any_human_players() const {
for (const auto& player : this->players) {
if (player.is_human()) {
return true;
}
}
return false;
}
size_t Tournament::Team::num_human_players() const {
size_t ret = 0;
for (const auto& player : this->players) {
ret += player.is_human();
}
return ret;
}
size_t Tournament::Team::num_com_players() const {
size_t ret = 0;
for (const auto& player : this->players) {
ret += player.is_com();
}
return ret;
}
Tournament::Match::Match(
@@ -139,7 +204,7 @@ string Tournament::Match::str() const {
return string_printf("[Match round=%zu winner=%s]", this->round_num, winner_str.c_str());
}
bool Tournament::Match::resolve_if_no_players() {
bool Tournament::Match::resolve_if_no_human_players() {
if (this->winner_team) {
return true;
}
@@ -148,8 +213,8 @@ bool Tournament::Match::resolve_if_no_players() {
// arbitrarily
if (this->preceding_a->winner_team &&
this->preceding_b->winner_team &&
this->preceding_a->winner_team->player_serial_numbers.empty() &&
this->preceding_b->winner_team->player_serial_numbers.empty()) {
!this->preceding_a->winner_team->has_any_human_players() &&
!this->preceding_b->winner_team->has_any_human_players()) {
this->set_winner_team((random_object<uint8_t>() & 1)
? this->preceding_b->winner_team : this->preceding_a->winner_team);
return true;
@@ -169,7 +234,7 @@ void Tournament::Match::on_winner_team_set() {
// Resolve the following match if possible (this skips CPU-only matches). If
// the following match can't be resolved, mark it pending.
auto following = this->following.lock();
if (following && !following->resolve_if_no_players()) {
if (following && !following->resolve_if_no_human_players()) {
tournament->pending_matches.emplace(following);
}
@@ -278,14 +343,15 @@ void Tournament::init() {
team->name = team_dict.at("name")->as_string();
team->password = team_dict.at("password")->as_string();
team_index_to_rounds_cleared.emplace_back(team_dict.at("num_rounds_cleared")->as_int());
for (const auto& serial_number_json : team_dict.at("player_serial_numbers")->as_list()) {
uint32_t serial_number = serial_number_json->as_int();
team->player_serial_numbers.emplace(serial_number);
this->all_player_serial_numbers.emplace(serial_number);
}
for (const auto& com_deck_name_json : team_dict.at("com_deck_names")->as_list()) {
team->com_decks.emplace_back(this->data_index->com_deck(
com_deck_name_json->as_string()));
for (const auto& player_json : team_dict.at("player_specs")->as_list()) {
if (player_json->is_int()) {
uint32_t serial_number = player_json->as_int();
team->players.emplace_back(serial_number);
this->all_player_serial_numbers.emplace(serial_number);
} else {
team->players.emplace_back(this->data_index->com_deck(
player_json->as_string()));
}
}
}
this->num_teams = this->teams.size();
@@ -410,16 +476,15 @@ std::shared_ptr<JSONObject> Tournament::json() const {
for (auto team : this->teams) {
unordered_map<string, shared_ptr<JSONObject>> team_dict;
team_dict.emplace("max_players", make_json_int(team->max_players));
vector<shared_ptr<JSONObject>> player_serial_numbers_list;
for (uint32_t player_serial_number : team->player_serial_numbers) {
player_serial_numbers_list.emplace_back(make_json_int(player_serial_number));
vector<shared_ptr<JSONObject>> player_jsons_list;
for (const auto& player : team->players) {
if (player.is_human()) {
player_jsons_list.emplace_back(make_json_int(player.serial_number));
} else {
player_jsons_list.emplace_back(make_json_str(player.com_deck->deck_name));
}
}
team_dict.emplace("player_serial_numbers", make_json_list(move(player_serial_numbers_list)));
vector<shared_ptr<JSONObject>> com_deck_names_list;
for (auto com_deck : team->com_decks) {
com_deck_names_list.emplace_back(make_json_str(com_deck->deck_name));
}
team_dict.emplace("com_deck_names", make_json_list(move(com_deck_names_list)));
team_dict.emplace("player_specs", make_json_list(move(player_jsons_list)));
team_dict.emplace("name", make_json_str(team->name));
team_dict.emplace("password", make_json_str(team->password));
team_dict.emplace("num_rounds_cleared", make_json_int(team->num_rounds_cleared));
@@ -503,13 +568,11 @@ shared_ptr<Tournament::Team> Tournament::team_for_serial_number(
}
for (auto team : this->teams) {
if (!team->player_serial_numbers.count(serial_number)) {
continue;
for (const auto& player : team->players) {
if (player.serial_number == serial_number) {
return team->is_active ? team : nullptr;
}
}
if (!team->is_active) {
return nullptr;
}
return team;
}
throw logic_error("serial number registered in tournament but not in any team");
@@ -533,11 +596,18 @@ void Tournament::start() {
if (t->name.empty()) {
t->name = string_printf("COM:%zu", z);
}
if (this->data_index->num_com_decks() < t->max_players - t->player_serial_numbers.size()) {
for (const auto& player : t->players) {
if (player.is_com()) {
throw logic_error("non-human player on team before tournament start");
}
}
if (this->data_index->num_com_decks() < t->max_players - t->players.size()) {
throw runtime_error("not enough COM decks to complete team");
}
while (t->player_serial_numbers.size() + t->com_decks.size() < t->max_players) {
t->com_decks.emplace_back(this->data_index->random_com_deck());
// TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the same
// team
while (t->players.size() < t->max_players) {
t->players.emplace_back(this->data_index->random_com_deck());
}
}
+20 -3
View File
@@ -28,12 +28,25 @@ public:
COMPLETE,
};
struct PlayerEntry {
// Invariant: (serial_number == 0) != (com_deck == nullptr)
// (that is, exactly one of the following must be valid)
uint32_t serial_number;
std::shared_ptr<const COMDeckDefinition> com_deck;
explicit PlayerEntry(uint32_t serial_number);
explicit PlayerEntry(std::shared_ptr<const COMDeckDefinition> com_deck);
bool is_com() const;
bool is_human() const;
};
struct Team : public std::enable_shared_from_this<Team> {
std::weak_ptr<Tournament> tournament;
size_t index;
size_t max_players;
std::set<uint32_t> player_serial_numbers;
std::vector<std::shared_ptr<const COMDeckDefinition>> com_decks;
std::vector<PlayerEntry> players;
std::string name;
std::string password;
size_t num_rounds_cleared;
@@ -50,6 +63,10 @@ public:
const std::string& team_name,
const std::string& password);
bool unregister_player(uint32_t serial_number);
bool has_any_human_players() const;
size_t num_human_players() const;
size_t num_com_players() const;
};
struct Match : public std::enable_shared_from_this<Match> {
@@ -69,7 +86,7 @@ public:
std::shared_ptr<Team> winner_team);
std::string str() const;
bool resolve_if_no_players();
bool resolve_if_no_human_players();
void on_winner_team_set();
void set_winner_team(std::shared_ptr<Team> team);
void set_winner_team_without_triggers(std::shared_ptr<Team> team);
+32 -15
View File
@@ -71,10 +71,18 @@ size_t Lobby::count_clients() const {
return ret;
}
void Lobby::add_client(shared_ptr<Client> c) {
void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
ssize_t index;
ssize_t min_client_id = (this->flags & Lobby::Flag::IS_SPECTATOR_TEAM) ? 4 : 0;
if (c->prefer_high_lobby_client_id) {
if (required_client_id >= 0) {
if (this->clients[required_client_id].get()) {
throw out_of_range("required slot is in use");
}
this->clients[required_client_id] = c;
index = required_client_id;
} else if (c->prefer_high_lobby_client_id) {
for (index = max_clients - 1; index >= min_client_id; index--) {
if (!this->clients[index].get()) {
this->clients[index] = c;
@@ -100,16 +108,15 @@ void Lobby::add_client(shared_ptr<Client> c) {
c->lobby_id = this->lobby_id;
// If there's no one else in the lobby, set the leader id as well
if (index == (max_clients - 1) * c->prefer_high_lobby_client_id) {
for (index = 0; index < max_clients; index++) {
if (this->clients[index].get() && this->clients[index] != c) {
break;
}
}
if (index >= max_clients) {
this->leader_id = c->lobby_client_id;
size_t leader_index;
for (leader_index = 0; leader_index < max_clients; leader_index++) {
if (this->clients[leader_index] && (this->clients[leader_index] != c)) {
break;
}
}
if (leader_index >= max_clients) {
this->leader_id = c->lobby_client_id;
}
// If the lobby is a game and item tracking is enabled, assign the inventory's
// item IDs
@@ -184,18 +191,28 @@ void Lobby::remove_client(shared_ptr<Client> c) {
}
}
void Lobby::move_client_to_lobby(shared_ptr<Lobby> dest_lobby,
shared_ptr<Client> c) {
void Lobby::move_client_to_lobby(
shared_ptr<Lobby> dest_lobby,
shared_ptr<Client> c,
ssize_t required_client_id) {
if (dest_lobby.get() == this) {
return;
}
if (dest_lobby->count_clients() >= dest_lobby->max_clients) {
throw out_of_range("no space left in lobby");
if (required_client_id >= 0) {
if (dest_lobby->clients[required_client_id]) {
throw out_of_range("required slot is in use");
}
} else {
ssize_t min_client_id = (this->flags & Lobby::Flag::IS_SPECTATOR_TEAM) ? 4 : 0;
size_t available_slots = dest_lobby->max_clients - min_client_id;
if (dest_lobby->count_clients() >= available_slots) {
throw out_of_range("no space left in lobby");
}
}
this->remove_client(c);
dest_lobby->add_client(c);
dest_lobby->add_client(c, required_client_id);
}
+7 -3
View File
@@ -106,6 +106,8 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
uint32_t flags;
std::shared_ptr<const Quest> loading_quest;
std::array<std::shared_ptr<Client>, 12> clients;
// Keys in this map are client_id
std::unordered_map<size_t, std::weak_ptr<Client>> tournament_clients_to_add;
explicit Lobby(uint32_t id);
@@ -117,11 +119,13 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
size_t count_clients() const;
bool any_client_loading() const;
void add_client(std::shared_ptr<Client> c);
void add_client(std::shared_ptr<Client> c, ssize_t required_client_id = -1);
void remove_client(std::shared_ptr<Client> c);
void move_client_to_lobby(std::shared_ptr<Lobby> dest_lobby,
std::shared_ptr<Client> c);
void move_client_to_lobby(
std::shared_ptr<Lobby> dest_lobby,
std::shared_ptr<Client> c,
ssize_t required_client_id = -1);
std::shared_ptr<Client> find_client(
const std::u16string* identifier = nullptr,
+130 -64
View File
@@ -875,54 +875,33 @@ static void on_ep3_meseta_transaction(shared_ptr<ServerState>,
send_command(c, command, 0x03, &out_cmd, sizeof(out_cmd));
}
static bool start_ep3_tournament_match_if_pending(
shared_ptr<ServerState> s,
shared_ptr<Lobby> l,
shared_ptr<Client> c,
int16_t table_number) {
auto team = c->ep3_tournament_team.lock();
if (!team) {
return false; // Client is not registered in a tournament
static bool add_next_tournament_match_client(
shared_ptr<ServerState> s, shared_ptr<Lobby> l) {
if (!l->tournament_match) {
return false;
}
auto tourn = team->tournament.lock();
auto tourn = l->tournament_match->tournament.lock();
if (!tourn) {
return false; // The tournament has been canceled
}
auto match = tourn->next_match_for_team(team);
if (!match) {
return false;
}
auto other_team = match->opponent_team_for_team(team);
unordered_set<uint32_t> required_serial_numbers;
for (uint32_t serial_number : team->player_serial_numbers) {
required_serial_numbers.emplace(serial_number);
auto it = l->tournament_clients_to_add.begin();
if (it == l->tournament_clients_to_add.end()) {
return false;
}
for (uint32_t serial_number : other_team->player_serial_numbers) {
required_serial_numbers.emplace(serial_number);
}
unordered_set<shared_ptr<Client>> game_clients;
for (const auto& other_c : l->clients) {
if (!other_c) {
continue;
}
if ((other_c->card_battle_table_number == table_number) &&
required_serial_numbers.erase(other_c->license->serial_number)) {
game_clients.emplace(other_c);
}
}
if (!required_serial_numbers.empty()) {
size_t target_client_id = it->first;
shared_ptr<Client> c = it->second.lock();
l->tournament_clients_to_add.erase(it);
// If the client has disconnected before they could join the match, disband
// the entire game
if (!c) {
send_command(l, 0xED, 0x00);
return false;
}
// At this point, we've checked all the necessary conditions for a tournament
// match to begin.
for (const auto& other_c : l->clients) {
if (other_c && (other_c->card_battle_table_number == table_number)) {
other_c->card_battle_table_number = -1;
other_c->card_battle_table_seat_number = 0;
}
if (l->clients[target_client_id] != nullptr) {
throw logic_error("client id is already in use");
}
G_SetStateFlags_GC_Ep3_6xB4x03 state_cmd;
@@ -955,8 +934,8 @@ static bool start_ep3_tournament_match_if_pending(
static const std::pair<uint16_t, uint16_t> final_lose_entries[10] = {
{1, -5}, {-1, -10}, {-3, -15}, {-7, -20}, {-15, -20}, {-20, -25}, {-30, -30}, {-40, -30}, {-50, -34}, {0, -40}};
G_SetEXResultValues_GC_Ep3_6xB4x4B ex_cmd;
const auto& win_entries = (match == tourn->get_final_match()) ? final_win_entries : non_final_win_entries;
const auto& lose_entries = (match == tourn->get_final_match()) ? final_lose_entries : non_final_lose_entries;
const auto& win_entries = (l->tournament_match == tourn->get_final_match()) ? final_win_entries : non_final_win_entries;
const auto& lose_entries = (l->tournament_match == tourn->get_final_match()) ? final_lose_entries : non_final_lose_entries;
for (size_t z = 0; z < 10; z++) {
ex_cmd.win_entries[z].threshold = win_entries[z].first;
ex_cmd.win_entries[z].value = win_entries[z].second;
@@ -968,17 +947,106 @@ static bool start_ep3_tournament_match_if_pending(
set_mask_for_ep3_game_command(&ex_cmd, sizeof(ex_cmd), mask_key);
}
// TODO: We don't know if this works with multiple players. Test it.
send_command_t(c, 0xC9, 0x00, state_cmd);
send_command_t(c, 0xC9, 0x00, ex_cmd);
s->change_client_lobby(c, l, true, target_client_id);
c->flags |= Client::Flag::LOADING;
return true;
}
static bool start_ep3_tournament_match_if_pending(
shared_ptr<ServerState> s,
shared_ptr<Lobby> l,
shared_ptr<Client> c,
int16_t table_number) {
auto team = c->ep3_tournament_team.lock();
if (!team) {
return false; // Client is not registered in a tournament
}
auto tourn = team->tournament.lock();
if (!tourn) {
return false; // The tournament has been canceled
}
auto match = tourn->next_match_for_team(team);
if (!match) {
return false;
}
auto other_team = match->opponent_team_for_team(team);
vector<uint32_t> required_serial_numbers;
required_serial_numbers.resize(4, 0);
auto add_team_players = [&](shared_ptr<const Episode3::Tournament::Team> team, size_t base_index) -> void {
size_t z = 0;
for (const auto& player : team->players) {
if (z >= 2) {
throw logic_error("more than 2 players on team");
}
if (player.is_human()) {
required_serial_numbers.at(base_index + z) = player.serial_number;
}
z++;
}
};
add_team_players(team, 0);
add_team_players(other_team, 2);
auto clients_map = l->clients_by_serial_number();
vector<shared_ptr<Client>> game_clients;
game_clients.resize(4);
for (size_t z = 0; z < required_serial_numbers.size(); z++) {
uint32_t serial_number = required_serial_numbers[z];
if (!serial_number) {
continue;
}
shared_ptr<Client> player_c;
try {
player_c = clients_map.at(serial_number);
} catch (const out_of_range&) {
return false;
}
if (player_c->card_battle_table_number != table_number) {
return false;
}
game_clients.at(z) = player_c;
}
// If there is already a game for this match, do nothing (the player is
// probably about to be pulled into it, when another player is done loading)
for (auto l : s->all_lobbies()) {
if (l->tournament_match == match) {
return false;
}
}
// At this point, we've checked all the necessary conditions for a tournament
// match to begin.
for (const auto& other_c : game_clients) {
if (!other_c) {
continue;
}
other_c->card_battle_table_number = -1;
other_c->card_battle_table_seat_number = 0;
send_self_leave_notification(other_c);
string message = string_printf(
"$C7Waiting to begin match in tournament\n$C6%s$C7...\n\n(Hold B+X+START to abort)",
tourn->get_name().c_str());
send_message_box(other_c, decode_sjis(message));
}
uint32_t flags = Lobby::Flag::NON_V1_ONLY | Lobby::Flag::EPISODE_3_ONLY;
auto game = create_game_generic(s, c, decode_sjis(tourn->get_name()), u"", 0xFF, 0, flags);
game->tournament_match = match;
for (auto game_c : game_clients) {
send_command_t(game_c, 0xC9, 0x00, state_cmd);
send_command_t(game_c, 0xC9, 0x00, ex_cmd);
s->change_client_lobby(game_c, game, false);
send_join_lobby(game_c, game);
game_c->flags |= Client::Flag::LOADING;
game->tournament_clients_to_add.clear();
for (size_t z = 0; z < game_clients.size(); z++) {
if (game_clients[z]) {
game->tournament_clients_to_add.emplace(z, game_clients[z]);
}
}
add_next_tournament_match_client(s, game);
return true;
}
@@ -1074,7 +1142,7 @@ static void on_tournament_bracket_updated(
if (tourn->get_state() == Episode3::Tournament::State::COMPLETE) {
auto team = tourn->get_winner_team();
if (team->player_serial_numbers.empty()) {
if (!team->has_any_human_players()) {
send_ep3_text_message_printf(s, "$C7A CPU team won\nthe tournament\n$C6%s", tourn->get_name().c_str());
} else {
send_ep3_text_message_printf(s, "$C6%s$C7\nwon the tournament\n$C6%s", team->name.c_str(), tourn->get_name().c_str());
@@ -1426,21 +1494,14 @@ static void on_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Clie
if (tourn) {
auto team = tourn->get_team(team_index);
if (team) {
string message;
if (team->name.empty()) {
message = string_printf("$C7(Unnamed team)\n%zu/%zu players\n%zu wins\n%s",
team->player_serial_numbers.size(),
team->max_players,
team->num_rounds_cleared,
team->password.empty() ? "" : "Locked");
} else {
message = string_printf("$C6%s$C7\n%zu/%zu players\n%zu wins\n%s",
team->name.c_str(),
team->player_serial_numbers.size(),
team->max_players,
team->num_rounds_cleared,
team->password.empty() ? "" : "Locked");
}
string team_name = team->name.empty() ? "(Unnamed team)" : team->name;
string message = string_printf("$C7(Unnamed team)\n%zuH/%zuC/%zu players\n%zu %s\n%s",
team->num_human_players(),
team->num_com_players(),
team->max_players,
team->num_rounds_cleared,
team->num_rounds_cleared == 1 ? "win" : "wins",
team->password.empty() ? "" : "Locked");
send_ship_info(c, decode_sjis(message));
} else {
send_ship_info(c, u"$C7No such team");
@@ -3083,12 +3144,17 @@ static void on_client_ready(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_get_player_info(c);
}
// Handle initial commands for spectator teams
auto watched_lobby = l->watched_lobby.lock();
if (l->battle_player && (l->flags & Lobby::Flag::START_BATTLE_PLAYER_IMMEDIATELY)) {
l->battle_player->start();
} else if (watched_lobby && watched_lobby->ep3_server_base) {
watched_lobby->ep3_server_base->server->send_commands_for_joining_spectator(c->channel);
}
// If this is a tournament match and not all players are present, try to bring
// in the next player
add_next_tournament_match_client(s, l);
}
+38 -55
View File
@@ -2021,7 +2021,7 @@ void send_ep3_tournament_entry_list(
entry.state = 2;
} else if (team->name.empty()) {
entry.state = 0;
} else if (team->player_serial_numbers.size() < team->max_players) {
} else if (team->players.size() < team->max_players) {
entry.state = 1;
} else {
entry.state = 2;
@@ -2093,19 +2093,19 @@ 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_GC_Ep3_E3::TeamEntry& entry, shared_ptr<const Episode3::Tournament::Team> team) -> void {
entry.team_name = team->name;
size_t z = 0;
for (uint32_t serial_number : team->player_serial_numbers) {
auto c = serial_number_to_client.at(serial_number);
auto& player_entry = entry.players[z++];
player_entry.name = c->game_data.player()->disp.name;
player_entry.description = ep3_description_for_client(c);
}
for (auto com_deck : team->com_decks) {
auto& player_entry = entry.players[z++];
player_entry.name = com_deck->player_name;
player_entry.description = "Deck: " + com_deck->deck_name;
auto describe_team = [&](S_TournamentGameDetails_GC_Ep3_E3::TeamEntry& team_entry, shared_ptr<const Episode3::Tournament::Team> team) -> void {
team_entry.team_name = team->name;
for (size_t z = 0; z < team->players.size(); z++) {
auto& entry = team_entry.players[z];
const auto& player = team->players[z];
if (player.is_human()) {
auto c = serial_number_to_client.at(player.serial_number);
entry.name = c->game_data.player()->disp.name;
entry.description = ep3_description_for_client(c);
} else {
entry.name = player.com_deck->player_name;
entry.description = "Deck: " + player.com_deck->deck_name;
}
}
};
describe_team(cmd.team_entries[0], tourn_match->preceding_a->winner_team);
@@ -2195,43 +2195,27 @@ void send_ep3_set_tournament_player_decks(
auto serial_number_to_client = l->clients_by_serial_number();
size_t z = 0;
auto add_entries_for_team = [&](shared_ptr<const Episode3::Tournament::Team> team) -> void {
for (uint32_t player_serial_number : team->player_serial_numbers) {
auto& entry = cmd.entries[z];
entry.type = 1; // Human
entry.player_name = serial_number_to_client.at(player_serial_number)->game_data.player()->disp.name;
entry.unknown_a2 = 6;
if (player_serial_number == c->license->serial_number) {
cmd.player_slot = z;
auto add_entries_for_team = [&](shared_ptr<const Episode3::Tournament::Team> team, size_t base_index) -> void {
for (size_t z = 0; z < team->players.size(); z++) {
auto& entry = cmd.entries[base_index + z];
const auto& player = team->players[z];
if (player.is_human()) {
entry.type = 1; // Human
entry.player_name = serial_number_to_client.at(player.serial_number)->game_data.player()->disp.name;
if (player.serial_number == c->license->serial_number) {
cmd.player_slot = base_index + z;
}
} else {
entry.type = 2; // COM
entry.player_name = player.com_deck->player_name;
entry.deck_name = player.com_deck->deck_name;
entry.card_ids = player.com_deck->card_ids;
}
z++;
}
for (auto com_deck : team->com_decks) {
auto& entry = cmd.entries[z];
entry.type = 2; // COM
entry.player_name = com_deck->player_name;
entry.deck_name = com_deck->deck_name;
entry.card_ids = com_deck->card_ids;
entry.unknown_a2 = 6;
z++;
}
};
add_entries_for_team(match->preceding_a->winner_team);
if (z < 1) {
throw logic_error("no entries from preceding team A");
}
if (z > 2) {
throw logic_error("too many entries from preceding team A");
}
z = 2;
add_entries_for_team(match->preceding_b->winner_team);
if (z < 3) {
throw logic_error("no entries from preceding team B");
}
if (z > 4) {
throw logic_error("too many entries from preceding team B");
}
add_entries_for_team(match->preceding_a->winner_team, 0);
add_entries_for_team(match->preceding_b->winner_team, 2);
if (!(tourn->get_data_index()->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
@@ -2258,14 +2242,13 @@ void send_ep3_tournament_match_result(
auto serial_number_to_client = l->clients_by_serial_number();
auto write_player_names = [&](G_TournamentMatchResult_GC_Ep3_6xB4x51::NamesEntry& entry, shared_ptr<const Episode3::Tournament::Team> team) -> void {
size_t z = 0;
for (uint32_t player_serial_number : team->player_serial_numbers) {
entry.player_names[z] = serial_number_to_client.at(player_serial_number)->game_data.player()->disp.name;
z++;
}
for (auto com_deck : team->com_decks) {
entry.player_names[z] = com_deck->player_name;
z++;
for (size_t z = 0; z < team->players.size(); z++) {
const auto& player = team->players[z];
if (player.is_human()) {
entry.player_names[z] = serial_number_to_client.at(player.serial_number)->game_data.player()->disp.name;
} else {
entry.player_names[z] = player.com_deck->player_name;
}
}
};
+6 -3
View File
@@ -121,15 +121,18 @@ void ServerState::remove_client_from_lobby(shared_ptr<Client> c) {
}
bool ServerState::change_client_lobby(
shared_ptr<Client> c, shared_ptr<Lobby> new_lobby, bool send_join_notification) {
shared_ptr<Client> c,
shared_ptr<Lobby> new_lobby,
bool send_join_notification,
ssize_t required_client_id) {
uint8_t old_lobby_client_id = c->lobby_client_id;
shared_ptr<Lobby> current_lobby = this->find_lobby(c->lobby_id);
try {
if (current_lobby) {
current_lobby->move_client_to_lobby(new_lobby, c);
current_lobby->move_client_to_lobby(new_lobby, c, required_client_id);
} else {
new_lobby->add_client(c);
new_lobby->add_client(c, required_client_id);
}
} catch (const out_of_range&) {
return false;
+5 -2
View File
@@ -118,8 +118,11 @@ struct ServerState {
void add_client_to_available_lobby(std::shared_ptr<Client> c);
void remove_client_from_lobby(std::shared_ptr<Client> c);
bool change_client_lobby(std::shared_ptr<Client> c,
std::shared_ptr<Lobby> new_lobby, bool send_join_notification = true);
bool change_client_lobby(
std::shared_ptr<Client> c,
std::shared_ptr<Lobby> new_lobby,
bool send_join_notification = true,
ssize_t required_client_id = -1);
void send_lobby_join_notifications(std::shared_ptr<Lobby> l,
std::shared_ptr<Client> joining_client);