Compare commits

..

1 commit

Author SHA1 Message Date
0e2546391b ResearchTree: add basic functionality and some nodes. 2021-07-21 11:30:41 +02:00
9 changed files with 209 additions and 120 deletions

View file

@ -36,6 +36,8 @@ add_executable(MassBuilderSaveTool WIN32
SaveTool/SaveTool_drawMainMenu.cpp SaveTool/SaveTool_drawMainMenu.cpp
SaveTool/SaveTool_MainManager.cpp SaveTool/SaveTool_MainManager.cpp
SaveTool/SaveTool_ProfileManager.cpp SaveTool/SaveTool_ProfileManager.cpp
MassBuilderManager/MassBuilderManager.h
MassBuilderManager/MassBuilderManager.cpp
ProfileManager/ProfileManager.h ProfileManager/ProfileManager.h
ProfileManager/ProfileManager.cpp ProfileManager/ProfileManager.cpp
Profile/Profile.h Profile/Profile.h

View file

@ -0,0 +1,87 @@
// MassBuilderSaveTool
// Copyright (C) 2021 Guillaume Jacquemin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "MassBuilderManager.h"
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/Unicode.h>
#include <shlobj.h>
#include <wtsapi32.h>
using namespace Corrade;
MassBuilderManager::MassBuilderManager() {
_ready = findSaveDirectory();
}
auto MassBuilderManager::ready() const -> bool {
return _ready;
}
auto MassBuilderManager::lastError() -> std::string const& {
return _lastError;
}
auto MassBuilderManager::saveDirectory() -> std::string const& {
return _saveDirectory;
}
void MassBuilderManager::checkGameState() {
WTS_PROCESS_INFOW* process_infos = nullptr;
unsigned long process_count = 0;
if(WTSEnumerateProcessesW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &process_infos, &process_count)) {
Containers::ScopeGuard guard{process_infos, WTSFreeMemory};
for(unsigned long i = 0; i < process_count; ++i) {
if(std::wcscmp(process_infos[i].pProcessName, L"MASS_Builder-Win64-Shipping.exe") == 0) {
_gameState = GameState::Running;
break;
}
else {
_gameState = GameState::NotRunning;
}
}
}
else {
_gameState = GameState::Unknown;
}
}
auto MassBuilderManager::gameState() -> GameState {
return _gameState;
}
auto MassBuilderManager::findSaveDirectory() -> bool {
wchar_t* localappdata_path;
Containers::ScopeGuard guard{localappdata_path, CoTaskMemFree};
if(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_NO_APPCONTAINER_REDIRECTION, nullptr, &localappdata_path) != S_OK)
{
_lastError = "SHGetKnownFolderPath() failed in MassBuilderManager::findSaveDirectory()";
return false;
}
_saveDirectory = Utility::Directory::join(Utility::Directory::fromNativeSeparators(Utility::Unicode::narrow(localappdata_path)), "MASS_Builder");
if(!Utility::Directory::exists(_saveDirectory)) {
_lastError = _saveDirectory + " wasn't found.";
return false;
}
return true;
}

View file

@ -0,0 +1,51 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021 Guillaume Jacquemin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <string>
#include <Magnum/Magnum.h>
using namespace Magnum;
enum class GameState : UnsignedByte {
Unknown, NotRunning, Running
};
class MassBuilderManager {
public:
MassBuilderManager();
auto ready() const -> bool;
auto lastError() -> std::string const&;
auto saveDirectory() -> std::string const&;
void checkGameState();
auto gameState() -> GameState;
private:
auto findSaveDirectory() -> bool;
bool _ready = false;
std::string _lastError = "";
std::string _saveDirectory = "";
GameState _gameState = GameState::Unknown;
};

View file

