Files
DarkflameServer/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp
Aaron Kimbrell f4d3d794fd fix: construct a player in jetpack mode with its jetpack
LWOControllablePhysComponent::Deserialize (0x00c45770) reads the jetpack
mode, effect, flying and bypass checks bits on construction, and
LWOControllablePhysComponent::SendMessage (0x00d2f020) keeps the same
from SetJetPackMode. DLU sent the message but never kept it, so a player
who came into range of someone flying was told they had no jetpack.

Live player constructions in jetpack mode carried effect 167, flying 0
and bypass checks 1, and a 0 after the pickup radius (the client reads
that bit into the flying flag). DLU wrote the jetpack mode there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:26:46 -06:00

351 lines
12 KiB
C++

#include "GMGreaterThanZeroCommands.h"
#include "ControllablePhysicsComponent.h"
#include "ChatServerLink.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"
#include "EffectsMessages.h"
#include "MovementMessages.h"
#include "ObjectMessages.h"
#include "PlayerMessages.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
ChatPackets::GMMute mute;
mute.playerID = characterId;
mute.expire = expire;
ChatServerLink::Send(mute);
} 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::SetJetPackMode jetPackMode;
jetPackMode.target = entity->GetObjectID();
jetPackMode.bUse = false;
jetPackMode.Send(UNASSIGNED_SYSTEM_ADDRESS);
if (auto* const physics = entity->GetComponent<ControllablePhysicsComponent>()) physics->SetJetPackMode(jetPackMode.bUse, jetPackMode.effectID, jetPackMode.bBypassChecks);
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::SetJetPackMode jetPackMode;
jetPackMode.target = entity->GetObjectID();
jetPackMode.bUse = true;
jetPackMode.bBypassChecks = true;
jetPackMode.bDoHover = false;
jetPackMode.effectID = 167;
jetPackMode.fAirspeed = airSpeed;
jetPackMode.fMaxAirspeed = maxAirSpeed;
jetPackMode.fVertVel = verticalVelocity;
jetPackMode.Send(UNASSIGNED_SYSTEM_ADDRESS);
if (auto* const physics = entity->GetComponent<ControllablePhysicsComponent>()) physics->SetJetPackMode(jetPackMode.bUse, jetPackMode.effectID, jetPackMode.bBypassChecks);
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::SetName setName;
setName.target = entity->GetObjectID();
setName.name = GeneralUtils::UTF8ToUTF16(args);
setName.Send(UNASSIGNED_SYSTEM_ADDRESS);
}
void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
std::string name = entity->GetCharacter()->GetName() + " - " + args;
GameMessages::SetName setName;
setName.target = entity->GetObjectID();
setName.name = GeneralUtils::UTF8ToUTF16(name);
setName.Send(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";
ChatPackets::ShowAllRequest request;
request.requestor = entity->GetObjectID();
request.displayZoneData = displayZoneData;
request.displayIndividualPlayers = displayIndividualPlayers;
ChatServerLink::Send(request);
}
void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
if (args.empty()) {
GameMessages::SlashCommandTextFeedback(entity->GetObjectID(), u"No player Given").SendToClient(entity->GetSystemAddress());
return;
}
ChatPackets::FindPlayerRequest request;
request.requestor = entity->GetObjectID();
request.playerName = LUWString(args);
ChatServerLink::Send(request);
}
void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
if (args.empty()) {
GameMessages::ForceCameraTargetCycle cameraCycle;
cameraCycle.target = entity->GetObjectID();
cameraCycle.bForceCycling = false;
cameraCycle.cyclingMode = eCameraTargetCyclingMode::DISALLOW_CYCLING;
cameraCycle.optionalTargetID = entity->GetObjectID();
cameraCycle.SendToClient(entity->GetSystemAddress());
return;
}
auto player = PlayerManager::GetPlayer(args);
if (!player) {
GameMessages::SlashCommandTextFeedback(entity->GetObjectID(), u"Player not found").SendToClient(entity->GetSystemAddress());
return;
}
GameMessages::SlashCommandTextFeedback(entity->GetObjectID(), u"Spectating Player").SendToClient(entity->GetSystemAddress());
GameMessages::ForceCameraTargetCycle cameraCycle;
cameraCycle.target = entity->GetObjectID();
cameraCycle.bForceCycling = false;
cameraCycle.cyclingMode = eCameraTargetCyclingMode::DISALLOW_CYCLING;
cameraCycle.optionalTargetID = player->GetObjectID();
cameraCycle.SendToClient(entity->GetSystemAddress());
}
}