Add debug gui window

This commit is contained in:
Federico Cecchetto 2022-01-07 01:22:05 +01:00
parent 655868d4ba
commit ce3245a57f
4 changed files with 565 additions and 6 deletions

View File

@ -0,0 +1,504 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "scheduler.hpp"
#include "command.hpp"
#include "gui.hpp"
#include "game/scripting/lua/context.hpp"
#include "game/scripting/lua/engine.hpp"
#include "game/scripting/execution.hpp"
#include <utils/string.hpp>
#include <utils/hook.hpp>
#include <utils/concurrency.hpp>
namespace gui_debug
{
namespace
{
game::dvar_t* cl_paused = nullptr;
enum object_type
{
square,
cube,
cube_mesh
};
float camera[3] = {};
float axis[3][3] = {};
struct draw_settings
{
bool enabled;
bool native_rendering;
bool camera_locked;
float camera[3] = {};
float range = 500.f;
float color[4] = {1.f, 0.f, 0.f, 1.f};
float mesh_thickness = 1.f;
float size = 10.f;
object_type type;
};
struct : draw_settings
{
float link_thickness = 1.f;
bool draw_linked_nodes;
} path_node_settings{};
float vector_dot(float* a, float* b)
{
return (a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]);
}
bool world_pos_to_screen_pos(float* origin, float* out)
{
float local[3] =
{
origin[0] - camera[0],
origin[1] - camera[1],
origin[2] - camera[2]
};
float transform[3] =
{
vector_dot(local, axis[1]),
vector_dot(local, axis[2]),
vector_dot(local, axis[0])
};
if (transform[2] < 0.1f)
{
return false;
}
const auto width = game::ScrPlace_GetViewPlacement()->realViewportSize[0];
const auto height = game::ScrPlace_GetViewPlacement()->realViewportSize[1];
out[0] = (width / 2) * (1 - transform[0] / game::refdef->fovX / transform[2]);
out[1] = (height / 2) * (1 - transform[1] / game::refdef->fovY / transform[2]);
return true;
}
void draw_line(float* start, float* end, float* color, float thickness)
{
ImGuiWindow* window = ImGui::GetCurrentWindow();
float start_screen[2] = {};
float end_screen[2] = {};
auto result = 1;
result *= world_pos_to_screen_pos(start, start_screen);
result *= world_pos_to_screen_pos(end, end_screen);
if (!result)
{
return;
}
const auto start_ = ImVec2(start_screen[0], start_screen[1]);
const auto end_ = ImVec2(end_screen[0], end_screen[1]);
const auto color_ = ImGui::GetColorU32({color[0], color[1], color[2], color[3]});
window->DrawList->AddLine(start_, end_, color_, thickness);
}
void draw_square(float* origin, float width, float* color)
{
const auto half = width / 2.f;
float p1[3] = {origin[0] - half, origin[1] + half, origin[2]};
float p2[3] = {origin[0] + half, origin[1] + half, origin[2]};
float p3[3] = {origin[0] + half, origin[1] - half, origin[2]};
float p4[3] = {origin[0] - half, origin[1] - half, origin[2]};
float p1_screen[2] = {};
float p2_screen[2] = {};
float p3_screen[2] = {};
float p4_screen[2] = {};
auto result = 1;
result *= world_pos_to_screen_pos(p1, p1_screen);
result *= world_pos_to_screen_pos(p2, p2_screen);
result *= world_pos_to_screen_pos(p3, p3_screen);
result *= world_pos_to_screen_pos(p4, p4_screen);
if (!result)
{
return;
}
ImGuiWindow* window = ImGui::GetCurrentWindow();
ImVec2 points[4] =
{
ImVec2(p1_screen[0], p1_screen[1]),
ImVec2(p2_screen[0], p2_screen[1]),
ImVec2(p3_screen[0], p3_screen[1]),
ImVec2(p4_screen[0], p4_screen[1])
};
const auto color_ = ImGui::GetColorU32({color[0], color[1], color[2], color[3]});
window->DrawList->AddConvexPolyFilled(points, 4, color_);
}
void draw_square_from_points(float* p1, float* p2, float* p3, float* p4, float* color)
{
float p1_screen[2] = {};
float p2_screen[2] = {};
float p3_screen[2] = {};
float p4_screen[2] = {};
auto result = 1;
result *= world_pos_to_screen_pos(p1, p1_screen);
result *= world_pos_to_screen_pos(p2, p2_screen);
result *= world_pos_to_screen_pos(p3, p3_screen);
result *= world_pos_to_screen_pos(p4, p4_screen);
if (!result)
{
return;
}
ImGuiWindow* window = ImGui::GetCurrentWindow();
ImVec2 points[4] =
{
ImVec2(p1_screen[0], p1_screen[1]),
ImVec2(p2_screen[0], p2_screen[1]),
ImVec2(p3_screen[0], p3_screen[1]),
ImVec2(p4_screen[0], p4_screen[1])
};
const auto color_ = ImGui::GetColorU32({color[0], color[1], color[2], color[3]});
window->DrawList->AddConvexPolyFilled(points, 4, color_);
}
void draw_cube(float* origin, float width, float* color)
{
const auto half = width / 2.f;
float p1[3] = {origin[0] - half, origin[1] + half, origin[2]};
float p2[3] = {origin[0] + half, origin[1] + half, origin[2]};
float p3[3] = {origin[0] + half, origin[1] - half, origin[2]};
float p4[3] = {origin[0] - half, origin[1] - half, origin[2]};
float p1_top[3] = {p1[0], p1[1], origin[2] + width};
float p2_top[3] = {p2[0], p2[1], origin[2] + width};
float p3_top[3] = {p3[0], p3[1], origin[2] + width};
float p4_top[3] = {p4[0], p4[1], origin[2] + width};
draw_square_from_points(p1, p2, p3, p4, color);
draw_square_from_points(p1_top, p2_top, p3_top, p4_top, color);
draw_square_from_points(p3, p2, p2_top, p3_top, color);
draw_square_from_points(p4, p1, p1_top, p4_top, color);
draw_square_from_points(p1, p2, p2_top, p1_top, color);
draw_square_from_points(p4, p3, p3_top, p4_top, color);
}
void draw_cube_mesh(float* origin, float width, float* color, float thickness)
{
const auto half = width / 2.f;
float p1[3] = {origin[0] - half, origin[1] + half, origin[2]};
float p2[3] = {origin[0] + half, origin[1] + half, origin[2]};
float p3[3] = {origin[0] + half, origin[1] - half, origin[2]};
float p4[3] = {origin[0] - half, origin[1] - half, origin[2]};
float p1_top[3] = {p1[0], p1[1], origin[2] + width};
float p2_top[3] = {p2[0], p2[1], origin[2] + width};
float p3_top[3] = {p3[0], p3[1], origin[2] + width};
float p4_top[3] = {p4[0], p4[1], origin[2] + width};
draw_line(p1, p2, color, thickness);
draw_line(p2, p3, color, thickness);
draw_line(p3, p4, color, thickness);
draw_line(p4, p1, color, thickness);
draw_line(p1_top, p2_top, color, thickness);
draw_line(p2_top, p3_top, color, thickness);
draw_line(p3_top, p4_top, color, thickness);
draw_line(p4_top, p1_top, color, thickness);
draw_line(p1, p1_top, color, thickness);
draw_line(p2, p2_top, color, thickness);
draw_line(p3, p3_top, color, thickness);
draw_line(p4, p4_top, color, thickness);
}
void draw_square_native(float* origin, float width, float* color)
{
const auto half = width / 2.f;
float p1[3] = {origin[0] - half, origin[1] + half, origin[2]};
float p2[3] = {origin[0] + half, origin[1] + half, origin[2]};
float p3[3] = {origin[0] + half, origin[1] - half, origin[2]};
float p4[3] = {origin[0] - half, origin[1] - half, origin[2]};
float p1_screen[2] = {};
float p2_screen[2] = {};
float p3_screen[2] = {};
float p4_screen[2] = {};
auto result = 1;
result *= world_pos_to_screen_pos(p1, p1_screen);
result *= world_pos_to_screen_pos(p2, p2_screen);
result *= world_pos_to_screen_pos(p3, p3_screen);
result *= world_pos_to_screen_pos(p4, p4_screen);
if (!result)
{
return;
}
game::rectangle rect;
rect.p0.x = p1_screen[0];
rect.p0.y = p1_screen[1];
rect.p0.f2 = 0.f;
rect.p0.f3 = 1.f;
rect.p1.x = p2_screen[0];
rect.p1.y = p2_screen[1];
rect.p1.f2 = 0.f;
rect.p1.f3 = 1.f;
rect.p2.x = p3_screen[0];
rect.p2.y = p3_screen[1];
rect.p2.f2 = 0.f;
rect.p2.f3 = 1.f;
rect.p3.x = p4_screen[0];
rect.p3.y = p4_screen[1];
rect.p3.f2 = 0.f;
rect.p3.f3 = 1.f;
const auto material = game::Material_RegisterHandle("white");
game::R_DrawRectangle(&rect, 0.f, 0.f, 1.f, 1.f, color, material);
}
void draw_window()
{
if (!gui::enabled_menus["debug"])
{
return;
}
ImGui::Begin("Debug", &gui::enabled_menus["debug"]);
if (ImGui::TreeNode("Path nodes"))
{
ImGui::Checkbox("Draw", &path_node_settings.enabled);
ImGui::Checkbox("Native rendering", &path_node_settings.native_rendering);
ImGui::Checkbox("Lock camera", &path_node_settings.camera_locked);
if (!path_node_settings.native_rendering)
{
ImGui::Checkbox("Draw linked nodes", &path_node_settings.draw_linked_nodes);
if (ImGui::TreeNode("Object type"))
{
ImGui::RadioButton("square", reinterpret_cast<int*>(&path_node_settings.type), object_type::square);
ImGui::RadioButton("cube", reinterpret_cast<int*>(&path_node_settings.type), object_type::cube);
ImGui::RadioButton("cube mesh", reinterpret_cast<int*>(&path_node_settings.type), object_type::cube_mesh);
ImGui::TreePop();
}
}
ImGui::SliderFloat("range", &path_node_settings.range, 0.f, 10000.f);
ImGui::SliderFloat("size", &path_node_settings.size, 5.f, 100.f);
if (path_node_settings.draw_linked_nodes)
{
ImGui::SliderFloat("link thickness", &path_node_settings.link_thickness, 1.f, 20.f);
}
if (path_node_settings.type == object_type::cube_mesh)
{
ImGui::SliderFloat("mesh thickness", &path_node_settings.mesh_thickness, 1.f, 20.f);
}
if (ImGui::TreeNode("Color picker"))
{
ImGui::ColorPicker4("color", path_node_settings.color);
ImGui::TreePop();
}
ImGui::TreePop();
}
ImGui::End();
}
float distance_2d(float* a, float* b)
{
return sqrt((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]));
}
void get_pathnode_origin(game::pathnode_t* node, float* out)
{
out[0] = node->vLocalOrigin[0];
out[1] = node->vLocalOrigin[1];
out[2] = node->vLocalOrigin[2];
game::PathNode_WorldifyPosFromParent(node, out);
}
void draw_linked_nodes(game::pathnode_t* node, float* origin)
{
for (unsigned int i = 0; i < node->totalLinkCount; i++)
{
float linked_origin[3] = {};
const auto num = node->Links[i].nodeNum;
const auto linked = &game::pathData->nodes[num];
get_pathnode_origin(linked, linked_origin);
if (distance_2d(path_node_settings.camera, linked_origin) < path_node_settings.range)
{
draw_line(origin, linked_origin, path_node_settings.color,
path_node_settings.link_thickness);
}
}
}
void draw_node_object(float* origin)
{
switch (path_node_settings.type)
{
case object_type::square:
draw_square(origin, path_node_settings.size, path_node_settings.color);
break;
case object_type::cube:
draw_cube(origin, path_node_settings.size, path_node_settings.color);
break;
case object_type::cube_mesh:
draw_cube_mesh(origin, path_node_settings.size, path_node_settings.color,
path_node_settings.mesh_thickness);
break;
}
}
void draw_nodes()
{
if (!game::SV_Loaded() || path_node_settings.native_rendering ||
!path_node_settings.enabled || cl_paused->current.integer)
{
return;
}
const auto& io = ImGui::GetIO();
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {0.0f, 0.0f});
ImGui::PushStyleColor(ImGuiCol_WindowBg, {0.0f, 0.0f, 0.0f, 0.0f});
ImGui::Begin("debug window", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs);
ImGui::SetWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::SetWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y), ImGuiCond_Always);
for (unsigned int i = 0; i < game::pathData->nodeCount; i++)
{
float origin[3] = {};
const auto node = &game::pathData->nodes[i];
get_pathnode_origin(node, origin);
if (distance_2d(path_node_settings.camera, origin) >= path_node_settings.range)
{
continue;
}
draw_node_object(origin);
if (path_node_settings.draw_linked_nodes)
{
draw_linked_nodes(node, origin);
}
}
ImGuiWindow* window = ImGui::GetCurrentWindow();
window->DrawList->PushClipRectFullScreen();
ImGui::End();
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
}
void update_camera()
{
camera[0] = game::refdef->org[0];
camera[1] = game::refdef->org[1];
camera[2] = game::refdef->org[2];
axis[0][0] = game::refdef->axis[0][0];
axis[0][1] = game::refdef->axis[0][1];
axis[0][2] = game::refdef->axis[0][2];
axis[1][0] = game::refdef->axis[1][0];
axis[1][1] = game::refdef->axis[1][1];
axis[1][2] = game::refdef->axis[1][2];
axis[2][0] = game::refdef->axis[2][0];
axis[2][1] = game::refdef->axis[2][1];
axis[2][2] = game::refdef->axis[2][2];
if (!path_node_settings.camera_locked)
{
path_node_settings.camera[0] = camera[0];
path_node_settings.camera[1] = camera[1];
path_node_settings.camera[2] = camera[2];
}
}
void draw_nodes_native()
{
if (!game::SV_Loaded() || !path_node_settings.enabled || cl_paused->current.integer)
{
return;
}
update_camera();
if (!path_node_settings.native_rendering)
{
return;
}
for (unsigned int i = 0; i < game::pathData->nodeCount; i++)
{
float origin[3] = {};
const auto node = &game::pathData->nodes[i];
get_pathnode_origin(node, origin);
if (distance_2d(path_node_settings.camera, origin) < path_node_settings.range)
{
draw_square_native(origin, path_node_settings.size, path_node_settings.color);
}
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
gui::on_frame(draw_window);
gui::on_frame(draw_nodes, true);
scheduler::on_game_initialized([]()
{
cl_paused = game::Dvar_FindVar("cl_paused");
});
scheduler::loop(draw_nodes_native, scheduler::pipeline::renderer);
}
};
}
REGISTER_COMPONENT(gui_debug::component)

