RunicVTT Open Source Virtual Tabletop for TTRPG using P2P
Loading...
Searching...
No Matches
GameTableManager Class Reference

#include <GameTableManager.h>

Inheritance diagram for GameTableManager:
Collaboration diagram for GameTableManager:

Public Member Functions

 GameTableManager (flecs::world ecs, std::shared_ptr< DirectoryWindow > map_directory, std::shared_ptr< DirectoryWindow > marker_directory)
 
 ~GameTableManager ()
 
void saveGameTable ()
 
void loadGameTable (std::filesystem::path game_table_file_path)
 
void setCameraFboDimensions (glm::vec2 fbo_dimensions)
 
bool isBoardActive ()
 
bool isGameTableActive ()
 
void setup ()
 
bool isConnected () const
 
void processReceivedMessages ()
 
void hostGameTablePopUp ()
 
void networkCenterPopUp ()
 
void renderNetworkCenterPlayer ()
 
void renderNetworkCenterGM ()
 
void connectToGameTablePopUp ()
 
void closeGameTablePopUp ()
 
void manageGameTablesPopUp ()
 
void renderUsernameChangePopup ()
 
void createBoardPopUp ()
 
void closeBoardPopUp ()
 
void saveBoardPopUp ()
 
void loadBoardPopUp ()
 
void guidePopUp ()
 
void aboutPopUp ()
 
void render (VertexArray &va, IndexBuffer &ib, Shader &shader, Shader &grid_shader, Renderer &renderer)
 
void handleInputs (glm::vec2 current_mouse_fbo_pos)
 
void handleCursorInputs ()
 
void handleMouseButtonInputs ()
 
void handleScrollInputs ()
 
void createGameTableFile (flecs::entity game_table)
 
std::vector< std::string > listBoardFiles ()
 
std::vector< std::string > listGameTableFiles ()
 
void setCameraWindowSizePos (glm::vec2 window_size, glm::vec2 window_pos)
 
void setActiveGameTableEntity (flecs::entity e)
 
flecs::entity getActiveGameTableEntity () const
 
void setToaster (std::shared_ptr< ImGuiToaster > t)
 
void pushStatusToast (const std::string &msg, ImGuiToaster::Level lvl, float durationSec=5.0f)
 
void processMouseInput (bool is_mouse_within_image_bounds, bool is_map_window_hovered)
 
void UI_LabelValue (const char *label, const std::string &value)
 
bool UI_CopyButtonWithToast (const char *btnId, const std::string &toCopy, const char *toastId, float seconds=1.5f)
 
void UI_TransientLine (const char *key, bool trigger, const ImVec4 &color, const char *text, float seconds=2.0f)
 
bool UI_RenderPortAndPassword (char *portBuf, size_t portBufSize, char *passBuf, size_t passBufSize)
 
bool UI_ConfirmModal (const char *popupId, const char *title, const char *text)
 

Public Attributes

std::shared_ptr< IdentityManageridentity_manager
 
std::string game_table_name
 
std::shared_ptr< ChatManagerchat_manager
 
std::shared_ptr< DirectoryWindowmap_directory
 
std::shared_ptr< NetworkManagernetwork_manager
 
std::shared_ptr< BoardManagerboard_manager
 
flecs::entity active_game_table = flecs::entity()
 

Private Member Functions

void handleMouseButtons (glm::vec2 current_mouse_fbo_pixels_bl_origin, int fbo_height)
 
void handleCursorMovement (glm::vec2 current_mouse_fbo_pixels_bl_origin)
 
void handleScroll (glm::vec2 current_mouse_fbo_pixels_bl_origin)
 

Static Private Member Functions

static flecs::entity findMarkerInBoard (flecs::entity boardEnt, uint64_t markerId)
 
static uint64_t nowMs ()
 

Private Attributes

std::shared_ptr< ImGuiToastertoaster_
 
flecs::world ecs
 
glm::vec2 current_mouse_pos
 
glm::vec2 current_mouse_ndc_pos
 
glm::vec2 current_mouse_world_pos
 
glm::vec2 current_mouse_fbo_pos
 
char buffer [124] = ""
 
char pass_buffer [124] = ""
 
char port_buffer [6] = "7777"
 
char username_buffer [124] = ""
 
std::string map_image_path = ""
 
bool mouse_left_clicked
 
bool mouse_right_clicked
 
bool mouse_middle_clicked
 
bool mouse_left_released
 
bool mouse_right_released
 
bool mouse_middle_released
 
float mouse_wheel_delta
 
bool ignore_mouse_until_release = false
 
bool is_non_map_window_hovered = false
 

Detailed Description

Definition at line 12 of file GameTableManager.h.

Constructor & Destructor Documentation

◆ GameTableManager()

GameTableManager::GameTableManager ( flecs::world ecs,
std::shared_ptr< DirectoryWindow > map_directory,
std::shared_ptr< DirectoryWindow > marker_directory )

Definition at line 9 of file GameTableManager.cpp.

9 :
10 ecs(ecs), identity_manager(std::make_shared<IdentityManager>()), network_manager(std::make_shared<NetworkManager>(ecs, identity_manager)), map_directory(map_directory), board_manager(std::make_shared<BoardManager>(ecs, network_manager, identity_manager, map_directory, marker_directory))
11{
12 identity_manager->loadMyIdentityFromFile();
13 identity_manager->loadAddressBookFromFile();
14 if (identity_manager->myUniqueId().empty())
15 {
16 // any stable generator you like — here is a tiny one-liner fallback:
17 const std::string newUid = std::to_string(std::random_device{}()) + "-" + std::to_string(std::random_device{}());
18 identity_manager->setMyIdentity(newUid, "Player");
19 }
20 chat_manager = std::make_shared<ChatManager>(network_manager, identity_manager);
21
22 std::filesystem::path map_directory_path = PathManager::getMapsPath();
23 map_directory->directoryName = "MapDiretory";
24 map_directory->directoryPath = map_directory_path.string();
25 map_directory->startMonitoring();
26 map_directory->generateTextureIDs();
27}
std::shared_ptr< IdentityManager > identity_manager
std::shared_ptr< NetworkManager > network_manager
std::shared_ptr< DirectoryWindow > map_directory
std::shared_ptr< BoardManager > board_manager
std::shared_ptr< ChatManager > chat_manager
static fs::path getMapsPath()
Definition PathManager.h:61
Here is the call graph for this function:

◆ ~GameTableManager()

GameTableManager::~GameTableManager ( )

Definition at line 34 of file GameTableManager.cpp.

35{
36}

Member Function Documentation

◆ aboutPopUp()

void GameTableManager::aboutPopUp ( )

Definition at line 1946 of file GameTableManager.cpp.

