Skip to content

Commit

Permalink
Add spt_hud_ent_info to imgui text hud tab
Browse files Browse the repository at this point in the history
  • Loading branch information
UncraftedName committed Sep 17, 2024
1 parent c9d1280 commit 8a5d9dc
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 30 deletions.
168 changes: 168 additions & 0 deletions spt/features/ent_props.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
#include "spt\utils\portal_utils.hpp"
#include "SPTLib\patterns.hpp"
#include "visualizations/imgui/imgui_interface.hpp"
#include "thirdparty/imgui/imgui_internal.h"

#include <string>
#include <unordered_map>
#include <inttypes.h>
#include <regex>

ConVar y_spt_hud_portal_bubble("y_spt_hud_portal_bubble", "0", FCVAR_CHEAT, "Turns on portal bubble index hud.\n");
ConVar y_spt_hud_ent_info(
Expand Down Expand Up @@ -492,9 +495,174 @@ void EntProps::LoadFeature()
if (result)
{
InitConcommandBase(y_spt_hud_ent_info);
SptImGui::RegisterHudCvarCallback(y_spt_hud_ent_info, ImGuiEntInfoCvarCallback, true);
}
}

void EntProps::ImGuiEntInfoCvarCallback(ConVar& var)
{
SptImGui::CvarValue(var, SptImGui::CVF_ALWAYS_QUOTE);

const char* oldVal = var.GetString();
static ImGuiTextBuffer newVal;
newVal.Buf.resize(0);
newVal.append(""); // explicit null terminator
bool updateCvar = false;

int nEnts = 0;
int nRegexesForEnt = 0;

int i = 0;
int len = strlen(oldVal);

if (whiteSpacesOnly(oldVal))
i = len;

/*
* Goal:
*
* - Parse the cvar value. The logic for this is intentially meant to be a duplicate of
* utils::FillInfoArray(). The exact flow control is slightly different in order to handle
* some ImGui state, but the parsed entity indices & regex should be the same.
*
* - As we parse, reconstruct the cvar value from left to right. If the user interacts with the
* GUI, edit the reconstructed value on the fly (e.g. add an extra regex, drop an ent index,
* etc.) and set the cvar value at the end.
*/

// go up to len so that we can cleanup the stack ID, indent, etc. w/o duplicating code
while (i <= len && nEnts < MAX_ENTRIES)
{
bool atEnd = i >= len;
bool readEntityIndex = !atEnd && (i == 0 || oldVal[i] == ENT_SEPARATOR);
i += i > 0 || nEnts > 0;
int nextSepIdx = i;

// find the next separator
while (nextSepIdx < len && oldVal[nextSepIdx] != PROP_SEPARATOR && oldVal[nextSepIdx] != ENT_SEPARATOR)
++nextSepIdx;

// new entity index, we're at the end, or parse error - cleanup ID, indent, etc.
if ((readEntityIndex || atEnd) && nEnts > 0)
{
if (ImGui::SmallButton("+"))
{
newVal.append(",.*");
updateCvar = true;
}
ImGui::SetItemTooltip("add regex");
ImGui::PopID(); // pop nEnts ID
ImGui::Unindent();
SptImGui::EndBordered();
}

if (atEnd)
break;

// read an index or a regex

if (readEntityIndex)
{
SptImGui::BeginBordered();

long long entIndex = atoll(oldVal + i);
IClientEntity* ent = utils::GetClientEntity(entIndex);
nRegexesForEnt = 0;

if (SptImGui::InputTextInteger("entity index,", "", entIndex, 10))
updateCvar = true;

ImGui::SameLine();
if (ent)
ImGui::Text("(class name: %s)", ent->GetClientClass()->GetName());
else
ImGui::TextColored(SPT_IMGUI_WARN_COLOR_YELLOW, "(invalid entity)");

ImGui::PushID(nEnts++);

ImGui::SameLine();
bool includeEntInNewVal = !ImGui::SmallButton("-");
ImGui::SetItemTooltip("remove entity");
if (includeEntInNewVal)
{
// newVal.empty() instead of e.g. (i == 0) or (nEnts == 0) prevents weird parsing edge cases
newVal.appendf("%s%" PRId64, newVal.empty() ? "" : ";", entIndex);
}
else
{
/*
* Jump ahead to the next entity. Note that we still entered the ID & indent scope
* for this entity - that will be popped in the next loop. This jump means that the
* newVal will not get any data (including the index) associated with this entity.
* There does cause one broken frame because the button for the ent deletion must
* exist but I don't draw the regexes for that entity. This is fixable but I cbf.
*/
while (nextSepIdx < len && oldVal[nextSepIdx] != ENT_SEPARATOR)
++nextSepIdx;
updateCvar = true;
}
ImGui::Indent();
}
else
{
ImGui::PushID(nRegexesForEnt++);

const char* regexStart = oldVal + i;
int regexLen = nextSepIdx - i;
char regexBuf[INFO_BUFFER_SIZE];
strncpy_s(regexBuf, regexStart, regexLen);
// don't allow the characters: ;,"
if (ImGui::InputTextEx(
"##regex",
"enter regex",
regexBuf,
sizeof regexBuf,
ImVec2{0, 0},
ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CallbackCharFilter,
[](ImGuiInputTextCallbackData* data) -> int
{ return data->EventChar >= 256 || strchr(";,\"", (char)data->EventChar); },
nullptr))
{
updateCvar = true;
}
if (ImGui::IsItemHovered() && regexBuf[0] != '\0')
ImGui::SetItemTooltip("enter regex");
ImGui::SameLine();
if (ImGui::SmallButton("-"))
updateCvar = true;
else
newVal.appendf(",%s", regexBuf);
ImGui::SetItemTooltip("remove regex");

// test if the regex is valid, report error if not
try
{
std::regex re{regexBuf};
}
catch (const std::regex_error& exp)
{
ImGui::SameLine();
ImGui::TextColored(SPT_IMGUI_WARN_COLOR_YELLOW, "(invalid regex)");
ImGui::SetItemTooltip("%s", exp.what());
}

ImGui::PopID();
}

i = nextSepIdx;
}

if (ImGui::SmallButton("+"))
{
newVal.appendf("%s-1,.*", nEnts == 0 ? "" : ";");
updateCvar = true;
}
ImGui::SetItemTooltip("add entity index");

if (updateCvar)
var.SetValue(newVal.c_str());
}

