#pragma once #include #include #include #include #include #include #include #include "DataIndexes.hh" struct Lobby; class Client; struct ServerState; namespace Episode3 { class Tournament : public std::enable_shared_from_this { public: enum Flag : uint8_t { IS_2V2 = 0x01, HAS_COM_TEAMS = 0x02, SHUFFLE_ENTRIES = 0x04, RESIZE_ON_START = 0x08, }; enum class State { REGISTRATION = 0, IN_PROGRESS, COMPLETE, }; struct PlayerEntry { // Invariant: (account_id == 0) != (com_deck == nullptr) // (that is, exactly one of the following must be valid) uint32_t account_id; std::shared_ptr com_deck; // client is valid if account_id is nonzero and the client is connected std::weak_ptr client; std::string player_name; // Not used for COM decks explicit PlayerEntry(uint32_t account_id, const std::string& player_name = ""); explicit PlayerEntry(std::shared_ptr c); explicit PlayerEntry(std::shared_ptr com_deck); bool is_com() const; bool is_human() const; phosg::JSON json() const; }; struct Team : public std::enable_shared_from_this { std::weak_ptr tournament; size_t index; size_t max_players; std::vector players; std::string name; std::string password; size_t num_rounds_cleared; bool is_active; Team( std::shared_ptr tournament, size_t index, size_t max_players); std::string str() const; void register_player( std::shared_ptr c, const std::string& team_name, const std::string& password); bool unregister_player(uint32_t account_id); 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 { std::weak_ptr tournament; std::shared_ptr preceding_a; std::shared_ptr preceding_b; std::weak_ptr following; std::shared_ptr winner_team; size_t round_num; Match( std::shared_ptr tournament, std::shared_ptr preceding_a, std::shared_ptr preceding_b); Match( std::shared_ptr tournament, std::shared_ptr winner_team); std::string str() const; bool resolve_if_skippable(); void on_winner_team_set(); void set_winner_team(std::shared_ptr team); void set_winner_team_without_triggers(std::shared_ptr team); std::shared_ptr opponent_team_for_team(std::shared_ptr team) const; }; Tournament( std::shared_ptr map_index, std::shared_ptr com_deck_index, const std::string& name, std::shared_ptr map, const Rules& rules, size_t num_teams, uint8_t flags); Tournament( std::shared_ptr map_index, std::shared_ptr com_deck_index, const phosg::JSON& json); ~Tournament() = default; void init(); phosg::JSON json() const; inline const std::string& get_name() const { return this->name; } inline std::shared_ptr get_map() const { return this->map; } inline const Rules& get_rules() const { return this->rules; } inline uint8_t get_flags() const { return this->flags; } inline State get_state() const { return this->current_state; } inline const std::vector>& all_teams() const { return this->teams; } inline std::shared_ptr 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 get_winner_team() const; std::shared_ptr next_match_for_team(std::shared_ptr team) const; std::shared_ptr get_final_match() const; std::shared_ptr team_for_account_id(uint32_t account_id) const; const std::set& get_all_player_account_ids() const; void start(); void send_all_state_updates() const; void send_all_state_updates_on_deletion() const; std::string bracket_str() const; private: void create_bracket_matches(); phosg::PrefixedLogger log; std::shared_ptr map_index; std::shared_ptr com_deck_index; phosg::JSON source_json; std::string name; std::shared_ptr map; Rules rules; size_t num_teams; uint8_t flags; State current_state; uint32_t menu_item_id; std::set all_player_account_ids; std::unordered_set> pending_matches; // This vector contains all teams in the original starting order of the // tournament (that is, all teams in the first round). The order within this // vector determines which team will play against which other team in the // first round: [0] will play against [1], [2] will play against [3], etc. std::vector> teams; // The tournament begins with a "zero round", in which each team automatically // "wins" a match, putting them into the first round. This is just to make the // data model easier to manage, so we don't have to have a type of match with // no preceding round. std::vector> zero_round_matches; std::shared_ptr final_match; }; class TournamentIndex { public: explicit TournamentIndex( std::shared_ptr map_index, std::shared_ptr com_deck_index, const std::string& state_filename, bool skip_load_state = false); ~TournamentIndex() = default; void save() const; inline const std::unordered_map>& all_tournaments() const { return this->name_to_tournament; } inline std::shared_ptr 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 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 create_tournament( const std::string& name, std::shared_ptr map, const Rules& rules, size_t num_teams, uint8_t flags); bool delete_tournament(const std::string& name); std::shared_ptr team_for_account_id(uint32_t account_id) const; void link_client(std::shared_ptr c); void link_all_clients(std::shared_ptr s); private: std::shared_ptr map_index; std::shared_ptr com_deck_index; std::string state_filename; std::unordered_map> name_to_tournament; std::vector> menu_item_id_to_tournament; }; } // namespace Episode3