1947{
1948 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
1949 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1950
1951 if (ImGui::BeginPopupModal("About", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
1952 {
1953 const char* appName = "RunicVTT";
1954 const char* version = "0.0.1";
1955 const char* author = "Pedro Vicente dos Santos";
1956 const char* yearRange = "2025";
1957 const char* repoUrl = "https://github.com/PedroVicente98/RunicVTT";
1958
1959 ImGui::Text("%s v%s", appName, version);
1960 ImGui::Separator();
1961
1962 ImGui::TextWrapped(
1963 "RunicVTT is a virtual tabletop focused on fast peer-to-peer play using WebRTC data channels. "
1964 "It’s built with C++ and integrates ImGui, GLFW, GLEW, and Flecs.");
1965
1966 ImGui::NewLine();
1967 ImGui::Text("Author: %s", author);
1968 ImGui::Text("Year: %s", yearRange);
1969
1970 ImGui::NewLine();
1971 ImGui::TextUnformatted("GitHub:");
1972 ImGui::SameLine();
1973 ImGui::TextColored(ImVec4(0.4f, 0.7f, 1.0f, 1.0f), "%s", repoUrl);
1974 ImGui::SameLine();
1975 if (ImGui::SmallButton("Copy URL"))
1976 {
1977 ImGui::SetClipboardText(repoUrl);
1978 }
1979
1980 // If you later want a button to open the browser on Windows:
1981 // if (ImGui::SmallButton("Open in Browser")) {
1982 // #ifdef _WIN32
1983 // ShellExecuteA(nullptr, "open", repoUrl, nullptr, nullptr, SW_SHOWNORMAL);
1984 // #endif
1985 // }
1986
1987 ImGui::Separator();
1988 if (ImGui::Button("Close"))
1989 ImGui::CloseCurrentPopup();
1990
1991 ImGui::EndPopup();
1992 }
1993}

◆ closeBoardPopUp()

void GameTableManager::closeBoardPopUp ( )

Definition at line 724 of file GameTableManager.cpp.

725{
726 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
727 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
728 if (ImGui::BeginPopupModal("CloseBoard", 0, ImGuiWindowFlags_AlwaysAutoResize))
729 {
730 ImGui::Text("Close Current GameTable?? Any unsaved changes will be lost!!");
731 if (ImGui::Button("Close"))
732 {
733 board_manager->closeBoard();
734 ImGui::CloseCurrentPopup();
735 }
736
737 ImGui::SameLine();
738
739 if (ImGui::Button("Cancel"))
740 {
741 ImGui::CloseCurrentPopup();
742 }
743 ImGui::EndPopup();
744 }
745}

◆ closeGameTablePopUp()

void GameTableManager::closeGameTablePopUp ( )

Definition at line 815 of file GameTableManager.cpp.

816{
817 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
818 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
819 if (ImGui::BeginPopupModal("CloseGameTable", 0, ImGuiWindowFlags_AlwaysAutoResize))
820 {
821 ImGui::Text("Close Current GameTable?? Any unsaved changes will be lost!!");
822 if (ImGui::Button("Close"))
823 {
824 board_manager->closeBoard();
825 active_game_table = flecs::entity();
826 network_manager->closeServer();
827 chat_manager->saveCurrent();
828 ImGui::CloseCurrentPopup();
829 }
830
831 ImGui::SameLine();
832
833 if (ImGui::Button("Cancel"))
834 {
835 ImGui::CloseCurrentPopup();
836 }
837 ImGui::EndPopup();
838 }
839}
flecs::entity active_game_table

◆ connectToGameTablePopUp()

void GameTableManager::connectToGameTablePopUp ( )

Definition at line 841 of file GameTableManager.cpp.

842{
843 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
844 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
845
846 if (ImGui::BeginPopupModal("ConnectToGameTable", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
847 {
848 ImGui::SetItemDefaultFocus();
849
850 // Username
851 ImGui::TextUnformatted("Enter your username and the connection string you received from the host.");
852 ImGui::InputText("Username", username_buffer, sizeof(username_buffer));
854
855 ImGui::Separator();
856
857 // Connection string
858 // Expected formats:
859 // runic:https://sub.loca.lt?PASSWORD
860 // runic:wss://host[:port/path]?PASSWORD
861 // runic:<host>:<port>?PASSWORD
862 ImGui::InputText("Connection String", buffer, sizeof(buffer), ImGuiInputTextFlags_AutoSelectAll);
864 // Small helper row
865 ImGui::BeginDisabled(true);
866 ImGui::InputText("Example", (char*)"runic:https://xyz.loca.lt?mypwd", 64);
868 ImGui::EndDisabled();
869
870 ImGui::Separator();
871
872 // UX actions
873 bool connectFailed = false;
874
875 if (ImGui::Button("Connect") && buffer[0] != '\0')
876 {
877 // set username (id will be assigned after auth)
878 auto my_unique = identity_manager->myUniqueId();
879 identity_manager->setMyIdentity(my_unique, username_buffer);
880
881 // this already accepts LocalTunnel URLs or host:port (parseConnectionString handles both)
882 if (network_manager->connectToPeer(buffer))
883 {
884 // cleanup & close
885 memset(buffer, 0, sizeof(buffer));
886 memset(username_buffer, 0, sizeof(username_buffer));
887 ImGui::CloseCurrentPopup();
888 }
889 else
890 {
891 connectFailed = true;
892 }
893 }
894
895 ImGui::SameLine();
896 if (ImGui::Button("Close"))
897 {
898 ImGui::CloseCurrentPopup();
899 memset(buffer, 0, sizeof(buffer));
900 }
901
902 // transient error (uses your helper)
903 UI_TransientLine("conn-fail",
904 connectFailed,
905 ImVec4(1.f, 0.f, 0.f, 1.f),
906 "Failed to Connect! Check your connection string and reachability.",
907 2.5f);
908
909 // Helpful hint (host-side details)
910 ImGui::Separator();
911 ImGui::TextDisabled("Tip: The host chooses the network mode while hosting.\n"
912 "LocalTunnel URLs may take a few seconds to become available.");
913
914 ImGui::EndPopup();
915 }
916}
void UI_TransientLine(const char *key, bool trigger, const ImVec4 &color, const char *text, float seconds=2.0f)
void TrackThisInput()
Here is the call graph for this function:

◆ createBoardPopUp()

void GameTableManager::createBoardPopUp ( )

Definition at line 648 of file GameTableManager.cpp.

649{
650 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
651 ImGui::SetNextWindowSizeConstraints(ImVec2(800, 600), ImVec2(FLT_MAX, FLT_MAX));
652 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
653 if (ImGui::BeginPopupModal("CreateBoard"))
654 {
655 ImGui::SetItemDefaultFocus();
656 ImGuiID dockspace_id = ImGui::GetID("CreateBoardDockspace");
657
658 if (ImGui::DockBuilderGetNode(dockspace_id) == 0)
659 {
660 ImGui::DockBuilderRemoveNode(dockspace_id);
661 ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace | ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoUndocking);
662
663 ImGui::DockBuilderDockWindow("MapDiretory", dockspace_id);
664 ImGui::DockBuilderFinish(dockspace_id);
665 }
666
667 ImGui::Columns(2, nullptr, false);
668
669 ImGui::Text("Create a new board");
670
671 ImGui::InputText("Board Name", buffer, sizeof(buffer));
673 std::string board_name(buffer);
674
675 ImGui::NewLine();
676
677 DirectoryWindow::ImageData selectedImage = map_directory->getSelectedImage();
678
679 if (!selectedImage.filename.empty())
680 {
681 ImGui::Text("Selected Map: %s", selectedImage.filename.c_str());
682 ImGui::Image((void*)(intptr_t)selectedImage.textureID, ImVec2(256, 256));
683 }
684 else
685 {
686 ImGui::Text("No map selected. Please select a map.");
687 }
688
689 ImGui::NewLine();
690
691 bool saved = false;
692 if (ImGui::Button("Save") && !selectedImage.filename.empty() && buffer[0] != '\0')
693 {
694 auto board = board_manager->createBoard(board_name, selectedImage.filename, selectedImage.textureID, selectedImage.size);
695 board.add(flecs::ChildOf, active_game_table);
696 map_directory->clearSelectedImage();
697 memset(buffer, 0, sizeof(buffer));
698
699 network_manager->broadcastBoard(board_manager->getActiveBoard());
700 saved = true;
701 ImGui::CloseCurrentPopup();
702 }
703
704 ImGui::SameLine();
705
706 if (ImGui::Button("Close"))
707 {
708 map_directory->clearSelectedImage();
709 ImGui::CloseCurrentPopup();
710 }
711
712 ImGui::NextColumn();
713 ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_PassthruCentralNode);
714 map_directory->renderDirectory();
715
716 ImGui::Columns(1);
717 // Optionally show transient "Saved!"
718 UI_TransientLine("board-saved", saved, ImVec4(0.4f, 1.f, 0.4f, 1.f), "Saved!", 1.5f);
719
720 ImGui::EndPopup();
721 }
722}
Here is the call graph for this function:

◆ createGameTableFile()

void GameTableManager::createGameTableFile ( flecs::entity game_table)

Definition at line 561 of file GameTableManager.cpp.

562{
563 namespace fs = std::filesystem;
564 auto game_tables_directory = PathManager::getGameTablesPath();
565 auto active_game_table_folder = game_tables_directory / game_table_name;
566 if (!fs::exists(active_game_table_folder) && !fs::is_directory(active_game_table_folder))
567 {
568 std::filesystem::create_directory(active_game_table_folder);
569 }
570
571 std::vector<unsigned char> buffer;
573
574 auto game_table_file = active_game_table_folder / (game_table_name + ".runic");
575 std::ofstream file(game_table_file.string(), std::ios::binary);
576 if (file.is_open())
577 {
578 file.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
579 file.close();
580 }
581 else
582 {
583 std::cerr << "Failed to open the file." << std::endl;
584 }
585}
std::string game_table_name
static fs::path getGameTablesPath()
Definition PathManager.h:76
static void serializeGameTableEntity(std::vector< unsigned char > &buffer, const flecs::entity entity, flecs::world &ecs)
Definition Serializer.h:150
Here is the call graph for this function:
Here is the caller graph for this function:

◆ findMarkerInBoard()

static flecs::entity GameTableManager::findMarkerInBoard ( flecs::entity boardEnt,
uint64_t markerId )
inlinestaticprivate

Definition at line 261 of file GameTableManager.h.

262 {
263 flecs::entity out;
264 boardEnt.children([&](flecs::entity child)
265 {
266 if (!child.has<MarkerComponent>()) return;
267 if (auto id = child.get<Identifier>(); id && id->id == markerId)
268 out = child; });
269 return out;
270 }
Here is the caller graph for this function:

◆ getActiveGameTableEntity()

flecs::entity GameTableManager::getActiveGameTableEntity ( ) const
inline

Definition at line 73 of file GameTableManager.h.

74 {
75 return active_game_table;
76 }

◆ guidePopUp()

void GameTableManager::guidePopUp ( )

Definition at line 1716 of file GameTableManager.cpp.

1717{
1718 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
1719 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1720
1721 if (ImGui::BeginPopupModal("Guide", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
1722 {
1723 // --- state ---
1724 static int section = 0;
1725 static const char* kSections[] = {
1726 "Getting Started",
1727 "Connecting to a Game",
1728 "Game Tables",
1729 "Boards",
1730 "Toolbar & Interface",
1731 "Markers",
1732 "Fog of War",
1733 "Networking & Security",
1734 "Known Issues",
1735 "Appendix"};
1736
1737 // quick helper
1738 auto Para = [](const char* s)
1739 {
1740 ImGui::TextWrapped("%s", s);
1741 ImGui::Dummy(ImVec2(0, 4));
1742 };
1743
1744 ImGui::TextUnformatted("RunicVTT Guide");
1745 ImGui::Separator();
1746
1747 // main area (Left nav + Right content)
1748 ImVec2 full = ImVec2(860, 520);
1749 ImGui::BeginChild("GuideContent", full, true);
1750
1751 // Left: nav
1752 ImGui::BeginChild("GuideNav", ImVec2(240, 0), true);
1753 for (int i = 0; i < IM_ARRAYSIZE(kSections); ++i)
1754 {
1755 if (ImGui::Selectable(kSections[i], section == i))
1756 section = i;
1757 }
1758 ImGui::EndChild();
1759
1760 ImGui::SameLine();
1761
1762 // Right: body (scroll)
1763 ImGui::BeginChild("GuideBody", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar);
1764
1765 switch (section)
1766 {
1767 case 0: // Getting Started
1768 {
1769 ImGui::SeparatorText("Overview");
1770 Para("RunicVTT is a virtual tabletop for sharing boards, markers, and fog of war in real-time across peers.");
1771
1772 ImGui::SeparatorText("Basic Flow");
1773 Para("Create or load a Game Table -> Host or Join -> Add a Board -> Place Markers / Fog -> Play.");
1774
1775 ImGui::SeparatorText("Requirements");
1776 Para("- Windows 10/11 recommended.\n"
1777 "- Internet connection for WAN.\n"
1778 "- Allow the app in your firewall/antivirus.\n"
1779 "- Read/write access for assets (Boards/Markers folders).");
1780
1781 ImGui::SeparatorText("Terminology");
1782 Para("- Game Table: a saved session containing chat and world state.\n"
1783 "- Board: a map image displayed to all players.\n"
1784 "- Marker: a token/object placed on a board.\n"
1785 "- Fog: an overlay hiding/revealing areas.\n"
1786 "- Peer: a connected client (GM or Player).");
1787 break;
1788 }
1789
1790 case 1: // Connecting to a Game
1791 {
1792 ImGui::SeparatorText("Connection String");
1793 Para("A connection string identifies the host session. Example formats:\n"
1794 "- https://runic-<yourLocalIp>.loca.lt?PASSWORD\n"
1795 "- runic:<host>:<port>?PASSWORD");
1796
1797 ImGui::SeparatorText("Hosting");
1798 Para("Choose a connection mode: LocalTunnel (public URL), Local (LAN), or External (WAN with port forwarding). "
1799 "Start hosting and copy the connection string from the Network Center.");
1800
1801 ImGui::SeparatorText("Joining");
1802 Para("Ask the host for a connection string and password, then use 'Connect to GameTable' and paste it.");
1803
1804 ImGui::SeparatorText("Troubleshooting");
1805 Para("- If connection closes over WAN: ensure firewall allows the app, port was fowarded manually or via UPnP and the host is reachable. \n"
1806 "- If connection closes over LAN: ensure firewall allows the app, and the moldem dont block local connections. \n"
1807 "- If connection closes over LocalTunnel: ensure firewall allows the app, and the generated URL is in the correct format https://runic-<YOURLOCALIP>.loca.lt. if not in the formar generate new by hosting again \n"
1808 "- Make sure you are connected to a Wifi or Ethernet moldem connection, 4G/5G mobile network arent supported due to their complex NAT protection. \n"
1809 "- Corporate networks or strict NAT may require TURN/relay.");
1810 break;
1811 }
1812
1813 case 2: // Game Tables
1814 {
1815 ImGui::SeparatorText("Create a Game Table");
1816 Para("Open 'Host GameTable' -> Create tab -> set name/username/password/port -> choose mode -> Host.");
1817
1818 ImGui::SeparatorText("Load a Game Table");
1819 Para("Open 'Host GameTable' -> Load tab -> select a saved table -> set credentials/port -> Host.");
1820
1821 ImGui::SeparatorText("Lifecycle");
1822 Para("Networking is tied to the Game Table. Closing it stops all connections. "
1823 "Chat and boards are saved per table.");
1824 break;
1825 }
1826
1827 case 3: // Boards
1828 {
1829 ImGui::SeparatorText("Create a Board");
1830 Para("Use 'Add Board' or board toolbar -> choose an image (PNG/JPG). The image is shared to peers.");
1831
1832 ImGui::SeparatorText("Edit Board");
1833 Para("Adjust size/scale, toggle grid, panning/zoom. Visibility affects whether players see it fully.");
1834
1835 ImGui::SeparatorText("Networking Notes");
1836 Para("Large images are chunked and sent reliably. Very large files transfer but take longer.");
1837 break;
1838 }
1839
1840 case 4: // Toolbar & Interface
1841 {
1842 ImGui::SeparatorText("Toolbar Overview");
1843 Para("- Move Tool: pan map or drag owned markers.\n"
1844 "- Fog Tool: create fog areas.\n"
1845 "- Edit/Delete: open edit window or remove entities.\n"
1846 "- Zoom/Pan: mouse wheel and drag (when panning).\n"
1847 "- Grid: open grid window to configure it.\n"
1848 "- Camera: open camera window to configure it.\n");
1849
1850 ImGui::SeparatorText("Windows & Panels");
1851 Para("- Chat Window: General chat + dice roller (/roll).\n"
1852 "- Edit Window: per-entity size/visibility/ownership.\n"
1853 "- Grid Window: per-board grid cell size/offset/visibility/snap to grid.\n"
1854 "- Camera Window: per-board camera zoom via button and sliders and reset.\n"
1855 "- Host Window: create or load gametable with credentials and port, start network and sets active gametable.\n"
1856 "- Connect Window: connect to hosted gametable, connection string and credential.\n"
1857 "- Network Center: peers, connection strings, status.");
1858
1859 break;
1860 }
1861
1862 case 5: // Fog of War
1863 {
1864 ImGui::SeparatorText("Create Fog");
1865 Para("Use the Fog tool to add opaque overlays to hide areas from players.");
1866
1867 ImGui::SeparatorText("Edit/Remove");
1868 Para("Move/resize fog areas or delete them. Fog updates are synchronized to all peers.");
1869
1870 ImGui::SeparatorText("Authority");
1871 Para("Fog is GM-controlled; players do not send fog updates.");
1872 break;
1873 }
1874
1875 case 6: // Markers
1876 {
1877 ImGui::SeparatorText("Create Markers");
1878 Para("Use the Marker directory to place tokens. Drag markers to the board from the Markers directory window.");
1879
1880 ImGui::SeparatorText("Edit & Ownership");
1881 Para("Edit window lets the GM set: owner peer ID, allow-all-players move, and locked state. \n"
1882 "Players can only move owned/unlocked markers; the GM can always move.");
1883
1884 ImGui::SeparatorText("Movement");
1885 Para("Drag markers to move. Updates are broadcast at a limited rate to reduce spam.");
1886 break;
1887 }
1888
1889 case 7: // Networking & Security
1890 {
1891 ImGui::SeparatorText("How It Works");
1892 Para("Peer-to-peer data channels synchronize boards, markers, fog, and chat.");
1893
1894 ImGui::SeparatorText("Firewall / Antivirus");
1895 Para("Allow the executable on first run. Some AVs may slow initial connection.");
1896
1897 ImGui::SeparatorText("Quality & Reliability");
1898 Para("Images are sent on reliable channels.");
1899 break;
1900 }
1901
1902 case 8: // Known Issues
1903 {
1904 ImGui::SeparatorText("Limitations");
1905 Para("- Very large images transfer slowly.\n"
1906 "- Network Over the 4G/5G doesnt work due to NAT restrictions. \n"
1907 "- Network do not work on Mobile Internet of any form(USB Anchoring, Wifi Hotspot, Ethernet and Bluetooth). ");
1908
1909 ImGui::SeparatorText("Troubleshooting");
1910 Para("If desync occurs, rejoin the session or have the host re-broadcast state via Game Table snapshot.");
1911
1912 ImGui::SeparatorText("Reporting Bugs");
1913 Para("Collect logs from the log window/folder and describe steps to reproduce.");
1914 break;
1915 }
1916
1917 case 9: // Appendix
1918 {
1919 ImGui::SeparatorText("File Paths");
1920 Para("- GameTables folder: <root path>/GameTables/ \n"
1921 "- Boards folder: <GameTableFolder>/<GameTableName>/Boards/ \n"
1922 "- Maps folder: <root path>/Maps/ \n"
1923 "- Marker folder: <root path>/Marker/.");
1924
1925 ImGui::SeparatorText("Glossary");
1926 Para("- GM: Game Master (host/authority).\n"
1927 "- Player: peer participant.\n"
1928 "- Peer: a connected client.");
1929
1930 break;
1931 }
1932 }
1933
1934 ImGui::EndChild(); // GuideBody
1935 ImGui::EndChild(); // GuideContent
1936
1937 ImGui::Separator();
1938 if (ImGui::Button("Close"))
1939 ImGui::CloseCurrentPopup();
1940
1941 ImGui::EndPopup();
1942 }
1943}

◆ handleCursorInputs()

void GameTableManager::handleCursorInputs ( )

Definition at line 526 of file GameTableManager.cpp.

527{
528 if (!isBoardActive())
529 return;
530
531 if (board_manager->isDraggingMarker())
532 {
533 board_manager->handleMarkerDragging(current_mouse_world_pos);
534 }
535
536 if (board_manager->isPanning())
537 {
539 }
540}
glm::vec2 current_mouse_world_pos
glm::vec2 current_mouse_fbo_pos
Here is the call graph for this function:
Here is the caller graph for this function:

◆ handleCursorMovement()

void GameTableManager::handleCursorMovement ( glm::vec2 current_mouse_fbo_pixels_bl_origin)
private

◆ handleInputs()

void GameTableManager::handleInputs ( glm::vec2 current_mouse_fbo_pos)

Definition at line 464 of file GameTableManager.cpp.

465{
466 current_mouse_world_pos = board_manager->camera.screenToWorldPosition(current_mouse_fbo_pos);
468
469 // Call individual handlers
471 handleCursorInputs(); // This will primarily deal with dragging/panning
473}
Here is the call graph for this function:

◆ handleMouseButtonInputs()

void GameTableManager::handleMouseButtonInputs ( )

Definition at line 475 of file GameTableManager.cpp.

476{
477 // Check if board is active first
478 if (!isBoardActive())
479 return;
480
481 // Left Mouse Button Press
483 {
484 if (board_manager->getCurrentTool() == Tool::MOVE)
485 {
486 if (board_manager->isMouseOverMarker(current_mouse_world_pos) /*and !board_manager->isDraggingMarker()*/)
487 {
488 board_manager->startMouseDrag(current_mouse_world_pos, false); // Drag Marker
489 }
490 else /*if(!board_manager->isPanning())*/
491 {
492 board_manager->startMouseDrag(current_mouse_world_pos, true); // Pan Board
493 }
494 }
495 if (board_manager->getCurrentTool() == Tool::FOG and !board_manager->isCreatingFog())
496 {
497 board_manager->startMouseDrag(current_mouse_world_pos, false); // Start fog drawing/erasing
498 }
499 if (board_manager->getCurrentTool() == Tool::SELECT)
500 {
501 auto entity = board_manager->getEntityAtMousePosition(current_mouse_world_pos);
502 if (entity.is_valid())
503 {
504 board_manager->setShowEditWindow(true, entity);
505 }
506 }
507 }
508
509 // Left Mouse Button Release
510 if (mouse_left_released || ImGui::IsMouseReleased(ImGuiMouseButton_Left))
511 {
512 if (board_manager->isPanning() || board_manager->isDraggingMarker())
513 {
514 board_manager->endMouseDrag();
515 }
516 if (board_manager->isCreatingFog())
517 {
518 board_manager->handleFogCreation(current_mouse_world_pos); // Use world_pos
519 board_manager->endMouseDrag();
520 }
521 }
522
523 board_manager->killIfMouseUp(ImGui::IsMouseDown(ImGuiMouseButton_Left));
524}
@ SELECT
Here is the call graph for this function:
Here is the caller graph for this function:

◆ handleMouseButtons()

void GameTableManager::handleMouseButtons ( glm::vec2 current_mouse_fbo_pixels_bl_origin,
int fbo_height )
private

◆ handleScroll()

void GameTableManager::handleScroll ( glm::vec2 current_mouse_fbo_pixels_bl_origin)
private

◆ handleScrollInputs()

void GameTableManager::handleScrollInputs ( )

Definition at line 542 of file GameTableManager.cpp.

543{
544 if (!isBoardActive())
545 return;
546 if (mouse_wheel_delta != 0.0f)
547 {
548 float zoom_factor;
549 if (mouse_wheel_delta > 0)
550 {
551 zoom_factor = 1.1f; // Zoom in by 10%
552 }
553 else
554 {
555 zoom_factor = 0.9f; // Zoom out by 10%
556 }
557 board_manager->camera.zoom(zoom_factor, current_mouse_world_pos);
558 }
559}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ hostGameTablePopUp()

void GameTableManager::hostGameTablePopUp ( )

Definition at line 1202 of file GameTableManager.cpp.

1203{
1205 static bool tryUpnp = false; // only used for EXTERNAL
1206 static char custom_host_buf[64] = "";
1207
1208 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
1209 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1210
1211 if (ImGui::BeginPopupModal("Host GameTable", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
1212 {
1213
1214 if (ImGui::BeginTabBar("HostTabs"))
1215 {
1216
1217 // ----------------------- CREATE TAB -----------------------
1218 if (ImGui::BeginTabItem("Create"))
1219 {
1220
1221 ImGui::InputText("GameTable Name", buffer, sizeof(buffer));
1223 ImGui::InputText("Username", username_buffer, sizeof(username_buffer));
1225 ImGui::InputText("Password", pass_buffer, sizeof(pass_buffer), ImGuiInputTextFlags_Password);
1227 ImGui::InputText("Port", port_buffer, sizeof(port_buffer),
1228 ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsNoBlank);
1230 // Mode selection
1231 ImGui::Separator();
1232 /*ImGui::TextUnformatted("Connection Mode:");
1233 int m = (hostMode == ConnectionType::LOCALTUNNEL ? 0 : hostMode == ConnectionType::LOCAL ? 1
1234 : 2);
1235 if (ImGui::RadioButton("LocalTunnel", m == 0))
1236 m = 0;
1237 ImGui::SameLine();
1238 if (ImGui::RadioButton("Local (LAN)", m == 1))
1239 m = 1;
1240 ImGui::SameLine();
1241 if (ImGui::RadioButton("External (Internet)", m == 2))
1242 m = 2;
1243 hostMode = (m == 0 ? ConnectionType::LOCALTUNNEL : m == 1 ? ConnectionType::LOCAL
1244 : ConnectionType::EXTERNAL);
1245 */
1246 ImGui::TextUnformatted("Connection Mode:");
1247 int m = (hostMode == ConnectionType::LOCALTUNNEL ? 0 : hostMode == ConnectionType::LOCAL ? 1
1248 : hostMode == ConnectionType::EXTERNAL ? 2
1249 : 3);
1250 if (ImGui::RadioButton("LocalTunnel", m == 0))
1251 m = 0;
1252 ImGui::SameLine();
1253 if (ImGui::RadioButton("Local (LAN)", m == 1))
1254 m = 1;
1255 ImGui::SameLine();
1256 if (ImGui::RadioButton("External (Internet)", m == 2))
1257 m = 2;
1258 ImGui::SameLine();
1259 if (ImGui::RadioButton("Custom IP (Overlay/Other)", m == 3))
1260 m = 3;
1261
1263
1264 // Explanations
1265 if (hostMode == ConnectionType::LOCALTUNNEL)
1266 {
1267 ImGui::TextDisabled("LocalTunnel: exposes your local server via a public URL.\n"
1268 "The URL becomes available a few seconds after the server starts.\n"
1269 "You can copy the connection string from Network Center.");
1270 tryUpnp = false;
1271 }
1272 else if (hostMode == ConnectionType::LOCAL)
1273 {
1274 ImGui::TextDisabled("Local (LAN): works only on the same local network.\n"
1275 "Share your LAN IP + port with players.");
1276 tryUpnp = false;
1277 }
1278 else if (hostMode == ConnectionType::EXTERNAL)
1279 {
1280 ImGui::TextDisabled("External: reachable from the Internet.\n"
1281 "Requires a public IP and port forwarding on your router (UPnP or manual).\n"
1282 "It might not work depending on your network.");
1283 ImGui::Checkbox("Try UPnP (auto port forward)", &tryUpnp);
1284 }
1285 else
1286 { // CUSTOM
1287 ImGui::TextDisabled("Custom IP (Overlay/Other): share a specific address (e.g. overlay adapter IP).\n"
1288 "Server still binds 0.0.0.0; this only builds a copyable connection string.");
1289 ImGui::InputText("Custom Host/IP", custom_host_buf, sizeof(custom_host_buf));
1290 tryUpnp = false;
1291 }
1292
1293 ImGui::Separator();
1294
1295 const bool valid = buffer[0] != '\0' && port_buffer[0] != '\0';
1296 if (!valid)
1297 {
1298 ImGui::TextColored(ImVec4(1, 0.6f, 0.2f, 1), "Name and Port are required.");
1299 }
1300
1301 if (ImGui::Button("Create & Host") && valid)
1302 {
1303 // Close whatever is running
1304 board_manager->closeBoard();
1305 active_game_table = flecs::entity();
1306 network_manager->closeServer();
1307
1308 // Create GT entity + file
1310 auto identifier = Identifier{board_manager->generateUniqueId()};
1311 auto game_table = ecs.entity("GameTable").set(identifier).set(GameTable{game_table_name});
1312 active_game_table = game_table;
1313 chat_manager->setActiveGameTable(identifier.id, game_table_name);
1314 createGameTableFile(game_table);
1315
1316 // Identity + network
1317 auto my_unique = identity_manager->myUniqueId();
1318 identity_manager->setMyIdentity(my_unique, username_buffer);
1319 network_manager->setCustomHost(custom_host_buf);
1320 network_manager->setNetworkPassword(pass_buffer);
1321 const unsigned p = static_cast<unsigned>(atoi(port_buffer));
1322 network_manager->startServer(hostMode, static_cast<unsigned short>(p), tryUpnp);
1323
1324 // cleanup + close
1325 memset(buffer, 0, sizeof(buffer));
1326 memset(username_buffer, 0, sizeof(username_buffer));
1327 memset(pass_buffer, 0, sizeof(pass_buffer));
1328 //memset(port_buffer, 0, sizeof(port_buffer));
1329 tryUpnp = false;
1330
1331 ImGui::CloseCurrentPopup();
1332 }
1333
1334 ImGui::SameLine();
1335 if (ImGui::Button("Cancel"))
1336 {
1337 ImGui::CloseCurrentPopup();
1338 }
1339
1340 ImGui::EndTabItem();
1341 }
1342
1343 // ------------------------ LOAD TAB ------------------------
1344 if (ImGui::BeginTabItem("Load"))
1345 {
1346 ImGui::BeginChild("GTList", ImVec2(260, 360), true);
1347 auto game_tables = listGameTableFiles();
1348 for (auto& file : game_tables)
1349 {
1350 if (ImGui::Selectable(file.c_str()))
1351 {
1352 strncpy(buffer, file.c_str(), sizeof(buffer) - 1);
1353 buffer[sizeof(buffer) - 1] = '\0';
1354 }
1355 }
1356 ImGui::EndChild();
1357
1358 ImGui::SameLine();
1359
1360 ImGui::BeginChild("GTDetails", ImVec2(620, 360), true, ImGuiWindowFlags_AlwaysAutoResize);
1361 ImGui::Text("Selected:");
1362 ImGui::SameLine();
1363 ImGui::TextUnformatted(buffer[0] ? buffer : "(none)");
1364
1365 ImGui::Separator();
1366 ImGui::InputText("Username", username_buffer, sizeof(username_buffer));
1368 ImGui::InputText("Password", pass_buffer, sizeof(pass_buffer), ImGuiInputTextFlags_Password);
1370 ImGui::InputText("Port", port_buffer, sizeof(port_buffer), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsNoBlank);
1372
1373 // Mode
1374 ImGui::Separator();
1375 ImGui::TextUnformatted("Connection Mode:");
1376 int m = (hostMode == ConnectionType::LOCALTUNNEL ? 0 : hostMode == ConnectionType::LOCAL ? 1
1377 : hostMode == ConnectionType::EXTERNAL ? 2
1378 : 3);
1379 if (ImGui::RadioButton("LocalTunnel", m == 0))
1380 m = 0;
1381 ImGui::SameLine();
1382 if (ImGui::RadioButton("Local (LAN)", m == 1))
1383 m = 1;
1384 ImGui::SameLine();
1385 if (ImGui::RadioButton("External (Internet)", m == 2))
1386 m = 2;
1387 ImGui::SameLine();
1388 if (ImGui::RadioButton("Custom IP (Overlay/Other)", m == 3))
1389 m = 3;
1390
1392
1393 if (hostMode == ConnectionType::LOCALTUNNEL)
1394 {
1395 ImGui::TextDisabled("LocalTunnel: URL appears after server starts.\n"
1396 "Copy connection string from Network Center.");
1397 tryUpnp = false;
1398 }
1399 else if (hostMode == ConnectionType::LOCAL)
1400 {
1401 ImGui::TextDisabled("Local (LAN): for players on the same network.");
1402 tryUpnp = false;
1403 }
1404 else if (hostMode == ConnectionType::EXTERNAL)
1405 {
1406 ImGui::TextDisabled("External: reachable from the Internet.\n"
1407 "Requires a public IP and port forwarding on your router (UPnP or manual).\n"
1408 "It might not work depending on your network.");
1409 ImGui::Checkbox("Try UPnP (auto port forward)", &tryUpnp);
1410 }
1411 else
1412 { // CUSTOM
1413 ImGui::TextDisabled("Custom IP (Overlay/Other): share a specific address (e.g. overlay adapter IP).\n"
1414 "Server still binds 0.0.0.0; this only builds a copyable connection string.");
1415 ImGui::InputText("Custom Host/IP", custom_host_buf, sizeof(custom_host_buf));
1416 tryUpnp = false;
1417 }
1418
1419 ImGui::Separator();
1420
1421 const bool valid = buffer[0] != '\0' && port_buffer[0] != '\0';
1422 if (ImGui::Button("Load & Host") && valid)
1423 {
1424 // strip ".runic" → game_table_name
1425 std::string file = buffer;
1426 std::string name = file;
1427 const std::string suffix = ".runic";
1428 if (name.size() >= suffix.size() &&
1429 name.compare(name.size() - suffix.size(), suffix.size(), suffix) == 0)
1430 {
1431 name.resize(name.size() - suffix.size());
1432 }
1433 game_table_name = name;
1434
1435 // close current + load
1436 board_manager->closeBoard();
1437 active_game_table = flecs::entity(); //Clean before load from file
1438 network_manager->closeServer();
1439
1440 std::filesystem::path game_table_file_path =
1441 PathManager::getRootDirectory() / "GameTables" / game_table_name / file;
1442 loadGameTable(game_table_file_path);
1443
1444 // start network
1445 auto my_unique = identity_manager->myUniqueId();
1446 identity_manager->setMyIdentity(my_unique, username_buffer);
1447 network_manager->setNetworkPassword(pass_buffer);
1448 network_manager->setCustomHost(custom_host_buf);
1449 const unsigned p = static_cast<unsigned>(atoi(port_buffer));
1450 network_manager->startServer(hostMode, static_cast<unsigned short>(p), tryUpnp);
1451
1452 // cleanup
1453 memset(buffer, 0, sizeof(buffer));
1454 memset(username_buffer, 0, sizeof(username_buffer));
1455 memset(pass_buffer, 0, sizeof(pass_buffer));
1456 //memset(port_buffer, 0, sizeof(port_buffer));
1457 tryUpnp = false;
1458
1459 ImGui::CloseCurrentPopup();
1460 }
1461
1462 ImGui::SameLine();
1463 if (ImGui::Button("Cancel"))
1464 {
1465 ImGui::CloseCurrentPopup();
1466 }
1467
1468 ImGui::EndChild();
1469 ImGui::EndTabItem();
1470 }
1471
1472 ImGui::EndTabBar();
1473 }
1474
1475 // Note: Share/copyable connection strings live in Network Center (as you prefer)
1476 ImGui::EndPopup();
1477 }
1478}
ConnectionType
Definition Message.h:20
void createGameTableFile(flecs::entity game_table)
std::vector< std::string > listGameTableFiles()
void loadGameTable(std::filesystem::path game_table_file_path)
static fs::path getRootDirectory()
Definition PathManager.h:55
Here is the call graph for this function:

◆ isBoardActive()

bool GameTableManager::isBoardActive ( )

Definition at line 94 of file GameTableManager.cpp.

95{
96 return board_manager->isBoardActive();
97}
Here is the caller graph for this function:

◆ isConnected()

bool GameTableManager::isConnected ( ) const

Definition at line 104 of file GameTableManager.cpp.

105{
106 return network_manager->isConnected();
107}

◆ isGameTableActive()

bool GameTableManager::isGameTableActive ( )

Definition at line 99 of file GameTableManager.cpp.

100{
101 return active_game_table.is_valid();
102}
Here is the caller graph for this function:

◆ listBoardFiles()

std::vector< std::string > GameTableManager::listBoardFiles ( )

Definition at line 588 of file GameTableManager.cpp.

589{
590 namespace fs = std::filesystem;
591 auto game_tables_directory = PathManager::getGameTablesPath();
592 auto active_game_table_folder = game_tables_directory / game_table_name;
593 if (!fs::exists(active_game_table_folder) && !fs::is_directory(active_game_table_folder))
594 {
595 std::filesystem::create_directory(active_game_table_folder);
596 }
597 auto game_table_boards_folder = active_game_table_folder / "Boards";
598
599 if (!fs::exists(game_table_boards_folder) && !fs::is_directory(game_table_boards_folder))
600 {
601 std::filesystem::create_directory(game_table_boards_folder);
602 }
603 std::vector<std::string> boards;
604 for (const auto& entry : std::filesystem::directory_iterator(game_table_boards_folder))
605 {
606 if (entry.is_regular_file())
607 {
608 boards.emplace_back(entry.path().filename().string());
609 }
610 }
611
612 return boards;
613}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ listGameTableFiles()

std::vector< std::string > GameTableManager::listGameTableFiles ( )

Definition at line 615 of file GameTableManager.cpp.

616{
617 namespace fs = std::filesystem;
618 auto game_tables_directory = PathManager::getGameTablesPath();
619
620 // Verifica se o diretório "GameTables" existe
621 if (!fs::exists(game_tables_directory) || !fs::is_directory(game_tables_directory))
622 {
623 std::cerr << "GameTables directory does not exist!" << std::endl;
624 return {};
625 }
626 std::vector<std::string> game_tables;
627 // Itera sobre todos os diretórios dentro de "GameTables"
628 for (const auto& folder : fs::directory_iterator(game_tables_directory))
629 {
630 if (folder.is_directory())
631 {
632 std::string folder_name = folder.path().filename().string();
633 auto runic_file_path = folder.path() / (folder_name + ".runic");
634
635 // Verifica se o arquivo "folder_name.runic" existe dentro da pasta
636 if (fs::exists(runic_file_path) && fs::is_regular_file(runic_file_path))
637 {
638 game_tables.emplace_back(runic_file_path.filename().string()); // Adiciona o nome do arquivo na lista
639 }
640 }
641 }
642
643 return game_tables;
644}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ loadBoardPopUp()

void GameTableManager::loadBoardPopUp ( )

Definition at line 777 of file GameTableManager.cpp.

778{
779 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
780 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
781 if (ImGui::BeginPopupModal("LoadBoard", 0, ImGuiWindowFlags_AlwaysAutoResize))
782 {
783 ImGui::Text("Load Board:");
784
785 ImGui::NewLine();
786 ImGui::Separator();
787
788 auto boards = listBoardFiles();
789 bool loaded = false;
790 for (auto& board : boards)
791 {
792 if (ImGui::Button(board.c_str()))
793 {
794 std::filesystem::path board_file_path = PathManager::getRootDirectory() / "GameTables" / game_table_name / "Boards" / board;
795 board_manager->loadActiveBoard(board_file_path.string());
796 network_manager->broadcastBoard(board_manager->getActiveBoard());
797 loaded = true;
798 ImGui::CloseCurrentPopup();
799 }
800 }
801
802 UI_TransientLine("board-loaded", loaded, ImVec4(0.4f, 1.f, 0.4f, 1.f), "Board Loaded!", 1.5f);
803
804 ImGui::NewLine();
805 ImGui::Separator();
806
807 if (ImGui::Button("Close"))
808 {
809 ImGui::CloseCurrentPopup();
810 }
811 ImGui::EndPopup();
812 }
813}
std::vector< std::string > listBoardFiles()
Here is the call graph for this function:

◆ loadGameTable()

void GameTableManager::loadGameTable ( std::filesystem::path game_table_file_path)

Definition at line 44 of file GameTableManager.cpp.

45{
46 std::ifstream inFile(game_table_file_path, std::ios::binary);
47 if (inFile)
48 {
49 std::vector<unsigned char> buffer((std::istreambuf_iterator<char>(inFile)), std::istreambuf_iterator<char>());
50 inFile.close();
51
52 size_t offset = 0;
54 auto tableId = active_game_table.get<Identifier>()->id;
55 game_table_name = active_game_table.get<GameTable>()->gameTableName;
56 chat_manager->setActiveGameTable(tableId, game_table_name);
57
58 ecs.defer_begin();
59 try
60 {
61 active_game_table.children([&](flecs::entity child)
62 {
63 if (child.has<Board>()) {
64 auto texture = child.get_mut<TextureComponent>();
65 auto board_image = map_directory->getImageByPath(texture->image_path);
66 texture->textureID = board_image.textureID;
67 texture->size = board_image.size;
68
69 child.children([&](flecs::entity grand_child) {
70 if (grand_child.has<MarkerComponent>()) {
71 auto grand_child_texture = grand_child.get_mut<TextureComponent>();
72 auto marker_image = board_manager->marker_directory->getImageByPath(grand_child_texture->image_path);
73 grand_child_texture->textureID = marker_image.textureID;
74 grand_child_texture->size = marker_image.size;
75 }
76 });
77 board_manager->setActiveBoard(child);
78 } });
79 }
80 catch (const std::exception&)
81 {
82 std::cout << "ERROR LOADING IMAGES" << std::endl;
83 }
84 ecs.defer_end();
85 toaster_->Push(ImGuiToaster::Level::Good, "GameTable '" + game_table_name + "' Saved Successfuly!!");
86 }
87 else
88 {
89 toaster_->Push(ImGuiToaster::Level::Error, "Failed Saving GameTable!!!");
90 std::cerr << "Failed to load GameTable from " << game_table_file_path.string() << std::endl;
91 }
92}
std::shared_ptr< ImGuiToaster > toaster_
static flecs::entity deserializeGameTableEntity(const std::vector< unsigned char > &buffer, size_t &offset, flecs::world &ecs)
Definition Serializer.h:174
Here is the call graph for this function:
Here is the caller graph for this function:

◆ manageGameTablesPopUp()

void GameTableManager::manageGameTablesPopUp ( )

Definition at line 1479 of file GameTableManager.cpp.

1480{
1481 namespace fs = std::filesystem;
1482 if (!ImGui::BeginPopupModal("Manage GameTables", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
1483 return;
1484
1485 // --- local UI state ---
1486 static bool refreshFS = true;
1487 static std::vector<std::string> tables; // folder names under GameTables/
1488 static std::vector<std::string> boards; // *.runic in GameTables/<table>/Boards
1489 static std::string selectedTable; // folder name
1490 static std::string pendingBoardToRename; // filename (with .runic)
1491 static std::string pendingBoardToDelete; // filename (with .runic)
1492 static char tableRenameBuf[128] = {0};
1493 static char boardRenameBuf[128] = {0};
1494
1495 const fs::path root = PathManager::getGameTablesPath();
1496
1497 // inline "reload" blocks so we don't create helpers
1498 if (refreshFS)
1499 {
1500 tables.clear();
1501 if (fs::exists(root))
1502 {
1503 for (auto& e : fs::directory_iterator(root))
1504 if (e.is_directory())
1505 tables.emplace_back(e.path().filename().string());
1506 std::sort(tables.begin(), tables.end());
1507 }
1508 // ensure selection is valid
1509 if (!selectedTable.empty() && !fs::exists(root / selectedTable))
1510 selectedTable.clear();
1511
1512 boards.clear();
1513 if (!selectedTable.empty())
1514 {
1515 const fs::path boardsDir = root / selectedTable / "Boards";
1516 if (fs::exists(boardsDir))
1517 {
1518 for (auto& e : fs::directory_iterator(boardsDir))
1519 if (e.is_regular_file() && e.path().extension() == ".runic")
1520 boards.emplace_back(e.path().filename().string());
1521 std::sort(boards.begin(), boards.end());
1522 }
1523 }
1524 refreshFS = false;
1525 }
1526
1527 const float leftW = 260.f;
1528
1529 // LEFT panel: tables
1530 ImGui::BeginChild("tables-left", ImVec2(leftW, 420), true);
1531 ImGui::TextUnformatted("GameTables");
1532 ImGui::Separator();
1533
1534 for (const auto& t : tables)
1535 {
1536 const bool sel = (t == selectedTable);
1537 if (ImGui::Selectable(t.c_str(), sel))
1538 {
1539 selectedTable = t;
1540 std::snprintf(tableRenameBuf, IM_ARRAYSIZE(tableRenameBuf), "%s", t.c_str());
1541 // refresh boards for this selection
1542 refreshFS = true;
1543 }
1544 }
1545
1546 ImGui::Separator();
1547 ImGui::TextUnformatted("Selected Table:");
1548 ImGui::SameLine();
1549 ImGui::TextColored(ImVec4(0.8f, 0.9f, 1.f, 1.f), "%s", selectedTable.empty() ? "(none)" : selectedTable.c_str());
1550
1551 ImGui::BeginDisabled(selectedTable.empty());
1552 if (ImGui::Button("Delete Table"))
1553 ImGui::OpenPopup("DeleteTable");
1554 ImGui::EndDisabled();
1555
1556 // Delete table popup
1557 if (ImGui::BeginPopupModal("DeleteTable", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
1558 {
1559 ImGui::Text("Delete table '%s'?\nThis removes the folder permanently.", selectedTable.c_str());
1560 ImGui::Separator();
1561 if (ImGui::Button("Delete", ImVec2(120, 0)))
1562 {
1563 try
1564 {
1565 const fs::path dir = root / selectedTable;
1566 if (fs::exists(dir))
1567 fs::remove_all(dir);
1568 selectedTable.clear();
1569 refreshFS = true;
1570 }
1571 catch (...)
1572 { /* ignore */
1573 }
1574 ImGui::CloseCurrentPopup();
1575 }
1576 ImGui::SameLine();
1577 if (ImGui::Button("Cancel", ImVec2(120, 0)))
1578 ImGui::CloseCurrentPopup();
1579 ImGui::EndPopup();
1580 }
1581
1582 ImGui::EndChild();
1583
1584 ImGui::SameLine();
1585
1586 // RIGHT panel: boards in selected table
1587 ImGui::BeginChild("boards-right", ImVec2(540, 420), true);
1588 ImGui::TextUnformatted("Boards");
1589 ImGui::Separator();
1590
1591 if (selectedTable.empty())
1592 {
1593 ImGui::TextDisabled("Select a table to see its boards.");
1594 }
1595 else
1596 {
1597 for (const auto& f : boards)
1598 {
1599 ImGui::PushID(f.c_str());
1600 ImGui::TextUnformatted(f.c_str());
1601 ImGui::SameLine();
1602
1603 ImGui::SameLine();
1604 if (ImGui::Button("Delete"))
1605 {
1606 pendingBoardToDelete = f;
1607 ImGui::OpenPopup("DeleteBoard");
1608 }
1609
1610 // Delete board popup
1611 if (ImGui::BeginPopupModal("DeleteBoard", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
1612 {
1613 ImGui::Text("Delete board '%s'?", pendingBoardToDelete.c_str());
1614 ImGui::Separator();
1615 if (ImGui::Button("Delete", ImVec2(120, 0)))
1616 {
1617 try
1618 {
1619 const fs::path boardsDir = root / selectedTable / "Boards";
1620 const fs::path p = boardsDir / pendingBoardToDelete;
1621 if (fs::exists(p))
1622 fs::remove(p);
1623 refreshFS = true;
1624 }
1625 catch (...)
1626 { /* ignore */
1627 }
1628 ImGui::CloseCurrentPopup();
1629 }
1630 ImGui::SameLine();
1631 if (ImGui::Button("Cancel", ImVec2(120, 0)))
1632 ImGui::CloseCurrentPopup();
1633 ImGui::EndPopup();
1634 }
1635
1636 ImGui::PopID();
1637 }
1638 if (boards.empty())
1639 ImGui::TextDisabled("(no boards in this table)");
1640 }
1641
1642 ImGui::EndChild();
1643
1644 ImGui::Separator();
1645 if (ImGui::Button("Close", ImVec2(120, 0)))
1646 ImGui::CloseCurrentPopup();
1647
1648 ImGui::EndPopup();
1649}
Here is the call graph for this function:

◆ networkCenterPopUp()

void GameTableManager::networkCenterPopUp ( )

Definition at line 919 of file GameTableManager.cpp.

920{
921 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
922 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
923
924 if (ImGui::BeginPopupModal("Network Center", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
925 {
926
927 // ---------- STATUS (keep this block) ----------
928 Role role = network_manager->getPeerRole();
929 const char* roleStr =
930 role == Role::GAMEMASTER ? "Hosting (GM)" : role == Role::PLAYER ? "Connected (Player)"
931 : "Idle";
932 ImVec4 roleClr =
933 role == Role::GAMEMASTER ? ImVec4(0.2f, 0.9f, 0.2f, 1) : role == Role::PLAYER ? ImVec4(0.2f, 0.6f, 1.0f, 1)
934 : ImVec4(1.0f, 0.6f, 0.2f, 1);
935 ImGui::Text("Status: ");
936 ImGui::SameLine();
937 ImGui::TextColored(roleClr, "%s", roleStr);
938
939 ImGui::Separator();
940
941 // ---------- BODY ----------
942 if (role == Role::GAMEMASTER)
943 {
944 renderNetworkCenterGM(); // full GM center
945 }
946 else if (role == Role::PLAYER)
947 {
948 renderNetworkCenterPlayer(); // player center (read-only)
949 }
950 else
951 {
952 ImGui::TextDisabled("No network connection established.");
953 }
954
955 ImGui::Separator();
956 if (ImGui::Button("Close"))
957 ImGui::CloseCurrentPopup();
958
959 ImGui::EndPopup();
960 }
961}
Role
Definition Message.h:13
@ PLAYER
@ GAMEMASTER
Here is the call graph for this function:

◆ nowMs()

static uint64_t GameTableManager::nowMs ( )
inlinestaticprivate

Definition at line 272 of file GameTableManager.h.

273 {
274 using Clock = std::chrono::steady_clock;
275 using namespace std::chrono;
276 return duration_cast<milliseconds>(Clock::now().time_since_epoch()).count();
277 }
std::chrono::steady_clock Clock

◆ processMouseInput()

void GameTableManager::processMouseInput ( bool is_mouse_within_image_bounds,
bool is_map_window_hovered )
inline

Definition at line 89 of file GameTableManager.h.

90 {
91
92 ImGuiIO& io = ImGui::GetIO();
93
94 mouse_wheel_delta = io.MouseWheel;
95
96 if ((io.MouseClicked[0] || io.MouseClicked[1] || io.MouseClicked[2]) && !is_mouse_within_image_bounds && is_map_window_hovered)
97 {
99 }
100 if (io.MouseReleased[0] || io.MouseReleased[1] || io.MouseReleased[2])
101 {
103 }
104
107
109 {
110 if (is_mouse_within_image_bounds && is_map_window_hovered)
111 {
112 if (io.MouseClicked[0])
113 { // LEFT mouse button just clicked
114 mouse_left_clicked = true;
115 }
116 if (io.MouseClicked[1])
117 { // RIGHT mouse button just clicked
118 mouse_right_clicked = true;
119 }
120 if (io.MouseClicked[2])
121 { // MIDDLE mouse button just clicked
123 }
124 }
125 }
126
127 if (io.MouseReleased[0])
128 { // LEFT mouse button just released
129 mouse_left_released = true;
130 }
131 if (io.MouseReleased[1])
132 { // RIGHT mouse button just released
134 }
135 if (io.MouseReleased[2])
136 { // MIDDLE mouse button just released
138 }
139 }

◆ processReceivedMessages()

void GameTableManager::processReceivedMessages ( )

Definition at line 109 of file GameTableManager.cpp.

110{
111 constexpr int kMaxPerFrame = 32; // avoid long stalls
112
113 /* static uint64_t last = 0;
114 uint64_t t = nowMs();
115 if (t - last >= 30000)
116 {
117 network_manager->housekeepPeers();
118 last = t;
119 }*/
120
121 network_manager->drainInboundRaw(kMaxPerFrame);
122 network_manager->drainEvents();
123 int processed = 0;
125 while (processed < kMaxPerFrame && network_manager->tryPopReadyMessage(m))
126 {
127 Logger::instance().log("localtunnel", Logger::Level::Info, msg::DCtypeString(m.kind) + "RECEIVED ON PROCESS!!!");
128 switch (m.kind)
129 {
131 {
132 if (!m.tableId || !m.name)
133 break;
134 active_game_table = ecs.entity("GameTable")
135 .set(GameTable{*m.name})
136 .set(Identifier{*m.tableId});
137 game_table_name = *m.name;
138 chat_manager->setActiveGameTable(*m.tableId, *m.name);
139 Logger::instance().log("localtunnel", Logger::Level::Info, "GameTable Created!!");
140
141 break;
142 }
143
145 {
146 if (!m.boardId || !m.boardMeta)
147 break;
148 GLuint tex = 0;
149 glm::vec2 texSize{0, 0};
150 if (m.bytes && !m.bytes->empty())
151 {
152 auto image = board_manager->LoadTextureFromMemory(m.bytes->data(),
153 m.bytes->size());
154 tex = image.textureID;
155 texSize = image.size;
156 Logger::instance().log("localtunnel", Logger::Level::Info, "Board Texture Created: " + std::to_string(tex));
157 }
158
159 const auto& bm = *m.boardMeta;
160 auto board = ecs.entity()
161 .set(Identifier{bm.boardId})
162 .set(Board{bm.boardName})
163 .set(Panning{false})
164 .set(Grid{bm.grid})
165 .set(TextureComponent{tex, "", texSize})
166 .set(Size{texSize.x, texSize.y});
167
168 board_manager->setActiveBoard(board);
169 Logger::instance().log("localtunnel", Logger::Level::Info, "Board Created!!");
170 break;
171 }
172
174 {
175 if (!m.boardId || !m.markerMeta)
176 break;
177 auto boardEnt = board_manager->findBoardById(*m.boardId);
178 if (!boardEnt.is_valid())
179 break;
180
181 GLuint tex = 0;
182 glm::vec2 texSize{m.markerMeta->size.width, m.markerMeta->size.height};
183 Logger::instance().log("localtunnel", Logger::Level::Info, "Marker Texture Byte Size: " + std::to_string(m.bytes->size()));
184 if (m.bytes && !m.bytes->empty())
185 {
186 auto image = board_manager->LoadTextureFromMemory(m.bytes->data(),
187 m.bytes->size());
188 tex = image.textureID;
189 texSize = image.size;
190 Logger::instance().log("localtunnel", Logger::Level::Info, "Marker Texture Created: " + std::to_string(tex));
191 }
192 const auto& mm = *m.markerMeta;
193 flecs::entity marker = ecs.entity()
194 .set(Identifier{mm.markerId})
195 .set(Position{mm.pos.x, mm.pos.y}) //World Position
196 .set(Size{mm.size.width, mm.size.height})
197 .set(TextureComponent{tex, "", texSize})
198 .set(Visibility{mm.vis})
199 .set(MarkerComponent{"", "", false, false})
200 .set(Moving{mm.mov});
201 marker.add(flecs::ChildOf, boardEnt);
202 Logger::instance().log("localtunnel", Logger::Level::Info, "Marker Created");
203 break;
204 }
205
207 {
208 if (!m.boardId || !m.fogId || !m.pos || !m.size || !m.vis)
209 break;
210 auto boardEnt = board_manager->findBoardById(*m.boardId);
211 if (!boardEnt.is_valid())
212 break;
213
214 auto fog = ecs.entity()
215 .set(Identifier{*m.fogId})
216 .set(Position{m.pos->x, m.pos->y})
217 .set(Size{m.size->width, m.size->height})
218 .set(Visibility{*m.vis});
219
220 fog.add<FogOfWar>();
221 fog.add(flecs::ChildOf, boardEnt);
222 Logger::instance().log("localtunnel", Logger::Level::Info, "Fog Created");
223 break;
224 }
225
227 {
228 if (!m.boardId || !m.fogId)
229 break;
230 auto boardEnt = board_manager->findBoardById(*m.boardId);
231 if (!boardEnt.is_valid())
232 break;
233
234 flecs::entity fogEnt;
235 boardEnt.children([&](flecs::entity child)
236 {
237 if (child.has<FogOfWar>()) {
238 auto id = child.get<Identifier>()->id;
239 if (id == *m.fogId) fogEnt = child;
240 } });
241 if (!fogEnt.is_valid())
242 break;
243
244 if (m.pos)
245 fogEnt.set<Position>(*m.pos);
246 if (m.size)
247 fogEnt.set<Size>(*m.size);
248 if (m.vis)
249 fogEnt.set<Visibility>(*m.vis);
250 if (m.mov)
251 fogEnt.set<Moving>(*m.mov); // if used
252 break;
253 }
254
256 {
257 if (!m.boardId || !m.fogId)
258 break;
259 auto boardEnt = board_manager->findBoardById(*m.boardId);
260 if (!boardEnt.is_valid())
261 break;
262
263 flecs::entity fogEnt;
264 boardEnt.children([&](flecs::entity child)
265 {
266 if (child.has<FogOfWar>()) {
267 auto id = child.get<Identifier>()->id;
268 if (id == *m.fogId) fogEnt = child;
269 } });
270 if (fogEnt.is_valid())
271 fogEnt.destruct();
272 break;
273 }
274
276 {
277 // Epoch/seq gate (no side effects)
278 if (!network_manager->shouldApplyMarkerMove(m))
279 break;
280
281 if (!m.boardId || !m.markerId || !m.pos)
282 break;
283
284 auto boardEnt = board_manager->findBoardById(*m.boardId);
285 if (!boardEnt.is_valid())
286 break;
287
288 auto markerEnt = findMarkerInBoard(boardEnt, *m.markerId);
289 if (!markerEnt.is_valid())
290 break;
291
292 // Apply streaming position; keep Moving true during drag
293 markerEnt.set<Position>(*m.pos);
294 //markerEnt.set<Moving>(Moving{true});
295 break;
296 }
297
299 {
300 if (!m.boardId || !m.markerId)
301 break;
302
303 auto boardEnt = board_manager->findBoardById(*m.boardId);
304 if (!boardEnt.is_valid())
305 break;
306
307 auto markerEnt = findMarkerInBoard(boardEnt, *m.markerId);
308 if (!markerEnt.is_valid())
309 break;
310
311 // Start of drag
312 if (m.mov && m.mov->isDragging)
313 {
314 if (!network_manager->shouldApplyMarkerMoveStateStart(m))
315 break;
316
317 // optional visual sync (safe; local drags are already set locally)
318 markerEnt.set<Moving>(Moving{true});
319 }
320 else // End of drag (final)
321 {
322 if (!network_manager->shouldApplyMarkerMoveStateFinal(m))
323 break;
324
325 if (m.pos)
326 markerEnt.set<Position>(*m.pos);
327 markerEnt.set<Moving>(Moving{false}); // ensure drag ends
328 }
329 break;
330 }
331
333 {
334 if (!m.boardId || !m.markerId)
335 break;
336
337 auto boardEnt = board_manager->findBoardById(*m.boardId);
338 if (!boardEnt.is_valid())
339 break;
340
341 auto markerEnt = findMarkerInBoard(boardEnt, *m.markerId);
342 if (!markerEnt.is_valid())
343 break;
344
345 // Apply only non-movement attributes
346 if (m.size)
347 markerEnt.set<Size>(*m.size);
348 if (m.vis)
349 markerEnt.set<Visibility>(*m.vis);
350 if (m.markerComp)
351 {
352 std::string oldOwnerUid = markerEnt.get<MarkerComponent>()->ownerUniqueId;
353 if (oldOwnerUid != m.markerComp->ownerUniqueId)
354 network_manager->drag_.erase(*m.markerId);
355 markerEnt.set<MarkerComponent>(*m.markerComp);
356 }
357
358 break;
359 }
360
362 {
363 if (!m.boardId || !m.markerId)
364 break;
365 auto boardEnt = board_manager->findBoardById(*m.boardId);
366 if (!boardEnt.is_valid())
367 break;
368
369 flecs::entity markerEnt;
370 boardEnt.children([&](flecs::entity child)
371 {
372 if (child.has<MarkerComponent>()) {
373 auto id = child.get<Identifier>()->id;
374 if (id == *m.markerId) markerEnt = child;
375 } });
376 if (markerEnt.is_valid())
377 markerEnt.destruct();
378 break;
379 }
380
381 // GameTableManager.cpp — inside processReceivedMessages() switch:
383 {
384 if (!m.tableId || !m.userUniqueId || !m.name || !m.text)
385 break;
386 if (*m.tableId != chat_manager->currentTableId_)
387 break;
388
389 const std::string uniqueId = *m.userUniqueId; // now uniqueId
390 const std::string fromPeerId = m.fromPeerId;
391 const std::string newU = *m.name;
392
393 // 1) record in address book
394 network_manager->upsertPeerIdentityWithUnique(/*peerId=*/m.fromPeerId, /*uniqueId=*/uniqueId, /*username=*/newU);
395 identity_manager->setUsernameForUnique(uniqueId, newU);
396 board_manager->onUsernameChanged(uniqueId, newU);
397 chat_manager->replaceUsernameForUnique(uniqueId, newU);
398
399 break;
400 }
401
403 {
404 chat_manager->applyReady(m);
405 break;
406 }
408 {
409 chat_manager->applyReady(m);
410 break;
411 }
413 {
414 chat_manager->applyReady(m);
415 break;
416 }
418 {
419 chat_manager->applyReady(m);
420 break;
421 }
422
424 {
425 if (!m.boardId || !m.grid)
426 break;
427
428 auto boardEnt = board_manager->findBoardById(*m.boardId);
429 if (!boardEnt.is_valid())
430 break;
431
432 boardEnt.set<Grid>(*m.grid);
433 break;
434 }
435
437 {
438 break;
439 }
440
442 {
443 break;
444 }
445
447 {
448 break;
449 }
450
451 default:
452 break;
453 }
454
455 ++processed;
456 }
457}
static flecs::entity findMarkerInBoard(flecs::entity boardEnt, uint64_t markerId)
static Logger & instance()
Definition Logger.h:39
std::string DCtypeString(DCType type)
Definition Message.h:61
float x
Definition Components.h:20
float width
Definition Components.h:27
Here is the call graph for this function:

◆ pushStatusToast()

void GameTableManager::pushStatusToast ( const std::string & msg,
ImGuiToaster::Level lvl,
float durationSec = 5.0f )
inline

Definition at line 83 of file GameTableManager.h.

84 {
85 if (toaster_)
86 toaster_->Push(lvl, msg, durationSec);
87 }
Definition Message.h:28

◆ render()

void GameTableManager::render ( VertexArray & va,
IndexBuffer & ib,
Shader & shader,
Shader & grid_shader,
Renderer & renderer )

Definition at line 1995 of file GameTableManager.cpp.

1996{
1997 if (isGameTableActive())
1998 {
1999 chat_manager->render();
2000 }
2001 if (board_manager->isBoardActive())
2002 {
2003 if (board_manager->isEditWindowOpen())
2004 {
2005 board_manager->renderEditWindow();
2006 }
2007 else
2008 {
2009 board_manager->setShowEditWindow(false);
2010 }
2011
2012 if (network_manager->getPeerRole() == Role::GAMEMASTER)
2013 {
2014 board_manager->marker_directory->renderDirectory();
2015 }
2016 board_manager->renderBoard(va, ib, shader, grid_shader, renderer);
2017 }
2018}
Here is the call graph for this function:

◆ renderNetworkCenterGM()

void GameTableManager::renderNetworkCenterGM ( )

Definition at line 963 of file GameTableManager.cpp.

964{
965 // ---------- INFO ----------
966 const auto local_ip = network_manager->getLocalIPAddress();
967 const auto external_ip = network_manager->getExternalIPAddress();
968 const auto port = network_manager->getPort();
969 const auto cs_local = network_manager->getNetworkInfo(ConnectionType::LOCAL);
970 const auto cs_external = network_manager->getNetworkInfo(ConnectionType::EXTERNAL);
971 const auto cs_lt = network_manager->getNetworkInfo(ConnectionType::LOCALTUNNEL);
972
973 ImGui::TextUnformatted("Local IP:");
974 ImGui::SameLine();
975 ImGui::TextUnformatted(local_ip.c_str());
976 ImGui::TextUnformatted("External IP:");
977 ImGui::SameLine();
978 ImGui::TextUnformatted(external_ip.c_str());
979 ImGui::TextUnformatted("Port:");
980 ImGui::SameLine();
981 ImGui::Text("%u", port);
982
983 ImGui::Separator();
984
985 auto copyRow = [this](const char* label, const std::string& value,
986 const char* btnId, const char* toastId)
987 {
988 ImGui::TextUnformatted(label);
989 ImGui::SameLine();
990 ImGui::TextUnformatted(value.c_str());
991 ImGui::SameLine();
992 UI_CopyButtonWithToast(btnId, value, toastId, 1.5f);
993 };
994
995 copyRow("LocalTunnel URL:", cs_lt, "Copy##lt", "toast-lt");
996 copyRow("Local Connection String:", cs_local, "Copy##loc", "toast-loc");
997 copyRow("External Connection String:", cs_external, "Copy##ext", "toast-ext");
998 const auto cs_custom = network_manager->getNetworkInfo(ConnectionType::CUSTOM);
999 if (!cs_custom.empty())
1000 {
1001 copyRow("Custom Connection String:", cs_custom, "Copy##custom", "toast-custom");
1002 }
1003 // ---------- PLAYERS (P2P) ----------
1004 ImGui::Separator();
1005 ImGui::Text("Players (P2P)");
1006 if (ImGui::BeginTable("PeersTable", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp))
1007 {
1008 ImGui::TableSetupColumn("Username", ImGuiTableColumnFlags_WidthStretch, 1.5f);
1009 ImGui::TableSetupColumn("Peer ID", ImGuiTableColumnFlags_WidthStretch, 2.0f);
1010 ImGui::TableSetupColumn("PC State", ImGuiTableColumnFlags_WidthFixed, 100.f);
1011 ImGui::TableSetupColumn("DataChannel", ImGuiTableColumnFlags_WidthFixed, 100.f);
1012 ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 120.f);
1013 ImGui::TableHeadersRow();
1014
1015 for (auto& [peerId, link] : network_manager->getPeers())
1016 {
1017 if (!link)
1018 continue;
1019
1020 ImGui::TableNextRow();
1021
1022 // Username
1023 ImGui::TableSetColumnIndex(0);
1024 ImGui::TextUnformatted(network_manager->displayNameForPeer(peerId).c_str());
1025
1026 // Peer ID
1027 ImGui::TableSetColumnIndex(1);
1028 ImGui::TextUnformatted(peerId.c_str());
1029
1030 // PC State (with color)
1031 ImGui::TableSetColumnIndex(2);
1032 const char* pcStr = link->pcStateString();
1033 ImVec4 pcCol = ImVec4(0.8f, 0.8f, 0.8f, 1);
1034 if (strcmp(pcStr, "Connected") == 0)
1035 pcCol = ImVec4(0.3f, 1.0f, 0.3f, 1);
1036 else if (strcmp(pcStr, "Connecting") == 0)
1037 pcCol = ImVec4(1.0f, 0.8f, 0.2f, 1);
1038 else if (strcmp(pcStr, "Disconnected") == 0 || strcmp(pcStr, "Failed") == 0)
1039 pcCol = ImVec4(1.0f, 0.3f, 0.3f, 1);
1040 ImGui::TextColored(pcCol, "%s", pcStr);
1041
1042 // DC state
1043 ImGui::TableSetColumnIndex(3);
1044 const bool dcOpen = link->isDataChannelOpen();
1045 ImGui::TextUnformatted(dcOpen ? "Open" : "Closed");
1046
1047 ImGui::TableSetColumnIndex(4);
1048
1049 ImGui::PushID(peerId.c_str());
1050 if (ImGui::SmallButton("Disconnect"))
1051 {
1052 ImGui::OpenPopup("ConfirmKickPeer");
1053 }
1054 if (UI_ConfirmModal("ConfirmKickPeer", "Disconnect this peer?",
1055 "This will disconnect the selected peer and notify others to drop it."))
1056 {
1057 // 1) Broadcast PeerDisconnect (so other peers drop links to this id)
1058 network_manager->broadcastPeerDisconnect(peerId);
1059 // 2) Optionally kick WS client too (server-side)
1060 if (auto srv = network_manager->getSignalingServer())
1061 {
1062 srv->disconnectClient(peerId); // add this helper to server if not present
1063 }
1064 // 3) Locally close our peer link
1065 network_manager->removePeer(peerId);
1066 }
1067 ImGui::PopID();
1068 }
1069 ImGui::EndTable();
1070 }
1071
1072 // ---------- CLIENTS (WS) ----------
1073 ImGui::Separator();
1074 ImGui::Text("Clients (WebSocket)");
1075 if (ImGui::BeginTable("ClientsTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp))
1076 {
1077 ImGui::TableSetupColumn("ClientId");
1078 ImGui::TableSetupColumn("Username");
1079 ImGui::TableSetupColumn("Actions");
1080 ImGui::TableHeadersRow();
1081
1082 if (auto srv = network_manager->getSignalingServer())
1083 {
1084 for (auto& [cid, ws] : srv->authClients())
1085 {
1086 ImGui::TableNextRow();
1087
1088 ImGui::TableSetColumnIndex(0);
1089 ImGui::TextUnformatted(cid.c_str());
1090
1091 ImGui::TableSetColumnIndex(1);
1092 ImGui::TextUnformatted(network_manager->displayNameForPeer(cid).c_str());
1093
1094 ImGui::TableSetColumnIndex(2);
1095 if (ImGui::SmallButton((std::string("Disconnect##") + cid).c_str()))
1096 {
1097 srv->disconnectClient(cid);
1098 }
1099 }
1100 }
1101 else
1102 {
1103 ImGui::TableNextRow();
1104 ImGui::TableSetColumnIndex(0);
1105 ImGui::TextDisabled("No signaling server");
1106 }
1107 ImGui::EndTable();
1108 }
1109
1110 // ---------- Controls ----------
1111 ImGui::Separator();
1112 if (ImGui::Button("Disconnect All"))
1113 {
1114 ImGui::OpenPopup("ConfirmGMDisconnectAll");
1115 }
1116 ImGui::SameLine();
1117 if (UI_ConfirmModal("ConfirmGMDisconnectAll", "Disconnect ALL clients?",
1118 "This will broadcast a shutdown and disconnect all clients, "
1119 "close all peer links, and stop the signaling server."))
1120 {
1121 network_manager->disconnectAllPeers(); // your GM teardown
1122 ImGui::CloseCurrentPopup();
1123 }
1124
1125 if (ImGui::Button("Close Network"))
1126 {
1127 ImGui::OpenPopup("ConfirmCloseNetwork");
1128 }
1129
1130 if (UI_ConfirmModal("ConfirmCloseNetwork", "Close Network?",
1131 "This close will close the server"
1132 "and stop the signaling server."))
1133 {
1134 network_manager->closeServer();
1135 ImGui::CloseCurrentPopup();
1136 }
1137}
bool UI_CopyButtonWithToast(const char *btnId, const std::string &toCopy, const char *toastId, float seconds=1.5f)
bool UI_ConfirmModal(const char *popupId, const char *title, const char *text)
Here is the call graph for this function:
Here is the caller graph for this function:

◆ renderNetworkCenterPlayer()

void GameTableManager::renderNetworkCenterPlayer ( )

Definition at line 1139 of file GameTableManager.cpp.

1140{
1141 // Username
1142 ImGui::Text("Username: %s", network_manager->getMyUsername().c_str());
1143 ImGui::NewLine();
1144
1145 // Peers (read-only)
1146 if (ImGui::BeginTable("PeersPlayer", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp))
1147 {
1148 ImGui::TableSetupColumn("Username", ImGuiTableColumnFlags_WidthStretch, 1.5f);
1149 ImGui::TableSetupColumn("Peer ID", ImGuiTableColumnFlags_WidthStretch, 2.0f);
1150 ImGui::TableSetupColumn("PC State", ImGuiTableColumnFlags_WidthFixed, 100.f);
1151 ImGui::TableSetupColumn("DataChannel", ImGuiTableColumnFlags_WidthFixed, 100.f);
1152 ImGui::TableHeadersRow();
1153
1154 for (auto& [peerId, link] : network_manager->getPeers())
1155 {
1156 if (!link)
1157 continue;
1158
1159 ImGui::TableNextRow();
1160
1161 // Username
1162 ImGui::TableSetColumnIndex(0);
1163 ImGui::TextUnformatted(network_manager->displayNameForPeer(peerId).c_str());
1164
1165 // Peer ID
1166 ImGui::TableSetColumnIndex(1);
1167 ImGui::TextUnformatted(peerId.c_str());
1168
1169 // PC state
1170 ImGui::TableSetColumnIndex(2);
1171 const char* pcStr = link->pcStateString();
1172 ImVec4 pcCol = ImVec4(0.8f, 0.8f, 0.8f, 1);
1173 if (strcmp(pcStr, "Connected") == 0)
1174 pcCol = ImVec4(0.3f, 1.0f, 0.3f, 1);
1175 else if (strcmp(pcStr, "Connecting") == 0)
1176 pcCol = ImVec4(1.0f, 0.8f, 0.2f, 1);
1177 else if (strcmp(pcStr, "Disconnected") == 0 || strcmp(pcStr, "Failed") == 0)
1178 pcCol = ImVec4(1.0f, 0.3f, 0.3f, 1);
1179 ImGui::TextColored(pcCol, "%s", pcStr);
1180
1181 // DC state
1182 ImGui::TableSetColumnIndex(3);
1183 ImGui::TextUnformatted(link->isDataChannelOpen() ? "Open" : "Closed");
1184 }
1185
1186 ImGui::EndTable();
1187 }
1188 // At end of renderNetworkCenterPlayer()
1189 if (ImGui::Button("Disconnect"))
1190 {
1191 ImGui::OpenPopup("ConfirmPlayerDisconnect");
1192 }
1193 if (UI_ConfirmModal("ConfirmPlayerDisconnect", "Disconnect?",
1194 "This will close your WebSocket and all peer connections."))
1195 {
1196 network_manager->disconectFromPeers(); // your player teardown
1197 ImGui::CloseCurrentPopup(); // close Network Center
1198 }
1199}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ renderUsernameChangePopup()

void GameTableManager::renderUsernameChangePopup ( )

Definition at line 1651 of file GameTableManager.cpp.

1652{
1653 // You already call OpenPopup("Change Username") elsewhere
1654 if (ImGui::BeginPopupModal("Change Username", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
1655 {
1656 static char usernameBuf[64] = {0};
1657
1658 // Seed the input when the popup appears
1659 if (ImGui::IsWindowAppearing())
1660 {
1661 const std::string cur = network_manager->getMyUsername();
1662 std::snprintf(usernameBuf, sizeof(usernameBuf), "%s", cur.c_str());
1663 }
1664
1665 ImGui::TextUnformatted("Username for this table:");
1666 ImGui::InputText("##uname", usernameBuf, (int)sizeof(usernameBuf));
1668 ImGui::Separator();
1669
1670 const bool hasTable = active_game_table.is_alive() && active_game_table.has<Identifier>();
1671 ImGui::BeginDisabled(!hasTable);
1672 if (ImGui::Button("Apply", ImVec2(120, 0)))
1673 {
1674 if (hasTable)
1675 {
1676 const uint64_t tableId = active_game_table.get<Identifier>()->id;
1677
1678 const std::string oldU = identity_manager->myUsername();
1679 const std::string newU = usernameBuf;
1680 if (!newU.empty() && newU != oldU)
1681 {
1682 identity_manager->setMyIdentity(identity_manager->myUniqueId(), newU);
1683
1684 // 2) Broadcast UsernameUpdate over Game DC (carry uniqueId!)
1685 // If you currently have a binary builder, change it to write uniqueId instead of peerId.
1686 std::vector<uint8_t> payload;
1687 network_manager->buildUserNameUpdate(payload,
1688 tableId,
1689 /*userUniqueId*/ identity_manager->myUniqueId(),
1690 /*oldUsername*/ oldU,
1691 /*newUsername*/ newU,
1692 /*reboundFlag*/ false);
1693 network_manager->broadcastUserNameUpdate(payload);
1694
1695 // 3) Apply locally (by uniqueId) for instant UI
1696 board_manager->onUsernameChanged(identity_manager->myUniqueId(), newU);
1697 chat_manager->replaceUsernameForUnique(identity_manager->myUniqueId(), newU);
1698 }
1699 }
1700 ImGui::CloseCurrentPopup();
1701 }
1702 ImGui::EndDisabled();
1703
1704 ImGui::SameLine();
1705 if (ImGui::Button("Cancel", ImVec2(120, 0)))
1706 {
1707 ImGui::CloseCurrentPopup();
1708 }
1709
1710 ImGui::EndPopup();
1711 }
1712}
Here is the call graph for this function:

◆ saveBoardPopUp()

void GameTableManager::saveBoardPopUp ( )

Definition at line 747 of file GameTableManager.cpp.

748{
749 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
750 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
751 if (ImGui::BeginPopupModal("SaveBoard", 0, ImGuiWindowFlags_AlwaysAutoResize))
752 {
753 ImGui::Text("Save current board?");
754 ImGui::NewLine();
755
756 bool saved = false;
757 if (ImGui::Button("Save"))
758 {
759 // your save logic here (you already said logic functions exist)
760 saved = true;
761 ImGui::CloseCurrentPopup();
762 }
763
764 ImGui::SameLine();
765
766 if (ImGui::Button("Close"))
767 {
768 ImGui::CloseCurrentPopup();
769 }
770
771 UI_TransientLine("save-board-ok", saved, ImVec4(0.4f, 1.f, 0.4f, 1.f), "Saved!", 1.5f);
772
773 ImGui::EndPopup();
774 }
775}
Here is the call graph for this function:

◆ saveGameTable()

void GameTableManager::saveGameTable ( )

Definition at line 38 of file GameTableManager.cpp.

39{
41 chat_manager->saveCurrent();
42}
Here is the call graph for this function:

◆ setActiveGameTableEntity()

void GameTableManager::setActiveGameTableEntity ( flecs::entity e)
inline

Definition at line 69 of file GameTableManager.h.

70 {
72 }

◆ setCameraFboDimensions()

void GameTableManager::setCameraFboDimensions ( glm::vec2 fbo_dimensions)

Definition at line 459 of file GameTableManager.cpp.

460{
461 board_manager->camera.setFboDimensions(fbo_dimensions);
462};

◆ setCameraWindowSizePos()

void GameTableManager::setCameraWindowSizePos ( glm::vec2 window_size,
glm::vec2 window_pos )

◆ setToaster()

void GameTableManager::setToaster ( std::shared_ptr< ImGuiToaster > t)
inline

Definition at line 78 of file GameTableManager.h.

79 {
80 toaster_ = t;
81 network_manager->setToaster(t);
82 }

◆ setup()

void GameTableManager::setup ( )

Definition at line 29 of file GameTableManager.cpp.

30{
31 network_manager->setup(board_manager, weak_from_this());
32}

◆ UI_ConfirmModal()

bool GameTableManager::UI_ConfirmModal ( const char * popupId,
const char * title,
const char * text )
inline

Definition at line 207 of file GameTableManager.h.

208 {
209 bool confirmed = false;
210 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
211 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
212 if (ImGui::BeginPopupModal(popupId, nullptr, ImGuiWindowFlags_AlwaysAutoResize))
213 {
214 ImGui::TextUnformatted(title);
215 ImGui::Separator();
216 ImGui::TextWrapped("%s", text);
217 ImGui::Separator();
218 if (ImGui::Button("Yes"))
219 {
220 confirmed = true;
221 ImGui::CloseCurrentPopup();
222 }
223 ImGui::SameLine();
224 if (ImGui::Button("No"))
225 {
226 ImGui::CloseCurrentPopup();
227 }
228 ImGui::EndPopup();
229 }
230 return confirmed;
231 }
Here is the caller graph for this function:

◆ UI_CopyButtonWithToast()

bool GameTableManager::UI_CopyButtonWithToast ( const char * btnId,
const std::string & toCopy,
const char * toastId,
float seconds = 1.5f )
inline

Definition at line 150 of file GameTableManager.h.

152 {
153 static std::unordered_map<std::string, double> s_toasts; // toastId -> expire time
154 bool clicked = ImGui::Button(btnId);
155 if (clicked)
156 {
157 ImGui::SetClipboardText(toCopy.c_str());
158 s_toasts[toastId] = ImGui::GetTime() + seconds;
159 }
160 if (auto it = s_toasts.find(toastId); it != s_toasts.end())
161 {
162 if (ImGui::GetTime() < it->second)
163 {
164 ImGui::SameLine();
165 ImGui::TextColored(ImVec4(0.4f, 1.f, 0.4f, 1.f), "Copied!");
166 }
167 else
168 {
169 s_toasts.erase(it);
170 }
171 }
172 return clicked;
173 }
Here is the caller graph for this function:

◆ UI_LabelValue()

void GameTableManager::UI_LabelValue ( const char * label,
const std::string & value )
inline

Definition at line 142 of file GameTableManager.h.

143 {
144 ImGui::TextUnformatted(label);
145 ImGui::SameLine();
146 ImGui::TextUnformatted(value.c_str());
147 }

◆ UI_RenderPortAndPassword()

bool GameTableManager::UI_RenderPortAndPassword ( char * portBuf,
size_t portBufSize,
char * passBuf,
size_t passBufSize )
inline

Definition at line 196 of file GameTableManager.h.

198 {
199 ImGui::InputText("Password", passBuf, passBufSize, ImGuiInputTextFlags_Password);
201 ImGui::InputText("Port", portBuf, portBufSize, ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsNoBlank);
203 return true;
204 }
Here is the call graph for this function:

◆ UI_TransientLine()

void GameTableManager::UI_TransientLine ( const char * key,
bool trigger,
const ImVec4 & color,
const char * text,
float seconds = 2.0f )
inline

Definition at line 176 of file GameTableManager.h.

178 {
179 static std::unordered_map<std::string, double> s_until; // key -> expire time
180 if (trigger)
181 s_until[key] = ImGui::GetTime() + seconds;
182 if (auto it = s_until.find(key); it != s_until.end())
183 {
184 if (ImGui::GetTime() < it->second)
185 {
186 ImGui::TextColored(color, "%s", text);
187 }
188 else
189 {
190 s_until.erase(it);
191 }
192 }
193 }
Here is the caller graph for this function:

Member Data Documentation

◆ active_game_table

flecs::entity GameTableManager::active_game_table = flecs::entity()

Definition at line 67 of file GameTableManager.h.

◆ board_manager

std::shared_ptr<BoardManager> GameTableManager::board_manager

Definition at line 66 of file GameTableManager.h.

◆ buffer

char GameTableManager::buffer[124] = ""
private

Definition at line 248 of file GameTableManager.h.

◆ chat_manager

std::shared_ptr<ChatManager> GameTableManager::chat_manager

Definition at line 63 of file GameTableManager.h.

◆ current_mouse_fbo_pos

glm::vec2 GameTableManager::current_mouse_fbo_pos
private

Definition at line 246 of file GameTableManager.h.

◆ current_mouse_ndc_pos

glm::vec2 GameTableManager::current_mouse_ndc_pos
private

Definition at line 244 of file GameTableManager.h.

◆ current_mouse_pos

glm::vec2 GameTableManager::current_mouse_pos
private

Definition at line 242 of file GameTableManager.h.

◆ current_mouse_world_pos

glm::vec2 GameTableManager::current_mouse_world_pos
private

Definition at line 245 of file GameTableManager.h.

◆ ecs

flecs::world GameTableManager::ecs
private

Definition at line 240 of file GameTableManager.h.

◆ game_table_name

std::string GameTableManager::game_table_name

Definition at line 61 of file GameTableManager.h.

◆ identity_manager

std::shared_ptr<IdentityManager> GameTableManager::identity_manager

Definition at line 60 of file GameTableManager.h.

◆ ignore_mouse_until_release

bool GameTableManager::ignore_mouse_until_release = false
private

Definition at line 258 of file GameTableManager.h.

◆ is_non_map_window_hovered

bool GameTableManager::is_non_map_window_hovered = false
private

Definition at line 259 of file GameTableManager.h.

◆ map_directory

std::shared_ptr<DirectoryWindow> GameTableManager::map_directory

Definition at line 64 of file GameTableManager.h.

◆ map_image_path

std::string GameTableManager::map_image_path = ""
private

Definition at line 253 of file GameTableManager.h.

◆ mouse_left_clicked

bool GameTableManager::mouse_left_clicked
private

Definition at line 255 of file GameTableManager.h.

◆ mouse_left_released

bool GameTableManager::mouse_left_released
private

Definition at line 256 of file GameTableManager.h.

◆ mouse_middle_clicked

bool GameTableManager::mouse_middle_clicked
private

Definition at line 255 of file GameTableManager.h.

◆ mouse_middle_released

bool GameTableManager::mouse_middle_released
private

Definition at line 256 of file GameTableManager.h.

◆ mouse_right_clicked

bool GameTableManager::mouse_right_clicked
private

Definition at line 255 of file GameTableManager.h.

◆ mouse_right_released

bool GameTableManager::mouse_right_released
private

Definition at line 256 of file GameTableManager.h.

◆ mouse_wheel_delta

float GameTableManager::mouse_wheel_delta
private

Definition at line 257 of file GameTableManager.h.

◆ network_manager

std::shared_ptr<NetworkManager> GameTableManager::network_manager

Definition at line 65 of file GameTableManager.h.

◆ pass_buffer

char GameTableManager::pass_buffer[124] = ""
private

Definition at line 249 of file GameTableManager.h.

◆ port_buffer

char GameTableManager::port_buffer[6] = "7777"
private

Definition at line 250 of file GameTableManager.h.

◆ toaster_

std::shared_ptr<ImGuiToaster> GameTableManager::toaster_
private

Definition at line 234 of file GameTableManager.h.

◆ username_buffer

char GameTableManager::username_buffer[124] = ""
private

Definition at line 251 of file GameTableManager.h.


The documentation for this class was generated from the following files: