diff --git a/Runtime/CMakeLists.txt b/Runtime/CMakeLists.txt index 549fb1b81..3e7f32c44 100644 --- a/Runtime/CMakeLists.txt +++ b/Runtime/CMakeLists.txt @@ -137,7 +137,7 @@ endfunction() set(RUNTIME_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) set(RUNTIME_LIBRARIES ${HECL_APPLICATION_REPS_TARGETS_LIST} RetroDataSpec AssetNameMapNull NESEmulator - libjpeg-turbo jbus kabufuda discord-rpc logvisor OptickCore) + libjpeg-turbo jbus kabufuda discord-rpc logvisor OptickCore imgui) add_runtime_common_library(RuntimeCommon ${RUNTIME_SOURCES_A}) target_include_directories(RuntimeCommon PUBLIC ${RUNTIME_INCLUDES}) @@ -204,8 +204,8 @@ elseif(UNIX) set(PLAT_LIBS rt) endif() -add_executable(metaforce CMain.cpp ${PLAT_SRCS}) -target_link_libraries(metaforce PUBLIC RuntimeCommon ${PLAT_LIBS} imgui) +add_executable(metaforce CMain.cpp ${PLAT_SRCS} ImGuiConsole.hpp ImGuiConsole.cpp) +target_link_libraries(metaforce PUBLIC RuntimeCommon ${PLAT_LIBS}) if(COMMAND add_sanitizers) add_sanitizers(metaforce) diff --git a/Runtime/IMain.hpp b/Runtime/IMain.hpp index 19193ea69..c44456667 100644 --- a/Runtime/IMain.hpp +++ b/Runtime/IMain.hpp @@ -44,12 +44,15 @@ public: virtual size_t GetExpectedIdSize() const = 0; virtual void WarmupShaders() = 0; virtual hecl::Console* Console() const = 0; - virtual EGame GetGame() const =0; - virtual ERegion GetRegion() const =0; + virtual EGame GetGame() const = 0; + virtual ERegion GetRegion() const = 0; virtual bool IsPAL() const = 0; virtual bool IsJapanese() const = 0; virtual bool IsUSA() const = 0; virtual bool IsTrilogy() const = 0; - virtual std::string_view GetVersionString() const=0; + virtual std::string_view GetVersionString() const = 0; + virtual void Quit() = 0; + virtual bool IsPaused() const = 0; + virtual void SetPaused(bool b) = 0; }; } // namespace metaforce diff --git a/Runtime/ImGuiConsole.cpp b/Runtime/ImGuiConsole.cpp new file mode 100644 index 000000000..1e9d54574 --- /dev/null +++ b/Runtime/ImGuiConsole.cpp @@ -0,0 +1,153 @@ +#include "ImGuiConsole.hpp" + +#include "CStateManager.hpp" +#include "GameGlobalObjects.hpp" +#include "MP1/MP1.hpp" + +#include "imgui.h" + +#include "TCastTo.hpp" // Generated file, do not modify include path + +namespace metaforce { + +struct EntityInfo { + TUniqueId uid; + bool closed = false; + + explicit EntityInfo(TUniqueId uid) : uid(uid) {} +}; + +static std::vector inspectingEntities; + +static void ShowMenuFile() { + static bool paused; + paused = g_Main->IsPaused(); + if (ImGui::MenuItem("Paused", nullptr, &paused)) { + g_Main->SetPaused(paused); + } + if (ImGui::MenuItem("Quit", "Alt+F4")) { + g_Main->Quit(); + } +} + +static void ShowInspectWindow(bool* isOpen) { + if (ImGui::Begin("Inspect", isOpen)) { + if (ImGui::BeginTable("Entities", 3, + ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_RowBg | + ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ScrollY)) { + ImGui::TableSetupColumn("ID", + ImGuiTableColumnFlags_PreferSortAscending | ImGuiTableColumnFlags_DefaultSort | + ImGuiTableColumnFlags_WidthFixed, + 0, 'id'); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0, 'name'); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableHeadersRow(); + CObjectList& list = *g_StateManager->GetObjectList(); + std::vector items; + items.reserve(list.size()); + for (auto* ent : list) { + items.push_back(ent); + } + if (ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs()) { + for (int i = 0; i < sortSpecs->SpecsCount; ++i) { + const auto& specs = sortSpecs->Specs[i]; + if (specs.ColumnUserID == 'id') { + std::sort(items.begin(), items.end(), [&](CEntity* a, CEntity* b) { + u16 aId = a->GetUniqueId().Value(); + u16 bId = b->GetUniqueId().Value(); + return specs.SortDirection == ImGuiSortDirection_Ascending ? aId < bId : aId > bId; + }); + } else if (specs.ColumnUserID == 'name') { + std::sort(items.begin(), items.end(), [&](CEntity* a, CEntity* b) { + int compare = a->GetName().compare(b->GetName()); + return specs.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0; + }); + } + } + } + for (const auto& item : items) { + TUniqueId uid = item->GetUniqueId(); + ImGui::PushID(uid.Value()); + ImGui::TableNextRow(); + if (ImGui::TableNextColumn()) { + ImGui::Text("%x", uid.Value()); + } + if (ImGui::TableNextColumn()) { + ImGui::Text("%s", item->GetName().data()); + } + if (ImGui::TableNextColumn()) { + if (ImGui::SmallButton("View")) { + if (!std::any_of(inspectingEntities.begin(), inspectingEntities.end(), + [=](EntityInfo& v) { return v.uid == uid; })) { + inspectingEntities.emplace_back(uid); + } + } + } + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::End(); + } +} + +static bool showEntityInfoWindow(EntityInfo& info) { + CEntity* ent = g_StateManager->ObjectById(info.uid); + if (ent == nullptr) { + return true; + } + const char* name = "Entity"; + if (!ent->GetName().empty()) { + name = ent->GetName().data(); + } + ImGui::PushID(info.uid.Value()); + if (ImGui::Begin(name, &info.closed, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("ID: %x", info.uid.Value()); + ImGui::Text("Name: %s", ent->GetName().data()); + if (const TCastToPtr act = ent) { + const zeus::CVector3f& pos = act->GetTranslation(); + ImGui::Text("Position: %f, %f, %f", pos.x(), pos.y(), pos.z()); + } + ImGui::End(); + } + ImGui::PopID(); + return false; +} + +static void ShowAppMainMenuBar() { + static bool showInspectWindow = false; + static bool showDemoWindow = false; + if (ImGui::BeginMainMenuBar()) { + if (ImGui::BeginMenu("File")) { + ShowMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Tools")) { + ImGui::MenuItem("Inspect", nullptr, &showInspectWindow); + ImGui::Separator(); + ImGui::MenuItem("Demo", nullptr, &showDemoWindow); + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } + if (showInspectWindow) { + ShowInspectWindow(&showInspectWindow); + } + { + auto iter = inspectingEntities.begin(); + while (iter != inspectingEntities.end()) { + if (iter->closed || showEntityInfoWindow(*iter)) { + iter = inspectingEntities.erase(iter); + } else { + iter++; + } + } + } + if (showDemoWindow) { + ImGui::ShowDemoWindow(&showDemoWindow); + } +} + +void ImGuiConsole::proc() { ShowAppMainMenuBar(); } +} // namespace metaforce \ No newline at end of file diff --git a/Runtime/ImGuiConsole.hpp b/Runtime/ImGuiConsole.hpp new file mode 100644 index 000000000..0c2e61eeb --- /dev/null +++ b/Runtime/ImGuiConsole.hpp @@ -0,0 +1,8 @@ +#pragma once + +namespace metaforce { +class ImGuiConsole { +public: + void proc(); +}; +} diff --git a/Runtime/MP1/MP1.cpp b/Runtime/MP1/MP1.cpp index 080f7ab7a..b78392c5e 100644 --- a/Runtime/MP1/MP1.cpp +++ b/Runtime/MP1/MP1.cpp @@ -769,6 +769,7 @@ void CMain::Init(const hecl::Runtime::FileStoreManager& storeMgr, hecl::CVarMana "Warp"sv, "Warps to a given area and world"sv, "[worldname] areaId"sv, [this](hecl::Console* console, const std::vector& args) { Warp(console, args); }, hecl::SConsoleCommand::ECommandFlags::Normal); + m_imGuiConsole = std::make_unique(); bool loadedVersion = false; if (CDvdFile::FileExists("version.yaml")) { @@ -907,8 +908,9 @@ bool CMain::Proc(float dt) { m_loadedPersistentResources = true; } - m_console->proc(); - if (!m_console->isOpen()) { + m_imGuiConsole->proc(); + + if (!m_paused) { CGBASupport::GlobalPoll(); x164_archSupport->UpdateTicks(dt); x164_archSupport->Update(dt); diff --git a/Runtime/MP1/MP1.hpp b/Runtime/MP1/MP1.hpp index b53978ec1..0bb5215c9 100644 --- a/Runtime/MP1/MP1.hpp +++ b/Runtime/MP1/MP1.hpp @@ -15,6 +15,7 @@ #include "Runtime/Character/CAssetFactory.hpp" #include "Runtime/World/CAi.hpp" #include "Runtime/CGameState.hpp" +#include "Runtime/ImGuiConsole.hpp" #include "Runtime/MP1/CInGameTweakManager.hpp" #include "Runtime/Particle/CElementGen.hpp" #include "Runtime/Character/CAnimData.hpp" @@ -247,12 +248,14 @@ private: hecl::CVarManager* m_cvarMgr = nullptr; std::unique_ptr m_cvarCommons; std::unique_ptr m_console; + std::unique_ptr m_imGuiConsole; // Warmup state std::vector m_warmupTags; std::vector::iterator m_warmupIt; bool m_needsWarmupClear = false; bool m_loadedPersistentResources = false; bool m_doQuit = false; + bool m_paused = false; DataSpec::MetaforceVersionInfo m_version; void InitializeSubsystems(); @@ -328,6 +331,9 @@ public: ERegion GetRegion() const override { return m_version.region; } EGame GetGame() const override { return m_version.game; } std::string_view GetVersionString() const override { return m_version.version; } + void Quit() override { m_doQuit = true; } + bool IsPaused() const override { return m_paused; } + void SetPaused(bool b) override { m_paused = b; } int m_warpWorldIdx = -1; TAreaId m_warpAreaId = 0; diff --git a/imgui/CMakeLists.txt b/imgui/CMakeLists.txt index 9653d9664..3cad079e0 100644 --- a/imgui/CMakeLists.txt +++ b/imgui/CMakeLists.txt @@ -8,7 +8,7 @@ add_library(imgui ImGuiEngine.hpp NotoMono.cpp ) -target_include_directories(imgui PUBLIC ../extern/imgui ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(imgui PUBLIC ${CMAKE_SOURCE_DIR}/extern/imgui ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(imgui PUBLIC IMGUI_USER_CONFIG="imconfig_user.h") target_link_libraries(imgui PRIVATE boo hecl-light RetroDataSpec) diff --git a/imgui/ImGuiEngine.cpp b/imgui/ImGuiEngine.cpp index e43b8893a..8a909d9b3 100644 --- a/imgui/ImGuiEngine.cpp +++ b/imgui/ImGuiEngine.cpp @@ -182,7 +182,6 @@ void ImGuiEngine::Begin(float dt, float scale) { } ImGui::NewFrame(); - ImGui::ShowDemoWindow(); } void ImGuiEngine::End() {