mirror of
https://github.com/AxioDL/metaforce.git
synced 2025-05-13 16:31:21 +00:00
It's redundant now as ImGui is storing the position, it wasn't before so I'm not sure what changed for it to suddenly work, however let's not store things redundantly.
2028 lines
79 KiB
C++
2028 lines
79 KiB
C++
#include <zeus/CVector2f.hpp>
|
|
#define IM_VEC2_CLASS_EXTRA \
|
|
ImVec2(const zeus::CVector2f& v) { \
|
|
x = v.x(); \
|
|
y = v.y(); \
|
|
} \
|
|
operator zeus::CVector2f() const { return zeus::CVector2f{x, y}; }
|
|
#include "ImGuiConsole.hpp"
|
|
|
|
#include "../version.h"
|
|
#include "MP1/MP1.hpp"
|
|
#include "Runtime/CStateManager.hpp"
|
|
#include "Runtime/GameGlobalObjects.hpp"
|
|
#include "Runtime/ImGuiEntitySupport.hpp"
|
|
#include "Runtime/World/CPlayer.hpp"
|
|
|
|
#include "ImGuiEngine.hpp"
|
|
#include "magic_enum.hpp"
|
|
#ifdef NATIVEFILEDIALOG_SUPPORTED
|
|
#include <nfd.hpp>
|
|
#endif
|
|
|
|
#include <cstdarg>
|
|
|
|
#include <zeus/CEulerAngles.hpp>
|
|
|
|
namespace ImGui {
|
|
// Internal functions
|
|
void ClearIniSettings();
|
|
} // namespace ImGui
|
|
|
|
namespace aurora::gfx {
|
|
extern std::atomic_uint32_t queuedPipelines;
|
|
extern std::atomic_uint32_t createdPipelines;
|
|
|
|
extern size_t g_drawCallCount;
|
|
extern size_t g_mergedDrawCallCount;
|
|
extern size_t g_lastVertSize;
|
|
extern size_t g_lastUniformSize;
|
|
extern size_t g_lastIndexSize;
|
|
extern size_t g_lastStorageSize;
|
|
} // namespace aurora::gfx
|
|
|
|
#include "TCastTo.hpp" // Generated file, do not modify include path
|
|
|
|
namespace metaforce {
|
|
static logvisor::Module Log{"Console"};
|
|
|
|
std::array<ImGuiEntityEntry, kMaxEntities> ImGuiConsole::entities;
|
|
std::set<TUniqueId> ImGuiConsole::inspectingEntities;
|
|
ImGuiPlayerLoadouts ImGuiConsole::loadouts;
|
|
|
|
ImGuiConsole::ImGuiConsole(CVarManager& cvarMgr, CVarCommons& cvarCommons)
|
|
: m_cvarMgr(cvarMgr), m_cvarCommons(cvarCommons) {
|
|
#ifdef NATIVEFILEDIALOG_SUPPORTED
|
|
NFD::Init();
|
|
#endif
|
|
}
|
|
|
|
void ImGuiStringViewText(std::string_view text) {
|
|
// begin()/end() do not work on MSVC
|
|
ImGui::TextUnformatted(text.data(), text.data() + text.size());
|
|
}
|
|
|
|
void ImGuiTextCenter(std::string_view text) {
|
|
ImGui::NewLine();
|
|
float fontSize = ImGui::CalcTextSize(text.data(), text.data() + text.size()).x;
|
|
ImGui::SameLine(ImGui::GetWindowSize().x / 2 - fontSize + fontSize / 2);
|
|
ImGuiStringViewText(text);
|
|
}
|
|
|
|
bool ImGuiButtonCenter(std::string_view text) {
|
|
ImGui::NewLine();
|
|
float fontSize = ImGui::CalcTextSize(text.data(), text.data() + text.size()).x;
|
|
fontSize += ImGui::GetStyle().FramePadding.x;
|
|
ImGui::SameLine(ImGui::GetWindowSize().x / 2 - fontSize + fontSize / 2);
|
|
return ImGui::Button(text.data());
|
|
}
|
|
|
|
static std::unordered_map<CAssetId, std::unique_ptr<CDummyWorld>> dummyWorlds;
|
|
static std::unordered_map<CAssetId, TCachedToken<CStringTable>> stringTables;
|
|
|
|
std::string ImGuiLoadStringTable(CAssetId stringId, int idx) {
|
|
if (!stringId.IsValid()) {
|
|
return ""s;
|
|
}
|
|
if (!stringTables.contains(stringId)) {
|
|
stringTables[stringId] = g_SimplePool->GetObj(SObjectTag{SBIG('STRG'), stringId});
|
|
}
|
|
return CStringExtras::ConvertToUTF8(stringTables[stringId].GetObj()->GetString(idx));
|
|
}
|
|
|
|
static bool ContainsCaseInsensitive(std::string_view str, std::string_view val) {
|
|
return std::search(str.begin(), str.end(), val.begin(), val.end(),
|
|
[](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }) != str.end();
|
|
}
|
|
|
|
static std::vector<std::pair<std::string, CAssetId>> ListWorlds() {
|
|
std::vector<std::pair<std::string, CAssetId>> worlds;
|
|
for (const auto& pak : g_ResFactory->GetResLoader()->GetPaks()) {
|
|
if (!pak->IsWorldPak()) {
|
|
continue;
|
|
}
|
|
CAssetId worldId = pak->GetMLVLId();
|
|
if (!dummyWorlds.contains(worldId)) {
|
|
dummyWorlds[worldId] = std::make_unique<CDummyWorld>(worldId, false);
|
|
}
|
|
auto& world = dummyWorlds[worldId];
|
|
bool complete = world->ICheckWorldComplete();
|
|
if (!complete) {
|
|
continue;
|
|
}
|
|
CAssetId stringId = world->IGetStringTableAssetId();
|
|
if (!stringId.IsValid()) {
|
|
continue;
|
|
}
|
|
worlds.emplace_back(ImGuiLoadStringTable(stringId, 0), worldId);
|
|
}
|
|
return worlds;
|
|
}
|
|
|
|
static std::vector<std::pair<std::string, TAreaId>> ListAreas(CAssetId worldId) {
|
|
std::vector<std::pair<std::string, TAreaId>> areas;
|
|
const auto& world = dummyWorlds[worldId];
|
|
for (TAreaId i = 0; i < world->IGetAreaCount(); ++i) {
|
|
const auto* area = world->IGetAreaAlways(i);
|
|
if (area == nullptr) {
|
|
continue;
|
|
}
|
|
CAssetId stringId = area->IGetStringTableAssetId();
|
|
if (!stringId.IsValid()) {
|
|
continue;
|
|
}
|
|
areas.emplace_back(ImGuiLoadStringTable(stringId, 0), i);
|
|
}
|
|
return areas;
|
|
}
|
|
|
|
static void Warp(const CAssetId worldId, TAreaId aId) {
|
|
g_GameState->SetCurrentWorldId(worldId);
|
|
g_GameState->GetWorldTransitionManager()->DisableTransition();
|
|
if (aId >= g_GameState->CurrentWorldState().GetLayerState()->GetAreaCount()) {
|
|
aId = 0;
|
|
}
|
|
g_GameState->CurrentWorldState().SetAreaId(aId);
|
|
g_Main->SetFlowState(EClientFlowStates::None);
|
|
if (g_StateManager != nullptr) {
|
|
g_StateManager->SetWarping(true);
|
|
g_StateManager->SetShouldQuitGame(true);
|
|
} else {
|
|
// TODO(encounter): warp from menu?
|
|
}
|
|
}
|
|
|
|
static inline float GetScale() { return ImGui::GetIO().DisplayFramebufferScale.x; }
|
|
|
|
void ImGuiConsole::ShowMenuGame() {
|
|
if (g_Main != nullptr) {
|
|
m_paused = g_Main->IsPaused();
|
|
}
|
|
if (ImGui::MenuItem("Paused", "F5", &m_paused, g_Main != nullptr)) {
|
|
g_Main->SetPaused(m_paused);
|
|
}
|
|
if (ImGui::MenuItem("Step Frame", "F6", &m_stepFrame, m_paused)) {
|
|
g_Main->SetPaused(false);
|
|
}
|
|
if (ImGui::BeginMenu("Warp", m_cheats && g_StateManager != nullptr && g_ResFactory != nullptr &&
|
|
g_ResFactory->GetResLoader() != nullptr)) {
|
|
for (const auto& world : ListWorlds()) {
|
|
if (ImGui::BeginMenu(world.first.c_str())) {
|
|
for (const auto& area : ListAreas(world.second)) {
|
|
if (ImGui::MenuItem(area.first.c_str())) {
|
|
Warp(world.second, area.second);
|
|
}
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::MenuItem("Quit", "Alt+F4")) {
|
|
m_quitRequested = true;
|
|
}
|
|
}
|
|
|
|
void ImGuiConsole::LerpDebugColor(CActor* act) {
|
|
if (!act->m_debugSelected && !act->m_debugHovered) {
|
|
act->m_debugAddColorTime = 0.f;
|
|
act->m_debugAddColor = zeus::skClear;
|
|
return;
|
|
}
|
|
act->m_debugAddColorTime += ImGui::GetIO().DeltaTime;
|
|
float lerp = act->m_debugAddColorTime;
|
|
if (lerp > 2.f) {
|
|
lerp = 0.f;
|
|
act->m_debugAddColorTime = 0.f;
|
|
} else if (lerp > 1.f) {
|
|
lerp = 2.f - lerp;
|
|
}
|
|
act->m_debugAddColor = zeus::CColor::lerp(zeus::skClear, zeus::skBlue, lerp);
|
|
}
|
|
|
|
void ImGuiConsole::UpdateEntityEntries() {
|
|
CObjectList& list = g_StateManager->GetAllObjectList();
|
|
s16 uid = list.GetFirstObjectIndex();
|
|
while (uid != -1) {
|
|
ImGuiEntityEntry& entry = ImGuiConsole::entities[uid];
|
|
if (entry.uid == kInvalidUniqueId || entry.ent == nullptr) {
|
|
CEntity* ent = list.GetObjectByIndex(uid);
|
|
entry.uid = ent->GetUniqueId();
|
|
entry.ent = ent;
|
|
entry.type = ent->ImGuiType();
|
|
entry.name = ent->GetName();
|
|
entry.isActor = TCastToPtr<CActor>(ent).IsValid();
|
|
} else {
|
|
entry.active = entry.ent->GetActive();
|
|
}
|
|
if (entry.isActor) {
|
|
LerpDebugColor(entry.AsActor());
|
|
}
|
|
entry.ent->m_debugHovered = false;
|
|
uid = list.GetNextObjectIndex(uid);
|
|
}
|
|
}
|
|
|
|
void ImGuiConsole::BeginEntityRow(const ImGuiEntityEntry& entry) {
|
|
ImGui::PushID(entry.uid.Value());
|
|
ImGui::TableNextRow();
|
|
bool isActive = entry.active;
|
|
|
|
ImVec4 textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
|
|
if (!isActive) {
|
|
textColor.w = 0.5f;
|
|
}
|
|
ImGui::PushStyleColor(ImGuiCol_Text, textColor);
|
|
|
|
if (ImGui::TableNextColumn()) {
|
|
auto text = fmt::format(FMT_STRING("0x{:04X}"), entry.uid.Value());
|
|
ImGui::Selectable(text.c_str(), &entry.ent->m_debugSelected,
|
|
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap);
|
|
if (ImGui::IsItemHovered()) {
|
|
entry.ent->m_debugHovered = true;
|
|
}
|
|
|
|
if (ImGui::BeginPopupContextItem(text.c_str())) {
|
|
ImGui::PopStyleColor();
|
|
if (ImGui::MenuItem(isActive ? "Deactivate" : "Activate")) {
|
|
entry.ent->SetActive(!isActive);
|
|
}
|
|
if (ImGui::MenuItem("Highlight", nullptr, &entry.ent->m_debugSelected)) {
|
|
entry.ent->SetActive(!isActive);
|
|
}
|
|
// Only allow deletion if none of the objects are player related
|
|
// The player objects will always be in the first 6 slots
|
|
if (entry.uid.Value() > 6) {
|
|
ImGui::Separator();
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{0.77f, 0.12f, 0.23f, 1.f});
|
|
if (ImGui::MenuItem("Delete")) {
|
|
g_StateManager->FreeScriptObject(entry.uid);
|
|
}
|
|
ImGui::PopStyleColor();
|
|
}
|
|
ImGui::EndPopup();
|
|
ImGui::PushStyleColor(ImGuiCol_Text, textColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImGuiConsole::EndEntityRow(const ImGuiEntityEntry& entry) {
|
|
ImGui::PopStyleColor();
|
|
if (ImGui::TableNextColumn()) {
|
|
if (ImGui::SmallButton("View")) {
|
|
ImGuiConsole::inspectingEntities.insert(entry.uid);
|
|
}
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
static void RenderEntityColumns(const ImGuiEntityEntry& entry) {
|
|
ImGuiConsole::BeginEntityRow(entry);
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText(entry.type);
|
|
}
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText(entry.name);
|
|
}
|
|
ImGuiConsole::EndEntityRow(entry);
|
|
}
|
|
|
|
void ImGuiConsole::ShowInspectWindow(bool* isOpen) {
|
|
float initialWindowSize = 400.f * GetScale();
|
|
ImGui::SetNextWindowSize(ImVec2{initialWindowSize, initialWindowSize * 1.5f}, ImGuiCond_FirstUseEver);
|
|
|
|
if (ImGui::Begin("Inspect", isOpen)) {
|
|
CObjectList& list = g_StateManager->GetAllObjectList();
|
|
ImGui::Text("Objects: %d / %d", list.size(), kMaxEntities);
|
|
ImGui::SameLine();
|
|
if (ImGui::SmallButton("Deselect all")) {
|
|
for (auto* const ent : list) {
|
|
ent->m_debugSelected = false;
|
|
}
|
|
}
|
|
if (ImGui::Button("Clear")) {
|
|
m_inspectFilterText.clear();
|
|
}
|
|
ImGui::SameLine();
|
|
ImGui::InputText("Filter", &m_inspectFilterText);
|
|
ImGui::Checkbox("Active", &m_inspectActiveOnly);
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("Current area", &m_inspectCurrentAreaOnly);
|
|
|
|
if (ImGui::BeginTable("Entities", 4,
|
|
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("Type", ImGuiTableColumnFlags_WidthFixed, 0, 'type');
|
|
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0, 'name');
|
|
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed |
|
|
ImGuiTableColumnFlags_NoResize);
|
|
ImGui::TableSetupScrollFreeze(0, 1);
|
|
ImGui::TableHeadersRow();
|
|
|
|
ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();
|
|
bool hasSortSpec = sortSpecs != nullptr &&
|
|
// no multi-sort
|
|
sortSpecs->SpecsCount == 1 &&
|
|
// We can skip sorting if we just want uid ascending,
|
|
// since that's how we iterate over CObjectList
|
|
(sortSpecs->Specs[0].ColumnUserID != 'id' ||
|
|
sortSpecs->Specs[0].SortDirection != ImGuiSortDirection_Ascending);
|
|
if (!m_inspectFilterText.empty() || m_inspectActiveOnly || m_inspectCurrentAreaOnly || hasSortSpec) {
|
|
std::vector<s16> sortedList;
|
|
sortedList.reserve(list.size());
|
|
s16 uid = list.GetFirstObjectIndex();
|
|
|
|
auto currAreaId = kInvalidAreaId;
|
|
CPlayer* player = nullptr;
|
|
if (m_inspectCurrentAreaOnly && (player = g_StateManager->Player()) != nullptr) {
|
|
currAreaId = player->GetAreaIdAlways();
|
|
}
|
|
|
|
while (uid != -1) {
|
|
ImGuiEntityEntry& entry = ImGuiConsole::entities[uid];
|
|
if ((!m_inspectActiveOnly || entry.active) &&
|
|
(!m_inspectCurrentAreaOnly || entry.ent->x4_areaId == currAreaId) &&
|
|
(m_inspectFilterText.empty() || ContainsCaseInsensitive(entry.type, m_inspectFilterText) ||
|
|
ContainsCaseInsensitive(entry.name, m_inspectFilterText))) {
|
|
sortedList.push_back(uid);
|
|
}
|
|
uid = list.GetNextObjectIndex(uid);
|
|
}
|
|
if (hasSortSpec) {
|
|
const auto& spec = sortSpecs->Specs[0];
|
|
if (spec.ColumnUserID == 'id') {
|
|
if (spec.SortDirection == ImGuiSortDirection_Ascending) {
|
|
// no-op
|
|
} else {
|
|
std::sort(sortedList.begin(), sortedList.end(), [&](s16 a, s16 b) { return a < b; });
|
|
}
|
|
} else if (spec.ColumnUserID == 'name') {
|
|
std::sort(sortedList.begin(), sortedList.end(), [&](s16 a, s16 b) {
|
|
int compare = ImGuiConsole::entities[a].name.compare(ImGuiConsole::entities[b].name);
|
|
return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0;
|
|
});
|
|
} else if (spec.ColumnUserID == 'type') {
|
|
std::sort(sortedList.begin(), sortedList.end(), [&](s16 a, s16 b) {
|
|
int compare = ImGuiConsole::entities[a].type.compare(ImGuiConsole::entities[b].type);
|
|
return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0;
|
|
});
|
|
}
|
|
}
|
|
for (const auto& item : sortedList) {
|
|
RenderEntityColumns(ImGuiConsole::entities[item]);
|
|
}
|
|
} else {
|
|
// Render uid ascending
|
|
s16 uid = list.GetFirstObjectIndex();
|
|
while (uid != -1) {
|
|
RenderEntityColumns(ImGuiConsole::entities[uid]);
|
|
uid = list.GetNextObjectIndex(uid);
|
|
}
|
|
}
|
|
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
bool ImGuiConsole::ShowEntityInfoWindow(TUniqueId uid) {
|
|
bool open = true;
|
|
ImGuiEntityEntry& entry = ImGuiConsole::entities[uid.Value()];
|
|
if (entry.ent == nullptr) {
|
|
return false;
|
|
}
|
|
auto name = fmt::format(FMT_STRING("{}##0x{:04X}"), !entry.name.empty() ? entry.name : entry.type, uid.Value());
|
|
if (ImGui::Begin(name.c_str(), &open, ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
ImGui::PushID(uid.Value());
|
|
entry.ent->ImGuiInspect();
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::End();
|
|
return open;
|
|
}
|
|
|
|
void ImGuiConsole::ShowConsoleVariablesWindow() {
|
|
// For some reason the window shows up tiny without this
|
|
float initialWindowSize = 350.f * GetScale();
|
|
ImGui::SetNextWindowSize(ImVec2{initialWindowSize, initialWindowSize}, ImGuiCond_FirstUseEver);
|
|
if (ImGui::Begin("Console Variables", &m_showConsoleVariablesWindow)) {
|
|
if (ImGui::Button("Clear")) {
|
|
m_cvarFiltersText.clear();
|
|
}
|
|
ImGui::SameLine();
|
|
ImGui::InputText("Filter", &m_cvarFiltersText);
|
|
auto cvars = m_cvarMgr.cvars(CVar::EFlags::Any & ~CVar::EFlags::Hidden);
|
|
if (ImGui::Button("Reset to defaults")) {
|
|
for (auto* cv : cvars) {
|
|
if (cv->name() == "developer" || cv->name() == "cheats") {
|
|
// don't reset developer or cheats to default
|
|
continue;
|
|
}
|
|
CVarUnlocker l(cv);
|
|
cv->fromLiteralToType(cv->defaultValue());
|
|
}
|
|
}
|
|
if (ImGui::BeginTable("ConsoleVariables", 2,
|
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_RowBg |
|
|
ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ScrollY)) {
|
|
ImGui::TableSetupColumn("Name",
|
|
ImGuiTableColumnFlags_PreferSortAscending | ImGuiTableColumnFlags_DefaultSort |
|
|
ImGuiTableColumnFlags_WidthFixed,
|
|
0, 'name');
|
|
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0, 'val');
|
|
ImGui::TableSetupScrollFreeze(0, 1);
|
|
ImGui::TableHeadersRow();
|
|
|
|
ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();
|
|
bool hasSortSpec = sortSpecs != nullptr &&
|
|
// no multi-sort
|
|
sortSpecs->SpecsCount == 1;
|
|
std::vector<CVar*> sortedList;
|
|
sortedList.reserve(cvars.size());
|
|
|
|
for (auto* cvar : cvars) {
|
|
if (cvar->isHidden()) {
|
|
continue;
|
|
}
|
|
if (!m_cvarFiltersText.empty()) {
|
|
if (ContainsCaseInsensitive(magic_enum::enum_name(cvar->type()), m_cvarFiltersText) ||
|
|
ContainsCaseInsensitive(cvar->name(), m_cvarFiltersText)) {
|
|
sortedList.push_back(cvar);
|
|
}
|
|
} else {
|
|
sortedList.push_back(cvar);
|
|
}
|
|
}
|
|
|
|
if (hasSortSpec) {
|
|
const auto& spec = sortSpecs->Specs[0];
|
|
if (spec.ColumnUserID == 'name') {
|
|
std::sort(sortedList.begin(), sortedList.end(), [&](CVar* a, CVar* b) {
|
|
int compare = a->name().compare(b->name());
|
|
return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0;
|
|
});
|
|
} else if (spec.ColumnUserID == 'val') {
|
|
std::sort(sortedList.begin(), sortedList.end(), [&](CVar* a, CVar* b) {
|
|
int compare = a->value().compare(b->value());
|
|
return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0;
|
|
});
|
|
}
|
|
|
|
for (auto* cv : sortedList) {
|
|
bool modified = cv->isModified();
|
|
ImGui::PushID(cv);
|
|
ImGui::TableNextRow();
|
|
// Name
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText(cv->name());
|
|
if (ImGui::IsItemHovered() && !cv->rawHelp().empty()) {
|
|
std::string sv(cv->rawHelp());
|
|
ImGui::SetTooltip("%s", sv.c_str());
|
|
}
|
|
}
|
|
// Value
|
|
if (ImGui::TableNextColumn()) {
|
|
switch (cv->type()) {
|
|
case CVar::EType::Boolean: {
|
|
bool b = cv->toBoolean();
|
|
if (ImGui::Checkbox("", &b)) {
|
|
cv->fromBoolean(b);
|
|
modified = true;
|
|
}
|
|
break;
|
|
}
|
|
case CVar::EType::Real: {
|
|
float f = cv->toReal();
|
|
if (ImGui::DragFloat("", &f)) {
|
|
cv->fromReal(f);
|
|
modified = true;
|
|
}
|
|
break;
|
|
}
|
|
case CVar::EType::Signed: {
|
|
std::array<s32, 1> i{cv->toSigned()};
|
|
if (ImGui::DragScalar("", ImGuiDataType_S32, i.data(), i.size())) {
|
|
cv->fromInteger(i[0]);
|
|
modified = true;
|
|
}
|
|
break;
|
|
}
|
|
case CVar::EType::Unsigned: {
|
|
std::array<u32, 1> i{cv->toUnsigned()};
|
|
if (ImGui::DragScalar("", ImGuiDataType_U32, i.data(), i.size())) {
|
|
cv->fromInteger(i[0]);
|
|
modified = true;
|
|
}
|
|
break;
|
|
}
|
|
case CVar::EType::Literal: {
|
|
char buf[4096];
|
|
strcpy(buf, cv->value().c_str());
|
|
if (ImGui::InputText("", buf, 4096, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
cv->fromLiteral(buf);
|
|
modified = true;
|
|
}
|
|
break;
|
|
}
|
|
case CVar::EType::Vec2f: {
|
|
auto vec = cv->toVec2f();
|
|
std::array<float, 2> scalars = {vec.x(), vec.y()};
|
|
if (ImGui::DragScalarN("", ImGuiDataType_Float, scalars.data(), scalars.size(), 0.1f)) {
|
|
vec.x() = scalars[0];
|
|
vec.y() = scalars[1];
|
|
cv->fromVec2f(vec);
|
|
modified = true;
|
|
}
|
|
break;
|
|
}
|
|
case CVar::EType::Vec2d: {
|
|
auto vec = cv->toVec2d();
|
|
std::array<double, 2> scalars = {vec.x(), vec.y()};
|
|
if (ImGui::DragScalarN("", ImGuiDataType_Double, scalars.data(), scalars.size(), 0.1f)) {
|
|
vec.x() = scalars[0];
|
|
vec.y() = scalars[1];
|
|
cv->fromVec2d(vec);
|
|
modified = true;
|
|
}
|
|
break;
|
|
}
|
|
case CVar::EType::Vec3f: {
|
|
auto vec = cv->toVec3f();
|
|
std::array<float, 3> scalars = {vec.x(), vec.y(), vec.z()};
|
|
if (cv->isColor()) {
|
|
if (ImGui::ColorEdit3("", scalars.data())) {
|
|
vec.x() = scalars[0];
|
|
vec.y() = scalars[1];
|
|
vec.z() = scalars[2];
|
|
cv->fromVec3f(vec);
|
|
modified = true;
|
|
}
|
|
} else if (ImGui::DragScalarN("", ImGuiDataType_Float, scalars.data(), scalars.size(), 0.1f)) {
|
|
vec.x() = scalars[0];
|
|
vec.y() = scalars[1];
|
|
vec.z() = scalars[2];
|
|
cv->fromVec3f(vec);
|
|
modified = true;
|
|
}
|
|
break;
|
|
}
|
|
case CVar::EType::Vec3d: {
|
|
auto vec = cv->toVec3d();
|
|
std::array<double, 3> scalars = {vec.x(), vec.y(), vec.z()};
|
|
if (cv->isColor()) {
|
|
std::array<float, 3> color{static_cast<float>(scalars[0]), static_cast<float>(scalars[1]),
|
|
static_cast<float>(scalars[2])};
|
|
if (ImGui::ColorEdit3("", color.data())) {
|
|
vec.x() = scalars[0];
|
|
vec.y() = scalars[1];
|
|
vec.z() = scalars[2];
|
|
cv->fromVec3d(vec);
|
|
modified = true;
|
|
}
|
|
} else if (ImGui::DragScalarN("", ImGuiDataType_Double, scalars.data(), scalars.size(), 0.1f)) {
|
|
vec.x() = scalars[0];
|
|
vec.y() = scalars[1];
|
|
vec.z() = scalars[2];
|
|
cv->fromVec3d(vec);
|
|
modified = true;
|
|
}
|
|
break;
|
|
}
|
|
case CVar::EType::Vec4f: {
|
|
auto vec = cv->toVec4f();
|
|
std::array<float, 4> scalars = {vec.x(), vec.y(), vec.z(), vec.w()};
|
|
if (cv->isColor()) {
|
|
if (ImGui::ColorEdit4("", scalars.data())) {
|
|
vec.x() = scalars[0];
|
|
vec.y() = scalars[1];
|
|
vec.z() = scalars[2];
|
|
vec.w() = scalars[2];
|
|
cv->fromVec4f(vec);
|
|
modified = true;
|
|
}
|
|
} else if (ImGui::DragScalarN("", ImGuiDataType_Float, scalars.data(), scalars.size(), 0.1f)) {
|
|
vec.x() = scalars[0];
|
|
vec.y() = scalars[1];
|
|
vec.z() = scalars[2];
|
|
vec.w() = scalars[2];
|
|
cv->fromVec4f(vec);
|
|
modified = true;
|
|
}
|
|
break;
|
|
}
|
|
case CVar::EType::Vec4d: {
|
|
auto vec = cv->toVec4d();
|
|
std::array<double, 4> scalars = {vec.x(), vec.y(), vec.z(), vec.w()};
|
|
if (cv->isColor()) {
|
|
std::array<float, 4> color{static_cast<float>(scalars[0]), static_cast<float>(scalars[1]),
|
|
static_cast<float>(scalars[2]), static_cast<float>(scalars[3])};
|
|
if (ImGui::ColorEdit4("", color.data())) {
|
|
vec.x() = scalars[0];
|
|
vec.y() = scalars[1];
|
|
vec.z() = scalars[2];
|
|
vec.w() = scalars[2];
|
|
cv->fromVec4d(vec);
|
|
modified = true;
|
|
}
|
|
} else if (ImGui::DragScalarN("", ImGuiDataType_Double, scalars.data(), scalars.size(), 0.1f)) {
|
|
vec.x() = scalars[0];
|
|
vec.y() = scalars[1];
|
|
vec.z() = scalars[2];
|
|
vec.w() = scalars[2];
|
|
cv->fromVec4d(vec);
|
|
modified = true;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
ImGui::Text("lawl wut? Please contact a developer, your copy of Metaforce is cursed!");
|
|
break;
|
|
}
|
|
if (modified && cv->modificationRequiresRestart()) {
|
|
ImGui::Text("Restart required for value to take affect!");
|
|
}
|
|
if (ImGui::IsItemHovered()) {
|
|
std::string sv(cv->defaultValue());
|
|
ImGui::SetTooltip("Default: %s", sv.c_str());
|
|
}
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void ImGuiConsole::ShowAboutWindow(bool preLaunch) {
|
|
// Center window
|
|
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
|
ImGui::SetNextWindowPos(center, preLaunch ? ImGuiCond_Always : ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
|
|
|
ImVec4& windowBg = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
|
|
ImGui::PushStyleColor(ImGuiCol_TitleBg, windowBg);
|
|
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, windowBg);
|
|
|
|
bool* open = nullptr;
|
|
ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoNav |
|
|
ImGuiWindowFlags_NoSavedSettings;
|
|
if (preLaunch) {
|
|
flags |= ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove;
|
|
} else {
|
|
open = &m_showAboutWindow;
|
|
}
|
|
if (ImGui::Begin("About", open, flags)) {
|
|
float iconSize = 128.f * GetScale();
|
|
ImGui::SameLine(ImGui::GetWindowSize().x / 2 - iconSize + (iconSize / 2));
|
|
ImGui::Image(ImGuiEngine::metaforceIcon, ImVec2{iconSize, iconSize});
|
|
ImGui::PushFont(ImGuiEngine::fontLarge);
|
|
ImGuiTextCenter("Metaforce");
|
|
ImGui::PopFont();
|
|
ImGuiTextCenter(METAFORCE_WC_DESCRIBE);
|
|
const ImVec2& padding = ImGui::GetStyle().WindowPadding;
|
|
ImGui::Dummy(padding);
|
|
if (preLaunch) {
|
|
if (ImGuiButtonCenter("Settings")) {
|
|
m_showPreLaunchSettingsWindow = true;
|
|
}
|
|
#ifdef NATIVEFILEDIALOG_SUPPORTED
|
|
ImGui::Dummy(padding);
|
|
if (ImGuiButtonCenter("Select Game")) {
|
|
NFD::UniquePathU8 outPath;
|
|
nfdresult_t nfdResult = NFD::OpenDialog(outPath, nullptr, 0, nullptr);
|
|
if (nfdResult == NFD_OKAY) {
|
|
m_gameDiscSelected = outPath.get();
|
|
} else if (nfdResult != NFD_CANCEL) {
|
|
Log.report(logvisor::Error, FMT_STRING("nativefiledialog error: {}"), NFD::GetError());
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef EMSCRIPTEN
|
|
if (ImGuiButtonCenter("Load Game")) {
|
|
m_gameDiscSelected = "game.iso";
|
|
}
|
|
#else
|
|
if (!m_lastDiscPath.empty()) {
|
|
if (ImGuiButtonCenter("Load Previous Game")) {
|
|
m_gameDiscSelected = m_lastDiscPath;
|
|
}
|
|
}
|
|
#endif
|
|
ImGui::Dummy(padding);
|
|
}
|
|
if (m_errorString) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{0.77f, 0.12f, 0.23f, 1.f});
|
|
ImGuiTextCenter(*m_errorString);
|
|
ImGui::PopStyleColor();
|
|
ImGui::Dummy(padding);
|
|
}
|
|
ImGuiTextCenter("2015-2022");
|
|
ImGui::BeginGroup();
|
|
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 200));
|
|
ImGuiStringViewText("Development & Research");
|
|
ImGui::PopStyleColor();
|
|
ImGuiStringViewText("Phillip Stephens (Antidote)");
|
|
ImGuiStringViewText("Jack Andersen (jackoalan)");
|
|
ImGuiStringViewText("Luke Street (encounter)");
|
|
ImGuiStringViewText("Lioncache");
|
|
ImGui::EndGroup();
|
|
ImGui::SameLine();
|
|
ImGui::BeginGroup();
|
|
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 200));
|
|
ImGuiStringViewText("Testing");
|
|
ImGui::PopStyleColor();
|
|
ImGuiStringViewText("Tom Lube");
|
|
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 200));
|
|
ImGuiStringViewText("Contributions");
|
|
ImGui::PopStyleColor();
|
|
ImGuiStringViewText("Darkszero (Profiling)");
|
|
ImGuiStringViewText("shio (Weapons)");
|
|
ImGui::EndGroup();
|
|
ImGui::Dummy(padding);
|
|
ImGui::Separator();
|
|
if (ImGui::BeginTable("Version Info", 2, ImGuiTableFlags_BordersInnerV)) {
|
|
ImGui::TableNextRow();
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText("Branch");
|
|
}
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText(METAFORCE_WC_BRANCH);
|
|
}
|
|
ImGui::TableNextRow();
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText("Revision");
|
|
}
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText(METAFORCE_WC_REVISION);
|
|
}
|
|
ImGui::TableNextRow();
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText("Build");
|
|
}
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText(METAFORCE_DLPACKAGE);
|
|
}
|
|
ImGui::TableNextRow();
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText("Date");
|
|
}
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText(METAFORCE_WC_DATE);
|
|
}
|
|
ImGui::TableNextRow();
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText("Type");
|
|
}
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText(METAFORCE_BUILD_TYPE);
|
|
}
|
|
if (g_Main != nullptr) {
|
|
ImGui::TableNextRow();
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText("Game");
|
|
}
|
|
if (ImGui::TableNextColumn()) {
|
|
ImGuiStringViewText(g_Main->GetVersionString());
|
|
}
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
ImGui::PopStyleColor(2);
|
|
}
|
|
|
|
static std::string BytesToString(size_t bytes) {
|
|
constexpr std::array suffixes{"B"sv, "KB"sv, "MB"sv, "GB"sv, "TB"sv, "PB"sv, "EB"sv};
|
|
u32 s = 0;
|
|
auto count = static_cast<double>(bytes);
|
|
while (count >= 1024.0 && s < 7) {
|
|
s++;
|
|
count /= 1024.0;
|
|
}
|
|
if (count - floor(count) == 0.0) {
|
|
return fmt::format(FMT_STRING("{}{}"), static_cast<size_t>(count), suffixes[s]);
|
|
}
|
|
return fmt::format(FMT_STRING("{:.1f}{}"), count, suffixes[s]);
|
|
}
|
|
|
|
void ImGuiConsole::ShowDebugOverlay() {
|
|
if (!m_developer && !m_frameCounter && !m_frameRate && !m_inGameTime && !m_roomTimer) {
|
|
return;
|
|
}
|
|
if (!m_playerInfo && !m_areaInfo && !m_worldInfo && !m_randomStats && !m_resourceStats && !m_pipelineInfo &&
|
|
!m_drawCallInfo && !m_bufferInfo) {
|
|
return;
|
|
}
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
|
|
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;
|
|
if (m_debugOverlayCorner != -1) {
|
|
SetOverlayWindowLocation(m_debugOverlayCorner);
|
|
windowFlags |= ImGuiWindowFlags_NoMove;
|
|
}
|
|
ImGui::SetNextWindowBgAlpha(0.65f);
|
|
if (ImGui::Begin("Debug Overlay", nullptr, windowFlags)) {
|
|
bool hasPrevious = false;
|
|
if (m_frameCounter && g_StateManager != nullptr) {
|
|
ImGuiStringViewText(fmt::format(FMT_STRING("Frame: {}\n"), g_StateManager->GetUpdateFrameIndex()));
|
|
hasPrevious = true;
|
|
}
|
|
if (m_frameRate) {
|
|
if (hasPrevious) {
|
|
ImGui::Separator();
|
|
}
|
|
hasPrevious = true;
|
|
|
|
ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {:.1f}\n"), io.Framerate));
|
|
}
|
|
if (m_inGameTime && g_GameState != nullptr) {
|
|
if (hasPrevious) {
|
|
ImGui::Separator();
|
|
}
|
|
hasPrevious = true;
|
|
|
|
double igt = g_GameState->GetTotalPlayTime();
|
|
u32 ms = u64(igt * 1000) % 1000;
|
|
auto pt = std::div(int(igt), 3600);
|
|
ImGuiStringViewText(
|
|
fmt::format(FMT_STRING("Play Time: {:02d}:{:02d}:{:02d}.{:03d}\n"), pt.quot, pt.rem / 60, pt.rem % 60, ms));
|
|
}
|
|
if (m_roomTimer && g_StateManager != nullptr) {
|
|
if (hasPrevious) {
|
|
ImGui::Separator();
|
|
}
|
|
hasPrevious = true;
|
|
|
|
double igt = g_GameState->GetTotalPlayTime();
|
|
double currentRoomTime = igt - m_currentRoomStart;
|
|
u32 curFrames = u32(std::round(u32(currentRoomTime * 60)));
|
|
u32 lastFrames = u32(std::round(u32(m_lastRoomTime * 60)));
|
|
ImGuiStringViewText(fmt::format(FMT_STRING("Room Time: {:7.3f} / {:5d} | Last Room:{:7.3f} / {:5d}\n"),
|
|
currentRoomTime, curFrames, m_lastRoomTime, lastFrames));
|
|
}
|
|
if (m_playerInfo && g_StateManager != nullptr && g_StateManager->Player() != nullptr && m_developer) {
|
|
if (hasPrevious) {
|
|
ImGui::Separator();
|
|
}
|
|
hasPrevious = true;
|
|
|
|
const CPlayer& pl = g_StateManager->GetPlayer();
|
|
const zeus::CQuaternion plQ = zeus::CQuaternion(pl.GetTransform().getRotation().buildMatrix3f());
|
|
const zeus::CTransform camXf = g_StateManager->GetCameraManager()->GetCurrentCameraTransform(*g_StateManager);
|
|
const zeus::CQuaternion camQ = zeus::CQuaternion(camXf.getRotation().buildMatrix3f());
|
|
ImGuiStringViewText(
|
|
fmt::format(FMT_STRING("Player Position x: {: .2f}, y: {: .2f}, z: {: .2f}\n"
|
|
" Roll: {: .2f}, Pitch: {: .2f}, Yaw: {: .2f}\n"
|
|
" Momentum x: {: .2f}, y: {: .2f}, z: {: .2f}\n"
|
|
" Velocity x: {: .2f}, y: {: .2f}, z: {: .2f}\n"
|
|
"Camera Position x: {: .2f}, y: {: .2f}, z {: .2f}\n"
|
|
" Roll: {: .2f}, Pitch: {: .2f}, Yaw: {: .2f}\n"),
|
|
pl.GetTranslation().x(), pl.GetTranslation().y(), pl.GetTranslation().z(),
|
|
zeus::radToDeg(plQ.roll()), zeus::radToDeg(plQ.pitch()), zeus::radToDeg(plQ.yaw()),
|
|
pl.GetMomentum().x(), pl.GetMomentum().y(), pl.GetMomentum().z(), pl.GetVelocity().x(),
|
|
pl.GetVelocity().y(), pl.GetVelocity().z(), camXf.origin.x(), camXf.origin.y(), camXf.origin.z(),
|
|
zeus::radToDeg(camQ.roll()), zeus::radToDeg(camQ.pitch()), zeus::radToDeg(camQ.yaw())));
|
|
}
|
|
if (m_worldInfo && g_StateManager != nullptr && m_developer) {
|
|
if (hasPrevious) {
|
|
ImGui::Separator();
|
|
}
|
|
hasPrevious = true;
|
|
|
|
const std::string name = ImGuiLoadStringTable(g_StateManager->GetWorld()->IGetStringTableAssetId(), 0);
|
|
ImGuiStringViewText(
|
|
fmt::format(FMT_STRING("World Asset ID: 0x{}, Name: {}\n"), g_GameState->CurrentWorldAssetId(), name));
|
|
}
|
|
if (m_areaInfo && g_StateManager != nullptr && m_developer) {
|
|
const metaforce::TAreaId aId = g_GameState->CurrentWorldState().GetCurrentAreaId();
|
|
if (g_StateManager->GetWorld() != nullptr && g_StateManager->GetWorld()->DoesAreaExist(aId)) {
|
|
if (hasPrevious) {
|
|
ImGui::Separator();
|
|
}
|
|
hasPrevious = true;
|
|
|
|
const auto& layerStates = g_GameState->CurrentWorldState().GetLayerState();
|
|
std::string layerBits;
|
|
u32 totalActive = 0;
|
|
for (int i = 0; i < layerStates->GetAreaLayerCount(aId); ++i) {
|
|
if (layerStates->IsLayerActive(aId, i)) {
|
|
++totalActive;
|
|
layerBits += "1";
|
|
} else {
|
|
layerBits += "0";
|
|
}
|
|
}
|
|
CGameArea* pArea = g_StateManager->GetWorld()->GetArea(aId);
|
|
CAssetId stringId = pArea->IGetStringTableAssetId();
|
|
ImGuiStringViewText(
|
|
fmt::format(FMT_STRING("Area Asset ID: 0x{}, Name: {}\nArea ID: {}, Active Layer bits: {}\n"),
|
|
pArea->GetAreaAssetId(), ImGuiLoadStringTable(stringId, 0), pArea->GetAreaId(), layerBits));
|
|
}
|
|
}
|
|
if (m_layerInfo && g_StateManager != nullptr && m_developer) {
|
|
const metaforce::TAreaId aId = g_GameState->CurrentWorldState().GetCurrentAreaId();
|
|
const auto* world = g_StateManager->GetWorld();
|
|
if (world != nullptr && world->DoesAreaExist(aId) && world->GetWorldLayers()) {
|
|
if (hasPrevious) {
|
|
ImGui::Separator();
|
|
}
|
|
hasPrevious = true;
|
|
|
|
ImGuiStringViewText("Area Layers:");
|
|
|
|
ImVec4 activeColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
|
|
ImVec4 inactiveColor = activeColor;
|
|
inactiveColor.w = 0.5f;
|
|
|
|
const CWorldLayers& layers = world->GetWorldLayers().value();
|
|
const auto& layerStates = g_GameState->CurrentWorldState().GetLayerState();
|
|
int layerCount = int(layerStates->GetAreaLayerCount(aId));
|
|
u32 startNameIdx = layers.m_areas[aId].m_startNameIdx;
|
|
if (startNameIdx + layerCount > layers.m_names.size()) {
|
|
ImGui::Text("Broken layer data, please re-package");
|
|
} else {
|
|
for (int i = 0; i < layerCount; ++i) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, layerStates->IsLayerActive(aId, i) ? activeColor : inactiveColor);
|
|
ImGuiStringViewText(" " + layers.m_names[startNameIdx + i]);
|
|
ImGui::PopStyleColor();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (m_randomStats && m_developer) {
|
|
if (hasPrevious) {
|
|
ImGui::Separator();
|
|
}
|
|
hasPrevious = true;
|
|
|
|
ImGuiStringViewText(
|
|
fmt::format(FMT_STRING("CRandom16::Next calls: {}\n"), metaforce::CRandom16::GetNumNextCalls()));
|
|
ImGuiStringViewText(fmt::format(FMT_STRING("CRandom16::LastSeed: 0x{:08X}\n"), CRandom16::GetLastSeed()));
|
|
}
|
|
if (m_resourceStats && g_SimplePool != nullptr) {
|
|
if (hasPrevious) {
|
|
ImGui::Separator();
|
|
}
|
|
hasPrevious = true;
|
|
|
|
ImGuiStringViewText(fmt::format(FMT_STRING("Resource Objects: {}\n"), g_SimplePool->GetLiveObjects()));
|
|
}
|
|
if (m_pipelineInfo && m_developer) {
|
|
if (hasPrevious) {
|
|
ImGui::Separator();
|
|
}
|
|
hasPrevious = true;
|
|
|
|
ImGuiStringViewText(fmt::format(FMT_STRING("Queued pipelines: {}\n"), aurora::gfx::queuedPipelines));
|
|
ImGuiStringViewText(fmt::format(FMT_STRING("Done pipelines: {}\n"), aurora::gfx::createdPipelines));
|
|
}
|
|
if (m_drawCallInfo && m_developer) {
|
|
if (hasPrevious) {
|
|
ImGui::Separator();
|
|
}
|
|
hasPrevious = true;
|
|
|
|
ImGuiStringViewText(fmt::format(FMT_STRING("Draw call count: {}\n"), aurora::gfx::g_drawCallCount));
|
|
ImGuiStringViewText(fmt::format(FMT_STRING("Merged draw calls: {}\n"), aurora::gfx::g_mergedDrawCallCount));
|
|
}
|
|
if (m_bufferInfo && m_developer) {
|
|
if (hasPrevious) {
|
|
ImGui::Separator();
|
|
}
|
|
hasPrevious = true;
|
|
|
|
ImGuiStringViewText(
|
|
fmt::format(FMT_STRING("Vertex size: {}\n"), BytesToString(aurora::gfx::g_lastVertSize)));
|
|
ImGuiStringViewText(
|
|
fmt::format(FMT_STRING("Uniform size: {}\n"), BytesToString(aurora::gfx::g_lastUniformSize)));
|
|
ImGuiStringViewText(
|
|
fmt::format(FMT_STRING("Index size: {}\n"), BytesToString(aurora::gfx::g_lastIndexSize)));
|
|
ImGuiStringViewText(
|
|
fmt::format(FMT_STRING("Storage size: {}\n"), BytesToString(aurora::gfx::g_lastStorageSize)));
|
|
ImGuiStringViewText(fmt::format(FMT_STRING("Total: {}\n"),
|
|
BytesToString(aurora::gfx::g_lastVertSize + aurora::gfx::g_lastUniformSize +
|
|
aurora::gfx::g_lastIndexSize + aurora::gfx::g_lastStorageSize)));
|
|
}
|
|
if (ShowCornerContextMenu(m_debugOverlayCorner, m_inputOverlayCorner)) {
|
|
m_cvarCommons.m_debugOverlayCorner->fromInteger(m_debugOverlayCorner);
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
void TextCenter(const std::string& text) {
|
|
float font_size = ImGui::GetFontSize() * text.size() / 2;
|
|
ImGui::SameLine(ImGui::GetWindowSize().x / 2 - font_size + (font_size / 2));
|
|
|
|
ImGui::TextUnformatted(text.c_str());
|
|
}
|
|
|
|
void ImGuiConsole::ShowInputViewer() {
|
|
if (!m_showInput || g_InputGenerator == nullptr) {
|
|
return;
|
|
}
|
|
auto input = g_InputGenerator->GetLastInput();
|
|
if (input.ControllerIdx() != 0) {
|
|
return;
|
|
}
|
|
|
|
u32 thisWhich = input.ControllerIdx();
|
|
if (m_whichController != thisWhich) {
|
|
const char* name = PADGetName(thisWhich);
|
|
if (name != nullptr) {
|
|
m_controllerName = name;
|
|
m_whichController = thisWhich;
|
|
}
|
|
}
|
|
|
|
// Code -stolen- borrowed from Practice Mod
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
|
|
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;
|
|
if (m_inputOverlayCorner != -1) {
|
|
SetOverlayWindowLocation(m_inputOverlayCorner);
|
|
windowFlags |= ImGuiWindowFlags_NoMove;
|
|
}
|
|
|
|
ImGui::SetNextWindowBgAlpha(0.65f);
|
|
if (ImGui::Begin("Input Overlay", nullptr, windowFlags)) {
|
|
float scale = GetScale();
|
|
if (!m_controllerName.empty()) {
|
|
TextCenter(m_controllerName);
|
|
ImGui::Separator();
|
|
}
|
|
ImDrawList* dl = ImGui::GetWindowDrawList();
|
|
zeus::CVector2f p = ImGui::GetCursorScreenPos();
|
|
|
|
float leftStickRadius = 30 * scale;
|
|
p = p + zeus::CVector2f{20, 20} * scale; // Pad p so we don't clip outside our rect
|
|
zeus::CVector2f leftStickCenter = p + zeus::CVector2f(30, 45) * scale;
|
|
float dpadRadius = 15 * scale;
|
|
float dpadWidth = 8 * scale;
|
|
zeus::CVector2f dpadCenter = p + zeus::CVector2f(80, 90) * scale;
|
|
float rightStickRadius = 20 * scale;
|
|
zeus::CVector2f rightStickCenter = p + zeus::CVector2f(160, 90) * scale;
|
|
float startButtonRadius = 8 * scale;
|
|
zeus::CVector2f startButtonCenter = p + zeus::CVector2f(120, 55) * scale;
|
|
float aButtonRadius = 16 * scale;
|
|
zeus::CVector2f aButtonCenter = p + zeus::CVector2f(210, 48) * scale;
|
|
float bButtonRadius = 8 * scale;
|
|
zeus::CVector2f bButtonCenter = aButtonCenter + zeus::CVector2f(-24, 16) * scale;
|
|
float xButtonRadius = 8 * scale;
|
|
zeus::CVector2f xButtonCenter = aButtonCenter + zeus::CVector2f(24, -16) * scale;
|
|
float yButtonRadius = 8 * scale;
|
|
zeus::CVector2f yButtonCenter = aButtonCenter + zeus::CVector2f(-12, -24) * scale;
|
|
float triggerWidth = leftStickRadius * 2;
|
|
float triggerHeight = 8 * scale;
|
|
zeus::CVector2f lCenter = leftStickCenter + zeus::CVector2f(0, -60) * scale;
|
|
zeus::CVector2f rCenter = zeus::CVector2f(aButtonCenter.x(), lCenter.y());
|
|
const auto zButtonCenter = rCenter + zeus::CVector2f{0, 24 * scale};
|
|
const float zButtonHalfWidth = triggerWidth / 2;
|
|
const float zButtonHalfHeight = 4 * scale;
|
|
|
|
constexpr ImU32 stickGray = IM_COL32(150, 150, 150, 255);
|
|
constexpr ImU32 darkGray = IM_COL32(60, 60, 60, 255);
|
|
constexpr ImU32 red = IM_COL32(255, 0, 0, 255);
|
|
constexpr ImU32 green = IM_COL32(0, 255, 0, 255);
|
|
|
|
// left stick
|
|
{
|
|
float x = input.ALeftX();
|
|
float y = -input.ALeftY();
|
|
dl->AddCircleFilled(leftStickCenter, leftStickRadius, stickGray, 8);
|
|
dl->AddLine(leftStickCenter, leftStickCenter + zeus::CVector2f(x * leftStickRadius, y * leftStickRadius),
|
|
IM_COL32(255, 244, 0, 255), 1.5f);
|
|
dl->AddCircleFilled(leftStickCenter + (zeus::CVector2f{x, y} * leftStickRadius), leftStickRadius / 3, red);
|
|
}
|
|
|
|
// right stick
|
|
{
|
|
float x = input.ARightX();
|
|
float y = -input.ARightY();
|
|
dl->AddCircleFilled(rightStickCenter, rightStickRadius, stickGray, 8);
|
|
dl->AddLine(rightStickCenter, rightStickCenter + zeus::CVector2f(x * rightStickRadius, y * rightStickRadius),
|
|
IM_COL32(255, 244, 0, 255), 1.5f);
|
|
dl->AddCircleFilled(rightStickCenter + (zeus::CVector2f{x, y} * rightStickRadius), rightStickRadius / 3, red);
|
|
}
|
|
|
|
// dpad
|
|
{
|
|
float halfWidth = dpadWidth / 2;
|
|
dl->AddRectFilled(dpadCenter + zeus::CVector2f(-halfWidth, -dpadRadius),
|
|
dpadCenter + zeus::CVector2f(halfWidth, dpadRadius), stickGray);
|
|
|
|
dl->AddRectFilled(dpadCenter + zeus::CVector2f(-dpadRadius, -halfWidth),
|
|
dpadCenter + zeus::CVector2f(dpadRadius, halfWidth), stickGray);
|
|
|
|
if (input.DDPUp()) {
|
|
dl->AddRectFilled(dpadCenter + zeus::CVector2f(-halfWidth, -dpadRadius),
|
|
dpadCenter + zeus::CVector2f(halfWidth, -dpadRadius / 2), red);
|
|
}
|
|
|
|
if (input.DDPDown()) {
|
|
dl->AddRectFilled(dpadCenter + zeus::CVector2f(-halfWidth, dpadRadius),
|
|
dpadCenter + zeus::CVector2f(halfWidth, dpadRadius / 2), red);
|
|
}
|
|
|
|
if (input.DDPLeft()) {
|
|
dl->AddRectFilled(dpadCenter + zeus::CVector2f(-dpadRadius, -halfWidth),
|
|
dpadCenter + zeus::CVector2f(-dpadRadius / 2, halfWidth), red);
|
|
}
|
|
|
|
if (input.DDPRight()) {
|
|
dl->AddRectFilled(dpadCenter + zeus::CVector2f(dpadRadius, -halfWidth),
|
|
dpadCenter + zeus::CVector2f(dpadRadius / 2, halfWidth), red);
|
|
}
|
|
}
|
|
|
|
// buttons
|
|
{
|
|
// start
|
|
dl->AddCircleFilled(startButtonCenter, startButtonRadius, input.DStart() ? red : stickGray);
|
|
|
|
// a
|
|
dl->AddCircleFilled(aButtonCenter, aButtonRadius, input.DA() ? green : stickGray);
|
|
|
|
// b
|
|
dl->AddCircleFilled(bButtonCenter, bButtonRadius, input.DB() ? red : stickGray);
|
|
|
|
// x
|
|
dl->AddCircleFilled(xButtonCenter, xButtonRadius, input.DX() ? red : stickGray);
|
|
|
|
// y
|
|
dl->AddCircleFilled(yButtonCenter, yButtonRadius, input.DY() ? red : stickGray);
|
|
|
|
// z
|
|
dl->AddRectFilled(zButtonCenter - zeus::CVector2f{zButtonHalfWidth, zButtonHalfHeight},
|
|
zButtonCenter + zeus::CVector2f{zButtonHalfWidth, zButtonHalfHeight},
|
|
input.DZ() ? IM_COL32(128, 0, 128, 255) : stickGray, 16);
|
|
}
|
|
|
|
// triggers
|
|
{
|
|
float halfTriggerWidth = triggerWidth / 2;
|
|
zeus::CVector2f lStart = lCenter - zeus::CVector2f(halfTriggerWidth, 0);
|
|
zeus::CVector2f lEnd = lCenter + zeus::CVector2f(halfTriggerWidth, triggerHeight);
|
|
float lValue = triggerWidth * std::min(1.f, input.ALTrigger());
|
|
|
|
dl->AddRectFilled(lStart, lStart + zeus::CVector2f(lValue, triggerHeight), input.DL() ? red : stickGray);
|
|
dl->AddRectFilled(lStart + zeus::CVector2f(lValue, 0), lEnd, darkGray);
|
|
|
|
zeus::CVector2f rStart = rCenter - zeus::CVector2f(halfTriggerWidth, 0);
|
|
zeus::CVector2f rEnd = rCenter + zeus::CVector2f(halfTriggerWidth, triggerHeight);
|
|
float rValue = triggerWidth * std::min(1.f, input.ARTrigger());
|
|
|
|
dl->AddRectFilled(rEnd - zeus::CVector2f(rValue, triggerHeight), rEnd, input.DR() ? red : stickGray);
|
|
dl->AddRectFilled(rStart, rEnd - zeus::CVector2f(rValue, 0), darkGray);
|
|
}
|
|
|
|
ImGui::Dummy(zeus::CVector2f(270, 130) * scale);
|
|
if (ShowCornerContextMenu(m_inputOverlayCorner, m_debugOverlayCorner)) {
|
|
m_cvarCommons.m_debugInputOverlayCorner->fromInteger(m_inputOverlayCorner);
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
bool ImGuiConsole::ShowCornerContextMenu(int& corner, int avoidCorner) const {
|
|
bool result = false;
|
|
if (ImGui::BeginPopupContextWindow()) {
|
|
if (ImGui::MenuItem("Custom", nullptr, corner == -1)) {
|
|
corner = -1;
|
|
result = true;
|
|
}
|
|
if (ImGui::MenuItem("Top-left", nullptr, corner == 0, avoidCorner != 0)) {
|
|
corner = 0;
|
|
result = true;
|
|
}
|
|
if (ImGui::MenuItem("Top-right", nullptr, corner == 1, avoidCorner != 1)) {
|
|
corner = 1;
|
|
result = true;
|
|
}
|
|
if (ImGui::MenuItem("Bottom-left", nullptr, corner == 2, avoidCorner != 2)) {
|
|
corner = 2;
|
|
result = true;
|
|
}
|
|
if (ImGui::MenuItem("Bottom-right", nullptr, corner == 3, avoidCorner != 3)) {
|
|
corner = 3;
|
|
result = true;
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void ImGuiConsole::SetOverlayWindowLocation(int corner) const {
|
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
ImVec2 workPos = viewport->WorkPos; // Use work area to avoid menu-bar/task-bar, if any!
|
|
ImVec2 workSize = viewport->WorkSize;
|
|
ImVec2 windowPos;
|
|
ImVec2 windowPosPivot;
|
|
constexpr float padding = 10.0f;
|
|
windowPos.x = (corner & 1) != 0 ? (workPos.x + workSize.x - padding) : (workPos.x + padding);
|
|
windowPos.y = (corner & 2) != 0 ? (workPos.y + workSize.y - padding) : (workPos.y + padding);
|
|
windowPosPivot.x = (corner & 1) != 0 ? 1.0f : 0.0f;
|
|
windowPosPivot.y = (corner & 2) != 0 ? 1.0f : 0.0f;
|
|
ImGui::SetNextWindowPos(windowPos, ImGuiCond_Always, windowPosPivot);
|
|
}
|
|
|
|
static void ImGuiCVarMenuItem(const char* name, CVar* cvar, bool& value) {
|
|
if (cvar == nullptr) {
|
|
return;
|
|
}
|
|
if (ImGui::MenuItem(name, nullptr, &value)) {
|
|
cvar->fromBoolean(value);
|
|
}
|
|
if (ImGui::IsItemHovered()) {
|
|
std::string tooltip{cvar->rawHelp()};
|
|
if (!tooltip.empty()) {
|
|
ImGui::SetTooltip("%s", tooltip.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImGuiConsole::ShowAppMainMenuBar(bool canInspect, bool preLaunch) {
|
|
if (ImGui::BeginMainMenuBar()) {
|
|
if (ImGui::BeginMenu("Game")) {
|
|
ShowMenuGame();
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::BeginMenu("Tools")) {
|
|
ImGui::MenuItem("Controller Config", nullptr, &m_controllerConfigVisible);
|
|
ImGui::MenuItem("Items", nullptr, &m_showItemsWindow, canInspect && m_cheats);
|
|
if (m_developer) {
|
|
ImGui::Separator();
|
|
ImGui::MenuItem("Console Variables", nullptr, &m_showConsoleVariablesWindow);
|
|
ImGui::MenuItem("Inspect", nullptr, &m_showInspectWindow, canInspect);
|
|
ImGui::MenuItem("Layers", nullptr, &m_showLayersWindow, canInspect);
|
|
ImGui::MenuItem("Player Transform", nullptr, &m_showPlayerTransformEditor, canInspect && m_cheats);
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::BeginMenu("Overlays")) {
|
|
ImGuiCVarMenuItem("Frame Counter", m_cvarCommons.m_debugOverlayShowFrameCounter, m_frameCounter);
|
|
ImGuiCVarMenuItem("Frame Rate", m_cvarCommons.m_debugOverlayShowFramerate, m_frameRate);
|
|
ImGuiCVarMenuItem("In-Game Time", m_cvarCommons.m_debugOverlayShowInGameTime, m_inGameTime);
|
|
ImGuiCVarMenuItem("Room Timer", m_cvarCommons.m_debugOverlayShowRoomTimer, m_roomTimer);
|
|
ImGuiCVarMenuItem("Player Info", m_cvarCommons.m_debugOverlayPlayerInfo, m_playerInfo);
|
|
ImGuiCVarMenuItem("World Info", m_cvarCommons.m_debugOverlayWorldInfo, m_worldInfo);
|
|
ImGuiCVarMenuItem("Area Info", m_cvarCommons.m_debugOverlayAreaInfo, m_areaInfo);
|
|
ImGuiCVarMenuItem("Layer Info", m_cvarCommons.m_debugOverlayLayerInfo, m_layerInfo);
|
|
ImGuiCVarMenuItem("Random Stats", m_cvarCommons.m_debugOverlayShowRandomStats, m_randomStats);
|
|
ImGuiCVarMenuItem("Draw Call Info", m_cvarCommons.m_debugOverlayDrawCallInfo, m_drawCallInfo);
|
|
ImGuiCVarMenuItem("Pipeline Info", m_cvarCommons.m_debugOverlayPipelineInfo, m_pipelineInfo);
|
|
ImGuiCVarMenuItem("Buffer Info", m_cvarCommons.m_debugOverlayBufferInfo, m_bufferInfo);
|
|
ImGuiCVarMenuItem("Resource Stats", m_cvarCommons.m_debugOverlayShowResourceStats, m_resourceStats);
|
|
ImGuiCVarMenuItem("Show Input", m_cvarCommons.m_debugOverlayShowInput, m_showInput);
|
|
#if 0 // Currently unimplemented
|
|
ImGui::Separator();
|
|
ImGuiCVarMenuItem("Draw AI Paths", m_cvarCommons.m_debugToolDrawAiPath, m_drawAiPath);
|
|
ImGuiCVarMenuItem("Draw Lighting", m_cvarCommons.m_debugToolDrawLighting, m_drawLighting);
|
|
ImGuiCVarMenuItem("Draw Collision Actors", m_cvarCommons.m_debugToolDrawCollisionActors, m_drawCollisionActors);
|
|
ImGuiCVarMenuItem("Draw Maze Path", m_cvarCommons.m_debugToolDrawMazePath, m_drawMazePath);
|
|
ImGuiCVarMenuItem("Draw Platform Collision", m_cvarCommons.m_debugToolDrawPlatformCollision,
|
|
m_drawPlatformCollision);
|
|
#endif
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::BeginMenu("Help")) {
|
|
ImGui::MenuItem("About", nullptr, &m_showAboutWindow, !preLaunch);
|
|
if (m_developer) {
|
|
ImGui::Separator();
|
|
if (ImGui::BeginMenu("ImGui")) {
|
|
if (ImGui::MenuItem("Clear Settings")) {
|
|
ImGui::ClearIniSettings();
|
|
}
|
|
#ifndef NDEBUG
|
|
ImGui::MenuItem("Show Demo", nullptr, &m_showDemoWindow);
|
|
#endif
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
ImGui::EndMainMenuBar();
|
|
}
|
|
}
|
|
|
|
void ImGuiConsole::ToggleVisible() {
|
|
if (g_Main != nullptr) {
|
|
m_isVisible ^= 1;
|
|
}
|
|
}
|
|
|
|
void ImGuiConsole::PreUpdate() {
|
|
OPTICK_EVENT();
|
|
bool preLaunch = g_Main == nullptr;
|
|
if (!m_isInitialized) {
|
|
m_isInitialized = true;
|
|
m_cvarCommons.m_debugOverlayShowFrameCounter->addListener([this](CVar* c) { m_frameCounter = c->toBoolean(); });
|
|
m_cvarCommons.m_debugOverlayShowFramerate->addListener([this](CVar* c) { m_frameRate = c->toBoolean(); });
|
|
m_cvarCommons.m_debugOverlayShowInGameTime->addListener([this](CVar* c) { m_inGameTime = c->toBoolean(); });
|
|
m_cvarCommons.m_debugOverlayShowRoomTimer->addListener([this](CVar* c) { m_roomTimer = c->toBoolean(); });
|
|
m_cvarCommons.m_debugOverlayPlayerInfo->addListener([this](CVar* c) { m_playerInfo = c->toBoolean(); });
|
|
m_cvarCommons.m_debugOverlayWorldInfo->addListener([this](CVar* c) { m_worldInfo = c->toBoolean(); });
|
|
m_cvarCommons.m_debugOverlayAreaInfo->addListener([this](CVar* c) { m_areaInfo = c->toBoolean(); });
|
|
m_cvarCommons.m_debugOverlayLayerInfo->addListener([this](CVar* c) { m_layerInfo = c->toBoolean(); });
|
|
m_cvarCommons.m_debugOverlayShowRandomStats->addListener([this](CVar* c) { m_randomStats = c->toBoolean(); });
|
|
m_cvarCommons.m_debugOverlayShowResourceStats->addListener([this](CVar* c) { m_resourceStats = c->toBoolean(); });
|
|
m_cvarCommons.m_debugOverlayShowInput->addListener([this](CVar* c) { m_showInput = c->toBoolean(); });
|
|
m_cvarCommons.m_debugToolDrawAiPath->addListener([this](CVar* c) { m_drawAiPath = c->toBoolean(); });
|
|
m_cvarCommons.m_debugToolDrawCollisionActors->addListener(
|
|
[this](CVar* c) { m_drawCollisionActors = c->toBoolean(); });
|
|
m_cvarCommons.m_debugToolDrawPlatformCollision->addListener(
|
|
[this](CVar* c) { m_drawPlatformCollision = c->toBoolean(); });
|
|
m_cvarCommons.m_debugToolDrawMazePath->addListener([this](CVar* c) { m_drawMazePath = c->toBoolean(); });
|
|
m_cvarCommons.m_debugToolDrawLighting->addListener([this](CVar* c) { m_drawLighting = c->toBoolean(); });
|
|
m_cvarCommons.m_debugOverlayCorner->addListener([this](CVar* c) { m_debugOverlayCorner = c->toSigned(); });
|
|
m_cvarCommons.m_debugInputOverlayCorner->addListener([this](CVar* c) { m_inputOverlayCorner = c->toSigned(); });
|
|
m_cvarCommons.m_lastDiscPath->addListener([this](CVar* c) { m_lastDiscPath = c->toLiteral(); });
|
|
m_cvarMgr.findCVar("developer")->addListener([this](CVar* c) { m_developer = c->toBoolean(); });
|
|
m_cvarMgr.findCVar("cheats")->addListener([this](CVar* c) { m_cheats = c->toBoolean(); });
|
|
}
|
|
if (!preLaunch && !m_isLaunchInitialized) {
|
|
if (m_developer) {
|
|
m_toasts.emplace_back("Press Left Alt to toggle menu"s, 5.f);
|
|
}
|
|
m_isLaunchInitialized = true;
|
|
}
|
|
// We need to make sure we have a valid CRandom16 at all times, so let's do that here
|
|
if (g_StateManager != nullptr && g_StateManager->GetActiveRandom() == nullptr) {
|
|
g_StateManager->SetActiveRandomToDefault();
|
|
}
|
|
|
|
if (!preLaunch) {
|
|
if (m_stepFrame) {
|
|
g_Main->SetPaused(true);
|
|
m_stepFrame = false;
|
|
}
|
|
if (m_paused && !m_stepFrame && ImGui::IsKeyPressed(ImGuiKey_F6)) {
|
|
g_Main->SetPaused(false);
|
|
m_stepFrame = true;
|
|
}
|
|
if (ImGui::IsKeyReleased(ImGuiKey_F5)) {
|
|
m_paused ^= 1;
|
|
g_Main->SetPaused(m_paused);
|
|
}
|
|
}
|
|
bool canInspect = g_StateManager != nullptr && g_StateManager->GetObjectList();
|
|
if (preLaunch || m_isVisible) {
|
|
ShowAppMainMenuBar(canInspect, preLaunch);
|
|
}
|
|
ShowToasts();
|
|
if (canInspect && (m_showInspectWindow || !inspectingEntities.empty())) {
|
|
UpdateEntityEntries();
|
|
if (m_showInspectWindow) {
|
|
ShowInspectWindow(&m_showInspectWindow);
|
|
}
|
|
auto iter = inspectingEntities.begin();
|
|
while (iter != inspectingEntities.end()) {
|
|
if (!ShowEntityInfoWindow(*iter)) {
|
|
iter = inspectingEntities.erase(iter);
|
|
} else {
|
|
iter++;
|
|
}
|
|
}
|
|
}
|
|
if (canInspect && m_showItemsWindow && m_cvarMgr.findCVar("cheats")->toBoolean()) {
|
|
ShowItemsWindow();
|
|
}
|
|
if (canInspect && m_showLayersWindow) {
|
|
ShowLayersWindow();
|
|
}
|
|
if (preLaunch || m_showAboutWindow) {
|
|
ShowAboutWindow(preLaunch);
|
|
}
|
|
if (m_showDemoWindow) {
|
|
ImGui::ShowDemoWindow(&m_showDemoWindow);
|
|
}
|
|
if (m_showConsoleVariablesWindow) {
|
|
ShowConsoleVariablesWindow();
|
|
}
|
|
ShowDebugOverlay();
|
|
ShowInputViewer();
|
|
ShowPlayerTransformEditor();
|
|
ShowPipelineProgress();
|
|
m_controllerConfig.show(m_controllerConfigVisible);
|
|
if (preLaunch && m_showPreLaunchSettingsWindow) {
|
|
ShowPreLaunchSettingsWindow();
|
|
}
|
|
}
|
|
|
|
void ImGuiConsole::PostUpdate() {
|
|
OPTICK_EVENT();
|
|
if (g_StateManager != nullptr && g_StateManager->GetObjectList()) {
|
|
// Clear deleted objects
|
|
CObjectList& list = g_StateManager->GetAllObjectList();
|
|
for (s16 uid = 0; uid < s16(entities.size()); uid++) {
|
|
ImGuiEntityEntry& item = entities[uid];
|
|
if (item.uid == kInvalidUniqueId) {
|
|
continue; // already cleared
|
|
}
|
|
CEntity* ent = list.GetObjectByIndex(uid);
|
|
if (ent == nullptr || ent != item.ent) {
|
|
// Remove inspect windows for deleted entities
|
|
inspectingEntities.erase(item.uid);
|
|
item.uid = kInvalidUniqueId;
|
|
item.ent = nullptr; // for safety
|
|
}
|
|
}
|
|
} else {
|
|
entities.fill(ImGuiEntityEntry{});
|
|
inspectingEntities.clear();
|
|
}
|
|
|
|
// Always calculate room time regardless of if the overlay is displayed, this allows us have an accurate display if
|
|
// the user chooses to display it later on during gameplay
|
|
if (g_StateManager != nullptr && m_currentRoom != g_StateManager->GetCurrentArea()) {
|
|
const double igt = g_GameState->GetTotalPlayTime();
|
|
m_currentRoom = static_cast<const void*>(g_StateManager->GetCurrentArea());
|
|
m_lastRoomTime = igt - m_currentRoomStart;
|
|
m_currentRoomStart = igt;
|
|
}
|
|
}
|
|
|
|
void ImGuiConsole::Shutdown() {
|
|
dummyWorlds.clear();
|
|
stringTables.clear();
|
|
#ifdef NATIVEFILEDIALOG_SUPPORTED
|
|
NFD::Quit();
|
|
#endif
|
|
}
|
|
|
|
static constexpr std::array GeneralItems{
|
|
CPlayerState::EItemType::EnergyTanks, CPlayerState::EItemType::CombatVisor, CPlayerState::EItemType::ScanVisor,
|
|
CPlayerState::EItemType::ThermalVisor, CPlayerState::EItemType::XRayVisor, CPlayerState::EItemType::GrappleBeam,
|
|
CPlayerState::EItemType::SpaceJumpBoots, CPlayerState::EItemType::PowerSuit, CPlayerState::EItemType::VariaSuit,
|
|
CPlayerState::EItemType::GravitySuit, CPlayerState::EItemType::PhazonSuit,
|
|
};
|
|
|
|
static constexpr std::array WeaponItems{
|
|
CPlayerState::EItemType::Missiles, CPlayerState::EItemType::PowerBeam, CPlayerState::EItemType::IceBeam,
|
|
CPlayerState::EItemType::WaveBeam, CPlayerState::EItemType::PlasmaBeam, CPlayerState::EItemType::SuperMissile,
|
|
CPlayerState::EItemType::Flamethrower, CPlayerState::EItemType::IceSpreader, CPlayerState::EItemType::Wavebuster,
|
|
CPlayerState::EItemType::ChargeBeam,
|
|
};
|
|
|
|
static constexpr std::array MorphBallItems{
|
|
CPlayerState::EItemType::PowerBombs, CPlayerState::EItemType::MorphBall, CPlayerState::EItemType::MorphBallBombs,
|
|
CPlayerState::EItemType::BoostBall, CPlayerState::EItemType::SpiderBall,
|
|
};
|
|
|
|
static constexpr std::array ArtifactItems{
|
|
CPlayerState::EItemType::Truth, CPlayerState::EItemType::Strength, CPlayerState::EItemType::Elder,
|
|
CPlayerState::EItemType::Wild, CPlayerState::EItemType::Lifegiver, CPlayerState::EItemType::Warrior,
|
|
CPlayerState::EItemType::Chozo, CPlayerState::EItemType::Nature, CPlayerState::EItemType::Sun,
|
|
CPlayerState::EItemType::World, CPlayerState::EItemType::Spirit, CPlayerState::EItemType::Newborn,
|
|
};
|
|
|
|
static constexpr std::array ItemLoadout21Percent{
|
|
std::make_pair(CPlayerState::EItemType::PowerSuit, 1), std::make_pair(CPlayerState::EItemType::CombatVisor, 1),
|
|
std::make_pair(CPlayerState::EItemType::ScanVisor, 1), std::make_pair(CPlayerState::EItemType::PowerBeam, 1),
|
|
std::make_pair(CPlayerState::EItemType::WaveBeam, 1), std::make_pair(CPlayerState::EItemType::IceBeam, 1),
|
|
std::make_pair(CPlayerState::EItemType::PlasmaBeam, 1), std::make_pair(CPlayerState::EItemType::XRayVisor, 1),
|
|
std::make_pair(CPlayerState::EItemType::Missiles, 5), std::make_pair(CPlayerState::EItemType::VariaSuit, 1),
|
|
std::make_pair(CPlayerState::EItemType::PhazonSuit, 1), std::make_pair(CPlayerState::EItemType::MorphBall, 1),
|
|
std::make_pair(CPlayerState::EItemType::MorphBallBombs, 1), std::make_pair(CPlayerState::EItemType::PowerBombs, 4),
|
|
};
|
|
|
|
static constexpr std::array ItemLoadoutAnyPercent{
|
|
std::make_pair(CPlayerState::EItemType::PowerSuit, 1), std::make_pair(CPlayerState::EItemType::CombatVisor, 1),
|
|
std::make_pair(CPlayerState::EItemType::ScanVisor, 1), std::make_pair(CPlayerState::EItemType::EnergyTanks, 3),
|
|
std::make_pair(CPlayerState::EItemType::PowerBeam, 1), std::make_pair(CPlayerState::EItemType::WaveBeam, 1),
|
|
std::make_pair(CPlayerState::EItemType::IceBeam, 1), std::make_pair(CPlayerState::EItemType::PlasmaBeam, 1),
|
|
std::make_pair(CPlayerState::EItemType::ChargeBeam, 1), std::make_pair(CPlayerState::EItemType::XRayVisor, 1),
|
|
std::make_pair(CPlayerState::EItemType::ThermalVisor, 1), std::make_pair(CPlayerState::EItemType::Missiles, 25),
|
|
std::make_pair(CPlayerState::EItemType::VariaSuit, 1), std::make_pair(CPlayerState::EItemType::PhazonSuit, 1),
|
|
std::make_pair(CPlayerState::EItemType::MorphBall, 1), std::make_pair(CPlayerState::EItemType::BoostBall, 1),
|
|
std::make_pair(CPlayerState::EItemType::MorphBallBombs, 1), std::make_pair(CPlayerState::EItemType::PowerBombs, 4),
|
|
std::make_pair(CPlayerState::EItemType::SpaceJumpBoots, 1),
|
|
};
|
|
|
|
int roundMultiple(int value, int multiple) {
|
|
if (multiple == 0) {
|
|
return value;
|
|
}
|
|
return static_cast<int>(std::round(static_cast<double>(value) / static_cast<double>(multiple)) *
|
|
static_cast<double>(multiple));
|
|
}
|
|
|
|
static void RenderItemType(CPlayerState& pState, CPlayerState::EItemType itemType) {
|
|
u32 maxValue = CPlayerState::GetPowerUpMaxValue(itemType);
|
|
std::string name{CPlayerState::ItemTypeToName(itemType)};
|
|
if (maxValue == 1) {
|
|
bool enabled = pState.GetItemCapacity(itemType) == 1;
|
|
if (ImGui::Checkbox(name.c_str(), &enabled)) {
|
|
if (enabled) {
|
|
pState.ReInitializePowerUp(itemType, 1);
|
|
pState.ResetAndIncrPickUp(itemType, 1);
|
|
} else {
|
|
pState.ReInitializePowerUp(itemType, 0);
|
|
}
|
|
if (itemType == CPlayerState::EItemType::VariaSuit || itemType == CPlayerState::EItemType::PowerSuit ||
|
|
itemType == CPlayerState::EItemType::GravitySuit || itemType == CPlayerState::EItemType::PhazonSuit) {
|
|
g_StateManager->Player()->AsyncLoadSuit(*g_StateManager);
|
|
}
|
|
}
|
|
} else if (maxValue > 1) {
|
|
int capacity = int(pState.GetItemCapacity(itemType));
|
|
int amount = int(pState.GetItemAmount(itemType));
|
|
if (ImGui::SliderInt((name + " (Capacity)").c_str(), &capacity, 0, int(maxValue), "%d",
|
|
ImGuiSliderFlags_AlwaysClamp)) {
|
|
if (itemType == CPlayerState::EItemType::Missiles) {
|
|
capacity = roundMultiple(capacity, 5);
|
|
}
|
|
pState.ReInitializePowerUp(itemType, u32(capacity));
|
|
pState.ResetAndIncrPickUp(itemType, u32(capacity));
|
|
}
|
|
if (capacity > 0) {
|
|
if (ImGui::SliderInt((name + " (Amount)").c_str(), &amount, 0, capacity, "%d", ImGuiSliderFlags_AlwaysClamp)) {
|
|
if (itemType == CPlayerState::EItemType::Missiles) {
|
|
amount = roundMultiple(amount, 5);
|
|
}
|
|
pState.ResetAndIncrPickUp(itemType, u32(amount));
|
|
}
|
|
} else {
|
|
ImGui::Dummy(ImGui::GetItemRectSize());
|
|
}
|
|
}
|
|
}
|
|
|
|
template <size_t N>
|
|
static inline void RenderItemsDualColumn(CPlayerState& pState, const std::array<CPlayerState::EItemType, N>& items,
|
|
int start) {
|
|
ImGui::BeginGroup();
|
|
// Render left group
|
|
for (int i = start; i < items.size(); i += 2) {
|
|
RenderItemType(pState, items[i]);
|
|
}
|
|
ImGui::EndGroup();
|
|
ImGui::SameLine();
|
|
ImGui::BeginGroup();
|
|
// Render right group
|
|
for (int i = start + 1; i < items.size(); i += 2) {
|
|
RenderItemType(pState, items[i]);
|
|
}
|
|
ImGui::EndGroup();
|
|
}
|
|
|
|
void ImGuiConsole::ShowItemsWindow() {
|
|
CPlayerState& pState = *g_StateManager->GetPlayerState();
|
|
if (ImGui::Begin("Items", &m_showItemsWindow, ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
if (ImGui::Button("Refill")) {
|
|
for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) {
|
|
auto itemType = static_cast<CPlayerState::EItemType>(i);
|
|
u32 maxValue = CPlayerState::GetPowerUpMaxValue(itemType);
|
|
pState.ResetAndIncrPickUp(itemType, maxValue);
|
|
}
|
|
}
|
|
auto& mapWorldInfo = *g_GameState->CurrentWorldState().MapWorldInfo();
|
|
ImGui::SameLine();
|
|
bool mapStationUsed = mapWorldInfo.GetMapStationUsed();
|
|
if (ImGui::Checkbox("Area map", &mapStationUsed)) {
|
|
mapWorldInfo.SetMapStationUsed(mapStationUsed);
|
|
}
|
|
if (ImGui::Button("All")) {
|
|
for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) {
|
|
auto itemType = static_cast<CPlayerState::EItemType>(i);
|
|
u32 maxValue = CPlayerState::GetPowerUpMaxValue(itemType);
|
|
pState.ReInitializePowerUp(itemType, maxValue);
|
|
pState.ResetAndIncrPickUp(itemType, maxValue);
|
|
}
|
|
mapWorldInfo.SetMapStationUsed(true);
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("None")) {
|
|
for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) {
|
|
auto itemType = static_cast<CPlayerState::EItemType>(i);
|
|
pState.ReInitializePowerUp(itemType, 0);
|
|
}
|
|
mapWorldInfo.SetMapStationUsed(false);
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("21%")) {
|
|
for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) {
|
|
auto itemType = static_cast<CPlayerState::EItemType>(i);
|
|
pState.ReInitializePowerUp(itemType, 0);
|
|
}
|
|
mapWorldInfo.SetMapStationUsed(false);
|
|
for (const auto& [item, count] : ItemLoadout21Percent) {
|
|
pState.ReInitializePowerUp(item, count);
|
|
pState.IncrPickup(item, count);
|
|
}
|
|
for (const auto& item : ArtifactItems) {
|
|
pState.ReInitializePowerUp(item, 1);
|
|
}
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Any%")) {
|
|
for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) {
|
|
auto itemType = static_cast<CPlayerState::EItemType>(i);
|
|
pState.ReInitializePowerUp(itemType, 0);
|
|
}
|
|
mapWorldInfo.SetMapStationUsed(false);
|
|
for (const auto& [item, count] : ItemLoadoutAnyPercent) {
|
|
pState.ReInitializePowerUp(item, count);
|
|
pState.IncrPickup(item, count);
|
|
}
|
|
for (const auto& item : ArtifactItems) {
|
|
pState.ReInitializePowerUp(item, 1);
|
|
}
|
|
}
|
|
|
|
if (ImGui::BeginTabBar("Items")) {
|
|
if (ImGui::BeginTabItem("General")) {
|
|
RenderItemType(pState, GeneralItems[0]); // full width
|
|
RenderItemsDualColumn(pState, GeneralItems, 1);
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("Weapons")) {
|
|
RenderItemType(pState, WeaponItems[0]); // full width
|
|
RenderItemsDualColumn(pState, WeaponItems, 1);
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("Morph Ball")) {
|
|
RenderItemType(pState, MorphBallItems[0]); // full width
|
|
RenderItemsDualColumn(pState, MorphBallItems, 1);
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("Artifacts")) {
|
|
ImGui::Text("NOTE: This doesn't affect Artifact Temple layers");
|
|
ImGui::Text("Use the Layers window to set them for progression");
|
|
RenderItemsDualColumn(pState, ArtifactItems, 0);
|
|
ImGui::EndTabItem();
|
|
}
|
|
ImGui::EndTabBar();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void ImGuiConsole::ShowLayersWindow() {
|
|
// For some reason the window shows up tiny without this
|
|
float initialWindowSize = 350.f * GetScale();
|
|
ImGui::SetNextWindowSize(ImVec2{initialWindowSize, initialWindowSize}, ImGuiCond_FirstUseEver);
|
|
|
|
if (ImGui::Begin("Layers", &m_showLayersWindow)) {
|
|
if (ImGui::Button("Clear")) {
|
|
m_layersFilterText.clear();
|
|
}
|
|
ImGui::SameLine();
|
|
ImGui::InputText("Filter", &m_layersFilterText);
|
|
bool hasSearch = !m_layersFilterText.empty();
|
|
if (hasSearch) {
|
|
// kinda hacky way reset the tree state when search changes
|
|
ImGui::PushID(m_layersFilterText.c_str());
|
|
}
|
|
for (const auto& world : ListWorlds()) {
|
|
const auto& layers = dummyWorlds[world.second]->GetWorldLayers();
|
|
if (!layers) {
|
|
continue;
|
|
}
|
|
|
|
auto worldLayerState = g_GameState->StateForWorld(world.second).GetLayerState();
|
|
auto areas = ListAreas(world.second);
|
|
auto iter = areas.begin();
|
|
while (iter != areas.end()) {
|
|
if (hasSearch && !ContainsCaseInsensitive(iter->first, m_layersFilterText)) {
|
|
iter = areas.erase(iter);
|
|
} else {
|
|
iter++;
|
|
}
|
|
}
|
|
if (areas.empty()) {
|
|
continue;
|
|
}
|
|
|
|
if (ImGui::TreeNodeEx(world.first.c_str(), hasSearch ? ImGuiTreeNodeFlags_DefaultOpen : 0)) {
|
|
for (const auto& area : areas) {
|
|
u32 layerCount = worldLayerState->GetAreaLayerCount(area.second);
|
|
if (layerCount == 0) {
|
|
continue;
|
|
}
|
|
if (ImGui::TreeNode(area.first.c_str())) {
|
|
if (ImGui::Button("Warp here")) {
|
|
Warp(world.second, area.second);
|
|
}
|
|
u32 startNameIdx = layers->m_areas[area.second].m_startNameIdx;
|
|
if (startNameIdx + layerCount > layers->m_names.size()) {
|
|
ImGui::Text("Broken layer data, please re-package");
|
|
} else {
|
|
for (int layer = 0; layer < layerCount; ++layer) {
|
|
bool active = worldLayerState->IsLayerActive(area.second, layer);
|
|
if (ImGui::Checkbox(layers->m_names[startNameIdx + layer].c_str(), &active)) {
|
|
worldLayerState->SetLayerActive(area.second, layer, active);
|
|
}
|
|
}
|
|
}
|
|
ImGui::TreePop();
|
|
}
|
|
}
|
|
ImGui::TreePop();
|
|
}
|
|
}
|
|
if (hasSearch) {
|
|
ImGui::PopID();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void ImGuiConsole::ShowToasts() {
|
|
if (m_toasts.empty()) {
|
|
return;
|
|
}
|
|
auto& toast = m_toasts.front();
|
|
const float dt = ImGui::GetIO().DeltaTime;
|
|
toast.remain -= dt;
|
|
toast.current += dt;
|
|
|
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
const ImVec2 workPos = viewport->WorkPos;
|
|
const ImVec2 workSize = viewport->WorkSize;
|
|
constexpr float padding = 10.0f;
|
|
const ImVec2 windowPos{workPos.x + workSize.x / 2, workPos.y + workSize.y - padding};
|
|
ImGui::SetNextWindowPos(windowPos, ImGuiCond_Always, ImVec2{0.5f, 1.f});
|
|
|
|
const float alpha = std::min({toast.remain, toast.current, 1.f});
|
|
ImGui::SetNextWindowBgAlpha(alpha * 0.65f);
|
|
ImVec4 textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
|
|
textColor.w *= alpha;
|
|
ImVec4 borderColor = ImGui::GetStyleColorVec4(ImGuiCol_Border);
|
|
borderColor.w *= alpha;
|
|
ImGui::PushStyleColor(ImGuiCol_Text, textColor);
|
|
ImGui::PushStyleColor(ImGuiCol_Border, borderColor);
|
|
if (ImGui::Begin("Toast", nullptr,
|
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
|
|
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav |
|
|
ImGuiWindowFlags_NoMove)) {
|
|
ImGuiStringViewText(toast.message);
|
|
}
|
|
ImGui::End();
|
|
ImGui::PopStyleColor(2);
|
|
|
|
if (toast.remain <= 0.f) {
|
|
m_toasts.pop_front();
|
|
}
|
|
}
|
|
|
|
void ImGuiConsole::ShowPlayerTransformEditor() {
|
|
if (!m_showPlayerTransformEditor) {
|
|
return;
|
|
}
|
|
|
|
if (ImGui::Begin("Player Transform", &m_showPlayerTransformEditor, ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
if (ImGui::CollapsingHeader("Position")) {
|
|
ImGui::PushID("player_position");
|
|
zeus::CVector3f vec = g_StateManager->GetPlayer().GetTranslation();
|
|
|
|
if (ImGuiVector3fInput("Position", vec)) {
|
|
g_StateManager->GetPlayer().SetTranslation(vec);
|
|
}
|
|
|
|
if (ImGui::Button("Save")) {
|
|
m_savedLocation.emplace(vec);
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Load") && m_savedLocation) {
|
|
g_StateManager->GetPlayer().SetTranslation(*m_savedLocation);
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Clear") && m_savedLocation) {
|
|
m_savedLocation.reset();
|
|
}
|
|
if (m_savedLocation) {
|
|
ImGui::Text("Saved: %g, %g, %g", float(m_savedLocation->x()), float(m_savedLocation->y()),
|
|
float(m_savedLocation->z()));
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
if (ImGui::CollapsingHeader("Rotation")) {
|
|
ImGui::PushID("player_rotation");
|
|
zeus::CEulerAngles angles(g_StateManager->GetPlayer().GetTransform());
|
|
angles = zeus::CEulerAngles(angles * zeus::skRadToDegVec);
|
|
if (ImGuiVector3fInput("Rotation", angles)) {
|
|
angles.x() = zeus::clamp(-179.999f, float(angles.x()), 179.999f);
|
|
angles.y() = zeus::clamp(-89.999f, float(angles.y()), 89.999f);
|
|
angles.z() = zeus::clamp(-179.999f, float(angles.z()), 179.999f);
|
|
auto xf = g_StateManager->GetPlayer().GetTransform();
|
|
xf.setRotation(zeus::CQuaternion(angles * zeus::skDegToRadVec).toTransform().buildMatrix3f());
|
|
g_StateManager->GetPlayer().SetTransform(xf);
|
|
}
|
|
|
|
if (ImGui::Button("Save")) {
|
|
m_savedRotation.emplace(angles);
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Load") && m_savedRotation) {
|
|
auto xf = g_StateManager->GetPlayer().GetTransform();
|
|
xf.setRotation(zeus::CQuaternion((*m_savedRotation) * zeus::skDegToRadVec).toTransform().buildMatrix3f());
|
|
g_StateManager->GetPlayer().SetTransform(xf);
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Clear") && m_savedRotation) {
|
|
m_savedRotation.reset();
|
|
}
|
|
|
|
if (m_savedRotation) {
|
|
ImGui::Text("Saved: %g, %g, %g", float(m_savedRotation->x()), float(m_savedRotation->y()),
|
|
float(m_savedRotation->z()));
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void ImGuiConsole::ShowPipelineProgress() {
|
|
const u32 queuedPipelines = aurora::gfx::queuedPipelines;
|
|
if (queuedPipelines == 0) {
|
|
return;
|
|
}
|
|
const u32 createdPipelines = aurora::gfx::createdPipelines;
|
|
const u32 totalPipelines = queuedPipelines + createdPipelines;
|
|
|
|
const auto* viewport = ImGui::GetMainViewport();
|
|
const auto padding = viewport->WorkPos.y + 10.f;
|
|
const auto halfWidth = viewport->GetWorkCenter().x;
|
|
ImGui::SetNextWindowPos(ImVec2{halfWidth, padding}, ImGuiCond_Always, ImVec2{0.5f, 0.f});
|
|
ImGui::SetNextWindowSize(ImVec2{halfWidth, 0.f}, ImGuiCond_Always);
|
|
ImGui::SetNextWindowBgAlpha(0.65f);
|
|
ImGui::Begin("Pipelines", nullptr,
|
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove |
|
|
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing);
|
|
const auto percent = static_cast<float>(createdPipelines) / static_cast<float>(totalPipelines);
|
|
const auto progressStr = fmt::format(FMT_STRING("Processing pipelines: {} / {}"), createdPipelines, totalPipelines);
|
|
const auto textSize = ImGui::CalcTextSize(progressStr.data(), progressStr.data() + progressStr.size());
|
|
ImGui::NewLine();
|
|
ImGui::SameLine(ImGui::GetWindowWidth() / 2.f - textSize.x + textSize.x / 2.f);
|
|
ImGuiStringViewText(progressStr);
|
|
ImGui::ProgressBar(percent);
|
|
ImGui::End();
|
|
}
|
|
|
|
void ImGuiConsole::ControllerAdded(uint32_t idx) {
|
|
const char* name = PADGetName(idx);
|
|
if (name != nullptr) {
|
|
m_toasts.emplace_back(fmt::format(FMT_STRING("Controller {} ({}) connected"), idx, name), 5.f);
|
|
} else {
|
|
m_toasts.emplace_back(fmt::format(FMT_STRING("Controller {} connected"), idx), 5.f);
|
|
}
|
|
}
|
|
|
|
void ImGuiConsole::ControllerRemoved(uint32_t idx) {
|
|
m_toasts.emplace_back(fmt::format(FMT_STRING("Controller {} disconnected"), idx), 5.f);
|
|
}
|
|
|
|
static void ImGuiCVarCheckbox(CVarManager& mgr, std::string_view cvarName, const char* label, bool* ptr = nullptr) {
|
|
auto* cvar = mgr.findCVar(cvarName);
|
|
if (cvar != nullptr) {
|
|
bool value = cvar->toBoolean();
|
|
bool modified = false;
|
|
if (ptr == nullptr) {
|
|
modified = ImGui::Checkbox(label, &value);
|
|
} else {
|
|
modified = ImGui::Checkbox(label, ptr);
|
|
value = *ptr;
|
|
}
|
|
// Kinda useless for these tbh
|
|
// std::string tooltip{cvar->rawHelp()};
|
|
// if (!tooltip.empty() && ImGui::IsItemHovered()) {
|
|
// ImGui::SetTooltip("%s", tooltip.c_str());
|
|
// }
|
|
if (modified) {
|
|
cvar->unlock();
|
|
cvar->fromBoolean(value);
|
|
cvar->lock();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImGuiConsole::ShowPreLaunchSettingsWindow() {
|
|
if (ImGui::Begin("Settings", &m_showPreLaunchSettingsWindow, ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
if (ImGui::BeginTabBar("Settings")) {
|
|
if (ImGui::BeginTabItem("Graphics")) {
|
|
size_t backendCount = 0;
|
|
const auto* backends = aurora_get_available_backends(&backendCount);
|
|
ImGuiStringViewText(fmt::format(FMT_STRING("Current backend: {}"), backend_name(aurora_get_backend())));
|
|
auto desiredBackend = static_cast<int>(BACKEND_AUTO);
|
|
if (auto* cvar = m_cvarMgr.findCVar("graphicsApi")) {
|
|
bool valid = false;
|
|
const auto name = cvar->toLiteral(&valid);
|
|
if (valid) {
|
|
desiredBackend = static_cast<int>(backend_from_string(name));
|
|
}
|
|
}
|
|
bool modified = false;
|
|
modified = ImGui::RadioButton("Auto", &desiredBackend, static_cast<int>(BACKEND_AUTO));
|
|
for (size_t i = 0; i < backendCount; ++i, ++backends) {
|
|
const auto backend = *backends;
|
|
modified =
|
|
ImGui::RadioButton(backend_name(backend).data(), &desiredBackend, static_cast<int>(backend)) || modified;
|
|
}
|
|
if (modified) {
|
|
m_cvarCommons.m_graphicsApi->fromLiteral(backend_to_string(static_cast<AuroraBackend>(desiredBackend)));
|
|
}
|
|
ImGuiCVarCheckbox(m_cvarMgr, "fullscreen", "Fullscreen");
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("Game")) {
|
|
ImGuiCVarCheckbox(m_cvarMgr, "allowJoystickInBackground", "Enable Background Joystick Input");
|
|
ImGuiCVarCheckbox(m_cvarMgr, "tweak.game.SplashScreensDisabled", "Skip Splash Screens");
|
|
ImGuiCVarCheckbox(m_cvarMgr, "cheats", "Enable Cheats", &m_cheats);
|
|
if (m_cheats) {
|
|
ImGuiCVarCheckbox(m_cvarMgr, "developer", "Developer Mode", &m_developer);
|
|
}
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("Experimental")) {
|
|
ImGuiCVarCheckbox(m_cvarMgr, "variableDt", "Variable Delta Time (broken)");
|
|
ImGui::EndTabItem();
|
|
}
|
|
ImGui::EndTabBar();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
static bool eq(std::string_view a, std::string_view b) {
|
|
if (a.size() != b.size()) {
|
|
return false;
|
|
}
|
|
return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == b; });
|
|
}
|
|
|
|
AuroraBackend backend_from_string(const std::string& str) {
|
|
if (eq(str, "d3d12"sv) || eq(str, "d3d"sv)) {
|
|
return BACKEND_D3D12;
|
|
}
|
|
if (eq(str, "metal"sv)) {
|
|
return BACKEND_METAL;
|
|
}
|
|
if (eq(str, "vulkan"sv) || eq(str, "vk"sv)) {
|
|
return BACKEND_VULKAN;
|
|
}
|
|
if (eq(str, "opengl"sv) || eq(str, "gl"sv)) {
|
|
return BACKEND_OPENGL;
|
|
}
|
|
if (eq(str, "opengles"sv) || eq(str, "gles"sv)) {
|
|
return BACKEND_OPENGLES;
|
|
}
|
|
if (eq(str, "webgpu"sv) || eq(str, "wgpu"sv)) {
|
|
return BACKEND_WEBGPU;
|
|
}
|
|
if (eq(str, "null"sv) || eq(str, "none"sv)) {
|
|
return BACKEND_NULL;
|
|
}
|
|
return BACKEND_AUTO;
|
|
}
|
|
|
|
std::string_view backend_to_string(AuroraBackend backend) {
|
|
switch (backend) {
|
|
default:
|
|
return "auto"sv;
|
|
case BACKEND_D3D12:
|
|
return "d3d12"sv;
|
|
case BACKEND_METAL:
|
|
return "metal"sv;
|
|
case BACKEND_VULKAN:
|
|
return "vulkan"sv;
|
|
case BACKEND_OPENGL:
|
|
return "opengl"sv;
|
|
case BACKEND_OPENGLES:
|
|
return "opengles"sv;
|
|
case BACKEND_WEBGPU:
|
|
return "webgpu"sv;
|
|
case BACKEND_NULL:
|
|
return "null"sv;
|
|
}
|
|
}
|
|
|
|
std::string_view backend_name(AuroraBackend backend) {
|
|
switch (backend) {
|
|
default:
|
|
return "Auto"sv;
|
|
case BACKEND_D3D12:
|
|
return "D3D12"sv;
|
|
case BACKEND_METAL:
|
|
return "Metal"sv;
|
|
case BACKEND_VULKAN:
|
|
return "Vulkan"sv;
|
|
case BACKEND_OPENGL:
|
|
return "OpenGL"sv;
|
|
case BACKEND_OPENGLES:
|
|
return "OpenGL ES"sv;
|
|
case BACKEND_WEBGPU:
|
|
return "WebGPU"sv;
|
|
case BACKEND_NULL:
|
|
return "Null"sv;
|
|
}
|
|
}
|
|
} // namespace metaforce
|