#include #include "loader/component_loader.hpp" #include "scheduler.hpp" #include "game/game.hpp" #include "game/dvars.hpp" #include #include namespace fps { namespace { const game::dvar_t* cg_drawFPS; const game::dvar_t* cg_drawPing; float fps_color[4] = {0.6f, 1.0f, 0.0f, 1.0f}; float origin_color[4] = {1.0f, 0.67f, 0.13f, 1.0f}; float ping_color[4] = {1.0f, 1.0f, 1.0f, 0.65f}; struct cg_perf_data { std::chrono::time_point perf_start; std::int32_t current_ms{}; std::int32_t previous_ms{}; std::int32_t frame_ms{}; std::int32_t history[32]{}; std::int32_t count{}; std::int32_t index{}; std::int32_t instant{}; std::int32_t total{}; float average{}; float variance{}; std::int32_t min{}; std::int32_t max{}; }; cg_perf_data cg_perf = cg_perf_data(); void perf_calc_fps(cg_perf_data* data, const std::int32_t value) { data->history[data->index % 32] = value; data->instant = value; data->min = std::numeric_limits::max(); data->max = 0; data->average = 0.0f; data->variance = 0.0f; data->total = 0; for (auto i = 0; i < data->count; ++i) { const std::int32_t idx = (data->index - i) % 32; if (idx < 0) { break; } data->total += data->history[idx]; if (data->min > data->history[idx]) { data->min = data->history[idx]; } if (data->max < data->history[idx]) { data->max = data->history[idx]; } } data->average = static_cast(data->total) / static_cast(data->count); ++data->index; } void perf_update() { cg_perf.count = 32; cg_perf.current_ms = static_cast(std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - cg_perf.perf_start).count()); cg_perf.frame_ms = cg_perf.current_ms - cg_perf.previous_ms; cg_perf.previous_ms = cg_perf.current_ms; perf_calc_fps(&cg_perf, cg_perf.frame_ms); utils::hook::invoke(SELECT_VALUE(0x1405806E0, 0x140658E30)); } void cg_draw_fps() { if (cg_drawFPS && cg_drawFPS->current.integer != 0) { const auto fps = static_cast(static_cast(1000.0f / static_cast(cg_perf.average)) + 9.313225746154785e-10); auto* font = game::R_RegisterFont("fonts/normalfont"); if (!font) return; const auto* const fps_string = utils::string::va("%i", fps); const auto scale = 1.0f; const auto x = (game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 10.0f) - game::R_TextWidth( fps_string, std::numeric_limits::max(), font) * scale; const auto y = font->pixelHeight * 1.2f; game::R_AddCmdDrawText(fps_string, std::numeric_limits::max(), font, x, y, scale, scale, 0.0f, fps_color, 6); if (game::mp::g_entities && cg_drawFPS->current.integer > 1 && game::SV_Loaded()) { const auto* const origin_string = utils::string::va("%f, %f, %f", game::mp::g_entities[0].client->ps.origin[0] * 1.0, game::mp::g_entities[0].client->ps.origin[1] * 1.0, game::mp::g_entities[0].client->ps.origin[2] * 1.0); const auto origin_x = (game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 10.0f) - game::R_TextWidth(origin_string, std::numeric_limits::max(), font) * scale; game::R_AddCmdDrawText(origin_string, std::numeric_limits::max(), font, origin_x, y + 50, scale, scale, 0.0f, origin_color, 6); } } } void cg_draw_ping() { if (cg_drawPing->current.integer != 0 && game::CL_IsCgameInitialized()) { const auto ping = *reinterpret_cast(0x1419E5100); auto* font = game::R_RegisterFont("fonts/normalfont"); if (!font) return; auto* const ping_string = utils::string::va("Ping: %i", ping); const auto scale = 1.0f; const auto x = (game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 375.0f) - game::R_TextWidth( ping_string, 0x7FFFFFFF, font) * scale; const auto y = font->pixelHeight * 1.2f; game::R_AddCmdDrawText(ping_string, std::numeric_limits::max(), font, x, y, scale, scale, 0.0f, ping_color, 6); } } const game::dvar_t* cg_draw_fps_register_stub(const char* dvar_name, const char** value_list, const int default_index, unsigned int /*flags*/, const char* description) { cg_drawFPS = game::Dvar_RegisterEnum(dvar_name, value_list, default_index, game::DVAR_FLAG_SAVED, description); return cg_drawFPS; } } class component final : public component_interface { public: void post_unpack() override { if (game::environment::is_dedi()) { return; } // fps setup cg_perf.perf_start = std::chrono::high_resolution_clock::now(); utils::hook::call(SELECT_VALUE(0x140242C11, 0x1402CF457), &perf_update); // change cg_drawfps flags to saved utils::hook::call(SELECT_VALUE(0x1401F400A, 0x140272B98), &cg_draw_fps_register_stub); cg_drawPing = game::Dvar_RegisterInt("cg_drawPing", 0, 0, 1, game::DVAR_FLAG_SAVED, "Draw ping"); scheduler::loop(cg_draw_fps, scheduler::pipeline::renderer); scheduler::loop(cg_draw_ping, scheduler::pipeline::renderer); } }; } REGISTER_COMPONENT(fps::component)