diff --git a/OTRExporter/assets/ship_of_harkinian/fonts/Fipps-Regular.otf b/OTRExporter/assets/ship_of_harkinian/fonts/Fipps-Regular.otf new file mode 100644 index 000000000..9334dad59 Binary files /dev/null and b/OTRExporter/assets/ship_of_harkinian/fonts/Fipps-Regular.otf differ diff --git a/OTRExporter/assets/ship_of_harkinian/fonts/PressStart2P-Regular.ttf b/OTRExporter/assets/ship_of_harkinian/fonts/PressStart2P-Regular.ttf new file mode 100644 index 000000000..2442affbf Binary files /dev/null and b/OTRExporter/assets/ship_of_harkinian/fonts/PressStart2P-Regular.ttf differ diff --git a/libultraship/libultraship/GameOverlay.cpp b/libultraship/libultraship/GameOverlay.cpp new file mode 100644 index 000000000..a8438643f --- /dev/null +++ b/libultraship/libultraship/GameOverlay.cpp @@ -0,0 +1,186 @@ +#include "GameOverlay.h" + +#include "Cvar.h" +#include "File.h" +#include "Archive.h" +#include "ResourceMgr.h" +#include "SohConsole.h" +#include "SohImGuiImpl.h" +#include "TextureMod.h" +#include "Lib/ImGui/imgui_internal.h" + +void Ship::GameOverlay::LoadFont(const std::string& name, const std::string& path, float fontSize) { + ImGuiIO& io = ImGui::GetIO(); + std::shared_ptr base = GlobalCtx2::GetInstance()->GetResourceManager()->GetArchive(); + std::shared_ptr font = std::make_shared(); + base->LoadFile(normalize(path), false, font); + if (font->bIsLoaded) { + char* font_data = new char[font->dwBufferSize]; + memcpy(font_data, font->buffer.get(), font->dwBufferSize); + Fonts[name] = io.Fonts->AddFontFromMemoryTTF(font_data, font->dwBufferSize, fontSize); + } +} + +void Ship::GameOverlay::TextDraw(float x, float y, bool shadow, const char* fmt, ...) IM_FMTARGS(5) { + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); + buf[IM_ARRAYSIZE(buf) - 1] = 0; + va_end(args); + + ImGui::PushFont(Fonts[this->CurrentFont]); + if (shadow) { + ImGui::SetCursorPos(ImVec2(x + 1, y + 1)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(.0f, .0f, .0f, 255)); + ImGui::Text(buf, args); + } + ImGui::PopStyleColor(); + ImGui::SetCursorPos(ImVec2(x, y)); + ImGui::Text(buf, args); + ImGui::PopFont(); +} + +float Ship::GameOverlay::GetScreenWidth() { + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + return viewport->Size.x; +} + +float Ship::GameOverlay::GetScreenHeight() { + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + return viewport->Size.y; +} + +float Ship::GameOverlay::GetStringWidth(const char* text) { + return CalculateTextSize(text).x; +} + +ImVec2 Ship::GameOverlay::CalculateTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width) { + ImGuiContext& g = *GImGui; + + const char* text_display_end; + if (hide_text_after_double_hash) + text_display_end = ImGui::FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string + else + text_display_end = text_end; + + GameOverlay* overlay = SohImGui::overlay; + + ImFont* font = overlay->CurrentFont == "Default" ? g.Font : overlay->Fonts[overlay->CurrentFont]; + const float font_size = font->FontSize; + if (text == text_display_end) + return ImVec2(0.0f, font_size); + ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); + + // Round + // FIXME: This has been here since Dec 2015 (7b0bf230) but down the line we want this out. + // FIXME: Investigate using ceilf or e.g. + // - https://git.musl-libc.org/cgit/musl/tree/src/math/ceilf.c + // - https://embarkstudios.github.io/rust-gpu/api/src/libm/math/ceilf.rs.html + text_size.x = IM_FLOOR(text_size.x + 0.99999f); + + return text_size; +} + +void Ship::GameOverlay::Init() { + this->LoadFont("Press Start 2P", "assets/ship_of_harkinian/fonts/PressStart2P-Regular.ttf", 12.0f); + this->LoadFont("Fipps", "assets/ship_of_harkinian/fonts/Fipps-Regular.otf", 32.0f); + const std::string DefaultFont = this->Fonts.begin()->first; + if(!this->Fonts.empty()) { + const std::string font = CVar_GetString("gOverlayFont", ImStrdup(DefaultFont.c_str())); + for (auto& [name, _] : this->Fonts) { + if (font.starts_with(name)) { + this->CurrentFont = name; + break; + } + this->CurrentFont = DefaultFont; + } + } + SohImGui::console->Commands["overlay"] = { OverlayCommand, "Draw an overlay using a cvar value" }; +} + +void Ship::GameOverlay::DrawSettings() { + ImGui::Text("Overlays Text Font"); + if (ImGui::BeginCombo("##TextFont", this->CurrentFont.c_str())) { + for (auto& [name, font] : this->Fonts) { + if (ImGui::Selectable(name.c_str(), name == this->CurrentFont)) { + this->CurrentFont = name; + CVar_SetString("gOverlayFont", ImStrdup(name.c_str())); + SohImGui::needs_save = true; + } + + } + ImGui::EndCombo(); + } +} + + +void Ship::GameOverlay::Draw() { + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + + ImGui::SetNextWindowPos(viewport->Pos, ImGuiCond_Always); + ImGui::SetNextWindowSize(viewport->Size, ImGuiCond_Always); + ImGui::Begin("SoHOverlay", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoInputs); + + float textY = 50; + for (auto &[key, overlay] : this->RegisteredOverlays) { + + if (overlay.type == OverlayType::TEXT) { + const char* text = ImStrdup(key.c_str()); + const CVar* var = CVar_GetVar(text); + + switch (var->type) { + case CVAR_TYPE_FLOAT: + this->TextDraw(30, textY, true, "%s %.2f", text, var->value.valueFloat); + break; + case CVAR_TYPE_S32: + this->TextDraw(30, textY, true, "%s %d", text, var->value.valueS32); + break; + case CVAR_TYPE_STRING: + this->TextDraw(30, textY, true, "%s %s", text, var->value.valueStr); + break; + } + + free((void*) text); + textY += 30; + } + } + + ImGui::End(); +} + + +bool Ship::OverlayCommand(const std::vector& args) { + if (args.size() < 3) { + return CMD_FAILED; + } + + if (CVar_GetVar(args[2].c_str()) != nullptr) { + const char* key = args[2].c_str(); + GameOverlay* overlay = SohImGui::overlay; + if (args[1] == "add") { + if (!overlay->RegisteredOverlays.contains(args[2])) { + overlay->RegisteredOverlays[args[2]] = { + OverlayType::TEXT, + key + }; + INFO("Added overlay: %s ", key); + } else { + ERROR("Overlay already exists: %s", key); + } + } + else if (args[1] == "remove") { + if (overlay->RegisteredOverlays.contains(args[2])) { + overlay->RegisteredOverlays.erase(args[2]); + INFO("Removed overlay: %s ", key); + } else { + ERROR("Overlay not found: %s ", key); + } + } + } else { + ERROR("CVar %s does not exist", args[2].c_str()); + } + + return CMD_SUCCESS; +} diff --git a/libultraship/libultraship/GameOverlay.h b/libultraship/libultraship/GameOverlay.h new file mode 100644 index 000000000..c53503a52 --- /dev/null +++ b/libultraship/libultraship/GameOverlay.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include + +#include "Lib/ImGui/imgui.h" +#include +#include + +enum class OverlayType { + TEXT, IMAGE +}; + +struct Overlay { + OverlayType type; + const char* value; +}; + +namespace Ship { + class GameOverlay { + public: + std::unordered_map RegisteredOverlays; + std::unordered_map Fonts; + std::string CurrentFont = "Default"; + void Init(); + void Draw(); + void DrawSettings(); + static float GetScreenWidth(); + static float GetScreenHeight(); + static float GetStringWidth(const char* text); + static ImVec2 CalculateTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); + private: + void TextDraw(float x, float y, bool shadow, const char* text, ...); + void LoadFont(const std::string& name, const std::string& path, float fontSize); + }; + + static bool OverlayCommand(const std::vector& args); +} + diff --git a/libultraship/libultraship/SohImGuiImpl.cpp b/libultraship/libultraship/SohImGuiImpl.cpp index 7e7082b7b..9adbdd820 100644 --- a/libultraship/libultraship/SohImGuiImpl.cpp +++ b/libultraship/libultraship/SohImGuiImpl.cpp @@ -18,6 +18,7 @@ #include "TextureMod.h" #include "Window.h" #include "Cvar.h" +#include "GameOverlay.h" #include "Texture.h" #include "../Fast3D/gfx_pc.h" #include "Lib/stb/stb_image.h" @@ -59,8 +60,10 @@ namespace SohImGui { WindowImpl impl; ImGuiIO* io; Console* console = new Console; + GameOverlay* overlay = new GameOverlay; bool p_open = false; bool needs_save = false; + std::vector CustomTexts; int SelectedLanguage = CVar_GetS32("gLanguages", 0); //Default Language to 0=English 1=German 2=French float kokiri_col[3] = { 0.118f, 0.41f, 0.106f }; float goron_col[3] = { 0.392f, 0.078f, 0.0f }; @@ -323,10 +326,13 @@ namespace SohImGui { ImGui::SetCurrentContext(ctx); io = &ImGui::GetIO(); io->ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io->Fonts->AddFontDefault(); + if (UseViewports()) { io->ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; } console->Init(); + overlay->Init(); ImGuiWMInit(); ImGuiBackendInit(); @@ -490,7 +496,7 @@ namespace SohImGui { } } - void DrawMainMenuAndCalculateGameSize() { + void DrawMainMenuAndCalculateGameSize() { console->Update(); ImGuiBackendNewFrame(); ImGuiWMNewFrame(); @@ -624,6 +630,7 @@ namespace SohImGui { } ImGui::EndCombo(); } + overlay->DrawSettings(); ImGui::EndMenu(); } @@ -800,6 +807,8 @@ namespace SohImGui { pos = ImVec2(size.x / 2 - sw / 2, 0); size = ImVec2(sw, size.y); } + + overlay->Draw(); } void DrawFramebufferAndGameInput() { diff --git a/libultraship/libultraship/SohImGuiImpl.h b/libultraship/libultraship/SohImGuiImpl.h index c4fe3f825..dd0ec9fd3 100644 --- a/libultraship/libultraship/SohImGuiImpl.h +++ b/libultraship/libultraship/SohImGuiImpl.h @@ -1,5 +1,6 @@ #pragma once +#include "GameOverlay.h" #include "Lib/ImGui/imgui.h" #include "SohConsole.h" @@ -58,6 +59,8 @@ namespace SohImGui { } CustomWindow; extern Console* console; + extern Ship::GameOverlay* overlay; + extern bool needs_save; void Init(WindowImpl window_impl); void Update(EventImpl event); diff --git a/libultraship/libultraship/libultraship.vcxproj b/libultraship/libultraship/libultraship.vcxproj index 3bf81a67d..b3448e7b0 100644 --- a/libultraship/libultraship/libultraship.vcxproj +++ b/libultraship/libultraship/libultraship.vcxproj @@ -256,6 +256,7 @@ + @@ -343,6 +344,7 @@ + diff --git a/libultraship/libultraship/libultraship.vcxproj.filters b/libultraship/libultraship/libultraship.vcxproj.filters index 5079d826e..8ac4f0afb 100644 --- a/libultraship/libultraship/libultraship.vcxproj.filters +++ b/libultraship/libultraship/libultraship.vcxproj.filters @@ -88,6 +88,9 @@ {bd6557f1-9480-413b-b0cd-843f8efc1939} + + {3285ab8a-06d8-4dac-9af9-efb2a9723ab1} + @@ -339,6 +342,9 @@ Source Files\CustomImpl + + Source Files\CustomImpl\Overlay + @@ -629,5 +635,8 @@ Source Files\Resources + + Source Files\CustomImpl\Overlay + \ No newline at end of file diff --git a/soh/soh/Enhancements/debugconsole.cpp b/soh/soh/Enhancements/debugconsole.cpp index e22f66d8c..94b230cd1 100644 --- a/soh/soh/Enhancements/debugconsole.cpp +++ b/soh/soh/Enhancements/debugconsole.cpp @@ -421,6 +421,14 @@ template bool is_number(const std::string& s) { return ((std::istringstream(s) >> n >> std::ws).eof()); } +char* Strdup(const char* src) { + const unsigned len = strlen(src) + 1; + char* newstr = static_cast(malloc(len)); + if (newstr) + memcpy(newstr, src, len); + return newstr; +} + void DebugConsole_LoadCVars() { if (File::Exists("cvars.cfg")) { @@ -431,7 +439,9 @@ void DebugConsole_LoadCVars() if (line.empty()) continue; if (cfg.size() < 2) continue; if (cfg[1].find("\"") != std::string::npos) { - CVar_SetString(cfg[0].c_str(), const_cast(cfg[1].c_str())); + std::string value(cfg[1]); + value.erase(std::ranges::remove(value, '\"').begin(), value.end()); + CVar_SetString(cfg[0].c_str(), Strdup(value.c_str())); } if (is_number(cfg[1])) { CVar_SetFloat(cfg[0].c_str(), std::stof(cfg[1]));