From b780f5ee6003a4903aa33fadd8a09315730f9043 Mon Sep 17 00:00:00 2001 From: Gent Semaj Date: Sun, 23 Jun 2024 18:25:46 -0700 Subject: [PATCH] Enable account level changing at runtime (#282) * Enable account level change at runtime * PR feedback --- src/CustomCommands.cpp | 71 ++++++++++++++++++++++++++++++++++++++++-- src/PlayerManager.cpp | 7 ++++- src/db/Database.hpp | 6 +++- src/db/login.cpp | 46 +++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 4 deletions(-) diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index 969401a..368b8e0 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -83,7 +83,74 @@ static void helpCommand(std::string full, std::vector& args, CNSock } static void accessCommand(std::string full, std::vector& args, CNSocket* sock) { - Chat::sendServerMessage(sock, "Your access level is " + std::to_string(PlayerManager::getPlayer(sock)->accountLevel)); + if (args.size() < 2) { + Chat::sendServerMessage(sock, "Usage: /access [new_level]"); + Chat::sendServerMessage(sock, "Use . for id to select yourself"); + return; + } + + char *tmp; + + Player* self = PlayerManager::getPlayer(sock); + int selfAccess = self->accountLevel; + + Player* player; + if (args[1].compare(".") == 0) { + player = self; + } else { + int id = std::strtol(args[1].c_str(), &tmp, 10); + if (*tmp) { + Chat::sendServerMessage(sock, "Invalid player ID " + args[1]); + return; + } + + player = PlayerManager::getPlayerFromID(id); + if (player == nullptr) { + Chat::sendServerMessage(sock, "Could not find player with ID " + std::to_string(id)); + return; + } + + // Messing with other players requires a baseline access of 30 + if (player != self && selfAccess > 30) { + Chat::sendServerMessage(sock, "Can't check or change other players access levels (insufficient privileges)"); + return; + } + } + + std::string playerName = PlayerManager::getPlayerName(player); + int currentAccess = player->accountLevel; + if (args.size() < 3) { + // just check + Chat::sendServerMessage(sock, playerName + " has access level " + std::to_string(currentAccess)); + return; + } + + // Can't change the access level of someone with stronger privileges + // N.B. lower value = stronger privileges + if (currentAccess <= selfAccess) { + Chat::sendServerMessage(sock, "Can't change this player's access level (insufficient privileges)"); + return; + } + + int newAccess = std::strtol(args[2].c_str(), &tmp, 10); + if (*tmp) { + Chat::sendServerMessage(sock, "Invalid access level " + args[2]); + return; + } + + // Can only assign an access level weaker than yours + if (newAccess <= selfAccess) { + Chat::sendServerMessage(sock, "Can only assign privileges weaker than your own"); + return; + } + + player->accountLevel = newAccess; + + // Save to database + int accountId = Database::getAccountIdForPlayer(player->iID); + Database::updateAccountLevel(accountId, newAccess); + + Chat::sendServerMessage(sock, "Changed access level for " + playerName + " from " + std::to_string(currentAccess) + " to " + std::to_string(newAccess)); } static void populationCommand(std::string full, std::vector& args, CNSocket* sock) { @@ -1200,7 +1267,7 @@ static void registerCommand(std::string cmd, int requiredLevel, CommandHandler h void CustomCommands::init() { registerCommand("help", 100, helpCommand, "list all unlocked server-side commands"); - registerCommand("access", 100, accessCommand, "print your access level"); + registerCommand("access", 100, accessCommand, "check or change access levels"); registerCommand("instance", 30, instanceCommand, "print or change your current instance"); registerCommand("mss", 30, mssCommand, "edit Monkey Skyway routes"); registerCommand("npcr", 30, npcRotateCommand, "rotate NPCs"); diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index f74cd4d..e646b1e 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -212,7 +212,12 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { response.iID = plr->iID; response.uiSvrTime = getTime(); - response.PCLoadData2CL.iUserLevel = plr->accountLevel; + + // The only client-side use of the account level is to block + // the sending of GM packets. Since account level can be changed + // at runtime and we validate it serverside, we can leave this at 0. + response.PCLoadData2CL.iUserLevel = 0; // plr->accountLevel; + response.PCLoadData2CL.iHP = plr->HP; response.PCLoadData2CL.iLevel = plr->level; response.PCLoadData2CL.iCandy = plr->money; diff --git a/src/db/Database.hpp b/src/db/Database.hpp index 48fd0f6..8883137 100644 --- a/src/db/Database.hpp +++ b/src/db/Database.hpp @@ -46,9 +46,13 @@ namespace Database { void close(); void findAccount(Account* account, std::string login); - // returns ID, 0 if something failed + + // return ID, 0 if something failed + int getAccountIdForPlayer(int playerId); int addAccount(std::string login, std::string password); + void updateAccountLevel(int accountId, int accountLevel); + // interface for the /ban command bool banPlayer(int playerId, std::string& reason); bool unbanPlayer(int playerId); diff --git a/src/db/login.cpp b/src/db/login.cpp index fdb40d2..f3493ba 100644 --- a/src/db/login.cpp +++ b/src/db/login.cpp @@ -27,6 +27,32 @@ void Database::findAccount(Account* account, std::string login) { sqlite3_finalize(stmt); } +int Database::getAccountIdForPlayer(int playerId) { + std::lock_guard lock(dbCrit); + + const char* sql = R"( + SELECT AccountID + FROM Players + WHERE PlayerID = ? + LIMIT 1; + )"; + sqlite3_stmt* stmt; + + sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + sqlite3_bind_int(stmt, 1, playerId); + + int rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) { + std::cout << "[WARN] Database: couldn't get account id for player " << playerId << std::endl; + sqlite3_finalize(stmt); + return 0; + } + + int accountId = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + return accountId; +} + int Database::addAccount(std::string login, std::string password) { std::lock_guard lock(dbCrit); @@ -52,6 +78,26 @@ int Database::addAccount(std::string login, std::string password) { return sqlite3_last_insert_rowid(db); } +void Database::updateAccountLevel(int accountId, int accountLevel) { + std::lock_guard lock(dbCrit); + + const char* sql = R"( + UPDATE Accounts SET + AccountLevel = ? + WHERE AccountID = ?; + )"; + sqlite3_stmt* stmt; + + sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + sqlite3_bind_int(stmt, 1, accountLevel); + sqlite3_bind_int(stmt, 2, accountId); + + int rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) + std::cout << "[WARN] Database fail on updateAccountLevel(): " << sqlite3_errmsg(db) << std::endl; + sqlite3_finalize(stmt); +} + void Database::updateSelected(int accountId, int slot) { std::lock_guard lock(dbCrit);