@ -25,11 +25,12 @@
static const std::string empty_string = ""; static const std::string empty_string = "";
MassManager::MassManager(const std::string& save_path, const std::string& steam_id, bool demo): MassManager::MassManager(const std::string& save_path, const std::string& steam_id, bool demo):
_saveDirectory{save_path},
_steamId{steam_id},
_demo{demo},
_stagingAreaDirectory{Utility::Directory::join(Utility::Directory::path(Utility::Directory::executableLocation()), "staging")} _stagingAreaDirectory{Utility::Directory::join(Utility::Directory::path(Utility::Directory::executableLocation()), "staging")}
{ {
_saveDirectory = save_path;
_steamId = steam_id;
_demo = demo;
Containers::arrayReserve(_hangars, 32); Containers::arrayReserve(_hangars, 32);
std::string mass_filename = ""; std::string mass_filename = "";

View file

@ -54,7 +54,7 @@ class MassManager {
std::string _steamId; std::string _steamId;
bool _demo; bool _demo;
std::string _lastError; std::string _lastError = "";
Containers::Array<Mass> _hangars; Containers::Array<Mass> _hangars;

View file

@ -17,9 +17,8 @@
#include "SaveTool.h" #include "SaveTool.h"
#include <cstring> #include <cstring>
#include <cstdarg>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/FormatStl.h> #include <Corrade/Utility/FormatStl.h>
#include <Corrade/Utility/String.h> #include <Corrade/Utility/String.h>
#include <Corrade/Utility/Unicode.h> #include <Corrade/Utility/Unicode.h>
@ -34,8 +33,6 @@
#include <winuser.h> #include <winuser.h>
#include <processthreadsapi.h> #include <processthreadsapi.h>
#include <shellapi.h> #include <shellapi.h>
#include <shlobj.h>
#include <wtsapi32.h>
#include "../FontAwesome/IconsFontAwesome5.h" #include "../FontAwesome/IconsFontAwesome5.h"
#include "../FontAwesome/IconsFontAwesome5Brands.h" #include "../FontAwesome/IconsFontAwesome5Brands.h"
@ -63,11 +60,11 @@ SaveTool::SaveTool(const Arguments& arguments):
Utility::Debug{} << "Clickthrough is available."; Utility::Debug{} << "Clickthrough is available.";
} }
else { else {
Utility::Warning{} << "Clickthrough is not available (hint couldn't be set)."; Utility::Debug{} << "Clickthrough is not available (hint couldn't be set).";
} }
} }
else { else {
Utility::Warning{} << "Clickthrough is not available (SDL2 is too old)."; Utility::Debug{} << "Clickthrough is not available (SDL2 is too old).";
} }
GL::Renderer::enable(GL::Renderer::Feature::Blending); GL::Renderer::enable(GL::Renderer::Feature::Blending);
@ -83,33 +80,7 @@ SaveTool::SaveTool(const Arguments& arguments):
if((_initEventId = SDL_RegisterEvents(1)) == UnsignedInt(-1)) { if((_initEventId = SDL_RegisterEvents(1)) == UnsignedInt(-1)) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
"SDL_RegisterEvents failed in SaveTool::SaveTool(). Exiting...", window()); "SDL_RegisterEvents failed in SaveTool::SaveTool(). Exiting...", nullptr);
exit(EXIT_FAILURE);
}
if(!findGameDataDirectory()) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app", _lastError.c_str(), window());
exit(EXIT_FAILURE);
}
_configDir = Utility::Directory::join(_gameDataDir, "Saved/Config/WindowsNoEditor");
_saveDir = Utility::Directory::join(_gameDataDir, "Saved/SaveGames");
_screenshotsDir = Utility::Directory::join(_gameDataDir, "Saved/Screenshots/WindowsNoEditor");
if(SDL_InitSubSystem(SDL_INIT_TIMER) != 0) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app", SDL_GetError(), window());
exit(EXIT_FAILURE);
}
checkGameState();
_gameCheckTimerId = SDL_AddTimer(2000,
[](UnsignedInt interval, void* param)->UnsignedInt{
static_cast<SaveTool*>(param)->checkGameState();
return interval;
},
this);
if(_gameCheckTimerId == 0) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", SDL_GetError(), window());
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
} }
@ -248,6 +219,22 @@ void SaveTool::initEvent(SDL_Event& event) {
case InitSuccess: case InitSuccess:
_uiState = UiState::ProfileManager; _uiState = UiState::ProfileManager;
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
SDL_InitSubSystem(SDL_INIT_TIMER);
_mbManager->checkGameState();
_gameCheckTimerId = SDL_AddTimer(2000,
[](UnsignedInt interval, void* param)->UnsignedInt{
static_cast<MassBuilderManager*>(param)->checkGameState();
return interval;
},
_mbManager.get());
if(_gameCheckTimerId == 0) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", SDL_GetError(), window());
exit(EXIT_FAILURE);
}
break;
case MbManagerFailure:
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising MassBuilderManager", _mbManager->lastError().c_str(), window());
exit(EXIT_FAILURE);
break; break;
case ProfileManagerFailure: case ProfileManagerFailure:
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising ProfileManager", _profileManager->lastError().c_str(), window()); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising ProfileManager", _profileManager->lastError().c_str(), window());
@ -307,7 +294,14 @@ void SaveTool::initialiseManager() {
SDL_zero(event); SDL_zero(event);
event.type = _initEventId; event.type = _initEventId;
_profileManager.emplace(_gameDataDir); _mbManager.emplace();
if(!_mbManager->ready()) {
event.user.code = MbManagerFailure;
SDL_PushEvent(&event);
return;
}
_profileManager.emplace(_mbManager->saveDirectory());
if(!_profileManager->ready()) { if(!_profileManager->ready()) {
event.user.code = ProfileManagerFailure; event.user.code = ProfileManagerFailure;
SDL_PushEvent(&event); SDL_PushEvent(&event);
@ -318,25 +312,6 @@ void SaveTool::initialiseManager() {
SDL_PushEvent(&event); SDL_PushEvent(&event);
} }
auto SaveTool::findGameDataDirectory() -> bool {
wchar_t* localappdata_path = nullptr;
Containers::ScopeGuard guard{localappdata_path, CoTaskMemFree};
if(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_NO_APPCONTAINER_REDIRECTION, nullptr, &localappdata_path) != S_OK)
{
_lastError = "SHGetKnownFolderPath() failed in SaveTool::findGameDataDirectory()";
return false;
}
_gameDataDir = Utility::Directory::join(Utility::Directory::fromNativeSeparators(Utility::Unicode::narrow(localappdata_path)), "MASS_Builder");
if(!Utility::Directory::exists(_gameDataDir)) {
_lastError = _gameDataDir + " wasn't found. Make sure to play the game at least once.";
return false;
}
return true;
}
void SaveTool::initialiseMassManager() { void SaveTool::initialiseMassManager() {
_currentProfile->refreshValues(); _currentProfile->refreshValues();
@ -479,7 +454,7 @@ void SaveTool::drawGameState() {
ImGui::TextUnformatted("Game state:"); ImGui::TextUnformatted("Game state:");
ImGui::SameLine(); ImGui::SameLine();
{ {
switch(_gameState) { switch(_mbManager->gameState()) {
case GameState::Unknown: case GameState::Unknown:
ImGui::TextColored(ImColor{0xff00a5ff}, ICON_FA_CIRCLE); ImGui::TextColored(ImColor{0xff00a5ff}, ICON_FA_CIRCLE);
drawTooltip("unknown"); drawTooltip("unknown");
@ -515,28 +490,18 @@ void SaveTool::drawTooltip(const char* text, Float wrap_pos) {
} }
} }
void SaveTool::drawUnsafeText(const char* text, ...) {
va_list args;
va_start(args, text);
if(!_unsafeMode && _mbManager->gameState() != GameState::NotRunning) {
ImGui::TextDisabledV(text, args);
}
else {
ImGui::TextV(text, args);
}
va_end(args);
}
void SaveTool::openUri(const std::string& uri) { void SaveTool::openUri(const std::string& uri) {
ShellExecuteW(nullptr, nullptr, Utility::Unicode::widen(uri).c_str(), nullptr, nullptr, SW_SHOW); ShellExecuteW(nullptr, nullptr, Utility::Unicode::widen(uri).c_str(), nullptr, nullptr, SW_SHOW);
} }
void SaveTool::checkGameState() {
WTS_PROCESS_INFOW* process_infos = nullptr;
unsigned long process_count = 0;
if(WTSEnumerateProcessesW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &process_infos, &process_count)) {
Containers::ScopeGuard guard{process_infos, WTSFreeMemory};
for(unsigned long i = 0; i < process_count; ++i) {
if(std::wcscmp(process_infos[i].pProcessName, L"MASS_Builder-Win64-Shipping.exe") == 0) {
_gameState = GameState::Running;
break;
}
else {
_gameState = GameState::NotRunning;
}
}
}
else {
_gameState = GameState::Unknown;
}
}

View file

@ -31,6 +31,7 @@
#include <efsw/efsw.hpp> #include <efsw/efsw.hpp>
#include "../MassBuilderManager/MassBuilderManager.h"
#include "../ProfileManager/ProfileManager.h" #include "../ProfileManager/ProfileManager.h"
#include "../MassManager/MassManager.h" #include "../MassManager/MassManager.h"
#include "../ResearchTree/ResearchTree.h" #include "../ResearchTree/ResearchTree.h"
@ -70,6 +71,7 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
enum InitStatus: Int { enum InitStatus: Int {
InitSuccess, InitSuccess,
MbManagerFailure,
ProfileManagerFailure ProfileManagerFailure
}; };
void initEvent(SDL_Event& event); void initEvent(SDL_Event& event);
@ -77,7 +79,6 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
// Initialisation methods // Initialisation methods
void initialiseGui(); void initialiseGui();
void initialiseManager(); void initialiseManager();
auto findGameDataDirectory() -> bool;
void initialiseMassManager(); void initialiseMassManager();
void initialiseFileWatcher(); void initialiseFileWatcher();
@ -110,7 +111,7 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
template<typename Functor, typename... Args> template<typename Functor, typename... Args>
auto drawUnsafeWidget(Functor func, Args... args) -> bool { auto drawUnsafeWidget(Functor func, Args... args) -> bool {
GameState game_state = _gameState; // Copying the value to reduce the risk of a data race. GameState game_state = _mbManager->gameState();
if(!_unsafeMode && game_state != GameState::NotRunning) { if(!_unsafeMode && game_state != GameState::NotRunning) {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f);
@ -127,20 +128,10 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
} // Obviously, should only be used with ImGui widgets that return a bool. } // Obviously, should only be used with ImGui widgets that return a bool.
// Also, func should be a lambda if there are any default arguments, like ImGui::Button(), etc... // Also, func should be a lambda if there are any default arguments, like ImGui::Button(), etc...
template<typename... Args> void drawUnsafeText(const char* text, ...); // Alternative to the above, for ImGui::Text*() variants.
void drawUnsafeText(const char* text, Args... args) { // Alternative to the above, for ImGui::Text*() variants.
if(!_unsafeMode && _gameState != GameState::NotRunning) {
ImGui::TextDisabled(text, std::forward<Args>(args)...);
}
else {
ImGui::Text(text, std::forward<Args>(args)...);
}
}
void openUri(const std::string& uri); void openUri(const std::string& uri);
void checkGameState();
Utility::Resource _rs{"assets"}; Utility::Resource _rs{"assets"};
// GUI-related members // GUI-related members
@ -151,7 +142,8 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
Initialising, Initialising,
ProfileManager, ProfileManager,
MainManager MainManager
} _uiState{UiState::Disclaimer}; };
UiState _uiState{UiState::Disclaimer};
bool _aboutPopup{false}; bool _aboutPopup{false};
#ifdef SAVETOOL_DEBUG_BUILD #ifdef SAVETOOL_DEBUG_BUILD
@ -163,18 +155,8 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener
std::thread _thread; std::thread _thread;
UnsignedInt _initEventId; UnsignedInt _initEventId;
std::string _lastError; Containers::Pointer<MassBuilderManager> _mbManager;
SDL_TimerID _gameCheckTimerId;
std::string _gameDataDir;
std::string _configDir;
std::string _saveDir;
std::string _screenshotsDir;
enum class GameState : UnsignedByte {
Unknown, NotRunning, Running
} _gameState{GameState::Unknown};
SDL_TimerID _gameCheckTimerId = 0;
Containers::Pointer<ProfileManager> _profileManager; Containers::Pointer<ProfileManager> _profileManager;
Profile* _currentProfile{nullptr}; Profile* _currentProfile{nullptr};

View file

@ -166,7 +166,7 @@ auto SaveTool::drawRenamePopup(Containers::ArrayView<char> name_view) -> bool {
ImGuiInputTextFlags_CallbackCharFilter, ImGuiInputTextFlags_CallbackCharFilter,
callback, nullptr); callback, nullptr);
ImGui::SameLine(); ImGui::SameLine();
GameState game_state = _gameState; GameState game_state = _mbManager->gameState();
if((!_unsafeMode && game_state != GameState::NotRunning) || if((!_unsafeMode && game_state != GameState::NotRunning) ||
!(len >= 6 && len <= 32) || !(len >= 6 && len <= 32) ||
!(name_view[0] != ' ' && name_view[len - 1] != ' ')) !(name_view[0] != ' ' && name_view[len - 1] != ' '))
@ -270,7 +270,7 @@ void SaveTool::drawGeneralInfo() {
} }
drawTooltip("Story progress directly affects unlocked levels."); drawTooltip("Story progress directly affects unlocked levels.");
if(ImGui::BeginPopup("StoryProgressMenu")) { if(ImGui::BeginPopup("StoryProgressMenu")) {
if(!_unsafeMode && _gameState != GameState::NotRunning) { if(!_unsafeMode && _mbManager->gameState() != GameState::NotRunning) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} }
for(const auto& sp : story_progress) { for(const auto& sp : story_progress) {
@ -564,7 +564,7 @@ void SaveTool::drawMassManager() {
ImGui::EndDragDropSource(); ImGui::EndDragDropSource();
} }
if((!_unsafeMode && _gameState == GameState::NotRunning) && ImGui::BeginDragDropTarget()) { if((!_unsafeMode && _mbManager->gameState() == GameState::NotRunning) && ImGui::BeginDragDropTarget()) {
if(const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("StagedMass")) { if(const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("StagedMass")) {
if(payload->DataSize != sizeof(std::string)) { if(payload->DataSize != sizeof(std::string)) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error", SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error",
@ -718,7 +718,7 @@ auto SaveTool::drawDeleteMassPopup(int mass_index) -> ImGuiID {
return 0; return 0;
} }
if(_gameState != GameState::NotRunning) { if(_mbManager->gameState() != GameState::NotRunning) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
ImGui::EndPopup(); ImGui::EndPopup();
return 0; return 0;

View file

@ -24,17 +24,18 @@
void SaveTool::drawMainMenu() { void SaveTool::drawMainMenu() {
if(ImGui::BeginMainMenuBar()) { if(ImGui::BeginMainMenuBar()) {
if(ImGui::BeginMenu("Save Tool##SaveToolMenu")) { if(ImGui::BeginMenu("Save Tool##SaveToolMenu")) {
if(ImGui::BeginMenu(ICON_FA_FOLDER_OPEN " Open game data directory", Utility::Directory::exists(_gameDataDir))) { if(ImGui::BeginMenu(ICON_FA_FOLDER_OPEN " Open data directory", _mbManager != nullptr)) {
if(ImGui::MenuItem(ICON_FA_COG " Configuration", nullptr, false, Utility::Directory::exists(_configDir))) { if(ImGui::MenuItem(ICON_FA_COG " Configuration", nullptr, false, _mbManager != nullptr)) {
openUri(Utility::Directory::toNativeSeparators(_configDir)); openUri(Utility::Directory::toNativeSeparators(_mbManager->saveDirectory() + "/Saved/Config/WindowsNoEditor"));
} }
if(ImGui::MenuItem(ICON_FA_SAVE " Saves", nullptr, false, Utility::Directory::exists(_saveDir))) { if(ImGui::MenuItem(ICON_FA_SAVE " Saves", nullptr, false, _profileManager != nullptr)) {
openUri(Utility::Directory::toNativeSeparators(_saveDir)); openUri(Utility::Directory::toNativeSeparators(_profileManager->saveDirectory()));
} }
if(ImGui::MenuItem(ICON_FA_IMAGE " Screenshots", nullptr, false, Utility::Directory::exists(_screenshotsDir))) { static bool _screenshotsAvailable = Utility::Directory::exists(_mbManager->saveDirectory() + "/Saved/Screenshots/WindowsNoEditor");
openUri(Utility::Directory::toNativeSeparators(_screenshotsDir)); if(ImGui::MenuItem(ICON_FA_IMAGE " Screenshots", nullptr, false, _screenshotsAvailable)) {
openUri(Utility::Directory::toNativeSeparators(_mbManager->saveDirectory() + "/Screenshots/WindowsNoEditor"));
} }
ImGui::EndMenu(); ImGui::EndMenu();
@ -104,7 +105,7 @@ void SaveTool::drawMainMenu() {
ImGui::EndMenu(); ImGui::EndMenu();
} }
if(_gameCheckTimerId != 0) { if(_mbManager != nullptr) {
if(ImGui::BeginTable("##MainMenuLayout", 2)) { if(ImGui::BeginTable("##MainMenuLayout", 2)) {
ImGui::TableSetupColumn("##Dummy", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("##Dummy", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("##GameState", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("##GameState", ImGuiTableColumnFlags_WidthFixed);