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:
David Markowitz
2025-10-05 20:13:27 -07:00
committed by GitHub
parent 5d5bce53d0
commit 62ac65c520
10 changed files with 197 additions and 67 deletions

View File

@@ -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__

View File

@@ -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);

View File

@@ -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;
} }

View File

@@ -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;
}

View File

@@ -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
*/ */

View File

@@ -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) {

View File

@@ -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);
} }

View File

@@ -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

View File

@@ -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>();

View File

@@ -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|