diff --git a/src/Client.cc b/src/Client.cc index eb197dbf..9ea84de9 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -438,9 +438,21 @@ bool Client::can_use_chat_commands() const { void Client::set_login(shared_ptr login) { this->login = login; + + auto s = this->require_server_state(); + if (!s->allow_same_account_concurrent_logins) { + auto it = s->client_for_account.find(login->account->account_id); + if ((it != s->client_for_account.end()) && (it->second.get() != this)) { + if (it->second->channel) { + it->second->channel->disconnect(); + } + s->client_for_account.erase(it); + } + s->client_for_account.emplace(this->login->account->account_id, this->shared_from_this()); + } + if (this->log.should_log(phosg::LogLevel::L_INFO)) { - string login_str = this->login->str(); - this->log.info_f("Login: {}", login_str); + this->log.info_f("Login: {}", this->login->str()); } } diff --git a/src/GameServer.cc b/src/GameServer.cc index 75b8f528..a203e756 100644 --- a/src/GameServer.cc +++ b/src/GameServer.cc @@ -195,4 +195,11 @@ asio::awaitable GameServer::destroy_client(std::shared_ptr c) { c->log.warning_f("Disconnect hook {} failed: {}", h_it.first, e.what()); } } + + if (c->login) { + auto it = this->state->client_for_account.find(c->login->account->account_id); + if ((it != this->state->client_for_account.end()) && (it->second == c)) { + this->state->client_for_account.erase(it); + } + } } diff --git a/src/GameServer.hh b/src/GameServer.hh index a0450fd0..a3956c7c 100644 --- a/src/GameServer.hh +++ b/src/GameServer.hh @@ -15,9 +15,7 @@ struct GameServerSocket : ServerSocket { ServerBehavior behavior; }; -class GameServer - : public Server, - public std::enable_shared_from_this { +class GameServer : public Server, public std::enable_shared_from_this { public: GameServer() = delete; GameServer(const GameServer&) = delete; diff --git a/src/ServerState.cc b/src/ServerState.cc index cc1506ef..c4d6b5cf 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -865,6 +865,7 @@ void ServerState::load_config_early() { this->ip_stack_debug = this->config_json->get_bool("IPStackDebug", false); this->allow_unregistered_users = this->config_json->get_bool("AllowUnregisteredUsers", false); this->allow_pc_nte = this->config_json->get_bool("AllowPCNTE", false); + this->allow_same_account_concurrent_logins = this->config_json->get_bool("AllowSameAccountConcurrentLogins", false); this->allow_saving_accounts = this->config_json->get_bool("AllowSavingAccounts", true); this->use_temp_accounts_for_prototypes = this->config_json->get_bool("UseTemporaryAccountsForPrototypes", true); this->notify_server_for_max_level_achieved = this->config_json->get_bool("NotifyServerForMaxLevelAchieved", false); diff --git a/src/ServerState.hh b/src/ServerState.hh index f21aa228..e38e7484 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -127,6 +127,7 @@ struct ServerState : public std::enable_shared_from_this { bool allow_unregistered_users = false; bool allow_pc_nte = false; bool use_temp_accounts_for_prototypes = true; + bool allow_same_account_concurrent_logins = true; std::array compatibility_groups = {}; bool enable_chat_commands = true; char chat_command_sentinel = '\0'; // 0 = default (@ on 11/2000; $ on all other versions) @@ -312,6 +313,8 @@ struct ServerState : public std::enable_shared_from_this { uint8_t pre_lobby_event = 0; int32_t ep3_menu_song = -1; + std::unordered_map> client_for_account; + std::map all_addresses; uint32_t local_address = 0; uint32_t external_address = 0; diff --git a/system/config.example.json b/system/config.example.json index 1d3c5716..17184310 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -226,6 +226,11 @@ // Guild Card numbers every time they connect and cannot be banned by serial number or username. "AllowPCNTE": true, + // If this is enabled, players can log in multiple times on the same account at the same time. If this is disabled, + // only one login is allowed per account at a time; when a new session is opened, any existing sessions on the same + // account are disconnected. + "AllowSameAccountConcurrentLogins": true, + // Whether to enable chat commands for all players. If this is true, all players will be able to use chat commands as // normal; if this is false, only players with the ALWAYS_ENABLE_CHAT_COMMANDS account flag will be able to use chat // commands. diff --git a/tests/config.json b/tests/config.json index 50ac3dc1..a133a9e6 100644 --- a/tests/config.json +++ b/tests/config.json @@ -190,6 +190,7 @@ "AllowUnregisteredUsers": true, "UseTemporaryAccountsForPrototypes": true, "AllowPCNTE": true, + "AllowSameAccountConcurrentLogins": true, "EnableChatCommands": true, "CompatibilityGroups": [0x0000, 0x0000, 0x0004, 0x0008, 0x00B0, 0x00B0, 0x0040, 0x00B0, 0x0100, 0x1200, 0x0400, 0x0800, 0x1200, 0x2000], "VersionNameColors": [