Skip to content

Commit

Permalink
feat: configurable number of players per account (opentibiabr#2000)
Browse files Browse the repository at this point in the history
New keys on config.lua:
maxPlayersOnlinePerAccount = 1
maxPlayersOutsidePZPerAccount = 1

This replaces the old `onePlayerOnlinePerAccount`. Setting those numbers to 1 will effectively be equivalent to 'onePlayerOnlinePerAccount = true' from before. But you can tweak these more flexibly now.

By setting 'maxPlayersOnlinePerAccount' to greater than 1 and keeping 'maxPlayersOutsidePZPerAccount' you still effectively prevent players from playing multiple chars at once, but you allow them to use a training dummy, for example.

Players will be blocked from leaving PZ or using spells even from within PZ if more than 'maxPlayersOutsidePZPerAccount' are already outside of a protection zone.
  • Loading branch information
luan authored Dec 13, 2023
1 parent 1c01b7a commit e570944
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 55 deletions.
3 changes: 2 additions & 1 deletion config.lua.dist
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@ statusProtocolPort = 7171
maxPlayers = 0
serverName = "OTServBR-Global"
serverMotd = "Welcome to the OTServBR-Global!"
onePlayerOnlinePerAccount = true
statusTimeout = 5 * 1000
replaceKickOnLogin = true
maxPacketsPerSecond = 25
maxItem = 2000
maxContainer = 100
maxPlayersOnlinePerAccount = 1
maxPlayersOutsidePZPerAccount = 1

-- Packet Compression
-- Minimize network bandwith and reduce ping
Expand Down
10 changes: 0 additions & 10 deletions data-otservbr-global/lib/compat/compat.lua
Original file line number Diff line number Diff line change
Expand Up @@ -573,16 +573,6 @@ function getOnlinePlayers()
return result
end

function getPlayersByAccountNumber(accountNumber)
local result = {}
for _, player in ipairs(Game.getPlayers()) do
if player:getAccountId() == accountNumber then
result[#result + 1] = player:getId()
end
end
return result
end

function getPlayerGUIDByName(name)
local player = Player(name)
if player then
Expand Down
12 changes: 0 additions & 12 deletions data/libs/functions/game.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,6 @@ function Game.getHouseByPlayerGUID(playerGUID)
return nil
end

function Game.getPlayersByAccountNumber(accountNumber)
local result = {}
local players, player = Game.getPlayers()
for i = 1, #players do
player = players[i]
if player:getAccountId() == accountNumber then
result[#result + 1] = player
end
end
return result
end

function Game.getPlayersByIPAddress(ip, mask)
if not mask then
mask = 0xFFFFFFFF
Expand Down
2 changes: 2 additions & 0 deletions src/config/config_definitions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ enum ConfigKey_t : uint16_t {
MAX_MESSAGEBUFFER,
MAX_PACKETS_PER_SECOND,
MAX_PLAYERS,
MAX_PLAYERS_OUTSIDE_PZ_PER_ACCOUNT,
MAX_PLAYERS_PER_ACCOUNT,
MAX_SPEED_ATTACKONFIST,
METRICS_ENABLE_OSTREAM,
METRICS_ENABLE_PROMETHEUS,
Expand Down
3 changes: 2 additions & 1 deletion src/config/configmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ bool ConfigManager::load() {
}

loadBoolConfig(L, ALLOW_CHANGEOUTFIT, "allowChangeOutfit", true);
loadBoolConfig(L, ONE_PLAYER_ON_ACCOUNT, "onePlayerOnlinePerAccount", true);
loadIntConfig(L, MAX_PLAYERS_PER_ACCOUNT, "maxPlayersOnlinePerAccount", 1);
loadIntConfig(L, MAX_PLAYERS_OUTSIDE_PZ_PER_ACCOUNT, "maxPlayersOutsidePZPerAccount", 1);
loadBoolConfig(L, AIMBOT_HOTKEY_ENABLED, "hotkeyAimbotEnabled", true);
loadBoolConfig(L, REMOVE_RUNE_CHARGES, "removeChargesFromRunes", true);
loadBoolConfig(L, EXPERIENCE_FROM_PLAYERS, "experienceByKillingPlayers", false);
Expand Down
19 changes: 19 additions & 0 deletions src/creatures/combat/spells.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,25 @@ Spells::Spells() = default;
Spells::~Spells() = default;

TalkActionResult_t Spells::playerSaySpell(std::shared_ptr<Player> player, std::string &words) {
auto maxOnline = g_configManager().getNumber(MAX_PLAYERS_PER_ACCOUNT, __FUNCTION__);
auto tile = player->getTile();
if (maxOnline > 1 && player->getAccountType() < account::ACCOUNT_TYPE_GAMEMASTER && tile && !tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
auto maxOutsizePZ = g_configManager().getNumber(MAX_PLAYERS_OUTSIDE_PZ_PER_ACCOUNT, __FUNCTION__);
auto accountPlayers = g_game().getPlayersByAccount(player->getAccount());
int countOutsizePZ = 0;
for (const auto &accountPlayer : accountPlayers) {
if (accountPlayer == player || accountPlayer->isOffline()) {
continue;
}
if (accountPlayer->getTile() && !accountPlayer->getTile()->hasFlag(TILESTATE_PROTECTIONZONE)) {
++countOutsizePZ;
}
}
if (countOutsizePZ >= maxOutsizePZ) {
player->sendTextMessage(MESSAGE_FAILURE, fmt::format("You cannot cast spells while you have {} character(s) outside of a protection zone.", maxOutsizePZ));
return TALKACTION_FAILED;
}
}
std::string str_words = words;

if (player->hasCondition(CONDITION_FEARED)) {
Expand Down
32 changes: 14 additions & 18 deletions src/creatures/players/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5987,32 +5987,28 @@ void Player::clearModalWindows() {
}

uint16_t Player::getHelpers() const {
uint16_t helpers;

if (guild && party) {
phmap::flat_hash_set<std::shared_ptr<Player>> helperSet;
const auto &guildMembers = guild->getMembersOnline();

const auto guildMembers = guild->getMembersOnline();
helperSet.insert(guildMembers.begin(), guildMembers.end());
stdext::vector_set<std::shared_ptr<Player>> helperSet;
helperSet.insert(helperSet.end(), guildMembers.begin(), guildMembers.end());
helperSet.insertAll(party->getMembers());
helperSet.insertAll(party->getInvitees());

const auto partyMembers = party->getMembers();
helperSet.insert(partyMembers.begin(), partyMembers.end());
helperSet.emplace(party->getLeader());

const auto partyInvitees = party->getInvitees();
helperSet.insert(partyInvitees.begin(), partyInvitees.end());
return static_cast<uint16_t>(helperSet.size());
}

helperSet.insert(party->getLeader());
if (guild) {
return static_cast<uint16_t>(guild->getMemberCountOnline());
}

helpers = helperSet.size();
} else if (guild) {
helpers = guild->getMemberCountOnline();
} else if (party) {
helpers = party->getMemberCount() + party->getInvitationCount() + 1;
} else {
helpers = 0;
if (party) {
return static_cast<uint16_t>(party->getMemberCount() + party->getInvitationCount() + 1);
}

return helpers;
return 0u;
}

void Player::sendClosePrivate(uint16_t channelId) {
Expand Down
16 changes: 11 additions & 5 deletions src/game/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -830,13 +830,19 @@ ReturnValue Game::getPlayerByNameWildcard(const std::string &s, std::shared_ptr<
return RETURNVALUE_NOERROR;
}

std::shared_ptr<Player> Game::getPlayerByAccount(uint32_t acc) {
for (const auto &it : players) {
if (it.second->getAccountId() == acc) {
return it.second;
std::vector<std::shared_ptr<Player>> Game::getPlayersByAccount(std::shared_ptr<account::Account> acc, bool allowOffline /* = false */) {
auto [accountPlayers, error] = acc->getAccountPlayers();
if (error != account::ERROR_NO) {
return {};
}
std::vector<std::shared_ptr<Player>> ret;
for (const auto &[name, _] : accountPlayers) {
auto player = getPlayerByName(name, allowOffline);
if (player) {
ret.push_back(player);
}
}
return nullptr;
return ret;
}

bool Game::internalPlaceCreature(std::shared_ptr<Creature> creature, const Position &pos, bool extendedPos /*=false*/, bool forced /*= false*/, bool creatureCheck /*= false*/) {
Expand Down
2 changes: 1 addition & 1 deletion src/game/game.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ class Game {

ReturnValue getPlayerByNameWildcard(const std::string &s, std::shared_ptr<Player> &player);

std::shared_ptr<Player> getPlayerByAccount(uint32_t acc);
std::vector<std::shared_ptr<Player>> getPlayersByAccount(std::shared_ptr<account::Account> acc, bool allowOffline = false);

bool internalPlaceCreature(std::shared_ptr<Creature> creature, const Position &pos, bool extendedPos = false, bool forced = false, bool creatureCheck = false);

Expand Down
21 changes: 21 additions & 0 deletions src/items/tile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,27 @@ ReturnValue Tile::queryAdd(int32_t, const std::shared_ptr<Thing> &thing, uint32_
}

const auto playerTile = player->getTile();
// moving from a pz tile to a non-pz tile
if (playerTile && playerTile->hasFlag(TILESTATE_PROTECTIONZONE)) {
auto maxOnline = g_configManager().getNumber(MAX_PLAYERS_PER_ACCOUNT, __FUNCTION__);
if (maxOnline > 1 && player->getAccountType() < account::ACCOUNT_TYPE_GAMEMASTER && !hasFlag(TILESTATE_PROTECTIONZONE)) {
auto maxOutsizePZ = g_configManager().getNumber(MAX_PLAYERS_OUTSIDE_PZ_PER_ACCOUNT, __FUNCTION__);
auto accountPlayers = g_game().getPlayersByAccount(player->getAccount());
int countOutsizePZ = 0;
for (const auto &accountPlayer : accountPlayers) {
if (accountPlayer == player || accountPlayer->isOffline()) {
continue;
}
if (accountPlayer->getTile() && !accountPlayer->getTile()->hasFlag(TILESTATE_PROTECTIONZONE)) {
++countOutsizePZ;
}
}
if (countOutsizePZ >= maxOutsizePZ) {
player->sendCreatureSay(player, TALKTYPE_MONSTER_SAY, fmt::format("You can only have {} character{} from your account outside of a protection zone.", maxOutsizePZ == 1 ? "one" : std::to_string(maxOutsizePZ), maxOutsizePZ > 1 ? "s" : ""), &getPosition());
return RETURNVALUE_NOTPOSSIBLE;
}
}
}
if (playerTile && player->isPzLocked()) {
if (!playerTile->hasFlag(TILESTATE_PVPZONE)) {
// player is trying to enter a pvp zone while being pz-locked
Expand Down
10 changes: 5 additions & 5 deletions src/lua/functions/creatures/npc/npc_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,6 @@ int NpcFunctions::luaNpcOpenShopWindowTable(lua_State* L) {
lua_pushnil(L);
while (lua_next(L, 3) != 0) {
const auto tableIndex = lua_gettop(L);
ShopBlock item;

auto itemId = getField<uint16_t>(L, tableIndex, "clientId");
auto subType = getField<int32_t>(L, tableIndex, "subType");
Expand All @@ -397,10 +396,11 @@ int NpcFunctions::luaNpcOpenShopWindowTable(lua_State* L) {
auto sellPrice = getField<uint32_t>(L, tableIndex, "sell");
auto storageKey = getField<int32_t>(L, tableIndex, "storageKey");
auto storageValue = getField<int32_t>(L, tableIndex, "storageValue");
auto realName = getFieldString(L, tableIndex, "name");
g_logger().debug("[{}] item '{}' sell price '{}', buyprice '{}'", __FUNCTION__, realName, sellPrice, buyPrice);

items.emplace_back(itemId, subType, buyPrice, sellPrice, storageKey, storageValue, std::move(realName));
auto itemName = getFieldString(L, tableIndex, "itemName");
if (itemName.empty()) {
itemName = Item::items[itemId].name;
}
items.emplace_back(itemId, subType, buyPrice, sellPrice, storageKey, storageValue, itemName);
lua_pop(L, 8);
}
lua_pop(L, 3);
Expand Down
24 changes: 22 additions & 2 deletions src/server/network/protocol/protocolgame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -515,9 +515,11 @@ void ProtocolGame::login(const std::string &name, uint32_t accountId, OperatingS
return;
}

if (g_configManager().getBoolean(ONE_PLAYER_ON_ACCOUNT, __FUNCTION__) && player->getAccountType() < account::ACCOUNT_TYPE_GAMEMASTER && g_game().getPlayerByAccount(player->getAccountId())) {
auto onlineCount = g_game().getPlayersByAccount(player->getAccount()).size();
auto maxOnline = g_configManager().getNumber(MAX_PLAYERS_PER_ACCOUNT, __FUNCTION__);
if (player->getAccountType() < account::ACCOUNT_TYPE_GAMEMASTER && onlineCount >= maxOnline) {
g_game().removePlayerUniqueLogin(player);
disconnectClient("You may only login with one character\nof your account at the same time.");
disconnectClient(fmt::format("You may only login with {} character{}\nof your account at the same time.", maxOnline, maxOnline > 1 ? "s" : ""));
return;
}

Expand Down Expand Up @@ -570,6 +572,24 @@ void ProtocolGame::login(const std::string &name, uint32_t accountId, OperatingS

player->setOperatingSystem(operatingSystem);

const auto tile = g_game().map.getOrCreateTile(player->getLoginPosition());
// moving from a pz tile to a non-pz tile
if (maxOnline > 1 && player->getAccountType() < account::ACCOUNT_TYPE_GAMEMASTER && !tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
auto maxOutsizePZ = g_configManager().getNumber(MAX_PLAYERS_OUTSIDE_PZ_PER_ACCOUNT, __FUNCTION__);
auto accountPlayers = g_game().getPlayersByAccount(player->getAccount());
int countOutsizePZ = 0;
for (const auto &accountPlayer : accountPlayers) {
if (accountPlayer != player && accountPlayer->getTile() && !accountPlayer->getTile()->hasFlag(TILESTATE_PROTECTIONZONE)) {
++countOutsizePZ;
}
}
if (countOutsizePZ >= maxOutsizePZ) {
g_game().removePlayerUniqueLogin(player);
disconnectClient(fmt::format("You can only have {} character{} from your account outside of a protection zone.", maxOutsizePZ == 1 ? "one" : std::to_string(maxOutsizePZ), maxOutsizePZ > 1 ? "s" : ""));
return;
}
}

if (!g_game().placeCreature(player, player->getLoginPosition()) && !g_game().placeCreature(player, player->getTemplePosition(), false, true)) {
g_game().removePlayerUniqueLogin(player);
disconnectClient("Temple position is wrong. Please, contact the administrator.");
Expand Down

0 comments on commit e570944

Please sign in to comment.