/ src / render / renderer.cpp
renderer.cpp
  1  // 作者: feng_xingzhe_cn_.86
  2  
  3  #include <onyx/render/renderer.hpp>
  4  #include <imgui.h>
  5  #include <imgui_impl_opengl3.h>
  6  #include <ctime>
  7  #include <GLES3/gl3.h>
  8  #include <algorithm>
  9  #include <sstream>
 10  #include <iomanip>
 11  #include "../../Deps/Roboto-Regular.h"
 12  
 13  namespace onyx::render {
 14  
 15      void Renderer::render_frame(const core::EspManager& esp, sdk::game::CameraSystem& camera, uintptr_t base_address) noexcept {
 16  
 17          ImGuiIO& io = ImGui::GetIO();
 18  
 19          struct timespec ts{};
 20          clock_gettime(CLOCK_MONOTONIC, &ts);
 21          double now = static_cast<double>(ts.tv_sec) + ts.tv_nsec / 1e9;
 22  
 23          io.DeltaTime = m_last_time > 0.0 ? static_cast<float>(now - m_last_time) : 1.0f / 60.0f;
 24          m_last_time = now;
 25  
 26          draw_esp(esp, camera);
 27          draw_offscreen_indicator(esp, camera);
 28          draw_launcher_bar();
 29          draw_menu(esp, camera);
 30  
 31      }
 32  
 33  void Renderer::initialize() noexcept {
 34  
 35      if (m_is_initialized) return;
 36  
 37      ImGuiIO& io = ImGui::GetIO();
 38      
 39      m_font_roboto = io.Fonts->AddFontFromMemoryTTF(Roboto_Regular, sizeof(Roboto_Regular), 20.0f);
 40      m_font_roboto_small = io.Fonts->AddFontFromMemoryTTF(Roboto_Regular, sizeof(Roboto_Regular), 17.0f);
 41      
 42      apply_styling();
 43  
 44      m_is_initialized = true;
 45  
 46  }
 47  
 48  void Renderer::draw_esp(const core::EspManager& esp, const sdk::game::CameraSystem& camera) noexcept {
 49  
 50      auto& settings = core::Settings::get();
 51  
 52      if (!settings.esp_enabled) return;
 53  
 54      ImGuiIO& io = ImGui::GetIO();
 55  
 56      auto players = esp.players();
 57  
 58      for (const auto& player : players) {
 59  
 60          if (!player.is_visible) continue;
 61  
 62          ImVec2 tl, br;
 63  
 64          bool has_2d = false;
 65  
 66          if (settings.esp_box_type > 0) {
 67  
 68              has_2d = get_2d_bounds(player, camera, tl, br);
 69  
 70          }
 71  
 72          if (settings.esp_box_type == 1 && has_2d) {
 73  
 74              draw_2d_box(player, camera);
 75  
 76          } else if (settings.esp_box_type == 2) {
 77  
 78              draw_3d_box(player, camera);
 79  
 80          }
 81  
 82          if (settings.esp_health_enabled && player.health > 0) {
 83  
 84              if (settings.esp_box_type == 1 && has_2d) {
 85  
 86                  draw_health_bar_2d(tl, br, player.health);
 87  
 88              } else {
 89  
 90                  ImDrawList* dl = ImGui::GetBackgroundDrawList();
 91  
 92                  std::string hp_text = "HP: " + std::to_string(player.health);
 93                  
 94                  if (m_font_roboto) ImGui::PushFont(m_font_roboto);
 95  
 96                  ImVec2 text_size = ImGui::CalcTextSize(hp_text.c_str());
 97                  ImVec2 text_pos = { player.screen_pos.x - text_size.x * 0.5f, player.screen_pos.y + 10.0f };
 98  
 99                  dl->AddRectFilled({text_pos.x - 4.0f, text_pos.y}, {text_pos.x + text_size.x + 4.0f, text_pos.y + text_size.y},IM_COL32(0, 0, 0, 160), 4.0f);
100                  dl->AddText(text_pos, IM_COL32(0, 255, 100, 255), hp_text.c_str());
101  
102                  if (m_font_roboto) ImGui::PopFont();
103  
104              }
105  
106          }
107  
108          if (settings.esp_distance_enabled) {
109  
110              ImDrawList* dl = ImGui::GetBackgroundDrawList();
111  
112              std::string dist_text = std::to_string(static_cast<int>(player.distance)) + "m";
113              
114              if (m_font_roboto) ImGui::PushFont(m_font_roboto);
115  
116              ImVec2 text_size = ImGui::CalcTextSize(dist_text.c_str());
117              
118              if (settings.esp_box_type == 2) {
119  
120                  ImVec2 text_pos = { player.screen_pos.x - text_size.x * 0.5f, player.screen_pos.y + 30.0f };
121  
122                  dl->AddRectFilled({text_pos.x - 4.0f, text_pos.y}, {text_pos.x + text_size.x + 4.0f, text_pos.y + text_size.y},IM_COL32(0, 0, 0, 160), 4.0f);
123                  dl->AddText(text_pos, IM_COL32(0, 255, 100, 255), dist_text.c_str());
124  
125              } else if (settings.esp_box_type == 1 && has_2d) {
126  
127                  ImVec2 text_pos = { (tl.x + br.x) * 0.5f - text_size.x * 0.5f, br.y + 5.0f };
128  
129                  dl->AddText(text_pos, IM_COL32(255, 255, 255, 255), dist_text.c_str());
130  
131              }
132  
133              if (m_font_roboto) ImGui::PopFont();
134  
135          }
136  
137          if (settings.esp_lines_enabled) {
138  
139              ImDrawList* dl = ImGui::GetBackgroundDrawList();
140  
141              ImVec2 screen_top = { io.DisplaySize.x * 0.5f, 0.0f };
142              ImVec2 target_top;
143  
144              if (settings.esp_box_type == 1 && has_2d) {
145  
146                  target_top = { (tl.x + br.x) * 0.5f, tl.y };
147  
148              } else {
149  
150                  target_top = player.screen_pos; // Default to feet if no box
151  
152              }
153              
154              dl->AddLine(screen_top, target_top, IM_COL32(200, 200, 200, 120), 1.0f);
155  
156          }
157  
158          if (settings.esp_skeleton && player.bone_count > 0) {
159  
160              draw_skeleton(player);
161  
162          }
163  
164      }
165  
166  }
167  
168  void Renderer::draw_2d_box(const core::PlayerData& player, const sdk::game::CameraSystem& camera) noexcept {
169  
170      ImVec2 tl, br;
171  
172      if (!get_2d_bounds(player, camera, tl, br)) return;
173  
174      auto& settings = core::Settings::get();
175  
176      ImDrawList* dl = ImGui::GetBackgroundDrawList();
177  
178      const ImU32 color = ImGui::ColorConvertFloat4ToU32(settings.esp_box_color);
179      
180      if (settings.esp_box_2d_style == 0) {
181  
182          dl->AddRect(tl, br, IM_COL32(0, 0, 0, 255), 0.0f, ImDrawFlags_None, 3.5f);
183          dl->AddRect(tl, br, color, 0.0f, ImDrawFlags_None, 1.5f);
184  
185      } else {
186  
187          float w = br.x - tl.x;
188          float h = br.y - tl.y;
189          float line_w = w * 0.25f;
190          float line_h = h * 0.25f;
191          
192          auto draw_corner = [&](ImVec2 p, float dx, float dy) {
193  
194              dl->AddLine({p.x, p.y}, {p.x + dx, p.y}, IM_COL32(0, 0, 0, 255), 3.5f);
195              dl->AddLine({p.x, p.y}, {p.x, p.y + dy}, IM_COL32(0, 0, 0, 255), 3.5f);
196              dl->AddLine({p.x, p.y}, {p.x + dx, p.y}, color, 1.5f);
197              dl->AddLine({p.x, p.y}, {p.x, p.y + dy}, color, 1.5f);
198  
199          };
200  
201          draw_corner(tl, line_w, line_h);
202          draw_corner({br.x, tl.y}, -line_w, line_h);
203          draw_corner({tl.x, br.y}, line_w, -line_h);
204          draw_corner(br, -line_w, -line_h);
205  
206      }
207  
208  }
209  
210  void Renderer::draw_3d_box(const core::PlayerData& player, const sdk::game::CameraSystem& camera) noexcept {
211  
212      auto& settings = core::Settings::get();
213  
214      sdk::Vector3 bmin, bmax;
215  
216      if (player.has_bounds) {
217          bmin = player.aabb_min;
218          bmax = player.aabb_max;
219      }
220  
221      sdk::Vector3 corners[8] = {
222  
223          {bmin.x, bmin.y, bmin.z},  // 0: 下-左-后
224          {bmax.x, bmin.y, bmin.z},  // 1: 下-右-后
225          {bmax.x, bmin.y, bmax.z},  // 2: 下-右-前
226          {bmin.x, bmin.y, bmax.z},  // 3: 下-左-前
227          {bmin.x, bmax.y, bmin.z},  // 4: 上-左-后
228          {bmax.x, bmax.y, bmin.z},  // 5: 上-右-后
229          {bmax.x, bmax.y, bmax.z},  // 6: 上-右-前
230          {bmin.x, bmax.y, bmax.z}   // 7: 上-左-前
231  
232      };
233  
234      ImVec2 screen_points[8];
235  
236      const float screen_w = ImGui::GetIO().DisplaySize.x;
237      const float screen_h = ImGui::GetIO().DisplaySize.y;
238  
239      for (int i = 0; i < 8; ++i) {
240  
241          if (!camera.world_to_screen(corners[i], screen_points[i], screen_w, screen_h)) {
242  
243              return;
244  
245          }
246  
247      }
248  
249      ImDrawList* draw_list = ImGui::GetBackgroundDrawList();
250  
251      const ImU32 color = ImGui::ColorConvertFloat4ToU32(settings.esp_box_color);
252  
253      draw_list->AddLine(screen_points[0], screen_points[1], color);
254      draw_list->AddLine(screen_points[1], screen_points[2], color);
255      draw_list->AddLine(screen_points[2], screen_points[3], color);
256      draw_list->AddLine(screen_points[3], screen_points[0], color);
257  
258      draw_list->AddLine(screen_points[4], screen_points[5], color);
259      draw_list->AddLine(screen_points[5], screen_points[6], color);
260      draw_list->AddLine(screen_points[6], screen_points[7], color);
261      draw_list->AddLine(screen_points[7], screen_points[4], color);
262  
263      draw_list->AddLine(screen_points[0], screen_points[4], color);
264      draw_list->AddLine(screen_points[1], screen_points[5], color);
265      draw_list->AddLine(screen_points[2], screen_points[6], color);
266      draw_list->AddLine(screen_points[3], screen_points[7], color);
267  
268  }
269  
270  void Renderer::draw_health_bar_2d(ImVec2 tl, ImVec2 br, int health) noexcept {
271  
272      ImDrawList* dl = ImGui::GetBackgroundDrawList();
273      
274      float h = br.y - tl.y;
275      float w = 4.0f;
276      float x = tl.x - 6.0f;
277      
278      dl->AddRectFilled({x - 1.0f, tl.y - 1.0f}, {x + w + 1.0f, br.y + 1.0f}, IM_COL32(0, 0, 0, 180));
279      
280      float health_height = (h * (health / 100.0f));
281      ImU32 health_color = IM_COL32(0, 255, 100, 255);
282      if (health < 30) health_color = IM_COL32(255, 0, 0, 255);
283      else if (health < 60) health_color = IM_COL32(255, 255, 0, 255);
284  
285      dl->AddRectFilled({x, br.y - health_height}, {x + w, br.y}, health_color);
286  
287  }
288  
289  bool Renderer::get_2d_bounds(const core::PlayerData& player, const sdk::game::CameraSystem& camera, ImVec2& tl, ImVec2& br) noexcept {
290  
291      sdk::Vector3 bmin, bmax;
292  
293      if (player.has_bounds) {
294  
295          bmin = player.aabb_min;
296          bmax = player.aabb_max;
297  
298      }
299  
300      sdk::Vector3 corners[8] = {
301  
302          {bmin.x, bmin.y, bmin.z}, {bmax.x, bmin.y, bmin.z},
303          {bmax.x, bmin.y, bmax.z}, {bmin.x, bmin.y, bmax.z},
304          {bmin.x, bmax.y, bmin.z}, {bmax.x, bmax.y, bmin.z},
305          {bmax.x, bmax.y, bmax.z}, {bmin.x, bmax.y, bmax.z}
306  
307      };
308  
309      float min_x = 10000.0f, min_y = 10000.0f, max_x = -10000.0f, max_y = -10000.0f;
310  
311      const float sw = ImGui::GetIO().DisplaySize.x;
312      const float sh = ImGui::GetIO().DisplaySize.y;
313  
314      bool any_visible = false;
315  
316      for (int i = 0; i < 8; ++i) {
317  
318          ImVec2 sp;
319  
320          if (camera.world_to_screen(corners[i], sp, sw, sh)) {
321  
322              min_x = std::min(min_x, sp.x);
323              min_y = std::min(min_y, sp.y);
324              max_x = std::max(max_x, sp.x);
325              max_y = std::max(max_y, sp.y);
326  
327              any_visible = true;
328  
329          }
330  
331      }
332  
333      if (!any_visible) return false;
334  
335      tl = {min_x, min_y};
336      br = {max_x, max_y};
337  
338      return true;
339  }
340  
341  void Renderer::draw_skeleton(const core::PlayerData& player) noexcept {
342  
343      auto& settings = core::Settings::get();
344  
345      ImDrawList* draw_list = ImGui::GetBackgroundDrawList();
346  
347      const ImU32 color = ImGui::ColorConvertFloat4ToU32(settings.esp_skeleton_color);
348      const float thickness = settings.esp_skeleton_thickness;
349  
350      for (int32_t i = 0; i < player.bone_count; ++i) {
351  
352          const auto& bone = player.bones[i];
353          if (!bone.is_valid) continue;
354  
355          int32_t pi = bone.parent_index;
356  
357          if (pi < 0 || pi >= player.bone_count) continue;
358          if (!player.bones[pi].is_valid) continue;
359  
360          draw_list->AddLine(player.bones[pi].screen_pos, bone.screen_pos, color, thickness);
361  
362      }
363  }
364  
365  void Renderer::draw_launcher_bar() noexcept {
366  
367      ImGuiIO& io = ImGui::GetIO();
368  
369      float screen_w = io.DisplaySize.x;
370  
371      std::time_t t = std::time(nullptr);
372      std::tm tm = *std::gmtime(&t);
373  
374      std::stringstream time_ss;
375  
376      time_ss << std::put_time(&tm, "%H:%M:%S");
377  
378      std::string text = "Onyx 1.60.0.f3153 | " + time_ss.str() + " | " + std::to_string(static_cast<int>(io.Framerate)) + " FPS";
379  
380      if (m_font_roboto) ImGui::PushFont(m_font_roboto);
381      
382      ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
383  
384      float padding = 8.0f;
385      float button_w = 90.0f;
386      float button_h = 30.0f;
387      float window_h = 46.0f;
388      float window_w = text_size.x + button_w + (padding * 4.0f);
389      
390      ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
391      ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f);
392      ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
393      ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.5f, 0.515f));
394      ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
395  
396      ImVec2 pos = { (screen_w - window_w) * 0.5f, io.DisplaySize.y - window_h - 20.0f };
397  
398      ImGui::SetNextWindowPos(pos);
399      ImGui::SetNextWindowSize({ window_w, window_h });
400      
401      ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoBackground;
402  
403      if (ImGui::Begin("##LauncherBar", nullptr, flags)) {
404  
405          ImDrawList* dl = ImGui::GetWindowDrawList();
406          
407          dl->AddRectFilled(pos, { pos.x + window_w, pos.y + window_h }, IM_COL32(15, 15, 15, 245));
408          
409          dl->AddRectFilled(pos, { pos.x + window_w, pos.y + 2.0f }, IM_COL32(0, 255, 230, 255));
410  
411          float available_h = window_h - 2.0f;
412          float content_start_y = pos.y + 2.0f;
413          
414          float text_y = content_start_y + (available_h - text_size.y) * 0.5f;
415          float button_y = content_start_y + (available_h - button_h) * 0.5f;
416  
417          ImGui::SetCursorScreenPos({ pos.x + padding * 1.5f, text_y });
418          ImGui::TextColored(ImVec4(0.9f, 0.9f, 0.9f, 1.0f), "%s", text.c_str());
419          
420          ImGui::SetCursorScreenPos({ pos.x + window_w - button_w - padding, button_y });
421          
422          if (m_font_roboto_small) {
423  
424              ImGui::PopFont();
425              ImGui::PushFont(m_font_roboto_small);
426  
427          }
428  
429          const char* btn_label = m_is_menu_open ? "Close" : "Open";
430  
431          if (ImGui::Button(btn_label, { button_w, button_h })) {
432  
433              m_is_menu_open = !m_is_menu_open;
434          }
435  
436  
437          if (m_font_roboto_small) {
438  
439              ImGui::PopFont();
440  
441              if (m_font_roboto) ImGui::PushFont(m_font_roboto);
442  
443          }
444  
445      }
446  
447      ImGui::End();
448      ImGui::PopStyleVar(5);
449  
450      if (m_font_roboto) ImGui::PopFont();
451  
452  }
453  
454  void Renderer::draw_menu(const core::EspManager& esp, const sdk::game::CameraSystem& camera) noexcept {
455  
456      if (!m_is_menu_open) return;
457  
458      auto& settings = core::Settings::get();
459  
460      ImGuiIO& io = ImGui::GetIO();
461  
462      if (m_font_roboto) ImGui::PushFont(m_font_roboto);
463  
464      ImGui::SetNextWindowPos({io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f}, ImGuiCond_FirstUseEver, {0.5f, 0.5f});
465      ImGui::SetNextWindowSize({io.DisplaySize.x * 0.4f, io.DisplaySize.y * 0.6f}, ImGuiCond_FirstUseEver);
466  
467      if (ImGui::Begin("Onyx - External ESP Framework", nullptr, ImGuiWindowFlags_NoCollapse)) {
468  
469          if (ImGui::BeginTabBar("MenuTabs")) {
470              
471              if (ImGui::BeginTabItem("Visuals")) {
472  
473                  ImGui::Spacing();
474                  
475                  ImGui::Text("Box Style:");
476                  ImGui::RadioButton("None", &settings.esp_box_type, 0); ImGui::SameLine();
477                  ImGui::RadioButton("2D", &settings.esp_box_type, 1); ImGui::SameLine();
478                  ImGui::RadioButton("3D", &settings.esp_box_type, 2);
479  
480                  if (settings.esp_box_type == 1) { // 2D specific options
481  
482                      ImGui::Text("2D Style:");
483                      ImGui::RadioButton("Full##2D", &settings.esp_box_2d_style, 0); ImGui::SameLine();
484                      ImGui::RadioButton("Corners##2D", &settings.esp_box_2d_style, 1);
485  
486                  }
487  
488                  if (settings.esp_box_type > 0) {
489  
490                      ImGui::ColorEdit4("Box Color", (float*)&settings.esp_box_color, ImGuiColorEditFlags_NoInputs);
491  
492                  }
493  
494                  ImGui::Spacing();
495                  ImGui::Checkbox("Skeleton ESP", &settings.esp_skeleton);
496  
497                  if (settings.esp_skeleton) {
498  
499                      ImGui::ColorEdit4("Skeleton Color", (float*)&settings.esp_skeleton_color, ImGuiColorEditFlags_NoInputs);
500                      ImGui::SliderFloat("Thickness", &settings.esp_skeleton_thickness, 0.5f, 5.0f);
501                      ImGui::SliderFloat("Y Offset", &settings.esp_skeleton_y_offset, -0.5f, 0.5f);
502  
503                  }
504  
505                  ImGui::Separator();
506  
507                  ImGui::Checkbox("Snap-Lines", &settings.esp_lines_enabled);
508                  ImGui::Checkbox("Display Health", &settings.esp_health_enabled);
509                  ImGui::Checkbox("Display Distance", &settings.esp_distance_enabled);
510                  ImGui::Checkbox("Offscreen Indicator", &settings.esp_offscreen_indicator);
511                  
512                  ImGui::EndTabItem();
513  
514              }
515  
516              if (ImGui::BeginTabItem("Misc")) {
517  
518                  ImGui::Spacing();
519                  
520                  sdk::Vector3 cam_pos = camera.main_camera.position;
521  
522                  ImGui::Text("Camera World Position:");
523                  ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.9f, 1.0f), "X: %.2f  Y: %.2f  Z: %.2f", cam_pos.x, cam_pos.y, cam_pos.z);
524                  
525                  ImGui::Spacing();
526                  
527                  const auto& lp = esp.local_player();
528  
529                  if (lp.is_valid()) {
530  
531                      ImGui::Text("Local Player Found:");
532                      ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.5f, 1.0f), "Address: 0x%lX", (unsigned long)lp.address);
533                      sdk::Vector3 lp_pos = lp.transform.position();
534                      ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.5f, 1.0f), "Pos: X: %.2f  Y: %.2f  Z: %.2f", lp_pos.x, lp_pos.y, lp_pos.z);
535                      ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.5f, 1.0f), "Health: %d", lp.health);
536  
537                  } else {
538  
539                      ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Local Player: Not Identified");
540  
541                  }
542                  
543                  ImGui::Separator();
544                  ImGui::TextDisabled("Build: " __DATE__ " " __TIME__);
545                  ImGui::TextDisabled("Platform: Android x86_64");
546                  ImGui::Separator();
547                  ImGui::TextWrapped("Onyx External ESP for Critical Ops. Use responsibly.");
548                  ImGui::EndTabItem();
549  
550              }
551  
552              ImGui::EndTabBar();
553  
554          }
555  
556      }
557  
558      ImGui::End();
559      
560      if (m_font_roboto) ImGui::PopFont();
561  
562  }
563  
564  void Renderer::apply_styling() noexcept {
565  
566      auto& style = ImGui::GetStyle();
567      
568      style.WindowRounding    = 12.0f;
569      style.ChildRounding     = 8.0f;
570      style.FrameRounding     = 8.0f;
571      style.PopupRounding     = 8.0f;
572      style.ScrollbarRounding = 12.0f;
573      style.GrabRounding      = 8.0f;
574      style.TabRounding       = 8.0f;
575      style.WindowBorderSize  = 1.0f;
576      style.FrameBorderSize   = 0.0f;
577  
578      style.AntiAliasedLines = true;
579      style.AntiAliasedFill  = true;
580      style.WindowPadding    = { 12, 12 };
581      style.ItemSpacing      = { 10, 8 };
582  
583      ImVec4* colors = style.Colors;
584      colors[ImGuiCol_Text]                   = ImVec4(0.95f, 0.96f, 0.98f, 1.00f);
585      colors[ImGuiCol_TextDisabled]           = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
586      colors[ImGuiCol_WindowBg]               = ImVec4(0.08f, 0.08f, 0.09f, 1.00f);
587      colors[ImGuiCol_ChildBg]                = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
588      colors[ImGuiCol_PopupBg]                = ImVec4(0.10f, 0.10f, 0.11f, 1.00f);
589      colors[ImGuiCol_Border]                 = ImVec4(0.20f, 0.20f, 0.22f, 1.00f);
590      colors[ImGuiCol_BorderShadow]           = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
591      colors[ImGuiCol_FrameBg]                = ImVec4(0.14f, 0.14f, 0.16f, 1.00f);
592      colors[ImGuiCol_FrameBgHovered]         = ImVec4(0.18f, 0.18f, 0.20f, 1.00f);
593      colors[ImGuiCol_FrameBgActive]          = ImVec4(0.22f, 0.22f, 0.24f, 1.00f);
594      colors[ImGuiCol_TitleBg]                = ImVec4(0.05f, 0.05f, 0.06f, 1.00f);
595      colors[ImGuiCol_TitleBgActive]          = ImVec4(0.10f, 0.10f, 0.11f, 1.00f);
596      colors[ImGuiCol_TitleBgCollapsed]       = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
597      colors[ImGuiCol_MenuBarBg]              = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
598      colors[ImGuiCol_ScrollbarBg]            = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
599      colors[ImGuiCol_ScrollbarGrab]          = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
600      colors[ImGuiCol_ScrollbarGrabHovered]   = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
601      colors[ImGuiCol_ScrollbarGrabActive]    = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
602      colors[ImGuiCol_CheckMark]              = ImVec4(0.00f, 1.00f, 0.90f, 1.00f);
603      colors[ImGuiCol_SliderGrab]             = ImVec4(0.00f, 0.80f, 0.70f, 1.00f);
604      colors[ImGuiCol_SliderGrabActive]       = ImVec4(0.00f, 1.00f, 0.90f, 1.00f);
605      colors[ImGuiCol_Button]                 = ImVec4(0.14f, 0.14f, 0.16f, 1.00f);
606      colors[ImGuiCol_ButtonHovered]          = ImVec4(0.18f, 0.18f, 0.20f, 1.00f);
607      colors[ImGuiCol_ButtonActive]           = ImVec4(0.22f, 0.22f, 0.24f, 1.00f);
608      colors[ImGuiCol_Header]                 = ImVec4(0.12f, 0.11f, 0.14f, 1.00f);
609      colors[ImGuiCol_HeaderHovered]          = ImVec4(0.19f, 0.18f, 0.21f, 1.00f);
610      colors[ImGuiCol_HeaderActive]           = ImVec4(0.25f, 0.24f, 0.27f, 1.00f);
611      colors[ImGuiCol_Separator]              = ImVec4(0.20f, 0.20f, 0.22f, 1.00f);
612      colors[ImGuiCol_SeparatorHovered]       = ImVec4(0.30f, 0.30f, 0.32f, 1.00f);
613      colors[ImGuiCol_SeparatorActive]        = ImVec4(0.40f, 0.40f, 0.42f, 1.00f);
614      colors[ImGuiCol_ResizeGrip]             = ImVec4(0.00f, 1.00f, 0.90f, 0.20f);
615      colors[ImGuiCol_ResizeGripHovered]      = ImVec4(0.00f, 1.00f, 0.90f, 0.67f);
616      colors[ImGuiCol_ResizeGripActive]       = ImVec4(0.00f, 1.00f, 0.90f, 0.95f);
617      colors[ImGuiCol_Tab]                    = ImVec4(0.11f, 0.11f, 0.13f, 1.00f);
618      colors[ImGuiCol_TabHovered]             = ImVec4(0.00f, 1.00f, 0.90f, 0.80f);
619      colors[ImGuiCol_TabActive]              = ImVec4(0.00f, 0.80f, 0.70f, 1.00f);
620      colors[ImGuiCol_TabUnfocused]           = ImVec4(0.11f, 0.11f, 0.13f, 1.00f);
621      colors[ImGuiCol_TabUnfocusedActive]     = ImVec4(0.14f, 0.14f, 0.16f, 1.00f);
622  
623  }
624  
625  void Renderer::draw_offscreen_indicator(const core::EspManager& esp, const sdk::game::CameraSystem& camera) noexcept {
626  
627      auto& settings = core::Settings::get();
628  
629      if (!settings.esp_offscreen_indicator) return;
630  
631  
632      ImDrawList* dl = ImGui::GetBackgroundDrawList();
633  
634      const ImVec2 display_size = ImGui::GetIO().DisplaySize;
635      const ImVec2 screen_center = { display_size.x * 0.5f, display_size.y * 0.5f };
636  
637      constexpr float k_radius = 180.0f;
638      constexpr float k_arrow_size = 12.0f;
639      constexpr float k_half_width = 6.0f;
640  
641      const ImU32 color = IM_COL32(0, 255, 230, 255); // Onyx Cyan
642  
643      const sdk::Matrix4x4 view = camera.view_matrix();
644      const auto players = esp.players();
645  
646      for (const auto& player : players) {
647  
648          if (player.is_visible) continue;
649  
650          const sdk::Vector3 local = view.MultiplyPoint3x4(player.world_pos);
651  
652          const float angle = std::atan2(local.x, local.z);
653  
654          const float s = std::sin(angle);
655          const float c = std::cos(angle);
656  
657          const ImVec2 pos = {screen_center.x + s * k_radius,screen_center.y - c * k_radius };
658  
659          const ImVec2 p1 = pos;
660          const ImVec2 p2 = { pos.x - s * k_arrow_size - c * k_half_width, pos.y + c * k_arrow_size - s * k_half_width };
661          const ImVec2 p3 = { pos.x - s * k_arrow_size + c * k_half_width, pos.y + c * k_arrow_size + s * k_half_width };
662  
663          dl->AddTriangleFilled(p1, p2, p3, color);
664          dl->AddTriangle(p1, p2, p3, IM_COL32(0, 0, 0, 180), 1.0f);
665  
666      }
667  
668  }
669  
670  } // namespace onyx::render