keep tournament state consistent on clients
This commit is contained in:
+158
-89
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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));
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user