implement spectator teams

This commit is contained in:
Martin Michelsen
2022-12-11 13:31:11 -08:00
parent cceaf5efde
commit 398a93b56f
9 changed files with 111 additions and 21 deletions
+1
View File
@@ -221,6 +221,7 @@ Some commands only work on the game server and not on the proxy server. The chat
* `$maxlevel <level>`: Sets the maximum level for players to join the current game.
* `$minlevel <level>`: Sets the minimum level for players to join the current game.
* `$password <password>`: Sets the game's join password. To unlock the game, run `$password` with nothing after it.
* `$spec`: Toggles the allow spectators flag. If any players are spectating when this flag is disabled, they will be sent back to the lobby.
* Cheat mode commands
* `$cheat`: Enables or disables cheat mode for the current game. All other cheat mode commands do nothing if cheat mode is disabled. This command does nothing on the proxy server - cheat commands are always available there.
+25 -1
View File
@@ -434,7 +434,7 @@ static void server_command_playrec(shared_ptr<ServerState> s, shared_ptr<Lobby>
shared_ptr<Episode3::BattleRecord> record(new Episode3::BattleRecord(data));
shared_ptr<Episode3::BattleRecordPlayer> battle_player(
new Episode3::BattleRecordPlayer(record, s->game_server->get_base()));
auto game = create_game_generic(s, c, args.c_str(), u"", 0xFF, 0, flags, battle_player);
auto game = create_game_generic(s, c, args.c_str(), u"", 0xFF, 0, flags, nullptr, battle_player);
}
}
@@ -516,6 +516,29 @@ static void server_command_password(shared_ptr<ServerState>, shared_ptr<Lobby> l
}
}
static void server_command_spec(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string&) {
check_is_game(l, true);
check_is_leader(l, c);
check_is_ep3(c, true);
if (!(l->flags & Lobby::Flag::EPISODE_3_ONLY)) {
throw logic_error("Episode 3 client in non-Episode 3 game");
}
if (l->flags & Lobby::Flag::SPECTATORS_FORBIDDEN) {
l->flags &= ~Lobby::Flag::SPECTATORS_FORBIDDEN;
send_text_message(l, u"$C6Spectators allowed");
} else {
l->flags |= Lobby::Flag::SPECTATORS_FORBIDDEN;
for (auto watcher_l : l->watcher_lobbies) {
send_command(watcher_l, 0xED, 0x00);
}
l->watcher_lobbies.clear();
send_text_message(l, u"$C6Spectators forbidden");
}
}
static void server_command_min_level(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_is_game(l, true);
@@ -1066,6 +1089,7 @@ static const unordered_map<u16string, ChatCommandDefinition> chat_commands({
{u"$silence", {server_command_silence, nullptr, u"Usage:\nsilence <name-or-number>"}},
// TODO: implement this on proxy server
{u"$song", {server_command_song, nullptr, u"Usage:\nsong <song-number>"}},
{u"$spec", {server_command_spec, nullptr, u"Usage:\nspec"}},
{u"$swa", {server_command_switch_assist, proxy_command_switch_assist, u"Usage:\nswa"}},
{u"$type", {server_command_lobby_type, nullptr, u"Usage:\ntype <name>"}},
{u"$warp", {server_command_warp, proxy_command_warp, u"Usage:\nwarp <area-number>"}},
+4 -1
View File
@@ -1963,7 +1963,10 @@ struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry<le_uint16_t, char16_t>
template <typename CharT>
struct C_CreateGame {
parray<le_uint32_t, 2> unused;
// menu_id and item_id are only used for the E7 (create spectator team) form
// of this command
le_uint32_t menu_id;
le_uint32_t item_id;
ptext<CharT, 0x10> name;
ptext<CharT, 0x10> password;
uint8_t difficulty = 0; // 0-3 (always 0 on Episode 3)
+6
View File
@@ -2119,6 +2119,9 @@ void Server::handle_6xB3x40_map_list_request(const string& data) {
G_MapList_GC_Ep3_6xB6x40{{{{0xB6, 0, 0}, subcommand_size}, 0x40, {}}, list_data.size(), 0});
w.write(list_data);
send_command(l, 0x6C, 0x00, w.str());
for (auto watcher_l : l->watcher_lobbies) {
send_command(watcher_l, 0x6C, 0x00, w.str());
}
if (l->battle_record && l->battle_record->writable()) {
l->battle_record->add_command(
@@ -2139,6 +2142,9 @@ void Server::handle_6xB3x41_map_request(const string& data) {
this->last_chosen_map = this->base()->data_index->definition_for_map_number(cmd.map_number);
auto out_cmd = this->prepare_6xB6x41_map_definition(this->last_chosen_map);
send_command(l, 0x6C, 0x00, out_cmd);
for (auto watcher_l : l->watcher_lobbies) {
send_command(watcher_l, 0x6C, 0x00, out_cmd);
}
if (l->battle_record && l->battle_record->writable()) {
l->battle_record->add_command(
+30 -11
View File
@@ -963,7 +963,7 @@ static bool start_ep3_tournament_match_if_pending(
// TODO: We don't know if this works with multiple players. Test it.
uint32_t flags = Lobby::Flag::NON_V1_ONLY | Lobby::Flag::EPISODE_3_ONLY;
auto game = create_game_generic(s, c, u"<Tournament>", u"", 0xFF, 0, flags);
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);
@@ -1174,10 +1174,10 @@ static void on_ep3_tournament_control(shared_ptr<ServerState> s, shared_ptr<Clie
break;
}
case 0x03: // Create tournament spectator team (get battle list)
send_lobby_message_box(c, u"$C6Not supported"); // TODO
send_game_menu(c, s, false, true);
break;
case 0x04: // Join tournament spectator team (get team list)
send_lobby_message_box(c, u"$C6Not supported"); // TODO
send_game_menu(c, s, true, true);
break;
default:
throw runtime_error("invalid tournament operation");
@@ -1933,9 +1933,9 @@ static void on_change_lobby(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
static void on_game_list_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 08
uint16_t command, uint32_t, const string& data) { // 08 E6
check_size_v(data.size(), 0);
send_game_menu(c, s);
send_game_menu(c, s, (command == 0xE6), false);
}
static void on_info_menu_request_dc_pc(shared_ptr<ServerState> s,
@@ -2796,6 +2796,7 @@ shared_ptr<Lobby> create_game_generic(
uint8_t episode,
uint8_t difficulty,
uint32_t flags,
shared_ptr<Lobby> watched_lobby,
shared_ptr<Episode3::BattleRecordPlayer> battle_player) {
// A player's actual level is their displayed level - 1, so the minimums for
@@ -2857,6 +2858,10 @@ shared_ptr<Lobby> create_game_generic(
game->max_clients = (game->flags & Lobby::Flag::IS_SPECTATOR_TEAM) ? 12 : 4;
game->min_level = min_level;
game->max_level = 0xFFFFFFFF;
if (watched_lobby) {
game->watched_lobby = watched_lobby;
watched_lobby->watcher_lobbies.emplace(game);
}
bool is_solo = (game->flags & Lobby::Flag::SOLO_MODE);
@@ -2939,12 +2944,12 @@ static void on_create_game_pc(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
static void on_create_game_dc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t command, uint32_t, const string& data) { // 0C C1 EC (EC Ep3 only)
uint16_t command, uint32_t, const string& data) { // 0C C1 E7 EC (E7/EC are Ep3 only)
const auto& cmd = check_size_t<C_CreateGame_DC_V3_0C_C1_Ep3_EC>(data);
// Only allow EC from Ep3 clients
// Only allow E7/EC from Ep3 clients
bool client_is_ep3 = !!(c->flags & Client::Flag::IS_EPISODE_3);
if ((command == 0xEC) != client_is_ep3) {
if (((command & 0xF0) == 0xE0) != client_is_ep3) {
return;
}
@@ -2975,8 +2980,22 @@ static void on_create_game_dc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c
flags |= Lobby::Flag::CHALLENGE_MODE;
}
}
shared_ptr<Lobby> watched_lobby;
if (command == 0xE7) {
if (cmd.menu_id != MenuID::GAME) {
throw runtime_error("incorrect menu ID");
}
watched_lobby = s->find_lobby(cmd.item_id);
if (watched_lobby->flags & Lobby::Flag::SPECTATORS_FORBIDDEN) {
send_lobby_message_box(c, u"$C6This game does not\nallow spectators");
return;
}
flags |= Lobby::Flag::IS_SPECTATOR_TEAM;
}
auto game = create_game_generic(
s, c, name.c_str(), password.c_str(), episode, cmd.difficulty, flags);
s, c, name.c_str(), password.c_str(), episode, cmd.difficulty, flags, watched_lobby);
s->change_client_lobby(c, game);
c->flags |= Client::Flag::LOADING;
}
@@ -3702,8 +3721,8 @@ static on_command_t handlers[0x100][6] = {
/* E3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_player_preview_request_bb, }, /* E3 */
/* E4 */ {nullptr, nullptr, nullptr, on_ep3_battle_table_state, nullptr, nullptr, }, /* E4 */
/* E5 */ {nullptr, nullptr, nullptr, on_ep3_battle_table_confirm, nullptr, on_create_character_bb, }, /* E5 */
/* E6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* E6 */
/* E7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_return_player_data_bb, }, /* E7 */
/* E6 */ {nullptr, nullptr, nullptr, on_game_list_request, nullptr, nullptr, }, /* E6 */
/* E7 */ {nullptr, nullptr, nullptr, on_create_game_dc_v3, nullptr, on_return_player_data_bb, }, /* E7 */
/* E8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_client_checksum_bb, }, /* E8 */
/* E9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* E9 */
/* EA */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_team_command_bb, }, /* EA */
+1
View File
@@ -14,6 +14,7 @@ std::shared_ptr<Lobby> create_game_generic(
uint8_t episode,
uint8_t difficulty,
uint32_t flags,
std::shared_ptr<Lobby> watched_lobby = nullptr,
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player = nullptr);
void on_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
+15 -2
View File
@@ -63,6 +63,12 @@ const CmdT& check_size_sc(
static const unordered_set<uint8_t> watcher_subcommands({
0x07, // Symbol chat
0x74, // Word select
0xBD, // Word select during battle (with private_flags)
});
static void forward_subcommand(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint8_t command, uint8_t flag, const void* data, size_t size) {
@@ -98,8 +104,15 @@ static void forward_subcommand(shared_ptr<Lobby> l, shared_ptr<Client> c,
send_command_excluding_client(l, c, command, flag, data, size);
}
for (const auto& watcher_lobby : l->watcher_lobbies) {
forward_subcommand(watcher_lobby, c, command, flag, data, size);
// Before battle, forward only chat commands to watcher lobbies; during
// battle, forward everything to watcher lobbies.
if (size &&
(watcher_subcommands.count(*reinterpret_cast<const uint8_t*>(data) ||
(l->ep3_server_base &&
l->ep3_server_base->server->setup_phase != Episode3::SetupPhase::REGISTRATION)))) {
for (const auto& watcher_lobby : l->watcher_lobbies) {
forward_subcommand(watcher_lobby, c, command, flag, data, size);
}
}
if (l->battle_record && l->battle_record->battle_in_progress()) {
+24 -5
View File
@@ -1084,7 +1084,11 @@ void send_menu(shared_ptr<Client> c, const u16string& menu_name,
template <typename CharT>
void send_game_menu_t(shared_ptr<Client> c, shared_ptr<ServerState> s) {
void send_game_menu_t(
shared_ptr<Client> c,
shared_ptr<ServerState> s,
bool is_spectator_team_list,
bool is_tournament_game_list) {
vector<S_GameMenuEntry<CharT>> entries;
{
auto& e = entries.emplace_back();
@@ -1100,6 +1104,7 @@ void send_game_menu_t(shared_ptr<Client> c, shared_ptr<ServerState> s) {
if (!l->is_game() || (l->version != c->version())) {
continue;
}
bool l_is_ep3 = !!(l->flags & Lobby::Flag::EPISODE_3_ONLY);
bool c_is_ep3 = !!(c->flags & Client::Flag::IS_EPISODE_3);
if (l_is_ep3 != c_is_ep3) {
@@ -1109,6 +1114,15 @@ void send_game_menu_t(shared_ptr<Client> c, shared_ptr<ServerState> s) {
continue;
}
bool l_is_spectator_team = !!(l->flags & Lobby::Flag::IS_SPECTATOR_TEAM);
if (l_is_spectator_team != is_spectator_team_list) {
continue;
}
bool l_is_tournament_game = !!l->tournament_match;
if (l_is_tournament_game != is_tournament_game_list) {
continue;
}
auto& e = entries.emplace_back();
e.menu_id = MenuID::GAME;
e.game_id = l->lobby_id;
@@ -1136,16 +1150,20 @@ void send_game_menu_t(shared_ptr<Client> c, shared_ptr<ServerState> s) {
e.name = l->name;
}
send_command_vt(c, 0x08, entries.size() - 1, entries);
send_command_vt(c, is_spectator_team_list ? 0xE6 : 0x08, entries.size() - 1, entries);
}
void send_game_menu(shared_ptr<Client> c, shared_ptr<ServerState> s) {
void send_game_menu(
shared_ptr<Client> c,
shared_ptr<ServerState> s,
bool is_spectator_team_list,
bool is_tournament_game_list) {
if ((c->version() == GameVersion::DC) ||
(c->version() == GameVersion::GC) ||
(c->version() == GameVersion::XB)) {
send_game_menu_t<char>(c, s);
send_game_menu_t<char>(c, s, is_spectator_team_list, is_tournament_game_list);
} else {
send_game_menu_t<char16_t>(c, s);
send_game_menu_t<char16_t>(c, s, is_spectator_team_list, is_tournament_game_list);
}
}
@@ -2064,6 +2082,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
entry.team_name = teams[z]->name;
}
cmd.num_bracket_entries = teams.size();
cmd.players_per_team = tourn->get_is_2v2() ? 2 : 1;
if (primary_lobby) {
auto serial_number_to_client = primary_lobby->clients_by_serial_number();
+5 -1
View File
@@ -234,7 +234,11 @@ void send_guild_card(
void send_guild_card(std::shared_ptr<Client> c, std::shared_ptr<Client> source);
void send_menu(std::shared_ptr<Client> c, const std::u16string& menu_name,
uint32_t menu_id, const std::vector<MenuItem>& items, bool is_info_menu = false);
void send_game_menu(std::shared_ptr<Client> c, std::shared_ptr<ServerState> s);
void send_game_menu(
std::shared_ptr<Client> c,
std::shared_ptr<ServerState> s,
bool is_spectator_team_list,
bool is_tournament_game_list);
void send_quest_menu(std::shared_ptr<Client> c, uint32_t menu_id,
const std::vector<std::shared_ptr<const Quest>>& quests, bool is_download_menu);
void send_quest_menu(std::shared_ptr<Client> c, uint32_t menu_id,