Files
DarkflameServer/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp
Aaron Kimbrell 2d1ef0e370 feat: web dashboard and playground work
The NexusDashboard-parity dashboard (dDashboardServer) and everything built on it on the experimental branch:
accounts, characters, properties and moderation tools, permissions shared with in-game slash commands, economy
reports, World 3D and property 3D views with client scenery, scheduled events (features, vanity changes, live
events, announcements, restarts), vanity files and events, the CDClient browser, the message inspector with saved
captures, chat filter tools, community challenges, live ops, the AI moderator helper, and the server-side changes
they need.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:33:10 -05:00

326 lines
11 KiB
C++

#include "GMGreaterThanZeroCommands.h"
#include "DashboardNotify.h"
#include "SlashCommandHandler.h"
// Classes
#include "Character.h"
#include "ChatPackets.h"
#include "dServer.h"
#include "PlayerManager.h"
#include "User.h"
// Database
#include "Database.h"
// Components
#include "DestroyableComponent.h"
#include "PropertyManagementComponent.h"
// Enums
#include "MessageType/Chat.h"
#include "eServerDisconnectIdentifiers.h"
#include "eObjectBits.h"
namespace GMGreaterThanZeroCommands {
void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.size() == 1) {
auto* player = PlayerManager::GetPlayer(splitArgs[0]);
std::u16string username = GeneralUtils::UTF8ToUTF16(splitArgs[0]);
if (player == nullptr) {
ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + username);
return;
}
const auto target = SlashCommandHandler::TargetOf(player);
if (!target || !SlashCommandHandler::MayActOn(entity, sysAddr, *target, SlashCommandLevels::eTargetRule::TOOLS, "kick")) return;
Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::KICK);
ChatPackets::SendSystemMessage(sysAddr, u"Kicked: " + username);
} else {
ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /kick <username>");
}
}
void Ban(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.size() == 1) {
const auto target = SlashCommandHandler::FindTarget(splitArgs[0]);
if (!target) {
ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(splitArgs[0]));
return;
}
if (!SlashCommandHandler::MayActOn(entity, sysAddr, *target, SlashCommandLevels::eTargetRule::MODERATION, "ban")) return;
// The server always keeps a GM 9 account that can sign in
if (target->gmLevel >= AccountRules::OPERATOR_LEVEL &&
AccountRules::RemovesLastOperator(target->gmLevel, Database::Get()->CountActiveAccountsAtGmLevel(AccountRules::OPERATOR_LEVEL, target->accountId))) {
ChatPackets::SendSystemMessage(sysAddr, u"/ban: " + GeneralUtils::UTF8ToUTF16(AccountRules::LastOperatorMessage("banned")));
return;
}
const auto accountId = target->accountId;
if (accountId != 0) Database::Get()->UpdateAccountBan(accountId, true);
if (accountId != 0) DashboardNotify::Changed("account_banned", accountId);
if (target->entity != nullptr) {
Game::server->Disconnect(target->entity->GetSystemAddress(), eServerDisconnectIdentifiers::FREE_TRIAL_EXPIRED);
}
ChatPackets::SendSystemMessage(sysAddr, u"Banned: " + GeneralUtils::ASCIIToUTF16(splitArgs[0]));
} else {
ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /ban <username>");
}
}
void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.size() < 2) return;
const auto& playerName = splitArgs[0];
auto playerInfo = Database::Get()->GetCharacterInfo(playerName);
uint32_t receiverID = 0;
if (!playerInfo) {
ChatPackets::SendSystemMessage(sysAddr, u"Failed to find that player");
return;
}
receiverID = playerInfo->id;
const auto target = SlashCommandHandler::FindTarget(playerName);
if (!target || !SlashCommandHandler::MayActOn(entity, sysAddr, *target, SlashCommandLevels::eTargetRule::ITEMS, "mailitem")) return;
const auto lot = GeneralUtils::TryParse<LOT>(splitArgs.at(1));
if (!lot) {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid item lot.");
return;
}
MailInfo mailInsert;
mailInsert.senderId = entity->GetObjectID();
mailInsert.senderUsername = "Darkflame Universe";
mailInsert.receiverId = receiverID;
mailInsert.recipient = playerName;
mailInsert.subject = "Lost item";
mailInsert.body = "This is a replacement item for one you lost.";
mailInsert.itemID = LWOOBJID_EMPTY;
mailInsert.itemLOT = lot.value();
mailInsert.itemSubkey = LWOOBJID_EMPTY;
mailInsert.itemCount = 1;
Database::Get()->InsertNewMail(mailInsert);
DashboardNotify::Changed("mail", mailInsert.receiverId);
ChatPackets::SendSystemMessage(sysAddr, u"Mail sent");
}
void ApproveProperty(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
if (PropertyManagementComponent::Instance() != nullptr) {
PropertyManagementComponent::Instance()->UpdateApprovedStatus(true);
}
}
void Mute(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.size() >= 1) {
const auto target = SlashCommandHandler::FindTarget(splitArgs[0]);
if (!target) {
ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(splitArgs[0]));
return;
}
if (!SlashCommandHandler::MayActOn(entity, sysAddr, *target, SlashCommandLevels::eTargetRule::MODERATION, "mute")) return;
const uint32_t accountId = target->accountId;
const LWOOBJID characterId = target->characterId;
time_t expire = 1; // Default to indefinate mute
if (splitArgs.size() >= 2) {
const auto days = GeneralUtils::TryParse<uint32_t>(splitArgs[1]);
if (!days) {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid days.");
return;
}
std::optional<uint32_t> hours;
if (splitArgs.size() >= 3) {
hours = GeneralUtils::TryParse<uint32_t>(splitArgs[2]);
if (!hours) {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid hours.");
return;
}
}
expire = time(NULL);
expire += 24 * 60 * 60 * days.value();
expire += 60 * 60 * hours.value_or(0);
}
if (accountId != 0) Database::Get()->UpdateAccountUnmuteTime(accountId, expire);
if (accountId != 0) DashboardNotify::Changed("account_muted", accountId);
char buffer[32] = "brought up for review.\0";
if (expire != 1) {
std::tm* ptm = std::localtime(&expire);
// Format: Mo, 15.06.2009 20:20:00
std::strftime(buffer, 32, "%a, %d.%m.%Y %H:%M:%S", ptm);
}
const auto timeStr = GeneralUtils::ASCIIToUTF16(std::string(buffer));
ChatPackets::SendSystemMessage(sysAddr, u"Muted: " + GeneralUtils::UTF8ToUTF16(splitArgs[0]) + u" until " + timeStr);
//Notify chat about it
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::GM_MUTE);
bitStream.Write(characterId);
bitStream.Write(expire);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
} else {
ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /mute <username> <days (optional)> <hours (optional)>");
}
}
void Fly(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
auto* character = entity->GetCharacter();
if (character) {
bool isFlying = character->GetIsFlying();
if (isFlying) {
GameMessages::SendSetJetPackMode(entity, false);
character->SetIsFlying(false);
} else {
float speedScale = 1.0f;
if (splitArgs.size() >= 1) {
const auto tempScaleStore = GeneralUtils::TryParse<float>(splitArgs.at(0));
if (tempScaleStore) {
speedScale = tempScaleStore.value();
} else {
ChatPackets::SendSystemMessage(sysAddr, u"Failed to parse speed scale argument.");
}
}
float airSpeed = 20 * speedScale;
float maxAirSpeed = 30 * speedScale;
float verticalVelocity = 1.5 * speedScale;
GameMessages::SendSetJetPackMode(entity, true, true, false, 167, airSpeed, maxAirSpeed, verticalVelocity);
character->SetIsFlying(true);
}
}
}
void AttackImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
const auto state = GeneralUtils::TryParse<int32_t>(splitArgs[0]);
if (!state) {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid state.");
return;
}
if (destroyableComponent) destroyableComponent->SetIsImmune(state.value());
}
void GmImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
const auto state = GeneralUtils::TryParse<int32_t>(splitArgs[0]);
if (!state) {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid state.");
return;
}
if (destroyableComponent) destroyableComponent->SetIsGMImmune(state.value());
}
void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
GameMessages::ToggleGMInvisEvent msg;
msg.Send(entity->GetObjectID());
}
void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(args), UNASSIGNED_SYSTEM_ADDRESS);
}
void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
std::string name = entity->GetCharacter()->GetName() + " - " + args;
GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS);
}
void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
bool displayZoneData = true;
bool displayIndividualPlayers = true;
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (!splitArgs.empty() && !splitArgs.at(0).empty()) displayZoneData = splitArgs.at(0) == "1";
if (splitArgs.size() > 1) displayIndividualPlayers = splitArgs.at(1) == "1";
ShowAllRequest request {
.requestor = entity->GetObjectID(),
.displayZoneData = displayZoneData,
.displayIndividualPlayers = displayIndividualPlayers
};
CBITSTREAM;
request.Serialize(bitStream);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
if (args.empty()) {
GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given");
return;
}
FindPlayerRequest request {
.requestor = entity->GetObjectID(),
.playerName = LUWString(args)
};
CBITSTREAM;
request.Serialize(bitStream);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
if (args.empty()) {
GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, entity->GetObjectID());
return;
}
auto player = PlayerManager::GetPlayer(args);
if (!player) {
GameMessages::SendSlashCommandFeedbackText(entity, u"Player not found");
return;
}
GameMessages::SendSlashCommandFeedbackText(entity, u"Spectating Player");
GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, player->GetObjectID());
}
}