diff --git a/README.md b/README.md index ed959e9..c5c44aa 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ # SOTF ModMenu + [![Thunderstore downloads](https://img.shields.io/github/downloads/Wolf11221/SOTF_ModMenu/total?label=Github%20downloads)](https://github.com/Wolf11221/SOTF_ModMenu/releases/latest) [![Thunderstore downloads](https://img.shields.io/badge/dynamic/json?color=yellowgreen&label=Thunderstore%20downloads&query=total_downloads&url=https%3A%2F%2Fthunderstore.io%2Fapi%2Fexperimental%2Fpackage%2FWolf11221%2FSOTF_ModMenu%2F)](https://thunderstore.io/c/sons-of-the-forest/p/Wolf11221/SOTF_ModMenu/) -Press ` backquote ` to open the trainer. -To change your keybind, edit the config file in "BepInEx\config\SOTF ModMenu.cfg". Or download [BepInEx ConfigurationManager](https://thunderstore.io/c/sons-of-the-forest/p/faulty/BepInEx_ConfigurationManager/) to change keybinds without closing the game! +Maintained fork of the original [Wolf11221/SOTF_ModMenu](https://github.com/Wolf11221/SOTF_ModMenu) with performance fixes, cheat cleanup, and restored features for current Sons of the Forest / BepInEx 6 builds. + +Press the configured hotkey to open the trainer (default: `Backslash` — on German keyboards this is the `^` key left of `1`). -Requires [BepInEx](https://thunderstore.io/c/sons-of-the-forest/p/BepInEx/BepInExPack_IL2CPP/) +To change your keybind, edit `BepInEx\config\SOTF_ModMenu.cfg`, or use [BepInEx ConfigurationManager](https://thunderstore.io/c/sons-of-the-forest/p/faulty/BepInEx_ConfigurationManager/) (SOTF Thunderstore pack recommended). -Recommended to allow keybind swaps ingame [BepInEx ConfigurationManager](https://thunderstore.io/c/sons-of-the-forest/p/faulty/BepInEx_ConfigurationManager/) +Requires [BepInEx IL2CPP](https://thunderstore.io/c/sons-of-the-forest/p/BepInEx/BepInExPack_IL2CPP/) ![SOTF ModMenu](https://i.imgur.com/Vfk7OQo.png) -## Features: +## Features * God Mode * Inf Heal @@ -23,25 +25,59 @@ Recommended to allow keybind swaps ingame [BepInEx ConfigurationManager](https:/ * No Thirst * Always Rested * Instant Build -* infinite Logs +* Infinite Logs * Infinite Ammo +* Infinite Build * Item Spawner * Cave Light * SpeedRun * ESP * OG Menu Music -* Nice UI -And more to come! - -## Installation: +## Installation 1. Download [BepInEx](https://thunderstore.io/c/sons-of-the-forest/p/BepInEx/BepInExPack_IL2CPP/) -2. Drag contents of the BepInEx zip file into Sons of the Forest game directory (right click in steam > browse local files) -3. Download SOTF_ModMenu.dll and drag it into BepInEx/plugins -4. Run game before trying to find and edit config files -4. Enjoy ;) +2. Drag the contents of the BepInEx zip into the Sons of the Forest game directory (Steam → Browse local files) +3. Download `SOTF_ModMenu.dll` and place it in `BepInEx/plugins` +4. Launch the game once so the config file is created +5. Enjoy + +## Building + +```bash +dotnet build SOTF_ModMenu/SOTF_ModMenu.csproj +``` + +Optional auto-copy after build: set `GamePluginsDir` in `SOTF_ModMenu.csproj` (or pass `-p:GamePluginsDir=...`) to your `BepInEx\plugins` folder. Copy only runs if that folder exists. + +## Changelog (this fork) + +### Fixes + +* **Menu hotkey** — Reliable open/close on German layouts (`Backslash` / `BackQuote` / `Caret`). Input is checked before cheat updates and also in `OnGUI` so a cheat exception cannot block the menu. +* **IL2CPP lifecycle** — Proper `IntPtr` constructor for `SotfMain`, safer GameObject setup, startup logs for easier debugging. +* **Cheat cleanup on leave world** — Side effects are reverted (SpeedyRun console, Instant/Infinite Build, CaveLight, NoCold, NoFallDamage, GodMode health, held-item hack) instead of only clearing bool flags. +* **Infinite Ammo** — Restored via `RangedWeaponItemInstanceModule` magazine refill each frame. +* **Infinite Logs** — Restored via `HeldOnlyItemController.InfiniteHack` (synced with Infinite Build so the two toggles do not fight each other). +* **No Fall Damage** — Applied on toggle instead of polling every frame. + +### Performance + +* **ESP** — Early exit when no category is active; actor labels use lookup maps instead of large switches; shared `GUIContent` and cached distance strings to cut GC. +* **ESP UX** — Disabling the master ESP toggle no longer wipes category selections every frame. +* **Item ID window** — Filtered list is rebuilt only when the search query changes. +* **UI buttons** — Simplified color-tag formatting for toggle buttons. +* **Update loop** — Removed empty/broken update calls from the hot path; fall-damage no longer polled every frame. + +### Quality of life + +* Default hotkey and config docs aligned with German `^` = Unity `Backslash`. +* `[Broken]` labels removed from Ammo / Logs once restored. +* Optional post-build copy to the game plugins folder. -Made by Nie +## Credits -Custom input and config file made by Maalik +* Original mod by **Nie** +* Custom input and config by **Maalik** +* Upstream packaging by **Wolf11221** +* Maintenance / optimizations in this fork by **CrossiiDev-Studio** diff --git a/SOTF_ModMenu/Cheats/ESP/ESP.cs b/SOTF_ModMenu/Cheats/ESP/ESP.cs index 50f402f..23c68a6 100644 --- a/SOTF_ModMenu/Cheats/ESP/ESP.cs +++ b/SOTF_ModMenu/Cheats/ESP/ESP.cs @@ -1,11 +1,12 @@ -using System; +using System; +using System.Collections.Generic; using System.Linq; using Construction; -using Il2CppSystem.Collections.Generic; using Sons.Ai.Vail; using SOTF_ModMenu.UI; using TheForest.Utils; using UnityEngine; +using Il2CppList = Il2CppSystem.Collections.Generic.List; namespace SOTF_ModMenu.Cheats.ESP; @@ -17,99 +18,157 @@ internal static class ESP public static bool EnemyEsp; public static bool FriendlyEsp; public static bool StructureDamageEsp; + + private static readonly Dictionary EnemyLabels = new() + { + { VailActorTypeId.Fingers, ("Fingers", Color.red) }, + { VailActorTypeId.Andy, ("Andy", Color.red) }, + { VailActorTypeId.Danny, ("Danny", Color.red) }, + { VailActorTypeId.Billy, ("Billy", Color.red) }, + { VailActorTypeId.Baby, ("Baby", Color.red) }, + { VailActorTypeId.Twins, ("Twins", Color.red) }, + { VailActorTypeId.GoldMask, ("Gold Mask", Color.red) }, + { VailActorTypeId.MuddyFemale, ("Muddy Female", Color.red) }, + { VailActorTypeId.MuddyMale, ("Muddy Male", Color.red) }, + { VailActorTypeId.HeavyMale, ("Heavy Male", Color.red) }, + { VailActorTypeId.FatMale, ("Fat Male", Color.red) }, + { VailActorTypeId.FatFemale, ("Fat Female", Color.red) }, + { VailActorTypeId.John2, ("John 2.0", Color.red) }, + { VailActorTypeId.FacelessMale, ("Faceless Male", Color.red) }, + { VailActorTypeId.Demon, ("Demon", Color.red) }, + { VailActorTypeId.PaintedMale, ("Painted Male", Color.red) }, + { VailActorTypeId.PaintedFemale, ("Painted Female", Color.red) }, + { VailActorTypeId.Timmy, ("Timmy", Color.red) }, + { VailActorTypeId.Carl, ("Carl", Color.red) }, + { VailActorTypeId.MrPuffy, ("Mr. Puffy", Color.red) }, + { VailActorTypeId.MissPuffy, ("Mrs. Puffy", Color.red) }, + { VailActorTypeId.Angel, ("Angel", Color.red) }, + { VailActorTypeId.Brandy, ("Brandy", Color.red) }, + { VailActorTypeId.Crystal, ("Crystal", Color.red) }, + { VailActorTypeId.Destiny, ("Destiny", Color.red) }, + { VailActorTypeId.DemonBoss, ("Demon Boss", Color.red) }, + { VailActorTypeId.PuffyBossMale, ("Puffy Boss Male", Color.red) }, + { VailActorTypeId.PuffyBossFemale, ("Puffy Boss Female", Color.red) }, + { VailActorTypeId.BossMutant, ("Boss Mutant", Color.red) }, + { VailActorTypeId.CreepyVirginia, ("Creepy Virginia", Color.red) }, + { VailActorTypeId.Armsy, ("Armsy", Color.red) }, + { VailActorTypeId.Frank, ("Frank", Color.red) }, + { VailActorTypeId.Eddy, ("Eddy", Color.red) }, + { VailActorTypeId.Greg, ("Greg", Color.red) }, + { VailActorTypeId.Henry, ("Henry", Color.red) }, + { VailActorTypeId.Igor, ("Igor", Color.red) }, + { VailActorTypeId.Elise, ("Elise", Color.red) }, + { VailActorTypeId.Legsy, ("Legsy", Color.red) }, + { VailActorTypeId.Holey, ("Holey", Color.red) }, + }; + + private static readonly Dictionary FriendlyLabels = new() + { + { VailActorTypeId.Robby, ("Robby", Color.cyan) }, + { VailActorTypeId.Virginia, ("Virginia", Color.cyan) }, + }; + + private static readonly Dictionary AnimalLabels = new() + { + { VailActorTypeId.Rabbit, ("Rabbit", Color.green) }, + { VailActorTypeId.Squirrel, ("Squirrel", Color.green) }, + { VailActorTypeId.Turtle, ("Turtle", Color.green) }, + { VailActorTypeId.Seagull, ("Seagull", Color.green) }, + { VailActorTypeId.Eagle, ("Eagle", Color.green) }, + { VailActorTypeId.Duck, ("Duck", Color.green) }, + { VailActorTypeId.Moose, ("Moose", Color.green) }, + { VailActorTypeId.Salmon, ("Salmon", Color.green) }, + { VailActorTypeId.Bat, ("Bat", Color.green) }, + { VailActorTypeId.Deer, ("Deer", Color.green) }, + { VailActorTypeId.Bluebird, ("Bluebird", Color.green) }, + { VailActorTypeId.Hummingbird, ("Hummingbird", Color.green) }, + { VailActorTypeId.LandTurtle, ("Land Turtle", Color.green) }, + { VailActorTypeId.Raccoon, ("Raccoon", Color.green) }, + { VailActorTypeId.Shark, ("Shark", Color.red) }, + { VailActorTypeId.KillerWhale, ("Killer Whale", Color.red) }, + }; public static void Enable() { - //didn't want to check camera for null in each new ESP added so moved to one - if (SotfMain.CameraMain != null) - { + if (SotfMain.CameraMain == null) return; + if (!AnimalsEsp && !EnemyEsp && !FriendlyEsp && !StructureDamageEsp) return; + + if (AnimalsEsp || EnemyEsp || FriendlyEsp) Actors(); + + if (StructureDamageEsp) Structures(); - } } public static void Disable() { - AnimalsEsp = false; - EnemyEsp = false; - FriendlyEsp = false; - StructureDamageEsp = false; + Enabled = false; } - // I need to do this so esp can work sorry public static void ToggleEnabled() { - if (!LocalPlayer.IsInWorld) { Plugin.log.LogError($"Failed to toggle ESP, not in a world"); return; } + if (!LocalPlayer.IsInWorld) { Plugin.log.LogError("Failed to toggle ESP, not in a world"); return; } Enabled = !Enabled; } public static void ToggleAnimalsEsp() { - if (!LocalPlayer.IsInWorld) { Plugin.log.LogError($"Failed to toggle ESP, not in a world"); return; } + if (!LocalPlayer.IsInWorld) { Plugin.log.LogError("Failed to toggle ESP, not in a world"); return; } AnimalsEsp = !AnimalsEsp; } public static void ToggleEnemiesEsp() { - if (!LocalPlayer.IsInWorld) { Plugin.log.LogError($"Failed to toggle ESP, not in a world"); return; } + if (!LocalPlayer.IsInWorld) { Plugin.log.LogError("Failed to toggle ESP, not in a world"); return; } EnemyEsp = !EnemyEsp; } public static void ToggleFriendlyEsp() { - if (!LocalPlayer.IsInWorld) { Plugin.log.LogError($"Failed to toggle ESP, not in a world"); return; } + if (!LocalPlayer.IsInWorld) { Plugin.log.LogError("Failed to toggle ESP, not in a world"); return; } FriendlyEsp = !FriendlyEsp; } public static void ToggleStructureDamageEsp() { - if (!LocalPlayer.IsInWorld) { Plugin.log.LogError($"Failed to toggle ESP, not in a world"); return; } + if (!LocalPlayer.IsInWorld) { Plugin.log.LogError("Failed to toggle ESP, not in a world"); return; } StructureDamageEsp = !StructureDamageEsp; } - /// - /// ESP for Damaged Structures - /// private static void Structures() { - if (StructureDamageEsp) + if (SotfMain.SonsMainScene.isLoaded && SotfMain.DirtyStructures == null) { - if (SotfMain.SonsMainScene.isLoaded && SotfMain.DirtyStructures == null) - { - var gameManagers = SotfMain.SonsMainScene.GetRootGameObjects() - .FirstOrDefault(ob => ob.name == "GameManagers"); - if (gameManagers != default) - SotfMain.DirtyStructures = gameManagers - .GetComponentInChildren()._distortedStructures; - } + var gameManagers = SotfMain.SonsMainScene.GetRootGameObjects() + .FirstOrDefault(ob => ob.name == "GameManagers"); + if (gameManagers != default) + SotfMain.DirtyStructures = gameManagers + .GetComponentInChildren()._distortedStructures; + } + + if (!SotfMain.SonsMainScene.isLoaded || SotfMain.DirtyStructures == null) return; - if (SotfMain.SonsMainScene.isLoaded && SotfMain.DirtyStructures != null) - foreach (var s in SotfMain.DirtyStructures) - { - if (s == null) continue; - var structurePosition = s.transform.position; - var worldToScreen = SotfMain.CameraMain.WorldToScreenPoint(structurePosition); - var reiDistance = Vector3.Distance(SotfMain.CameraMain.transform.position, - structurePosition); + var cam = SotfMain.CameraMain; + var camPos = cam.transform.position; + var screenHeight = Screen.height; - if (worldToScreen.z >= 0f && reiDistance < 250f && StructureDamageEsp) - { - UIHelper.DrawString(new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Repair", - Color.magenta, 12); - UIHelper.DrawString(new Vector2(worldToScreen.x, Screen.height - worldToScreen.y + 12), - Mathf.Round(reiDistance) + "m", Color.yellow, 12); - } - } + foreach (var s in SotfMain.DirtyStructures) + { + if (s == null) continue; + var structurePosition = s.transform.position; + var worldToScreen = cam.WorldToScreenPoint(structurePosition); + var distance = Vector3.Distance(camPos, structurePosition); + + if (worldToScreen.z < 0f || distance >= 250f) continue; + + var screenPos = new Vector2(worldToScreen.x, screenHeight - worldToScreen.y); + UIHelper.DrawEspLabel(screenPos, "Repair", Color.magenta, distance); } } - /// - /// ESP for Actors in world - /// private static void Actors() { - //get actors from VailActorManager - List actors; + Il2CppList actors; try { actors = VailActorManager._instance._activeActors; @@ -117,167 +176,44 @@ private static void Actors() catch (Exception) { return; - // ignored } - if (actors != null) - foreach (var actor in actors) - { - if (!actor || actor._isDead) continue; - //get player and actors position in world, and distance from each other - var actorPosition = actor.transform.position; - var worldToScreen = SotfMain.CameraMain.WorldToScreenPoint(actorPosition); - var reiDistance = Vector3.Distance(SotfMain.CameraMain.transform.position, actorPosition); - //variable to determine if actor was drawn within section, trying to prevent drawing distance when more than one ESP is active - var drawn = false; - //check the distance to actor is not too far or too close for unnecessary renders - if (worldToScreen.z >= 0f && reiDistance < 250f && EnemyEsp) - { - drawn = actor.TypeId switch - { - VailActorTypeId.Fingers => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Fingers", Color.red, 12), - VailActorTypeId.Andy => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Andy", Color.red, 12), - VailActorTypeId.Danny => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Danny", Color.red, 12), - VailActorTypeId.Billy => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Billy", Color.red, 12), - VailActorTypeId.Baby => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Baby", Color.red, 12), - VailActorTypeId.Twins => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Twins", Color.red, 12), - VailActorTypeId.GoldMask => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Gold Mask", Color.red, 12), - VailActorTypeId.MuddyFemale => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Muddy Female", Color.red, 12), - VailActorTypeId.MuddyMale => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Muddy Male", Color.red, 12), - VailActorTypeId.HeavyMale => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Heavy Male", Color.red, 12), - VailActorTypeId.FatMale => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Fat Male", Color.red, 12), - VailActorTypeId.FatFemale => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Fat Female", Color.red, 12), - VailActorTypeId.John2 => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "John 2.0", Color.red, 12), - VailActorTypeId.FacelessMale => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Faceless Male", Color.red, 12), - VailActorTypeId.Demon => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Demon", Color.red, 12), - VailActorTypeId.PaintedMale => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Painted Male", Color.red, 12), - VailActorTypeId.PaintedFemale => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Painted Female", Color.red, 12), - VailActorTypeId.Timmy => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Timmy", Color.red, 12), - VailActorTypeId.Carl => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Carl", Color.red, 12), - VailActorTypeId.MrPuffy => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Mr. Puffy", Color.red, 12), - VailActorTypeId.MissPuffy => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Mrs. Puffy", Color.red, 12), - VailActorTypeId.Angel => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Angel", Color.red, 12), - VailActorTypeId.Brandy => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Brandy", Color.red, 12), - VailActorTypeId.Crystal => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Crystal", Color.red, 12), - VailActorTypeId.Destiny => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Destiny", Color.red, 12), - VailActorTypeId.DemonBoss => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Demon Boss", Color.red, 12), - VailActorTypeId.PuffyBossMale => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Puffy Boss Male", Color.red, 12), - VailActorTypeId.PuffyBossFemale => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Puffy Boss Female", Color.red, 12), - VailActorTypeId.BossMutant => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Boss Mutant", Color.red, 12), - VailActorTypeId.CreepyVirginia => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Creepy Virginia", Color.red, 12), - VailActorTypeId.Armsy => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Armsy", Color.red, 12), - VailActorTypeId.Frank => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Frank", Color.red, 12), - VailActorTypeId.Eddy => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Eddy", Color.red, 12), - VailActorTypeId.Greg => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Greg", Color.red, 12), - VailActorTypeId.Henry => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Henry", Color.red, 12), - VailActorTypeId.Igor => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Igor", Color.red, 12), - VailActorTypeId.Elise => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Elise", Color.red, 12), - VailActorTypeId.Legsy => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Legsy", Color.red, 12), - VailActorTypeId.Holey => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Holey", Color.red, 12), - _ => drawn - }; + if (actors == null) return; + + var cam = SotfMain.CameraMain; + var camPos = cam.transform.position; + var screenHeight = Screen.height; + + foreach (var actor in actors) + { + if (!actor || actor._isDead) continue; + + var actorPosition = actor.transform.position; + var worldToScreen = cam.WorldToScreenPoint(actorPosition); + if (worldToScreen.z < 0f) continue; - if (drawn) - UIHelper.DrawString(new Vector2(worldToScreen.x, Screen.height - worldToScreen.y + 12), - Mathf.Round(reiDistance) + "m", Color.yellow, 12); - } + var distance = Vector3.Distance(camPos, actorPosition); + if (distance >= 250f) continue; - if (worldToScreen.z >= 0f && FriendlyEsp) - { - drawn = actor.TypeId switch - { - VailActorTypeId.Robby => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Robby", Color.cyan, 12), - VailActorTypeId.Virginia => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Virginia", Color.cyan, 12), - _ => false - }; - if (drawn) - UIHelper.DrawString(new Vector2(worldToScreen.x, Screen.height - worldToScreen.y + 12), - Mathf.Round(reiDistance) + "m", Color.yellow, 12); - } + var typeId = actor.TypeId; + var screenPos = new Vector2(worldToScreen.x, screenHeight - worldToScreen.y); - if (worldToScreen.z >= 0f && reiDistance < 250f && AnimalsEsp) - { - drawn = actor.TypeId switch - { - VailActorTypeId.Rabbit => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Rabbit", Color.green, 12), - VailActorTypeId.Squirrel => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Squirrel", Color.green, 12), - VailActorTypeId.Turtle => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Turtle", Color.green, 12), - VailActorTypeId.Seagull => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Seagull", Color.green, 12), - VailActorTypeId.Eagle => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Eagle", Color.green, 12), - VailActorTypeId.Duck => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Duck", Color.green, 12), - VailActorTypeId.Moose => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Moose", Color.green, 12), - VailActorTypeId.Salmon => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Salmon", Color.green, 12), - VailActorTypeId.Bat => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Bat", Color.green, 12), - VailActorTypeId.Deer => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Deer", Color.green, 12), - VailActorTypeId.Bluebird => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Bluebird", Color.green, 12), - VailActorTypeId.Hummingbird => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Hummingbird", Color.green, 12), - VailActorTypeId.LandTurtle => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Land Turtle", Color.green, 12), - VailActorTypeId.Raccoon => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Raccoon", Color.green, 12), - VailActorTypeId.Shark => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Shark", Color.red, 12), - VailActorTypeId.KillerWhale => UIHelper.DrawString( - new Vector2(worldToScreen.x, Screen.height - worldToScreen.y), "Killer Whale", Color.red, 12), - _ => false - }; - if (drawn) - UIHelper.DrawString(new Vector2(worldToScreen.x, Screen.height - worldToScreen.y + 12), - Mathf.Round(reiDistance) + "m", Color.yellow, 12); - } + if (EnemyEsp && EnemyLabels.TryGetValue(typeId, out var enemy)) + { + UIHelper.DrawEspLabel(screenPos, enemy.Name, enemy.Color, distance); + continue; + } + + if (FriendlyEsp && FriendlyLabels.TryGetValue(typeId, out var friendly)) + { + UIHelper.DrawEspLabel(screenPos, friendly.Name, friendly.Color, distance); + continue; } + + if (AnimalsEsp && AnimalLabels.TryGetValue(typeId, out var animal)) + { + UIHelper.DrawEspLabel(screenPos, animal.Name, animal.Color, distance); + } + } } -} \ No newline at end of file +} diff --git a/SOTF_ModMenu/Cheats/Other/InfiniteLogs.cs b/SOTF_ModMenu/Cheats/Other/InfiniteLogs.cs index 5c7321e..3dae46e 100644 --- a/SOTF_ModMenu/Cheats/Other/InfiniteLogs.cs +++ b/SOTF_ModMenu/Cheats/Other/InfiniteLogs.cs @@ -1,4 +1,5 @@ -using TheForest.Utils; +using SOTF_ModMenu.Cheats.World; +using TheForest.Utils; namespace SOTF_ModMenu.Cheats.Other; @@ -8,21 +9,22 @@ public class InfiniteLogs public static void Toggle() { - if (!LocalPlayer.IsInWorld) { Plugin.log.LogError($"Failed to toggle InfiniteBuild, not in a world"); return; } + if (!LocalPlayer.IsInWorld) { Plugin.log.LogError("Failed to toggle InfiniteLogs, not in a world"); return; } Enabled = !Enabled; + InfiniteBuild.ApplyHeldHack(); } - - public static void Update() - { - // INFO: LocalPlayer.Inventory.Logs no longer exists - - /* + public static void Disable() + { if (!Enabled) return; - - if (LocalPlayer.Inventory.Logs.HasLogs && LocalPlayer.Inventory.Logs.Amount < 2) - LocalPlayer.Inventory.Logs._heldItemController._heldCount = 2; - - */ + Enabled = false; + try + { + InfiniteBuild.ApplyHeldHack(); + } + catch + { + // Game may already be unloading + } } -} \ No newline at end of file +} diff --git a/SOTF_ModMenu/Cheats/Player/GodMode.cs b/SOTF_ModMenu/Cheats/Player/GodMode.cs index cc21e4e..dfea5f4 100644 --- a/SOTF_ModMenu/Cheats/Player/GodMode.cs +++ b/SOTF_ModMenu/Cheats/Player/GodMode.cs @@ -1,4 +1,4 @@ -using TheForest.Utils; +using TheForest.Utils; namespace SOTF_ModMenu.Cheats.Player; @@ -30,6 +30,27 @@ public static void Toggle() LocalPlayer.Vitals._health._max = _maxHealthBefore; } } + + public static void Disable() + { + if (Enabled) + { + try + { + if (_gotMaxHealthValue && LocalPlayer.Vitals != null) + LocalPlayer.Vitals._health._max = _maxHealthBefore; + } + catch + { + // Game may already be unloading + } + } + + Enabled = false; + _check = false; + _gotMaxHealthValue = false; + _maxHealthBefore = 0; + } public static void Update() { diff --git a/SOTF_ModMenu/Cheats/Player/InfAmmo.cs b/SOTF_ModMenu/Cheats/Player/InfAmmo.cs index e4903ff..789243f 100644 --- a/SOTF_ModMenu/Cheats/Player/InfAmmo.cs +++ b/SOTF_ModMenu/Cheats/Player/InfAmmo.cs @@ -10,22 +10,32 @@ public class InfAmmo public static void Toggle() { - if (!LocalPlayer.IsInWorld) { Plugin.log.LogError($"Failed to toggle InfAmmo, not in a world"); return; } + if (!LocalPlayer.IsInWorld) { Plugin.log.LogError("Failed to toggle InfAmmo, not in a world"); return; } Enabled = !Enabled; } public static void Update() { - // INFO: LocalPlayer.Inventory.Logs no longer exists - - /* - //if holding item, try to get RangedWeaponItemInstanceModule and set ammo to max if (!Enabled || !LocalPlayer.IsInWorld) return; - - if (!LocalPlayer.Inventory.IsRightHandEmpty() && LocalPlayer.Inventory.RightHandItem.Data._type.HasFlag(Types.RangedWeapon)) - if (LocalPlayer.Inventory.RightHandItem.TryGetModule(out RangedWeaponItemInstanceModule module)) - if (module._rangedWeapon._ammo._currentCount < module._rangedWeapon._ammo._maxCount) - module._rangedWeapon._ammo._currentCount = module._rangedWeapon._ammo._maxCount; - */ + + try + { + var inventory = LocalPlayer.Inventory; + if (inventory == null || inventory.IsRightHandEmpty()) return; + + var item = inventory.RightHandItem; + if (item?.Data == null) return; + if (!item.Data._type.HasFlag(Types.RangedWeapon)) return; + if (!item.TryGetModule(out RangedWeaponItemInstanceModule module)) return; + if (module?._rangedWeapon?._ammo == null) return; + + var ammo = module._rangedWeapon._ammo; + if (ammo._currentCount < ammo._maxCount) + ammo._currentCount = ammo._maxCount; + } + catch + { + // Weapon type may not expose ammo the same way + } } -} \ No newline at end of file +} diff --git a/SOTF_ModMenu/Cheats/Player/NoCold.cs b/SOTF_ModMenu/Cheats/Player/NoCold.cs index a34fe81..da0a0cd 100644 --- a/SOTF_ModMenu/Cheats/Player/NoCold.cs +++ b/SOTF_ModMenu/Cheats/Player/NoCold.cs @@ -1,4 +1,4 @@ -using TheForest.Utils; +using TheForest.Utils; namespace SOTF_ModMenu.Cheats.Player; @@ -13,4 +13,19 @@ public static void Toggle() LocalPlayer.Stats.InteriorSpaceWarmth = Enabled; } + + public static void Disable() + { + if (!Enabled) return; + Enabled = false; + try + { + if (LocalPlayer.Stats != null) + LocalPlayer.Stats.InteriorSpaceWarmth = false; + } + catch + { + // Game may already be unloading + } + } } \ No newline at end of file diff --git a/SOTF_ModMenu/Cheats/Player/NoFallDamage.cs b/SOTF_ModMenu/Cheats/Player/NoFallDamage.cs index e947ab2..188fa1c 100644 --- a/SOTF_ModMenu/Cheats/Player/NoFallDamage.cs +++ b/SOTF_ModMenu/Cheats/Player/NoFallDamage.cs @@ -1,4 +1,4 @@ -using TheForest.Utils; +using TheForest.Utils; namespace SOTF_ModMenu.Cheats.Player; @@ -11,12 +11,21 @@ public static void Toggle() if (!LocalPlayer.IsInWorld) { Plugin.log.LogError("Failed to toggle NoFallDamage, not in a world"); return; } Enabled = !Enabled; + LocalPlayer.FpCharacter._allowFallDamage = !Enabled; } - - public static void Update() + + public static void Disable() { - if (!Enabled || !LocalPlayer.IsInWorld) return; - - LocalPlayer.FpCharacter._allowFallDamage = !Enabled; + if (!Enabled) return; + Enabled = false; + try + { + if (LocalPlayer.FpCharacter != null) + LocalPlayer.FpCharacter._allowFallDamage = true; + } + catch + { + // Game may already be unloading + } } } \ No newline at end of file diff --git a/SOTF_ModMenu/Cheats/Player/SpeedyRun.cs b/SOTF_ModMenu/Cheats/Player/SpeedyRun.cs index b6ccae1..865fce8 100644 --- a/SOTF_ModMenu/Cheats/Player/SpeedyRun.cs +++ b/SOTF_ModMenu/Cheats/Player/SpeedyRun.cs @@ -1,4 +1,4 @@ -using TheForest; +using TheForest; using TheForest.Utils; namespace SOTF_ModMenu.Cheats.Player; @@ -14,4 +14,19 @@ public static void Toggle() DebugConsole.Instance._speedyrun(Enabled ? "on" : "off"); } + + public static void Disable() + { + if (!Enabled) return; + Enabled = false; + try + { + if (DebugConsole.Instance != null) + DebugConsole.Instance._speedyrun("off"); + } + catch + { + // Game may already be unloading + } + } } \ No newline at end of file diff --git a/SOTF_ModMenu/Cheats/World/CaveLight.cs b/SOTF_ModMenu/Cheats/World/CaveLight.cs index a95bfb2..cf9f9c2 100644 --- a/SOTF_ModMenu/Cheats/World/CaveLight.cs +++ b/SOTF_ModMenu/Cheats/World/CaveLight.cs @@ -1,4 +1,4 @@ -using TheForest.Utils; +using TheForest.Utils; using UnityEngine; using UnityEngine.Rendering.HighDefinition; @@ -18,6 +18,17 @@ public static void Toggle() ToggleCaveLight(); } + public static void Disable() + { + Enabled = false; + if (_light != null) + { + Object.Destroy(_light); + _light = null; + } + _lightCreated = false; + } + private static void ToggleCaveLight() { if(!_lightCreated) @@ -44,9 +55,7 @@ private static void ToggleCaveLight() public static void Update() { - if (!LocalPlayer.IsInWorld && !SotfMain.SonsMainScene.isLoaded) - { - _lightCreated = false; - } + if (!LocalPlayer.IsInWorld && !SotfMain.SonsMainScene.isLoaded && _lightCreated) + Disable(); } } \ No newline at end of file diff --git a/SOTF_ModMenu/Cheats/World/InfiniteBuild.cs b/SOTF_ModMenu/Cheats/World/InfiniteBuild.cs index 1a6b1e4..19655ae 100644 --- a/SOTF_ModMenu/Cheats/World/InfiniteBuild.cs +++ b/SOTF_ModMenu/Cheats/World/InfiniteBuild.cs @@ -1,4 +1,5 @@ -using TheForest.Utils; +using SOTF_ModMenu.Cheats.Other; +using TheForest.Utils; namespace SOTF_ModMenu.Cheats.World; @@ -8,9 +9,28 @@ public class InfiniteBuild public static void Toggle() { - if (!LocalPlayer.IsInWorld) { Plugin.log.LogError($"Failed to toggle InfiniteBuild, not in a world"); return; } + if (!LocalPlayer.IsInWorld) { Plugin.log.LogError("Failed to toggle InfiniteBuild, not in a world"); return; } Enabled = !Enabled; - - LocalPlayer.Inventory.HeldOnlyItemController.InfiniteHack = Enabled; + ApplyHeldHack(); } -} \ No newline at end of file + + public static void Disable() + { + if (!Enabled) return; + Enabled = false; + try + { + ApplyHeldHack(); + } + catch + { + // Game may already be unloading + } + } + + public static void ApplyHeldHack() + { + if (LocalPlayer.Inventory?.HeldOnlyItemController == null) return; + LocalPlayer.Inventory.HeldOnlyItemController.InfiniteHack = Enabled || InfiniteLogs.Enabled; + } +} diff --git a/SOTF_ModMenu/Cheats/World/InstantBuild.cs b/SOTF_ModMenu/Cheats/World/InstantBuild.cs index c65edbf..e928c3c 100644 --- a/SOTF_ModMenu/Cheats/World/InstantBuild.cs +++ b/SOTF_ModMenu/Cheats/World/InstantBuild.cs @@ -1,4 +1,4 @@ -using Sons.Crafting.Structures; +using Sons.Crafting.Structures; using TheForest.Utils; namespace SOTF_ModMenu.Cheats.World; @@ -12,7 +12,21 @@ public static void Toggle() if (!LocalPlayer.IsInWorld) { Plugin.log.LogError($"Failed to toggle InstantBuild, not in a world"); return; } Enabled = !Enabled; - //LocalPlayer.StructureCraftingSystem.InstantBuild = Enabled; StructureCraftingSystem._instance.InstantBuild = Enabled; } + + public static void Disable() + { + if (!Enabled) return; + Enabled = false; + try + { + if (StructureCraftingSystem._instance != null) + StructureCraftingSystem._instance.InstantBuild = false; + } + catch + { + // Game may already be unloading + } + } } \ No newline at end of file diff --git a/SOTF_ModMenu/Main.cs b/SOTF_ModMenu/Main.cs index 03abf41..072e4d8 100644 --- a/SOTF_ModMenu/Main.cs +++ b/SOTF_ModMenu/Main.cs @@ -1,3 +1,4 @@ +using System; using Construction; using Il2CppSystem.Collections.Generic; using Sons.Input; @@ -15,14 +16,19 @@ namespace SOTF_ModMenu; public class SotfMain : MonoBehaviour { + public SotfMain(IntPtr ptr) : base(ptr) { } + public static Camera CameraMain; public static HashSet DirtyStructures; public static Scene SonsMainScene; private List _playerViewsList = new(); private bool _stylesInitialized; + private bool _wasSceneLoaded; + private int _lastToggleFrame = -1; private void Start() { + Plugin.log.LogInfo("SotfMain started"); InvokeRepeating(nameof(SlowUpdate), 0.0f, 0.5f); OgMenuMusic.Start(); @@ -36,6 +42,9 @@ private void Start() private void OnGUI() { + // Hotkey also here: if Update throws on cheats, menu still opens + TryToggleMenu(); + if (!_stylesInitialized) { _stylesInitialized = true; @@ -47,60 +56,81 @@ private void OnGUI() private void SlowUpdate() { - // Disable all cheats when in main menu - if (!SonsMainScene.isLoaded) - { - ESP.Enabled = false; - InfiniteLogs.Enabled = false; - AlwaysRested.Enabled = false; - GodMode.Enabled = false; - InfAmmo.Enabled = false; - InfHeal.Enabled = false; - InfLungCapacity.Enabled = false; - InfStamina.Enabled = false; - NoCold.Enabled = false; - NoFallDamage.Enabled = false; - NoHunger.Enabled = false; - NoThirst.Enabled = false; - SpeedyRun.Enabled = false; - CaveLight.Enabled = false; - InfiniteBuild.Enabled = false; - InstantBuild.Enabled = false; - } + bool sceneLoaded = SonsMainScene.isLoaded; + + if (_wasSceneLoaded && !sceneLoaded) + DisableAllCheats(); + + _wasSceneLoaded = sceneLoaded; + } + + private static void DisableAllCheats() + { + ESP.Disable(); + SpeedyRun.Disable(); + InstantBuild.Disable(); + InfiniteBuild.Disable(); + InfiniteLogs.Disable(); + CaveLight.Disable(); + NoCold.Disable(); + NoFallDamage.Disable(); + GodMode.Disable(); + + AlwaysRested.Enabled = false; + InfAmmo.Enabled = false; + InfHeal.Enabled = false; + InfLungCapacity.Enabled = false; + InfStamina.Enabled = false; + NoHunger.Enabled = false; + NoThirst.Enabled = false; + + DirtyStructures = null; } private void Update() { - // Cache Scene, Camera - if(!SonsMainScene.isLoaded) SonsMainScene = SceneManager.GetSceneByName("SonsMain"); - if(CameraMain == null) CameraMain = Camera.main ?? null; - - GodMode.Update(); - CaveLight.Update(); - NoThirst.Update(); - NoHunger.Update(); - InfStamina.Update(); - InfHeal.Update(); - InfLungCapacity.Update(); - InfAmmo.Update(); - AlwaysRested.Update(); - InfiniteLogs.Update(); - NoFallDamage.Update(); - - if(!Input.anyKey || !Input.anyKeyDown) return; - - if (Input.GetKeyDown(Plugin.ModKey.Value)) + TryToggleMenu(); + + try + { + if (!SonsMainScene.isLoaded) SonsMainScene = SceneManager.GetSceneByName("SonsMain"); + if (CameraMain == null) CameraMain = Camera.main ?? null; + + GodMode.Update(); + CaveLight.Update(); + NoThirst.Update(); + NoHunger.Update(); + InfStamina.Update(); + InfHeal.Update(); + InfLungCapacity.Update(); + AlwaysRested.Update(); + InfAmmo.Update(); + } + catch (Exception ex) + { + Plugin.log.LogError($"Cheat update error: {ex.Message}"); + } + } + + private void TryToggleMenu() + { + if (!IsMenuTogglePressed()) return; + if (Time.frameCount == _lastToggleFrame) return; + _lastToggleFrame = Time.frameCount; + + UIManager.MenuVisible = !UIManager.MenuVisible; + Plugin.log.LogInfo($"Mod menu {(UIManager.MenuVisible ? "opened" : "closed")} (key={Plugin.ModKey.Value})"); + + if (UIManager.MenuVisible) + { + InputSystem.SetState(0, true); + Cursor.visible = true; + Cursor.lockState = CursorLockMode.None; + return; + } + + try { - UIManager.MenuVisible = !UIManager.MenuVisible; - - if (UIManager.MenuVisible) - { - InputSystem.SetState(0, true); - Cursor.visible = true; - Cursor.lockState = CursorLockMode.None; - return; - } - if (_playerViewsList.Contains(LocalPlayer.CurrentView)) { InputSystem.SetState(0, false); @@ -108,5 +138,25 @@ private void Update() Cursor.lockState = CursorLockMode.Locked; } } + catch + { + // LocalPlayer may be unavailable in menu + } + } + + private static bool IsMenuTogglePressed() + { + var key = Plugin.ModKey.Value; + if (Input.GetKeyDown(key)) return true; + + // German ^ key can be reported as any of these depending on layout/Unity + if (key is KeyCode.Backslash or KeyCode.BackQuote or KeyCode.Caret) + { + if (Input.GetKeyDown(KeyCode.Backslash)) return true; + if (Input.GetKeyDown(KeyCode.BackQuote)) return true; + if (Input.GetKeyDown(KeyCode.Caret)) return true; + } + + return false; } -} \ No newline at end of file +} diff --git a/SOTF_ModMenu/Plugin.cs b/SOTF_ModMenu/Plugin.cs index 074e043..af23327 100644 --- a/SOTF_ModMenu/Plugin.cs +++ b/SOTF_ModMenu/Plugin.cs @@ -20,7 +20,7 @@ public const string VERSION = "1.5.0"; public static ConfigFile ConfigFile = new (Path.Combine(Paths.ConfigPath, "SOTF_ModMenu.cfg"), true); - public static ConfigEntry ModKey = ConfigFile.Bind("Hotkeys", "Toggle", KeyCode.BackQuote, "Toggles menu visibility"); + public static ConfigEntry ModKey = ConfigFile.Bind("Hotkeys", "Toggle", KeyCode.Backslash, "Opens the mod menu. German ^ key is Backslash."); public static ConfigEntry OgMenu = ConfigFile.Bind("Other", "Enabled", false, "Plays the og menu music form The Forest"); public Plugin() @@ -34,9 +34,10 @@ public override void Load() { ClassInjector.RegisterTypeInIl2Cpp(); GameObject gameObject = new GameObject("SotfBehavior"); - gameObject.AddComponent(); - gameObject.hideFlags = HideFlags.HideAndDontSave; Object.DontDestroyOnLoad(gameObject); + gameObject.hideFlags = HideFlags.HideAndDontSave; + gameObject.AddComponent(); + log.LogInfo($"SOTF_ModMenu loaded. Toggle key: {ModKey.Value}"); } catch { diff --git a/SOTF_ModMenu/SOTF_ModMenu.csproj b/SOTF_ModMenu/SOTF_ModMenu.csproj index 6a6c32d..0103fd9 100644 --- a/SOTF_ModMenu/SOTF_ModMenu.csproj +++ b/SOTF_ModMenu/SOTF_ModMenu.csproj @@ -8,8 +8,15 @@ true latest SOTF_ModMenu + + E:\SteamLibrary\steamapps\common\Sons Of The Forest\BepInEx\plugins + + + + + Libs\0Harmony.dll diff --git a/SOTF_ModMenu/UI/UIHelper.cs b/SOTF_ModMenu/UI/UIHelper.cs index a5d0354..b4fffa8 100644 --- a/SOTF_ModMenu/UI/UIHelper.cs +++ b/SOTF_ModMenu/UI/UIHelper.cs @@ -1,4 +1,4 @@ -using Il2CppSystem; +using Il2CppSystem; using UnityEngine; using Action = System.Action; @@ -7,6 +7,8 @@ namespace SOTF_ModMenu.UI; public class UIHelper { public static GUIStyle StringStyle { get; set; } = new(GUI.skin.label); + private static readonly GUIContent SharedContent = new(); + private static readonly string[] DistanceLabels = CreateDistanceLabels(); private static float x, y, @@ -15,6 +17,14 @@ private static float controlHeight, controlDist, nextControlY; + + private static string[] CreateDistanceLabels() + { + var labels = new string[251]; + for (int i = 0; i < labels.Length; i++) + labels[i] = i + "m"; + return labels; + } private static Texture2D _panelOff; private static Texture2D _panelOn; @@ -108,22 +118,15 @@ private static Rect NextControlRect() return r; } - static Color onColor = new Color(231 / 255f, 18 / 255f, 0 / 255f); - static Color offColor = Color.white; + private const string OnColorTag = "#E71200"; + private const string OffColorTag = "#FFFFFF"; private static string MakeEnable(string text, bool state) { PanelStyle.normal.background = state ? _panelOn : _panelOff; - - Color textColor = state ? onColor : offColor; - - int r = (int)(textColor.r * 255f); - int g = (int)(textColor.g * 255f); - int b = (int)(textColor.b * 255f); - - string colorTag = $"#{r:X2}{g:X2}{b:X2}"; - - return $"{text}"; + return state + ? $"{text}" + : $"{text}"; } public static void Button(string text, bool state, Action function) @@ -168,11 +171,18 @@ public static bool DrawString(Vector2 position, string label, Color color, int f GUI.color = color; StringStyle.fontSize = fontSize; StringStyle.normal.textColor = color; - var guicontent = new GUIContent(label); - var vector = StringStyle.CalcSize(guicontent); - GUI.Label(new Rect(centered ? position - vector / 2f : position, vector), guicontent, StringStyle); + SharedContent.text = label; + var vector = StringStyle.CalcSize(SharedContent); + GUI.Label(new Rect(centered ? position - vector / 2f : position, vector), SharedContent, StringStyle); return true; } + + public static void DrawEspLabel(Vector2 position, string name, Color color, float distance) + { + DrawString(position, name, color, 12); + int meters = Mathf.Clamp(Mathf.RoundToInt(distance), 0, DistanceLabels.Length - 1); + DrawString(new Vector2(position.x, position.y + 12f), DistanceLabels[meters], Color.yellow, 12); + } public static void DrawLine(Vector2 start, Vector2 end, Color color, float width) { GUI.depth = 0; diff --git a/SOTF_ModMenu/UI/UIManager.cs b/SOTF_ModMenu/UI/UIManager.cs index ff04236..0e98298 100644 --- a/SOTF_ModMenu/UI/UIManager.cs +++ b/SOTF_ModMenu/UI/UIManager.cs @@ -19,15 +19,9 @@ public class UIManager public static void Display() { - //ESP Draw, placed before check for cheat visible to prevent the ESP from not rendering when GUI not visible + // ESP Draw, before menu visibility check so ESP still renders with menu closed if (ESP.Enabled) - { ESP.Enable(); - } - else - { - ESP.Disable(); - } if(!MenuVisible) return; @@ -44,7 +38,7 @@ public static void Display() UIHelper.Button("No Hunger", NoHunger.Enabled, NoHunger.Toggle); UIHelper.Button("No Thirst", NoThirst.Enabled, NoThirst.Toggle); UIHelper.Button("Always Rested", AlwaysRested.Enabled, AlwaysRested.Toggle); - UIHelper.Button("Infinite Ammo [Broken]", InfAmmo.Enabled, InfAmmo.Toggle); + UIHelper.Button("Infinite Ammo", InfAmmo.Enabled, InfAmmo.Toggle); UIHelper.Button("SpeedRun", SpeedyRun.Enabled, SpeedyRun.Toggle); // World @@ -64,7 +58,7 @@ public static void Display() // Other UIHelper.Begin("Other", 505, 10, 165, 88, 0, 22, 1); UIHelper.Button("OG Menu Music", OgMenuMusic.Enabled, OgMenuMusic.Toggle); - UIHelper.Button("Infinite Logs [Broken]", InfiniteLogs.Enabled, InfiniteLogs.Toggle); + UIHelper.Button("Infinite Logs", InfiniteLogs.Enabled, InfiniteLogs.Toggle); if (UIHelper.Button("Fix Stuck Mouse")) { InputSystem.SetState(0, false); @@ -113,6 +107,8 @@ private static void ItemSpawnerWindow() private static Vector2 _scrollPosition = Vector2.zero; private static List _itemList; private static string _searchQuery = ""; + private static string _lastFilterQuery; + private static string _cachedFilterResult = ""; private static bool _initialized; private static void AllIdsWindow() @@ -124,6 +120,7 @@ private static void AllIdsWindow() { _initialized = true; _itemList ??= ItemDatabaseManager.Items; + _lastFilterQuery = null; } } @@ -140,20 +137,26 @@ private static void AllIdsWindow() } else { - var writer = new StringWriter(); - foreach (ItemData item in _itemList) + if (_lastFilterQuery != _searchQuery) { - if (item._name.ToLower().Contains(_searchQuery.ToLower())) + _lastFilterQuery = _searchQuery; + var query = _searchQuery.ToLower(); + var writer = new StringWriter(); + foreach (ItemData item in _itemList) { - writer.Write(item._name); - writer.Write(" : "); - writer.WriteLine(item._id); + if (item._name.ToLower().Contains(query)) + { + writer.Write(item._name); + writer.Write(" : "); + writer.WriteLine(item._id); + } } + _cachedFilterResult = writer.ToString(); } _scrollPosition = GUILayout.BeginScrollView(_scrollPosition); GUILayout.BeginVertical(); - GUILayout.Label(writer.ToString()); + GUILayout.Label(_cachedFilterResult); GUILayout.EndVertical(); GUILayout.EndScrollView(); }