Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions data/migrations/61.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
function onUpdateDatabase()
logger.info("Updating database to version 61 (add player_bans and player_ban_history)")

db.query([[CREATE TABLE IF NOT EXISTS `player_bans` (
`player_id` int(11) NOT NULL,
`reason` varchar(255) NOT NULL,
`banned_at` bigint(20) NOT NULL,
`expires_at` bigint(20) NOT NULL,
`banned_by` int(11) NOT NULL,
INDEX `banned_by` (`banned_by`),
CONSTRAINT `player_bans_pk` PRIMARY KEY (`player_id`),
CONSTRAINT `player_bans_players_fk`
FOREIGN KEY (`player_id`) REFERENCES `players` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `player_bans_players2_fk`
FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;]])

db.query([[CREATE TABLE IF NOT EXISTS `player_ban_history` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`player_id` int(11) NOT NULL,
`reason` varchar(255) NOT NULL,
`banned_at` bigint(20) NOT NULL,
`expired_at` bigint(20) NOT NULL,
`banned_by` int(11) NOT NULL,
INDEX `player_id` (`player_id`),
INDEX `banned_by` (`banned_by`),
CONSTRAINT `player_ban_history_pk` PRIMARY KEY (`id`),
CONSTRAINT `player_ban_history_players_fk`
FOREIGN KEY (`player_id`) REFERENCES `players` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `player_ban_history_players2_fk`
FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;]])
end
11 changes: 11 additions & 0 deletions data/scripts/globalevents/server_initialization.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ local function moveExpiredBansToHistory()

Result.free(resultId)
end

resultId = db.storeQuery("SELECT * FROM `player_bans` WHERE `expires_at` != 0 AND `expires_at` <= " .. os.time())
if resultId then
repeat
local playerId = Result.getNumber(resultId, "player_id")
db.asyncQuery("INSERT INTO `player_ban_history` (`player_id`, `reason`, `banned_at`, `expired_at`, `banned_by`) VALUES (" .. playerId .. ", " .. db.escapeString(Result.getString(resultId, "reason")) .. ", " .. Result.getNumber(resultId, "banned_at") .. ", " .. Result.getNumber(resultId, "expires_at") .. ", " .. Result.getNumber(resultId, "banned_by") .. ")")
db.asyncQuery("DELETE FROM `player_bans` WHERE `player_id` = " .. playerId)
until not Result.next(resultId)

Result.free(resultId)
end
end

-- Function to store towns in the database
Expand Down
55 changes: 55 additions & 0 deletions data/scripts/talkactions/gm/player_ban.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
local playerBan = TalkAction("/playerban")

function playerBan.onSay(player, words, param)
-- create log
logCommand(player, words, param)

local params = param:split(",")
if #params < 3 then
player:sendCancelMessage("Command requires 3 parameters: /playerban <player name>, <duration in days>, <reason>")
return true
end

local playerName = params[1]:trim()
local banDuration = tonumber(params[2]:trim())
local banReason = params[3]:trim()

if not banDuration or banDuration <= 0 then
player:sendCancelMessage("Ban duration must be a positive number.")
return true
end

local resultId = db.storeQuery("SELECT `id` FROM `players` WHERE `name` = " .. db.escapeString(playerName))
if resultId == false then
player:sendCancelMessage("Player not found.")
return true
end

local targetGuid = Result.getNumber(resultId, "id")
Result.free(resultId)

resultId = db.storeQuery("SELECT 1 FROM `player_bans` WHERE `player_id` = " .. targetGuid)
if resultId ~= false then
player:sendTextMessage(MESSAGE_ADMINISTRATOR, playerName .. " is already banned.")
Result.free(resultId)
return true
end

local currentTime = os.time()
local expirationTime = currentTime + (banDuration * 24 * 60 * 60)
db.query(string.format("INSERT INTO `player_bans` (`player_id`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES (%d, %s, %d, %d, %d)", targetGuid, db.escapeString(banReason), currentTime, expirationTime, player:getGuid()))

local target = Player(playerName)
if target then
player:sendTextMessage(MESSAGE_ADMINISTRATOR, string.format("Character %s has been banned for %d days.", target:getName(), banDuration))
target:remove()
Webhook.sendMessage("Player Banned", string.format("Character %s has been banned for %d days. Reason: %s (by: %s)", target:getName(), banDuration, banReason, player:getName()), WEBHOOK_COLOR_YELLOW, announcementChannels["serverAnnouncements"])
else
player:sendTextMessage(MESSAGE_ADMINISTRATOR, string.format("Character %s has been banned for %d days.", playerName, banDuration))
end
return true
end

playerBan:separator(" ")
playerBan:groupType("gamemaster")
playerBan:register()
3 changes: 2 additions & 1 deletion data/scripts/talkactions/gm/unban.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ function unban.onSay(player, words, param)
return true
end

local resultId = db.storeQuery("SELECT `account_id`, `lastip` FROM `players` WHERE `name` = " .. db.escapeString(param))
local resultId = db.storeQuery("SELECT `id`, `account_id`, `lastip` FROM `players` WHERE `name` = " .. db.escapeString(param))
if resultId == false then
return true
end

db.asyncQuery("DELETE FROM `account_bans` WHERE `account_id` = " .. Result.getNumber(resultId, "account_id"))
db.asyncQuery("DELETE FROM `ip_bans` WHERE `ip` = " .. Result.getNumber(resultId, "lastip"))
db.asyncQuery("DELETE FROM `player_bans` WHERE `player_id` = " .. Result.getNumber(resultId, "id"))
Result.free(resultId)
local text = param .. " has been unbanned."
player:sendTextMessage(MESSAGE_ADMINISTRATOR, text)
Expand Down
42 changes: 41 additions & 1 deletion schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS `server_config` (
CONSTRAINT `server_config_pk` PRIMARY KEY (`config`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '60'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');
INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '61'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');

-- Table structure `accounts`
CREATE TABLE IF NOT EXISTS `accounts` (
Expand Down Expand Up @@ -599,6 +599,46 @@ CREATE TABLE IF NOT EXISTS `player_statements` (
FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Table structure `player_bans`
CREATE TABLE IF NOT EXISTS `player_bans` (
`player_id` int(11) NOT NULL,
`reason` varchar(255) NOT NULL,
`banned_at` bigint(20) NOT NULL,
`expires_at` bigint(20) NOT NULL,
`banned_by` int(11) NOT NULL,
INDEX `banned_by` (`banned_by`),
CONSTRAINT `player_bans_pk` PRIMARY KEY (`player_id`),
CONSTRAINT `player_bans_players_fk`
FOREIGN KEY (`player_id`) REFERENCES `players` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `player_bans_players2_fk`
FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Table structure `player_ban_history`
CREATE TABLE IF NOT EXISTS `player_ban_history` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`player_id` int(11) NOT NULL,
`reason` varchar(255) NOT NULL,
`banned_at` bigint(20) NOT NULL,
`expired_at` bigint(20) NOT NULL,
`banned_by` int(11) NOT NULL,
INDEX `player_id` (`player_id`),
INDEX `banned_by` (`banned_by`),
CONSTRAINT `player_ban_history_pk` PRIMARY KEY (`id`),
CONSTRAINT `player_ban_history_players_fk`
FOREIGN KEY (`player_id`) REFERENCES `players` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `player_ban_history_players2_fk`
FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Table structure `player_deaths`
CREATE TABLE IF NOT EXISTS `player_deaths` (
`player_id` int(11) NOT NULL,
Expand Down
30 changes: 30 additions & 0 deletions src/creatures/players/management/ban.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,36 @@ bool IOBan::isIpBanned(uint32_t clientIP, BanInfo &banInfo) {
return true;
}

bool IOBan::isPlayerBanned(uint32_t playerId, BanInfo &banInfo) {
Database &db = Database::getInstance();

std::ostringstream query;
query << "SELECT `reason`, `expires_at`, `banned_at`, `banned_by`, (SELECT `name` FROM `players` WHERE `id` = `banned_by`) AS `name` FROM `player_bans` WHERE `player_id` = " << playerId;

const DBResult_ptr result = db.storeQuery(query.str());
if (!result) {
return false;
}

const auto expiresAt = result->getNumber<int64_t>("expires_at");
if (expiresAt != 0 && time(nullptr) > expiresAt) {
// Move the ban to history if it has expired
query.str(std::string());
query << "INSERT INTO `player_ban_history` (`player_id`, `reason`, `banned_at`, `expired_at`, `banned_by`) VALUES (" << playerId << ',' << db.escapeString(result->getString("reason")) << ',' << result->getNumber<time_t>("banned_at") << ',' << expiresAt << ',' << result->getNumber<uint32_t>("banned_by") << ')';
g_databaseTasks().execute(query.str());

query.str(std::string());
query << "DELETE FROM `player_bans` WHERE `player_id` = " << playerId;
g_databaseTasks().execute(query.str());
return false;
}

banInfo.expiresAt = expiresAt;
banInfo.reason = result->getString("reason");
banInfo.bannedBy = result->getString("name");
return true;
}

bool IOBan::isPlayerNamelocked(uint32_t playerId) {
std::ostringstream query;
query << "SELECT 1 FROM `player_namelocks` WHERE `player_id` = " << playerId;
Expand Down
1 change: 1 addition & 0 deletions src/creatures/players/management/ban.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ class IOBan {
public:
static bool isAccountBanned(uint32_t accountId, BanInfo &banInfo);
static bool isIpBanned(uint32_t clientIP, BanInfo &banInfo);
static bool isPlayerBanned(uint32_t playerId, BanInfo &banInfo);
static bool isPlayerNamelocked(uint32_t playerId);
};
18 changes: 18 additions & 0 deletions src/server/network/protocol/protocolgame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,24 @@ void ProtocolGame::login(const std::string &name, uint32_t accountId, OperatingS
disconnectClient(ss.str());
return;
}

if (IOBan::isPlayerBanned(player->getGUID(), banInfo)) {
if (banInfo.reason.empty()) {
banInfo.reason = "(none)";
}

std::ostringstream ss;
if (banInfo.expiresAt > 0) {
ss << "Your character has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n"
<< banInfo.reason;
} else {
ss << "Your character has been permanently banned by " << banInfo.bannedBy << ".\n\nReason specified:\n"
<< banInfo.reason;
}

disconnectClient(ss.str());
return;
}
}

WaitingList &waitingList = WaitingList::getInstance();
Expand Down
Loading