View File

@ -68,6 +68,43 @@ namespace game
char __pad3[392];
}; // size = 760
struct pathlink_s
{
char __pad0[4];
unsigned __int16 nodeNum;
char __pad[6];
};
static_assert(sizeof(pathlink_s) == 12);
struct pathnode_t
{
unsigned __int16 type;
unsigned int spawnflags;
unsigned int targetname;
unsigned int script_linkName;
unsigned int script_noteworthy;
unsigned int target;
unsigned int animscript;
int animscriptfunc;
float vLocalOrigin[3];
char __pad0[28];
unsigned __int16 totalLinkCount;
char __pad1[2];
pathlink_s* Links;
char __pad2[104];
}; // size = 192
static_assert(sizeof(pathnode_t) == 192);
struct PathData
{
const char* name;
unsigned int nodeCount;
pathnode_t* nodes;
// ...
};
struct Material
{
const char* name;
@ -416,6 +453,16 @@ namespace game
vec2_t subScreenLeft;
};
struct refdef_t
{
char __pad0[0x10];
float fovX;
float fovY;
char __pad1[0x8];
float org[3];
float axis[3][3];
};
struct CmdArgs
{
int nesting;

View File

@ -40,9 +40,9 @@ namespace game
WEAK symbol<void(char* buffer, int index)> Dvar_GetCombinedString{0x5A75D0};
WEAK symbol<dvar_t*(int hash, const char* name, bool value, unsigned int flags)> Dvar_RegisterBool{0x617BB0};
WEAK symbol<dvar_t*(int hash, const char* name, int value, int min, int max, unsigned int flags)> Dvar_RegisterInt{0x618090};
WEAK symbol<dvar_t* (int hash, const char* dvarName, float value, float min, float max, unsigned int flags)>
WEAK symbol<dvar_t*(int hash, const char* dvarName, float value, float min, float max, unsigned int flags)>
Dvar_RegisterFloat{0x617F80};
WEAK symbol<dvar_t* (int hash, const char* dvarName, const char* value, unsigned int flags)>
WEAK symbol<dvar_t*(int hash, const char* dvarName, const char* value, unsigned int flags)>
Dvar_RegisterString{0x618170};
WEAK symbol<dvar_t*(int dvarName, const char* a2, float x, float y, float z, float w, float min, float max,
unsigned int flags)> Dvar_RegisterVec4{0x6185F0};
@ -58,7 +58,7 @@ namespace game
const float* color, int style, const float* glowColor, Material* fxMaterial, Material* fxMaterialGlow,
int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, int a17)> CL_DrawTextPhysicalWithEffects{0x3D4990};
WEAK symbol<unsigned int (unsigned int parentId, unsigned int name)> FindVariable{0x5C1D50};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int name)> FindVariable{0x5C1D50};
WEAK symbol<unsigned int(int entnum, unsigned int classnum)> FindEntityId{0x5C1C50};
WEAK symbol<void(VariableValue* result, unsigned int classnum, int entnum, int offset)> GetEntityFieldValue{0x5C6100};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)> GetVariable{0x5C2690};
@ -72,26 +72,30 @@ namespace game
G_GivePlayerWeapon{0x51B660};
WEAK symbol<void(void* ps, const unsigned int weapon, int hadWeapon)> G_InitializeAmmo{0x4C4110};
WEAK symbol<void(int clientNum, const unsigned int weapon)> G_SelectWeapon{0x51C0D0};
WEAK symbol<bool(int localClientNum, ScreenPlacement* screenPlacement, const float* worldDir, float* outScreenPos)> WorldPosToScreenPos{0x36F310};
WEAK symbol<char*(char* string)> I_CleanStr{0x620660};
WEAK symbol<char* (GfxImage* image, uint32_t width, uint32_t height, uint32_t depth, uint32_t mipCount,
WEAK symbol<char*(GfxImage* image, uint32_t width, uint32_t height, uint32_t depth, uint32_t mipCount,
uint32_t imageFlags, DXGI_FORMAT imageFormat, int a8, const char* name, const void* initData)> Image_Setup{0x74B2A0};
WEAK symbol<const char* (int, int, int)> Key_KeynumToString{0x3D32D0};
WEAK symbol<const char*(int, int, int)> Key_KeynumToString{0x3D32D0};
WEAK symbol<void(int clientNum, const char* menu, int a3, int a4, unsigned int a5)> LUI_OpenMenu{0x5F0EE0};
WEAK symbol<bool(int clientNum, const char* menu)> Menu_IsMenuOpenAndVisible{0x5EE1A0};
WEAK symbol<Material*(const char* material)> Material_RegisterHandle{0x759BA0};
WEAK symbol<const float* (const float* v)> Scr_AllocVector{0x5C3220};
WEAK symbol<void(pathnode_t*, float* out)> PathNode_WorldifyPosFromParent{0x525830};
WEAK symbol<const float*(const float* v)> Scr_AllocVector{0x5C3220};
WEAK symbol<void()> Scr_ClearOutParams{0x5C6E50};
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{0x5C56C0};
WEAK symbol<unsigned int(int entnum, unsigned int classnum)> Scr_GetEntityId{0x5C5610};
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x512190};
WEAK symbol<void(unsigned int id, scr_string_t stringValue, unsigned int paramcount)> Scr_NotifyId{0x5C8240};
WEAK symbol<unsigned int(unsigned int threadId)> Scr_GetSelf{0x5C57C0};
WEAK symbol<void()> Scr_ErrorInternal{0x5C6EC0};
WEAK symbol<unsigned int(unsigned int localId, const char* pos, unsigned int paramcount)> VM_Execute{0x5C8DB0};
@ -115,6 +119,7 @@ namespace game
float materialTime, __int64 a7, __int64 a8)> R_AddDObjToScene{0x775C40};
WEAK symbol<ScreenPlacement*()> ScrPlace_GetViewPlacement{0x3E16A0};
WEAK symbol<ScreenPlacement*()> ScrPlace_GetView{0x3E1660};
WEAK symbol<const char*(scr_string_t stringValue)> SL_ConvertToString{0x5BFBB0};
WEAK symbol<scr_string_t(const char* str, unsigned int user)> SL_GetString{0x5C0170};
@ -148,10 +153,12 @@ namespace game
WEAK symbol<int> g_poolSize{0xBF2E40};
WEAK symbol<gentity_s> g_entities{0x52DDDA0};
WEAK symbol<PathData> pathData{0x52CCDA0};
WEAK symbol<DWORD> threadIds{0xB11DC80};
WEAK symbol<GfxDrawMethod_s> gfxDrawMethod{0xEDF9E00};
WEAK symbol<refdef_t> refdef{0x1BC2500};
WEAK symbol<int> keyCatchers{0x203F3C0};

View File

@ -89,6 +89,7 @@
#include <d3d11.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <backends/imgui_impl_dx11.h>
#include <backends/imgui_impl_win32.h>
#include <misc/cpp/imgui_stdlib.h>