mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-06-25 08:04:22 +00:00
Replace old dNavigation/dTerrain raw parser with new Raw module in dZoneManager. Parse heightmaps, color maps, and scene maps from .raw files to determine which scene a position belongs to. Build scene adjacency graph from terrain data and scene transitions. Adds NiColor type, SceneColor lookup table, eSceneType enum, terrain mesh generation with OBJ export, and debug slash commands for scene visualization. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2163 lines
81 KiB
C++
2163 lines
81 KiB
C++
#include "DEVGMCommands.h"
|
|
|
|
#include <ranges>
|
|
|
|
// Classes
|
|
#include "AssetManager.h"
|
|
#include "Character.h"
|
|
#include "ChatPackets.h"
|
|
#include "dConfig.h"
|
|
#include "dNavMesh.h"
|
|
#include "dpWorld.h"
|
|
#include "dServer.h"
|
|
#include "dpShapeSphere.h"
|
|
#include "dZoneManager.h"
|
|
#include "EntityInfo.h"
|
|
#include "Metrics.h"
|
|
#include "PlayerManager.h"
|
|
#include "SlashCommandHandler.h"
|
|
#include "UserManager.h"
|
|
#include "User.h"
|
|
#include "VanityUtilities.h"
|
|
#include "WorldPackets.h"
|
|
#include "ZoneInstanceManager.h"
|
|
|
|
// Database
|
|
#include "Database.h"
|
|
#include "CDObjectsTable.h"
|
|
#include "CDRewardCodesTable.h"
|
|
#include "CDLootMatrixTable.h"
|
|
#include "CDLootTableTable.h"
|
|
|
|
// Components
|
|
#include "BuffComponent.h"
|
|
#include "CharacterComponent.h"
|
|
#include "ControllablePhysicsComponent.h"
|
|
#include "DestroyableComponent.h"
|
|
#include "HavokVehiclePhysicsComponent.h"
|
|
#include "InventoryComponent.h"
|
|
#include "LevelProgressionComponent.h"
|
|
#include "MissionComponent.h"
|
|
#include "MovingPlatformComponent.h"
|
|
#include "PossessorComponent.h"
|
|
#include "ProximityMonitorComponent.h"
|
|
#include "RenderComponent.h"
|
|
#include "ScriptedActivityComponent.h"
|
|
#include "SkillComponent.h"
|
|
#include "TriggerComponent.h"
|
|
#include "RigidbodyPhantomPhysicsComponent.h"
|
|
#include "PhantomPhysicsComponent.h"
|
|
|
|
// Enums
|
|
#include "eGameMasterLevel.h"
|
|
#include "MessageType/Master.h"
|
|
#include "eInventoryType.h"
|
|
#include "ePlayerFlag.h"
|
|
#include "StringifiedEnum.h"
|
|
#include "BinaryPathFinder.h"
|
|
|
|
namespace DEVGMCommands {
|
|
void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
User* user = UserManager::Instance()->GetUser(entity->GetSystemAddress());
|
|
|
|
const auto level_intermed = GeneralUtils::TryParse<uint32_t>(args);
|
|
if (!level_intermed) {
|
|
GameMessages::SendSlashCommandFeedbackText(entity, u"Invalid GM level.");
|
|
return;
|
|
}
|
|
eGameMasterLevel level = static_cast<eGameMasterLevel>(level_intermed.value());
|
|
|
|
#ifndef DEVELOPER_SERVER
|
|
if (user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER) {
|
|
level = eGameMasterLevel::CIVILIAN;
|
|
}
|
|
#endif
|
|
|
|
if (level > user->GetMaxGMLevel()) level = user->GetMaxGMLevel();
|
|
|
|
if (level == entity->GetGMLevel()) return;
|
|
bool success = user->GetMaxGMLevel() >= level;
|
|
|
|
if (success) {
|
|
WorldPackets::SendGMLevelChange(entity->GetSystemAddress(), success, user->GetMaxGMLevel(), entity->GetGMLevel(), level);
|
|
GameMessages::SendChatModeUpdate(entity->GetObjectID(), level);
|
|
entity->SetGMLevel(level);
|
|
LOG("User %s (%i) has changed their GM level to %i for charID %llu", user->GetUsername().c_str(), user->GetAccountID(), level, entity->GetObjectID());
|
|
}
|
|
|
|
#ifndef DEVELOPER_SERVER
|
|
if ((entity->GetGMLevel() > user->GetMaxGMLevel()) || (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN && user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER)) {
|
|
WorldPackets::SendGMLevelChange(entity->GetSystemAddress(), true, user->GetMaxGMLevel(), entity->GetGMLevel(), eGameMasterLevel::CIVILIAN);
|
|
GameMessages::SendChatModeUpdate(entity->GetObjectID(), eGameMasterLevel::CIVILIAN);
|
|
entity->SetGMLevel(eGameMasterLevel::CIVILIAN);
|
|
|
|
GameMessages::ToggleGMInvis msg;
|
|
msg.Send(entity->GetObjectID());
|
|
|
|
GameMessages::SendSlashCommandFeedbackText(entity, u"Your game master level has been changed, you may not be able to use all commands.");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
if ((Game::config->GetValue("allow_nameplate_off") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER)) return;
|
|
|
|
auto* character = entity->GetCharacter();
|
|
if (character && character->GetBillboardVisible()) {
|
|
character->SetBillboardVisible(false);
|
|
GameMessages::SendSlashCommandFeedbackText(entity, u"Your nameplate has been turned off and is not visible to players currently in this zone.");
|
|
} else {
|
|
character->SetBillboardVisible(true);
|
|
GameMessages::SendSlashCommandFeedbackText(entity, u"Your nameplate is now on and visible to all players.");
|
|
}
|
|
}
|
|
|
|
void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER) return;
|
|
auto* character = entity->GetCharacter();
|
|
if (!character) return;
|
|
bool current = character->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS);
|
|
character->SetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS, !current);
|
|
if (!current) {
|
|
GameMessages::SendSlashCommandFeedbackText(entity, u"You have elected to skip cinematics. Note that not all cinematics can be skipped, but most will be skipped now.");
|
|
} else {
|
|
GameMessages::SendSlashCommandFeedbackText(entity, u"Cinematics will no longer be skipped.");
|
|
}
|
|
}
|
|
|
|
void ResetMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
const auto missionId = GeneralUtils::TryParse<uint32_t>(splitArgs[0]);
|
|
if (!missionId) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission ID.");
|
|
return;
|
|
}
|
|
|
|
auto* missionComponent = entity->GetComponent<MissionComponent>();
|
|
if (!missionComponent) return;
|
|
missionComponent->ResetMission(missionId.value());
|
|
}
|
|
|
|
void SetMinifig(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.size() < 2) return;
|
|
|
|
const auto minifigItemIdExists = GeneralUtils::TryParse<int32_t>(splitArgs[1]);
|
|
if (!minifigItemIdExists) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid Minifig Item Id ID.");
|
|
return;
|
|
}
|
|
const int32_t minifigItemId = minifigItemIdExists.value();
|
|
Game::entityManager->DestructEntity(entity, sysAddr);
|
|
auto* charComp = entity->GetComponent<CharacterComponent>();
|
|
std::string lowerName = splitArgs[0];
|
|
if (lowerName.empty()) return;
|
|
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
|
|
if (lowerName == "eyebrows") {
|
|
charComp->m_Character->SetEyebrows(minifigItemId);
|
|
} else if (lowerName == "eyes") {
|
|
charComp->m_Character->SetEyes(minifigItemId);
|
|
} else if (lowerName == "haircolor") {
|
|
charComp->m_Character->SetHairColor(minifigItemId);
|
|
} else if (lowerName == "hairstyle") {
|
|
charComp->m_Character->SetHairStyle(minifigItemId);
|
|
} else if (lowerName == "pants") {
|
|
charComp->m_Character->SetPantsColor(minifigItemId);
|
|
} else if (lowerName == "lefthand") {
|
|
charComp->m_Character->SetLeftHand(minifigItemId);
|
|
} else if (lowerName == "mouth") {
|
|
charComp->m_Character->SetMouth(minifigItemId);
|
|
} else if (lowerName == "righthand") {
|
|
charComp->m_Character->SetRightHand(minifigItemId);
|
|
} else if (lowerName == "shirtcolor") {
|
|
charComp->m_Character->SetShirtColor(minifigItemId);
|
|
} else if (lowerName == "hands") {
|
|
charComp->m_Character->SetLeftHand(minifigItemId);
|
|
charComp->m_Character->SetRightHand(minifigItemId);
|
|
} else {
|
|
Game::entityManager->ConstructEntity(entity);
|
|
Game::entityManager->ConstructEntity(entity, entity->GetSystemAddress());
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid Minifig item to change, try one of the following: Eyebrows, Eyes, HairColor, HairStyle, Pants, LeftHand, Mouth, RightHand, Shirt, Hands");
|
|
return;
|
|
}
|
|
|
|
Game::entityManager->ConstructEntity(entity);
|
|
Game::entityManager->ConstructEntity(entity, entity->GetSystemAddress());
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(lowerName) + u" set to " + (GeneralUtils::to_u16string(minifigItemId)));
|
|
|
|
}
|
|
|
|
void PlayAnimation(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
std::u16string anim = GeneralUtils::ASCIIToUTF16(splitArgs[0], splitArgs[0].size());
|
|
RenderComponent::PlayAnimation(entity, anim);
|
|
auto* possessorComponent = entity->GetComponent<PossessorComponent>();
|
|
if (possessorComponent) {
|
|
auto* possessedComponent = Game::entityManager->GetEntity(possessorComponent->GetPossessable());
|
|
if (possessedComponent) RenderComponent::PlayAnimation(possessedComponent, anim);
|
|
}
|
|
}
|
|
|
|
void ListSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
for (const auto& pair : Game::entityManager->GetSpawnPointEntities()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(pair.first));
|
|
}
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Current: " + GeneralUtils::ASCIIToUTF16(entity->GetCharacter()->GetTargetScene()));
|
|
}
|
|
|
|
void UnlockEmote(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
const auto emoteID = GeneralUtils::TryParse<int32_t>(splitArgs[0]);
|
|
|
|
if (!emoteID) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid emote ID.");
|
|
return;
|
|
}
|
|
|
|
entity->GetCharacter()->UnlockEmote(emoteID.value());
|
|
}
|
|
|
|
void ForceSave(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
entity->GetCharacter()->SaveXMLToDatabase();
|
|
}
|
|
|
|
void Kill(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Brutally murdering that player, if online on this server.");
|
|
|
|
auto* player = PlayerManager::GetPlayer(splitArgs[0]);
|
|
if (player) {
|
|
player->Smash(entity->GetObjectID());
|
|
ChatPackets::SendSystemMessage(sysAddr, u"It has been done, do you feel good about yourself now?");
|
|
return;
|
|
}
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"They were saved from your carnage.");
|
|
return;
|
|
}
|
|
|
|
void SpeedBoost(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
const auto boostOptional = GeneralUtils::TryParse<float>(splitArgs[0]);
|
|
if (!boostOptional) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost.");
|
|
return;
|
|
}
|
|
const float boost = boostOptional.value();
|
|
|
|
auto* controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>();
|
|
|
|
if (!controllablePhysicsComponent) return;
|
|
controllablePhysicsComponent->SetSpeedMultiplier(boost);
|
|
|
|
// speedboost possessables
|
|
auto possessor = entity->GetComponent<PossessorComponent>();
|
|
if (possessor) {
|
|
auto possessedID = possessor->GetPossessable();
|
|
if (possessedID != LWOOBJID_EMPTY) {
|
|
auto possessable = Game::entityManager->GetEntity(possessedID);
|
|
if (possessable) {
|
|
auto* possessControllablePhysicsComponent = possessable->GetComponent<ControllablePhysicsComponent>();
|
|
if (possessControllablePhysicsComponent) {
|
|
possessControllablePhysicsComponent->SetSpeedMultiplier(boost);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Game::entityManager->SerializeEntity(entity);
|
|
}
|
|
|
|
void Freecam(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto state = !entity->GetVar<bool>(u"freecam");
|
|
entity->SetVar<bool>(u"freecam", state);
|
|
|
|
GameMessages::SendSetPlayerControlScheme(entity, static_cast<eControlScheme>(state ? 9 : 1));
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Toggled freecam.");
|
|
}
|
|
|
|
void SetControlScheme(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
const auto scheme = GeneralUtils::TryParse<uint32_t>(splitArgs[0]);
|
|
|
|
if (!scheme) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid control scheme.");
|
|
return;
|
|
}
|
|
|
|
GameMessages::SendSetPlayerControlScheme(entity, static_cast<eControlScheme>(scheme.value()));
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Switched control scheme.");
|
|
}
|
|
|
|
void SetUiState(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
AMFArrayValue uiState;
|
|
|
|
uiState.Insert("state", splitArgs[0]);
|
|
|
|
GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, "pushGameState", uiState);
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Switched UI state.");
|
|
}
|
|
|
|
void Toggle(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
AMFArrayValue amfArgs;
|
|
|
|
amfArgs.Insert("visible", true);
|
|
|
|
GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, splitArgs[0], amfArgs);
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Toggled UI state.");
|
|
}
|
|
|
|
void SetInventorySize(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
const auto sizeOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[0]);
|
|
if (!sizeOptional) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid size.");
|
|
return;
|
|
}
|
|
const uint32_t size = sizeOptional.value();
|
|
|
|
eInventoryType selectedInventory = eInventoryType::ITEMS;
|
|
|
|
// a possible inventory was provided if we got more than 1 argument
|
|
if (splitArgs.size() >= 2) {
|
|
selectedInventory = GeneralUtils::TryParse<eInventoryType>(splitArgs.at(1)).value_or(eInventoryType::INVALID);
|
|
if (selectedInventory == eInventoryType::INVALID) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid inventory.");
|
|
return;
|
|
} else {
|
|
// In this case, we treat the input as a string and try to find it in the reflection list
|
|
std::transform(splitArgs.at(1).begin(), splitArgs.at(1).end(), splitArgs.at(1).begin(), ::toupper);
|
|
for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) {
|
|
if (std::string_view(splitArgs.at(1)) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) selectedInventory = static_cast<eInventoryType>(index);
|
|
}
|
|
}
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory " +
|
|
GeneralUtils::ASCIIToUTF16(splitArgs.at(1)) +
|
|
u" to size " +
|
|
GeneralUtils::to_u16string(size));
|
|
} else ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory ITEMS to size " + GeneralUtils::to_u16string(size));
|
|
|
|
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
|
if (inventoryComponent) {
|
|
auto* inventory = inventoryComponent->GetInventory(selectedInventory);
|
|
|
|
inventory->SetSize(size);
|
|
}
|
|
}
|
|
|
|
void HandleMacro(Entity& entity, const SystemAddress& sysAddr, std::istream& inStream) {
|
|
if (inStream.good()) {
|
|
std::string line;
|
|
while (std::getline(inStream, line)) {
|
|
// Do this in two separate calls to catch both \n and \r\n
|
|
line.erase(std::remove(line.begin(), line.end(), '\n'), line.end());
|
|
line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());
|
|
SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16(line), &entity, sysAddr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RunMacro(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
// Only process if input does not contain separator charaters
|
|
if (splitArgs[0].find("/") != std::string::npos) return;
|
|
if (splitArgs[0].find("\\") != std::string::npos) return;
|
|
|
|
const auto resServerPath = BinaryPathFinder::GetBinaryDir() / "resServer";
|
|
auto infile = Game::assetManager->GetFile("macros/" + splitArgs[0] + ".scm");
|
|
auto resServerInFile = std::ifstream(resServerPath / "macros" / (splitArgs[0] + ".scm"));
|
|
if (!infile.good() && !resServerInFile.good()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?");
|
|
return;
|
|
}
|
|
|
|
HandleMacro(*entity, sysAddr, infile);
|
|
HandleMacro(*entity, sysAddr, resServerInFile);
|
|
}
|
|
|
|
void AddMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
const auto missionID = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0));
|
|
|
|
if (!missionID) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id.");
|
|
return;
|
|
}
|
|
|
|
auto comp = static_cast<MissionComponent*>(entity->GetComponent(eReplicaComponentType::MISSION));
|
|
if (comp) comp->AcceptMission(missionID.value(), true);
|
|
}
|
|
|
|
void CompleteMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
const auto missionID = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0));
|
|
|
|
if (!missionID) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id.");
|
|
return;
|
|
}
|
|
|
|
auto comp = static_cast<MissionComponent*>(entity->GetComponent(eReplicaComponentType::MISSION));
|
|
if (comp) comp->CompleteMission(missionID.value(), true);
|
|
}
|
|
|
|
void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.size() == 1) {
|
|
const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(0));
|
|
|
|
if (!flagId) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id.");
|
|
return;
|
|
}
|
|
|
|
entity->GetCharacter()->SetPlayerFlag(flagId.value(), true);
|
|
} else if (splitArgs.size() >= 2) {
|
|
const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(1));
|
|
std::string onOffFlag = splitArgs.at(0);
|
|
if (!flagId) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id.");
|
|
return;
|
|
}
|
|
|
|
if (onOffFlag != "off" && onOffFlag != "on") {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type.");
|
|
return;
|
|
}
|
|
|
|
entity->GetCharacter()->SetPlayerFlag(flagId.value(), onOffFlag == "on");
|
|
}
|
|
}
|
|
|
|
void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(0));
|
|
|
|
if (!flagId) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id.");
|
|
return;
|
|
}
|
|
|
|
entity->GetCharacter()->SetPlayerFlag(flagId.value(), false);
|
|
}
|
|
|
|
void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.size() < 3) return;
|
|
|
|
const auto effectID = GeneralUtils::TryParse<int32_t>(splitArgs.at(0));
|
|
|
|
if (!effectID) return;
|
|
|
|
// FIXME: use fallible ASCIIToUTF16 conversion, because non-ascii isn't valid anyway
|
|
GameMessages::SendPlayFXEffect(entity->GetObjectID(), effectID.value(), GeneralUtils::ASCIIToUTF16(splitArgs.at(1)), splitArgs.at(2));
|
|
}
|
|
|
|
void StopEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
GameMessages::SendStopFXEffect(entity, true, splitArgs[0]);
|
|
}
|
|
|
|
void SetAnnTitle(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
entity->GetCharacter()->SetAnnouncementTitle(args);
|
|
}
|
|
|
|
void SetAnnMsg(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
entity->GetCharacter()->SetAnnouncementMessage(args);
|
|
}
|
|
|
|
void Announce(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
if (entity->GetCharacter()->GetAnnouncementTitle().empty() || entity->GetCharacter()->GetAnnouncementMessage().empty()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Use /setanntitle <title> & /setannmsg <msg> first!");
|
|
return;
|
|
}
|
|
|
|
SlashCommandHandler::SendAnnouncement(entity->GetCharacter()->GetAnnouncementTitle(), entity->GetCharacter()->GetAnnouncementMessage());
|
|
}
|
|
|
|
void ShutdownUniverse(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
//Tell the master server that we're going to be shutting down whole "universe":
|
|
CBITSTREAM;
|
|
BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SHUTDOWN_UNIVERSE);
|
|
Game::server->SendToMaster(bitStream);
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Sent universe shutdown notification to master.");
|
|
|
|
//Tell chat to send an announcement to all servers
|
|
SlashCommandHandler::SendAnnouncement("Servers Closing Soon!", "DLU servers will close for maintenance in 10 minutes from now.");
|
|
}
|
|
|
|
void GetNavmeshHeight(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto control = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS));
|
|
if (!control) return;
|
|
|
|
float y = dpWorld::GetNavMesh()->GetHeightAtPoint(control->GetPosition());
|
|
std::u16string msg = u"Navmesh height: " + (GeneralUtils::to_u16string(y));
|
|
ChatPackets::SendSystemMessage(sysAddr, msg);
|
|
}
|
|
|
|
void GmAddItem(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
|
|
if (splitArgs.size() == 1) {
|
|
const auto itemLOT = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0));
|
|
|
|
if (!itemLOT) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT.");
|
|
return;
|
|
}
|
|
|
|
InventoryComponent* inventory = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY));
|
|
|
|
inventory->AddItem(itemLOT.value(), 1, eLootSourceType::MODERATION);
|
|
} else if (splitArgs.size() == 2) {
|
|
const auto itemLOT = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0));
|
|
if (!itemLOT) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT.");
|
|
return;
|
|
}
|
|
|
|
const auto count = GeneralUtils::TryParse<uint32_t>(splitArgs.at(1));
|
|
if (!count) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid item count.");
|
|
return;
|
|
}
|
|
|
|
InventoryComponent* inventory = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY));
|
|
|
|
inventory->AddItem(itemLOT.value(), count.value(), eLootSourceType::MODERATION);
|
|
} else {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /gmadditem <lot>");
|
|
}
|
|
}
|
|
|
|
// Parse coordinates with support for relative positioning (~)
|
|
std::optional<float> ParseRelativeAxis(const float currentValue, const std::string& rawCoord) {
|
|
if (rawCoord.empty()) return std::nullopt;
|
|
std::string coord = rawCoord;
|
|
// Remove any '+' characters to simplify parsing, since they don't affect the value
|
|
coord.erase(std::remove(coord.begin(), coord.end(), '+'), coord.end());
|
|
if (coord[0] == '~') {
|
|
if (coord.length() == 1) {
|
|
return currentValue;
|
|
} else {
|
|
auto offsetOpt = GeneralUtils::TryParse<float>(coord.substr(1));
|
|
if (!offsetOpt) return std::nullopt;
|
|
return currentValue + offsetOpt.value();
|
|
}
|
|
} else {
|
|
auto absoluteOpt = GeneralUtils::TryParse<float>(coord);
|
|
if (!absoluteOpt) return std::nullopt;
|
|
return absoluteOpt.value();
|
|
}
|
|
}
|
|
|
|
void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
|
|
const auto& sourcePos = entity->GetPosition();
|
|
NiPoint3 pos{};
|
|
auto* sourceEntity = entity;
|
|
if (splitArgs.size() == 3) {
|
|
const auto x = ParseRelativeAxis(sourcePos.x, splitArgs[0]);
|
|
if (!x) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid x.");
|
|
return;
|
|
}
|
|
|
|
const auto y = ParseRelativeAxis(sourcePos.y, splitArgs[1]);
|
|
if (!y) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid y.");
|
|
return;
|
|
}
|
|
|
|
const auto z = ParseRelativeAxis(sourcePos.z, splitArgs[2]);
|
|
if (!z) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid z.");
|
|
return;
|
|
}
|
|
|
|
pos.SetX(x.value());
|
|
pos.SetY(y.value());
|
|
pos.SetZ(z.value());
|
|
|
|
LOG("Teleporting objectID: %llu to %f, %f, %f", entity->GetObjectID(), pos.x, pos.y, pos.z);
|
|
} else if (splitArgs.size() == 2) {
|
|
const auto x = ParseRelativeAxis(sourcePos.x, splitArgs[0]);
|
|
auto* sourcePlayer = PlayerManager::GetPlayer(splitArgs[0]);
|
|
if (!x && !sourcePlayer) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid x or source player not found.");
|
|
return;
|
|
}
|
|
if (sourcePlayer) sourceEntity = sourcePlayer;
|
|
|
|
const auto z = ParseRelativeAxis(sourcePos.z, splitArgs[1]);
|
|
const auto* const targetPlayer = PlayerManager::GetPlayer(splitArgs[1]);
|
|
if (!z && !targetPlayer) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid z or target player not found.");
|
|
return;
|
|
}
|
|
|
|
if (x && z) {
|
|
pos.SetX(x.value());
|
|
pos.SetY(0.0f);
|
|
pos.SetZ(z.value());
|
|
} else if (sourcePlayer && targetPlayer) {
|
|
pos = targetPlayer->GetPosition();
|
|
} else {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Unable to teleport.");
|
|
return;
|
|
}
|
|
LOG("Teleporting objectID: %llu to X: %f, Z: %f", entity->GetObjectID(), pos.x, pos.z);
|
|
} else {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /teleport <x> (<y>) <z> - if no Y given, will teleport to the height of the terrain (or any physics object).");
|
|
}
|
|
GameMessages::SendTeleport(sourceEntity->GetObjectID(), pos, sourceEntity->GetRotation(), sourceEntity->GetSystemAddress());
|
|
|
|
auto* possessorComponent = sourceEntity->GetComponent<PossessorComponent>();
|
|
if (possessorComponent) {
|
|
auto* possassableEntity = Game::entityManager->GetEntity(possessorComponent->GetPossessable());
|
|
|
|
if (possassableEntity != nullptr) {
|
|
auto* havokVehiclePhysicsComponent = possassableEntity->GetComponent<HavokVehiclePhysicsComponent>();
|
|
if (havokVehiclePhysicsComponent) {
|
|
havokVehiclePhysicsComponent->SetPosition(pos);
|
|
Game::entityManager->SerializeEntity(possassableEntity);
|
|
} else GameMessages::SendTeleport(possassableEntity->GetObjectID(), pos, QuatUtils::IDENTITY, sysAddr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TpAll(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto pos = entity->GetPosition();
|
|
|
|
const auto characters = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::CHARACTER);
|
|
|
|
for (auto* character : characters) {
|
|
GameMessages::SendTeleport(character->GetObjectID(), pos, QuatUtils::IDENTITY, character->GetSystemAddress());
|
|
}
|
|
}
|
|
|
|
void Dismount(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto* possessorComponent = entity->GetComponent<PossessorComponent>();
|
|
if (possessorComponent) {
|
|
auto possessableId = possessorComponent->GetPossessable();
|
|
if (possessableId != LWOOBJID_EMPTY) {
|
|
auto* possessableEntity = Game::entityManager->GetEntity(possessableId);
|
|
if (possessableEntity) possessorComponent->Dismount(possessableEntity, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BuffMe(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE));
|
|
if (dest) {
|
|
dest->SetHealth(999);
|
|
dest->SetMaxHealth(999.0f);
|
|
dest->SetArmor(999);
|
|
dest->SetMaxArmor(999.0f);
|
|
dest->SetImagination(999);
|
|
dest->SetMaxImagination(999.0f);
|
|
}
|
|
Game::entityManager->SerializeEntity(entity);
|
|
}
|
|
|
|
void StartCelebration(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
const auto celebration = GeneralUtils::TryParse<int32_t>(splitArgs.at(0));
|
|
|
|
if (!celebration) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid celebration.");
|
|
return;
|
|
}
|
|
|
|
GameMessages::SendStartCelebrationEffect(entity, entity->GetSystemAddress(), celebration.value());
|
|
}
|
|
|
|
void BuffMed(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE));
|
|
if (dest) {
|
|
dest->SetHealth(9);
|
|
dest->SetMaxHealth(9.0f);
|
|
dest->SetArmor(9);
|
|
dest->SetMaxArmor(9.0f);
|
|
dest->SetImagination(9);
|
|
dest->SetMaxImagination(9.0f);
|
|
}
|
|
Game::entityManager->SerializeEntity(entity);
|
|
}
|
|
|
|
void RefillStats(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE));
|
|
if (dest) {
|
|
dest->SetHealth(static_cast<int32_t>(dest->GetMaxHealth()));
|
|
dest->SetArmor(static_cast<int32_t>(dest->GetMaxArmor()));
|
|
dest->SetImagination(static_cast<int32_t>(dest->GetMaxImagination()));
|
|
}
|
|
|
|
Game::entityManager->SerializeEntity(entity);
|
|
}
|
|
|
|
void Lookup(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto query = CDClientDatabase::CreatePreppedStmt(
|
|
"SELECT `id`, `name` FROM `Objects` WHERE `displayName` LIKE ?1 OR `name` LIKE ?1 OR `description` LIKE ?1 LIMIT 50");
|
|
|
|
const std::string query_text = "%" + args + "%";
|
|
query.bind(1, query_text.c_str());
|
|
|
|
auto tables = query.execQuery();
|
|
|
|
std::map<LOT, std::string> lotToName{};
|
|
std::map<std::string, LOT> nameToLot{};
|
|
while (!tables.eof()) {
|
|
const auto lot = tables.getIntField("id");
|
|
const auto name = tables.getStringField("name");
|
|
lotToName[lot] = name;
|
|
nameToLot[name] = lot;
|
|
tables.nextRow();
|
|
}
|
|
|
|
// if there arent a ton of results, print them to chat instead
|
|
if (lotToName.size() < 5) {
|
|
std::stringstream ss;
|
|
ss << "Lookup results for \"" << args << "\":";
|
|
for (const auto& [lot, name] : lotToName) {
|
|
ss << "\nLOT: " << lot << " - Name: " << name;
|
|
}
|
|
ChatPackets::SendSystemMessage(sysAddr, ss.str());
|
|
} else {
|
|
AMFArrayValue response;
|
|
response.Insert("visible", true);
|
|
response.Insert("objectID", "Search Results for: " + args);
|
|
response.Insert("serverInfo", true);
|
|
auto* const info = response.InsertArray("data");
|
|
auto& lotSort = info->PushDebug("Sorted by LOT");
|
|
for (const auto& [lot, name] : lotToName) {
|
|
auto& entry = lotSort.PushDebug<AMFStringValue>(std::to_string(lot)) = name;
|
|
}
|
|
auto& nameSort = info->PushDebug("Sorted by Name");
|
|
for (const auto& [name, lot] : nameToLot) {
|
|
auto& entry = nameSort.PushDebug<AMFStringValue>(name) = std::to_string(lot);
|
|
}
|
|
GameMessages::SendUIMessageServerToSingleClient("ToggleObjectDebugger", response, sysAddr);
|
|
}
|
|
}
|
|
|
|
void Spawn(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
ControllablePhysicsComponent* comp = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS));
|
|
if (!comp) return;
|
|
|
|
const auto lot = GeneralUtils::TryParse<uint32_t>(splitArgs[0]);
|
|
|
|
if (!lot) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot.");
|
|
return;
|
|
}
|
|
|
|
EntityInfo info;
|
|
info.lot = lot.value();
|
|
info.pos = comp->GetPosition();
|
|
info.rot = comp->GetRotation();
|
|
info.spawner = nullptr;
|
|
info.spawnerID = entity->GetObjectID();
|
|
info.spawnerNodeID = 0;
|
|
info.settings.Insert<bool>(u"SpawnedFromSlashCommand", true);
|
|
|
|
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
|
|
|
if (newEntity == nullptr) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity.");
|
|
return;
|
|
}
|
|
|
|
Game::entityManager->ConstructEntity(newEntity);
|
|
}
|
|
|
|
void SpawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.size() < 3) return;
|
|
|
|
const auto lot = GeneralUtils::TryParse<uint32_t>(splitArgs[0]);
|
|
if (!lot) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot.");
|
|
return;
|
|
}
|
|
|
|
const auto numberToSpawnOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[1]);
|
|
if (!numberToSpawnOptional) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn.");
|
|
return;
|
|
}
|
|
uint32_t numberToSpawn = numberToSpawnOptional.value();
|
|
|
|
// Must spawn within a radius of at least 0.0f
|
|
const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse<float>(splitArgs[2]);
|
|
if (!radiusToSpawnWithinOptional || radiusToSpawnWithinOptional.value() < 0.0f) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within.");
|
|
return;
|
|
}
|
|
const float radiusToSpawnWithin = radiusToSpawnWithinOptional.value();
|
|
|
|
EntityInfo info;
|
|
info.lot = lot.value();
|
|
info.spawner = nullptr;
|
|
info.spawnerID = entity->GetObjectID();
|
|
info.spawnerNodeID = 0;
|
|
info.settings.Insert(u"SpawnedFromSlashCommand", true);
|
|
|
|
auto playerPosition = entity->GetPosition();
|
|
while (numberToSpawn > 0) {
|
|
auto randomAngle = GeneralUtils::GenerateRandomNumber<float>(0.0f, 2 * PI);
|
|
auto randomRadius = GeneralUtils::GenerateRandomNumber<float>(0.0f, radiusToSpawnWithin);
|
|
|
|
// Set the position to the generated random position plus the player position. This will
|
|
// spawn the entity in a circle around the player. As you get further from the player, the angle chosen will get less accurate.
|
|
info.pos = playerPosition + NiPoint3(cos(randomAngle) * randomRadius, 0.0f, sin(randomAngle) * randomRadius);
|
|
info.rot = QuatUtils::IDENTITY;
|
|
|
|
auto newEntity = Game::entityManager->CreateEntity(info);
|
|
if (newEntity == nullptr) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity.");
|
|
return;
|
|
}
|
|
|
|
Game::entityManager->ConstructEntity(newEntity);
|
|
numberToSpawn--;
|
|
}
|
|
}
|
|
|
|
void GiveUScore(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
const auto uscoreOptional = GeneralUtils::TryParse<int32_t>(splitArgs[0]);
|
|
if (!uscoreOptional) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid uscore.");
|
|
return;
|
|
}
|
|
const int32_t uscore = uscoreOptional.value();
|
|
|
|
CharacterComponent* character = entity->GetComponent<CharacterComponent>();
|
|
if (character) character->SetUScore(character->GetUScore() + uscore);
|
|
// MODERATION should work but it doesn't. Relog to see uscore changes
|
|
|
|
eLootSourceType lootType = eLootSourceType::MODERATION;
|
|
|
|
if (splitArgs.size() >= 2) {
|
|
const auto type = GeneralUtils::TryParse<eLootSourceType>(splitArgs[1]);
|
|
lootType = type.value_or(lootType);
|
|
}
|
|
|
|
GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), uscore, lootType);
|
|
}
|
|
|
|
void SetLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
// We may be trying to set a specific players level to a level. If so override the entity with the requested players.
|
|
std::string requestedPlayerToSetLevelOf = "";
|
|
if (splitArgs.size() > 1) {
|
|
requestedPlayerToSetLevelOf = splitArgs[1];
|
|
|
|
auto requestedPlayer = PlayerManager::GetPlayer(requestedPlayerToSetLevelOf);
|
|
|
|
if (!requestedPlayer) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"No player found with username: (" + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u").");
|
|
return;
|
|
}
|
|
|
|
if (!requestedPlayer->GetOwner()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"No entity found with username: (" + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u").");
|
|
return;
|
|
}
|
|
|
|
entity = requestedPlayer->GetOwner();
|
|
}
|
|
const auto requestedLevelOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[0]);
|
|
uint32_t oldLevel;
|
|
|
|
// first check the level is valid
|
|
if (!requestedLevelOptional) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid level.");
|
|
return;
|
|
}
|
|
uint32_t requestedLevel = requestedLevelOptional.value();
|
|
// query to set our uscore to the correct value for this level
|
|
|
|
auto characterComponent = entity->GetComponent<CharacterComponent>();
|
|
if (!characterComponent) return;
|
|
auto levelComponent = entity->GetComponent<LevelProgressionComponent>();
|
|
auto query = CDClientDatabase::CreatePreppedStmt("SELECT requiredUScore from LevelProgressionLookup WHERE id = ?;");
|
|
query.bind(1, static_cast<int>(requestedLevel));
|
|
auto result = query.execQuery();
|
|
|
|
if (result.eof()) return;
|
|
|
|
// Set the UScore first
|
|
oldLevel = levelComponent->GetLevel();
|
|
characterComponent->SetUScore(result.getIntField(0, characterComponent->GetUScore()));
|
|
|
|
// handle level up for each level we have passed if we set our level to be higher than the current one.
|
|
if (oldLevel < requestedLevel) {
|
|
while (oldLevel < requestedLevel) {
|
|
oldLevel += 1;
|
|
levelComponent->SetLevel(oldLevel);
|
|
levelComponent->HandleLevelUp();
|
|
}
|
|
} else {
|
|
levelComponent->SetLevel(requestedLevel);
|
|
}
|
|
|
|
if (requestedPlayerToSetLevelOf != "") {
|
|
ChatPackets::SendSystemMessage(
|
|
sysAddr, u"Set " + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u"'s level to " + GeneralUtils::to_u16string(requestedLevel) +
|
|
u" and UScore to " + GeneralUtils::to_u16string(characterComponent->GetUScore()) +
|
|
u". Relog to see changes.");
|
|
} else {
|
|
ChatPackets::SendSystemMessage(
|
|
sysAddr, u"Set your level to " + GeneralUtils::to_u16string(requestedLevel) +
|
|
u" and UScore to " + GeneralUtils::to_u16string(characterComponent->GetUScore()) +
|
|
u". Relog to see changes.");
|
|
}
|
|
}
|
|
|
|
void Pos(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto position = entity->GetPosition();
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(position.x)) + u", " + (GeneralUtils::to_u16string(position.y)) + u", " + (GeneralUtils::to_u16string(position.z)) + u">");
|
|
|
|
LOG("Position: %f, %f, %f", position.x, position.y, position.z);
|
|
}
|
|
|
|
void Rot(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto rotation = entity->GetRotation();
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(rotation.w)) + u", " + (GeneralUtils::to_u16string(rotation.x)) + u", " + (GeneralUtils::to_u16string(rotation.y)) + u", " + (GeneralUtils::to_u16string(rotation.z)) + u">");
|
|
|
|
LOG("Rotation: %f, %f, %f, %f", rotation.w, rotation.x, rotation.y, rotation.z);
|
|
}
|
|
|
|
void LocRow(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto position = entity->GetPosition();
|
|
const auto rotation = entity->GetRotation();
|
|
|
|
LOG("<location x=\"%f\" y=\"%f\" z=\"%f\" rw=\"%f\" rx=\"%f\" ry=\"%f\" rz=\"%f\" />", position.x, position.y, position.z, rotation.w, rotation.x, rotation.y, rotation.z);
|
|
}
|
|
|
|
void PlayLvlFx(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
GameMessages::SendPlayFXEffect(entity, 7074, u"create", "7074", LWOOBJID_EMPTY, 1.0f, 1.0f, true);
|
|
}
|
|
|
|
void PlayRebuildFx(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
GameMessages::SendPlayFXEffect(entity, 230, u"rebuild", "230", LWOOBJID_EMPTY, 1.0f, 1.0f, true);
|
|
}
|
|
|
|
void FreeMoney(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
const auto money = GeneralUtils::TryParse<int64_t>(splitArgs[0]);
|
|
|
|
if (!money) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid money.");
|
|
return;
|
|
}
|
|
|
|
auto* ch = entity->GetCharacter();
|
|
ch->SetCoins(ch->GetCoins() + money.value(), eLootSourceType::MODERATION);
|
|
}
|
|
|
|
void SetCurrency(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
const auto money = GeneralUtils::TryParse<int64_t>(splitArgs[0]);
|
|
|
|
if (!money) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid money.");
|
|
return;
|
|
}
|
|
|
|
auto* ch = entity->GetCharacter();
|
|
ch->SetCoins(money.value(), eLootSourceType::MODERATION);
|
|
}
|
|
|
|
void Buff(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.size() < 2) return;
|
|
|
|
auto* buffComponent = entity->GetComponent<BuffComponent>();
|
|
|
|
const auto id = GeneralUtils::TryParse<int32_t>(splitArgs[0]);
|
|
if (!id) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff id.");
|
|
return;
|
|
}
|
|
|
|
const auto duration = GeneralUtils::TryParse<int32_t>(splitArgs[1]);
|
|
if (!duration) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff duration.");
|
|
return;
|
|
}
|
|
|
|
if (buffComponent) buffComponent->ApplyBuff(id.value(), duration.value(), entity->GetObjectID());
|
|
}
|
|
|
|
void TestMap(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Requesting map change...");
|
|
LWOCLONEID cloneId = 0;
|
|
LWOINSTANCEID instanceID{};
|
|
std::string targetScene;
|
|
|
|
const auto reqZoneOptional = GeneralUtils::TryParse<LWOMAPID>(splitArgs[0]);
|
|
if (!reqZoneOptional) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone.");
|
|
return;
|
|
}
|
|
const LWOMAPID reqZone = reqZoneOptional.value();
|
|
|
|
if (splitArgs.size() > 1) {
|
|
const auto cloneIdOptional = GeneralUtils::TryParse<LWOCLONEID>(splitArgs[1]);
|
|
if (!cloneIdOptional) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone id.");
|
|
return;
|
|
}
|
|
|
|
cloneId = cloneIdOptional.value();
|
|
|
|
if (splitArgs.size() > 2) {
|
|
const auto instanceIDVal = GeneralUtils::TryParse<LWOINSTANCEID>(splitArgs[2]);
|
|
if (!instanceIDVal) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid instance id.");
|
|
return;
|
|
}
|
|
|
|
instanceID = instanceIDVal.value();
|
|
}
|
|
|
|
if (splitArgs.size() > 3) {
|
|
targetScene = splitArgs[3];
|
|
}
|
|
}
|
|
|
|
const auto objid = entity->GetObjectID();
|
|
|
|
if (Game::zoneManager->CheckIfAccessibleZone(reqZone)) { // to prevent tomfoolery
|
|
|
|
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, reqZone, cloneId, false, [objid, targetScene](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
|
|
|
|
auto* entity = Game::entityManager->GetEntity(objid);
|
|
if (!entity) return;
|
|
|
|
const auto sysAddr = entity->GetSystemAddress();
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Transfering map...");
|
|
|
|
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
|
|
if (entity->GetCharacter()) {
|
|
auto* characterComponent = entity->GetComponent<CharacterComponent>();
|
|
if (characterComponent) {
|
|
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));
|
|
}
|
|
entity->GetCharacter()->SetZoneID(zoneID);
|
|
entity->GetCharacter()->SetZoneInstance(zoneInstance);
|
|
entity->GetCharacter()->SetZoneClone(zoneClone);
|
|
entity->GetCharacter()->SetTargetScene(targetScene);
|
|
entity->GetComponent<CharacterComponent>()->SetLastRocketConfig(u"");
|
|
}
|
|
|
|
entity->GetCharacter()->SaveXMLToDatabase();
|
|
|
|
WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift);
|
|
return;
|
|
});
|
|
} else {
|
|
std::string msg = "ZoneID not found or allowed: ";
|
|
msg.append(splitArgs[0]); // FIXME: unnecessary utf16 re-encoding just for error
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(msg, msg.size()));
|
|
}
|
|
}
|
|
|
|
void CreatePrivate(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.size() < 3) return;
|
|
|
|
const auto zone = GeneralUtils::TryParse<uint32_t>(splitArgs[0]);
|
|
if (!zone) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone.");
|
|
return;
|
|
}
|
|
|
|
const auto clone = GeneralUtils::TryParse<uint32_t>(splitArgs[1]);
|
|
if (!clone) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone.");
|
|
return;
|
|
}
|
|
|
|
const auto& password = splitArgs[2];
|
|
if (password.length() >= 50) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Password is too long.");
|
|
return;
|
|
}
|
|
|
|
ZoneInstanceManager::Instance()->CreatePrivateZone(Game::server, zone.value(), clone.value(), password);
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("Sent request for private zone with password: " + password));
|
|
}
|
|
|
|
void DebugUi(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Opening UIDebugger...");
|
|
}
|
|
|
|
void Boost(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
|
|
auto* possessorComponent = entity->GetComponent<PossessorComponent>();
|
|
|
|
if (possessorComponent == nullptr) {
|
|
return;
|
|
}
|
|
|
|
auto* vehicle = Game::entityManager->GetEntity(possessorComponent->GetPossessable());
|
|
|
|
if (vehicle == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (splitArgs.size() >= 1) {
|
|
const auto time = GeneralUtils::TryParse<float>(splitArgs[0]);
|
|
|
|
if (!time) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost time.");
|
|
return;
|
|
} else {
|
|
GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
|
|
entity->AddCallbackTimer(time.value(), [vehicle]() {
|
|
if (!vehicle) return;
|
|
GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
|
|
});
|
|
}
|
|
} else {
|
|
GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
|
|
}
|
|
}
|
|
|
|
void Unboost(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto* possessorComponent = entity->GetComponent<PossessorComponent>();
|
|
|
|
if (possessorComponent == nullptr) return;
|
|
auto* vehicle = Game::entityManager->GetEntity(possessorComponent->GetPossessable());
|
|
|
|
if (vehicle == nullptr) return;
|
|
GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
|
|
}
|
|
|
|
void ActivateSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
auto spawners = Game::zoneManager->GetSpawnersByName(splitArgs[0]);
|
|
|
|
for (auto* spawner : spawners) {
|
|
spawner->Activate();
|
|
}
|
|
|
|
spawners = Game::zoneManager->GetSpawnersInGroup(splitArgs[0]);
|
|
|
|
for (auto* spawner : spawners) {
|
|
spawner->Activate();
|
|
}
|
|
}
|
|
|
|
void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
//Go tell physics to spawn all the vertices:
|
|
auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PHANTOM_PHYSICS);
|
|
for (const auto* en : entities) {
|
|
const auto* phys = static_cast<PhantomPhysicsComponent*>(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS));
|
|
if (phys)
|
|
phys->SpawnVertices();
|
|
}
|
|
for (const auto* en : Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS)) {
|
|
const auto* phys = en->GetComponent<RigidbodyPhantomPhysicsComponent>();
|
|
if (phys)
|
|
phys->SpawnVertices();
|
|
}
|
|
}
|
|
|
|
void ReportProxPhys(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PROXIMITY_MONITOR);
|
|
for (auto en : entities) {
|
|
auto phys = static_cast<ProximityMonitorComponent*>(en->GetComponent(eReplicaComponentType::PROXIMITY_MONITOR));
|
|
if (phys) {
|
|
for (auto prox : phys->GetProximitiesData()) {
|
|
if (!prox.second) continue;
|
|
|
|
auto sphere = static_cast<dpShapeSphere*>(prox.second->GetShape());
|
|
auto pos = prox.second->GetPosition();
|
|
LOG("Proximity: %s, r: %f, pos: %f, %f, %f", prox.first.c_str(), sphere->GetRadius(), pos.x, pos.y, pos.z);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TriggerSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
auto spawners = Game::zoneManager->GetSpawnersByName(splitArgs[0]);
|
|
|
|
for (auto* spawner : spawners) {
|
|
spawner->Spawn();
|
|
}
|
|
|
|
spawners = Game::zoneManager->GetSpawnersInGroup(splitArgs[0]);
|
|
|
|
for (auto* spawner : spawners) {
|
|
spawner->Spawn();
|
|
}
|
|
}
|
|
|
|
void Reforge(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.size() < 2) return;
|
|
|
|
const auto baseItem = GeneralUtils::TryParse<LOT>(splitArgs[0]);
|
|
if (!baseItem) return;
|
|
|
|
const auto reforgedItem = GeneralUtils::TryParse<LOT>(splitArgs[1]);
|
|
if (!reforgedItem) return;
|
|
|
|
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
|
if (!inventoryComponent) return;
|
|
|
|
LwoNameValue config;
|
|
config.Insert<LOT>(u"reforgedLOT", reforgedItem.value());
|
|
|
|
inventoryComponent->AddItem(baseItem.value(), 1, eLootSourceType::MODERATION, eInventoryType::INVALID, config);
|
|
}
|
|
|
|
void Crash(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Crashing...");
|
|
|
|
int* badPtr = nullptr;
|
|
*badPtr = 0;
|
|
}
|
|
|
|
void Metrics(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
AMFArrayValue response;
|
|
response.Insert("visible", true);
|
|
response.Insert("objectID", "Metrics");
|
|
response.Insert("serverInfo", true);
|
|
auto* info = response.InsertArray("data");
|
|
for (const auto variable : Metrics::GetAllMetrics()) {
|
|
auto& metricData = info->PushDebug(Metrics::MetricVariableToString(variable));
|
|
|
|
const auto& metric = Metrics::GetMetric(variable);
|
|
|
|
metricData.PushDebug<AMFStringValue>("Maximum") = std::to_string(Metrics::ToMiliseconds(metric.max)) + "ms";
|
|
metricData.PushDebug<AMFStringValue>("Minimum") = std::to_string(Metrics::ToMiliseconds(metric.min)) + "ms";
|
|
metricData.PushDebug<AMFStringValue>("Average") = std::to_string(Metrics::ToMiliseconds(metric.average)) + "ms";
|
|
metricData.PushDebug<AMFStringValue>("Measurements Count") = std::to_string(metric.measurementSize);
|
|
}
|
|
auto& processInfo = info->PushDebug("Process Info");
|
|
processInfo.PushDebug<AMFStringValue>("Peak RSS") = std::to_string(static_cast<double>(Metrics::GetPeakRSS()) / 1.024e6) + "MB";
|
|
processInfo.PushDebug<AMFStringValue>("Current RSS") = std::to_string(static_cast<double>(Metrics::GetCurrentRSS()) / 1.024e6) + "MB";
|
|
processInfo.PushDebug<AMFIntValue>("Process ID") = Metrics::GetProcessID();
|
|
GameMessages::SendUIMessageServerToSingleClient("ToggleObjectDebugger", response, sysAddr);
|
|
}
|
|
|
|
void ReloadConfig(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
Game::config->ReloadConfig();
|
|
VanityUtilities::SpawnVanity();
|
|
dpWorld::Reload();
|
|
auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY);
|
|
for (const auto* const entity : entities) {
|
|
auto* const scriptedActivityComponent = entity->GetComponent<ScriptedActivityComponent>();
|
|
if (!scriptedActivityComponent) continue;
|
|
|
|
scriptedActivityComponent->ReloadConfig();
|
|
}
|
|
Game::server->UpdateMaximumMtuSize();
|
|
Game::server->UpdateBandwidthLimit();
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Successfully reloaded config for world!");
|
|
}
|
|
|
|
void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.size() < 3) return;
|
|
|
|
const auto lootMatrixIndex = GeneralUtils::TryParse<uint32_t>(splitArgs[0]);
|
|
if (!lootMatrixIndex) return;
|
|
|
|
const auto targetLot = GeneralUtils::TryParse<uint32_t>(splitArgs[1]);
|
|
if (!targetLot) return;
|
|
|
|
const auto loops = GeneralUtils::TryParse<uint32_t>(splitArgs[2]);
|
|
if (!loops) return;
|
|
|
|
auto* const lootMatrixTable = CDClientManager::GetTable<CDLootMatrixTable>();
|
|
auto* const lootTableTable = CDClientManager::GetTable<CDLootTableTable>();
|
|
bool found = false;
|
|
for (const auto& entry : lootMatrixTable->GetMatrix(lootMatrixIndex.value())) {
|
|
for (const auto& loot : lootTableTable->GetTable(entry.LootTableIndex)) {
|
|
found = targetLot.value() == loot.itemid;
|
|
if (found) break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
std::stringstream ss;
|
|
ss << "Target LOT " << targetLot.value() << " not found in loot matrix " << lootMatrixIndex.value() << ".";
|
|
ChatPackets::SendSystemMessage(sysAddr, ss.str());
|
|
return;
|
|
}
|
|
|
|
uint64_t totalRuns = 0;
|
|
|
|
for (uint32_t i = 0; i < loops; i++) {
|
|
while (true) {
|
|
const auto lootRoll = Loot::RollLootMatrix(nullptr, lootMatrixIndex.value());
|
|
totalRuns += 1;
|
|
if (lootRoll.contains(targetLot.value())) break;
|
|
}
|
|
}
|
|
|
|
std::u16string message = u"Ran loot drops looking for "
|
|
+ GeneralUtils::to_u16string(targetLot.value())
|
|
+ u", "
|
|
+ GeneralUtils::to_u16string(loops.value())
|
|
+ u" times. It ran "
|
|
+ GeneralUtils::to_u16string(totalRuns)
|
|
+ u" times. Averaging out at "
|
|
+ GeneralUtils::to_u16string(static_cast<float>(totalRuns) / loops.value());
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, message);
|
|
}
|
|
|
|
void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
eInventoryType inventoryType = eInventoryType::INVALID;
|
|
|
|
const auto inventoryTypeOptional = GeneralUtils::TryParse<eInventoryType>(splitArgs[0]);
|
|
if (!inventoryTypeOptional) {
|
|
// In this case, we treat the input as a string and try to find it in the reflection list
|
|
std::transform(splitArgs[0].begin(), splitArgs[0].end(), splitArgs[0].begin(), ::toupper);
|
|
LOG("looking for inventory %s", splitArgs[0].c_str());
|
|
for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) {
|
|
if (std::string_view(splitArgs[0]) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) inventoryType = static_cast<eInventoryType>(index);
|
|
}
|
|
} else {
|
|
inventoryType = inventoryTypeOptional.value();
|
|
}
|
|
|
|
if (inventoryType == eInventoryType::INVALID || inventoryType >= NUMBER_OF_INVENTORIES) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Invalid inventory provided.");
|
|
return;
|
|
}
|
|
|
|
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
|
if (!inventoryComponent) return;
|
|
|
|
auto* inventoryToDelete = inventoryComponent->GetInventory(inventoryType);
|
|
if (!inventoryToDelete) return;
|
|
|
|
inventoryToDelete->DeleteAllItems();
|
|
LOG("Deleted inventory %s for user %llu", splitArgs[0].c_str(), entity->GetObjectID());
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Deleted inventory " + GeneralUtils::UTF8ToUTF16(splitArgs[0]));
|
|
}
|
|
|
|
void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
auto* skillComponent = entity->GetComponent<SkillComponent>();
|
|
if (skillComponent) {
|
|
const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[0]);
|
|
|
|
if (!skillId) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill ID.");
|
|
return;
|
|
} else {
|
|
skillComponent->CastSkill(skillId.value(), entity->GetObjectID(), entity->GetObjectID());
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Cast skill");
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.size() < 2) return;
|
|
|
|
auto* const inventoryComponent = entity->GetComponent<InventoryComponent>();
|
|
if (inventoryComponent) {
|
|
const auto slot = GeneralUtils::TryParse<BehaviorSlot>(splitArgs[0]);
|
|
if (!slot) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot.");
|
|
return;
|
|
} else {
|
|
const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[1]);
|
|
if (!skillId) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill.");
|
|
return;
|
|
} else {
|
|
if (inventoryComponent->SetSkill(slot.value(), skillId.value())) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully");
|
|
else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
|
if (destroyableComponent) {
|
|
const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[0]);
|
|
|
|
if (!faction) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction.");
|
|
return;
|
|
} else {
|
|
destroyableComponent->SetFaction(faction.value());
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Set faction and updated enemies list");
|
|
}
|
|
}
|
|
}
|
|
|
|
void AddFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
|
|
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
|
if (destroyableComponent) {
|
|
const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[0]);
|
|
|
|
if (!faction) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction.");
|
|
return;
|
|
} else {
|
|
destroyableComponent->AddFaction(faction.value());
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Added faction and updated enemies list");
|
|
}
|
|
}
|
|
}
|
|
|
|
void GetFactions(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
|
if (destroyableComponent) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:");
|
|
for (const auto entry : destroyableComponent->GetFactionIDs()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry)));
|
|
}
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Enemy factions:");
|
|
for (const auto entry : destroyableComponent->GetEnemyFactionsIDs()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetRewardCode(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto* character = entity->GetCharacter();
|
|
if (!character) return;
|
|
auto* user = character->GetParentUser();
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
auto* cdrewardCodes = CDClientManager::GetTable<CDRewardCodesTable>();
|
|
|
|
auto id = cdrewardCodes->GetCodeID(splitArgs[0]);
|
|
if (id != -1) Database::Get()->InsertRewardCode(user->GetAccountID(), id);
|
|
}
|
|
|
|
void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
if (splitArgs.empty()) return;
|
|
std::optional<LWOOBJID> idIntermed;
|
|
if (splitArgs[0] == "zoneControl") {
|
|
idIntermed = 0x3FFF'FFFFFFFE;
|
|
} else if (splitArgs[0] == "localCharacter") {
|
|
idIntermed = entity->GetObjectID();
|
|
} else {
|
|
idIntermed = GeneralUtils::TryParse<LWOOBJID>(splitArgs[0]);
|
|
}
|
|
const auto idParsed = idIntermed;
|
|
|
|
// First try to get the object by its ID if provided.
|
|
// Second try to get the object by player name.
|
|
// Lastly assume we were passed a component or LDF and try to find the closest entity with that component or LDF.
|
|
Entity* closest = nullptr;
|
|
if (idParsed) closest = Game::entityManager->GetEntity(idParsed.value());
|
|
float closestDistance = 0.0f;
|
|
|
|
std::u16string ldf;
|
|
|
|
bool isLDF = false;
|
|
|
|
if (!closest) closest = PlayerManager::GetPlayer(splitArgs[0]);
|
|
if (!closest) {
|
|
auto component = GeneralUtils::TryParse<eReplicaComponentType>(splitArgs[0]);
|
|
if (!component) {
|
|
component = eReplicaComponentType::INVALID;
|
|
|
|
ldf = GeneralUtils::UTF8ToUTF16(splitArgs[0]);
|
|
|
|
isLDF = true;
|
|
}
|
|
|
|
auto reference = entity->GetPosition();
|
|
|
|
|
|
const auto candidates = Game::entityManager->GetEntitiesByComponent(component.value());
|
|
|
|
for (auto* candidate : candidates) {
|
|
if (candidate->GetLOT() == 1 || candidate->GetLOT() == 8092) {
|
|
continue;
|
|
}
|
|
|
|
if (isLDF && !candidate->HasVar(ldf)) {
|
|
continue;
|
|
}
|
|
|
|
if (!closest) {
|
|
closest = candidate;
|
|
|
|
closestDistance = NiPoint3::Distance(candidate->GetPosition(), reference);
|
|
|
|
continue;
|
|
}
|
|
|
|
const auto distance = NiPoint3::Distance(candidate->GetPosition(), reference);
|
|
|
|
if (distance < closestDistance) {
|
|
closest = candidate;
|
|
|
|
closestDistance = distance;
|
|
}
|
|
}
|
|
} else {
|
|
closestDistance = NiPoint3::Distance(entity->GetPosition(), closest->GetPosition());
|
|
}
|
|
|
|
if (!closest) return;
|
|
|
|
GameMessages::RequestServerObjectInfo objectInfo;
|
|
objectInfo.bVerbose = true;
|
|
objectInfo.target = closest->GetObjectID();
|
|
objectInfo.targetForReport = closest->GetObjectID();
|
|
objectInfo.clientId = entity->GetObjectID();
|
|
closest->HandleMsg(objectInfo);
|
|
|
|
Game::entityManager->SerializeEntity(closest);
|
|
|
|
auto* table = CDClientManager::GetTable<CDObjectsTable>();
|
|
|
|
const auto& info = table->GetByID(closest->GetLOT());
|
|
|
|
std::stringstream header;
|
|
|
|
header << info.name << " [" << std::to_string(info.id) << "]" << " " << std::to_string(closestDistance) << " " << std::to_string(closest->IsSleeping());
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(header.str()));
|
|
|
|
if (splitArgs.size() >= 2) {
|
|
if (splitArgs[1] == "-m" && splitArgs.size() >= 3) {
|
|
auto* const movingPlatformComponent = closest->GetComponent<MovingPlatformComponent>();
|
|
|
|
const auto mValue = GeneralUtils::TryParse<int32_t>(splitArgs[2]);
|
|
|
|
if (!movingPlatformComponent || !mValue) return;
|
|
|
|
movingPlatformComponent->SetSerialized(true);
|
|
|
|
if (mValue == -1) {
|
|
movingPlatformComponent->StopPathing();
|
|
} else {
|
|
movingPlatformComponent->GotoWaypoint(mValue.value());
|
|
}
|
|
|
|
Game::entityManager->SerializeEntity(closest);
|
|
} else if (splitArgs[1] == "-a" && splitArgs.size() >= 3) {
|
|
RenderComponent::PlayAnimation(closest, splitArgs.at(2));
|
|
} else if (splitArgs[1] == "-p") {
|
|
const auto postion = closest->GetPosition();
|
|
|
|
ChatPackets::SendSystemMessage(
|
|
sysAddr,
|
|
GeneralUtils::ASCIIToUTF16("< " + std::to_string(postion.x) + ", " + std::to_string(postion.y) + ", " + std::to_string(postion.z) + " >")
|
|
);
|
|
} else if (splitArgs[1] == "-f") {
|
|
auto* destuctable = closest->GetComponent<DestroyableComponent>();
|
|
|
|
if (destuctable == nullptr) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!");
|
|
return;
|
|
}
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Smashable: " + (GeneralUtils::to_u16string(destuctable->GetIsSmashable())));
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:");
|
|
for (const auto entry : destuctable->GetFactionIDs()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry)));
|
|
}
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Enemy factions:");
|
|
for (const auto entry : destuctable->GetEnemyFactionsIDs()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry)));
|
|
}
|
|
|
|
if (splitArgs.size() >= 3) {
|
|
const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[2]);
|
|
if (!faction) return;
|
|
|
|
destuctable->SetFaction(-1);
|
|
destuctable->AddFaction(faction.value(), true);
|
|
}
|
|
} else if (splitArgs[1] == "-cf") {
|
|
auto* destuctable = entity->GetComponent<DestroyableComponent>();
|
|
if (!destuctable) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!");
|
|
return;
|
|
}
|
|
if (destuctable->IsEnemy(closest)) ChatPackets::SendSystemMessage(sysAddr, u"They are our enemy");
|
|
else ChatPackets::SendSystemMessage(sysAddr, u"They are NOT our enemy");
|
|
} else if (splitArgs[1] == "-t") {
|
|
auto* phantomPhysicsComponent = closest->GetComponent<PhantomPhysicsComponent>();
|
|
|
|
if (phantomPhysicsComponent != nullptr) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Type: " + (GeneralUtils::to_u16string(static_cast<uint32_t>(phantomPhysicsComponent->GetEffectType()))));
|
|
const auto dir = phantomPhysicsComponent->GetDirection();
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Direction: <" + (GeneralUtils::to_u16string(dir.x)) + u", " + (GeneralUtils::to_u16string(dir.y)) + u", " + (GeneralUtils::to_u16string(dir.z)) + u">");
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Multiplier: " + (GeneralUtils::to_u16string(phantomPhysicsComponent->GetDirectionalMultiplier())));
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Active: " + (GeneralUtils::to_u16string(phantomPhysicsComponent->GetPhysicsEffectActive())));
|
|
}
|
|
|
|
auto* triggerComponent = closest->GetComponent<TriggerComponent>();
|
|
if (triggerComponent) {
|
|
auto trigger = triggerComponent->GetTrigger();
|
|
if (trigger) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Trigger: " + (GeneralUtils::to_u16string(trigger->id)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Shutdown(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto* character = entity->GetCharacter();
|
|
if (character) LOG("Mythran (%s) has shutdown the world", character->GetName().c_str());
|
|
Game::OnSignal(-1);
|
|
}
|
|
|
|
void Barfight(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
auto* const characterComponent = entity->GetComponent<CharacterComponent>();
|
|
if (!characterComponent) return;
|
|
|
|
for (auto* const player : PlayerManager::GetAllPlayers()) {
|
|
auto* const pCharacterComponent = player->GetComponent<CharacterComponent>();
|
|
if (pCharacterComponent) pCharacterComponent->SetPvpEnabled(true);
|
|
Game::entityManager->SerializeEntity(player);
|
|
}
|
|
const auto msg = u"Pvp has been turned on for all players by " + GeneralUtils::ASCIIToUTF16(characterComponent->GetName());
|
|
ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, msg, true);
|
|
}
|
|
|
|
void Despawn(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
if (args.empty()) return;
|
|
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
const auto objId = GeneralUtils::TryParse<LWOOBJID>(splitArgs[0]);
|
|
if (!objId) return;
|
|
|
|
auto* const target = Game::entityManager->GetEntity(*objId);
|
|
if (!target) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Entity not found: " + GeneralUtils::UTF8ToUTF16(splitArgs[0]));
|
|
return;
|
|
}
|
|
|
|
if (!target->GetVar<bool>(u"SpawnedFromSlashCommand")) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"You cannot despawn this entity as it was not despawned from a slash command.");
|
|
return;
|
|
}
|
|
|
|
target->Smash(LWOOBJID_EMPTY, eKillType::SILENT);
|
|
LOG("Despawned entity (%llu)", target->GetObjectID());
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Despawned entity: " + GeneralUtils::to_u16string(target->GetObjectID()));
|
|
}
|
|
|
|
void Execute(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
if (args.empty()) {
|
|
ChatPackets::SendSystemMessage(sysAddr,
|
|
u"Usage: /execute <subcommand> ... run <command>\n"
|
|
u"Subcommands:\n"
|
|
u" as <playername> - Execute as different player\n"
|
|
u" at <playername> - Execute from player's position\n"
|
|
u" positioned <x> <y> <z> - Execute from coordinates (absolute or relative with ~)\n"
|
|
u"Examples:\n"
|
|
u" /execute as Player1 run pos\n"
|
|
u" /execute at Player2 positioned 100 200 300 run spawn 1234\n"
|
|
u" /execute positioned ~5 ~10 ~ run spawn 1234"
|
|
);
|
|
return;
|
|
}
|
|
|
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
|
|
|
// Prevent execute command recursion by checking if this is already an execute command
|
|
for (const auto& arg : splitArgs) {
|
|
if (arg == "execute" || arg == "exec") {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: Recursive execute commands are not allowed");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Context variables for execution
|
|
Entity* execEntity = entity; // Entity to execute as
|
|
NiPoint3 execPosition = entity->GetPosition(); // Position to execute from
|
|
bool positionOverridden = false;
|
|
std::string finalCommand;
|
|
|
|
// Parse subcommands
|
|
size_t i = 0;
|
|
while (i < splitArgs.size()) {
|
|
const std::string& subcommand = splitArgs[i];
|
|
|
|
if (subcommand == "as") {
|
|
if (i + 1 >= splitArgs.size()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'as' requires a player name");
|
|
return;
|
|
}
|
|
|
|
const std::string& targetName = splitArgs[i + 1];
|
|
auto* targetPlayer = PlayerManager::GetPlayer(targetName);
|
|
if (!targetPlayer) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: Player '" + GeneralUtils::ASCIIToUTF16(targetName) + u"' not found");
|
|
return;
|
|
}
|
|
|
|
execEntity = targetPlayer;
|
|
i += 2;
|
|
|
|
} else if (subcommand == "at") {
|
|
if (i + 1 >= splitArgs.size()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'at' requires a player name");
|
|
return;
|
|
}
|
|
|
|
const std::string& targetName = splitArgs[i + 1];
|
|
auto* targetPlayer = PlayerManager::GetPlayer(targetName);
|
|
if (!targetPlayer) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: Player '" + GeneralUtils::ASCIIToUTF16(targetName) + u"' not found");
|
|
return;
|
|
}
|
|
|
|
execPosition = targetPlayer->GetPosition();
|
|
positionOverridden = true;
|
|
i += 2;
|
|
|
|
} else if (subcommand == "positioned") {
|
|
if (i + 3 >= splitArgs.size()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'positioned' requires x, y, z coordinates");
|
|
return;
|
|
}
|
|
|
|
auto xOpt = ParseRelativeAxis(execPosition.x, splitArgs[i + 1]);
|
|
auto yOpt = ParseRelativeAxis(execPosition.y, splitArgs[i + 2]);
|
|
auto zOpt = ParseRelativeAxis(execPosition.z, splitArgs[i + 3]);
|
|
|
|
if (!xOpt || !yOpt || !zOpt) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: Invalid coordinates for 'positioned'. Use numeric values or relative coordinates with ~.");
|
|
return;
|
|
}
|
|
|
|
execPosition = NiPoint3(xOpt.value(), yOpt.value(), zOpt.value());
|
|
positionOverridden = true;
|
|
|
|
i += 4;
|
|
|
|
} else if (subcommand == "run") {
|
|
// Everything after "run" is the command to execute
|
|
if (i + 1 >= splitArgs.size()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'run' requires a command");
|
|
return;
|
|
}
|
|
|
|
// Reconstruct the command from remaining args
|
|
for (size_t j = i + 1; j < splitArgs.size(); ++j) {
|
|
if (!finalCommand.empty()) finalCommand += " ";
|
|
finalCommand += splitArgs[j];
|
|
}
|
|
break;
|
|
|
|
} else {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: Unknown subcommand '" + GeneralUtils::ASCIIToUTF16(subcommand) + u"'");
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Valid subcommands: as, at, positioned, run");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (finalCommand.empty()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: No command specified to run. Use 'run <command>' at the end.");
|
|
return;
|
|
}
|
|
|
|
// Validate that the command starts with a valid character
|
|
if (finalCommand.empty() || finalCommand[0] == '/') {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: Command should not start with '/'. Just specify the command name.");
|
|
return;
|
|
}
|
|
|
|
// Store original position if we need to restore it
|
|
NiPoint3 originalPosition;
|
|
bool needToRestore = false;
|
|
|
|
if (positionOverridden && execEntity == entity) {
|
|
// If we're executing as ourselves but from a different position,
|
|
// temporarily move the entity
|
|
originalPosition = entity->GetPosition();
|
|
needToRestore = true;
|
|
|
|
// Set the position temporarily for the command execution
|
|
auto* controllable = entity->GetComponent<ControllablePhysicsComponent>();
|
|
if (controllable) {
|
|
controllable->SetPosition(execPosition);
|
|
}
|
|
}
|
|
|
|
// Provide feedback about what we're executing
|
|
std::string execAsName = execEntity->GetCharacter() ? execEntity->GetCharacter()->GetName() : "Unknown";
|
|
ChatPackets::SendSystemMessage(sysAddr, u"[Execute] Running as '" + GeneralUtils::ASCIIToUTF16(execAsName) +
|
|
u"' from <" + GeneralUtils::to_u16string(execPosition.x) + u", " +
|
|
GeneralUtils::to_u16string(execPosition.y) + u", " +
|
|
GeneralUtils::to_u16string(execPosition.z) + u">: /" +
|
|
GeneralUtils::ASCIIToUTF16(finalCommand));
|
|
|
|
// Execute the command through the slash command handler
|
|
SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16("/" + finalCommand), execEntity, sysAddr);
|
|
|
|
// Restore original position if needed
|
|
if (needToRestore) {
|
|
auto* controllable = entity->GetComponent<ControllablePhysicsComponent>();
|
|
if (controllable) {
|
|
controllable->SetPosition(originalPosition);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GetScene(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto position = entity->GetPosition();
|
|
|
|
// Get the scene ID from the zone manager
|
|
const auto sceneID = Game::zoneManager->GetSceneIDFromPosition(position);
|
|
|
|
if (sceneID == LWOSCENEID_INVALID) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"No scene found at current position.");
|
|
return;
|
|
}
|
|
|
|
// Get the scene reference from the zone to get the name
|
|
const auto* zone = Game::zoneManager->GetZone();
|
|
if (!zone) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"No zone loaded.");
|
|
return;
|
|
}
|
|
|
|
// Build the feedback message
|
|
std::ostringstream feedback;
|
|
feedback << "Scene ID: " << sceneID.GetSceneID();
|
|
feedback << " (Layer: " << sceneID.GetLayerID() << ")";
|
|
|
|
// Get the scene name
|
|
const auto* sceneRef = zone->GetScene(sceneID);
|
|
if (sceneRef && !sceneRef->name.empty()) {
|
|
feedback << " - Name: " << sceneRef->name;
|
|
}
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
|
}
|
|
|
|
void GetAdjacentScenes(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
const auto position = entity->GetPosition();
|
|
|
|
// Get the scene ID from the zone manager
|
|
const auto sceneID = Game::zoneManager->GetSceneIDFromPosition(position);
|
|
|
|
if (sceneID == LWOSCENEID_INVALID) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"No scene found at current position.");
|
|
return;
|
|
}
|
|
|
|
// Get the zone reference
|
|
const auto* zone = Game::zoneManager->GetZone();
|
|
if (!zone) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"No zone loaded.");
|
|
return;
|
|
}
|
|
|
|
// Get current scene info
|
|
const auto* currentScene = zone->GetScene(sceneID);
|
|
std::string currentSceneName = currentScene && !currentScene->name.empty() ? currentScene->name : "Unknown";
|
|
|
|
// Get adjacent scenes
|
|
const auto adjacentSceneIDs = Game::zoneManager->GetAdjacentScenes(sceneID);
|
|
|
|
if (adjacentSceneIDs.empty()) {
|
|
std::ostringstream feedback;
|
|
feedback << "Current Scene: " << sceneID.GetSceneID() << " (" << currentSceneName << ")";
|
|
feedback << " - No adjacent scenes found.";
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
|
return;
|
|
}
|
|
|
|
// Build the feedback message with current scene
|
|
std::ostringstream feedback;
|
|
feedback << "Current Scene: " << sceneID.GetSceneID() << " (" << currentSceneName << ")";
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
|
|
|
// List all adjacent scenes
|
|
feedback.str("");
|
|
feedback << "Adjacent Scenes (" << adjacentSceneIDs.size() << "):";
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
|
|
|
for (const auto& adjSceneID : adjacentSceneIDs) {
|
|
feedback.str("");
|
|
feedback << " - Scene ID: " << adjSceneID.GetSceneID();
|
|
feedback << " (Layer: " << adjSceneID.GetLayerID() << ")";
|
|
|
|
// Get the scene name if available
|
|
const auto* sceneRef = zone->GetScene(adjSceneID);
|
|
if (sceneRef && !sceneRef->name.empty()) {
|
|
feedback << " - " << sceneRef->name;
|
|
}
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
|
}
|
|
}
|
|
|
|
void SpawnScenePoints(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
// Hardcoded to use LOT 33
|
|
const uint32_t lot = 33;
|
|
|
|
// Get player's current position and scene
|
|
const auto position = entity->GetPosition();
|
|
const auto currentSceneID = Game::zoneManager->GetSceneIDFromPosition(position);
|
|
if (currentSceneID == LWOSCENEID_INVALID) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"No scene found at current position.");
|
|
return;
|
|
}
|
|
|
|
// Get the zone
|
|
const auto* zone = Game::zoneManager->GetZone();
|
|
if (!zone) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"No zone loaded.");
|
|
return;
|
|
}
|
|
|
|
// Get the Raw terrain data
|
|
const auto& raw = zone->GetZoneRaw();
|
|
if (raw.chunks.empty()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Zone does not have valid terrain data.");
|
|
return;
|
|
}
|
|
|
|
// Spawn at all sceneMap points in the current scene
|
|
uint32_t spawnedCount = 0;
|
|
|
|
for (const auto& chunk : raw.chunks) {
|
|
if (chunk.sceneMap.empty() || chunk.colorMapResolution == 0 || chunk.heightMap.empty()
|
|
|| chunk.width <= 1 || chunk.height <= 1 || chunk.scaleFactor <= 0.0f) continue;
|
|
|
|
for (uint32_t i = 0; i < chunk.width; ++i) {
|
|
for (uint32_t j = 0; j < chunk.height; ++j) {
|
|
const uint32_t heightIndex = chunk.width * i + j;
|
|
if (heightIndex >= chunk.heightMap.size()) continue;
|
|
|
|
const float y = chunk.heightMap[heightIndex];
|
|
|
|
const float sceneMapI = (static_cast<float>(i) / static_cast<float>(chunk.width - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
|
|
const float sceneMapJ = (static_cast<float>(j) / static_cast<float>(chunk.height - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
|
|
|
|
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), chunk.colorMapResolution - 1);
|
|
const uint32_t sceneJ = std::min(static_cast<uint32_t>(sceneMapJ), chunk.colorMapResolution - 1);
|
|
const uint32_t sceneIndex = sceneI * chunk.colorMapResolution + sceneJ;
|
|
|
|
uint8_t sceneID = 0;
|
|
if (sceneIndex < chunk.sceneMap.size()) {
|
|
sceneID = chunk.sceneMap[sceneIndex];
|
|
}
|
|
|
|
if (sceneID == currentSceneID.GetSceneID()) {
|
|
const float worldX = (static_cast<float>(i) + (chunk.offsetX / chunk.scaleFactor)) * chunk.scaleFactor;
|
|
const float worldY = y;
|
|
const float worldZ = (static_cast<float>(j) + (chunk.offsetZ / chunk.scaleFactor)) * chunk.scaleFactor;
|
|
|
|
NiPoint3 spawnPos(worldX, worldY, worldZ);
|
|
EntityInfo info;
|
|
info.lot = lot + currentSceneID.GetSceneID();
|
|
info.pos = spawnPos;
|
|
info.rot = QuatUtils::IDENTITY;
|
|
info.spawner = nullptr;
|
|
info.spawnerID = entity->GetObjectID();
|
|
info.spawnerNodeID = 0;
|
|
info.settings.Insert(u"SpawnedFromSlashCommand", true);
|
|
|
|
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
|
if (newEntity != nullptr) {
|
|
Game::entityManager->ConstructEntity(newEntity);
|
|
spawnedCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (spawnedCount == 0) {
|
|
std::ostringstream feedback;
|
|
feedback << "No spawn points found in current scene (ID: " << currentSceneID.GetSceneID() << ").";
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
|
return;
|
|
}
|
|
|
|
// Send feedback
|
|
const auto* sceneRef = zone->GetScene(currentSceneID);
|
|
const std::string sceneName = sceneRef ? sceneRef->name : "Unknown";
|
|
std::ostringstream feedback;
|
|
feedback << "Spawned " << spawnedCount << " points (LOT " << lot + currentSceneID.GetSceneID() << ") in scene "
|
|
<< currentSceneID.GetSceneID() << " (" << sceneName << ").";
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
|
}
|
|
|
|
void SpawnAllScenePoints(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
|
// Hardcoded to use LOT 33
|
|
const uint32_t lot = 33;
|
|
|
|
// Get the zone
|
|
const auto* zone = Game::zoneManager->GetZone();
|
|
if (!zone) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"No zone loaded.");
|
|
return;
|
|
}
|
|
|
|
// Get the Raw terrain data
|
|
const auto& raw = zone->GetZoneRaw();
|
|
if (raw.chunks.empty()) {
|
|
ChatPackets::SendSystemMessage(sysAddr, u"Zone does not have valid terrain data.");
|
|
return;
|
|
}
|
|
|
|
// Spawn at all sceneMap points across all scenes
|
|
uint32_t spawnedCount = 0;
|
|
std::map<uint8_t, uint32_t> sceneSpawnCounts; // Track spawns per scene
|
|
|
|
for (const auto& chunk : raw.chunks) {
|
|
if (chunk.sceneMap.empty() || chunk.colorMapResolution == 0 || chunk.heightMap.empty()
|
|
|| chunk.width <= 1 || chunk.height <= 1 || chunk.scaleFactor <= 0.0f) continue;
|
|
|
|
for (uint32_t i = 0; i < chunk.width; ++i) {
|
|
for (uint32_t j = 0; j < chunk.height; ++j) {
|
|
const uint32_t heightIndex = chunk.width * i + j;
|
|
if (heightIndex >= chunk.heightMap.size()) continue;
|
|
|
|
const float y = chunk.heightMap[heightIndex];
|
|
|
|
const float sceneMapI = (static_cast<float>(i) / static_cast<float>(chunk.width - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
|
|
const float sceneMapJ = (static_cast<float>(j) / static_cast<float>(chunk.height - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
|
|
|
|
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), chunk.colorMapResolution - 1);
|
|
const uint32_t sceneJ = std::min(static_cast<uint32_t>(sceneMapJ), chunk.colorMapResolution - 1);
|
|
const uint32_t sceneIndex = sceneI * chunk.colorMapResolution + sceneJ;
|
|
|
|
uint8_t sceneID = 0;
|
|
if (sceneIndex < chunk.sceneMap.size()) {
|
|
sceneID = chunk.sceneMap[sceneIndex];
|
|
}
|
|
|
|
if (sceneID == 0) continue;
|
|
|
|
const float worldX = (static_cast<float>(i) + (chunk.offsetX / chunk.scaleFactor)) * chunk.scaleFactor;
|
|
const float worldY = y;
|
|
const float worldZ = (static_cast<float>(j) + (chunk.offsetZ / chunk.scaleFactor)) * chunk.scaleFactor;
|
|
|
|
NiPoint3 spawnPos(worldX, worldY, worldZ);
|
|
EntityInfo info;
|
|
info.lot = lot + sceneID;
|
|
info.pos = spawnPos;
|
|
info.rot = QuatUtils::IDENTITY;
|
|
info.spawner = nullptr;
|
|
info.spawnerID = entity->GetObjectID();
|
|
info.spawnerNodeID = 0;
|
|
info.settings.Insert(u"SpawnedFromSlashCommand", true);
|
|
|
|
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
|
if (newEntity != nullptr) {
|
|
Game::entityManager->ConstructEntity(newEntity);
|
|
spawnedCount++;
|
|
sceneSpawnCounts[sceneID]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send detailed feedback
|
|
std::ostringstream feedback;
|
|
feedback << "Spawned " << spawnedCount << " total points (base LOT " << lot << ") across "
|
|
<< sceneSpawnCounts.size() << " scenes:\n";
|
|
|
|
for (const auto& [sceneID, count] : sceneSpawnCounts) {
|
|
const auto* sceneRef = zone->GetScene(LWOSCENEID(sceneID));
|
|
const std::string sceneName = sceneRef ? sceneRef->name : "Unknown";
|
|
feedback << " Scene " << static_cast<int>(sceneID) << ", LOT: " << (lot + sceneID) << " (" << sceneName << "): " << count << " points\n";
|
|
}
|
|
|
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
|
}
|
|
};
|
|
|