diff --git a/src/Lobby.cc b/src/Lobby.cc index 8a2857d3..cab4d8e8 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -674,6 +674,51 @@ shared_ptr Lobby::find_client(const string* identifier, uint64_t serial_ throw out_of_range("client not found"); } +Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr c, const std::string* password) const { + if (this->count_clients() >= this->max_clients) { + return JoinError::FULL; + } + if (!this->version_is_allowed(c->version()) && !c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { + return JoinError::VERSION_CONFLICT; + } + if (this->is_game()) { + if (this->check_flag(Flag::QUEST_IN_PROGRESS)) { + return JoinError::QUEST_IN_PROGRESS; + } + if (this->check_flag(Flag::BATTLE_IN_PROGRESS)) { + return JoinError::BATTLE_IN_PROGRESS; + } + if (this->mode == GameMode::SOLO) { + return JoinError::SOLO; + } + if (!(c->license->flags & License::Flag::FREE_JOIN_GAMES)) { + if (password && !this->password.empty() && (*password != this->password)) { + return JoinError::INCORRECT_PASSWORD; + } + auto p = c->character(); + if (p->disp.stats.level < this->min_level) { + return JoinError::LEVEL_TOO_LOW; + } + if (p->disp.stats.level > this->max_level) { + return JoinError::LEVEL_TOO_HIGH; + } + if (this->quest) { + size_t num_clients = this->count_clients() + 1; + if (!c->can_see_quest(this->quest, this->difficulty, num_clients) || + !c->can_play_quest(this->quest, this->difficulty, num_clients)) { + return JoinError::NO_ACCESS_TO_QUEST; + } + } + } + // Only prevent joining during loading if the client is actually trying to + // join (not just loading the game list) + if (password && this->any_client_loading()) { + return JoinError::LOADING; + } + } + return JoinError::ALLOWED; +} + uint8_t Lobby::game_event_for_lobby_event(uint8_t lobby_event) { if (lobby_event > 7) { return 0; diff --git a/src/Lobby.hh b/src/Lobby.hh index 1034fe59..2307cc0d 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -228,6 +228,21 @@ struct Lobby : public std::enable_shared_from_this { const std::string* identifier = nullptr, uint64_t serial_number = 0); + enum class JoinError { + ALLOWED = 0, + FULL, + VERSION_CONFLICT, + QUEST_IN_PROGRESS, + BATTLE_IN_PROGRESS, + LOADING, + SOLO, + INCORRECT_PASSWORD, + LEVEL_TOO_LOW, + LEVEL_TOO_HIGH, + NO_ACCESS_TO_QUEST, + }; + JoinError join_error_for_client(std::shared_ptr c, const std::string* password) const; + bool item_exists(uint8_t floor, uint32_t item_id) const; std::shared_ptr find_item(uint8_t floor, uint32_t item_id) const; void add_item(uint8_t floor, const ItemData& item, float x, float z, uint16_t visibility_flags); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 4ff415af..1dccabf5 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -2390,66 +2390,56 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { send_lobby_message_box(c, "$C6You cannot join this\ngame because it no\nlonger exists."); break; } - if (!game->is_game()) { - send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nnot a game."); - break; - } - if (game->count_clients() >= game->max_clients) { - send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfull."); - break; - } - if (!game->version_is_allowed(c->version()) && !c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { - send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO."); - break; - } - if (game->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { - send_lobby_message_box(c, "$C6You cannot join this\ngame because a\nquest is already\nin progress."); - break; - } - if (game->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) { - send_lobby_message_box(c, "$C6You cannot join this\ngame because a\nbattle is already\nin progress."); - break; - } - if (game->any_client_loading()) { - send_lobby_message_box(c, "$C6You cannot join this\ngame because\nanother player is\ncurrently loading.\nTry again soon."); - break; - } - if (game->mode == GameMode::SOLO) { - send_lobby_message_box(c, "$C6You cannot join this\n game because it is\na Solo Mode game."); - break; - } - - if (!(c->license->flags & License::Flag::FREE_JOIN_GAMES)) { - if (!game->password.empty() && (password != game->password)) { + switch (game->join_error_for_client(c, &password)) { + case Lobby::JoinError::ALLOWED: + if (!s->change_client_lobby(c, game)) { + throw logic_error("client cannot join game after all preconditions satisfied"); + } + if (game->is_game()) { + c->config.set_flag(Client::Flag::LOADING); + // If no one was in the game before, then there's no leader to send the + // item state - send it to the joining player (who is now the leader) + if (game->count_clients() == 1) { + // No one was in the game before, so the object and enemy state is lost; + // regenerate it as if the game was just created + game->load_maps(); + c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ITEM_STATE); + } + } + break; + case Lobby::JoinError::FULL: + send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfull."); + break; + case Lobby::JoinError::VERSION_CONFLICT: + send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO."); + break; + case Lobby::JoinError::QUEST_IN_PROGRESS: + send_lobby_message_box(c, "$C6You cannot join this\ngame because a\nquest is already\nin progress."); + break; + case Lobby::JoinError::BATTLE_IN_PROGRESS: + send_lobby_message_box(c, "$C6You cannot join this\ngame because a\nbattle is already\nin progress."); + break; + case Lobby::JoinError::LOADING: + send_lobby_message_box(c, "$C6You cannot join this\ngame because\nanother player is\ncurrently loading.\nTry again soon."); + break; + case Lobby::JoinError::SOLO: + send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\na Solo Mode game."); + break; + case Lobby::JoinError::INCORRECT_PASSWORD: send_lobby_message_box(c, "$C6Incorrect password."); break; - } - auto p = c->character(); - if (p->disp.stats.level < game->min_level) { + case Lobby::JoinError::LEVEL_TOO_LOW: send_lobby_message_box(c, "$C6Your level is too\nlow to join this\ngame."); break; - } - if (p->disp.stats.level > game->max_level) { + case Lobby::JoinError::LEVEL_TOO_HIGH: send_lobby_message_box(c, "$C6Your level is too\nhigh to join this\ngame."); break; - } - if (game->quest && !c->can_play_quest(game->quest, game->difficulty, game->count_clients() + 1)) { + case Lobby::JoinError::NO_ACCESS_TO_QUEST: send_lobby_message_box(c, "$C6You don't have access\nto the quest in progress\nin this game, or there\nis no space for another\nplayer in the quest."); break; - } - } - - if (!s->change_client_lobby(c, game)) { - throw logic_error("client cannot join game after all preconditions satisfied"); - } - c->config.set_flag(Client::Flag::LOADING); - // If no one was in the game before, then there's no leader to send the - // item state - send it to the joining player (who is now the leader) - if (game->count_clients() == 1) { - // No one was in the game before, so the object and enemy state is lost; - // regenerate it as if the game was just created - game->load_maps(); - c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ITEM_STATE); + default: + send_lobby_message_box(c, "$C6You cannot join this\ngame."); + break; } break; } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index cd92160a..a88ca17f 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -1413,13 +1413,15 @@ void send_game_menu_t( e.flags |= 0x20; break; case GameMode::SOLO: - // These should only be visible to other BB clients - e.flags |= 0x04; // Grayed (but not disabled apparently) e.episode = 0x10 | episode_num; break; default: throw logic_error("invalid game mode"); } + // On BB, gray out games that can't be joined + if ((c->version() == Version::BB_V4) && (l->join_error_for_client(c, nullptr) != Lobby::JoinError::ALLOWED)) { + e.flags |= 0x04; + } } e.name.encode(l->name, c->language()); }