keep tournament state consistent on clients

This commit is contained in:
Martin Michelsen
2023-09-15 22:09:31 -07:00
parent 5caa21bccb
commit 1d45c18ce8
11 changed files with 404 additions and 161 deletions
+158 -89
View File
@@ -10,8 +10,11 @@ using namespace std;
namespace Episode3 {
Tournament::PlayerEntry::PlayerEntry(uint32_t serial_number)
: serial_number(serial_number),
com_deck() {}
: serial_number(serial_number) {}
Tournament::PlayerEntry::PlayerEntry(shared_ptr<Client> c)
: serial_number(c->license->serial_number),
client(c) {}
Tournament::PlayerEntry::PlayerEntry(
shared_ptr<const COMDeckDefinition> com_deck)
@@ -57,7 +60,7 @@ string Tournament::Team::str() const {
}
void Tournament::Team::register_player(
uint32_t serial_number,
shared_ptr<Client> c,
const string& team_name,
const string& password) {
if (this->players.size() >= this->max_players) {
@@ -72,17 +75,17 @@ void Tournament::Team::register_player(
if (!tournament) {
throw runtime_error("tournament has been deleted");
}
if (!tournament->all_player_serial_numbers.emplace(serial_number).second) {
if (!tournament->all_player_serial_numbers.emplace(c->license->serial_number).second) {
throw runtime_error("player already registered in same tournament");
}
for (const auto& player : this->players) {
if (player.is_human() && (player.serial_number == serial_number)) {
if (player.is_human() && (player.serial_number == c->license->serial_number)) {
throw logic_error("player already registered in team but not in tournament");
}
}
this->players.emplace_back(serial_number);
this->players.emplace_back(c);
if (this->name.empty()) {
this->name = team_name;
@@ -290,24 +293,23 @@ shared_ptr<Tournament::Team> Tournament::Match::opponent_team_for_team(
Tournament::Tournament(
shared_ptr<const MapIndex> map_index,
shared_ptr<const COMDeckIndex> com_deck_index,
uint8_t number,
const string& name,
shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules,
size_t num_teams,
bool is_2v2,
bool has_com_teams)
: log(string_printf("[Tournament/%02hhX] ", number)),
: log(string_printf("[Tournament/%s] ", name.c_str())),
map_index(map_index),
com_deck_index(com_deck_index),
number(number),
name(name),
map(map),
rules(rules),
num_teams(num_teams),
is_2v2(is_2v2),
has_com_teams(has_com_teams),
current_state(State::REGISTRATION) {
current_state(State::REGISTRATION),
menu_item_id(0xFFFFFFFF) {
if (this->num_teams < 4) {
throw invalid_argument("team count must be 4 or more");
}
@@ -322,13 +324,11 @@ Tournament::Tournament(
Tournament::Tournament(
shared_ptr<const MapIndex> map_index,
shared_ptr<const COMDeckIndex> com_deck_index,
uint8_t number,
const JSON& json)
: log(string_printf("[Tournament/%02hhX] ", number)),
: log(string_printf("[Tournament/%s] ", json.get_string("name").c_str())),
map_index(map_index),
com_deck_index(com_deck_index),
source_json(json),
number(number),
current_state(State::REGISTRATION) {}
void Tournament::init() {
@@ -380,27 +380,27 @@ void Tournament::init() {
this->shared_from_this(), this->teams[this->zero_round_matches.size()]));
}
// Create the bracket matches
vector<shared_ptr<Match>> current_round_matches = this->zero_round_matches;
while (current_round_matches.size() > 1) {
vector<shared_ptr<Match>> next_round_matches;
for (size_t z = 0; z < current_round_matches.size(); z += 2) {
auto m = make_shared<Match>(
this->shared_from_this(),
current_round_matches[z],
current_round_matches[z + 1]);
current_round_matches[z]->following = m;
current_round_matches[z + 1]->following = m;
next_round_matches.emplace_back(std::move(m));
}
current_round_matches = std::move(next_round_matches);
}
this->final_match = current_round_matches.at(0);
// Compute the match state from the teams' states
if (is_registration_complete) {
this->current_state = State::IN_PROGRESS;
// Create the bracket matches
vector<shared_ptr<Match>> current_round_matches = this->zero_round_matches;
while (current_round_matches.size() > 1) {
vector<shared_ptr<Match>> next_round_matches;
for (size_t z = 0; z < current_round_matches.size(); z += 2) {
auto m = make_shared<Match>(
this->shared_from_this(),
current_round_matches[z],
current_round_matches[z + 1]);
current_round_matches[z]->following = m;
current_round_matches[z + 1]->following = m;
next_round_matches.emplace_back(std::move(m));
}
current_round_matches = std::move(next_round_matches);
}
this->final_match = current_round_matches.at(0);
// Start with all first-round matches in the match queue
unordered_set<shared_ptr<Match>> match_queue;
for (auto match : this->zero_round_matches) {
@@ -501,6 +501,9 @@ shared_ptr<Tournament::Team> Tournament::get_winner_team() const {
if (this->current_state != State::COMPLETE) {
return nullptr;
}
if (!this->final_match) {
throw logic_error("tournament is complete but final match is missing");
}
if (!this->final_match->winner_team) {
throw logic_error("tournament is complete but winner is not set");
}
@@ -604,6 +607,23 @@ void Tournament::start() {
}
}
void Tournament::send_all_state_updates(shared_ptr<ServerState> s) const {
for (const auto& team : this->teams) {
for (const auto& player : team->players) {
auto c = player.client.lock();
// Note: The last check here is to make sure the client is still linked
// with this instance of the tournament - an intervening shell command
// `reload ep3` could have changed the client's linkage
if (c &&
(c->flags & Client::Flag::IS_EPISODE_3) &&
!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) &&
(c->ep3_tournament_team.lock() == team)) {
send_ep3_confirm_tournament_entry(s, c, this->shared_from_this());
}
}
}
}
void Tournament::print_bracket(FILE* stream) const {
function<void(shared_ptr<Match>, size_t)> print_match = [&](shared_ptr<Match> m, size_t indent_level) -> void {
for (size_t z = 0; z < indent_level; z++) {
@@ -619,7 +639,7 @@ void Tournament::print_bracket(FILE* stream) const {
print_match(m->preceding_b, indent_level + 1);
}
};
fprintf(stream, "Tournament %02hhX: %s\n", this->number, this->name.c_str());
fprintf(stream, "Tournament \"%s\"\n", this->name.c_str());
string map_name = this->map->map.name;
fprintf(stream, " Map: %08" PRIX32 " (%s)\n", this->map->map.map_number.load(), map_name.c_str());
string rules_str = this->rules.str();
@@ -639,8 +659,10 @@ void Tournament::print_bracket(FILE* stream) const {
fprintf(stream, " State: UNKNOWN\n");
break;
}
fprintf(stream, " Standings:\n");
print_match(this->final_match, 2);
if (this->final_match) {
fprintf(stream, " Standings:\n");
print_match(this->final_match, 2);
}
fprintf(stream, " Pending matches:\n");
for (const auto& match : this->pending_matches) {
string match_str = match->str();
@@ -667,14 +689,38 @@ TournamentIndex::TournamentIndex(
json = JSON::list();
}
if (json.size() > 0x20) {
throw runtime_error("tournament JSON list length is incorrect");
}
for (size_t z = 0; z < min<size_t>(json.size(), 0x20); z++) {
if (!json.at(z).is_null()) {
this->tournaments[z].reset(new Tournament(this->map_index, this->com_deck_index, z, json.at(z)));
this->tournaments[z]->init();
if (json.is_list()) {
if (json.size() > 0x20) {
throw runtime_error("tournament JSON list length is incorrect");
}
for (size_t z = 0; z < min<size_t>(json.size(), 0x20); z++) {
if (!json.at(z).is_null()) {
shared_ptr<Tournament> tourn(new Tournament(this->map_index, this->com_deck_index, json.at(z)));
tourn->init();
if (!this->name_to_tournament.emplace(tourn->get_name(), tourn).second) {
throw runtime_error("multiple tournaments have the same name: " + tourn->get_name());
}
tourn->set_menu_item_id(this->menu_item_id_to_tournament.size());
this->menu_item_id_to_tournament.emplace_back(tourn);
}
}
} else if (json.is_dict()) {
if (json.size() > 0x20) {
throw runtime_error("tournament JSON dict length is incorrect");
}
for (const auto& it : json.as_dict()) {
shared_ptr<Tournament> tourn(new Tournament(this->map_index, this->com_deck_index, *it.second));
tourn->init();
if (!this->name_to_tournament.emplace(tourn->get_name(), tourn).second) {
// This is logic_error instead of runtime_error because JSON dicts are
// supposed to already have unique keys
throw logic_error("multiple tournaments have the same name: " + tourn->get_name());
}
tourn->set_menu_item_id(this->menu_item_id_to_tournament.size());
this->menu_item_id_to_tournament.emplace_back(tourn);
}
} else {
throw runtime_error("tournament state root JSON is not a list or dict");
}
}
@@ -683,25 +729,11 @@ void TournamentIndex::save() const {
return;
}
auto list = JSON::list();
for (size_t z = 0; z < 0x20; z++) {
if (this->tournaments[z]) {
list.emplace_back(this->tournaments[z]->json());
} else {
list.emplace_back(nullptr);
}
auto json = JSON::dict();
for (const auto& it : this->name_to_tournament) {
json.emplace(it.second->get_name(), it.second->json());
}
save_file(this->state_filename, list.serialize(JSON::SerializeOption::FORMAT));
}
vector<shared_ptr<Tournament>> TournamentIndex::all_tournaments() const {
vector<shared_ptr<Tournament>> ret;
for (size_t z = 0; z < 0x20; z++) {
if (this->tournaments[z]) {
ret.emplace_back(this->tournaments[z]);
}
}
return ret;
save_file(this->state_filename, json.serialize(JSON::SerializeOption::FORMAT));
}
shared_ptr<Tournament> TournamentIndex::create_tournament(
@@ -711,48 +743,52 @@ shared_ptr<Tournament> TournamentIndex::create_tournament(
size_t num_teams,
bool is_2v2,
bool has_com_teams) {
// Find an unused tournament number
uint8_t number;
for (number = 0; number < 0x20; number++) {
if (!this->tournaments[number]) {
break;
}
}
if (number >= 0x20) {
throw runtime_error("all tournament slots are full");
if (this->name_to_tournament.size() >= 0x20) {
throw runtime_error("there can be at most 32 tournaments at a time");
}
auto t = make_shared<Tournament>(
this->map_index, this->com_deck_index, number, name, map, rules, num_teams, is_2v2, has_com_teams);
this->map_index, this->com_deck_index, name, map, rules, num_teams, is_2v2, has_com_teams);
t->init();
this->tournaments[number] = t;
this->name_to_tournament.emplace(t->get_name(), t);
size_t z;
for (z = 0; z < this->menu_item_id_to_tournament.size(); z++) {
if (!this->menu_item_id_to_tournament[z]) {
t->set_menu_item_id(z);
this->menu_item_id_to_tournament[z] = t;
break;
}
}
if (z == this->menu_item_id_to_tournament.size()) {
t->set_menu_item_id(this->menu_item_id_to_tournament.size());
this->menu_item_id_to_tournament.emplace_back(t);
}
this->save();
return t;
}
void TournamentIndex::delete_tournament(uint8_t number) {
this->tournaments[number].reset();
}
shared_ptr<Tournament> TournamentIndex::get_tournament(uint8_t number) const {
return this->tournaments[number];
}
shared_ptr<Tournament> TournamentIndex::get_tournament(const string& name) const {
for (size_t z = 0; z < 0x20; z++) {
if (this->tournaments[z] && (this->tournaments[z]->get_name() == name)) {
return this->tournaments[z];
bool TournamentIndex::delete_tournament(const string& name) {
auto it = this->name_to_tournament.find(name);
if (it == this->name_to_tournament.end()) {
return false;
}
for (size_t z = 0; z < this->menu_item_id_to_tournament.size(); z++) {
if (this->menu_item_id_to_tournament[z] == it->second) {
this->menu_item_id_to_tournament[z] = nullptr;
it->second->set_menu_item_id(0xFFFFFFFF);
}
}
return nullptr;
this->name_to_tournament.erase(it);
this->save();
return true;
}
shared_ptr<Tournament::Team> TournamentIndex::team_for_serial_number(
uint32_t serial_number) const {
for (size_t z = 0; z < 0x20; z++) {
if (!this->tournaments[z]) {
continue;
}
auto team = this->tournaments[z]->team_for_serial_number(serial_number);
shared_ptr<Tournament::Team> TournamentIndex::team_for_serial_number(uint32_t serial_number) const {
for (const auto& it : this->name_to_tournament) {
const auto& tourn = it.second;
auto team = tourn->team_for_serial_number(serial_number);
if (team) {
return team;
}
@@ -760,4 +796,37 @@ shared_ptr<Tournament::Team> TournamentIndex::team_for_serial_number(
return nullptr;
}
void TournamentIndex::link_client(shared_ptr<ServerState> s, shared_ptr<Client> c) {
if (!(c->flags & Client::Flag::IS_EPISODE_3)) {
return;
}
auto team = this->team_for_serial_number(c->license->serial_number);
auto tourn = team ? team->tournament.lock() : nullptr;
if (team && tourn) {
for (auto& player : team->players) {
if (player.serial_number == c->license->serial_number) {
c->ep3_tournament_team = team;
player.client = c;
if (!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) {
send_ep3_confirm_tournament_entry(s, c, tourn);
}
return;
}
}
throw logic_error("tournament team found for player, but player not found on team");
} else {
c->ep3_tournament_team.reset();
if (!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) {
send_ep3_confirm_tournament_entry(s, c, nullptr);
}
}
}
void TournamentIndex::link_all_clients(std::shared_ptr<ServerState> s) {
for (const auto& c_it : s->channel_to_client) {
this->link_client(s, c_it.second);
}
}
} // namespace Episode3
+41 -15
View File
@@ -13,6 +13,8 @@
#include "../Player.hh"
struct Lobby;
struct Client;
struct ServerState;
namespace Episode3 {
@@ -32,7 +34,11 @@ public:
uint32_t serial_number;
std::shared_ptr<const COMDeckDefinition> com_deck;
// client is valid if serial_number is nonzero and the client is connected
std::weak_ptr<Client> client;
explicit PlayerEntry(uint32_t serial_number);
explicit PlayerEntry(std::shared_ptr<Client> c);
explicit PlayerEntry(std::shared_ptr<const COMDeckDefinition> com_deck);
bool is_com() const;
@@ -57,7 +63,7 @@ public:
std::string str() const;
void register_player(
uint32_t serial_number,
std::shared_ptr<Client> c,
const std::string& team_name,
const std::string& password);
bool unregister_player(uint32_t serial_number);
@@ -94,7 +100,6 @@ public:
Tournament(
std::shared_ptr<const MapIndex> map_index,
std::shared_ptr<const COMDeckIndex> com_deck_index,
uint8_t number,
const std::string& name,
std::shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules,
@@ -104,16 +109,12 @@ public:
Tournament(
std::shared_ptr<const MapIndex> map_index,
std::shared_ptr<const COMDeckIndex> com_deck_index,
uint8_t number,
const JSON& json);
~Tournament() = default;
void init();
JSON json() const;
inline uint8_t get_number() const {
return this->number;
}
inline const std::string& get_name() const {
return this->name;
}
@@ -135,9 +136,15 @@ public:
inline const std::vector<std::shared_ptr<Team>>& all_teams() const {
return this->teams;
}
std::shared_ptr<Team> get_team(size_t index) const {
inline std::shared_ptr<Team> get_team(size_t index) const {
return this->teams.at(index);
}
inline uint32_t get_menu_item_id() const {
return this->menu_item_id;
}
inline void set_menu_item_id(uint32_t menu_item_id) {
this->menu_item_id = menu_item_id;
}
std::shared_ptr<Team> get_winner_team() const;
std::shared_ptr<Match> next_match_for_team(std::shared_ptr<Team> team) const;
@@ -147,6 +154,8 @@ public:
void start();
void send_all_state_updates(std::shared_ptr<ServerState> s) const;
void print_bracket(FILE* stream) const;
private:
@@ -155,7 +164,6 @@ private:
std::shared_ptr<const MapIndex> map_index;
std::shared_ptr<const COMDeckIndex> com_deck_index;
JSON source_json;
uint8_t number;
std::string name;
std::shared_ptr<const MapIndex::MapEntry> map;
Rules rules;
@@ -163,6 +171,7 @@ private:
bool is_2v2;
bool has_com_teams;
State current_state;
uint32_t menu_item_id;
std::set<uint32_t> all_player_serial_numbers;
std::unordered_set<std::shared_ptr<Match>> pending_matches;
@@ -191,7 +200,23 @@ public:
void save() const;
std::vector<std::shared_ptr<Tournament>> all_tournaments() const;
inline const std::unordered_map<std::string, std::shared_ptr<Tournament>>& all_tournaments() const {
return this->name_to_tournament;
}
inline std::shared_ptr<Tournament> get_tournament(uint32_t menu_item_id) const {
try {
return this->menu_item_id_to_tournament.at(menu_item_id);
} catch (const std::out_of_range&) {
return nullptr;
}
}
inline std::shared_ptr<Tournament> get_tournament(const std::string& name) const {
try {
return this->name_to_tournament.at(name);
} catch (const std::out_of_range&) {
return nullptr;
}
}
std::shared_ptr<Tournament> create_tournament(
const std::string& name,
@@ -200,18 +225,19 @@ public:
size_t num_teams,
bool is_2v2,
bool has_com_teams);
void delete_tournament(uint8_t number);
std::shared_ptr<Tournament> get_tournament(uint8_t number) const;
std::shared_ptr<Tournament> get_tournament(const std::string& name) const;
bool delete_tournament(const std::string& name);
std::shared_ptr<Tournament::Team> team_for_serial_number(
uint32_t serial_number) const;
std::shared_ptr<Tournament::Team> team_for_serial_number(uint32_t serial_number) const;
void link_client(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
void link_all_clients(std::shared_ptr<ServerState> s);
private:
std::shared_ptr<const MapIndex> map_index;
std::shared_ptr<const COMDeckIndex> com_deck_index;
std::string state_filename;
std::shared_ptr<Tournament> tournaments[0x20];
std::unordered_map<std::string, std::shared_ptr<Tournament>> name_to_tournament;
std::vector<std::shared_ptr<Tournament>> menu_item_id_to_tournament;
};
} // namespace Episode3
+1
View File
@@ -1655,6 +1655,7 @@ int main(int argc, char** argv) {
shared_ptr<struct event_base> base(event_base_new(), event_base_free);
shared_ptr<ServerState> state(new ServerState(config_filename, is_replay));
state->init();
shared_ptr<DNSServer> dns_server;
if (state->dns_server_port && (behavior != Behavior::REPLAY_LOG)) {
+7 -30
View File
@@ -231,9 +231,7 @@ static void send_main_menu(shared_ptr<ServerState> s, shared_ptr<Client> c) {
void on_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
if (c->flags & Client::Flag::IS_EPISODE_3) {
auto team = s->ep3_tournament_index->team_for_serial_number(c->license->serial_number);
auto tourn = team ? team->tournament.lock() : nullptr;
c->ep3_tournament_team = team;
s->ep3_tournament_index->link_client(s, c);
}
// On the BB data server, this function is called only on the last connection
@@ -1227,25 +1225,7 @@ static void on_DC_Ep3(shared_ptr<ServerState> s, shared_ptr<Client> c,
static void on_tournament_bracket_updated(
shared_ptr<ServerState> s, shared_ptr<const Episode3::Tournament> tourn) {
const auto& serial_numbers = tourn->get_all_player_serial_numbers();
for (const auto& l : s->all_lobbies()) {
for (const auto& c : l->clients) {
if (!c ||
!c->license ||
!serial_numbers.count(c->license->serial_number) ||
c->ep3_tournament_team.expired() ||
(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) {
continue;
}
send_ep3_confirm_tournament_entry(s, c, tourn);
}
}
if (tourn && (tourn->get_state() == Episode3::Tournament::State::COMPLETE)) {
s->ep3_tournament_index->delete_tournament(tourn->get_number());
}
tourn->send_all_state_updates(s);
if (tourn->get_state() == Episode3::Tournament::State::COMPLETE) {
auto team = tourn->get_winner_team();
if (!team->has_any_human_players()) {
@@ -1253,10 +1233,10 @@ static void on_tournament_bracket_updated(
} 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());
}
s->ep3_tournament_index->delete_tournament(tourn->get_number());
s->ep3_tournament_index->delete_tournament(tourn->get_name());
} else {
s->ep3_tournament_index->save();
}
s->ep3_tournament_index->save();
}
static void on_CA_Ep3(shared_ptr<ServerState> s, shared_ptr<Client> c,
@@ -2060,12 +2040,9 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
auto team = tourn->get_team(team_index);
if (team) {
try {
team->register_player(
c->license->serial_number,
encode_sjis(team_name),
encode_sjis(password));
team->register_player(c, encode_sjis(team_name), encode_sjis(password));
c->ep3_tournament_team = team;
send_ep3_confirm_tournament_entry(s, c, tourn);
tourn->send_all_state_updates(s);
string message = string_printf("$C7You are registered in $C6%s$C7.\n\
\n\
After registration ends, start your matches by\n\
+6 -5
View File
@@ -2285,7 +2285,8 @@ void send_ep3_tournament_list(
bool is_for_spectator_team_create) {
S_TournamentList_GC_Ep3_E0 cmd;
size_t z = 0;
for (const auto& tourn : s->ep3_tournament_index->all_tournaments()) {
for (const auto& it : s->ep3_tournament_index->all_tournaments()) {
const auto& tourn = it.second;
if (z >= 0x20) {
throw logic_error("more than 32 tournaments exist");
}
@@ -2293,7 +2294,7 @@ void send_ep3_tournament_list(
entry.menu_id = is_for_spectator_team_create
? MenuID::TOURNAMENTS_FOR_SPEC
: MenuID::TOURNAMENTS;
entry.item_id = tourn->get_number();
entry.item_id = tourn->get_menu_item_id();
// TODO: What does it mean for a tournament to be locked? Should we support
// that?
// TODO: Write appropriate round text (1st, 2nd, 3rd) here. This is
@@ -2333,7 +2334,7 @@ void send_ep3_tournament_entry_list(
}
auto& entry = cmd.entries[z];
entry.menu_id = MenuID::TOURNAMENT_ENTRIES;
entry.item_id = (tourn->get_number() << 16) | z;
entry.item_id = (tourn->get_menu_item_id() << 16) | z;
entry.unknown_a2 = team->num_rounds_cleared;
entry.locked = team->password.empty() ? 0 : 1;
if (tourn->get_state() != Episode3::Tournament::State::REGISTRATION) {
@@ -2597,8 +2598,8 @@ void send_ep3_tournament_match_result(
send_command_t(l, 0xC9, 0x00, cmd);
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
send_text_message_printf(l, "$C5TOURN/%02hhX/%zu WIN %c",
tourn->get_number(), match->round_num,
send_text_message_printf(l, "$C5TOURN/%" PRIX32 "/%zu WIN %c",
tourn->get_menu_item_id(), match->round_num,
match->winner_team == match->preceding_a->winner_team ? 'A' : 'B');
}
}
+9 -9
View File
@@ -38,7 +38,7 @@ void Server::disconnect_client(shared_ptr<Client> c) {
c->id, bufferevent_getfd(c->channel.bev.get()));
}
this->channel_to_client.erase(&c->channel);
this->state->channel_to_client.erase(&c->channel);
c->channel.disconnect();
try {
@@ -118,7 +118,7 @@ void Server::on_listen_accept(struct evconnlistener* listener,
c->channel.on_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error;
c->channel.context_obj = this;
this->channel_to_client.emplace(&c->channel, c);
this->state->channel_to_client.emplace(&c->channel, c);
server_log.info("Client connected: C-%" PRIX64 " on fd %d via %d (%s)",
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
@@ -148,7 +148,7 @@ void Server::connect_client(
name_for_version(version),
name_for_server_behavior(initial_state));
this->channel_to_client.emplace(&c->channel, c);
this->state->channel_to_client.emplace(&c->channel, c);
// Manually set the remote address, since the bufferevent has no fd and the
// Channel constructor can't figure out the virtual remote address
@@ -174,7 +174,7 @@ void Server::on_listen_error(struct evconnlistener* listener) {
void Server::on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
Server* server = reinterpret_cast<Server*>(ch.context_obj);
shared_ptr<Client> c = server->channel_to_client.at(&ch);
shared_ptr<Client> c = server->state->channel_to_client.at(&ch);
if (c->should_disconnect) {
server->disconnect_client(c);
@@ -197,7 +197,7 @@ void Server::on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::
void Server::on_client_error(Channel& ch, short events) {
Server* server = reinterpret_cast<Server*>(ch.context_obj);
shared_ptr<Client> c = server->channel_to_client.at(&ch);
shared_ptr<Client> c = server->state->channel_to_client.at(&ch);
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
@@ -271,13 +271,13 @@ void Server::add_socket(
}
shared_ptr<Client> Server::get_client() const {
if (this->channel_to_client.empty()) {
if (this->state->channel_to_client.empty()) {
throw runtime_error("no clients on game server");
}
if (this->channel_to_client.size() > 1) {
if (this->state->channel_to_client.size() > 1) {
throw runtime_error("multiple clients on game server");
}
return this->channel_to_client.begin()->second;
return this->state->channel_to_client.begin()->second;
}
vector<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident) const {
@@ -296,7 +296,7 @@ vector<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident
// TODO: It's kind of not great that we do a linear search here, but this is
// only used in the shell, so it should be pretty rare.
vector<shared_ptr<Client>> results;
for (const auto& it : this->channel_to_client) {
for (const auto& it : this->state->channel_to_client) {
auto c = it.second;
if (c->license && c->license->serial_number == serial_number_dec) {
results.emplace_back(std::move(c));
-1
View File
@@ -52,7 +52,6 @@ private:
ServerBehavior behavior);
};
std::unordered_map<int, ListeningSocket> listening_sockets;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
std::shared_ptr<ServerState> state;
+7 -10
View File
@@ -182,7 +182,7 @@ Server commands:\n\
start-tournament TOURNAMENT-NAME\n\
End registration for a tournament and allow matches to begin. Quotes are\n\
required around the tournament name unless the name contains no spaces.\n\
tournament-state TOURNAMENT-NAME\n\
describe-tournament TOURNAMENT-NAME\n\
Show the current state of a tournament. Quotes are required around the\n\
tournament name unless the name contains no spaces.\n\
\n\
@@ -540,23 +540,19 @@ Proxy session commands:\n\
}
auto tourn = this->state->ep3_tournament_index->create_tournament(
name, map, rules, num_teams, is_2v2, has_com_teams);
this->state->ep3_tournament_index->save();
fprintf(stderr, "created tournament %02hhX\n", tourn->get_number());
fprintf(stderr, "created tournament \"%s\"\n", tourn->get_name().c_str());
} else if (command_name == "delete-tournament") {
string name = get_quoted_string(command_args);
auto tourn = this->state->ep3_tournament_index->get_tournament(name);
if (tourn) {
this->state->ep3_tournament_index->delete_tournament(tourn->get_number());
this->state->ep3_tournament_index->save();
if (this->state->ep3_tournament_index->delete_tournament(name)) {
fprintf(stderr, "tournament deleted\n");
} else {
fprintf(stderr, "no such tournament exists\n");
}
} else if (command_name == "list-tournaments") {
for (const auto& tourn : this->state->ep3_tournament_index->all_tournaments()) {
fprintf(stderr, " %s\n", tourn->get_name().c_str());
for (const auto& it : this->state->ep3_tournament_index->all_tournaments()) {
fprintf(stderr, " %s\n", it.second->get_name().c_str());
}
} else if (command_name == "start-tournament") {
@@ -565,13 +561,14 @@ Proxy session commands:\n\
if (tourn) {
tourn->start();
this->state->ep3_tournament_index->save();
tourn->send_all_state_updates(this->state);
send_ep3_text_message_printf(this->state, "$C7The tournament\n$C6%s$C7\nhas begun", tourn->get_name().c_str());
fprintf(stderr, "tournament started\n");
} else {
fprintf(stderr, "no such tournament exists\n");
}
} else if (command_name == "tournament-status") {
} else if (command_name == "describe-tournament") {
string name = get_quoted_string(command_args);
auto tourn = this->state->ep3_tournament_index->get_tournament(name);
if (tourn) {
+4 -1
View File
@@ -41,7 +41,9 @@ ServerState::ServerState(const char* config_filename, bool is_replay)
local_address(0),
external_address(0),
proxy_allow_save_files(true),
proxy_enable_login_options(false) {
proxy_enable_login_options(false) {}
void ServerState::init() {
vector<shared_ptr<Lobby>> non_v1_only_lobbies;
vector<shared_ptr<Lobby>> ep3_only_lobbies;
@@ -875,6 +877,7 @@ void ServerState::load_ep3_data() {
const string& tournament_state_filename = "system/ep3/tournament-state.json";
this->ep3_tournament_index.reset(new Episode3::TournamentIndex(
this->ep3_map_index, this->ep3_com_deck_index, tournament_state_filename));
this->ep3_tournament_index->link_all_clients(this->shared_from_this());
config_log.info("Loaded Episode 3 tournament state");
}
+7 -1
View File
@@ -33,7 +33,7 @@ struct PortConfiguration {
ServerBehavior behavior;
};
struct ServerState {
struct ServerState : public std::enable_shared_from_this<ServerState> {
enum class RunShellBehavior {
DEFAULT = 0,
ALWAYS,
@@ -129,6 +129,7 @@ struct ServerState {
std::u16string pc_patch_server_message;
std::u16string bb_patch_server_message;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order_v1;
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order_non_v1;
@@ -148,6 +149,11 @@ struct ServerState {
std::shared_ptr<Server> game_server;
ServerState(const char* config_filename, bool is_replay);
ServerState(const ServerState&) = delete;
ServerState(ServerState&&) = delete;
ServerState& operator=(const ServerState&) = delete;
ServerState& operator=(ServerState&&) = delete;
void init();
void add_client_to_available_lobby(std::shared_ptr<Client> c);
void remove_client_from_lobby(std::shared_ptr<Client> c);
+164
View File
@@ -61,6 +61,88 @@ I 32209 2023-09-08 23:38:59 - [Commands] Sending to C-6 (version=GC command=04 f
0010 | 0E 89 2A 49 0A 03 02 00 30 45 53 33 00 00 00 00 | *I 0ES3
0020 | 00 00 FF FF FF FF FF FF FF FF FF FF |
I 32209 2023-09-08 23:38:59 - [Commands] Sending to C-6 (version=GC command=B7 flag=00)
0000 | CC 00 0C 05 00 00 00 00 00 00 00 00 00 00 00 00 |
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0020 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0070 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0080 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0090 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0100 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0110 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0120 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0130 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0150 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0160 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0170 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0180 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0190 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
01A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
01B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
01C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
01D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
01E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
01F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0200 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0210 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0220 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0230 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0240 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0250 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0260 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0270 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0280 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0290 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
02A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
02B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
02C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
02D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
02E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
02F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0300 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0310 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0320 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0330 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0340 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0350 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0360 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0370 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0380 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0390 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
03A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
03B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
03C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
03D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
03E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
03F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0400 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0410 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0420 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0430 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0440 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0450 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0460 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0470 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0480 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0490 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
04A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
04B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
04C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
04D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
04E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
04F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0500 | 00 00 00 00 00 00 00 00 00 00 00 00 |
I 32209 2023-09-08 23:38:59 - [Commands] Sending to C-6 (version=GC command=B7 flag=00)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0010 | 00 00 00 00 40 42 0F 00 40 42 0F 00 FF FF FF FF |
I 32209 2023-09-08 23:38:59 - [Commands] Sending to C-6 (version=GC command=95 flag=00)
@@ -2705,6 +2787,88 @@ I 32209 2023-09-08 23:39:02 - [Commands] Sending to C-7 (version=GC command=04 f
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9
0010 | 0E 89 2A 49 0A 44 82 00 30 45 53 33 00 00 00 00 | *I D 0ES3
0020 | 00 00 FF FF FF FF FF FF FF FF FF FF |
I 32209 2023-09-08 23:39:02 - [Commands] Sending to C-7 (version=GC command=04 flag=00)
0000 | CC 00 0C 05 00 00 00 00 00 00 00 00 00 00 00 00 |
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0020 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0070 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0080 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0090 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0100 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0110 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0120 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0130 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0150 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0160 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0170 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0180 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0190 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
01A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
01B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
01C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
01D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
01E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
01F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0200 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0210 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0220 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0230 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0240 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0250 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0260 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0270 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0280 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0290 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
02A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
02B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
02C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
02D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
02E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
02F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0300 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0310 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0320 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0330 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0340 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0350 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0360 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0370 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0380 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0390 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
03A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
03B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
03C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
03D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
03E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
03F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0400 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0410 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0420 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0430 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0440 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0450 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0460 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0470 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0480 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0490 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
04A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
04B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
04C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
04D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
04E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
04F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0500 | 00 00 00 00 00 00 00 00 00 00 00 00 |
I 32209 2023-09-08 23:39:02 - [Commands] Sending to C-7 (version=GC command=83 flag=14)
0000 | 83 14 F4 00 33 00 00 33 01 00 00 00 00 00 00 00 | 3 3
0010 | 33 00 00 33 02 00 00 00 00 00 00 00 33 00 00 33 | 3 3 3 3