mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-10-11 01:48:07 +00:00
feat: Mission Component debug (#1901)
* feat: Mission Component debug * Add player argument to inspect command * Add completion details * Remove unlocalized server string done on client instead
This commit is contained in:
@@ -50,7 +50,10 @@ enum class eMissionState : int {
|
|||||||
/**
|
/**
|
||||||
* The mission has been completed before and has now been completed again. Used for daily missions.
|
* The mission has been completed before and has now been completed again. Used for daily missions.
|
||||||
*/
|
*/
|
||||||
COMPLETE_READY_TO_COMPLETE = 12
|
COMPLETE_READY_TO_COMPLETE = 12,
|
||||||
|
|
||||||
|
// The mission is failed (don't know where this is used)
|
||||||
|
FAILED = 16,
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //!__MISSIONSTATE__H__
|
#endif //!__MISSIONSTATE__H__
|
||||||
|
@@ -2247,6 +2247,7 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::GameMsg& msg) {
|
|||||||
response.Insert("objectID", std::to_string(m_ObjectID));
|
response.Insert("objectID", std::to_string(m_ObjectID));
|
||||||
response.Insert("serverInfo", true);
|
response.Insert("serverInfo", true);
|
||||||
GameMessages::GetObjectReportInfo info{};
|
GameMessages::GetObjectReportInfo info{};
|
||||||
|
info.bVerbose = requestInfo.bVerbose;
|
||||||
info.info = response.InsertArray("data");
|
info.info = response.InsertArray("data");
|
||||||
auto& objectInfo = info.info->PushDebug("Object Details");
|
auto& objectInfo = info.info->PushDebug("Object Details");
|
||||||
auto* table = CDClientManager::GetTable<CDObjectsTable>();
|
auto* table = CDClientManager::GetTable<CDObjectsTable>();
|
||||||
@@ -2260,14 +2261,14 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::GameMsg& msg) {
|
|||||||
|
|
||||||
auto& componentDetails = objectInfo.PushDebug("Component Information");
|
auto& componentDetails = objectInfo.PushDebug("Component Information");
|
||||||
for (const auto [id, component] : m_Components) {
|
for (const auto [id, component] : m_Components) {
|
||||||
componentDetails.PushDebug<AMFStringValue>(StringifiedEnum::ToString(id)) = "";
|
componentDetails.PushDebug(StringifiedEnum::ToString(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& configData = objectInfo.PushDebug("Config Data");
|
auto& configData = objectInfo.PushDebug("Config Data");
|
||||||
for (const auto config : m_Settings) {
|
for (const auto config : m_Settings) {
|
||||||
configData.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(config->GetKey())) = config->GetValueAsString();
|
configData.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(config->GetKey())) = config->GetValueAsString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleMsg(info);
|
HandleMsg(info);
|
||||||
|
|
||||||
auto* client = Game::entityManager->GetEntity(requestInfo.clientId);
|
auto* client = Game::entityManager->GetEntity(requestInfo.clientId);
|
||||||
|
@@ -70,7 +70,7 @@ bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
|||||||
for (const auto zoneID : m_VisitedLevels) {
|
for (const auto zoneID : m_VisitedLevels) {
|
||||||
std::stringstream sstream;
|
std::stringstream sstream;
|
||||||
sstream << "MapID: " << zoneID.GetMapID() << " CloneID: " << zoneID.GetCloneID();
|
sstream << "MapID: " << zoneID.GetMapID() << " CloneID: " << zoneID.GetCloneID();
|
||||||
vl.PushDebug<AMFStringValue>(sstream.str()) = "";
|
vl.PushDebug(sstream.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// visited locations
|
// visited locations
|
||||||
@@ -95,7 +95,7 @@ bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
|||||||
const int32_t flagId = base + i;
|
const int32_t flagId = base + i;
|
||||||
std::stringstream stream;
|
std::stringstream stream;
|
||||||
stream << "Flag: " << flagId;
|
stream << "Flag: " << flagId;
|
||||||
allFlags.PushDebug<AMFStringValue>(stream.str()) = "";
|
allFlags.PushDebug(stream.str());
|
||||||
}
|
}
|
||||||
flagChunkCopy >>= 1;
|
flagChunkCopy >>= 1;
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,10 @@ std::unordered_map<AchievementCacheKey, std::vector<uint32_t>> MissionComponent:
|
|||||||
|
|
||||||
//! Initializer
|
//! Initializer
|
||||||
MissionComponent::MissionComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
|
MissionComponent::MissionComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
|
||||||
|
using namespace GameMessages;
|
||||||
m_LastUsedMissionOrderUID = Game::zoneManager->GetUniqueMissionIdStartingValue();
|
m_LastUsedMissionOrderUID = Game::zoneManager->GetUniqueMissionIdStartingValue();
|
||||||
|
|
||||||
|
RegisterMsg<GetObjectReportInfo>(this, &MissionComponent::OnGetObjectReportInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Destructor
|
//! Destructor
|
||||||
@@ -622,3 +625,111 @@ void MissionComponent::ResetMission(const int32_t missionId) {
|
|||||||
m_Missions.erase(missionId);
|
m_Missions.erase(missionId);
|
||||||
GameMessages::SendResetMissions(m_Parent, m_Parent->GetSystemAddress(), missionId);
|
GameMessages::SendResetMissions(m_Parent, m_Parent->GetSystemAddress(), missionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PushMissions(const std::map<uint32_t, Mission*>& missions, AMFArrayValue& V, bool verbose) {
|
||||||
|
for (const auto& [id, mission] : missions) {
|
||||||
|
std::stringstream ss;
|
||||||
|
if (!mission) {
|
||||||
|
ss << "Mission ID: " << id;
|
||||||
|
V.PushDebug(ss.str());
|
||||||
|
} else if (!verbose) {
|
||||||
|
ss << "%[Missions_" << id << "_name]" << ", Mission ID";
|
||||||
|
V.PushDebug<AMFIntValue>(ss.str()) = id;
|
||||||
|
} else {
|
||||||
|
ss << "%[Missions_" << id << "_name]" << ", Mission ID: " << id;
|
||||||
|
auto& missionV = V.PushDebug(ss.str());
|
||||||
|
auto& missionInformation = missionV.PushDebug("Mission Information");
|
||||||
|
|
||||||
|
if (mission->IsComplete()) {
|
||||||
|
missionInformation.PushDebug<AMFStringValue>("Time mission last completed") = std::to_string(mission->GetTimestamp());
|
||||||
|
missionInformation.PushDebug<AMFIntValue>("Number of times completed") = mission->GetCompletions();
|
||||||
|
}
|
||||||
|
// Expensive to network this especially when its read from the client anyways
|
||||||
|
// missionInformation.PushDebug("Description").PushDebug("None");
|
||||||
|
// missionInformation.PushDebug("Text").PushDebug("None");
|
||||||
|
|
||||||
|
auto& statusInfo = missionInformation.PushDebug("Mission statuses for local player");
|
||||||
|
if (mission->IsAvalible()) statusInfo.PushDebug("Available");
|
||||||
|
if (mission->IsActive()) statusInfo.PushDebug("Active");
|
||||||
|
if (mission->IsReadyToComplete()) statusInfo.PushDebug("Ready To Complete");
|
||||||
|
if (mission->IsComplete()) statusInfo.PushDebug("Completed");
|
||||||
|
if (mission->IsFailed()) statusInfo.PushDebug("Failed");
|
||||||
|
const auto& clientInfo = mission->GetClientInfo();
|
||||||
|
|
||||||
|
statusInfo.PushDebug<AMFBoolValue>("Is an achievement mission") = mission->IsAchievement();
|
||||||
|
statusInfo.PushDebug<AMFBoolValue>("Is an timed mission") = clientInfo.time_limit > 0;
|
||||||
|
auto& taskInfo = statusInfo.PushDebug("Task Info");
|
||||||
|
taskInfo.PushDebug<AMFIntValue>("Number of tasks in this mission") = mission->GetTasks().size();
|
||||||
|
int32_t i = 0;
|
||||||
|
for (const auto* task : mission->GetTasks()) {
|
||||||
|
auto& thisTask = taskInfo.PushDebug("Task " + std::to_string(i));
|
||||||
|
// Expensive to network this especially when its read from the client anyways
|
||||||
|
// thisTask.PushDebug("Description").PushDebug("%[MissionTasks_" + taskUidStr + "_description]");
|
||||||
|
thisTask.PushDebug<AMFIntValue>("Number done") = std::min(task->GetProgress(), static_cast<uint32_t>(task->GetClientInfo().targetValue));
|
||||||
|
thisTask.PushDebug<AMFIntValue>("Number total needed") = task->GetClientInfo().targetValue;
|
||||||
|
thisTask.PushDebug<AMFIntValue>("Task Type") = task->GetClientInfo().taskType;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// auto& chatText = missionInformation.PushDebug("Chat Text for Mission States");
|
||||||
|
// Expensive to network this especially when its read from the client anyways
|
||||||
|
// chatText.PushDebug("Available Text").PushDebug("%[MissionText_" + idStr + "_chat_state_1]");
|
||||||
|
// chatText.PushDebug("Active Text").PushDebug("%[MissionText_" + idStr + "_chat_state_2]");
|
||||||
|
// chatText.PushDebug("Ready-to-Complete Text").PushDebug("%[MissionText_" + idStr + "_chat_state_3]");
|
||||||
|
// chatText.PushDebug("Complete Text").PushDebug("%[MissionText_" + idStr + "_chat_state_4]");
|
||||||
|
|
||||||
|
if (clientInfo.time_limit > 0) {
|
||||||
|
missionInformation.PushDebug<AMFIntValue>("Time Limit") = clientInfo.time_limit;
|
||||||
|
missionInformation.PushDebug<AMFDoubleValue>("Time Remaining") = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientInfo.offer_objectID != -1) {
|
||||||
|
missionInformation.PushDebug<AMFIntValue>("Offer Object LOT") = clientInfo.offer_objectID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientInfo.target_objectID != -1) {
|
||||||
|
missionInformation.PushDebug<AMFIntValue>("Complete Object LOT") = clientInfo.target_objectID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clientInfo.prereqMissionID.empty()) {
|
||||||
|
missionInformation.PushDebug<AMFStringValue>("Requirement Mission IDs") = clientInfo.prereqMissionID;
|
||||||
|
}
|
||||||
|
|
||||||
|
missionInformation.PushDebug<AMFBoolValue>("Is Repeatable") = clientInfo.repeatable;
|
||||||
|
const bool hasNoOfferer = clientInfo.offer_objectID == -1 || clientInfo.offer_objectID == 0;
|
||||||
|
const bool hasNoCompleter = clientInfo.target_objectID == -1 || clientInfo.target_objectID == 0;
|
||||||
|
missionInformation.PushDebug<AMFBoolValue>("Is Achievement") = hasNoOfferer && hasNoCompleter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MissionComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||||
|
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||||
|
auto& missionInfo = reportMsg.info->PushDebug("Mission (Laggy)");
|
||||||
|
missionInfo.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||||
|
// Sort the missions so they are easier to parse and present to the end user
|
||||||
|
std::map<uint32_t, Mission*> achievements;
|
||||||
|
std::map<uint32_t, Mission*> missions;
|
||||||
|
std::map<uint32_t, Mission*> doneMissions;
|
||||||
|
for (const auto [id, mission] : m_Missions) {
|
||||||
|
if (!mission) continue;
|
||||||
|
else if (mission->IsComplete()) doneMissions[id] = mission;
|
||||||
|
else if (mission->IsAchievement()) achievements[id] = mission;
|
||||||
|
else if (mission->IsMission()) missions[id] = mission;
|
||||||
|
}
|
||||||
|
|
||||||
|
// None of these should be empty, but if they are dont print the field
|
||||||
|
if (!achievements.empty() || !missions.empty()) {
|
||||||
|
auto& incompleteMissions = missionInfo.PushDebug("Incomplete Missions");
|
||||||
|
PushMissions(achievements, incompleteMissions, reportMsg.bVerbose);
|
||||||
|
PushMissions(missions, incompleteMissions, reportMsg.bVerbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doneMissions.empty()) {
|
||||||
|
auto& completeMissions = missionInfo.PushDebug("Completed Missions");
|
||||||
|
PushMissions(doneMissions, completeMissions, reportMsg.bVerbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@@ -171,6 +171,7 @@ public:
|
|||||||
|
|
||||||
void ResetMission(const int32_t missionId);
|
void ResetMission(const int32_t missionId);
|
||||||
private:
|
private:
|
||||||
|
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||||
/**
|
/**
|
||||||
* All the missions owned by this entity, mapped by mission ID
|
* All the missions owned by this entity, mapped by mission ID
|
||||||
*/
|
*/
|
||||||
|
@@ -6415,6 +6415,7 @@ namespace GameMessages {
|
|||||||
void RequestServerObjectInfo::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
void RequestServerObjectInfo::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||||
auto* handlingEntity = Game::entityManager->GetEntity(targetForReport);
|
auto* handlingEntity = Game::entityManager->GetEntity(targetForReport);
|
||||||
if (handlingEntity) handlingEntity->HandleMsg(*this);
|
if (handlingEntity) handlingEntity->HandleMsg(*this);
|
||||||
|
else LOG("Failed to find target %llu", targetForReport);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestUse::Deserialize(RakNet::BitStream& stream) {
|
bool RequestUse::Deserialize(RakNet::BitStream& stream) {
|
||||||
|
@@ -270,6 +270,12 @@ bool Mission::IsReadyToComplete() const {
|
|||||||
return m_State == eMissionState::READY_TO_COMPLETE || m_State == eMissionState::COMPLETE_READY_TO_COMPLETE;
|
return m_State == eMissionState::READY_TO_COMPLETE || m_State == eMissionState::COMPLETE_READY_TO_COMPLETE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Mission::IsFailed() const {
|
||||||
|
const auto underlying = GeneralUtils::ToUnderlying(m_State);
|
||||||
|
const auto target = GeneralUtils::ToUnderlying(eMissionState::FAILED);
|
||||||
|
return (underlying & target) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
void Mission::MakeReadyToComplete() {
|
void Mission::MakeReadyToComplete() {
|
||||||
SetMissionState(m_Completions == 0 ? eMissionState::READY_TO_COMPLETE : eMissionState::COMPLETE_READY_TO_COMPLETE);
|
SetMissionState(m_Completions == 0 ? eMissionState::READY_TO_COMPLETE : eMissionState::COMPLETE_READY_TO_COMPLETE);
|
||||||
}
|
}
|
||||||
|
@@ -244,6 +244,8 @@ public:
|
|||||||
|
|
||||||
const std::set<uint32_t>& GetTestedMissions() const;
|
const std::set<uint32_t>& GetTestedMissions() const;
|
||||||
|
|
||||||
|
bool IsFailed() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Progresses all the newly accepted tasks for this mission after it has been accepted to reflect the state of the
|
* Progresses all the newly accepted tasks for this mission after it has been accepted to reflect the state of the
|
||||||
|
@@ -1475,50 +1475,55 @@ namespace DEVGMCommands {
|
|||||||
if (splitArgs.empty()) return;
|
if (splitArgs.empty()) return;
|
||||||
|
|
||||||
Entity* closest = nullptr;
|
Entity* closest = nullptr;
|
||||||
|
float closestDistance = 0.0f;
|
||||||
|
|
||||||
std::u16string ldf;
|
std::u16string ldf;
|
||||||
|
|
||||||
bool isLDF = false;
|
bool isLDF = false;
|
||||||
|
|
||||||
auto component = GeneralUtils::TryParse<eReplicaComponentType>(splitArgs[0]);
|
closest = PlayerManager::GetPlayer(splitArgs[0]);
|
||||||
if (!component) {
|
if (!closest) {
|
||||||
component = eReplicaComponentType::INVALID;
|
auto component = GeneralUtils::TryParse<eReplicaComponentType>(splitArgs[0]);
|
||||||
|
if (!component) {
|
||||||
|
component = eReplicaComponentType::INVALID;
|
||||||
|
|
||||||
ldf = GeneralUtils::UTF8ToUTF16(splitArgs[0]);
|
ldf = GeneralUtils::UTF8ToUTF16(splitArgs[0]);
|
||||||
|
|
||||||
isLDF = true;
|
isLDF = true;
|
||||||
}
|
|
||||||
|
|
||||||
auto reference = entity->GetPosition();
|
|
||||||
|
|
||||||
auto closestDistance = 0.0f;
|
|
||||||
|
|
||||||
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)) {
|
auto reference = entity->GetPosition();
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
const auto candidates = Game::entityManager->GetEntitiesByComponent(component.value());
|
||||||
if (!closest) {
|
|
||||||
closest = candidate;
|
for (auto* candidate : candidates) {
|
||||||
|
if (candidate->GetLOT() == 1 || candidate->GetLOT() == 8092) {
|
||||||
closestDistance = NiPoint3::Distance(candidate->GetPosition(), reference);
|
continue;
|
||||||
|
}
|
||||||
continue;
|
|
||||||
}
|
if (isLDF && !candidate->HasVar(ldf)) {
|
||||||
|
continue;
|
||||||
const auto distance = NiPoint3::Distance(candidate->GetPosition(), reference);
|
}
|
||||||
|
|
||||||
if (distance < closestDistance) {
|
if (!closest) {
|
||||||
closest = candidate;
|
closest = candidate;
|
||||||
|
|
||||||
closestDistance = distance;
|
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;
|
if (!closest) return;
|
||||||
@@ -1684,7 +1689,7 @@ namespace DEVGMCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||||
|
|
||||||
// Prevent execute command recursion by checking if this is already an execute command
|
// Prevent execute command recursion by checking if this is already an execute command
|
||||||
for (const auto& arg : splitArgs) {
|
for (const auto& arg : splitArgs) {
|
||||||
if (arg == "execute" || arg == "exec") {
|
if (arg == "execute" || arg == "exec") {
|
||||||
@@ -1692,51 +1697,51 @@ namespace DEVGMCommands {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context variables for execution
|
// Context variables for execution
|
||||||
Entity* execEntity = entity; // Entity to execute as
|
Entity* execEntity = entity; // Entity to execute as
|
||||||
NiPoint3 execPosition = entity->GetPosition(); // Position to execute from
|
NiPoint3 execPosition = entity->GetPosition(); // Position to execute from
|
||||||
bool positionOverridden = false;
|
bool positionOverridden = false;
|
||||||
std::string finalCommand;
|
std::string finalCommand;
|
||||||
|
|
||||||
// Parse subcommands
|
// Parse subcommands
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
while (i < splitArgs.size()) {
|
while (i < splitArgs.size()) {
|
||||||
const std::string& subcommand = splitArgs[i];
|
const std::string& subcommand = splitArgs[i];
|
||||||
|
|
||||||
if (subcommand == "as") {
|
if (subcommand == "as") {
|
||||||
if (i + 1 >= splitArgs.size()) {
|
if (i + 1 >= splitArgs.size()) {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'as' requires a player name");
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'as' requires a player name");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& targetName = splitArgs[i + 1];
|
const std::string& targetName = splitArgs[i + 1];
|
||||||
auto* targetPlayer = PlayerManager::GetPlayer(targetName);
|
auto* targetPlayer = PlayerManager::GetPlayer(targetName);
|
||||||
if (!targetPlayer) {
|
if (!targetPlayer) {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Error: Player '" + GeneralUtils::ASCIIToUTF16(targetName) + u"' not found");
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: Player '" + GeneralUtils::ASCIIToUTF16(targetName) + u"' not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
execEntity = targetPlayer;
|
execEntity = targetPlayer;
|
||||||
i += 2;
|
i += 2;
|
||||||
|
|
||||||
} else if (subcommand == "at") {
|
} else if (subcommand == "at") {
|
||||||
if (i + 1 >= splitArgs.size()) {
|
if (i + 1 >= splitArgs.size()) {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'at' requires a player name");
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'at' requires a player name");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& targetName = splitArgs[i + 1];
|
const std::string& targetName = splitArgs[i + 1];
|
||||||
auto* targetPlayer = PlayerManager::GetPlayer(targetName);
|
auto* targetPlayer = PlayerManager::GetPlayer(targetName);
|
||||||
if (!targetPlayer) {
|
if (!targetPlayer) {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Error: Player '" + GeneralUtils::ASCIIToUTF16(targetName) + u"' not found");
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: Player '" + GeneralUtils::ASCIIToUTF16(targetName) + u"' not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
execPosition = targetPlayer->GetPosition();
|
execPosition = targetPlayer->GetPosition();
|
||||||
positionOverridden = true;
|
positionOverridden = true;
|
||||||
i += 2;
|
i += 2;
|
||||||
|
|
||||||
} else if (subcommand == "positioned") {
|
} else if (subcommand == "positioned") {
|
||||||
if (i + 3 >= splitArgs.size()) {
|
if (i + 3 >= splitArgs.size()) {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'positioned' requires x, y, z coordinates");
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'positioned' requires x, y, z coordinates");
|
||||||
@@ -1754,69 +1759,69 @@ namespace DEVGMCommands {
|
|||||||
|
|
||||||
execPosition = NiPoint3(xOpt.value(), yOpt.value(), zOpt.value());
|
execPosition = NiPoint3(xOpt.value(), yOpt.value(), zOpt.value());
|
||||||
positionOverridden = true;
|
positionOverridden = true;
|
||||||
|
|
||||||
i += 4;
|
i += 4;
|
||||||
|
|
||||||
} else if (subcommand == "run") {
|
} else if (subcommand == "run") {
|
||||||
// Everything after "run" is the command to execute
|
// Everything after "run" is the command to execute
|
||||||
if (i + 1 >= splitArgs.size()) {
|
if (i + 1 >= splitArgs.size()) {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'run' requires a command");
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'run' requires a command");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reconstruct the command from remaining args
|
// Reconstruct the command from remaining args
|
||||||
for (size_t j = i + 1; j < splitArgs.size(); ++j) {
|
for (size_t j = i + 1; j < splitArgs.size(); ++j) {
|
||||||
if (!finalCommand.empty()) finalCommand += " ";
|
if (!finalCommand.empty()) finalCommand += " ";
|
||||||
finalCommand += splitArgs[j];
|
finalCommand += splitArgs[j];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Error: Unknown subcommand '" + GeneralUtils::ASCIIToUTF16(subcommand) + u"'");
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: Unknown subcommand '" + GeneralUtils::ASCIIToUTF16(subcommand) + u"'");
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Valid subcommands: as, at, positioned, run");
|
ChatPackets::SendSystemMessage(sysAddr, u"Valid subcommands: as, at, positioned, run");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finalCommand.empty()) {
|
if (finalCommand.empty()) {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Error: No command specified to run. Use 'run <command>' at the end.");
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: No command specified to run. Use 'run <command>' at the end.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that the command starts with a valid character
|
// Validate that the command starts with a valid character
|
||||||
if (finalCommand.empty() || finalCommand[0] == '/') {
|
if (finalCommand.empty() || finalCommand[0] == '/') {
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"Error: Command should not start with '/'. Just specify the command name.");
|
ChatPackets::SendSystemMessage(sysAddr, u"Error: Command should not start with '/'. Just specify the command name.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store original position if we need to restore it
|
// Store original position if we need to restore it
|
||||||
NiPoint3 originalPosition;
|
NiPoint3 originalPosition;
|
||||||
bool needToRestore = false;
|
bool needToRestore = false;
|
||||||
|
|
||||||
if (positionOverridden && execEntity == entity) {
|
if (positionOverridden && execEntity == entity) {
|
||||||
// If we're executing as ourselves but from a different position,
|
// If we're executing as ourselves but from a different position,
|
||||||
// temporarily move the entity
|
// temporarily move the entity
|
||||||
originalPosition = entity->GetPosition();
|
originalPosition = entity->GetPosition();
|
||||||
needToRestore = true;
|
needToRestore = true;
|
||||||
|
|
||||||
// Set the position temporarily for the command execution
|
// Set the position temporarily for the command execution
|
||||||
auto* controllable = entity->GetComponent<ControllablePhysicsComponent>();
|
auto* controllable = entity->GetComponent<ControllablePhysicsComponent>();
|
||||||
if (controllable) {
|
if (controllable) {
|
||||||
controllable->SetPosition(execPosition);
|
controllable->SetPosition(execPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide feedback about what we're executing
|
// Provide feedback about what we're executing
|
||||||
std::string execAsName = execEntity->GetCharacter() ? execEntity->GetCharacter()->GetName() : "Unknown";
|
std::string execAsName = execEntity->GetCharacter() ? execEntity->GetCharacter()->GetName() : "Unknown";
|
||||||
ChatPackets::SendSystemMessage(sysAddr, u"[Execute] Running as '" + GeneralUtils::ASCIIToUTF16(execAsName) +
|
ChatPackets::SendSystemMessage(sysAddr, u"[Execute] Running as '" + GeneralUtils::ASCIIToUTF16(execAsName) +
|
||||||
u"' from <" + GeneralUtils::to_u16string(execPosition.x) + u", " +
|
u"' from <" + GeneralUtils::to_u16string(execPosition.x) + u", " +
|
||||||
GeneralUtils::to_u16string(execPosition.y) + u", " +
|
GeneralUtils::to_u16string(execPosition.y) + u", " +
|
||||||
GeneralUtils::to_u16string(execPosition.z) + u">: /" +
|
GeneralUtils::to_u16string(execPosition.z) + u">: /" +
|
||||||
GeneralUtils::ASCIIToUTF16(finalCommand));
|
GeneralUtils::ASCIIToUTF16(finalCommand));
|
||||||
|
|
||||||
// Execute the command through the slash command handler
|
// Execute the command through the slash command handler
|
||||||
SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16("/" + finalCommand), execEntity, sysAddr);
|
SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16("/" + finalCommand), execEntity, sysAddr);
|
||||||
|
|
||||||
// Restore original position if needed
|
// Restore original position if needed
|
||||||
if (needToRestore) {
|
if (needToRestore) {
|
||||||
auto* controllable = entity->GetComponent<ControllablePhysicsComponent>();
|
auto* controllable = entity->GetComponent<ControllablePhysicsComponent>();
|
||||||
|
@@ -80,7 +80,7 @@ These commands are primarily for development and testing. The usage of many of t
|
|||||||
|getnavmeshheight|`/getnavmeshheight`|Displays the navmesh height at your current position.|8|
|
|getnavmeshheight|`/getnavmeshheight`|Displays the navmesh height at your current position.|8|
|
||||||
|giveuscore|`/giveuscore <uscore>`|Gives uscore.|8|
|
|giveuscore|`/giveuscore <uscore>`|Gives uscore.|8|
|
||||||
|gmadditem|`/gmadditem <id> (count)`|Adds the given item to your inventory by id.|8|
|
|gmadditem|`/gmadditem <id> (count)`|Adds the given item to your inventory by id.|8|
|
||||||
|inspect|`/inspect <component> (-m <waypoint> \| -a <animation> \| -s \| -p \| -f (faction) \| -t)`|Finds the closest entity with the given component or LDF variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the the IDs of all components the entity has. See [Detailed `/inspect` Usage](#detailed-inspect-usage) below.|8|
|
|inspect|`/inspect <component or ldf variable or player name> (-m <waypoint> \| -a <animation> \| -s \| -p \| -f (faction) \| -t)`|Finds the closest entity with the given component or LDF variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the the IDs of all components the entity has. See [Detailed `/inspect` Usage](#detailed-inspect-usage) below.|8|
|
||||||
|list-spawns|`/list-spawns`|Lists all the character spawn points in the zone. Additionally, this command will display the current scene that plays when the character lands in the next zone, if there is one.|8|
|
|list-spawns|`/list-spawns`|Lists all the character spawn points in the zone. Additionally, this command will display the current scene that plays when the character lands in the next zone, if there is one.|8|
|
||||||
|locrow|`/locrow`|Prints the your current position and rotation information to the console.|8|
|
|locrow|`/locrow`|Prints the your current position and rotation information to the console.|8|
|
||||||
|lookup|`/lookup <query>`|Searches through the Objects table in the client SQLite database for items whose display name, name, or description contains the query. Query can be multiple words delimited by spaces.|8|
|
|lookup|`/lookup <query>`|Searches through the Objects table in the client SQLite database for items whose display name, name, or description contains the query. Query can be multiple words delimited by spaces.|8|
|
||||||
|
Reference in New Issue
Block a user