mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-15 16:27:14 +00:00
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>
351 lines
12 KiB
C++
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());
|
|
}
|
|
}
|