void** _InternalPlayerField::GetServerPtr() const
{
auto serverplayer = reinterpret_cast<uintptr_t>(spt_entprops.GetPlayer(true));
Expand Down
2 changes: 2 additions & 0 deletions spt/features/ent_props.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ class EntProps : public FeatureWrapper<EntProps>
std::vector<patterns::MatchedPattern> clientPatterns;
std::vector<utils::DatamapWrapper*> wrappers;
std::unordered_map<std::string, utils::DatamapWrapper*> nameToMapWrapper;

static void ImGuiEntInfoCvarCallback(ConVar& var);
};

extern EntProps spt_entprops;
Expand Down
10 changes: 8 additions & 2 deletions spt/features/visualizations/imgui/imgui_interface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ namespace SptImGuiGroup

// features that access game state
inline Tab GameIo{"Game IO", &Root};
inline Tab GameIo_EntProps{"Ent props", &GameIo};
inline Tab GameIo_ISG{"ISG", &GameIo};

// hud cvars - use the RegisterHudCvarXXX functions below to add cvars
Expand Down Expand Up @@ -264,10 +263,17 @@ class SptImGui

// SPT-related widgets

enum CvarValueFlags
{
CVF_NONE = 0,
CVF_ALWAYS_QUOTE = 1,
// TODO: copy/reset widgets
};

// a button for a ConCommand with no args, does NOT invoke the command (because of OE compat)
static bool CmdButton(const char* label, ConCommand& cmd);
// display cvar name & value, surrounds the value in quotes if it has a space (if the value already has quotes, too bad!)
static void CvarValue(const ConVar& c);
static void CvarValue(const ConVar& c, CvarValueFlags flags = CVF_NONE);
// a checkbox for a boolean cvar, returns value of cvar
static bool CvarCheckbox(ConVar& c, const char* label);
// a combo/dropdown box for a cvar with multiple integer options (does not use clipper - not optimized for huge lists), returns value of cvar
Expand Down
6 changes: 3 additions & 3 deletions spt/features/visualizations/imgui/spt_imgui_widgets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ bool SptImGui::CmdButton(const char* label, ConCommand& cmd)
return ret;
}

void SptImGui::CvarValue(const ConVar& c)
void SptImGui::CvarValue(const ConVar& c, CvarValueFlags flags)
{
const char* v = c.GetString();
const char* surround = "";
if (strchr(v, ' '))
surround = " ";
if ((flags & CVF_ALWAYS_QUOTE) || !*v || strchr(v, ' '))
surround = "\"";
ImGui::TextDisabled("(%s %s%s%s)", WrangleLegacyCommandName(c.GetName(), true, nullptr), surround, v, surround);
ImGui::SameLine();
HelpMarker("%s", c.GetHelpText());
Expand Down
29 changes: 4 additions & 25 deletions spt/utils/ent_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ namespace utils
{
IClientEntity* GetClientEntity(int index)
{
if (index >= MAX_EDICTS)
return nullptr;
return interfaces::entList->GetClientEntity(index + 1);
}

Expand Down Expand Up @@ -318,31 +320,8 @@ namespace utils

while (i < argSize && entries < maxEntries)
{
bool readEntityIndex = false;

// Read entity index
if (args[i] == entSep)
{
++i;
readEntityIndex = true;
}
// Also read if at the start of the string. Note: does not increment the index since we're at the start of the string.
else if (i == 0)
{
readEntityIndex = true;
}
else if (args[i] == sep)
{
++i;
}
else // error occurred
{
i = argSize;
arr = (wchar_t*)L"error occurred in parsing";
entries = 1;
break;
}

bool readEntityIndex = i == 0 || args[i] == entSep;
i += i > 0 || entries > 0; // i is at a separtor from the previous loop, go to next char
int endIndex = i;

// Go to the next separator
Expand Down

0 comments on commit 8a5d9dc

Please sign in to comment.