diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a220fb0..d2e4ba0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,6 +37,7 @@ jobs: shell: pwsh run: | cp OutRun2006Tweaks.ini build/bin/OutRun2006Tweaks.ini + cp OutRun2006Tweaks.lods.ini build/bin/OutRun2006Tweaks.lods.ini - name: Download OR2006C2C shell: pwsh diff --git a/OutRun2006Tweaks.lods.ini b/OutRun2006Tweaks.lods.ini new file mode 100644 index 0000000..ae24e40 --- /dev/null +++ b/OutRun2006Tweaks.lods.ini @@ -0,0 +1,10 @@ +# This file contains known-bad object IDs that will be excluded from being drawn by the DrawDistanceIncrease tweak +# Such as LOD models or other bad stage models +# +# OutRun2006Tweaks includes an overlay that can help to find the IDs for bad objects if they appear +# Just make sure [Overlay] Enabled = true is set, then press F11 when you see a bad model while playing a stage + +# AMAZON +[Stage 22] +# Jungle -> Skyscrapers bushes during bunki +0x5 = 0xBB diff --git a/src/dllmain.cpp b/src/dllmain.cpp index 4d82abc..cb38883 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -16,6 +16,7 @@ namespace Module constexpr std::string_view IniFileName = "OutRun2006Tweaks.ini"; constexpr std::string_view UserIniFileName = "OutRun2006Tweaks.user.ini"; + constexpr std::string_view LodIniFileName = "OutRun2006Tweaks.lods.ini"; constexpr std::string_view LogFileName = "OutRun2006Tweaks.log"; void init() @@ -34,6 +35,7 @@ namespace Module LogPath = dllParent / LogFileName; IniPath = dllParent / IniFileName; UserIniPath = dllParent / UserIniFileName; + LodIniPath = dllParent / LodIniFileName; Game::init(); } diff --git a/src/game_addrs.hpp b/src/game_addrs.hpp index 8bfbcf6..01796f5 100644 --- a/src/game_addrs.hpp +++ b/src/game_addrs.hpp @@ -91,6 +91,7 @@ namespace Game inline fn_1arg_int GetNowStageNum = nullptr; inline fn_1arg_int GetStageUniqueNum = nullptr; inline fn_1arg_int GetMaxCsLen = nullptr; + inline fn_1arg_char GetStageUniqueName = nullptr; inline fn_stdcall_1arg_int Sumo_CheckRacerUnlocked = nullptr; @@ -192,6 +193,7 @@ namespace Game GetNowStageNum = Module::fn_ptr(0x50380); GetStageUniqueNum = Module::fn_ptr(0x4DC50); GetMaxCsLen = Module::fn_ptr(0x3D470); + GetStageUniqueName = Module::fn_ptr(0x4BE80); Sumo_CheckRacerUnlocked = Module::fn_ptr(0xE8410); diff --git a/src/hooks_drawdistance.cpp b/src/hooks_drawdistance.cpp index 198ad72..020faac 100644 --- a/src/hooks_drawdistance.cpp +++ b/src/hooks_drawdistance.cpp @@ -4,6 +4,7 @@ #include #include #include +#include std::array, 256> ObjectNodes; std::array, 256>, 128> ObjectExclusionsPerStage; @@ -49,11 +50,24 @@ void Overlay_DrawDistOverlay() } ImGui::SliderInt("Draw Distance", &Settings::DrawDistanceIncrease, 0, 1024); + if (ImGui::Button("<<<")) - Settings::DrawDistanceIncrease--; + Settings::DrawDistanceIncrease -= 10; + ImGui::SameLine(); + if (ImGui::Button("<<")) + Settings::DrawDistanceIncrease -= 5; + ImGui::SameLine(); + if (ImGui::Button("<")) + Settings::DrawDistanceIncrease -= 1; + ImGui::SameLine(); + if (ImGui::Button(">")) + Settings::DrawDistanceIncrease += 1; + ImGui::SameLine(); + if (ImGui::Button(">>")) + Settings::DrawDistanceIncrease += 5; ImGui::SameLine(); if (ImGui::Button(">>>")) - Settings::DrawDistanceIncrease++; + Settings::DrawDistanceIncrease += 10; if (num_columns > 0) { @@ -133,7 +147,7 @@ void Overlay_DrawDistOverlay() } } if (!clipboard.empty()) - clipboard = std::format("[Stage {}]{}", cur_stage_num, clipboard); + clipboard = std::format("# {}\n[Stage {}]{}", Game::GetStageUniqueName(cur_stage_num), cur_stage_num, clipboard); HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, clipboard.length() + 1); if (hMem) @@ -286,6 +300,129 @@ class DrawDistanceIncrease : public Hook } } + static int get_number(std::string_view sectionName) + { + int number = -1; + + // Find the position of the numeric part + auto pos = sectionName.find_first_of("0123456789"); + if (pos != std::string::npos) + { + auto result = std::from_chars(sectionName.data() + pos, sectionName.data() + sectionName.size(), number); + if (result.ec == std::errc()) + return number; + } + + return -1; + } + + bool read_exclusions() + { + // Try reading exclusions + std::filesystem::path& iniPath = Module::LodIniPath; + + if (!std::filesystem::exists(Module::LodIniPath)) + { + spdlog::error("DrawDistanceIncrease::read_exclusions - failed to locate exclusion INI from path {}", iniPath.string()); + return false; + } + + spdlog::info("DrawDistanceIncrease::read_exclusions - reading INI from {}", iniPath.string()); + + const std::wstring iniPathStr = iniPath.wstring(); + + // Read INI via FILE* since INIReader doesn't support wstring + FILE* iniFile; + errno_t result = _wfopen_s(&iniFile, iniPathStr.c_str(), L"r"); + if (result != 0 || !iniFile) + { + spdlog::error("DrawDistanceIncrease::read_exclusions - INI read failed! Error code {}", result); + return false; + } + + inih::INIReader ini; + try + { + ini = inih::INIReader(iniFile); + } + catch (...) + { + spdlog::error("DrawDistanceIncrease::read_exclusions - INI read failed! The file may be invalid or have duplicate settings inside"); + fclose(iniFile); + return false; + } + fclose(iniFile); + + for (auto& section : ini.Sections()) + { + int stageNum = get_number(section); + if (stageNum >= ObjectExclusionsPerStage.size()) + { + spdlog::error("DrawDistanceIncrease::read_exclusions - INI contains invalid stage section \"{}\", skipping...", section); + continue; + } + + for (auto& key : ini.Keys(section)) + { + int objectId = -1; + try + { + objectId = std::stol(key, nullptr, 0); + } + catch (const std::invalid_argument& e) + { + continue; + } + catch (const std::out_of_range& e) + { + continue; + } + + if (objectId < 0) + continue; + + if (objectId >= ObjectExclusionsPerStage[stageNum].size()) + { + spdlog::error("DrawDistanceIncrease::read_exclusions - INI contains invalid object number \"{}\", skipping...", key); + continue; + } + + std::vector nodes; + + auto value = ini.Get(section, key); + std::istringstream stream(value); + std::string token; + + // Tokenize the string using ',' as the delimiter + while (std::getline(stream, token, ',')) + { + // Remove leading/trailing whitespace + token.erase(0, token.find_first_not_of(" \t")); + token.erase(token.find_last_not_of(" \t") + 1); + + // Convert the token to an integer + try + { + nodes.push_back(std::stol(token, nullptr, 0)); + } + catch (const std::invalid_argument& e) + { + } + catch (const std::out_of_range& e) + { + } + } + + for (auto& node : nodes) + { + ObjectExclusionsPerStage[stageNum][objectId][node] = true; + } + } + } + + return true; + } + public: std::string_view description() override { @@ -309,6 +446,8 @@ class DrawDistanceIncrease : public Hook DrawDistanceIncreaseEnabled = true; + read_exclusions(); + return true; } diff --git a/src/plugin.hpp b/src/plugin.hpp index a076e73..6e587ad 100644 --- a/src/plugin.hpp +++ b/src/plugin.hpp @@ -22,6 +22,7 @@ namespace Module inline std::filesystem::path LogPath{}; inline std::filesystem::path IniPath{}; inline std::filesystem::path UserIniPath{}; + inline std::filesystem::path LodIniPath{}; template inline T* exe_ptr(uintptr_t offset) { if (ExeHandle) return (T*)(((uintptr_t)ExeHandle) + offset); else return nullptr; }