RunicVTT Open Source Virtual Tabletop for TTRPG using P2P
Loading...
Searching...
No Matches
BoardManager.cpp
Go to the documentation of this file.
1#include <GL/glew.h>
2#include <GLFW/glfw3.h>
3#include "BoardManager.h"
4#include "UiTypingGuard.h"
5#include "Renderer.h"
6#include "Shader.h"
7#include "VertexBuffer.h"
8#include "IndexBuffer.h"
9#include "VertexArray.h"
10#include "VertexBufferLayout.h"
11#include "Texture.h"
12
13#include "glm/glm.hpp"
14#include "glm/gtc/matrix_transform.hpp"
15#include <glm/gtc/round.hpp>
16#include <glm/gtc/epsilon.hpp> // epsilonEqual, epsilonNotEqual
17#include <glm/common.hpp>
18
19#include "Components.h"
20#include <filesystem>
21#include <random> // For random number generation
22#include <atomic> // For atomic counter
23#include <cstdint> // For uint64_t and UINT64_MAX
24#include "Serializer.h"
25#include "NetworkManager.h"
26#include "Logger.h"
27
28#include <chrono>
29#include <cstdlib> // getenv
30#include <functional> // std::hash
31#include <string>
32#include <thread>
33#include <unordered_map>
34
35BoardManager::BoardManager(flecs::world ecs, std::weak_ptr<NetworkManager> network_manager, std::shared_ptr<IdentityManager> identity_manager, std::shared_ptr<DirectoryWindow> map_directory, std::shared_ptr<DirectoryWindow> marker_directory) :
36 ecs(ecs), camera(), identity_manager(identity_manager), currentTool(Tool::MOVE), mouse_start_screen_pos({0, 0}), mouse_start_world_pos({0, 0}), mouse_current_world_pos({0, 0}), marker_directory(marker_directory), map_directory(map_directory), network_manager(network_manager)
37{
38
39 std::filesystem::path map_path = std::filesystem::path(map_directory->directoryPath);
40 std::filesystem::path base_path = map_path.parent_path();
41 std::filesystem::path marker_directory_path = base_path / "Markers";
42
43 marker_directory->directoryName = "MarkerDiretory";
44 marker_directory->directoryPath = marker_directory_path.string();
45 marker_directory->startMonitoring();
46 marker_directory->generateTextureIDs();
47}
48
52
54{
55 return active_board.is_valid();
56}
57
59{
60 active_board = flecs::entity();
61}
62
63flecs::entity BoardManager::createBoard(std::string board_name, std::string map_image_path, GLuint texture_id, glm::vec2 size)
64{
65 auto board = ecs.entity()
67 .set(Board{board_name})
68 .set(Panning{false})
69 .set(Grid{{0, 0}, 50.0f, false, false, false, 0.5f})
70 .set(TextureComponent{texture_id, map_image_path, size})
71 .set(Size{size.x, size.y});
72 setActiveBoard(board);
73 return board;
74}
75
76void BoardManager::setActiveBoard(flecs::entity board_entity)
77{
78 active_board = board_entity;
79 auto nm = network_manager.lock();
80 if (!nm)
81 if (nm->getPeerRole() == Role::GAMEMASTER)
82 nm->broadcastBoard(active_board);
83}
84/*
85void BoardManager::renderToolbar(const ImVec2& window_position)
86{
87
88 ImGuiWindowFlags toolbar_child_flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize;
89
90 auto panel_color = ImVec4(0.18f, 0.22f, 0.27f, 1.00f); // toolbar panel
91 auto bg_color = ImVec4(0.12f, 0.14f, 0.17f, 1.00f);
92 auto old_bg_color = ImVec4(0.2f, 0.3f, 0.4f, 1.0f);
93
94 auto green_light_color = ImVec4(0.55f, 0.95f, 0.65f, 1.00f);
95 auto green_mid_color = ImVec4(0.20f, 0.75f, 0.35f, 1.00f);
96 auto green_dark_color = ImVec4(0.12f, 0.45f, 0.22f, 1.00f);
97 auto mint_color = ImVec4(0.50f, 0.95f, 0.85f, 1.00f);
98
99 auto blue_light_color = ImVec4(0.55f, 0.75f, 1.00f, 1.00f);
100 auto blue_mid_color = ImVec4(0.28f, 0.50f, 0.92f, 1.00f);
101 auto blue_dark_color = ImVec4(0.15f, 0.30f, 0.55f, 1.00f);
102
103 auto purple_color = ImVec4(0.70f, 0.45f, 0.95f, 1.00f);
104 auto violet_color = ImVec4(0.55f, 0.35f, 0.85f, 1.00f);
105 auto mid_purple_color = ImVec4(0.45f, 0.28f, 0.70f, 1.00f);
106
107 auto orange_color = ImVec4(0.98f, 0.60f, 0.20f, 1.00f);
108 auto amber_color = ImVec4(1.00f, 0.78f, 0.25f, 1.00f);
109 auto yellow_color = ImVec4(0.95f, 0.90f, 0.30f, 1.00f);
110
111 auto button_toggled_color = amber_color;
112 auto button_toggled_hover = orange_color;
113 auto button_toggled_active = yellow_color;
114
115 auto button_untoggled_color = blue_mid_color;
116 auto button_untoggled_hover = blue_light_color;
117 auto button_untoggled_active = yellow_color;
118
119 auto button_popup_color = green_dark_color;
120 auto button_popup_hover = green_mid_color;
121 auto button_popup_active = mint_color;
122
123 ImGui::SetCursorPos(window_position);
124
125 ImVec2 toolbar_size = ImVec2(0, 0); // Auto-size to content
126
127 ImGui::PushStyleColor(ImGuiCol_WindowBg, panel_color);
128 ImGui::BeginChild("ToolbarChild", toolbar_size, false, toolbar_child_flags);
129
130 // Tool: Move
131 ImGui::PushStyleColor(ImGuiCol_Button, currentTool == Tool::MOVE ? button_toggled_color : button_untoggled_color);
132 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, currentTool == Tool::MOVE ? button_toggled_hover : button_untoggled_hover);
133 ImGui::PushStyleColor(ImGuiCol_ButtonActive, currentTool == Tool::MOVE ? button_toggled_active : button_untoggled_active);
134 if (ImGui::Button("Move Tool", ImVec2(100, 40)))
135 {
136 currentTool = Tool::MOVE;
137 }
138 ImGui::PopStyleColor(3);
139 ImGui::SameLine(); // Ensure buttons are on the same row
140 auto nm = network_manager.lock();
141 if (!nm)
142 throw std::exception("[BoardManager] NetworkManager Expired");
143
144 if (nm->getPeerRole() == Role::GAMEMASTER)
145 {
146 // Tool: Fog
147 ImGui::PushStyleColor(ImGuiCol_Button, currentTool == Tool::FOG ? button_toggled_color : button_untoggled_color);
148 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, currentTool == Tool::FOG ? button_toggled_hover : button_untoggled_hover);
149 ImGui::PushStyleColor(ImGuiCol_ButtonActive, currentTool == Tool::FOG ? button_toggled_active : button_untoggled_active);
150 if (ImGui::Button("Fog Tool", ImVec2(100, 40)))
151 {
152 currentTool = Tool::FOG;
153 }
154 ImGui::PopStyleColor(3);
155 ImGui::SameLine(); // Ensure buttons are on the same row
156
157 // Tool: Select
158 ImGui::PushStyleColor(ImGuiCol_Button, currentTool == Tool::SELECT ? button_toggled_color : button_untoggled_color);
159 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, currentTool == Tool::SELECT ? button_toggled_hover : button_untoggled_hover);
160 ImGui::PushStyleColor(ImGuiCol_ButtonActive, currentTool == Tool::SELECT ? button_toggled_active : button_untoggled_active);
161 if (ImGui::Button("Select Tool", ImVec2(100, 40)))
162 {
163 currentTool = Tool::SELECT;
164 }
165 ImGui::PopStyleColor(3);
166 ImGui::SameLine(); // Ensure buttons are on the same row
167 }
168
169 ImGui::PushStyleColor(ImGuiCol_Button, button_popup_color);
170 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_popup_hover);
171 ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_popup_active);
172 if (ImGui::Button("Camera Settings", ImVec2(110, 40)))
173 {
174 showCameraSettings = !showCameraSettings;
175 }
176 ImGui::PopStyleColor(3);
177 if (nm->getPeerRole() == Role::GAMEMASTER)
178 {
179 ImGui::SameLine(); // Ensure buttons are on the same row
180 ImGui::PushStyleColor(ImGuiCol_Button, button_popup_color);
181 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_popup_hover);
182 ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_popup_active);
183 if (ImGui::Button("Grid Settings", ImVec2(110, 40)))
184 {
185 showGridSettings = !showGridSettings;
186 }
187 ImGui::PopStyleColor(3);
188 }
189
190 // Pop the toolbar's background color
191 ImGui::PopStyleColor();
192 // End the child window
193 ImGui::EndChild();
194
195 if (!showCameraSettings && !showGridSettings && !showEditWindow)
196 {
197 setIsNonMapWindowHovered(false);
198 }
199 renderGridWindow();
200 renderCameraWindow();
201}
202*/
203
204void BoardManager::renderToolbar(const ImVec2& window_position)
205{
206 ImGuiWindowFlags toolbar_child_flags =
207 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoMove |
208 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoTitleBar |
209 ImGuiWindowFlags_AlwaysAutoResize;
210
211 auto panel_color = ImVec4(0.18f, 0.22f, 0.27f, 1.00f); // toolbar panel
212 auto bg_color = ImVec4(0.12f, 0.14f, 0.17f, 1.00f);
213 auto old_bg_color = ImVec4(0.2f, 0.3f, 0.4f, 1.0f);
214
215 auto green_light_color = ImVec4(0.55f, 0.95f, 0.65f, 1.00f);
216 auto green_mid_color = ImVec4(0.20f, 0.75f, 0.35f, 1.00f);
217 auto green_dark_color = ImVec4(0.12f, 0.45f, 0.22f, 1.00f);
218 auto mint_color = ImVec4(0.50f, 0.95f, 0.85f, 1.00f);
219
220 auto blue_light_color = ImVec4(0.55f, 0.75f, 1.00f, 1.00f);
221 auto blue_mid_color = ImVec4(0.28f, 0.50f, 0.92f, 1.00f);
222 auto blue_dark_color = ImVec4(0.15f, 0.30f, 0.55f, 1.00f);
223
224 auto purple_color = ImVec4(0.70f, 0.45f, 0.95f, 1.00f);
225 auto violet_color = ImVec4(0.55f, 0.35f, 0.85f, 1.00f);
226 auto mid_purple_color = ImVec4(0.45f, 0.28f, 0.70f, 1.00f);
227
228 auto orange_color = ImVec4(0.98f, 0.60f, 0.20f, 1.00f);
229 auto amber_color = ImVec4(1.00f, 0.78f, 0.25f, 1.00f);
230 auto yellow_color = ImVec4(0.95f, 0.90f, 0.30f, 1.00f);
231
232 auto button_toggled_color = amber_color;
233 auto button_toggled_hover = orange_color;
234 auto button_toggled_active = yellow_color;
235
236 auto button_untoggled_color = blue_mid_color;
237 auto button_untoggled_hover = blue_light_color;
238 auto button_untoggled_active = yellow_color;
239
240 auto button_popup_color = green_dark_color;
241 auto button_popup_hover = green_mid_color;
242 auto button_popup_active = mint_color;
243
244 ImGui::SetCursorPos(window_position);
245
246 ImVec2 toolbar_size = ImVec2(0, 0); // Auto-size to content
247
248 // -------- Hotkeys (before rendering) ------------------------------------
249 auto nm = network_manager.lock();
250 if (!nm)
251 throw std::exception("[BoardManager] NetworkManager Expired");
252
253 ImGuiIO& io = ImGui::GetIO();
254 bool isGM = (nm->getPeerRole() == Role::GAMEMASTER);
255 bool allowHotkeys = !UiTypingGuard::IsTyping();
256
257 // We need a tiny bit of state for Space-hold override
258 static bool s_spaceHoldActive = false;
259 static Tool s_spaceReturnTool = Tool::MOVE; // Tool to restore on Space release
260
261 // Helper to change tool and always record previousTool
262 auto setTool = [&](Tool t)
263 {
264 if (currentTool != t)
265 {
266 previousTool = currentTool; // remember where we came from
267 currentTool = t;
268 }
269 };
270
271 if (allowHotkeys)
272 {
273 // Space: press => temp Move; release => restore the tool that was active at press time
274 if (ImGui::IsKeyPressed(ImGuiKey_Space))
275 {
276 if (!s_spaceHoldActive)
277 {
278 s_spaceHoldActive = true;
279 s_spaceReturnTool = currentTool; // snapshot tool active at press time
280 if (currentTool != Tool::MOVE)
281 {
283 }
284 }
285 }
286 if (s_spaceHoldActive && ImGui::IsKeyReleased(ImGuiKey_Space))
287 {
288 s_spaceHoldActive = false;
289 currentTool = s_spaceReturnTool; // restore exactly what was active at press time
290 }
291
292 // Regular hotkeys (don’t fire while Space-hold is active)
293 if (!s_spaceHoldActive)
294 {
295 if (ImGui::IsKeyPressed(ImGuiKey_M))
296 setTool(Tool::MOVE);
297 if (isGM && ImGui::IsKeyPressed(ImGuiKey_F))
298 setTool(Tool::FOG);
299 if (isGM && ImGui::IsKeyPressed(ImGuiKey_S))
300 setTool(Tool::SELECT);
301
302 if (ImGui::IsKeyPressed(ImGuiKey_C))
304
305 if (isGM && ImGui::IsKeyPressed(ImGuiKey_G))
307
308 if (ImGui::IsKeyPressed(ImGuiKey_Q))
309 std::swap(currentTool, previousTool);
310 }
311 }
312 // -------------------------------------------------------------------------
313
314 ImGui::PushStyleColor(ImGuiCol_WindowBg, panel_color);
315 ImGui::BeginChild("ToolbarChild", toolbar_size, false, toolbar_child_flags);
316
317 // Tool: Previous (Q)
318 ImGui::PushStyleColor(ImGuiCol_Button, purple_color);
319 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, violet_color);
320 ImGui::PushStyleColor(ImGuiCol_ButtonActive, mid_purple_color);
321 if (ImGui::Button("Prev.(Q)", ImVec2(65, 40)))
322 {
323 std::swap(currentTool, previousTool);
324 }
325 ImGui::PopStyleColor(3);
326 ImGui::SameLine();
327
328 // Tool: Move (M)
329 ImGui::PushStyleColor(ImGuiCol_Button, currentTool == Tool::MOVE ? button_toggled_color : button_untoggled_color);
330 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, currentTool == Tool::MOVE ? button_toggled_hover : button_untoggled_hover);
331 ImGui::PushStyleColor(ImGuiCol_ButtonActive, currentTool == Tool::MOVE ? button_toggled_active : button_untoggled_active);
332 if (ImGui::Button(" Move (M)\n(Hold Space)", ImVec2(100, 40)))
333 {
334 setTool(Tool::MOVE);
335 }
336 ImGui::PopStyleColor(3);
337 ImGui::SameLine();
338
339 if (isGM)
340 {
341 // Tool: Fog (F)
342 ImGui::PushStyleColor(ImGuiCol_Button, currentTool == Tool::FOG ? button_toggled_color : button_untoggled_color);
343 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, currentTool == Tool::FOG ? button_toggled_hover : button_untoggled_hover);
344 ImGui::PushStyleColor(ImGuiCol_ButtonActive, currentTool == Tool::FOG ? button_toggled_active : button_untoggled_active);
345 if (ImGui::Button("Fog (F)", ImVec2(100, 40)))
346 {
347 setTool(Tool::FOG);
348 }
349 ImGui::PopStyleColor(3);
350 ImGui::SameLine();
351
352 // Tool: Select (S)
353 ImGui::PushStyleColor(ImGuiCol_Button, currentTool == Tool::SELECT ? button_toggled_color : button_untoggled_color);
354 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, currentTool == Tool::SELECT ? button_toggled_hover : button_untoggled_hover);
355 ImGui::PushStyleColor(ImGuiCol_ButtonActive, currentTool == Tool::SELECT ? button_toggled_active : button_untoggled_active);
356 if (ImGui::Button("Select (S)", ImVec2(100, 40)))
357 {
358 setTool(Tool::SELECT);
359 }
360 ImGui::PopStyleColor(3);
361 ImGui::SameLine();
362 }
363
364 // Camera Settings (no hotkey here)
365 ImGui::PushStyleColor(ImGuiCol_Button, button_popup_color);
366 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_popup_hover);
367 ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_popup_active);
368 if (ImGui::Button("Camera Settings (C)", ImVec2(150, 40)))
369 {
371 }
372 ImGui::PopStyleColor(3);
373
374 if (isGM)
375 {
376 ImGui::SameLine();
377 ImGui::PushStyleColor(ImGuiCol_Button, button_popup_color);
378 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_popup_hover);
379 ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_popup_active);
380 if (ImGui::Button("Grid Settings (G)", ImVec2(150, 40)))
381 {
383 }
384 ImGui::PopStyleColor(3);
385 }
386
387 ImGui::PopStyleColor(); // toolbar bg
388 ImGui::EndChild();
389
392}
393
394glm::vec2 BoardManager::computeMarkerDrawSize_ARFit(const TextureComponent& tex, float basePx /*e.g., 50.0f*/, float scale /*slider, default 1.0f*/)
395{
396 const glm::vec2 texPx = tex.size; // original pixels stored in your TextureComponent
397 if (texPx.x <= 0.0f || texPx.y <= 0.0f)
398 {
399 const float box = basePx * scale;
400 return {box, box};
401 }
402 const float box = basePx * scale;
403 const float s = std::min(box / texPx.x, box / texPx.y);
404 return texPx * s; // scaled to fit inside the box, AR preserved
405}
406
407void BoardManager::renderBoard(VertexArray& va, IndexBuffer& ib, Shader& shader, Shader& grid_shader, Renderer& renderer)
408{
409 auto nm = network_manager.lock();
410 if (!nm)
411 throw std::exception("[BoardManager] Network Manager expired!!");
412
413 const TextureComponent* texture = active_board.get<TextureComponent>();
414 if (texture->textureID != 0)
415 {
416 const Board* board = active_board.get<Board>();
417 const Grid* grid = active_board.get<Grid>();
418 const Size* size = active_board.get<Size>();
419
420 glm::mat4 viewMatrix = camera.getViewMatrix(); // Obtém a matriz de visualização da câmera (pan/zoom)
421 glm::mat4 projection = camera.getProjectionMatrix();
422 glm::mat4 board_model = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f));
423 board_model = glm::scale(board_model, glm::vec3(size->width, size->height, 1.0f));
424
425 shader.Bind();
426 shader.SetUniformMat4f("projection", projection);
427 shader.SetUniformMat4f("view", viewMatrix);
428 shader.SetUniformMat4f("model", board_model);
429 shader.SetUniform1f("u_Alpha", 1.0f);
430 shader.SetUniform1i("u_UseTexture", 1);
431 shader.SetUniform1i("u_Texture", 0);
432 shader.Unbind();
433
434 GLCall(glActiveTexture(GL_TEXTURE0));
435 GLCall(glBindTexture(GL_TEXTURE_2D, texture->textureID));
436
437 renderer.Draw(va, ib, shader);
438
439 if (grid)
440 {
441 if (grid->visible)
442 {
443 grid_shader.Bind();
444 grid_shader.SetUniformMat4f("projection", projection);
445 grid_shader.SetUniformMat4f("view", viewMatrix);
446 grid_shader.SetUniformMat4f("model", board_model); // Grid model is the same as the board
447 grid_shader.SetUniform1i("grid_type", grid->is_hex ? 1 : 0);
448 grid_shader.SetUniform1f("cell_size", grid->cell_size);
449 grid_shader.SetUniform2f("grid_offset", grid->offset.x, grid->offset.y);
450 grid_shader.SetUniform1f("opacity", grid->opacity);
451 grid_shader.Unbind();
452
453 renderer.Draw(va, ib, grid_shader);
454 }
455 }
456
457 ecs.defer_begin(); // Start deferring modifications
458 active_board.children([&](flecs::entity child)
459 {
460 if (child.has<MarkerComponent>()) {
461 const TextureComponent* texture_marker = child.get<TextureComponent>();
462 if (texture_marker->textureID != 0) {
463 const Position* position_marker = child.get<Position>();
464 const Visibility* visibility_marker = child.get<Visibility>();
465 const Size* size_marker = child.get<Size>();
466
467 glm::mat4 marker_model = glm::translate(glm::mat4(1.0f), glm::vec3(position_marker->x, position_marker->y, 0.0f));
468 marker_model = glm::scale(marker_model, glm::vec3(size_marker->width, size_marker->height, 1.0f));
469
470 //glm::mat4 mvp = projection * viewMatrix * marker_model; //Calculate Screen Position(Can use method to standize it, but alter to return the MVP
471 float alpha = 1.0f;
472 if (!visibility_marker->isVisible) {
473 if (nm->getPeerRole() == Role::GAMEMASTER)
474 {
475 alpha = 0.5f;
476 }
477 else {
478 alpha = 0.0f;
479 }
480 }
481
482 shader.Bind();
483 shader.SetUniformMat4f("projection", projection);
484 shader.SetUniformMat4f("view", viewMatrix);
485 shader.SetUniformMat4f("model", marker_model);
486 shader.SetUniform1f("u_Alpha", alpha);
487 shader.SetUniform1i("u_Texture", 0);
488 shader.SetUniform1i("u_UseTexture", 1);
489 shader.Unbind();
490
491 GLCall(glActiveTexture(GL_TEXTURE0));
492 GLCall(glBindTexture(GL_TEXTURE_2D, texture_marker->textureID));
493
494 renderer.Draw(va, ib, shader);
495 }
496 }
497
498 if (child.has<FogOfWar>()) {
499 const Position* position_marker = child.get<Position>();
500 const Visibility* visibility_marker = child.get<Visibility>();
501 const TextureComponent* texture_marker = child.get<TextureComponent>();
502 const Size* size_marker = child.get<Size>();
503
504 glm::mat4 fog_model = glm::translate(glm::mat4(1.0f), glm::vec3(position_marker->x, position_marker->y, 0.0f));
505 fog_model = glm::scale(fog_model, glm::vec3(size_marker->width, size_marker->height, 1.0f));
506
507 float alpha = 1.0f;
508
509 if (!visibility_marker->isVisible) {
510 if (nm->getPeerRole() == Role::GAMEMASTER) {
511 alpha = 0.3f;
512 }
513 else
514 {
515 alpha = 0.0f;
516 }
517 }
518 else
519 {
520 if (nm->getPeerRole() == Role::GAMEMASTER)
521 {
522 alpha = 0.6f;
523 }
524 }
525
526 shader.Bind();
527 shader.SetUniformMat4f("projection", projection);
528 shader.SetUniformMat4f("view", viewMatrix);
529 shader.SetUniformMat4f("model", fog_model);
530 shader.SetUniform1f("u_Alpha", alpha);
531 shader.SetUniform1i("u_UseTexture", 0);
532 shader.Unbind();
533
534 renderer.Draw(va, ib, shader);
535
536 } });
537 ecs.defer_end();
538 }
539}
540
541flecs::entity BoardManager::createMarker(const std::string& imageFilePath, GLuint textureId, glm::vec2 position, glm::vec2 size)
542{
543 auto nm = network_manager.lock();
544 if (!nm)
545 throw std::exception("[BoardManager] Network Manager expired!!");
546
547 auto markerScale = marker_directory->getGlobalSizeSlider();
548 auto texture_marker = TextureComponent{textureId, imageFilePath, size};
549 const glm::vec2 drawSz = computeMarkerDrawSize_ARFit(texture_marker, markerBasePx, markerScale);
550
551 flecs::entity marker = ecs.entity()
553 .set(Position{position.x, position.y}) //World Position
554 .set(Size{drawSz.x, drawSz.y})
555 .set(texture_marker)
556 .set(Visibility{true})
557 .set(MarkerComponent{"", "", false, false})
558 .set(Moving{false});
559
560 marker.add(flecs::ChildOf, active_board);
561
562 auto board_id = active_board.get<Identifier>()->id;
563 nm->broadcastMarker(board_id, marker);
564 return marker;
565}
566
567void BoardManager::deleteMarker(flecs::entity markerEntity)
568{
569 markerEntity.destruct();
570}
571
572// Generates a unique 64-bit ID
573//uint64_t BoardManager::generateUniqueId()
574//{
575// static std::atomic<uint64_t> counter{0}; // Atomic counter for thread safety
576// static std::mt19937_64 rng(std::random_device{}()); // Random number generator
577// static std::uniform_int_distribution<uint64_t> dist(1, UINT64_MAX);
578//
579// uint64_t random_part = dist(rng); // Generate a random 64-bit number
580// uint64_t unique_id = (random_part & 0xFFFFFFFFFFFF0000) | (counter++ & 0xFFFF); // Combine random and counter
581//
582// return unique_id;
583//}
584
586{
587 // 64-bit Snowflake layout:
588 // [ 42 bits timestamp(ms since 2020-01-01 UTC) ][ 10 bits node ][ 12 bits sequence ]
589 static constexpr uint64_t TS_BITS = 42;
590 static constexpr uint64_t NODE_BITS = 10;
591 static constexpr uint64_t SEQ_BITS = 12;
592
593 static constexpr uint64_t TS_MASK = (1ULL << TS_BITS) - 1;
594 static constexpr uint64_t NODE_MASK = (1ULL << NODE_BITS) - 1;
595 static constexpr uint64_t SEQ_MASK = (1ULL << SEQ_BITS) - 1;
596
597 // Custom epoch: 2020-01-01T00:00:00Z in UNIX ms
598 static constexpr int64_t EPOCH_MS = 1577836800000LL;
599
600 // Derive a stable 10-bit node id for this process/machine (once).
601 // Try COMPUTERNAME/HOSTNAME; fallback to random.
602 static const uint16_t NODE_ID = []() -> uint16_t
603 {
604 std::string basis;
605 if (const char* cn = std::getenv("COMPUTERNAME"); cn && *cn)
606 basis = cn;
607 else if (const char* hn = std::getenv("HOSTNAME"); hn && *hn)
608 basis = hn;
609 else
610 {
611 std::random_device rd;
612 basis = std::to_string(rd());
613 }
614 uint64_t h = std::hash<std::string>{}(basis);
615 // xor-fold a bit to mix high/low
616 h ^= (h >> 33) ^ (h >> 17) ^ (h >> 9);
617 return static_cast<uint16_t>(h) & NODE_MASK; // 10 bits
618 }();
619
620 // Per-process state
621 static std::atomic<uint64_t> lastMs{0};
622 static std::atomic<uint16_t> seq{0};
623
624 // Current ms since custom epoch
625 const auto nowEpoch = std::chrono::time_point_cast<std::chrono::milliseconds>(
626 std::chrono::system_clock::now())
627 .time_since_epoch()
628 .count();
629 uint64_t nowMs = (nowEpoch >= EPOCH_MS) ? static_cast<uint64_t>(nowEpoch - EPOCH_MS) : 0ULL;
630
631 uint64_t last = lastMs.load(std::memory_order_relaxed);
632 if (nowMs == last)
633 {
634 // Same millisecond -> increment sequence (12 bits)
635 uint16_t s = static_cast<uint16_t>(seq.fetch_add(1, std::memory_order_relaxed)) & SEQ_MASK;
636 if (s == 0)
637 {
638 // Overflowed the 12-bit sequence; wait for the next millisecond
639 do
640 {
641 std::this_thread::yield();
642 const auto now2 = std::chrono::time_point_cast<std::chrono::milliseconds>(
643 std::chrono::system_clock::now())
644 .time_since_epoch()
645 .count();
646 nowMs = (now2 >= EPOCH_MS) ? static_cast<uint64_t>(now2 - EPOCH_MS) : 0ULL;
647 } while (nowMs == last);
648 lastMs.store(nowMs, std::memory_order_relaxed);
649 seq.store(0, std::memory_order_relaxed);
650 s = 0;
651 }
652 // Pack: (ts << (10+12)) | (node << 12) | seq
653 return ((nowMs & TS_MASK) << (NODE_BITS + SEQ_BITS)) | ((static_cast<uint64_t>(NODE_ID) & NODE_MASK) << SEQ_BITS) | (static_cast<uint64_t>(s) & SEQ_MASK);
654 }
655 else
656 {
657 // New millisecond -> reset sequence
658 lastMs.store(nowMs, std::memory_order_relaxed);
659 seq.store(0, std::memory_order_relaxed);
660 return ((nowMs & TS_MASK) << (NODE_BITS + SEQ_BITS)) | ((static_cast<uint64_t>(NODE_ID) & NODE_MASK) << SEQ_BITS) | 0ULL;
661 }
662}
663
664// Finds an entity by its Identifier component with the specified ID
665flecs::entity BoardManager::findEntityById(uint64_t target_id)
666{
667 flecs::entity result;
668
669 // Iterate over all entities with Identifier component
670 ecs.each<Identifier>([&](flecs::entity e, Identifier& identifier)
671 {
672 if (identifier.id == target_id) {
673 result = e; // Store matching entity
674 } });
675
676 return result; // Returns the found entity, or an empty entity if not found
677}
678// BoardManager.cpp
679//bool BoardManager::canMoveMarker(const MarkerComponent* mc, uint64_t markerId) const
680//{
681// if (!mc)
682// return false;
683//
684// auto nm = network_manager.lock();
685// if (!nm)
686// return false;
687//
688// if (edit_window_entity.is_valid() && edit_window_entity.has<Moving>())
689// {
690// if (edit_window_entity.get<Moving>()->isDragging)
691// return false;
692// }
693//
694// const auto role = nm->getPeerRole(); // existing in your NM
695// if (role == Role::GAMEMASTER)
696// return true; // GM can always move
697//
698// if (mc->locked)
699// return false; // hard lock blocks players
700// if (mc->allowAllPlayersMove)
701// return true; // GM allowed “all players move”
702//
703// const std::string me = nm->getMyId();
704// if (mc->ownerPeerId.empty())
705// return false;
706//
707// return (mc->ownerPeerId == me);
708//}
709
710bool BoardManager::canMoveMarker(const MarkerComponent* mc, flecs::entity markerEnt) const
711{
712 if (!mc || !markerEnt.is_valid())
713 return false;
714
715 auto nm = network_manager.lock();
716 if (!nm)
717 return false;
718
719 // Disallow if being dragged by anyone
720 if (markerEnt.has<Identifier>())
721 {
722 const auto mid = markerEnt.get<Identifier>()->id;
723 if (nm->isMarkerBeingDragged(mid))
724 return false;
725 }
726
727 const auto role = nm->getPeerRole();
728 if (role == Role::GAMEMASTER)
729 return true;
730
731 if (mc->locked)
732 return false;
733
734 if (mc->allowAllPlayersMove)
735 return true;
736
737 const std::string unique_id = identity_manager->myUniqueId();
738 if (!mc->ownerUniqueId.empty() && mc->ownerUniqueId == unique_id)
739 return true;
740
743 //if (auto vis = markerEnt.get<Visibility>(); vis && !vis->isVisible)
744 //{
745 // // Compute marker AABB
746 // auto pos = markerEnt.get<Position>();
747 // auto siz = markerEnt.get<Size>();
748 // if (pos && siz)
749 // {
750 // const float mx1 = pos->x - siz->width * 0.5f;
751 // const float mx2 = pos->x + siz->width * 0.5f;
752 // const float my1 = pos->y - siz->height * 0.5f;
753 // const float my2 = pos->y + siz->height * 0.5f;
754
755 // bool covered = false;
756 // active_board.children([&](flecs::entity child)
757 // {
758 // if (!child.has<FogOfWar>()) return;
759 // if (auto fvis = child.get<Visibility>(); !fvis || !fvis->isVisible) return; // fog must be visible
760
761 // auto fpos = child.get<Position>();
762 // auto fsz = child.get<Size>();
763 // if (!fpos || !fsz) return;
764
765 // const float fx1 = fpos->x - fsz->width * 0.5f;
766 // const float fx2 = fpos->x + fsz->width * 0.5f;
767 // const float fy1 = fpos->y - fsz->height * 0.5f;
768 // const float fy2 = fpos->y + fsz->height * 0.5f;
769
770 // // full containment
771 // if (mx1 >= fx1 && mx2 <= fx2 && my1 >= fy1 && my2 <= fy2)
772 // covered = true; });
773 // if (covered)
774 // return true;
775 // }
776 //}
777
778 return false;
779}
780
781void BoardManager::killIfMouseUp(bool isMouseDown)
782{
783 if (isMouseDown)
784 return;
785
786 // Ensure panning is off
787 if (active_board.is_valid())
788 active_board.set<Panning>({false});
789
790 auto nm = network_manager.lock();
791
792 // If some marker still locally dragging (UI glitch), force-end it now
793 ecs.defer_begin();
794 ecs.each([&](flecs::entity entity, MarkerComponent& mc, Moving& moving, Position& pos)
795 {
796 if (!entity.has(flecs::ChildOf, active_board))
797 return;
798 if (!moving.isDragging)
799 return;
800 const auto mid = entity.get<Identifier>()->id;
801 if (!nm->amIDragging(mid))
802 return; // skip markers dragged by others
803
804 moving.isDragging = false;
805
806 const auto bid = active_board.get<Identifier>()->id;
807
808 nm->markDraggingLocal(mid, false);
809 nm->broadcastMarkerMoveState(bid, entity); // end (final)
810 nm->forceCloseDrag(mid); });
811
812 ecs.defer_end();
813}
814
815// Snap to the nearest cell center in a square grid:
816// centers are at: offset + (i + 0.5) * cell_size
817static inline glm::vec2 snapToSquareCenter(const glm::vec2& worldPos, const glm::vec2& offset, float cell)
818{
819 // transform to grid-local space (subtract offset, not add)
820 const glm::vec2 local = worldPos - offset;
821
822 // center within a cell
823 const glm::vec2 half(cell * 0.5f);
824
825 // round to nearest cell center in local space, then bring back to world
826 const glm::vec2 snappedLocal = glm::round((local - half) / cell) * cell + half;
827 return snappedLocal + offset;
828}
829
830static inline glm::vec2 snapToGridCenter(const glm::vec2& worldPos, const Grid& grid)
831{
832 if (!grid.snap_to_grid || grid.cell_size <= 0.0f)
833 return worldPos;
834 return snapToSquareCenter(worldPos, grid.offset, grid.cell_size);
835}
836
838{
839 if (grid.cell_size <= 0.0f)
840 return;
841
842 auto nm = network_manager.lock();
843
844 ecs.defer_begin();
845 active_board.children([&](flecs::entity e)
846 {
847 if (!e.has<MarkerComponent>()) return;
848
849 Position* pos = e.get_mut<Position>();
850 if (!pos) return;
851
852 glm::vec2 p(pos->x, pos->y);
853 glm::vec2 goal = snapToSquareCenter(p, grid.offset, grid.cell_size);
854
855 // Only write if it actually changes (avoid network spam)
856 if (glm::any(glm::epsilonNotEqual(p, goal, glm::vec2(1e-4f)))) {
857 pos->x = goal.x;
858 pos->y = goal.y;
859
860 // If you're broadcasting grid-driven corrections:
861 if (nm && e.has<Identifier>()) {
862 const auto bid = active_board.get<Identifier>()->id;
863 nm->broadcastMarkerUpdate(bid, e); // or your existing "final pos" message
864 }
865 } });
866 ecs.defer_end();
867}
868//
869//void BoardManager::startMouseDrag(glm::vec2 mousePos, bool draggingMap)
870//{
871// mouse_start_world_pos = mousePos; // Captura a posição inicial do mouse
872// mouse_start_screen_pos = camera.worldToScreenPosition(mousePos);
873// if (currentTool == Tool::MOVE)
874// {
875// if (draggingMap)
876// {
877// active_board.set<Panning>({true});
878// }
879// }
880// else if (currentTool == Tool::FOG)
881// {
882// is_creating_fog = true;
883// }
884//}
885
886bool BoardManager::isMouseOverMarker(glm::vec2 world_position)
887{
888 bool hovered = false;
889 ecs.defer_begin();
890 ecs.each([&](flecs::entity entity, const MarkerComponent& marker_component, const Position& markerPos, const Size& markerSize, Moving& moving)
891 {
892
893 if (entity.has(flecs::ChildOf, active_board)) {
894 bool withinXBounds = (world_position.x >= (markerPos.x - markerSize.width / 2)) &&
895 (world_position.x <= (markerPos.x + markerSize.width / 2));
896
897 bool withinYBounds = (world_position.y >= (markerPos.y - markerSize.height / 2)) &&
898 (world_position.y <= (markerPos.y + markerSize.height / 2));
899
900 if (!(withinXBounds && withinYBounds))
901 return;
902 if (!canMoveMarker(&marker_component, entity))
903 return;
904
905 //moving.isDragging = true;
906 hovered = true;
907 //if (auto nm = network_manager.lock())
908 //{
909 // if (entity.has<Identifier>() && active_board.has<Identifier>())
910 // {
911 // const auto bid = active_board.get<Identifier>()->id;
912 // const auto mid = entity.get<Identifier>()->id;
913
914 // nm->markDraggingLocal(mid, true);
915 // nm->broadcastMarkerMoveState(bid, entity);
916 // }
917 //}
918 } });
919 ecs.defer_end();
920
921 return hovered;
922}
923
924void BoardManager::startMouseDrag(glm::vec2 mousePos, bool draggingMap)
925{
926 mouse_start_world_pos = mousePos;
928
929 if (currentTool == Tool::MOVE)
930 {
931 if (draggingMap)
932 {
933 active_board.set<Panning>({true});
934 return;
935 }
936
937 // Marker drag start:
938 auto ent = getEntityAtMousePosition(mousePos);
939 if (!ent.is_valid() || !ent.has<MarkerComponent>() || !ent.has<Moving>() || !ent.has<Identifier>())
940 return;
941
942 auto nm = network_manager.lock();
943 if (!nm)
944 return;
945
946 const auto mid = ent.get<Identifier>()->id;
947
948 // Do NOT start if someone else is already dragging this marker.
949 if (nm->isMarkerBeingDragged(mid) && !nm->amIDragging(mid))
950 return;
951
952 // Optional: prevent starting a second local drag (you said players shouldn’t drag 2 markers)
953 if (isDraggingMarker())
954 return;
955
956 if (!canMoveMarker(ent.get<MarkerComponent>(), ent))
957 return;
958
959 // Local start
960 ent.set<Moving>(Moving{true});
961 nm->markDraggingLocal(mid, true);
962
963 if (active_board.has<Identifier>())
964 {
965 const auto bid = active_board.get<Identifier>()->id;
966 nm->broadcastMarkerMoveState(bid, ent); // START (isDragging=true)
967 }
968 }
969 else if (currentTool == Tool::FOG)
970 {
971 is_creating_fog = true;
972 }
973}
974
976{
977 if (!active_board.is_valid())
978 return;
979
980 active_board.set<Panning>({false});
981 auto nm = network_manager.lock();
982 if (!nm)
983 throw std::exception("[BoardManager] Network Manager expired!!");
984
985 const Grid* grid = active_board.get<Grid>();
986 const bool canSnap = (grid && grid->snap_to_grid && grid->cell_size > 0.0f);
987
988 ecs.defer_begin();
989 ecs.each([&](flecs::entity entity, const MarkerComponent& marker, Moving& moving, Position& pos)
990 {
991 if (!entity.has(flecs::ChildOf, active_board)) return;
992 if (!moving.isDragging) return;
993
994 const auto mid = entity.get<Identifier>()->id;
995 if (!nm->amIDragging(mid))
996 return; // skip markers dragged by others
997
998 if (canSnap)
999 {
1000 glm::vec2 snapped = snapToSquareCenter(glm::vec2(pos.x, pos.y), grid->offset, grid->cell_size);
1001 pos.x = snapped.x;
1002 pos.y = snapped.y;
1003 }
1004
1005 moving.isDragging = false;
1006
1007 const auto bid = active_board.get<Identifier>()->id;
1008
1009 nm->markDraggingLocal(mid, false); // <- local registry
1010 nm->broadcastMarkerMoveState(bid, entity); // end (isDragging=false + final pos)broadcastMarkerUpdate(bid, entity); // <- final pos + mov=false
1011 nm->forceCloseDrag(mid); });
1012
1013 ecs.defer_end();
1014 is_creating_fog = false;
1015}
1016
1017void BoardManager::handleMarkerDragging(glm::vec2 world_position)
1018{
1019 auto nm = network_manager.lock();
1020 if (!nm)
1021 throw std::exception("[BoardManager] Network Manager expired!!");
1022
1023 ecs.defer_begin();
1024 ecs.each([&](flecs::entity entity, const MarkerComponent& marker, Moving& moving, Position& position)
1025 {
1026 if (!entity.has(flecs::ChildOf, active_board)) return;
1027 if (!moving.isDragging) return;
1028
1029 if (!entity.has<Identifier>())
1030 return;
1031 const auto mid = entity.get<Identifier>()->id;
1032
1033 // Only advance locally if I am the drag owner (prevents flicker)
1034 if (!nm->amIDragging(mid))
1035 return;
1036
1037 glm::vec2 delta = world_position - mouse_start_world_pos;
1038 position.x += delta.x;
1039 position.y += delta.y;
1040 mouse_start_world_pos = world_position;
1041
1042 const auto id = entity.get<Identifier>()->id;
1043 if (shouldSendMarkerMove(id))
1044 {
1045 //Logger::instance().log("localtunnel", Logger::Level::Info, "broadcastMarkerMove!");
1046 nm->broadcastMarkerMove(active_board.get<Identifier>()->id, entity);
1047 } });
1048 ecs.defer_end();
1049}
1050
1051bool BoardManager::shouldSendMarkerMove(uint64_t markerId) const
1052{
1053 using namespace std::chrono;
1054 static std::unordered_map<uint64_t, steady_clock::time_point> lastSent;
1055 static constexpr auto kMinInterval = milliseconds(33); // ~30 Hz. Use 50ms for ~20 Hz if you prefer.
1056
1057 const auto now = steady_clock::now();
1058 auto it = lastSent.find(markerId);
1059 if (it == lastSent.end() || (now - it->second) >= kMinInterval)
1060 {
1061 lastSent[markerId] = now;
1062 return true; // allow this send
1063 }
1064 return false; // too soon, skip this tick
1065}
1066
1068{
1069 const Panning* panning = active_board.get<Panning>();
1070 return panning->isPanning;
1071}
1072
1073bool BoardManager::isDraggingMarker(bool local_drag_only /*= true*/)
1074{
1075 auto nm = network_manager.lock();
1076 if (!nm)
1077 return false;
1078
1079 bool any = false;
1080
1081 ecs.defer_begin();
1082 ecs.each([&](flecs::entity e, const MarkerComponent&, const Moving& mv, const Identifier& id)
1083 {
1084 if (any) return; // early out from further work in this lambda (flecs will still loop, but we do nothing)
1085 if (!e.has(flecs::ChildOf, active_board)) return;
1086 if (!mv.isDragging) return;
1087
1088 if (local_drag_only)
1089 {
1090 if (nm->amIDragging(id.id))
1091 any = true; // found a local-owned drag
1092 }
1093 else
1094 {
1095 any = true; // any drag at all
1096 } });
1097 ecs.defer_end();
1098
1099 return any;
1100}
1101
1103{
1104 return mouse_start_world_pos;
1105}
1106
1107void BoardManager::panBoard(glm::vec2 current_mouse_fbo_pos)
1108{
1109 glm::vec2 delta_screen = mouse_start_screen_pos - current_mouse_fbo_pos;
1110 float world_scale_factor = camera.getZoom();
1111 glm::vec2 delta_world = delta_screen / world_scale_factor;
1112 camera.pan(delta_world);
1113 mouse_start_screen_pos = current_mouse_fbo_pos;
1114}
1115
1117{
1118 camera.setPosition(glm::vec2(0.0f, 0.0f));
1119 camera.setZoom(1.0f);
1120}
1121
1122void BoardManager::deleteFogOfWar(flecs::entity fogEntity)
1123{
1124 fogEntity.destruct();
1125}
1126
1127flecs::entity BoardManager::createFogOfWar(glm::vec2 startPos, glm::vec2 size)
1128{
1129 auto nm = network_manager.lock();
1130 if (!nm)
1131 throw std::exception("[BoardManager] Network Manager expired!!");
1132
1133 auto fog = ecs.entity()
1135 .set(Position{startPos.x, startPos.y})
1136 .set(Size{size.x, size.y})
1137 .set(Visibility{true});
1138
1139 fog.add<FogOfWar>();
1140 fog.add(flecs::ChildOf, active_board);
1141
1142 auto board_id = active_board.get<Identifier>()->id;
1143 nm->broadcastFog(board_id, fog);
1144
1145 return fog;
1146}
1147
1148void BoardManager::handleFogCreation(glm::vec2 end_world_position)
1149{
1150 glm::vec2 start_world_position = getMouseStartPosition();
1151
1152 // Calculate size
1153 glm::vec2 size = end_world_position - start_world_position; //glm::abs(end_world_position - start_world_position); // Make sure size is positive
1154 glm::vec2 corrected_start_position;
1155 if (size.x < 0)
1156 {
1157 corrected_start_position.x = end_world_position.x + glm::abs(size.x) / 2;
1158 }
1159 else
1160 {
1161 corrected_start_position.x = start_world_position.x + size.x / 2;
1162 }
1163 if (size.y < 0)
1164 {
1165 corrected_start_position.y = end_world_position.y + glm::abs(size.y) / 2;
1166 }
1167 else
1168 {
1169 corrected_start_position.y = start_world_position.y + size.y / 2;
1170 }
1171 createFogOfWar(corrected_start_position, glm::abs(size));
1172}
1173
1175{
1176 return currentTool;
1177}
1178
1180{
1181 currentTool = newTool;
1182}
1183
1184flecs::entity BoardManager::getEntityAtMousePosition(glm::vec2 mouse_position)
1185{
1186
1187 auto entity_at_mouse = flecs::entity();
1188 ecs.defer_begin();
1189 ecs.each([&](flecs::entity entity, const Position& entity_pos, const Size& entity_size)
1190 {
1191 const bool isMarker = entity.has<MarkerComponent>();
1192 if (isMarker) {
1193 glm::vec2 world_position = mouse_position;
1194
1195 if (entity.has(flecs::ChildOf, active_board)) {
1196
1197 bool withinXBounds = (world_position.x >= (entity_pos.x - entity_size.width / 2)) &&
1198 (world_position.x <= (entity_pos.x + entity_size.width / 2));
1199
1200 bool withinYBounds = (world_position.y >= (entity_pos.y - entity_size.height / 2)) &&
1201 (world_position.y <= (entity_pos.y + entity_size.height / 2));
1202
1203 if (withinXBounds && withinYBounds) {
1204 entity_at_mouse = entity;
1205 }
1206 }
1207
1208 } });
1209 ecs.defer_end();
1210
1211 if (entity_at_mouse.is_valid())
1212 return entity_at_mouse;
1213
1214 ecs.defer_begin();
1215 ecs.each([&](flecs::entity entity, const Position& entity_pos, const Size& entity_size)
1216 {
1217
1218 glm::vec2 world_position = mouse_position;
1219
1220 if (entity.has(flecs::ChildOf, active_board)) {
1221
1222 bool withinXBounds = (world_position.x >= (entity_pos.x - entity_size.width / 2)) &&
1223 (world_position.x <= (entity_pos.x + entity_size.width / 2));
1224
1225 bool withinYBounds = (world_position.y >= (entity_pos.y - entity_size.height / 2)) &&
1226 (world_position.y <= (entity_pos.y + entity_size.height / 2));
1227
1228 if (withinXBounds && withinYBounds) {
1229 entity_at_mouse = entity;
1230 }
1231 } });
1232 ecs.defer_end();
1233 return entity_at_mouse;
1234}
1235
1236void BoardManager::onUsernameChanged(const std::string& uniqueId, const std::string& newUsername)
1237{
1238 // Update all markers owned by uniqueId: only the DISPLAY name changes.
1239 ecs.each([&](flecs::entity e, MarkerComponent& mc)
1240 {
1241 // you will add/keep ownerUniqueId in MarkerComponent:
1242 // struct MarkerComponent {
1243 // std::string ownerUniqueId; // authoritative owner
1244 // std::string ownerPeerUsername; // purely visual
1245 // bool allowAllPlayersMove = false;
1246 // bool locked = false;
1247 // };
1248
1249 if (mc.ownerUniqueId == uniqueId)
1250 mc.ownerPeerUsername = newUsername; });
1251
1252 // If you draw any overlays/caches of names, invalidate them here (optional).
1253}
1254
1255//Save and Load Board --------------------------------------------------------------------
1256
1257void BoardManager::saveActiveBoard(std::filesystem::path& board_directory_path)
1258{
1259 if (!active_board.is_alive())
1260 {
1261 std::cerr << "No active board to save." << std::endl;
1262 return;
1263 }
1264 auto board = active_board.get<Board>();
1265 if (!std::filesystem::exists(board_directory_path))
1266 {
1267 std::filesystem::create_directory(board_directory_path);
1268 }
1269
1270 auto board_file_path = board_directory_path / (board->board_name + ".runic");
1271
1272 std::vector<uint8_t> buffer;
1274
1275 std::ofstream outFile(board_file_path, std::ios::binary);
1276 if (outFile)
1277 {
1278 outFile.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
1279 outFile.close();
1280 std::cout << "Board saved successfully to " << board_directory_path << std::endl;
1281 }
1282 else
1283 {
1284 std::cerr << "Failed to save board to " << board_directory_path << std::endl;
1285 }
1286}
1287
1288void BoardManager::loadActiveBoard(const std::string& board_file_path)
1289{
1290 std::ifstream inFile(board_file_path, std::ios::binary);
1291 if (inFile)
1292 {
1293 std::vector<uint8_t> buffer((std::istreambuf_iterator<char>(inFile)), std::istreambuf_iterator<char>());
1294 inFile.close();
1295
1296 size_t offset = 0;
1298 auto texture = active_board.get_mut<TextureComponent>();
1299 auto map_image = map_directory->getImageByPath(texture->image_path);
1300 texture->textureID = map_image.textureID;
1301 texture->size = map_image.size;
1302
1303 ecs.defer_begin();
1304 active_board.children([&](flecs::entity child)
1305 {
1306 if (child.has<MarkerComponent>()) {
1307 auto child_texture = child.get_mut<TextureComponent>();
1308 auto marker_image = marker_directory->getImageByPath(child_texture->image_path);
1309 child_texture->textureID = marker_image.textureID;
1310 child_texture->size = marker_image.size;
1311 } });
1312 ecs.defer_end();
1313
1315 std::cout << "Board loaded successfully from " << board_file_path << std::endl;
1316 }
1317 else
1318 {
1319 std::cerr << "Failed to load board from " << board_file_path << std::endl;
1320 }
1321}
1322
1323flecs::entity BoardManager::getActiveBoard() const
1324{
1325 return active_board;
1326}
1327
1329{
1330 return showEditWindow;
1331}
1332
1334{
1335 bool is_hovered = false;
1336 // Get the current mouse position to set the window position
1337 ImVec2 mousePos = ImGui::GetMousePos();
1338 ImGui::SetNextWindowPos(mousePos, ImGuiCond_Appearing);
1339 ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.2f, 0.3f, 0.4f, 1.0f)); // Set the background color (RGBA)
1340 ImGui::Begin("EditEntity", &showEditWindow, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove);
1341
1342 // Retrieve the Size and Visibility components of the entity
1343 is_hovered = ImGui::IsWindowHovered();
1344 auto is_popup_open = false;
1346 {
1347 auto nm = network_manager.lock();
1348 auto boardEnt = getActiveBoard();
1349
1350 auto size = edit_window_entity.get_mut<Size>(); // Mutable access to the size
1351 auto visibility = edit_window_entity.get_mut<Visibility>(); // Mutable access to the visibility
1352
1353 ImGui::BeginGroup();
1354 if (ImGui::Button("+ Size"))
1355 {
1356 if (nm && boardEnt.is_valid())
1357 {
1358 size->width = size->width * 1.1;
1359 size->height = size->height * 1.1; // Adjust height proportionally to the width
1361 {
1362 nm->broadcastMarkerUpdate(boardEnt.get<Identifier>()->id, edit_window_entity);
1363 }
1364 else if (edit_window_entity.has<FogOfWar>())
1365 {
1366 nm->broadcastFogUpdate(boardEnt.get<Identifier>()->id, edit_window_entity);
1367 }
1368 }
1369 }
1370 ImGui::SameLine();
1371 if (ImGui::Button("- Size"))
1372 {
1373 if (nm && boardEnt.is_valid())
1374 {
1375 size->width = size->width * 0.90;
1376 size->height = size->height * 0.90; // Adjust height proportionally to the width
1378 {
1379 nm->broadcastMarkerUpdate(boardEnt.get<Identifier>()->id, edit_window_entity);
1380 }
1381 else if (edit_window_entity.has<FogOfWar>())
1382 {
1383 nm->broadcastFogUpdate(boardEnt.get<Identifier>()->id, edit_window_entity);
1384 }
1385 }
1386 }
1387
1388 ImGui::EndGroup();
1389 // Checkbox for visibility change
1390 auto vis_temp = visibility->isVisible;
1391 if (ImGui::Checkbox("Visible", &vis_temp))
1392 {
1393 if (nm && boardEnt.is_valid())
1394 {
1395 visibility->isVisible = vis_temp;
1397 {
1398 nm->broadcastMarkerUpdate(boardEnt.get<Identifier>()->id, edit_window_entity);
1399 }
1400 else if (edit_window_entity.has<FogOfWar>())
1401 {
1402 nm->broadcastFogUpdate(boardEnt.get<Identifier>()->id, edit_window_entity);
1403 }
1404 }
1405 }
1406
1407 ImGui::Separator();
1408
1409 // Button to delete the entity (with a confirmation popup)
1410
1411 if (ImGui::Button("Delete"))
1412 {
1413 ImGui::OpenPopup("Confirm Delete");
1414 is_popup_open = true;
1415 }
1416
1417 if (ImGui::IsPopupOpen("Confirm Delete"))
1418 is_popup_open = true;
1419 if (ImGui::BeginPopupModal("Confirm Delete", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
1420 {
1421 ImGui::Text("Are you sure you want to delete this entity?");
1422 ImGui::Separator();
1423
1424 if (ImGui::Button("Yes", ImVec2(120, 0)))
1425 {
1426 if (edit_window_entity.is_alive())
1427 {
1428 if (nm && boardEnt.is_valid())
1429 {
1431 {
1432 nm->broadcastMarkerDelete(boardEnt.get<Identifier>()->id, edit_window_entity);
1433 }
1434 else if (edit_window_entity.has<FogOfWar>())
1435 {
1436 nm->broadcastFogDelete(boardEnt.get<Identifier>()->id, edit_window_entity);
1437 }
1438 edit_window_entity.destruct(); // Delete the entity
1439 showEditWindow = false;
1440 }
1441 }
1442 ImGui::CloseCurrentPopup(); // Close the popup after deletion
1443 }
1444 ImGui::SameLine();
1445 if (ImGui::Button("No", ImVec2(120, 0)))
1446 {
1447 ImGui::CloseCurrentPopup(); // Close the popup without deletion
1448 }
1449 ImGui::EndPopup();
1450 }
1451 }
1452 else
1453 {
1454 ImGui::Text("Invalid entity or missing components!");
1455 }
1456 // --- Ownership (only for markers) ---
1457 if (edit_window_entity.is_valid() && edit_window_entity.has<MarkerComponent>())
1458 {
1459 auto nm = network_manager.lock();
1460 auto mc = edit_window_entity.get_mut<MarkerComponent>();
1461 auto idm = identity_manager; // you already have this in BoardManager
1462
1463 ImGui::Separator();
1464 ImGui::TextUnformatted("Owner");
1465 ImGui::Spacing();
1466
1467 // Build options as (uniqueId, displayName). Index 0 = (none)
1468 struct Opt
1469 {
1470 std::string uid;
1471 std::string label;
1472 };
1473 std::vector<Opt> options;
1474 options.push_back(Opt{"", "(none)"}); // index 0
1475
1476 // Collect connected peers -> uniqueId (dedup by uid)
1477 std::set<std::string> seenUids;
1478 if (nm && idm)
1479 {
1480 for (auto& [peerId, link] : nm->getPeers())
1481 {
1482 if (!link)
1483 continue;
1484 auto uidOpt = idm->uniqueForPeer(peerId);
1485 if (!uidOpt || uidOpt->empty())
1486 continue;
1487
1488 const std::string& uid = *uidOpt;
1489 if (!seenUids.insert(uid).second)
1490 continue; // dedup
1491
1492 // label = current username for that uid
1493 std::string label = idm->usernameForUnique(uid);
1494 if (label.empty())
1495 label = uid.substr(0, std::min<size_t>(8, uid.size()));
1496 options.push_back(Opt{uid, label});
1497 }
1498 }
1499
1500 // If current owner is offline, include them so the selection stays visible
1501 if (idm && !mc->ownerUniqueId.empty() && !seenUids.count(mc->ownerUniqueId))
1502 {
1503 std::string label = idm->usernameForUnique(mc->ownerUniqueId);
1504 if (label.empty())
1505 label = mc->ownerUniqueId.substr(0, std::min<size_t>(8, mc->ownerUniqueId.size()));
1506 options.push_back(Opt{mc->ownerUniqueId, label});
1507 }
1508
1509 // Find current selection by ownerUniqueId
1510 int selectedIndex = 0;
1511 for (int i = 1; i < (int)options.size(); ++i)
1512 if (options[i].uid == mc->ownerUniqueId)
1513 {
1514 selectedIndex = i;
1515 break;
1516 }
1517
1518 // Pagination (unchanged UI)
1519 static int ownerPage = 0;
1520 const int rowsPerPage = 6;
1521 const int totalRows = (int)options.size();
1522 const int totalPages = (totalRows + rowsPerPage - 1) / rowsPerPage;
1523 ownerPage = std::clamp(ownerPage, 0, std::max(0, totalPages - 1));
1524 auto ToggleRow = [&](const char* label, bool selected, int id) -> bool
1525 {
1526 ImGui::PushID(id);
1527 if (selected)
1528 {
1529 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.20f, 0.50f, 0.80f, 1.0f));
1530 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.55f, 0.85f, 1.0f));
1531 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.15f, 0.45f, 0.75f, 1.0f));
1532 }
1533 else
1534 {
1535 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.25f, 0.28f, 0.32f, 1.0f));
1536 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.30f, 0.33f, 0.38f, 1.0f));
1537 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.22f, 0.25f, 0.29f, 1.0f));
1538 }
1539 bool clicked = ImGui::Button(label, ImVec2(-FLT_MIN, 0));
1540 ImGui::PopStyleColor(3);
1541 ImGui::PopID();
1542 return clicked;
1543 };
1544
1545 const int start = ownerPage * rowsPerPage;
1546 const int end = std::min(start + rowsPerPage, totalRows);
1547
1548 // Render visible rows
1549 for (int i = start; i < end; ++i)
1550 {
1551 const bool isSel = (selectedIndex == i);
1552 const char* label = options[i].label.c_str();
1553 if (ToggleRow(label, isSel, i))
1554 {
1555 selectedIndex = i;
1556
1557 // APPLY IMMEDIATELY (no "Apply Ownership" button)
1558 const std::string prevOwnerUid = mc->ownerUniqueId;
1559 mc->ownerUniqueId = options[i].uid; // << authoritative owner
1560 mc->ownerPeerUsername.clear();
1561 if (idm && !mc->ownerUniqueId.empty())
1562 mc->ownerPeerUsername = idm->usernameForUnique(mc->ownerUniqueId); // display only
1563
1564 // If owner changed, clear drag state so new owner can take over smoothly
1565 if (edit_window_entity.has<Identifier>() && nm)
1566 {
1567 const auto mid = edit_window_entity.get<Identifier>()->id;
1568 if (prevOwnerUid != mc->ownerUniqueId)
1569 nm->clearDragState(mid); // small helper: drag_.erase(mid)
1570 }
1571
1572 // Broadcast the change now
1573 if (nm)
1574 {
1575 auto boardEnt = getActiveBoard();
1576 if (boardEnt.is_valid())
1577 nm->broadcastMarkerUpdate(boardEnt.get<Identifier>()->id, edit_window_entity);
1578 }
1579 }
1580 }
1581
1582 // Pagination controls
1583 if (totalPages > 1)
1584 {
1585 ImGui::Spacing();
1586 ImGui::BeginDisabled(ownerPage <= 0);
1587 if (ImGui::Button("< Prev"))
1588 ownerPage--;
1589 ImGui::EndDisabled();
1590 ImGui::SameLine();
1591 ImGui::Text("Page %d / %d", ownerPage + 1, std::max(1, totalPages));
1592 ImGui::SameLine();
1593 ImGui::BeginDisabled(ownerPage >= totalPages - 1);
1594 if (ImGui::Button("Next >"))
1595 ownerPage++;
1596 ImGui::EndDisabled();
1597 }
1598
1599 // Flags — apply immediately when toggled
1600 bool flagsChanged = false;
1601 flagsChanged |= ImGui::Checkbox("Allow all players to move", &mc->allowAllPlayersMove);
1602 flagsChanged |= ImGui::Checkbox("Locked (players cannot move)", &mc->locked);
1603 if (flagsChanged && nm)
1604 {
1605 auto boardEnt = getActiveBoard();
1606 if (boardEnt.is_valid())
1607 nm->broadcastMarkerUpdate(boardEnt.get<Identifier>()->id, edit_window_entity);
1608 }
1609 }
1610
1611 ImGui::End();
1612 ImGui::PopStyleColor(); // Restore the original background color
1613
1614 if (!is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !is_popup_open)
1615 {
1616 showEditWindow = false;
1617 }
1618
1619 if (!showEditWindow)
1620 {
1621 edit_window_entity = flecs::entity();
1622 }
1623 //close edit window when clicking outside it
1624}
1625
1627{
1628 // Check if the window should be shown
1629 if (!showCameraSettings)
1630 {
1631 return;
1632 }
1633
1634 // Begin the ImGui window
1635 auto mouse_pos = ImGui::GetMousePos();
1636 ImGui::SetNextWindowPos(ImVec2(mouse_pos.x, mouse_pos.y + ImGui::GetFrameHeightWithSpacing()), ImGuiCond_Appearing);
1637 ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.1f, 0.2f, 1.0f)); // Set the background color (RGBA)
1638 ImGui::Begin("Camera", &showCameraSettings, ImGuiWindowFlags_AlwaysAutoResize);
1639 ImGui::PopStyleColor();
1640 auto zoom = camera.getZoom();
1641
1642 ImGui::Text("Zoom Buttons");
1643 if (ImGui::Button("-"))
1644 {
1645 zoom = zoom - 0.01f;
1646 }
1647 ImGui::SameLine();
1648 if (ImGui::Button("+"))
1649 {
1650 zoom = zoom + 0.01f;
1651 }
1652
1653 ImGui::Separator();
1654 ImGui::SliderFloat("Zoom Slider", &zoom, 0.1f, 10.0f);
1655 camera.setZoom(zoom);
1656 ImGui::Separator();
1657 if (ImGui::Button("Reset Camera"))
1658 {
1659 resetCamera();
1660 }
1661
1662 ImGui::End();
1663}
1664
1666{
1667 // Check if the window should be shown
1668 if (!showGridSettings)
1669 {
1670 return;
1671 }
1672
1673 // Begin the ImGui window
1674 auto mouse_pos = ImGui::GetMousePos();
1675 ImGui::SetNextWindowPos(ImVec2(mouse_pos.x, mouse_pos.y + ImGui::GetFrameHeightWithSpacing()), ImGuiCond_Appearing);
1676 ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.1f, 0.2f, 1.0f)); // Set the background color (RGBA)
1677 ImGui::Begin("Grid", &showGridSettings, ImGuiWindowFlags_AlwaysAutoResize);
1678
1679 ImGui::PopStyleColor();
1680 // Get a mutable reference to the Grid component from the active board
1681 auto grid = active_board.get_mut<Grid>();
1682 bool changed = false;
1683
1684 if (grid)
1685 {
1686 // --- BOOLEAN CHECKBOXES ---
1687 changed |= ImGui::Checkbox("Visible", &grid->visible);
1688 changed |= ImGui::Checkbox("Snap to Grid", &grid->snap_to_grid);
1689 //ImGui::Checkbox("Hexagonal Grid", &grid->is_hex);
1690
1691 // --- FLOAT SLIDERS ---
1692 changed |= ImGui::SliderFloat("Cell Size", &grid->cell_size, 10.0f, 200.0f);
1693 ImGui::SameLine();
1694 if (ImGui::Button("-##size"))
1695 {
1696 grid->cell_size = grid->cell_size - 0.01f;
1697 changed = true;
1698 }
1699 ImGui::SameLine();
1700 if (ImGui::Button("+##size"))
1701 {
1702 grid->cell_size = grid->cell_size + 0.01f;
1703 changed = true;
1704 }
1705 // --- OFFSET CONTROLS (SIMPLIFIED WITH SLIDERS) ---
1706 ImGui::Text("Grid Offset");
1707 changed |= ImGui::SliderFloat("Offset X", &grid->offset.x, -500.0f, 500.0f);
1708 ImGui::SameLine();
1709 if (ImGui::Button("-##offsetx"))
1710 {
1711 grid->offset.x = grid->offset.x - 0.01f;
1712 changed = true;
1713 }
1714 ImGui::SameLine();
1715 if (ImGui::Button("+##offsetx"))
1716 {
1717 grid->offset.x = grid->offset.x + 0.01f;
1718 changed = true;
1719 }
1720 changed |= ImGui::SliderFloat("Offset Y", &grid->offset.y, -500.0f, 500.0f);
1721
1722 ImGui::SameLine();
1723 if (ImGui::Button("-##offsety"))
1724 {
1725 grid->offset.y = grid->offset.y - 0.01f;
1726 changed = true;
1727 }
1728 ImGui::SameLine();
1729 if (ImGui::Button("+##offsety"))
1730 {
1731 grid->offset.y = grid->offset.y + 0.01f;
1732 changed = true;
1733 }
1734
1735 // Button to reset the offset
1736 if (ImGui::Button("Reset Offset"))
1737 {
1738 grid->offset = glm::vec2(0.0f);
1739 changed = true;
1740 }
1741
1742 if (changed)
1743 {
1744 if (grid->snap_to_grid)
1745 {
1747 }
1748 if (auto nm = network_manager.lock())
1749 {
1750 if (active_board.has<Identifier>())
1751 nm->broadcastGridUpdate(active_board.get<Identifier>()->id, active_board);
1752 }
1753 }
1754 }
1755 else
1756 {
1757 ImGui::Text("Active board entity does not have a Grid component.");
1758 }
1759
1760 ImGui::End();
1761}
1762
1763flecs::entity BoardManager::findBoardById(uint64_t boardId)
1764{
1765 flecs::entity result;
1766 ecs.each([&](flecs::entity e, const Board& b, const Identifier& id)
1767 {
1768 Logger::instance().log("localtunnel", Logger::Level::Info, "Board Name: " + b.board_name);
1769 if (e.is_valid() && id.id == boardId) {
1770 result = e;
1771 Logger::instance().log("localtunnel", Logger::Level::Info, "Found Board By ID!!");
1772 } });
1773
1774 return result; // will be invalid if not found
1775}
1776
1777BoardImageData BoardManager::LoadTextureFromMemory(const uint8_t* bytes, size_t sizeBytes)
1778{
1779 if (!bytes || sizeBytes == 0)
1780 {
1781 std::cerr << "LoadTextureFromMemory: empty buffer\n";
1782 return BoardImageData{};
1783 }
1784
1785 // stb_image: ensure vertical flip matches your expectations
1786 stbi_set_flip_vertically_on_load(0);
1787
1788 int width = 0, height = 0, nrChannels = 0;
1789 uint8_t* data = stbi_load_from_memory(bytes, (int)sizeBytes, &width, &height, &nrChannels, 4);
1790 if (!data)
1791 {
1792 std::cerr << "LoadTextureFromMemory: decode failed\n";
1793 return BoardImageData{};
1794 }
1795
1796 GLuint tex = 0;
1797 glGenTextures(1, &tex);
1798 glBindTexture(GL_TEXTURE_2D, tex);
1799
1800 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height,
1801 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
1802
1803 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1804 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1805 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1806 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1807
1808 stbi_image_free(data);
1809 return BoardImageData(tex, glm::vec2(width, height), /*path*/ "");
1810}
1811//glm::vec2 BoardManager::worldToScreenPosition(glm::vec2 world_position) {
1812// // Step 1: Get the combined MVP matrix
1813// glm::mat4 MVP = camera.getProjectionMatrix() * camera.getViewMatrix();
1814//
1815// // Step 2: Transform world position to clip space (NDC)
1816// glm::vec4 clipPos = MVP * glm::vec4(world_position, 0.0f, 1.0f);
1817//
1818// // Step 3: Convert NDC to screen coordinates
1819// glm::vec2 windowSize = camera.getWindowSize();
1820// float screenX = ((clipPos.x / clipPos.w) + 1.0f) * 0.5f * windowSize.x;
1821// float screenY = (1.0f - (clipPos.y / clipPos.w)) * 0.5f * windowSize.y; // Flip Y-axis
1822//
1823// // Step 4: Return screen position as 2D (x, y) coordinates
1824//
1825// glm::vec2 screen_position = glm::vec2(screenX, screenY);
1826//
1827// return screen_position;
1828//}
1829//
1830//void BoardManager::sendEntityUpdate(flecs::entity entity, MessageType message_type)
1831//{
1832// if (message_type == MessageType::MarkerUpdate) {
1833// Message markerMessage;
1834// markerMessage.type = MessageType::MarkerUpdate;
1835// std::vector<uint8_t> buffer;
1836// auto pos = entity.get<Position>();
1837// auto size = entity.get<Size>();
1838// auto texture = entity.get<TextureComponent>();
1839// auto visibility = entity.get<Visibility>();
1840// auto moving = entity.get<Moving>();
1841// // Serialize marker components
1842// Serializer::serializePosition(buffer, pos);
1843// Serializer::serializeSize(buffer, size);
1844// Serializer::serializeTextureComponent(buffer, texture);
1845// Serializer::serializeVisibility(buffer, visibility);
1846// Serializer::serializeMoving(buffer, moving);
1847//
1848// markerMessage.payload = buffer;
1849// // Queue the message for the network manager
1850// network_manager->queueMessage(markerMessage);
1851// }
1852// else if (message_type == MessageType::FogUpdate)
1853// {
1854// Message fogMessage;
1855// fogMessage.type = MessageType::FogUpdate;
1856// std::vector<uint8_t> buffer;
1857//
1858// auto pos = entity.get<Position>();
1859// auto size = entity.get<Size>();
1860// auto visibility = entity.get<Visibility>();
1861// // Serialize marker components
1862// Serializer::serializePosition(buffer, pos);
1863// Serializer::serializeSize(buffer, size);
1864// Serializer::serializeVisibility(buffer, visibility);
1865//
1866// fogMessage.payload = buffer;
1867// // Queue the message for the network manager
1868// network_manager->queueMessage(fogMessage);
1869//
1870// }
1871// else if (message_type == MessageType::CreateMarker)
1872// {
1873// Message markerMessage;
1874// markerMessage.type = MessageType::MarkerUpdate;
1875// std::vector<uint8_t> buffer;
1876// auto pos = entity.get<Position>();
1877// auto size = entity.get<Size>();
1878// auto texture = entity.get<TextureComponent>();
1879// auto visibility = entity.get<Visibility>();
1880// auto moving = entity.get<Moving>();
1881// // Serialize marker components
1882// Serializer::serializePosition(buffer, pos);
1883// Serializer::serializeSize(buffer, size);
1884// Serializer::serializeTextureComponent(buffer, texture);
1885// Serializer::serializeVisibility(buffer, visibility);
1886// Serializer::serializeMoving(buffer, moving);
1887//
1888// markerMessage.payload = buffer;
1889// // Queue the message for the network manager
1890// network_manager->queueMessage(markerMessage);
1891//
1892// }
1893// else if (message_type == MessageType::CreateFog)
1894// {
1895// Message fogMessage;
1896// fogMessage.type = MessageType::FogUpdate;
1897// std::vector<uint8_t> buffer;
1898//
1899// auto pos = entity.get<Position>();
1900// auto size = entity.get<Size>();
1901// auto visibility = entity.get<Visibility>();
1902// // Serialize marker components
1903// Serializer::serializePosition(buffer, pos);
1904// Serializer::serializeSize(buffer, size);
1905// Serializer::serializeVisibility(buffer, visibility);
1906//
1907// fogMessage.payload = buffer;
1908// // Queue the message for the network manager
1909// network_manager->queueMessage(fogMessage);
1910// }
1911//}
1912
1913//
1914//glm::vec2 BoardManager::screenToWorldPosition(glm::vec2 screen_position) {
1915//
1916// glm::vec2 relative_screen_position = { screen_position.x - camera.getWindowPosition().x, screen_position.y - camera.getWindowPosition().y};
1917//
1918// // Get the view matrix (which handles panning and zoom)
1919// glm::mat4 view_matrix = camera.getViewMatrix();
1920//
1921// // Get the projection matrix (which might handle window size)
1922// glm::mat4 proj_matrix = camera.getProjectionMatrix();
1923//
1924// // Create a normalized device coordinate from the screen position
1925// glm::vec2 window_size = camera.getWindowSize();
1926//
1927// // Convert the screen position to normalized device coordinates (NDC)
1928// float ndc_x = (2.0f * relative_screen_position.x) / window_size.x - 1.0f;
1929// float ndc_y = 1.0f - (2.0f * relative_screen_position.y) / window_size.y; // Inverting y-axis for OpenGL
1930// glm::vec4 ndc_position = glm::vec4(ndc_x, ndc_y, 0.0f, 1.0f);
1931//
1932// // Calculate the inverse MVP matrix (to map from NDC back to world space)
1933// glm::mat4 mvp = proj_matrix * view_matrix;
1934// glm::mat4 inverse_mvp = glm::inverse(mvp);
1935//
1936// // Transform the NDC position back to world coordinates
1937// glm::vec4 world_position = inverse_mvp * ndc_position;
1938//
1939// // Perform perspective divide to get the correct world position
1940// if (world_position.w != 0.0f) {
1941// world_position /= world_position.w;
1942// }
1943//
1944// // Return the world position as a 2D vector (we're ignoring the Z-axis for 2D rendering)
1945// return glm::vec2(world_position.x, world_position.y);
1946//}
static glm::vec2 snapToGridCenter(const glm::vec2 &worldPos, const Grid &grid)
static glm::vec2 snapToSquareCenter(const glm::vec2 &worldPos, const glm::vec2 &offset, float cell)
Tool
@ SELECT
@ GAMEMASTER
#define GLCall(x)
Definition Renderer.h:12
void deleteMarker(flecs::entity markerEntity)
flecs::entity getEntityAtMousePosition(glm::vec2 mouse_position)
void renderBoard(VertexArray &va, IndexBuffer &ib, Shader &shader, Shader &grid_shader, Renderer &renderer)
void renderToolbar(const ImVec2 &window_position)
uint64_t generateUniqueId()
bool isEditWindowOpen() const
flecs::entity findBoardById(uint64_t boardId)
bool isMouseOverMarker(glm::vec2 mousePos)
flecs::entity edit_window_entity
void renderEditWindow()
void saveActiveBoard(const std::string &filePath)
bool showCameraSettings
bool canMoveMarker(const MarkerComponent *mc, flecs::entity markerEnt) const
void startMouseDrag(glm::vec2 mousePos, bool draggingMarker)
flecs::entity findEntityById(uint64_t target_id)
BoardImageData LoadTextureFromMemory(const unsigned char *bytes, size_t sizeBytes)
glm::vec2 getMouseStartPosition() const
std::string board_name
glm::vec2 mouse_start_world_pos
bool showGridSettings
void deleteFogOfWar(flecs::entity fogEntity)
BoardManager(flecs::world ecs, std::weak_ptr< NetworkManager > network_manager, std::shared_ptr< IdentityManager > identity_manager, std::shared_ptr< DirectoryWindow > map_directory, std::shared_ptr< DirectoryWindow > marker_directory)
void renderGridWindow()
Tool getCurrentTool() const
bool isBoardActive()
void renderCameraWindow()
void onUsernameChanged(const std::string &uniqueId, const std::string &newUsername)
std::shared_ptr< DirectoryWindow > marker_directory
void setActiveBoard(flecs::entity board_entity)
std::weak_ptr< NetworkManager > network_manager
flecs::world ecs
glm::vec2 computeMarkerDrawSize_ARFit(const TextureComponent &tex, float basePx, float scale)
void setCurrentTool(Tool newTool)
void resnapAllMarkersToNearest(const Grid &grid)
flecs::entity createFogOfWar(glm::vec2 startPos, glm::vec2 size)
flecs::entity getActiveBoard() const
void killIfMouseUp(bool isMouseDown)
bool isDraggingMarker(bool local_drag_only=true)
flecs::entity createMarker(const std::string &imageFilePath, GLuint textureId, glm::vec2 position, glm::vec2 size)
flecs::entity active_board
std::shared_ptr< IdentityManager > identity_manager
bool shouldSendMarkerMove(uint64_t markerId) const
void loadActiveBoard(const std::string &filePath)
void handleFogCreation(glm::vec2 end_world_position)
void handleMarkerDragging(glm::vec2 mousePos)
std::shared_ptr< DirectoryWindow > map_directory
glm::vec2 mouse_start_screen_pos
void panBoard(glm::vec2 currentMousePos)
flecs::entity createBoard(std::string board_name, std::string map_image_path, GLuint texture_id, glm::vec2 size)
void setZoom(float newZoomLevel)
glm::mat4 getViewMatrix() const
glm::mat4 getProjectionMatrix() const
void setPosition(glm::vec2 newPosition)
float getZoom() const
glm::vec2 worldToScreenPosition(glm::vec2 world_position) const
void pan(glm::vec2 delta)
static Logger & instance()
Definition Logger.h:39
static void serializeBoardEntity(std::vector< unsigned char > &buffer, const flecs::entity entity, flecs::world &ecs)
Definition Serializer.h:192
static flecs::entity deserializeBoardEntity(const std::vector< unsigned char > &buffer, size_t &offset, flecs::world &ecs)
Definition Serializer.h:244
bool snap_to_grid
Definition Components.h:68
glm::vec2 offset
Definition Components.h:65
float cell_size
Definition Components.h:66
uint64_t id
Definition Components.h:14
bool isPanning
Definition Components.h:59
float y
Definition Components.h:21
float x
Definition Components.h:20