diff --git a/.gitignore b/.gitignore index 940794e..ee1c53a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +# Dependencies +Source/References/ + # Build results [Dd]ebug/ [Dd]ebugPublic/ @@ -72,6 +75,7 @@ artifacts/ *.pidb *.svclog *.scc +*.kra # Chutzpah Test files _Chutzpah* diff --git a/DebugMod.sln b/DebugMod.sln index 75072fc..3d5b1bd 100644 --- a/DebugMod.sln +++ b/DebugMod.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Express 2013 for Windows Desktop -VisualStudioVersion = 12.0.21005.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1340 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DebugMod", "Source\DebugMod.csproj", "{E3E4D0B7-656C-6C50-7567-696E2E57696E}" EndProject @@ -18,4 +18,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3A3F4FD5-C58A-44B9-88F8-1970BD2A3530} + EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 2b30b8b..8aafc2e 100644 --- a/README.md +++ b/README.md @@ -1 +1,123 @@ -Original mod by KeinZantezuken. Remaking using canvas instead of imgui and the modding framework created by myself and MyEyes/Firzen. \ No newline at end of file +---------------------------------------------------------------------------------------- + FEATURES +---------------------------------------------------------------------------------------- +* A toggleable UI in-game that provides the following functions: +* Cheats such as invincibility and noclip +* The ability to unlock all charms or repair broken ones +* Change which skills the player has +* Change which items the player has +* Give the player more of consumable resources such as geo and essence +* Respawn bosses +* Hold multiple dream gate positions +* Change the player's respawn point to anywhere in the current scene +* Recall to the set respawn point +* Kill all enemies +* Add HP bars to enemies +* Draw collision boxes for enemies +* Clone or delete any enemy +* Set an enemy's health to 9999 +* Change the player's nail damage +* Damage the player +* Change the camera zoom level +* Disable the in game HUD +* Make the player invisible +* Disable the lighting around the player +* Disable the vignette drawn around the player +* Change the time scale of the game +---------------------------------------------------------------------------------------- + INSTALLATION +---------------------------------------------------------------------------------------- + +## (STEAM/GOG, WINDOWS) +1) Download the modding API for Hollow Knight 1.2.2.1 + At the time of writing, 1.2.2.1-41 is the most up-to-date release: https://cdn.discordapp.com/attachments/298798821402607618/817653175586783242/Assembly-CSharp.dll +2) Right click Hollow Knight in Steam -> Properties -> Local Files -> Browse Local Files + OR + In GOG galaxy 2 -> Click button to the right of Update -> Manage Installation -> Show Folder +3) Create a backup of the game files located here +4) Copy the contents of the modding API zip into this folder (Overwrite files when asked) +5) Copy the contents of this zip into the folder (Overwrite files when asked) +6) This mod should not affect saves negatively, but it is a good idea to back them up anyway. + Saves are located at %AppData%\..\LocalLow\Team Cherry\Hollow Knight\ + +## How to build (for devs): +1) Make a folder `Source/References/`, then +2) add `Assembly-CSharp.dll` (with modding-api), and `PlayMaker.dll`, `UnityEngine.dll` and `UnityEngine.UI.dll` from your `Hollow Knight/hollow_knight_Data/Managed/`-folder + +---------------------------------------------------------------------------------------- + SAVESTATE BASICS +---------------------------------------------------------------------------------------- + +## Savestates +In order to acess the new save-states, bind `Next Page` and `Prev Page` to any keys you want (2nd page of binds in debug mod) +You can select as many total pages you want (default: 10) in the `DebugMod.GlobalSettings-1.2.2.1` in your saves folder. +Prev/Next Page will scroll through these. +After this, you can just do what you would do in normal debug mod to save and load states. + +To use your old savestates, go to this folder %APPDATA%\..\LocalLow\Team Cherry\Hollow Knight\Savestates-1221 +copy the files in this folder into the folder labeled 0 ( if it doesn't exist, make it. ) +Start the game and you will have your old saves plus 9 other pages full of empty slots by default. + +*If you have performance issues, please report it.* + +To use numpad for slot select; after installing debugmod, start and stop the game, +then go to the Hollow Knight saves-directory and open the `DebugMod.GlobalSettings-1.2.2.1` json-file. +In that file find `"NumPadForSaveStates"`, and change the corresponding value from `0` to `1`. + +To change the amount of available Savestate-slots per page (default: 6), find `MaxSaveStates` and set the corresponding value between `0` and `10` respective of how many slots you want. + +Savestates files are located in `%APPDATA%\..\LocalLow\Team Cherry\Hollow Knight\Savestates-1221\`. They use the name format `savestate.json`. +After saving a savestate to file, you can edit the name of that savestate. To do this, open the file in any text-editor, and the first variable/line should be something like `"saveStateIdentifier": "",`. Change `` inside the pair of `"`-s to whatever you want that savestate named in the select savestate in-game menu. + +## Quickslot: +The main savestate used. Not saved permanently, cleared when the game restarts. + +## Quickslot save to file +Specifies slot number, then saves the current Quickslot from temporary memory to a numbered json-file in the game save directory, overwriting any files with identical number as the selected one. + +## Load file to quickslot +Asks to specify slot number, then reads the json-file with that number from the game save directory and loads it into the Quickslot, overwriting any current savestate there. + +## Save new state to file +Specifies slot number, then makes a new savestate and saves to a json-file with the given slot number. + +## Load new state from file +Specifies slot number, then loads savestate from that file directly. + +## How to use room specific Savestates +1. Make sure you are in one of the following rooms, as these are the only ones supported + Room_Final_Boss_Core + Deepnest_Spider_Town + (you can check what room you are in by pressing f2 and reading 'scene name') +2. Create a savestate in the said room. +3. Open your hollow knight saveStates folder (%APPDATA%\..\LocalLow\Team Cherry\Hollow Knight\Savestates-1221\) +4. Subtract 1 from the page of savestates you saved in, and open that folder. (example, saved in page 4, open folder named 3) +5. Open the savestate.json, whatever number you pressed being the number it will be (example, pressed 4, open file named savestate4 with a text editor such as notepad) +6. Change the value labeled useRoomSpecific to 1 to enable room specific savestates.(if you can't find it, search for 'useRoomSpecific' with Ctrl+f) +7. Load the savestate. + +---------------------------------------------------------------------------------------- + Known Issues +---------------------------------------------------------------------------------------- +## Savestates: +* Charm effects not updating properly after loading savestates. Workaround: quitout and load back in or bench to manually apply the new charms. +* UI not refreshing properly to remove obsolete vessel fragments +* ~~Soul meter stuck at full if in that state before loading~~ +* Softlocks if loading savestate during dream transitions +* If loading savestate during transition visuals will not load. Either press 'esc' twice to get control of the knight and walk through a transition if this happens, or quit to menu and load back in before loading the savestate +* (All savestates are loaded in RAM, which means fast loading from savestates fetched from files BUT potentially general performance issues) + +---------------------------------------------------------------------------------------- + CREDITS +---------------------------------------------------------------------------------------- +Seresharp +56 +Yurihaia +Krythom +The Embraced One +Katie +KeinZantezuken +MyEyes/Firzen +Cerpintext +Mulhima +DemoJameson diff --git a/Source/BindableFunctions.cs b/Source/BindableFunctions.cs index 9b78e52..fba5a04 100644 --- a/Source/BindableFunctions.cs +++ b/Source/BindableFunctions.cs @@ -1,8 +1,14 @@ -using System; +using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.IO; using UnityEngine; using GlobalEnums; +using HutongGames.PlayMaker; +using UnityEngine.SceneManagement; +using HutongGames.PlayMaker.Actions; +using System.Collections; namespace DebugMod { @@ -10,11 +16,18 @@ public static class BindableFunctions { private static readonly FieldInfo TimeSlowed = typeof(GameManager).GetField("timeSlowed", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); private static readonly FieldInfo IgnoreUnpause = typeof(UIManager).GetField("ignoreUnpause", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); - + private static bool corniferYeeteded = false; internal static readonly FieldInfo cameraGameplayScene = typeof(CameraController).GetField("isGameplayScene", BindingFlags.Instance | BindingFlags.NonPublic); #region Misc + [BindableMethod(name = "Clear White Screen", category = "Misc")] + public static void ClearWhiteScreen() + { + GameObject.Find("Blanker White").LocateMyFSM("Blanker Control").SendEvent("FADE OUT"); + HeroController.instance.EnableRenderer(); + } + [BindableMethod(name = "Nail Damage +4", category = "Misc")] public static void IncreaseNailDamage() { @@ -152,16 +165,225 @@ public static void TimescaleUp() } } + [BindableMethod(name = "Yeet Cornifer-Toggle", category = "Misc")] + public static void CorniferYeet() + { + corniferYeeteded = !corniferYeeteded; + + if (corniferYeeteded) + { + CorniferYeeted(); + UnityEngine.SceneManagement.SceneManager.activeSceneChanged += CorniferYeeted; + Console.AddLine("Cornifer yeeted on next loads lol"); + } + else + { + UnityEngine.SceneManagement.SceneManager.activeSceneChanged -= CorniferYeeted; + Console.AddLine("Cornifer unyeeted from next loads on???"); + } + } + + private static void CorniferYeeted(Scene current, Scene next) => CorniferYeeted(); + + private static void CorniferYeeted() + { + (from x in UnityEngine.Object.FindObjectsOfType() + where x.name.Contains("Cornifer") + select x).ToList().ForEach(delegate (GameObject x) + { + UnityEngine.Object.Destroy(x); + }); + } + + + [BindableMethod(name = "Reset Debug States", category = "Misc")] + public static void Reset() + { + var pd = PlayerData.instance; + var HC = HeroController.instance; + var GC = GameCameras.instance; + + //nail damage + pd.nailDamage = 5+ pd.nailSmithUpgrades * 4; + PlayMakerFSM.BroadcastEvent("UPDATE NAIL DAMAGE"); + + //Hero Light + GameObject gameObject = DebugMod.RefKnight.transform.Find("HeroLight").gameObject; + Color color = gameObject.GetComponent().color; + color.a = 0.7f; + gameObject.GetComponent().color = color; + + //HUD + if (!GC.hudCanvas.gameObject.activeInHierarchy) + GC.hudCanvas.gameObject.SetActive(true); + + //Hide Hero + tk2dSprite component = DebugMod.RefKnight.GetComponent(); + color = component.color; color.a = 1f; + component.color = color; + + //rest all is self explanatory + Time.timeScale = 1f; + GC.tk2dCam.ZoomFactor = 1f; + HC.vignette.enabled = false; + EnemiesPanel.hitboxes = false; + EnemiesPanel.hpBars = false; + EnemiesPanel.autoUpdate = false; + pd.infiniteAirJump=false; + DebugMod.infiniteSoul = false; + DebugMod.infiniteHP = false; + pd.isInvincible=false; + DebugMod.noclip=false; + } + + #endregion + + #region SaveStates + + [BindableMethod(name = "Position Save", category = "Savestates")] + public static void RoomSaveState() + { + SavePositionManager.SaveState(); + } + [BindableMethod(name = "Position Load", category = "Savestates")] + public static void RoomLoadState() + { + SavePositionManager.LoadState(); + } + [BindableMethod(name = "Quickslot (save)", category = "Savestates")] + public static void SaveState() + { + DebugMod.saveStateManager.SaveState(SaveStateType.Memory); + } + + [BindableMethod(name = "Quickslot (load)", category = "Savestates")] + public static void LoadState() + { + DebugMod.saveStateManager.LoadState(SaveStateType.Memory); + } + + [BindableMethod(name = "Quickslot save to file", category = "Savestates")] + public static void CurrentSaveStateToFile() + { + DebugMod.saveStateManager.SaveState(SaveStateType.File); + } + + [BindableMethod(name = "Load file to quickslot", category = "Savestates")] + public static void CurrentSlotToSaveMemory() + { + DebugMod.saveStateManager.LoadState(SaveStateType.File); + } + + [BindableMethod(name = "Save new state to file", category = "Savestates")] + public static void NewSaveStateToFile() + { + DebugMod.saveStateManager.SaveState(SaveStateType.SkipOne); + + } + [BindableMethod(name = "Load new state from file", category = "Savestates")] + public static void LoadFromFile() + { + DebugMod.saveStateManager.LoadState(SaveStateType.SkipOne); + } + [BindableMethod(name = "Next Save Page", category = "Savestates")] + public static void NextStatePage() + { + if (SaveStateManager.inSelectSlotState) { + SaveStateManager.currentStateFolder++; + if (SaveStateManager.currentStateFolder == SaveStateManager.savePages) { SaveStateManager.currentStateFolder = 0; } //rollback to 0 if 10, keep folder between 0 and 9 + SaveStateManager.path = ( + Application.persistentDataPath + + "/Savestates-1221/" + + SaveStateManager.currentStateFolder.ToString() + + "/"); //change path + DebugMod.saveStateManager.RefreshStateMenu(); // update menu + } + } + [BindableMethod(name = "Prev Save Page", category = "Savestates")] + public static void PrevStatePage() + { + if (SaveStateManager.inSelectSlotState) { + SaveStateManager.currentStateFolder--; + if (SaveStateManager.currentStateFolder == -1) { SaveStateManager.currentStateFolder = SaveStateManager.savePages-1; } //rollback to max if past limit, keep folder between 0 and 9 + SaveStateManager.path = ( + Application.persistentDataPath + + "/Savestates-1221/" + + SaveStateManager.currentStateFolder.ToString() + + "/"); //change path + DebugMod.saveStateManager.RefreshStateMenu(); // update menu + } + } + + + /* + [BindableMethod(name = "Toggle auto slot", category = "Savestates")] + public static void ToggleAutoSlot() + { + DebugMod.saveStateManager.ToggleAutoSlot(); + } + + + [BindableMethod(name = "Refresh state menu", category = "Savestates")] + public static void RefreshSaveStates() + { + DebugMod.saveStateManager.RefreshStateMenu(); + } + */ + #endregion #region Visual + [BindableMethod(name = "Show Hitboxes", category = "Visual")] + public static void ShowHitboxes() + { + if (++DebugMod.settings.ShowHitBoxes > 2) DebugMod.settings.ShowHitBoxes = 0; + Console.AddLine("Toggled show hitboxes: " + DebugMod.settings.ShowHitBoxes); + } + [BindableMethod(name = "Toggle Vignette", category = "Visual")] public static void ToggleVignette() { HeroController.instance.vignette.enabled = !HeroController.instance.vignette.enabled; } + [BindableMethod(name = "Deactivate Visual Masks", category = "Visual")] + public static void DeactivateVisualMasks() { + int ctr = 0; + + void disableMask(GameObject go) { + foreach (Renderer r in go.GetComponentsInChildren()) { + if (r.enabled) { + ctr++; + r.enabled = false; + } + } + } + + float knightZ = HeroController.instance.transform.position.z; + foreach (GameObject go in GameObject.FindObjectsOfType()) { + if (go.transform.position.z > knightZ) continue; + + // A collection of ways to identify masks. It's possible some slip through the cracks I guess + if (go.name.StartsWith("msk_")) + disableMask(go); + else if (go.name.StartsWith("Tut_msk")) + disableMask(go); + else if (go.name.StartsWith("black_solid")) + disableMask(go); + else if (go.name.ToLower().Contains("vignette")) + disableMask(go); + else if (go.LocateMyFSM("unmasker") is PlayMakerFSM) + disableMask(go); + else if (go.LocateMyFSM("remasker_inverse") is PlayMakerFSM) + disableMask(go); + else if (go.LocateMyFSM("remasker") is PlayMakerFSM) + disableMask(go); + } + + Console.AddLine($"Deactivated {ctr} masks"); + } + [BindableMethod(name = "Toggle Hero Light", category = "Visual")] public static void ToggleHeroLight() { @@ -196,6 +418,14 @@ public static void ToggleHUD() } } + [BindableMethod(name = "Toggle Camera Shake", category = "Visual")] + public static void ToggleCameraShake() + { + bool newValue = !GameCameras.instance.cameraShakeFSM.enabled; + GameCameras.instance.cameraShakeFSM.enabled = newValue; + Console.AddLine($"{(newValue ? "Enabling" : "Disabling")} Camera Shake..."); + } + [BindableMethod(name = "Reset Camera Zoom", category = "Visual")] public static void ResetZoom() { @@ -245,10 +475,26 @@ public static void HideHero() [BindableMethod(name = "Toggle All UI", category = "Mod UI")] public static void ToggleAllPanels() { - bool active = !(DebugMod.settings.HelpPanelVisible || DebugMod.settings.InfoPanelVisible || DebugMod.settings.EnemiesPanelVisible || DebugMod.settings.TopMenuVisible || DebugMod.settings.ConsoleVisible); + bool active = !( + DebugMod.settings.HelpPanelVisible || + DebugMod.settings.InfoPanelVisible || + DebugMod.settings.EnemiesPanelVisible || + DebugMod.settings.TopMenuVisible || + DebugMod.settings.ConsoleVisible || + DebugMod.settings.MinInfoPanelVisible + ); + if (MinimalInfoPanel.minInfo) + { + DebugMod.settings.InfoPanelVisible = false; + DebugMod.settings.MinInfoPanelVisible = active; + } + else + { + DebugMod.settings.InfoPanelVisible = active; + DebugMod.settings.MinInfoPanelVisible = false; + } DebugMod.settings.TopMenuVisible = active; - DebugMod.settings.InfoPanelVisible = active; DebugMod.settings.EnemiesPanelVisible = active; DebugMod.settings.ConsoleVisible = active; DebugMod.settings.HelpPanelVisible = active; @@ -258,6 +504,11 @@ public static void ToggleAllPanels() EnemiesPanel.RefreshEnemyList(); } } + [BindableMethod(name = "Toggle showing room IDs", category = "Mod UI")] + public static void ToggleShowRoomIDs() + { + DebugMod.settings.ShowRoomIDs = !DebugMod.settings.ShowRoomIDs; + } [BindableMethod(name = "Toggle Binds", category = "Mod UI")] public static void ToggleHelpPanel() @@ -268,10 +519,19 @@ public static void ToggleHelpPanel() [BindableMethod(name = "Toggle Info", category = "Mod UI")] public static void ToggleInfoPanel() { - DebugMod.settings.InfoPanelVisible = !DebugMod.settings.InfoPanelVisible; + if (MinimalInfoPanel.minInfo) + { + DebugMod.settings.InfoPanelVisible = false; + DebugMod.settings.MinInfoPanelVisible = !DebugMod.settings.MinInfoPanelVisible; + } + else + { + DebugMod.settings.InfoPanelVisible = !DebugMod.settings.InfoPanelVisible; + DebugMod.settings.MinInfoPanelVisible = false; + } } - [BindableMethod(name = "Toggle Menu", category = "Mod UI")] + [BindableMethod(name = "Toggle Top Menu", category = "Mod UI")] public static void ToggleTopRightPanel() { DebugMod.settings.TopMenuVisible = !DebugMod.settings.TopMenuVisible; @@ -293,11 +553,43 @@ public static void ToggleEnemyPanel() } } + [BindableMethod(name = "Toggle SaveState Panel", category = "Mod UI")] + public static void ToggleSaveStatesPanel() + { + DebugMod.settings.SaveStatePanelVisible = !DebugMod.settings.SaveStatePanelVisible; + } + + // A variant of info panel. View handled in the two InfoPanel classes + // TODO: stop not knowing how to use xor in c# + [BindableMethod(name = "Alt. Info Switch", category = "Mod UI")] + public static void ToggleFullInfo() + { + MinimalInfoPanel.minInfo = !MinimalInfoPanel.minInfo; + + if (MinimalInfoPanel.minInfo) + { + if (DebugMod.settings.InfoPanelVisible) + { + DebugMod.settings.InfoPanelVisible = false; + DebugMod.settings.MinInfoPanelVisible = true; + } + } + else + { + if (DebugMod.settings.MinInfoPanelVisible) + { + DebugMod.settings.MinInfoPanelVisible = false; + DebugMod.settings.InfoPanelVisible = true; + } + } + + } + #endregion #region Enemies - [BindableMethod(name = "Toggle Hitboxes", category = "Enemy Panel")] + [BindableMethod(name = "Toggle Hitboxes (enemy panel)", category = "Enemy Panel")] public static void ToggleEnemyCollision() { EnemiesPanel.hitboxes = !EnemiesPanel.hitboxes; @@ -459,11 +751,15 @@ public static void ToggleNoclip() if (DebugMod.noclip) { + if (!DebugMod.playerInvincible) + ToggleInvincibility(); Console.AddLine("Enabled noclip"); DebugMod.noclipPos = DebugMod.RefKnight.transform.position; } else { + if (DebugMod.playerInvincible) + ToggleInvincibility(); Console.AddLine("Disabled noclip"); } } @@ -473,12 +769,33 @@ public static void KillSelf() { if (DebugMod.GM.isPaused) UIManager.instance.TogglePauseGame(); HeroController.instance.TakeHealth(9999); + HeroController.instance.heroDeathPrefab.SetActive(true); DebugMod.GM.ReadyForRespawn(); GameCameras.instance.hudCanvas.gameObject.SetActive(false); GameCameras.instance.hudCanvas.gameObject.SetActive(true); } + [BindableMethod(name = "Toggle Hero Collider", category = "Cheats")] + public static void ToggleHeroCollider() + { + if (!DebugMod.RefHeroCollider.enabled) + { + DebugMod.RefHeroCollider.enabled = true; + DebugMod.RefHeroBox.enabled = true; + Console.AddLine("Enabled hero collider" + (DebugMod.noclip ? " and disabled noclip" : "")); + DebugMod.noclip = false; + } + else + { + DebugMod.RefHeroCollider.enabled = false; + DebugMod.RefHeroBox.enabled = false; + Console.AddLine("Disabled hero collider" + (DebugMod.noclip ? "" : " and enabled noclip")); + DebugMod.noclip = true; + DebugMod.noclipPos = DebugMod.RefKnight.transform.position; + } + } + #endregion #region Charms @@ -500,6 +817,7 @@ public static void GiveAllCharms() PlayerData.instance.hasCharm = true; PlayerData.instance.charmsOwned = 40; PlayerData.instance.royalCharmState = 4; + PlayerData.instance.gotShadeCharm = true; PlayerData.instance.gotKingFragment = true; PlayerData.instance.gotQueenFragment = true; PlayerData.instance.notchShroomOgres = true; @@ -517,6 +835,7 @@ public static void GiveAllCharms() PlayerData.instance.SetBoolInternal("fragileHealth_unbreakable", true); PlayerData.instance.SetBoolInternal("fragileStrength_unbreakable", true); PlayerData.instance.SetIntInternal("grimmChildLevel", 5); + PlayerData.instance.gotGrimmNotch = true; PlayerData.instance.charmSlots = 11; } @@ -532,8 +851,10 @@ public static void IncreaseKingsoulLevel() } PlayerData.instance.royalCharmState++; + + PlayerData.instance.gotShadeCharm = PlayerData.instance.royalCharmState == 4; - if (PlayerData.instance.royalCharmState >= 5) + if (PlayerData.instance.royalCharmState >= 5) { PlayerData.instance.royalCharmState = 0; } @@ -569,7 +890,7 @@ public static void FixFragileStrength() } } - [BindableMethod(name = "Overcharm", category = "Charms")] + [BindableMethod(name = "Can Overcharm", category = "Charms")] public static void ToggleOvercharm() { PlayerData.instance.canOvercharm = true; @@ -868,7 +1189,57 @@ public static void IncreaseQuakeLevel() #endregion #region Bosses + [BindableMethod(name = "Reload Radiance Fight", category = "Bosses")] + public static void LoadRadiance() + { + GameManager.instance.StartCoroutine(LoadRadianceRoom()); + } + private static IEnumerator LoadRadianceRoom() + { + //makes sure the initial platform and challage prompt appears + HeroController.instance.gameObject.LocateMyFSM("ProxyFSM").FsmVariables.FindFsmBool("Faced Radiance").Value = false; + HeroController.instance.RelinquishControl(); + HeroController.instance.StopAnimationControl(); + PlayMakerFSM.BroadcastEvent("START DREAM ENTRY"); + PlayMakerFSM.BroadcastEvent("DREAM ENTER"); + if (DebugMod.GM.IsGamePaused()) + { + PlayerData.instance.disablePause = false; + UIManager.instance.TogglePauseGame(); + } + HeroController.instance.enterWithoutInput = true; // stop early control on scene load + GameManager.instance.ChangeToScene("Dream_Final_Boss", "door1", 0f); + yield return new WaitUntil(() => HeroController.instance.acceptingInput); + yield return null; + //cuz people mostly have full soul from thk fight + HeroController.instance.AddMPCharge(198); + } + [BindableMethod(name = "Force Shade Fireball", category = "Bosses")] + public static void ShadeFireball() + { + try + { + GameObject shade = GameObject.Find("Hollow Shade(Clone)"); + PlayMakerFSM fsm = shade.LocateMyFSM("Shade Control"); + //FsmState fsmState = fsm.FsmStates.First(t => t.Name == "Attack Choice"); i couldnt get this to work. the current solution works good enough. + //SendRandomEvent sendRandom = fsmState.Actions.OfType().First(); + //sendRandom.weights[0] = 0f; + //sendRandom.weights[1] = 1; + fsm.SetState("Fireball Pos"); + } + catch (Exception e) + { + Console.AddLine("Ignore below message if no shade is in scene"); + Console.AddLine(e.Message); + } + } + [BindableMethod(name = "Force Uumuu extra attack", category = "Bosses")] + public static void ForceUumuuExtra() + { + BossHandler.UumuuExtra(); + } + [BindableMethod(name = "Respawn Ghost", category = "Bosses")] public static void RespawnGhost() { @@ -1074,7 +1445,7 @@ public static void ToggleXunFlower() } else { - PlayerData.instance.hasLantern = false; + PlayerData.instance.hasXunFlower = false; Console.AddLine("Taking away delicate flower"); } } @@ -1165,5 +1536,120 @@ public static void AddDGPosition() } #endregion + + #region ExportData + + [BindableMethod(name = "SceneData to file", category = "ExportData")] + public static void SceneDataToFile() + { + File.WriteAllText(string.Concat( + new object[] { Application.persistentDataPath, "/SceneData.json" }), + JsonUtility.ToJson( + SceneData.instance, + prettyPrint: true + ) + ); + } + + [BindableMethod(name = "PlayerData to file", category = "ExportData")] + public static void PlayerDataToFile() + { + File.WriteAllText(string.Concat( + new object[] { Application.persistentDataPath, "/PlayerData.json" }), + JsonUtility.ToJson( + PlayerData.instance, + prettyPrint: true + ) + ); + } + + /* + [BindableMethod(name = "Scene FSMs to file", category = "ExportData")] + public static void FSMsToFile() + { + foreach (var fsm in GameManager.instance.GetComponents()) + { + DebugMod.instance.Log(fsm); + } + + } + */ + + // Use some threading or coroutine lol + // commented out for being painfully unoptimised. + // if you have use for this in its current state, you know how to uncomment and compile, or to ask me for the list :p + /* + [BindableMethod(name = "AllScenesByIndex (SLOW)", category = "ExportData")] + public static void SceneIndexesToFile() + { + Console.AddLine("sceneCountInBuildSettings: " + UnityEngine.SceneManagement.SceneManager.sceneCountInBuildSettings); + Console.AddLine("sceneCount: " + UnityEngine.SceneManagement.SceneManager.sceneCount); + + Dictionary sceneIndexList = new Dictionary(); + Scene tmp; + int curr = UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex; + + for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCountInBuildSettings; i++) + { + if (curr != i) UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(i); + tmp = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i); + sceneIndexList.Add(tmp.buildIndex, tmp.name); + //Console.AddLine(tmp.name + " == " + tmp.buildIndex); + if (curr != i) UnityEngine.SceneManagement.SceneManager.UnloadScene(i); + } + + File.WriteAllLines(string.Concat(new object[] {Application.persistentDataPath, "/SceneIndexList.txt"}), + sceneIndexList.Select(x => "[" + x.Key + " -- " + x.Value + "]").ToArray() + ); + } + */ + #endregion + + #region MovePlayer + + [BindableMethod(name = "Move 0.1 units to the right", category = "PlayerMovement")] + public static void MoveRight() + { + var HeroPos = HeroController.instance.transform.position; + HeroController.instance.transform.position= new Vector3(HeroPos.x + DebugMod.AmountToMove, HeroPos.y); + Console.AddLine("Moved player 0.1 units to the right"); + } + [BindableMethod(name = "Move 0.1 units to the left", category = "PlayerMovement")] + public static void MoveL() + { + var HeroPos = HeroController.instance.transform.position; + HeroController.instance.transform.position = new Vector3(HeroPos.x - DebugMod.AmountToMove, HeroPos.y); + Console.AddLine("Moved player 0.1 units to the left"); + } + [BindableMethod(name = "Move 0.1 units up", category = "PlayerMovement")] + public static void MoveUp() + { + var HeroPos = HeroController.instance.transform.position; + HeroController.instance.transform.position = new Vector3(HeroPos.x, HeroPos.y + DebugMod.AmountToMove); + Console.AddLine("Moved player 0.1 units to the up"); + } + [BindableMethod(name = "Move 0.1 units down", category = "PlayerMovement")] + public static void MoveDown() + { + var HeroPos = HeroController.instance.transform.position; + HeroController.instance.transform.position = new Vector3(HeroPos.x, HeroPos.y - DebugMod.AmountToMove); + Console.AddLine("Moved player 0.1 units to the down"); + } + + [BindableMethod(name = "FaceLeft", category = "PlayerMovement")] + public static void FaceLeft() + { + HeroController.instance.FaceLeft(); + Console.AddLine("Made player face left"); + } + + [BindableMethod(name = "FaceRight", category = "PlayerMovement")] + public static void FaceRight() + { + HeroController.instance.FaceRight(); + Console.AddLine("Made player face right"); + } + + #endregion } } diff --git a/Source/BossHandler.cs b/Source/BossHandler.cs index 9e3a09a..cd51f38 100644 --- a/Source/BossHandler.cs +++ b/Source/BossHandler.cs @@ -1,16 +1,24 @@ -using System.Collections.Generic; +using System.Collections; +using System.Collections.Generic; +using System.Linq; using UnityEngine; +using UnityEngine.SceneManagement; +using HutongGames.PlayMaker; +using HutongGames.PlayMaker.Actions; +//using Vasi; +using System; namespace DebugMod { public static class BossHandler { - public static bool bossSub; - public static Dictionary> bossData; public static Dictionary ghostData; public static bool bossFound; public static bool ghostFound; + + private static bool fsmToggle = false; + private static NonBouncer _coro; public static void LookForBoss(string sceneName) { @@ -124,5 +132,57 @@ public static void RespawnGhost() Console.AddLine("No ghost in this scene to respawn"); } } + + public static void UumuuExtra() + { + if (!fsmToggle) + { + SetUumuuExtra(UnityEngine.SceneManagement.SceneManager.GetActiveScene(), DebugMod.GM.nextScene); + UnityEngine.SceneManagement.SceneManager.activeSceneChanged += SetUumuuExtra; + fsmToggle = true; + } + else + { + UnityEngine.SceneManagement.SceneManager.activeSceneChanged -= SetUumuuExtra; + fsmToggle = false; + Console.AddLine("Uumuu forced extra attack OFF"); + } + } + + private static void SetUumuuExtra(Scene sceneFrom, Scene sceneTo) + { + Console.AddLine("SetUumuuExtra scene check: " + sceneFrom.name + " " + sceneTo.name); + if (sceneTo.name == "Fungus3_archive_02" && sceneTo != null) + { + try + { + if (DebugMod.GM == null) + { + throw new Exception("StartUumuuCoro( ) - gamemanager is null"); + } + DebugMod.GM.StartCoroutine(UumuuExtraCoro(sceneTo)); + } + catch (Exception e) + { + DebugMod.instance.Log(e.Message); + } + } + } + + private static IEnumerator UumuuExtraCoro(Scene activeScene) + { + Console.AddLine("Coro launched"); + while (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name != activeScene.name) + { + yield return null; + } + // Find Uumuu, their FSM, the specific State, and the Action that dictates the possibility of extra attacks + GameObject uumuu = GameObject.Find("Mega Jellyfish"); + PlayMakerFSM fsm = uumuu.LocateMyFSM("Mega Jellyfish"); + FsmState fsmState = fsm.FsmStates.First(t => t.Name == "Idle"); + WaitRandom waitRandom = (WaitRandom)fsmState.Actions.OfType().First(); + waitRandom.timeMax.Value = 1.6f; + yield break; + } } } diff --git a/Source/DebugMod.cs b/Source/DebugMod.cs index ae00844..0ae9ebf 100644 --- a/Source/DebugMod.cs +++ b/Source/DebugMod.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Collections.Generic; using System.Reflection; @@ -10,30 +10,37 @@ namespace DebugMod { - public class DebugMod : Mod + public class DebugMod : Mod, IMod { private static GameManager _gm; private static InputHandler _ih; private static HeroController _hc; + private static NailSlash _ns; private static GameObject _refKnight; private static PlayMakerFSM _refKnightSlash; private static CameraController _refCamera; private static PlayMakerFSM _refDreamNail; + private static Collider2D _refHeroCollider; + private static Collider2D _refHeroBox; internal static GameManager GM => _gm != null ? _gm : (_gm = GameManager.instance); internal static InputHandler IH => _ih != null ? _ih : (_ih = GM.inputHandler); internal static HeroController HC => _hc != null ? _hc : (_hc = GM.hero_ctrl); internal static GameObject RefKnight => _refKnight != null ? _refKnight : (_refKnight = HC.gameObject); + //internal static NailSlash NailSlash => _ns != null ? _ns : (_ns = typeof(HC.normalSlash.GetType()); internal static PlayMakerFSM RefKnightSlash => _refKnightSlash != null ? _refKnightSlash : (_refKnightSlash = RefKnight.transform.Find("Attacks/Slash").GetComponent()); internal static CameraController RefCamera => _refCamera != null ? _refCamera : (_refCamera = GM.cameraCtrl); internal static PlayMakerFSM RefDreamNail => _refDreamNail != null ? _refDreamNail : (_refDreamNail = FSMUtility.LocateFSM(RefKnight, "Dream Nail")); - + internal static Collider2D RefHeroCollider => _refHeroCollider != null ? _refHeroCollider : (_refHeroCollider = RefKnight.GetComponent()); + internal static Collider2D RefHeroBox => _refHeroBox != null ? _refHeroBox : (_refHeroBox = RefKnight.transform.Find("HeroBox").GetComponent()); internal static DebugMod instance; internal static GlobalSettings settings; private static float _loadTime; private static float _unloadTime; private static bool _loadingChar; + public static bool KeyBindLock; + public static float AmountToMove; internal static bool infiniteHP; internal static bool infiniteSoul; @@ -42,101 +49,155 @@ public class DebugMod : Mod internal static Vector3 noclipPos; internal static bool cameraFollow; + internal static SaveStateManager saveStateManager; + internal static Dictionary bindMethods = new Dictionary(); + internal static Dictionary alphaKeyDict = new Dictionary(); + + static int alphaStart; + static int alphaEnd; public override void Initialize() { - instance = this; - - instance.Log("Initializing"); + try + { + instance = this; - float startTime = Time.realtimeSinceStartup; - instance.Log("Building MethodInfo dict..."); + instance.Log("Initializing"); - bindMethods.Clear(); - foreach (MethodInfo method in typeof(BindableFunctions).GetMethods(BindingFlags.Public | BindingFlags.Static)) - { - object[] attributes = method.GetCustomAttributes(typeof(BindableMethod), false); + float startTime = Time.realtimeSinceStartup; + instance.Log("Building MethodInfo dict..."); - if (attributes.Any()) + bindMethods.Clear(); + foreach (MethodInfo method in typeof(BindableFunctions).GetMethods(BindingFlags.Public | BindingFlags.Static)) { - BindableMethod attr = (BindableMethod)attributes[0]; - string name = attr.name; - string cat = attr.category; + object[] attributes = method.GetCustomAttributes(typeof(BindableMethod), false); - bindMethods.Add(name, new Pair(cat, method)); + if (attributes.Any()) + { + BindableMethod attr = (BindableMethod)attributes[0]; + string name = attr.name; + string cat = attr.category; + + bindMethods.Add(name, new Pair(cat, method)); + } } - } - instance.Log("Done! Time taken: " + (Time.realtimeSinceStartup - startTime) + "s. Found " + bindMethods.Count + " methods"); + instance.Log("Done! Time taken: " + (Time.realtimeSinceStartup - startTime) + "s. Found " + bindMethods.Count + " methods"); - settings = GlobalSettings; + settings = GlobalSettings; - if (settings.FirstRun) - { - instance.Log("First run detected, setting default binds"); - - settings.FirstRun = false; - settings.binds.Clear(); - - settings.binds.Add("Toggle All UI", (int)KeyCode.F1); - settings.binds.Add("Toggle Info", (int)KeyCode.F2); - settings.binds.Add("Toggle Menu", (int)KeyCode.F3); - settings.binds.Add("Toggle Console", (int)KeyCode.F4); - settings.binds.Add("Force Pause", (int)KeyCode.F5); - settings.binds.Add("Hazard Respawn", (int)KeyCode.F6); - settings.binds.Add("Set Respawn", (int)KeyCode.F7); - settings.binds.Add("Force Camera Follow", (int)KeyCode.F8); - settings.binds.Add("Toggle Enemy Panel", (int)KeyCode.F9); - settings.binds.Add("Self Damage", (int)KeyCode.F10); - settings.binds.Add("Toggle Binds", (int)KeyCode.BackQuote); - settings.binds.Add("Nail Damage +4", (int)KeyCode.Equals); - settings.binds.Add("Nail Damage -4", (int)KeyCode.Minus); - settings.binds.Add("Increase Timescale", (int)KeyCode.KeypadPlus); - settings.binds.Add("Decrease Timescale", (int)KeyCode.KeypadMinus); - settings.binds.Add("Toggle Hero Light", (int)KeyCode.Home); - settings.binds.Add("Toggle Vignette", (int)KeyCode.Insert); - settings.binds.Add("Zoom In", (int)KeyCode.PageUp); - settings.binds.Add("Zoom Out", (int)KeyCode.PageDown); - settings.binds.Add("Reset Camera Zoom", (int)KeyCode.End); - settings.binds.Add("Toggle HUD", (int)KeyCode.Delete); - settings.binds.Add("Hide Hero", (int)KeyCode.Backspace); - } + if (settings.FirstRun) + { + instance.Log("First run detected, setting default binds"); + + settings.FirstRun = false; + settings.MinInfoPanelVisible = false; + settings.SaveStatePanelVisible = false; + settings.NumPadForSaveStates = false; + settings.MaxSaveStates = 6; + settings.SaveStatePages = 10; + settings.binds.Clear(); + + settings.binds.Add("Toggle All UI", (int)KeyCode.F1); + settings.binds.Add("Toggle Info", (int)KeyCode.F2); + settings.binds.Add("Toggle Top Menu", (int)KeyCode.F3); + settings.binds.Add("Toggle Console", (int)KeyCode.F4); + settings.binds.Add("Toggle Binds", (int)KeyCode.F5); + settings.binds.Add("Alt. Info Switch", (int)KeyCode.F6); + //settings.binds.Add("Hazard Respawn", (int)KeyCode.F6); + //settings.binds.Add("Set Respawn", (int)KeyCode.F7); + settings.binds.Add("Force Camera Follow", (int)KeyCode.F8); + settings.binds.Add("Toggle Enemy Panel", (int)KeyCode.F9); + //settings.binds.Add("Self Damage", (int)KeyCode.F10); + //settings.binds.Add("Nail Damage +4", (int)KeyCode.Equals); + //settings.binds.Add("Nail Damage -4", (int)KeyCode.Minus); + settings.binds.Add("Increase Timescale", (int)KeyCode.KeypadPlus); + settings.binds.Add("Decrease Timescale", (int)KeyCode.KeypadMinus); + settings.binds.Add("Toggle Hero Light", (int)KeyCode.Home); + settings.binds.Add("Toggle Vignette", (int)KeyCode.Insert); + settings.binds.Add("Zoom In", (int)KeyCode.PageUp); + settings.binds.Add("Zoom Out", (int)KeyCode.PageDown); + settings.binds.Add("Reset Camera Zoom", (int)KeyCode.End); + settings.binds.Add("Toggle HUD", (int)KeyCode.Delete); + settings.binds.Add("Hide Hero", (int)KeyCode.Backspace); + } - UnityEngine.SceneManagement.SceneManager.activeSceneChanged += LevelActivated; - GameObject UIObj = new GameObject(); - UIObj.AddComponent(); - GameObject.DontDestroyOnLoad(UIObj); + if (settings.NumPadForSaveStates) + { + alphaStart = (int)KeyCode.Keypad0; + alphaEnd = (int)KeyCode.Keypad9; + } + else + { + alphaStart = (int)KeyCode.Alpha0; + alphaEnd = (int)KeyCode.Alpha9; + } + + int alphaInt = 0; + alphaKeyDict.Clear(); + + for (int i = alphaStart; i <= alphaEnd; i++) + { + KeyCode tmpKeyCode = (KeyCode)i; + alphaKeyDict.Add(tmpKeyCode, alphaInt++); + } + + UnityEngine.SceneManagement.SceneManager.activeSceneChanged += LevelActivated; + GameObject UIObj = new GameObject(); + UIObj.AddComponent(); + GameObject.DontDestroyOnLoad(UIObj); + + saveStateManager = new SaveStateManager(); - ModHooks.Instance.SavegameLoadHook += LoadCharacter; - ModHooks.Instance.NewGameHook += NewCharacter; - ModHooks.Instance.BeforeSceneLoadHook += OnLevelUnload; - ModHooks.Instance.TakeHealthHook += PlayerDamaged; - ModHooks.Instance.ApplicationQuitHook += SaveSettings; + ModHooks.Instance.SavegameLoadHook += LoadCharacter; + ModHooks.Instance.NewGameHook += NewCharacter; + ModHooks.Instance.BeforeSceneLoadHook += OnLevelUnload; + ModHooks.Instance.TakeHealthHook += PlayerDamaged; + ModHooks.Instance.ApplicationQuitHook += SaveSettings; - BossHandler.PopulateBossLists(); - GUIController.Instance.BuildMenus(); + BossHandler.PopulateBossLists(); + GUIController.Instance.BuildMenus(); - Console.AddLine("New session started " + DateTime.Now); + Console.AddLine("New session started " + DateTime.Now); + AmountToMove = settings.AmountToMove; + KeyBindLock = false; + } + catch (Exception e) { + DebugMod.instance.Log(String.Concat( + "\n - Source: ", e.Source, + "\n - Message: ", e.Message, + "\n - InnerException: ", e.InnerException, + "\n - RuntimeType:", e.GetType(), + "\n - StackTrace:", e.StackTrace) + ); + } } public override string GetVersion() { - return "1.3.3"; + string version = "1.5.5"; +#if DEBUG + version = string.Concat(version, "-dev"); +#endif + return version; } public override bool IsCurrent() { + /* try { - GithubVersionHelper helper = new GithubVersionHelper("seanpr96/DebugMod"); + GithubVersionHelper helper = new GithubVersionHelper("seresharp/DebugMod"); Log("Github = " + helper.GetVersion()); return helper.GetVersion() == GetVersion(); } catch (Exception) { return true; - } + u } + */ + return true; } private void SaveSettings() diff --git a/Source/DebugMod.csproj b/Source/DebugMod.csproj index 0693ca6..594d55c 100644 --- a/Source/DebugMod.csproj +++ b/Source/DebugMod.csproj @@ -30,6 +30,8 @@ false default 1591 + false + false true @@ -39,31 +41,56 @@ bin\Windows\AnyCPU\Release\DebugMod.xml PLATFORM_WINDOWS prompt + default 4 false + CS1591 + + + true + + + False - ..\..\Steam\steamapps\common\Hollow Knight\hollow_knight_Data\Managed\Assembly-CSharp.dll + .\References\Assembly-CSharp.dll + False False - ..\..\Steam\steamapps\common\Hollow Knight\hollow_knight_Data\Managed\PlayMaker.dll + .\References\PlayMaker.dll + False + - ..\..\Steam\steamapps\common\Hollow Knight\hollow_knight_Data\Managed\UnityEngine.dll + .\References\UnityEngine.dll + False - ..\..\Steam\steamapps\common\Hollow Knight\hollow_knight_Data\Managed\UnityEngine.UI.dll + .\References\UnityEngine.UI.dll + False + + + ..\packages\ValueTupleBridge.0.1.5\lib\net35\ValueTupleBridge.dll + + + + + + + + + @@ -74,9 +101,6 @@ - - UnityEngine.dll - @@ -103,6 +127,7 @@ + @@ -143,11 +168,31 @@ + + + + + + + + + + + + + + + + <_PostBuildHookTimestamp>@(IntermediateAssembly->'%(FullPath).timestamp') <_PostBuildHookHostPlatform>$(Platform) + + + + diff --git a/Source/EnemiesPanel.cs b/Source/EnemiesPanel.cs index c15ddde..35c9691 100644 --- a/Source/EnemiesPanel.cs +++ b/Source/EnemiesPanel.cs @@ -13,7 +13,7 @@ public static class EnemiesPanel public static bool autoUpdate; private static float lastTime; public static List enemyPool = new List(); - private static GameObject parent; + public static GameObject parent; public static bool hpBars; public static bool hitboxes; public static readonly MethodInfo takeDamage = typeof(HeroController).GetMethod("TakeDamage"); @@ -241,7 +241,7 @@ public static void Update() position.x *= 1920f / Screen.width; position.y *= 1080f / Screen.height; position.y = 1080f - position.y; - + dat.hitbox.SetPosition(position); dat.hitbox.ResizeBG(size); } diff --git a/Source/EnemyData.cs b/Source/EnemyData.cs index 636f9e5..39a908b 100644 --- a/Source/EnemyData.cs +++ b/Source/EnemyData.cs @@ -15,7 +15,7 @@ public struct EnemyData public EnemyData(int hp, PlayMakerFSM fsm, Component spr, GameObject parent = null, GameObject go = null) { HP = hp; - maxHP = hp; + maxHP = hp; // max hp is just curr hp on panel init FSM = fsm; Spr = spr; gameObject = go; diff --git a/Source/GUIController.cs b/Source/GUIController.cs index 5a8183f..57a211c 100644 --- a/Source/GUIController.cs +++ b/Source/GUIController.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Reflection; +using DebugMod.Hitbox; using UnityEngine; using UnityEngine.UI; @@ -16,6 +17,8 @@ public class GUIController : MonoBehaviour public Dictionary images = new Dictionary(); public Vector3 hazardLocation; public string respawnSceneWatch; + public static bool didInput, inputEsc; + private static readonly HitboxViewer hitboxes = new(); private GameObject canvas; private static GUIController _instance; @@ -37,6 +40,8 @@ public void BuildMenus() scaler.referenceResolution = new Vector2(1920f, 1080f); canvas.AddComponent(); + MinimalInfoPanel.BuildMenu(canvas); + SaveStatesPanel.BuildMenu(canvas); InfoPanel.BuildMenu(canvas); TopMenu.BuildMenu(canvas); EnemiesPanel.BuildMenu(canvas); @@ -82,6 +87,7 @@ private void LoadResources() foreach (string res in resourceNames) { + //DebugMod.instance.Log(res + "\n\n"); if (res.StartsWith("DebugMod.Images.")) { try @@ -109,12 +115,14 @@ private void LoadResources() public void Update() { - InfoPanel.Update(); + SaveStatesPanel.Update(); TopMenu.Update(); EnemiesPanel.Update(); Console.Update(); KeyBindPanel.Update(); - + MinimalInfoPanel.Update(); + InfoPanel.Update(); + if (DebugMod.GetSceneName() != "Menu_Title") { //Handle keybinds @@ -126,8 +134,9 @@ public void Update() { foreach (KeyCode kc in Enum.GetValues(typeof(KeyCode))) { - if (Input.GetKeyDown(kc)) + if (Input.GetKeyDown(kc) && kc != KeyCode.Mouse0) { + // Fix UX if (KeyBindPanel.keyWarning != kc) { foreach (KeyValuePair kvp in DebugMod.settings.binds) @@ -144,7 +153,15 @@ public void Update() KeyBindPanel.keyWarning = KeyCode.None; - DebugMod.settings.binds[bind.Key] = (int)kc; + //remove bind + if (kc == KeyCode.Escape) + { + DebugMod.settings.binds.Remove(bind.Key); + } + else if (kc != KeyCode.Escape) + { + DebugMod.settings.binds[bind.Key] = (int) kc; + } KeyBindPanel.UpdateHelpText(); break; } @@ -152,13 +169,29 @@ public void Update() } else if (Input.GetKeyDown((KeyCode)bind.Value)) { - try + //This makes sure atleast you can close the UI when the KeyBindLock is active. + //Im sure theres a better way to do this but idk. + if (bind.Value == DebugMod.settings.binds["Toggle All UI"]) { - ((MethodInfo)DebugMod.bindMethods[bind.Key].Second).Invoke(null, null); + try + { + ((MethodInfo)DebugMod.bindMethods[bind.Key].Second).Invoke(null, null); + } + catch (Exception e) + { + DebugMod.instance.LogError("Error running keybind method " + bind.Key + ":\n" + e.ToString()); + } } - catch (Exception e) + else if (!DebugMod.KeyBindLock) { - DebugMod.instance.LogError("Error running keybind method " + bind.Key + ":\n" + e.ToString()); + try + { + ((MethodInfo)DebugMod.bindMethods[bind.Key].Second).Invoke(null, null); + } + catch (Exception e) + { + DebugMod.instance.LogError("Error running keybind method " + bind.Key + ":\n" + e.ToString()); + } } } } @@ -169,9 +202,39 @@ public void Update() } } + if (SaveStateManager.inSelectSlotState && DebugMod.settings.SaveStatePanelVisible) + { + foreach (KeyValuePair entry in DebugMod.alphaKeyDict) + { + if (Input.GetKeyDown(entry.Key)) + { + if (DebugMod.alphaKeyDict.TryGetValue(entry.Key, out int keyInt)) + { + // keyInt should be between 0-9 + SaveStateManager.currentStateSlot = keyInt; + didInput = true; + break; + } + else + { + didInput = inputEsc = true; + break; + } + } + } + } + if (DebugMod.infiniteSoul && PlayerData.instance.MPCharge < PlayerData.instance.maxMP && PlayerData.instance.health > 0 && !HeroController.instance.cState.dead && GameManager.instance.IsGameplayScene()) { - PlayerData.instance.MPCharge = PlayerData.instance.maxMP; + PlayerData.instance.MPCharge = PlayerData.instance.maxMP - 1; + if (PlayerData.instance.MPReserveMax > 0) + { + PlayerData.instance.MPReserve = PlayerData.instance.MPReserveMax - 1; + HeroController.instance.TakeReserveMP(1); + HeroController.instance.AddMPChargeSpa(2); + } + //HeroController.instance.TakeReserveMP(1); + HeroController.instance.AddMPChargeSpa(1); } if (DebugMod.playerInvincible && PlayerData.instance != null) @@ -211,11 +274,6 @@ public void Update() } } - if (DebugMod.IH.inputActions.pause.WasPressed && DebugMod.GM.IsGamePaused()) - { - UIManager.instance.TogglePauseGame(); - } - if (DebugMod.cameraFollow) { BindableFunctions.cameraGameplayScene.SetValue(DebugMod.RefCamera, false); @@ -250,6 +308,17 @@ public void Update() PlayerData.instance.respawnMarkerName.ToString() })); } + if (HitboxViewer.State != DebugMod.settings.ShowHitBoxes) + { + if (DebugMod.settings.ShowHitBoxes != 0) + { + hitboxes.Load(); + } + else if (HitboxViewer.State != 0 && DebugMod.settings.ShowHitBoxes == 0) + { + hitboxes.Unload(); + } + } } } @@ -276,4 +345,4 @@ public static GUIController Instance } } } -} \ No newline at end of file +} diff --git a/Source/Hitbox/Drawing.cs b/Source/Hitbox/Drawing.cs new file mode 100644 index 0000000..8bbb841 --- /dev/null +++ b/Source/Hitbox/Drawing.cs @@ -0,0 +1,187 @@ +using System.Reflection; +using UnityEngine; + +namespace DebugMod.Hitbox { + // Line drawing routine originally courtesy of Linusmartensson: + // http://forum.unity3d.com/threads/71979-Drawing-lines-in-the-editor + // + // Rewritten to improve performance by Yossarian King / August 2013. + // + // Circle drawing functionality added by Cherno + // http://answers.unity.com/answers/713428/view.html + // + // This version produces virtually identical results to the original (tested by drawing + // one over the other and observing errors of one pixel or less), but for large numbers + // of lines this version is more than four times faster than the original, and comes + // within about 70% of the raw performance of Graphics.DrawTexture. + // + // Peak performance on my laptop is around 200,000 lines per second. The laptop is + // Windows 7 64-bit, Intel Core2 Duo CPU 2.53GHz, 4G RAM, NVIDIA GeForce GT 220M. + // Line width and anti-aliasing had negligible impact on performance. + // + // For a graph of benchmark results in a standalone Windows build, see this image: + // https://app.box.com/s/hyuhi565dtolqdm97e00 + // + // For a Google spreadsheet with full benchmark results, see: + // https://docs.google.com/spreadsheet/ccc?key=0AvJlJlbRO26VdHhzeHNRMVF2UHZHMXFCTVFZN011V1E&usp=sharing + + public static class Drawing { + private static Texture2D aaLineTex = null; + private static Texture2D lineTex = null; + private static Material blitMaterial = null; + private static Material blendMaterial = null; + private static Rect lineRect = new Rect(0, 0, 1, 1); + + // Draw a line in screen space, suitable for use from OnGUI calls from either + // MonoBehaviour or EditorWindow. Note that this should only be called during repaint + // events, when (Event.current.type == EventType.Repaint). + // + // Works by computing a matrix that transforms a unit square -- Rect(0,0,1,1) -- into + // a scaled, rotated, and offset rectangle that corresponds to the line and its width. + // A DrawTexture call used to draw a line texture into the transformed rectangle. + // + // More specifically: + // scale x by line length, y by line width + // rotate around z by the angle of the line + // offset by the position of the upper left corner of the target rectangle + // + // By working out the matrices and applying some trigonometry, the matrix calculation comes + // out pretty simple. See https://app.box.com/s/xi08ow8o8ujymazg100j for a picture of my + // notebook with the calculations. + public static void DrawLine(Vector2 pointA, Vector2 pointB, Color color, float width, bool antiAlias) { + // Normally the static initializer does this, but to handle texture reinitialization + // after editor play mode stops we need this check in the Editor. +#if UNITY_EDITOR + if (!lineTex) + { + Initialize(); + } +#endif + + // Note that theta = atan2(dy, dx) is the angle we want to rotate by, but instead + // of calculating the angle we just use the sine (dy/len) and cosine (dx/len). + float dx = pointB.x - pointA.x; + float dy = pointB.y - pointA.y; + float len = Mathf.Sqrt(dx * dx + dy * dy); + + // Early out on tiny lines to avoid divide by zero. + // Plus what's the point of drawing a line 1/1000th of a pixel long?? + if (len < 0.001f) { + return; + } + + // Pick texture and material (and tweak width) based on anti-alias setting. + Texture2D tex; + Material mat; + if (antiAlias) { + // Multiplying by three is fine for anti-aliasing width-1 lines, but make a wide "fringe" + // for thicker lines, which may or may not be desirable. + width = width * 3.0f; + tex = aaLineTex; + mat = blendMaterial; + } else { + tex = lineTex; + mat = blitMaterial; + } + + float wdx = width * dy / len; + float wdy = width * dx / len; + + Matrix4x4 matrix = Matrix4x4.identity; + matrix.m00 = dx; + matrix.m01 = -wdx; + matrix.m03 = pointA.x + 0.5f * wdx; + matrix.m10 = dy; + matrix.m11 = wdy; + matrix.m13 = pointA.y - 0.5f * wdy; + + // Use GL matrix and Graphics.DrawTexture rather than GUI.matrix and GUI.DrawTexture, + // for better performance. (Setting GUI.matrix is slow, and GUI.DrawTexture is just a + // wrapper on Graphics.DrawTexture.) + GL.PushMatrix(); + GL.MultMatrix(matrix); + + Graphics.DrawTexture(lineRect, tex, lineRect, 0, 0, 0, 0, color, mat); + // 启用抗锯齿时颜色似乎是半透明的,所以重复绘制一次使其更清晰 + if (antiAlias) { + Graphics.DrawTexture(lineRect, tex, lineRect, 0, 0, 0, 0, color, mat); + } + + GL.PopMatrix(); + } + + public static void DrawCircle(Vector2 center, int radius, Color color, float width, int segmentsPerQuarter) { + DrawCircle(center, radius, color, width, false, segmentsPerQuarter); + } + + public static void DrawCircle(Vector2 center, int radius, Color color, float width, bool antiAlias, int segmentsPerQuarter) { + float rh = (float) radius * 0.551915024494f; + + Vector2 p1 = new Vector2(center.x, center.y - radius); + Vector2 p1_tan_a = new Vector2(center.x - rh, center.y - radius); + Vector2 p1_tan_b = new Vector2(center.x + rh, center.y - radius); + + Vector2 p2 = new Vector2(center.x + radius, center.y); + Vector2 p2_tan_a = new Vector2(center.x + radius, center.y - rh); + Vector2 p2_tan_b = new Vector2(center.x + radius, center.y + rh); + + Vector2 p3 = new Vector2(center.x, center.y + radius); + Vector2 p3_tan_a = new Vector2(center.x - rh, center.y + radius); + Vector2 p3_tan_b = new Vector2(center.x + rh, center.y + radius); + + Vector2 p4 = new Vector2(center.x - radius, center.y); + Vector2 p4_tan_a = new Vector2(center.x - radius, center.y - rh); + Vector2 p4_tan_b = new Vector2(center.x - radius, center.y + rh); + + DrawBezierLine(p1, p1_tan_b, p2, p2_tan_a, color, width, antiAlias, segmentsPerQuarter); + DrawBezierLine(p2, p2_tan_b, p3, p3_tan_b, color, width, antiAlias, segmentsPerQuarter); + DrawBezierLine(p3, p3_tan_a, p4, p4_tan_b, color, width, antiAlias, segmentsPerQuarter); + DrawBezierLine(p4, p4_tan_a, p1, p1_tan_a, color, width, antiAlias, segmentsPerQuarter); + } + + // Other than method name, DrawBezierLine is unchanged from Linusmartensson's original implementation. + public static void DrawBezierLine(Vector2 start, Vector2 startTangent, Vector2 end, Vector2 endTangent, Color color, float width, + bool antiAlias, int segments) { + Vector2 lastV = CubeBezier(start, startTangent, end, endTangent, 0); + for (int i = 1; i < segments + 1; ++i) { + Vector2 v = CubeBezier(start, startTangent, end, endTangent, i / (float) segments); + DrawLine(lastV, v, color, width, antiAlias); + lastV = v; + } + } + + + private static Vector2 CubeBezier(Vector2 s, Vector2 st, Vector2 e, Vector2 et, float t) { + float rt = 1 - t; + return rt * rt * rt * s + 3 * rt * rt * t * st + 3 * rt * t * t * et + t * t * t * e; + } + + // This static initializer works for runtime, but apparently isn't called when + // Editor play mode stops, so DrawLine will re-initialize if needed. + static Drawing() { + Initialize(); + } + + private static void Initialize() { + if (lineTex == null) { + lineTex = new Texture2D(1, 1, TextureFormat.ARGB32, false); + lineTex.SetPixel(0, 1, Color.white); + lineTex.Apply(); + } + + if (aaLineTex == null) { + // TODO: better anti-aliasing of wide lines with a larger texture? or use Graphics.DrawTexture with border settings + aaLineTex = new Texture2D(1, 3, TextureFormat.ARGB32, false); + aaLineTex.SetPixel(0, 0, new Color(1, 1, 1, 0)); + aaLineTex.SetPixel(0, 1, Color.white); + aaLineTex.SetPixel(0, 2, new Color(1, 1, 1, 0)); + aaLineTex.Apply(); + } + + // GUI.blitMaterial and GUI.blendMaterial are used internally by GUI.DrawTexture, + // depending on the alphaBlend parameter. Use reflection to "borrow" these references. + blitMaterial = (Material) typeof(GUI).GetMethod("get_blitMaterial", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null); + blendMaterial = (Material) typeof(GUI).GetMethod("get_blendMaterial", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null); + } + } +} \ No newline at end of file diff --git a/Source/Hitbox/HitboxRender.cs b/Source/Hitbox/HitboxRender.cs new file mode 100644 index 0000000..bceadad --- /dev/null +++ b/Source/Hitbox/HitboxRender.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using GlobalEnums; +using UnityEngine; + +namespace DebugMod.Hitbox +{ + public class HitboxRender : MonoBehaviour + { + // ReSharper disable once StructCanBeMadeReadOnly + private struct HitboxType: IComparable + { + public static readonly HitboxType Knight = new(Color.yellow, 0); + public static readonly HitboxType Enemy = new(new Color(0.8f, 0, 0), 1); + public static readonly HitboxType Attack = new(Color.cyan, 2); + public static readonly HitboxType Terrain = new(new Color(0, 0.8f, 0), 3); + public static readonly HitboxType Trigger = new(new Color(0.5f, 0.5f, 1f), 4); + public static readonly HitboxType Other = new(new Color(0.9f, 0.6f, 0.4f), 5); + + public readonly Color Color; + public readonly int Depth; + + private HitboxType(Color color, int depth) + { + Color = color; + Depth = depth; + } + + public int CompareTo(HitboxType other) + { + return other.Depth.CompareTo(Depth); + } + } + + private readonly SortedDictionary> colliders = new() + { + {HitboxType.Knight, new HashSet()}, + {HitboxType.Enemy, new HashSet()}, + {HitboxType.Attack, new HashSet()}, + {HitboxType.Terrain, new HashSet()}, + {HitboxType.Trigger, new HashSet()}, + {HitboxType.Other, new HashSet()}, + }; + + private float LineWidth => Math.Max(0.7f, Screen.width / 960f * GameCameras.instance.tk2dCam.ZoomFactor); + + private void Start() + { + foreach (Collider2D col in Resources.FindObjectsOfTypeAll()) + { + TryAddHitboxes(col); + } + } + + public void UpdateHitbox(GameObject go) + { + foreach (Collider2D col in go.GetComponentsInChildren(true)) + { + TryAddHitboxes(col); + } + } + + private Vector2 LocalToScreenPoint(Camera camera, Collider2D collider2D, Vector2 point) + { + Vector2 result = camera.WorldToScreenPoint(collider2D.transform.TransformPoint(point + collider2D.offset)); + return new Vector2((int) Math.Round(result.x), (int) Math.Round(Screen.height - result.y)); + } + + private void TryAddHitboxes(Collider2D collider2D) + { + if (collider2D == null) + { + return; + } + + if (collider2D is BoxCollider2D or PolygonCollider2D or EdgeCollider2D or CircleCollider2D) + { + GameObject go = collider2D.gameObject; + if (collider2D.GetComponent() || collider2D.gameObject.LocateMyFSM("damages_hero")) + { + colliders[HitboxType.Enemy].Add(collider2D); + } else if (go.LocateMyFSM("health_manager_enemy") || go.LocateMyFSM("health_manager")) + { + colliders[HitboxType.Other].Add(collider2D); + } else if (go.layer == (int) PhysLayers.TERRAIN) + { + colliders[HitboxType.Terrain].Add(collider2D); + } else if (go == HeroController.instance?.gameObject && !collider2D.isTrigger) + { + colliders[HitboxType.Knight].Add(collider2D); + } else if (go.LocateMyFSM("damages_enemy") || go.name == "Damager" && go.LocateMyFSM("Damage")) + { + colliders[HitboxType.Attack].Add(collider2D); + } else if (collider2D.isTrigger && (collider2D.GetComponent() || collider2D.GetComponent())) + { + colliders[HitboxType.Trigger].Add(collider2D); + } else if (collider2D.GetComponent()) + { + NonBouncer bounce = collider2D.GetComponent(); + if (bounce == null || !bounce.active) + { + colliders[HitboxType.Trigger].Add(collider2D); + } + } else if (HitboxViewer.State == 2) + { + colliders[HitboxType.Other].Add(collider2D); + } + } + } + + private void OnGUI() + { + if (Event.current?.type != EventType.Repaint || GameManager.instance.isPaused || Camera.main == null) + { + return; + } + + GUI.depth = int.MaxValue; + Camera camera = Camera.main; + float lineWidth = LineWidth; + foreach (var pair in colliders) + { + foreach (Collider2D collider2D in pair.Value) + { + DrawHitbox(camera, collider2D, pair.Key, lineWidth); + } + } + } + + private void DrawHitbox(Camera camera, Collider2D collider2D, HitboxType hitboxType, float lineWidth) + { + if (collider2D == null || !collider2D.isActiveAndEnabled) + { + return; + } + + int origDepth = GUI.depth; + GUI.depth = hitboxType.Depth; + if (collider2D is BoxCollider2D or EdgeCollider2D or PolygonCollider2D) + { + List points = null; + switch (collider2D) + { + case BoxCollider2D boxCollider2D: + { + Vector2 halfSize = boxCollider2D.size / 2f; + Vector2 topLeft = new(-halfSize.x, halfSize.y); + Vector2 topRight = halfSize; + Vector2 bottomRight = new(halfSize.x, -halfSize.y); + Vector2 bottomLeft = -halfSize; + points = new List + { + topLeft, topRight, bottomRight, bottomLeft, topLeft + }; + break; + } + case EdgeCollider2D edgeCollider2D: + points = new List(edgeCollider2D.points); + break; + case PolygonCollider2D polygonCollider2D: + { + points = new List(polygonCollider2D.points); + if (points.Count > 0) + { + points.Add(points[0]); + } + + break; + } + } + + for (int i = 0; i < points.Count - 1; i++) + { + Vector2 pointA = LocalToScreenPoint(camera, collider2D, points[i]); + Vector2 pointB = LocalToScreenPoint(camera, collider2D, points[i + 1]); + Drawing.DrawLine(pointA, pointB, hitboxType.Color, lineWidth, true); + } + } else if (collider2D is CircleCollider2D circleCollider2D) + { + Vector2 offset = circleCollider2D.offset; + Vector2 center = LocalToScreenPoint(camera, collider2D, offset); + Vector2 right = LocalToScreenPoint(camera, collider2D, new Vector2(offset.x + circleCollider2D.radius, offset.y)); + int radius = (int) Math.Round(Vector2.Distance(center, right)); + Drawing.DrawCircle(center, radius, hitboxType.Color, lineWidth, true, Mathf.Clamp(radius / 16, 4, 32)); + } + + GUI.depth = origDepth; + } + } +} \ No newline at end of file diff --git a/Source/Hitbox/HitboxViewer.cs b/Source/Hitbox/HitboxViewer.cs new file mode 100644 index 0000000..9293725 --- /dev/null +++ b/Source/Hitbox/HitboxViewer.cs @@ -0,0 +1,58 @@ +using Modding; +using UnityEngine; +using UnityEngine.SceneManagement; +using Object = UnityEngine.Object; + +namespace DebugMod.Hitbox +{ + public class HitboxViewer + { + public static int State { get; private set; } + private HitboxRender hitboxRender; + + public void Load() + { + State = DebugMod.settings.ShowHitBoxes; + Unload(); + UnityEngine.SceneManagement.SceneManager.activeSceneChanged += CreateHitboxRender; + ModHooks.Instance.ColliderCreateHook += UpdateHitboxRender; + CreateHitboxRender(); + } + + public void Unload() + { + State = DebugMod.settings.ShowHitBoxes; + UnityEngine.SceneManagement.SceneManager.activeSceneChanged -= CreateHitboxRender; + ModHooks.Instance.ColliderCreateHook -= UpdateHitboxRender; + DestroyHitboxRender(); + } + + private void CreateHitboxRender(Scene current, Scene next) => CreateHitboxRender(); + + private void CreateHitboxRender() + { + DestroyHitboxRender(); + if (GameManager.instance.IsGameplayScene()) + { + hitboxRender = new GameObject().AddComponent(); + } + } + + private void DestroyHitboxRender() + { + if (hitboxRender != null) + { + Object.Destroy(hitboxRender); + hitboxRender = null; + } + } + + private void UpdateHitboxRender(GameObject go) + { + if (hitboxRender != null) + { + hitboxRender.UpdateHitbox(go); + } + } + } +} \ No newline at end of file diff --git a/Source/Images/BlankBox.png b/Source/Images/BlankBox.png new file mode 100644 index 0000000..acca5ef Binary files /dev/null and b/Source/Images/BlankBox.png differ diff --git a/Source/Images/BlankVertical.png b/Source/Images/BlankVertical.png new file mode 100644 index 0000000..f8e3b3f Binary files /dev/null and b/Source/Images/BlankVertical.png differ diff --git a/Source/Images/ButtonRun.png b/Source/Images/ButtonRun.png new file mode 100644 index 0000000..8c96879 Binary files /dev/null and b/Source/Images/ButtonRun.png differ diff --git a/Source/Images/ScrollBarArrowLeft.png b/Source/Images/ScrollBarArrowLeft.png new file mode 100644 index 0000000..a151f77 Binary files /dev/null and b/Source/Images/ScrollBarArrowLeft.png differ diff --git a/Source/Images/ScrollBarArrowRight.png b/Source/Images/ScrollBarArrowRight.png new file mode 100644 index 0000000..e6e8814 Binary files /dev/null and b/Source/Images/ScrollBarArrowRight.png differ diff --git a/Source/InfoPanel.cs b/Source/InfoPanel.cs index b899f09..4a48747 100644 --- a/Source/InfoPanel.cs +++ b/Source/InfoPanel.cs @@ -1,4 +1,5 @@ -using UnityEngine; +using System; +using UnityEngine; using InControl; namespace DebugMod @@ -123,8 +124,8 @@ public static void BuildMenu(GameObject canvas) panel.AddText("Right1 Label", "Session Time\nLoad\nHero Pos\nMove Raw", new Vector2(1285, 747), Vector2.zero, GUIController.Instance.arial); panel.AddText("Right1", "", new Vector2(1385, 747), Vector2.zero, GUIController.Instance.trajanNormal); - panel.AddText("Right2 Label", "Move Vector\nKey Pressed\nMove Pressed\nInput X", new Vector2(1500, 747), Vector2.zero, GUIController.Instance.arial); - panel.AddText("Right2", "", new Vector2(1600, 747), Vector2.zero, GUIController.Instance.trajanNormal); + panel.AddText("Right2 Label", "Move Vector\nKey Pressed\nMove Pressed\nInput X", new Vector2(1550, 747), Vector2.zero, GUIController.Instance.arial); + panel.AddText("Right2", "", new Vector2(1650, 747), Vector2.zero, GUIController.Instance.trajanNormal); panel.FixRenderOrder(); } @@ -220,11 +221,19 @@ public static void Update() int time1 = Mathf.FloorToInt(Time.realtimeSinceStartup / 60f); int time2 = Mathf.FloorToInt(Time.realtimeSinceStartup - (float)(time1 * 60)); - panel.GetText("Right1").UpdateText(string.Format("{0:00}:{1:00}", time1, time2) + "\n" + DebugMod.GetLoadTime() + "s\n" + (Vector2)DebugMod.RefKnight.transform.position + "\n" + string.Format("L: {0} R: {1}", DebugMod.IH.inputActions.left.RawValue, DebugMod.IH.inputActions.right.RawValue)); + panel.GetText("Right1").UpdateText(string.Format("{0:00}:{1:00}", time1, time2) + "\n" + DebugMod.GetLoadTime() + "s\n" + GetHeroPos() + "\n" + string.Format("L: {0} R: {1}", DebugMod.IH.inputActions.left.RawValue, DebugMod.IH.inputActions.right.RawValue)); panel.GetText("Right2").UpdateText(DebugMod.IH.inputActions.moveVector.Vector.x + ", " + DebugMod.IH.inputActions.moveVector.Vector.y + "\n" + GetStringForBool(InputManager.AnyKeyIsPressed) + "\n" + GetStringForBool(DebugMod.IH.inputActions.left.IsPressed || DebugMod.IH.inputActions.right.IsPressed) + "\n" + DebugMod.IH.inputX); } } + private static string GetHeroPos() + { + float HeroX = (float) DebugMod.RefKnight.transform.position.x; + float HeroY = (float) DebugMod.RefKnight.transform.position.y; + + return $"({HeroX}, {HeroY})"; + } + private static string GetStringForBool(bool b) { return b ? "✓" : "X"; diff --git a/Source/KeyBindPanel.cs b/Source/KeyBindPanel.cs index 87504c1..074e114 100644 --- a/Source/KeyBindPanel.cs +++ b/Source/KeyBindPanel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using UnityEngine; namespace DebugMod @@ -12,21 +13,49 @@ public static class KeyBindPanel private static Dictionary> bindPages = new Dictionary>(); private static List pageKeys; - + public static KeyCode keyWarning = KeyCode.None; + // TODO: Refactor to allow rotating images public static void BuildMenu(GameObject canvas) { - panel = new CanvasPanel(canvas, GUIController.Instance.images["HelpBG"], new Vector2(1123, 456), Vector2.zero, new Rect(0, 0, GUIController.Instance.images["HelpBG"].width, GUIController.Instance.images["HelpBG"].height)); - panel.AddText("Label", "Binds", new Vector2(130f, -25f), Vector2.zero, GUIController.Instance.trajanBold, 30); - - panel.AddText("Category", "", new Vector2(25f, 25f), Vector2.zero, GUIController.Instance.trajanNormal, 20); - panel.AddText("Help", "", new Vector2(25f, 50f), Vector2.zero, GUIController.Instance.arial, 15); - panel.AddButton("Next", GUIController.Instance.images["ButtonRect"], new Vector2(125, 250), Vector2.zero, NextClicked, new Rect(0, 0, GUIController.Instance.images["ButtonRect"].width, GUIController.Instance.images["ButtonRect"].height), GUIController.Instance.trajanBold, "# / #"); + panel = new CanvasPanel(canvas, GUIController.Instance.images["HelpBG"], new Vector2(1123, 456), Vector2.zero, new Rect(0, 0, GUIController.Instance.images["HelpBG"].width, GUIController.Instance.images["HelpBG"].height)); + panel.AddText("Label", "Binds", new Vector2(130f, -25f), Vector2.zero, GUIController.Instance.trajanBold, 30); + + panel.AddText("Category", "", new Vector2(25f, 25f), Vector2.zero, GUIController.Instance.trajanNormal, 20); + panel.AddText("Help", "", new Vector2(25f, 50f), Vector2.zero, GUIController.Instance.arial, 15); + panel.AddButton("Page", GUIController.Instance.images["ButtonRect"], new Vector2(125, 250), Vector2.zero, NextClicked, new Rect(0, 0, GUIController.Instance.images["ButtonRect"].width, GUIController.Instance.images["ButtonRect"].height), GUIController.Instance.trajanBold, "# / #"); + + + panel.AddButton( + "NextPage", + GUIController.Instance.images["ScrollBarArrowRight"], + new Vector2(223, 254), + Vector2.zero, + NextClicked, + new Rect( + 0, + 0, + GUIController.Instance.images["ScrollBarArrowRight"].width, + GUIController.Instance.images["ScrollBarArrowRight"].height) + ); + panel.AddButton( + "PrevPage", + GUIController.Instance.images["ScrollBarArrowLeft"], + new Vector2(95, 254), + Vector2.zero, + NextClicked, + new Rect( + 0, + 0, + GUIController.Instance.images["ScrollBarArrowLeft"].width, + GUIController.Instance.images["ScrollBarArrowLeft"].height) + ); for (int i = 0; i < 11; i++) { - panel.AddButton(i.ToString(), GUIController.Instance.images["Scrollbar_point"], new Vector2(300f, 45f + 17.5f * i), Vector2.zero, ChangeBind, new Rect(0, 0, GUIController.Instance.images["Scrollbar_point"].width, GUIController.Instance.images["Scrollbar_point"].height)); + panel.AddButton(i.ToString(), GUIController.Instance.images["Scrollbar_point"], new Vector2(290f, 45f + 17.5f * i), Vector2.zero, ChangeBind, new Rect(0, 0, GUIController.Instance.images["Scrollbar_point"].width, GUIController.Instance.images["Scrollbar_point"].height)); + panel.AddButton($"run{i}", GUIController.Instance.images["ButtonRun"], new Vector2(308f, 51f + 17.5f * i), new Vector2(12f, 12f), RunBind, new Rect(0, 0, GUIController.Instance.images["ButtonRun"].width, GUIController.Instance.images["ButtonRun"].height)); } //Build pages based on categories @@ -42,7 +71,7 @@ public static void BuildMenu(GameObject canvas) pageKeys = bindPages.Keys.ToList(); panel.GetText("Category").UpdateText(pageKeys[page]); - panel.GetButton("Next").UpdateText((page + 1) + " / " + pageKeys.Count); + panel.GetButton("Page").UpdateText((page + 1) + " / " + pageKeys.Count); UpdateHelpText(); } @@ -85,11 +114,19 @@ public static void UpdateHelpText() private static void NextClicked(string buttonName) { - page++; - if (page >= pageKeys.Count) page = 0; + if (buttonName.StartsWith("Prev")) + { + page--; + if (page < 0) page = pageKeys.Count - 1; + } + else + { + page++; + if (page >= pageKeys.Count) page = 0; + } panel.GetText("Category").UpdateText(pageKeys[page]); - panel.GetButton("Next").UpdateText((page + 1) + " / " + pageKeys.Count); + panel.GetButton("Page").UpdateText((page + 1) + " / " + pageKeys.Count); UpdateHelpText(); } @@ -117,6 +154,13 @@ private static void ChangeBind(string buttonName) UpdateHelpText(); } + private static void RunBind(string buttonName) { + int bindIndex = Convert.ToInt32(buttonName.Substring(3)); // strip leading "run" + string bindName = bindPages[pageKeys[page]][bindIndex]; + MethodInfo method = (MethodInfo)DebugMod.bindMethods[bindName].Second; + method.Invoke(null, null); + } + public static void Update() { if (panel == null) @@ -148,6 +192,7 @@ public static void Update() for (int i = 0; i < 11; i++) { panel.GetButton(i.ToString()).SetActive(bindPages[pageKeys[page]].Count > i); + panel.GetButton($"run{i}").SetActive(bindPages[pageKeys[page]].Count > i); } } } diff --git a/Source/MinimalInfoPanel.cs b/Source/MinimalInfoPanel.cs new file mode 100644 index 0000000..3c586f3 --- /dev/null +++ b/Source/MinimalInfoPanel.cs @@ -0,0 +1,149 @@ +using UnityEngine; +using InControl; +using System.Collections.Generic; + +namespace DebugMod +{ + public static class MinimalInfoPanel + { + public static bool minInfo = false; + private static CanvasPanel altPanel; + //private static CanvasPanel panelCurrentSaveState; + + public static void BuildMenu(GameObject canvas) + { + altPanel = new CanvasPanel( + canvas, + GUIController.Instance.images["BlankBox"], + new Vector2(130f, 230f), + Vector2.zero, + new Rect( + 0f, + 0f, + GUIController.Instance.images["BlankBox"].width, + GUIController.Instance.images["BlankBox"].height + ) + ); + + //Labels + altPanel.AddText("Alt vel Label", "Vel", new Vector2(10f, 10f), Vector2.zero, GUIController.Instance.arial, 15); + altPanel.AddText("Alt pos Label", "Pos", new Vector2(110f, 10f), Vector2.zero, GUIController.Instance.arial, 15); + + altPanel.AddText("Alt MP Label", "MP", new Vector2(10f, 30f), Vector2.zero, GUIController.Instance.arial, 15); + altPanel.AddText("Alt canSuperdash Label", "CanCdash", new Vector2(100f, 30f), Vector2.zero, GUIController.Instance.arial, 15); + + altPanel.AddText("Alt Nail Damage Label", "NailDmg", new Vector2(10f, 50f), Vector2.zero, GUIController.Instance.arial, 15); + + altPanel.AddText("Alt Completion Label", "Completion", new Vector2(10f, 70f), Vector2.zero, GUIController.Instance.arial, 15); + altPanel.AddText("Alt Grubs Label", "Grubs", new Vector2(140f, 70f), Vector2.zero, GUIController.Instance.arial, 15); + + altPanel.AddText("Alt Scene Name Label", "Scene Name", new Vector2(10f, 90f), Vector2.zero, GUIController.Instance.arial, 15); + altPanel.AddText("Alt Current Save State Lable", "Current SaveState", new Vector2(10f, 110f), Vector2.zero, GUIController.Instance.arial, 15); + //altPanel.AddText("Alt SaveState AutoSlot", "Autoslot", new Vector2(10f, 130f), Vector2.zero, GUIController.Instance.arial, 15); + altPanel.AddText("Alt SaveState CurrentSlot", "Current slot", new Vector2(110f, 130f), Vector2.zero, GUIController.Instance.arial, 15); + altPanel.AddText("Alt WillHardfall", "Hardfall", new Vector2(10f, 130f), Vector2.zero, GUIController.Instance.arial, 15); + + //Values + altPanel.AddText("Vel", "", new Vector2(40f, 14f), Vector2.zero, GUIController.Instance.trajanNormal); + altPanel.AddText("Pos", "", new Vector2(140f, 14f), Vector2.zero, GUIController.Instance.trajanNormal); + + altPanel.AddText("MP", "", new Vector2(40f, 34f), Vector2.zero, GUIController.Instance.trajanNormal); + altPanel.AddText("CanCdash", "", new Vector2(190f, 34f), Vector2.zero, GUIController.Instance.trajanNormal); + + altPanel.AddText("NailDmg", "", new Vector2(100f, 54f), Vector2.zero, GUIController.Instance.trajanNormal); + + altPanel.AddText("Completion", "", new Vector2(95f, 74f), Vector2.zero, GUIController.Instance.trajanNormal); + altPanel.AddText("Grubs", "", new Vector2(195f, 74f), Vector2.zero, GUIController.Instance.trajanNormal); + + altPanel.AddText("Scene Name", "", new Vector2(140f, 94f), Vector2.zero, GUIController.Instance.trajanNormal); + altPanel.AddText("Current SaveState", "", new Vector2(140f, 114f), Vector2.zero, GUIController.Instance.trajanNormal); + //altPanel.AddText("Autoslot", "", new Vector2(80f, 134f), Vector2.zero, GUIController.Instance.trajanNormal); + altPanel.AddText("Current slot", "", new Vector2(200f, 134f), Vector2.zero, GUIController.Instance.trajanNormal); + + altPanel.AddText("Hardfall", "", new Vector2(80f, 134f), Vector2.zero, GUIController.Instance.trajanNormal); + + altPanel.FixRenderOrder(); + } + + public static void Update() + { + + if (altPanel == null) + { + return; + } + + if (DebugMod.GM.IsNonGameplayScene()) + { + if (altPanel.active) + { + altPanel.SetActive(false, true); + } + return; + } + + // Not intended min/full info panel logic, but should show the two panels one at a time + if (DebugMod.settings.MinInfoPanelVisible && !altPanel.active) + { + altPanel.SetActive(true, false); + } + else if (!DebugMod.settings.MinInfoPanelVisible && altPanel.active) + { + altPanel.SetActive(false, true); + } + + if (altPanel.active) + { + PlayerData.instance.CountGameCompletion(); + + altPanel.GetText("Vel").UpdateText(HeroController.instance.current_velocity.ToString()); + altPanel.GetText("Pos").UpdateText(GetHeroPos()); + + altPanel.GetText("MP").UpdateText((PlayerData.instance.MPCharge + PlayerData.instance.MPReserve).ToString()); + altPanel.GetText("NailDmg").UpdateText(DebugMod.RefKnightSlash.FsmVariables.GetFsmInt("damageDealt").Value + " (Flat " + PlayerData.instance.nailDamage + ", x" + DebugMod.RefKnightSlash.FsmVariables.GetFsmFloat("Multiplier").Value + ")"); + altPanel.GetText("CanCdash").UpdateText(GetStringForBool(HeroController.instance.CanSuperDash())); + + altPanel.GetText("Completion").UpdateText(PlayerData.instance.completionPercentage.ToString() + "%"); + altPanel.GetText("Grubs").UpdateText(PlayerData.instance.grubsCollected + " / 46"); + + altPanel.GetText("Scene Name").UpdateText(DebugMod.GetSceneName()); + + if (SaveStateManager.quickState.IsSet()) + { + //string[] temp = ; + //altPanel.GetText("Current SaveState").UpdateText(string.Format("{0}\n{1}", temp[2], temp[1])); + altPanel.GetText("Current SaveState").UpdateText(SaveStateManager.quickState.GetSaveStateID()); + } + else + { + altPanel.GetText("Current SaveState").UpdateText("No savestate"); + } + + string slotSet = SaveStateManager.GetCurrentSlot().ToString(); + if (slotSet == "-1") + { + slotSet = "unset"; + } + + //altPanel.GetText("Autoslot").UpdateText(string.Format("{0}", + // GetStringForBool(SaveStateManager.GetAutoSlot()))); + altPanel.GetText("Current slot").UpdateText(string.Format("{0}", slotSet)); + + altPanel.GetText("Hardfall").UpdateText(GetStringForBool(HeroController.instance.cState.willHardLand)); + } + } + + private static string GetHeroPos() + { + float HeroX = (float)DebugMod.RefKnight.transform.position.x; + float HeroY = (float)DebugMod.RefKnight.transform.position.y; + + return $"({HeroX}, {HeroY})"; + } + + private static string GetStringForBool(bool b) + { + return b ? "✓" : "X"; + } + } +} diff --git a/Source/PlayerDeathWatcher.cs b/Source/PlayerDeathWatcher.cs index 3c0df2d..cc082b2 100644 --- a/Source/PlayerDeathWatcher.cs +++ b/Source/PlayerDeathWatcher.cs @@ -2,7 +2,7 @@ { public static class PlayerDeathWatcher { - private static bool playerDead; + public static bool playerDead; public static void Reset() { diff --git a/Source/RoomSpecific.cs b/Source/RoomSpecific.cs new file mode 100644 index 0000000..0391166 --- /dev/null +++ b/Source/RoomSpecific.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using HutongGames.PlayMaker; +using UnityEngine; + +namespace DebugMod +{ + public static class RoomSpecific + { + //This class is intended to recreate some scenarios, with more accuracy than that of the savestate class. + //This should be eventually included to compatible with savestates, stored in the same location for easier access. + #region Functions + private static void EnterSpiderTownTrap(int index) //Deepnest_Spider_Town + { + string goName = "RestBench Spider"; + string websFsmName = "Fade"; + string benchFsmName = "Bench Control Spider"; + PlayMakerFSM websFSM = FindFsmGlobally(goName, websFsmName); + PlayMakerFSM benchFSM = FindFsmGlobally(goName, benchFsmName); + benchFSM.SetState("Start Rest"); + benchFSM.SendEvent("WAIT"); + benchFSM.SendEvent("FINISHED"); + benchFSM.SendEvent("STRUGGLE"); + websFSM.SendEvent("FIRST STRUGGLE"); + websFSM.SendEvent("FINISHED"); + websFSM.SendEvent("FINISHED"); + websFSM.SendEvent("FINISHED"); + websFSM.SendEvent("LAND"); + websFSM.SendEvent("FINISHED"); + if (index == 2) + { + websFSM.SendEvent("FINISHED"); + } + } + private static void BreakTHKChains(int index) + { + string fsmName = "Control"; + string goName1 = "hollow_knight_chain_base"; + string goName2 = "hollow_knight_chain_base 2"; + string goName3 = "hollow_knight_chain_base 3"; + string goName4 = "hollow_knight_chain_base 4"; + PlayMakerFSM fsm1 = FindFsmGlobally(goName1, fsmName); + PlayMakerFSM fsm2 = FindFsmGlobally(goName2, fsmName); + PlayMakerFSM fsm3 = FindFsmGlobally(goName3, fsmName); + PlayMakerFSM fsm4 = FindFsmGlobally(goName4, fsmName); + fsm1.SetState("Break"); + fsm2.SetState("Break"); + fsm3.SetState("Break"); + fsm4.SetState("Break"); + } //Room_Final_Boss + #endregion + public static void DoRoomSpecific(string scene, int index)//index only used if multiple functionallities in one room, safe to ignore for now. + { + switch (scene) + { + case "Deepnest_Spider_Town": + EnterSpiderTownTrap(index); + break; + case "Room_Final_Boss_Core": + BreakTHKChains(index); + break; + default: + Console.AddLine("No Room Specific Function Found In: " + scene); + break; + } + } + private static PlayMakerFSM FindFsmGlobally(string gameObjectName, string fsmName) + { + return GameObject.Find(gameObjectName).LocateMyFSM(fsmName); + } + } +} diff --git a/Source/SavePositionManager.cs b/Source/SavePositionManager.cs new file mode 100644 index 0000000..2578e5f --- /dev/null +++ b/Source/SavePositionManager.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using System.Reflection; +using static DebugMod.EnemiesPanel; +using System; + +namespace DebugMod +{ + public class SavePositionManager + { + private static List FSMs = new List(); + private static List CreatedFSMs = new List(); + //public static List; + //public static List + // too complicated public static PlayMakerFSM KnightFsm = DebugMod.RefKnight.LocateMyFSM("Knight-ProxyFSM") ; + private static Vector3 KnightPos; + private static Vector3 CamPos; + private static Vector2 KnightVel; + private static string PositionScene; + private static bool InitializedPosition = false; + // public static List + public static void SaveState() + { + + //float dash_timer = ReflectionHelper.GetField(HeroController.instance, "dash_timer") + KnightPos = DebugMod.RefKnight.gameObject.transform.position; + KnightVel = HeroController.instance.current_velocity; + CamPos = DebugMod.RefCamera.gameObject.transform.position; + PositionScene = DebugMod.GetSceneName(); + InitializedPosition = true; + FSMs = GetAllEnemies(new List()); + //TODO: check this . used to be FSMs = GetAllEnemies(CreatedFSMs); + FSMs.ForEach(delegate (EnemyData dat) { dat.gameObject.SetActive(false); }); + Console.AddLine("Positional save set in " + DebugMod.GetSceneName()); + } + public static void LoadState() + { + if (PositionScene == DebugMod.GetSceneName() && InitializedPosition) { + try + { + RemoveAllCopies(); + CreatedFSMs = Create(); + // Move knight to saved location, change velocity to saved velocity, Move Camera to saved camera position, + DebugMod.RefKnight.gameObject.transform.position = KnightPos; + HeroController.instance.current_velocity = KnightVel; + DebugMod.RefCamera.gameObject.transform.position = CamPos; + } + catch (Exception) + { + Console.AddLine("No positional save in " + DebugMod.GetSceneName()+", or a bug has occured."); + } + } else { + Console.AddLine("No positional save in " + DebugMod.GetSceneName()); + } + } + + private static List Create() + { + List data = new List(); + for (int i = 0; i < FSMs.Count; i++) + { + EnemyData dattemp = FSMs.FindAll(ed => ed.gameObject != null)[i]; + GameObject gameObject = UnityEngine.Object.Instantiate(dattemp.FSM.gameObject, dattemp.gameObject.transform.position, dattemp.gameObject.transform.rotation) as GameObject; + Component component = gameObject.GetComponent(); + PlayMakerFSM playMakerFSM2 = FSMUtility.LocateFSM(gameObject, dattemp.FSM.FsmName); + int health = playMakerFSM2.FsmVariables.GetFsmInt("HP").Value; + gameObject.SetActive(true); + data.Add(new EnemyData(health, playMakerFSM2, component, parent, gameObject)); + }; + return data; + } + private static void RemoveAllCopies() + { + //get all copies and remove them. + CreatedFSMs.ForEach(delegate (EnemyData dat) + { + if (!FSMs.Any(ed => ed.gameObject == dat.gameObject)) + GameObject.Destroy(dat.gameObject.gameObject.gameObject.gameObject); + }); + } + // this works but idk how ?? + private static List GetAllEnemies(List Exclude) + { + float boxSize = 250f; + List ret = new List(); + if (HeroController.instance != null && !HeroController.instance.cState.transitioning && DebugMod.GM.IsGameplayScene()) + { + int layerMask = 133120; + Collider2D[] array = Physics2D.OverlapBoxAll(DebugMod.RefKnight.transform.position, new Vector2(boxSize, boxSize), 1f, layerMask); + if (array != null) + { + for (int i = 0; i < array.Length; i++) + { + PlayMakerFSM playMakerFSM = FSMUtility.LocateFSM(array[i].gameObject, "health_manager_enemy"); + if (playMakerFSM == null) + { + FSMUtility.LocateFSM(array[i].gameObject, "health_manager"); + } + if (playMakerFSM && array[i].gameObject.activeSelf && !(Exclude.Any(ed => ed.gameObject == array[i].gameObject)) && !Ignore(array[i].gameObject.name)) + { + Component component = array[i].gameObject.GetComponent(); + if (component == null) + { + component = null; //? + } + int Health = playMakerFSM.FsmVariables.GetFsmInt("HP").Value; + ret.Add(new EnemyData(Health, playMakerFSM, component, parent, array[i].gameObject)); + } + } + } + } + return ret; + } + } +} diff --git a/Source/SaveState.cs b/Source/SaveState.cs new file mode 100644 index 0000000..8d9963a --- /dev/null +++ b/Source/SaveState.cs @@ -0,0 +1,328 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using HutongGames.PlayMaker.Actions; +using UnityEngine; + +namespace DebugMod +{ + /// + /// Handles struct SaveStateData and individual SaveState operations + /// + internal class SaveState + { + [Serializable] + public class SaveStateData + { + public string saveStateIdentifier; + public string saveScene; + public int useRoomSpecific = 0; + public int facingRight = -1; // -1 for unknown, so that older savestate files behave the same. + public PlayerData savedPd; + public object lockArea; + public SceneData savedSd; + public Vector3 savePos; + public FieldInfo cameraLockArea; + public string filePath; + internal SaveStateData() { } + + internal SaveStateData(SaveStateData _data) + { + saveStateIdentifier = _data.saveStateIdentifier; + saveScene = _data.saveScene; + useRoomSpecific = _data.useRoomSpecific; + facingRight = _data.facingRight; + cameraLockArea = _data.cameraLockArea; + savedPd = _data.savedPd; + savedSd = _data.savedSd; + savePos = _data.savePos; + lockArea = _data.lockArea; + } + } + + [SerializeField] + public SaveStateData data; + + internal SaveState() + { + data = new SaveStateData(); + } + /* TODO: + handle fury/full-health fury + apply/deapply twister effects when charms changed + adjust nail dmg from one save to another + */ + #region saving + + public void SaveTempState() + { + DebugMod.GM.SaveLevelState(); + data.saveScene = GameManager.instance.GetSceneNameString(); + data.saveStateIdentifier = "(tmp)_" + data.saveScene + "-" + DateTime.Now.ToString("H:mm_d-MMM"); + data.savedPd = JsonUtility.FromJson(JsonUtility.ToJson(PlayerData.instance)); + data.savedSd = JsonUtility.FromJson(JsonUtility.ToJson(SceneData.instance)); + data.savePos = HeroController.instance.gameObject.transform.position; + data.cameraLockArea = (data.cameraLockArea ?? typeof(CameraController).GetField("currentLockArea", BindingFlags.Instance | BindingFlags.NonPublic)); + data.lockArea = data.cameraLockArea.GetValue(GameManager.instance.cameraCtrl); + data.useRoomSpecific = 0; + data.facingRight = (HeroController.instance.cState.facingRight) ? 1:0; + } + + public void NewSaveStateToFile(int paramSlot) + { + SaveTempState(); + SaveStateToFile(paramSlot); + } + + public void SaveStateToFile(int paramSlot) + { + try + { + if (data.saveStateIdentifier.StartsWith("(tmp)_")) + { + data.saveStateIdentifier = data.saveStateIdentifier.Substring(6); + } + else if (String.IsNullOrEmpty(data.saveStateIdentifier)) + { + throw new Exception("No temp save state set"); + } + + File.WriteAllText ( + string.Concat(new object[] { + SaveStateManager.path, + "savestate", + paramSlot, + ".json" + }), + JsonUtility.ToJson( data, + prettyPrint: true + ) + ); + + + /* + DebugMod.instance.Log(string.Concat(new object[] { + "SaveStateToFile (this): \n - ", data.saveStateIdentifier, + "\n - ", data.saveScene, + "\n - ", (JsonUtility.ToJson(data.savedPd)), + "\n - ", (JsonUtility.ToJson(data.savedSd)), + "\n - ", data.savePos.ToString(), + "\n - ", data.cameraLockArea ?? typeof(CameraController).GetField("currentLockArea", BindingFlags.Instance | BindingFlags.NonPublic), + "\n - ", data.lockArea.ToString(), " ========= ", data.cameraLockArea.GetValue(GameManager.instance.cameraCtrl) + })); + DebugMod.instance.Log("SaveStateToFile (data): " + data); + */ + } + catch (Exception ex) + { + DebugMod.instance.LogDebug(ex.Message); + throw ex; + } + } + #endregion + + #region loading + + public void LoadTempState() + { + //Don't load states if not alive/in transition (breaks savestates) + if (!PlayerDeathWatcher.playerDead && !HeroController.instance.cState.transitioning) { + HeroController.instance.StartCoroutine(LoadStateCoro()); + } + else + { + Console.AddLine("Don't load states while dead or in a transition! if you are not, this is a bug."); + } + } + + public void NewLoadStateFromFile() + { + LoadStateFromFile(SaveStateManager.currentStateSlot); + LoadTempState(); + } + + public void LoadStateFromFile(int paramSlot) + { + try + { + data.filePath = string.Concat( + new object[] + { + SaveStateManager.path, + "savestate", + paramSlot, + ".json" + }); + DebugMod.instance.Log("prep filepath: " + data.filePath); + + if (File.Exists(data.filePath)) + { + //DebugMod.instance.Log("checked filepath: " + data.filePath); + SaveStateData tmpData = JsonUtility.FromJson(File.ReadAllText(data.filePath)); + try + { + data.saveStateIdentifier = tmpData.saveStateIdentifier; + data.cameraLockArea = tmpData.cameraLockArea; + data.savedPd = tmpData.savedPd; + data.savedSd = tmpData.savedSd; + data.savePos = tmpData.savePos; + data.saveScene = tmpData.saveScene; + data.lockArea = tmpData.lockArea; + data.useRoomSpecific = tmpData.useRoomSpecific; + data.facingRight = tmpData.facingRight; + DebugMod.instance.LogFine("Load SaveState ready: " + data.saveStateIdentifier); + } + catch (Exception ex) + { + DebugMod.instance.Log(string.Format(ex.Source, ex.Message)); + } + } + } + catch (Exception ex) + { + DebugMod.instance.LogDebug(ex.Message); + throw ex; + } + } + + private IEnumerator LoadStateCoro() + { + /* + string scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name; + + if (data.saveScene == scene) + { + yield return UnityEngine.SceneManagement.SceneManager.UnloadScene(scene); + } + */ + //Console.AddLine("LoadStateCoro line1: " + data.savedPd.hazardRespawnLocation.ToString()); + int oldMPReserveMax = PlayerData.instance.MPReserveMax; + int oldMP = PlayerData.instance.MPCharge; + + data.cameraLockArea = (data.cameraLockArea ?? typeof(CameraController).GetField("currentLockArea", BindingFlags.Instance | BindingFlags.NonPublic)); + string scene = "Room_Mender_House"; + if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == "Room_Mender_House") + { + scene = "Room_Sly_Storeroom"; + } + GameManager.instance.ChangeToScene(scene, "", 0f);// i hate that i have to do this. + while (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name != scene) + { + yield return null; + } + GameManager.instance.sceneData = (SceneData.instance = JsonUtility.FromJson(JsonUtility.ToJson(data.savedSd))); + GameManager.instance.ResetSemiPersistentItems(); + + yield return null; + HeroController.instance.gameObject.transform.position = data.savePos; + PlayerData.instance = (GameManager.instance.playerData = (HeroController.instance.playerData = JsonUtility.FromJson(JsonUtility.ToJson(data.savedPd)))); + GameManager.instance.ChangeToScene(data.saveScene, "", 0.4f); + try + { + data.cameraLockArea.SetValue(GameManager.instance.cameraCtrl, data.lockArea); + GameManager.instance.cameraCtrl.LockToArea(data.lockArea as CameraLockArea); + BindableFunctions.cameraGameplayScene.SetValue(GameManager.instance.cameraCtrl, true); + } + catch (Exception message) + { + Debug.LogError(message); + } + yield return new WaitUntil(() => UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == data.saveScene); + HeroController.instance.playerData = PlayerData.instance; + HeroController.instance.geoCounter.playerData = PlayerData.instance; + HeroController.instance.geoCounter.TakeGeo(0); + + if (PlayerData.instance.MPCharge >= 99 || oldMP >= 99) + { + if (PlayerData.instance.MPReserve > 0) + { + HeroController.instance.TakeReserveMP(1); + HeroController.instance.AddMPChargeSpa(1); + } + HeroController.instance.TakeMP(1); + yield return null; + HeroController.instance.AddMPCharge(1); + } + else + { + if(PlayerData.instance.MPCharge != 0) + { + HeroController.instance.TakeMP(1); + HeroController.instance.AddMPCharge(1); + } + else + { + HeroController.instance.AddMPCharge(1); + HeroController.instance.TakeMP(1); + } + + } + + HeroController.instance.proxyFSM.SendEvent("HeroCtrl-HeroLanded"); + HeroAnimationController component = HeroController.instance.GetComponent(); + typeof(HeroAnimationController).GetField("pd", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(component, PlayerData.instance); + + HeroController.instance.TakeHealth(1); + HeroController.instance.AddHealth(1); + GameCameras.instance.hudCanvas.gameObject.SetActive(true); + HeroController.instance.TakeHealth(1); + HeroController.instance.AddHealth(1); + + GameManager.instance.inputHandler.RefreshPlayerData(); + + if (data.facingRight == 1) + { + HeroController.instance.FaceRight(); + } + else if (data.facingRight == 0) + { + HeroController.instance.FaceLeft(); + } + + if (DebugMod.settings.EnemiesPanelVisible) EnemiesPanel.RefreshEnemyList(); + //UnityEngine.Object.Destroy(GameCameras.instance.gameObject); + //yield return null; + //DebugMod.GM.SetupSceneRefs(); + if(data.useRoomSpecific != 0) + { + RoomSpecific.DoRoomSpecific(data.saveScene,data.useRoomSpecific); + } + yield break; + // need to redraw UI somehow + } + #endregion + + #region helper functionality + + public bool IsSet() + { + bool isSet = !String.IsNullOrEmpty(data.saveStateIdentifier); + return isSet; + } + + public string GetSaveStateID() + { + return data.saveStateIdentifier; + } + + public string[] GetSaveStateInfo() + { + return new string[] + { + data.saveStateIdentifier, + data.saveScene + }; + + } + public SaveStateData DeepCopy() + { + return new SaveStateData(this.data); + } + + #endregion + } +} diff --git a/Source/SaveStateManager.cs b/Source/SaveStateManager.cs new file mode 100644 index 0000000..d438eff --- /dev/null +++ b/Source/SaveStateManager.cs @@ -0,0 +1,411 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; + +namespace DebugMod +{ + public enum SaveStateType + { + Memory, + File, + SkipOne + } + + // TODO: Fix vessel count between savestates + + /// + /// Handles organisation of SaveState-s + /// quickState replicating legacy behaviour of only stored in RAM. + /// Dictionary(int slot : file). Might change to HashMap(?) + /// if: memory requirement too high: array for limiting savestates? hashmap as all states should logically be unique? + /// HUD for viewing necessary info for UX. + /// AutoSlotSelect to iterate over slots, eventually overwrite when circled and no free slots. + /// + internal class SaveStateManager + { + public static int maxSaveStates = DebugMod.settings.MaxSaveStates; + public static int currentStateFolder = 0; + public static SaveState quickState; + public static bool inSelectSlotState = false; // a mutex, in practice? + public static int savePages; + public static int currentStateSlot = -1; + public static string path = Application.persistentDataPath + "/Savestates-1221/0/"; + public static string currentStateOperation = null; + //public static bool hasFury = typeof(Modding.ReflectionHelper).GetField(, "fury"); + + private static string[] stateStrings = + { + "Quickslot (save)", + "Quickslot (load)", + "Save quickslot to file", + "Load quickslot from file", + "Save new state to file", + "Load new state from file" + }; + private static Dictionary saveStateFiles = new Dictionary(); + + //private static bool autoSlot; + private DateTime timeoutHelper; + private double timeoutAmount = 30; + + //public static bool preserveThroughStates = false; + + internal SaveStateManager() + { + try + { + inSelectSlotState = false; + //autoSlot = false; + DebugMod.settings.SaveStatePanelVisible = false; + if (DebugMod.settings.SaveStatePages == -1) DebugMod.settings.SaveStatePages = 10; + savePages = DebugMod.settings.SaveStatePages; + quickState = new SaveState(); + for (int i = 0; i < savePages; i++) + { + if (!Directory.Exists(Application.persistentDataPath + "/Savestates-1221/" + i.ToString())) + { + Directory.CreateDirectory(Application.persistentDataPath + "/Savestates-1221/" + i.ToString()); + } + else + { + RefreshStateMenu(); + } + } + } + catch (Exception) + { + throw; + } + } + + #region saving + public void SaveState(SaveStateType stateType) + { + switch (stateType) + { + case SaveStateType.Memory: + quickState.SaveTempState(); + break; + case SaveStateType.File or SaveStateType.SkipOne: + if (!inSelectSlotState) + { + RefreshStateMenu(); + GameManager.instance.StartCoroutine(SelectSlot(true, stateType)); + } + break; + default: break; + } + } + + #endregion + + #region loading + + public void LoadState(SaveStateType stateType) + { + switch (stateType) + { + case SaveStateType.Memory: + if (quickState.IsSet()) + { + quickState.LoadTempState(); + } + else + { + Console.AddLine("No save state active"); + } + break; + case SaveStateType.File or SaveStateType.SkipOne: + + if (!inSelectSlotState) + { + RefreshStateMenu(); + GameManager.instance.StartCoroutine(SelectSlot(false, stateType)); + } + break; + default: + break; + } + } + + #endregion + + #region helper functionality + private IEnumerator SelectSlot(bool save, SaveStateType stateType) + { + switch (stateType) + { + case SaveStateType.Memory: + currentStateOperation = save ? "Quickslot (save)" : "Quickslot (load)"; + break; + case SaveStateType.File: + currentStateOperation = save ? "Quickslot save to file" : "Load file to quickslot"; + break; + case SaveStateType.SkipOne: + currentStateOperation = save ? "Save new state to file" : "Load new state from file"; + break; + default: + //DebugMod.instance.LogError("SelectSlot ended started"); + throw new ArgumentException("Helper func SelectSlot requires `bool` and `SaveStateType` to proceed the savestate process"); + } + + if (DebugMod.settings.binds.TryGetValue(currentStateOperation, out int keycode)) + { + DebugMod.alphaKeyDict.Add((KeyCode)keycode, keycode); + } + else + { + throw new Exception("Helper func SelectSlot could not find a binding for the `" + currentStateOperation + "` savestate process"); + } + + yield return null; + timeoutHelper = DateTime.Now.AddSeconds(timeoutAmount); + DebugMod.settings.SaveStatePanelVisible = inSelectSlotState = true; + yield return new WaitUntil(DidInput); + + if (GUIController.didInput && !GUIController.inputEsc) + { + if (currentStateSlot >= 0 && currentStateSlot < maxSaveStates) + { + if (save) + { + SaveCoroHelper(stateType); + } + else + { + LoadCoroHelper(stateType); + } + } + } + else + { + if (GUIController.didInput) Console.AddLine("Savestate action cancelled"); + else Console.AddLine("Timeout (" + timeoutAmount.ToString() + ")s was reached"); + } + + DebugMod.alphaKeyDict.Remove((KeyCode)keycode); + currentStateOperation = null; + GUIController.inputEsc = GUIController.didInput = false; + DebugMod.settings.SaveStatePanelVisible = inSelectSlotState = false; + } + + // Todo: cleanup Adds and Removes, because used to C++ :) + private void SaveCoroHelper(SaveStateType stateType) + { + switch (stateType) + { + case SaveStateType.File: + if (quickState == null || !quickState.IsSet()) + { + quickState.SaveTempState(); + } + if (saveStateFiles.ContainsKey(currentStateSlot)) + { + saveStateFiles.Remove(currentStateSlot); + } + saveStateFiles.Add(currentStateSlot, new SaveState()); + saveStateFiles[currentStateSlot].data = quickState.DeepCopy(); + saveStateFiles[currentStateSlot].SaveStateToFile(currentStateSlot); + break; + case SaveStateType.SkipOne: + if (saveStateFiles.ContainsKey(currentStateSlot)) + { + saveStateFiles.Remove(currentStateSlot); + } + saveStateFiles.Add(currentStateSlot, new SaveState()); + saveStateFiles[currentStateSlot].NewSaveStateToFile(currentStateSlot); + break; + default: + break; + } + } + + private void LoadCoroHelper(SaveStateType stateType) + { + switch (stateType) + { + case SaveStateType.File: + if (saveStateFiles.ContainsKey(currentStateSlot)) + { + saveStateFiles.Remove(currentStateSlot); + } + saveStateFiles.Add(currentStateSlot, new SaveState()); + saveStateFiles[currentStateSlot].LoadStateFromFile(currentStateSlot); + quickState.data = saveStateFiles[currentStateSlot].DeepCopy(); + break; + case SaveStateType.SkipOne: + if (saveStateFiles.ContainsKey(currentStateSlot)) + { + saveStateFiles.Remove(currentStateSlot); + } + saveStateFiles.Add(currentStateSlot, new SaveState()); + saveStateFiles[currentStateSlot].NewLoadStateFromFile(); + break; + default: + break; + } + } + + private bool DidInput() + { + if (GUIController.didInput) + { + return true; + } + else if (timeoutHelper < DateTime.Now) + { + return true; + } + return false; + } + + /* + public void ToggleAutoSlot() + { + autoSlot = !autoSlot; + } + + public static bool GetAutoSlot() + { + return autoSlot; + } + */ + + public static int GetCurrentSlot() + { + return currentStateSlot; + } + + public string[] GetCurrentMemoryState() + { + if (quickState.IsSet()) + { + return quickState.GetSaveStateInfo(); + } + return null; + } + + public static bool HasFiles() + { + return (saveStateFiles.Count != 0); + } + + public static Dictionary GetSaveStatesInfo() + { + Dictionary returnData = new Dictionary(); + if (HasFiles()) + { + int total = 0; + foreach (KeyValuePair stateData in saveStateFiles) + { + if (stateData.Value.IsSet() + && stateData.Key < DebugMod.settings.MaxSaveStates + && stateData.Key >= 0 + && total < DebugMod.settings.MaxSaveStates) + { + returnData.Add(stateData.Key, stateData.Value.GetSaveStateInfo()); + ++total; + } + } + } + return returnData; + } + + public void RefreshStateMenu() + { + try + { + //SaveState tempSave = new SaveState(); + saveStateFiles.Clear(); + string shortFileName; + string[] files = Directory.GetFiles(path); + //DebugMod.instance.Log( + // "path var: " + path + + // "\nSavestates: " + files.ToString() + // ); + + foreach (string file in files) + { + shortFileName = Path.GetFileName(file); + //DebugMod.instance.Log("file: " + shortFileName); + var digits = shortFileName.SkipWhile(c => !Char.IsDigit(c)).TakeWhile(Char.IsDigit).ToArray(); + int slot = int.Parse(new string(digits)); + + if (File.Exists(file) && (slot >= 0 || slot < maxSaveStates)) + { + if (saveStateFiles.ContainsKey(slot)) + { + saveStateFiles.Remove(slot); + } + saveStateFiles.Add(slot, new SaveState()); + saveStateFiles[slot].LoadStateFromFile(slot); + + //DebugMod.instance.LogError(saveStateFiles[slot].GetSaveStateID()); + } + } + } + catch (Exception ex) + { + DebugMod.instance.LogError(string.Format(ex.Source, ex.Message)); + //throw ex; + } + } + + /* + private void AutoSlotSelect(SaveStateType stateType) + { + if (autoSlot) + { + int i = 0; + int initSlot = currentStateSlot; + + // saveStateFiles.Keys; + // Refactor using dict.keys()? + while (++currentStateSlot != initSlot && ++i < maxSaveStates && saveStateFiles.ContainsKey(currentStateSlot)) + { + if (currentStateSlot + 1 > maxSaveStates) + { + currentStateSlot = 0; + } + } + + if (currentStateSlot == initSlot) + { + // TODO: Inquire if want to overwrite + currentStateSlot--; + if (currentStateSlot < 0) + { + currentStateSlot = maxSaveStates - 1; + } + saveStateFiles.Remove(currentStateSlot); + } + + saveStateFiles.Add(currentStateSlot, new SaveState()); + if (stateType == SaveStateType.Memory) + { + if (quickState == null || !quickState.IsSet()) + { + quickState.SaveTempState(); + } + // May not need DeepCopy() + saveStateFiles[currentStateSlot].data = quickState.DeepCopy(); + saveStateFiles[currentStateSlot].SaveStateToFile(currentStateSlot); + } + else if (stateType == SaveStateType.SkipOne) + { + saveStateFiles[currentStateSlot].NewSaveStateToFile(currentStateSlot); + } + } + else + { + GameManager.instance.StartCoroutine(SelectSlot(true, stateType)); + } + } + */ + + #endregion + } +} diff --git a/Source/SaveStatesPanel.cs b/Source/SaveStatesPanel.cs new file mode 100644 index 0000000..5ec1a9e --- /dev/null +++ b/Source/SaveStatesPanel.cs @@ -0,0 +1,115 @@ +using UnityEngine; +using InControl; +using System.Collections.Generic; + +namespace DebugMod +{ + public static class SaveStatesPanel + { + private static CanvasPanel statePanel; + + public static void BuildMenu(GameObject canvas) + { + statePanel = new CanvasPanel( + canvas, + GUIController.Instance.images["BlankVertical"], + new Vector2(720f, 40f), + Vector2.zero, + new Rect( + 0f, + 0f, + GUIController.Instance.images["BlankVertical"].width, + GUIController.Instance.images["BlankVertical"].height + ) + ); + statePanel.AddText("CurrentFolder", "Page: "+(SaveStateManager.currentStateFolder + 1).ToString(), new Vector2(8,0), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("Mode", "mode: ", new Vector2(8, 20), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("currentmode", "-", new Vector2(60, 20), Vector2.zero, GUIController.Instance.arial, 15); + + for (int i = 0; i < SaveStateManager.maxSaveStates; i++) { + + //Labels + statePanel.AddText("Slot " + i, i.ToString(), new Vector2(10, i * 20 + 40), Vector2.zero, GUIController.Instance.arial, 15); + + //Values + statePanel.AddText(i.ToString(), "", new Vector2(50, i * 20 + 40), Vector2.zero, GUIController.Instance.arial, 15); + } + /* + statePanel.AddText("Slot1", "1", new Vector2(10f, 40f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("Slot2", "2", new Vector2(10f, 60f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("Slot3", "3", new Vector2(10f, 80f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("Slot4", "4", new Vector2(10f, 100f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("Slot5", "5", new Vector2(10f, 120f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("Slot6", "6", new Vector2(10f, 140f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("Slot7", "7", new Vector2(10f, 160f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("Slot8", "8", new Vector2(10f, 180f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("Slot9", "9", new Vector2(10f, 200f), Vector2.zero, GUIController.Instance.arial, 15); + + //Values + statePanel.AddText("0", "", new Vector2(50f, 20f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("1", "", new Vector2(50f, 40f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("2", "", new Vector2(50f, 60f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("3", "", new Vector2(50f, 80f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("4", "", new Vector2(50f, 100f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("5", "", new Vector2(50f, 120f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("6", "", new Vector2(50f, 140f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("7", "", new Vector2(50f, 160f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("8", "", new Vector2(50f, 180f), Vector2.zero, GUIController.Instance.arial, 15); + statePanel.AddText("9", "", new Vector2(50f, 200f), Vector2.zero, GUIController.Instance.arial, 15); + */ + } + public static void Update() + { + if (statePanel == null) + { + return; + } + + if (DebugMod.GM.IsNonGameplayScene()) + { + if (statePanel.active) + { + statePanel.SetActive(false, true); + } + + return; + } + + if (DebugMod.settings.SaveStatePanelVisible && !statePanel.active) + { + statePanel.SetActive(true, false); + } + else if (!DebugMod.settings.SaveStatePanelVisible && statePanel.active) + { + statePanel.SetActive(false, true); + } + + if (statePanel.active) + { + statePanel.GetText("currentmode").UpdateText(SaveStateManager.currentStateOperation); + statePanel.GetText("CurrentFolder").UpdateText("Page: " + (SaveStateManager.currentStateFolder + 1).ToString()+"/"+SaveStateManager.savePages); + for (int i = 0; i < SaveStateManager.maxSaveStates; i++) + { + statePanel.GetText(i.ToString()).UpdateText("open"); + } + + foreach (KeyValuePair entry in SaveStateManager.GetSaveStatesInfo()) + { + if (DebugMod.settings.ShowRoomIDs) + { + statePanel.GetText(entry.Key.ToString()).UpdateText(string.Format("{0} - {1}", entry.Value[0], entry.Value[1])); + } + else + { + statePanel.GetText(entry.Key.ToString()).UpdateText(string.Format("{0}", entry.Value[0])); + } + } + } + } + + private static string GetStringForBool(bool b) + { + return b ? "✓" : "X"; + } + } +} diff --git a/Source/Settings.cs b/Source/Settings.cs index 11bc84d..bc95632 100644 --- a/Source/Settings.cs +++ b/Source/Settings.cs @@ -2,7 +2,10 @@ namespace DebugMod { - //Empty class required for DebugMod class definition + // + // + // + //class required for DebugMod class definition public class SaveSettings : IModSettings { } public class GlobalSettings : IModSettings @@ -21,7 +24,6 @@ public bool EnemiesPanelVisible get => GetBool(true); set => SetBool(value); } - public bool HelpPanelVisible { get => GetBool(true); @@ -34,16 +36,62 @@ public bool InfoPanelVisible set => SetBool(value); } - public bool TopMenuVisible + public bool MinInfoPanelVisible { get => GetBool(true); set => SetBool(value); } + public bool SaveStatePanelVisible + { + get => GetBool(true); + set => SetBool(value); + } + + public bool TopMenuVisible + { + get => GetBool(true); + set => SetBool(value); + } + public bool ShowRoomIDs + { + get => GetBool(true); + set => SetBool(value); + } public bool FirstRun { get => GetBool(true); set => SetBool(value); } + + public bool NumPadForSaveStates + { + get => GetBool(true); + set => SetBool(value); + } + + public int ShowHitBoxes + { + get => GetInt(); + set => SetInt(value); + } + + public int MaxSaveStates + { + get => GetInt(); + set => SetInt(value); + } + + public int SaveStatePages + { + get => GetInt(-1); + set => SetInt(value); + } + + public float AmountToMove + { + get => GetFloat(0.1f); + set => SetFloat(value); + } } } diff --git a/Source/TopMenu.cs b/Source/TopMenu.cs index 6c8dabd..fc213d5 100644 --- a/Source/TopMenu.cs +++ b/Source/TopMenu.cs @@ -1,4 +1,4 @@ -using System; +using System; using UnityEngine; namespace DebugMod @@ -27,7 +27,7 @@ public static void BuildMenu(GameObject canvas) panel.AddButton("DreamGate", GUIController.Instance.images["ButtonRect"], new Vector2(546f, 68f), Vector2.zero, DreamGatePanelClicked, buttonRect, GUIController.Instance.trajanBold, "DreamGate"); //Dropdown panels - panel.AddPanel("Cheats Panel", GUIController.Instance.images["DropdownBG"], new Vector2(45f, 75f), Vector2.zero, new Rect(0, 0, GUIController.Instance.images["DropdownBG"].width, 210f)); + panel.AddPanel("Cheats Panel", GUIController.Instance.images["DropdownBG"], new Vector2(45f, 75f), Vector2.zero, new Rect(0, 0, GUIController.Instance.images["DropdownBG"].width, 240f)); panel.AddPanel("Charms Panel", GUIController.Instance.images["DropdownBG"], new Vector2(145f, 75f), Vector2.zero, new Rect(0, 0, GUIController.Instance.images["DropdownBG"].width, 240f)); panel.AddPanel("Skills Panel", GUIController.Instance.images["DropdownBG"], new Vector2(245f, 75f), Vector2.zero, new Rect(0, 0, GUIController.Instance.images["DropdownBG"].width, GUIController.Instance.images["DropdownBG"].height)); panel.AddPanel("Items Panel", GUIController.Instance.images["DropdownBG"], new Vector2(345f, 75f), Vector2.zero, new Rect(0, 0, GUIController.Instance.images["DropdownBG"].width, GUIController.Instance.images["DropdownBG"].height)); @@ -41,6 +41,7 @@ public static void BuildMenu(GameObject canvas) panel.GetPanel("Cheats Panel").AddButton("Invincibility", GUIController.Instance.images["ButtonRectEmpty"], new Vector2(5f, 120f), Vector2.zero, InvincibilityClicked, new Rect(0f, 0f, 80f, 20f), GUIController.Instance.trajanNormal, "Invincibility", 10); panel.GetPanel("Cheats Panel").AddButton("Noclip", GUIController.Instance.images["ButtonRectEmpty"], new Vector2(5f, 150f), Vector2.zero, NoclipClicked, new Rect(0f, 0f, 80f, 20f), GUIController.Instance.trajanNormal, "Noclip", 10); panel.GetPanel("Cheats Panel").AddButton("Kill Self", GUIController.Instance.images["ButtonRectEmpty"], new Vector2(5f, 180f), Vector2.zero, KillSelfClicked, new Rect(0f, 0f, 80f, 20f), GUIController.Instance.trajanNormal, "Kill Self", 10); + panel.GetPanel("Cheats Panel").AddButton("Lock KeyBinds", GUIController.Instance.images["ButtonRectEmpty"], new Vector2(5f, 210f), Vector2.zero, KeyBindLockClicked, new Rect(0f, 0f, 80f, 20f), GUIController.Instance.trajanNormal, "Lock Binds", 10); //Charms panel @@ -185,6 +186,7 @@ public static void Update() panel.GetButton("Infinite HP", "Cheats Panel").SetTextColor(DebugMod.infiniteHP ? new Color(244f / 255f, 127f / 255f, 32f / 255f) : Color.white); panel.GetButton("Invincibility", "Cheats Panel").SetTextColor(PlayerData.instance.isInvincible ? new Color(244f / 255f, 127f / 255f, 32f / 255f) : Color.white); panel.GetButton("Noclip", "Cheats Panel").SetTextColor(DebugMod.noclip ? new Color(244f / 255f, 127f / 255f, 32f / 255f) : Color.white); + panel.GetButton("Lock KeyBinds", "Cheats Panel").SetTextColor(DebugMod.KeyBindLock ? new Color(244f / 255f, 127f / 255f, 32f / 255f) : Color.white); } if (panel.GetPanel("Bosses Panel").active) @@ -623,5 +625,11 @@ private static void ScrollDownClicked(string buttonName) DreamGate.scrollPosition++; } } + + private static void KeyBindLockClicked(string buttonName) + { + DebugMod.KeyBindLock = !DebugMod.KeyBindLock; + Console.AddLine((DebugMod.KeyBindLock ? "Removing" : "Adding") + " the ability to use keybinds"); + } } } diff --git a/Source/packages.config b/Source/packages.config new file mode 100644 index 0000000..a1b93e0 --- /dev/null +++ b/Source/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file