diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj index 1bbbf50e..614a2046 100644 --- a/Assembly-CSharp.csproj +++ b/Assembly-CSharp.csproj @@ -239,6 +239,7 @@ + @@ -254,6 +255,7 @@ + diff --git a/Assets.Scripts.Core.AssetManagement/AssetManager.cs b/Assets.Scripts.Core.AssetManagement/AssetManager.cs index ce36426d..95599dc3 100644 --- a/Assets.Scripts.Core.AssetManagement/AssetManager.cs +++ b/Assets.Scripts.Core.AssetManagement/AssetManager.cs @@ -117,8 +117,8 @@ public string PathToAssetWithName(string name, PathCascadeList artset) { int backgroundSetIndex = BurikoMemory.Instance.GetGlobalFlag("GBackgroundSet").IntValue(); - // If force og backgrounds is enabled, always check OGBackgrounds first. - if (backgroundSetIndex == 2) + // If OG backgrounds are enabled, always check OGBackgrounds first. + if (backgroundSetIndex == 1) { string filePath = Path.Combine(Path.Combine(assetPath, "OGBackgrounds"), name); if (File.Exists(filePath)) @@ -129,8 +129,8 @@ public string PathToAssetWithName(string name, PathCascadeList artset) foreach (var artSetPath in artset.paths) { - // If force console backgrounds is enabled, don't check OGBackgrounds - if (backgroundSetIndex == 1 && artSetPath == "OGBackgrounds") + // If console backgrounds are enabled, don't check OGBackgrounds + if (backgroundSetIndex == 0 && artSetPath == "OGBackgrounds") { continue; } diff --git a/Assets.Scripts.Core.Buriko/BurikoMemory.cs b/Assets.Scripts.Core.Buriko/BurikoMemory.cs index 40834fd6..70dc317e 100644 --- a/Assets.Scripts.Core.Buriko/BurikoMemory.cs +++ b/Assets.Scripts.Core.Buriko/BurikoMemory.cs @@ -3,6 +3,7 @@ using Assets.Scripts.Core.Buriko.VarTypes; using MOD.Scripts.Core.Audio; using MOD.Scripts.Core.Scene; +using MOD.Scripts.UI; using Newtonsoft.Json; using Newtonsoft.Json.Bson; using System; @@ -29,6 +30,8 @@ internal class BurikoMemory private List cgflags = new List(); + private MODCustomFlagPreset customFlagPreset = new MODCustomFlagPreset(); + private int scopeLevel; public static BurikoMemory Instance @@ -187,6 +190,11 @@ public void ResetScope() MODSceneController.ClearLayerFilters(); } + public MODCustomFlagPreset GetCustomFlagPresetInstance() + { + return customFlagPreset; + } + public bool SeenCG(string cg) { return cgflags.Contains(cg); @@ -527,6 +535,25 @@ public void LoadGlobals() { readText = jsonSerializer.Deserialize>>(reader); } + try + { + using (BsonReader reader = new BsonReader(stream) { CloseInput = false }) + { + Dictionary deserializedState = jsonSerializer.Deserialize>(reader); + if(deserializedState == null) + { + Debug.LogWarning("Failed to load graphics preset state (serializer returned null)! Probably is old global.dat file missing this data."); + } + else + { + customFlagPreset.Flags = deserializedState; + } + } + } + catch(Exception arg) + { + Debug.LogWarning("Failed to load graphics preset state! Exception: " + arg); + } } } catch (Exception arg) @@ -574,6 +601,11 @@ public void SaveGlobals() jsonSerializer.Serialize(jsonWriter, globalFlags); jsonSerializer.Serialize(jsonWriter, cgflags); jsonSerializer.Serialize(jsonWriter, readText); + if(customFlagPreset.Enabled) + { + customFlagPreset.SavePresetToMemory(); + } + jsonSerializer.Serialize(jsonWriter, customFlagPreset.Flags); inputBytes = memoryStream.ToArray(); } } diff --git a/Assets.Scripts.Core.Buriko/BurikoScriptFile.cs b/Assets.Scripts.Core.Buriko/BurikoScriptFile.cs index 6affea92..0c75ee2b 100644 --- a/Assets.Scripts.Core.Buriko/BurikoScriptFile.cs +++ b/Assets.Scripts.Core.Buriko/BurikoScriptFile.cs @@ -2508,6 +2508,9 @@ public BurikoVariable OperationMODDrawCharacter() bool flag = ReadVariable().BoolValue(); string textureName2 = textureName + "0"; string text = textureName + str; + + x = MODRyukishiRevertSpritePosition(textureName, x); + MODSystem.instance.modSceneController.MODLipSyncInvalidateAndGenerateId(character); if (!MODSystem.instance.modSceneController.MODLipSyncIsEnabled()) { @@ -2556,6 +2559,9 @@ public BurikoVariable OperationMODDrawCharacterWithFiltering() bool flag = ReadVariable().BoolValue(); string textureName2 = textureName + "0"; string text = textureName + str; + + x = MODRyukishiRevertSpritePosition(textureName, x); + MODSystem.instance.modSceneController.MODLipSyncInvalidateAndGenerateId(character); if (!MODSystem.instance.modSceneController.MODLipSyncIsEnabled()) { @@ -2769,7 +2775,7 @@ public BurikoVariable OperationMODGenericCall() case "ShowSetupMenuIfRequired": if(MODAudioSet.Instance.HasAudioSetsDefined() && !MODAudioSet.Instance.GetCurrentAudioSet(out _)) { - GameSystem.Instance.MainUIController.modMenu.SetMode(ModMenuMode.AudioSetup); + GameSystem.Instance.MainUIController.modMenu.SetSubMenu(ModSubMenu.AudioSetup); GameSystem.Instance.MainUIController.modMenu.Show(); } break; @@ -2780,5 +2786,35 @@ public BurikoVariable OperationMODGenericCall() } return BurikoVariable.Null; } + + /// + /// This function reverts the x positions of sprites when using 4:3 backgrounds to match the original game + /// In some places, our mod has 'spread out' the sprite positions to better match 16:9 widescreen by setting + /// their X position to 240/-240 instead of 160/-160 (mostly when there are 3 characters on the screen at once). + /// However, when playing in 4:3 mode, this causes the sprites to be more cut-off than they were in the original game. + /// This function attempts to revert this specific case by changing an X of 240/-240 into 160/-160. + /// + private int MODRyukishiRevertSpritePosition(string path, int x) + { + path = path.ToLower(); + + if(BurikoMemory.Instance.GetGlobalFlag("GBackgroundSet").IntValue() == 1 && // Using OG Backgrounds AND + BurikoMemory.Instance.GetGlobalFlag("GStretchBackgrounds").IntValue() == 0) // Not stretching backgrounds + { + if (path.StartsWith("sprite/") || path.StartsWith("portrait/")) // is from the sprite or portrait folder + { + if (x == 240) + { + return 160; + } + else if (x == -240) + { + return -160; + } + } + } + + return x; + } } } diff --git a/Assets.Scripts.Core.Scene/Layer.cs b/Assets.Scripts.Core.Scene/Layer.cs index 7fb2d00b..cea1b841 100644 --- a/Assets.Scripts.Core.Scene/Layer.cs +++ b/Assets.Scripts.Core.Scene/Layer.cs @@ -334,7 +334,15 @@ private void EnsureCorrectlySizedMesh(int width, int height, LayerAlignment alig bool stretchToFit = false; if (texturePath != null) { - ryukishiClamp = isBustShot && Buriko.BurikoMemory.Instance.GetGlobalFlag("GRyukishiMode").IntValue() == 1 && (texturePath.Contains("sprite/") || texturePath.Contains("sprite\\")); + // We want to clamp sprites to 4:3 if you are using the OG backgrounds, and you are not stretching the background + ryukishiClamp = isBustShot && + Buriko.BurikoMemory.Instance.GetGlobalFlag("GBackgroundSet").IntValue() == 1 && // Using OG Backgrounds AND + Buriko.BurikoMemory.Instance.GetGlobalFlag("GStretchBackgrounds").IntValue() == 0 && // Not stretching backgrounds AND + (texturePath.Contains("sprite/") || + texturePath.Contains("sprite\\") || + texturePath.Contains("portrait/") || + texturePath.Contains("portrait\\")); // Is a sprite or portrait image. I don't think we can rely only on isBustShot, as sometimes non-sprites are drawn with isBustShot + stretchToFit = Buriko.BurikoMemory.Instance.GetGlobalFlag("GStretchBackgrounds").IntValue() == 1 && texturePath.Contains("OGBackgrounds"); } @@ -816,72 +824,5 @@ private void Update() public void MODOnlyRecompile() { } - - public void MODDrawLayer(string textureName, Texture2D tex2d, int x, int y, int z, Vector2? origin, float alpha, bool isBustshot, int type, float wait, bool isBlocking) - { - cachedIsBustShot = isBustshot; - FinishAll(); - if (textureName == string.Empty) - { - HideLayer(); - } - else if (tex2d == null) - { - Logger.LogError("Failed to load texture " + textureName); - } - else - { - startRange = 0f; - targetRange = alpha; - targetAlpha = alpha; - meshRenderer.enabled = true; - shaderType = type; - PrimaryName = textureName; - float num = 1f; - if (z > 0) - { - num = 1f - (float)z / 400f; - } - if (z < 0) - { - num = 1f + (float)z / -400f; - } - EnsureCorrectlySizedMesh( - width: tex2d.width, height: tex2d.height, - alignment: ((x != 0 || y != 0) && !isBustshot) ? LayerAlignment.AlignTopleft : LayerAlignment.AlignCenter, - origin: origin, - isBustShot: isBustshot, - finalXOffset: x, - texturePath: null - ); - if (primary != null) - { - material.shader = shaderCrossfade; - SetSecondaryTexture(primary); - SetPrimaryTexture(tex2d); - startRange = 1f; - targetRange = 0f; - targetAlpha = 1f; - } - else - { - material.shader = shaderDefault; - if (type == 3) - { - material.shader = shaderMultiply; - } - SetPrimaryTexture(tex2d); - } - SetRange(startRange); - base.transform.localPosition = new Vector3((float)x, 0f - (float)y, (float)Priority * -0.1f); - base.transform.localScale = new Vector3(num, num, 1f); - targetPosition = base.transform.localPosition; - targetScale = base.transform.localScale; - if (Mathf.Approximately(wait, 0f)) - { - FinishFade(); - } - } - } } } diff --git a/Assets.Scripts.Core.Scene/SceneController.cs b/Assets.Scripts.Core.Scene/SceneController.cs index c4ff0424..c6d23dcd 100644 --- a/Assets.Scripts.Core.Scene/SceneController.cs +++ b/Assets.Scripts.Core.Scene/SceneController.cs @@ -767,6 +767,14 @@ public void ReloadAllImages() { layer.ReloadTexture(); } + + // Force Unity to unload unused assets. + // Higuarshi will do this if you play the game normally each time ExecuteActions() + // is called (it appears as "Unloading 7 unused Assets to reduce memory usage." in the log). + // However, if you don't advance the text, it won't ever clean up. + // Eventually, you can run out of memory if this function is repeatedly + // called (for example, constantly toggling art styles) + Resources.UnloadUnusedAssets(); } private Scene GetActiveScene() @@ -839,54 +847,6 @@ public void MODOnlyRecompile() { } - public void MODDrawBustshot(int layer, string textureName, Texture2D tex2d, int x, int y, int z, int oldx, int oldy, int oldz, bool move, int priority, int type, float wait, bool isblocking) - { - if (MODSkipImage(textureName)) - { - return; - } - - Layer layer2 = GetLayer(layer); - while (layer2.FadingOut) - { - layer2.HideLayer(); - layer2 = GetLayer(layer); - } - if (!move) - { - oldx = x; - oldy = y; - oldz = z; - } - layer2.MODDrawLayer(textureName, tex2d, oldx, oldy, oldz, null, 1f, /*isBustshot:*/ true, type, wait, isblocking); - layer2.SetPriority(priority); - if (move) - { - layer2.MoveLayer(x, y, z, 1f, 0, wait, isblocking, adjustAlpha: false); - } - iTween.Stop(layer2.gameObject); - if (Mathf.Approximately(wait, 0f)) - { - layer2.FinishFade(); - } - else - { - layer2.FadeInLayer(wait); - if (isblocking) - { - GameSystem.Instance.AddWait(new Wait(wait, WaitTypes.WaitForMove, layer2.FinishFade)); - } - if (layer2.UsingCrossShader() && layer2.gameObject.layer != GetActiveLayerMask()) - { - SetLayerActiveOnBothScenes(layer2); - } - else - { - UpdateLayerMask(layer2, priority); - } - } - } - public IEnumerator MODDrawLipSync(int character, int audiolayer, string audiofile) { ulong coroutineId = MODSystem.instance.modSceneController.MODLipSyncInvalidateAndGenerateId(character); @@ -916,13 +876,13 @@ public IEnumerator MODDrawLipSync(int character, int audiolayer, string audiofil switch (exparray[k]) { case "2": - MODSystem.instance.modSceneController.MODLipSyncProcess(character, "2", exp2, coroutineId); + MODSystem.instance.modSceneController.MODLipSyncProcess(character, exp2, coroutineId); break; case "1": - MODSystem.instance.modSceneController.MODLipSyncProcess(character, "1", exp3, coroutineId); + MODSystem.instance.modSceneController.MODLipSyncProcess(character, exp3, coroutineId); break; case "0": - MODSystem.instance.modSceneController.MODLipSyncProcess(character, "0", exp4, coroutineId); + MODSystem.instance.modSceneController.MODLipSyncProcess(character, exp4, coroutineId); break; } } @@ -931,9 +891,9 @@ public IEnumerator MODDrawLipSync(int character, int audiolayer, string audiofil } else { - MODSystem.instance.modSceneController.MODLipSyncProcess(character, "0", exp4, coroutineId); + MODSystem.instance.modSceneController.MODLipSyncProcess(character, exp4, coroutineId); yield return (object)new WaitForSeconds(0.25f); - MODSystem.instance.modSceneController.MODLipSyncProcess(character, "1", exp3, coroutineId); + MODSystem.instance.modSceneController.MODLipSyncProcess(character, exp3, coroutineId); yield return (object)new WaitForSeconds(0.25f); int k = 0; if (GameSystem.Instance.AudioController.IsVoicePlaying(audiolayer)) @@ -948,14 +908,14 @@ public IEnumerator MODDrawLipSync(int character, int audiolayer, string audiofil { break; } - MODSystem.instance.modSceneController.MODLipSyncProcess(character, "0", exp4, coroutineId); + MODSystem.instance.modSceneController.MODLipSyncProcess(character, exp4, coroutineId); yield return (object)new WaitForSeconds(0.25f); - MODSystem.instance.modSceneController.MODLipSyncProcess(character, "1", exp3, coroutineId); + MODSystem.instance.modSceneController.MODLipSyncProcess(character, exp3, coroutineId); yield return (object)new WaitForSeconds(0.25f); } } } - MODSystem.instance.modSceneController.MODLipSyncProcess(character, "0", exp4, coroutineId); + MODSystem.instance.modSceneController.MODLipSyncProcess(character, exp4, coroutineId); } public void MODLipSyncStart(int character, int audiolayer, string audiofile) diff --git a/Assets.Scripts.UI/MainUIController.cs b/Assets.Scripts.UI/MainUIController.cs index 56d14f90..1cfe0a2a 100644 --- a/Assets.Scripts.UI/MainUIController.cs +++ b/Assets.Scripts.UI/MainUIController.cs @@ -520,7 +520,7 @@ public void OnGUI() { if(BurikoSaveManager.lastSaveError != null) { - MODMenuSupport.EmergencyModMenu("Error loading save file! Please backup your saves, DISABLE STEAM SYNC, then delete the following save file:", BurikoSaveManager.lastSaveError); + MODMenuSupport.EmergencyModMenu("Error loading save file! Please 1. Backup your saves, 2. DISABLE STEAM CLOUD, 3. Delete the following save file:", BurikoSaveManager.lastSaveError); return; } diff --git a/MOD.Scripts.Core.Scene/MODSceneController.cs b/MOD.Scripts.Core.Scene/MODSceneController.cs index b4ed7655..1fdd9828 100644 --- a/MOD.Scripts.Core.Scene/MODSceneController.cs +++ b/MOD.Scripts.Core.Scene/MODSceneController.cs @@ -347,18 +347,12 @@ public bool MODLipSyncIsAnimationCurrent(int character, ulong coroutineId) return coroutineId == MODLipSync_CoroutineId[character]; } - public void MODLipSyncProcess(int charnum, string expressionnum, Texture2D tex2d, ulong coroutineId) + public void MODLipSyncProcess(int charnum, Texture2D tex2d, ulong coroutineId) { if (MODLipSyncIsAnimationCurrent(charnum, coroutineId)) { int layer = MODLipSync_Layer[charnum]; - string textureName = MODLipSync_Texture[charnum] + expressionnum; - int x = MODLipSync_X[charnum]; - int y = MODLipSync_Y[charnum]; - int z = MODLipSync_Z[charnum]; - int priority = MODLipSync_Priority[charnum]; - int type = MODLipSync_Type[charnum]; - GameSystem.Instance.SceneController.MODDrawBustshot(layer, textureName, tex2d, x, y, z, 0, 0, 0, /*move:*/ false, priority, type, 0f, /*isblocking:*/ false); + GameSystem.Instance.SceneController.GetLayer(layer)?.SetPrimaryTexture(tex2d); } } } diff --git a/MOD.Scripts.Core.Scene/MODTextureController.cs b/MOD.Scripts.Core.Scene/MODTextureController.cs index 4fdc5e14..24ed892f 100644 --- a/MOD.Scripts.Core.Scene/MODTextureController.cs +++ b/MOD.Scripts.Core.Scene/MODTextureController.cs @@ -62,7 +62,6 @@ public void SetArtStyle(int artSetIndex, bool showInfoToast = true) { AssetManager.Instance.CurrentArtsetIndex = artSetIndex; BurikoMemory.Instance.SetGlobalFlag("GArtStyle", AssetManager.Instance.CurrentArtsetIndex); - BurikoMemory.Instance.SetGlobalFlag("GBackgroundSet", 0); RestoreTextures(); GameSystem.Instance.SceneController.ReloadAllImages(); if (showInfoToast) { UI.MODToaster.Show($"Art Style: {AssetManager.Instance.CurrentArtset.nameEN}"); } diff --git a/MOD.Scripts.UI/MODActions.cs b/MOD.Scripts.UI/MODActions.cs index 55b45f36..74024134 100644 --- a/MOD.Scripts.UI/MODActions.cs +++ b/MOD.Scripts.UI/MODActions.cs @@ -22,8 +22,8 @@ private enum WindowFilterType public enum ModPreset { - ADV = 0, - NVL = 1, + Console = 0, + MangaGamer = 1, OG = 2, } @@ -47,29 +47,36 @@ private static void TryRedrawTextWindowBackground(WindowFilterType filterType) } /// - /// Cycles and saves ADV->NVL->OG->ADV... + /// Cycles and saves Console->MangaGamer->OG->Custom->Console... /// /// True if set and displayed, false if in a NVL_in_ADV region and value might not be applied immediately public static void ToggleAndSaveADVMode() { - if (BurikoMemory.Instance.GetGlobalFlag("GRyukishiMode").IntValue() == 1) - { - SetAndSaveADV(ModPreset.ADV); - } - else if (BurikoMemory.Instance.GetGlobalFlag("GADVMode").IntValue() == 1) + MODCustomFlagPreset customPreset = BurikoMemory.Instance.GetCustomFlagPresetInstance(); + + // Custom Preset -> Console + if (customPreset.Enabled) { - SetAndSaveADV(ModPreset.NVL); + SetGraphicsPreset(ModPreset.Console); + return; } - else + + switch (GetADVNVLRyukishiModeFromFlags()) { - if(HasOGBackgrounds()) - { - SetAndSaveADV(ModPreset.OG); - } - else - { - SetAndSaveADV(ModPreset.ADV); - } + // Console -> MangaGamer + case 0: + SetGraphicsPreset(ModPreset.MangaGamer); + break; + + // MangaGamer -> OG + case 1: + SetGraphicsPreset(ModPreset.OG); + break; + + // OG -> Custom Preset + case 2: + LoadCustomGraphicsPreset(); + break; } } @@ -82,22 +89,80 @@ public static void ToggleAndSaveADVMode() /// True if set and displayed, false if in a NVL_in_ADV region and value might not be applied immediately /// /// NOTE: if this function is updated, you should update the corresponding "GetModeFromFlags()" function immediately below - public static void SetAndSaveADV(ModPreset setting, bool showInfoToast = true) + public static void SetGraphicsPreset(ModPreset setting, bool showInfoToast = true) { MODMainUIController mODMainUIController = new MODMainUIController(); - if (setting == ModPreset.ADV) + + BurikoMemory.Instance.GetCustomFlagPresetInstance().DisablePresetAndSavePresetToMemory(); + if (setting == ModPreset.Console) { - BurikoMemory.Instance.SetGlobalFlag("GADVMode", 1); - BurikoMemory.Instance.SetGlobalFlag("GLinemodeSp", 0); - BurikoMemory.Instance.SetGlobalFlag("GRyukishiMode", 0); + // Make sure lipsync is enabled when using Console preset + BurikoMemory.Instance.SetGlobalFlag("GLipSync", 1); BurikoMemory.Instance.SetGlobalFlag("GHideCG", 0); + BurikoMemory.Instance.SetGlobalFlag("GBackgroundSet", 0); + BurikoMemory.Instance.SetGlobalFlag("GStretchBackgrounds", 0); + SetTextWindowAppearanceInternal(setting, mODMainUIController, false); + Core.MODSystem.instance.modTextureController.SetArtStyle(0, false); + if (showInfoToast) { UI.MODToaster.Show($"Preset: Console"); } + } + else if (setting == ModPreset.MangaGamer) + { + BurikoMemory.Instance.SetGlobalFlag("GHideCG", 0); + BurikoMemory.Instance.SetGlobalFlag("GBackgroundSet", 0); + BurikoMemory.Instance.SetGlobalFlag("GStretchBackgrounds", 0); + SetTextWindowAppearanceInternal(setting, mODMainUIController, false); + Core.MODSystem.instance.modTextureController.SetArtStyle(1, false); + if (showInfoToast) { UI.MODToaster.Show($"Preset: MangaGamer"); } + } + else if (setting == ModPreset.OG) + { + BurikoMemory.Instance.SetGlobalFlag("GHideCG", 1); + BurikoMemory.Instance.SetGlobalFlag("GBackgroundSet", 1); BurikoMemory.Instance.SetGlobalFlag("GStretchBackgrounds", 0); + SetTextWindowAppearanceInternal(setting, mODMainUIController, false); + Core.MODSystem.instance.modTextureController.SetArtStyle(2, false); + if (showInfoToast) { UI.MODToaster.Show($"Preset: Original/Ryukishi"); } + } + } + + public static void LoadCustomGraphicsPreset(bool showInfoToast = true) + { + BurikoMemory.Instance.GetCustomFlagPresetInstance().EnablePreset(restorePresetFromMemory: true); + SetTextWindowAppearanceInternal((ModPreset) GetADVNVLRyukishiModeFromFlags(), new MODMainUIController(), showInfoToast: false); + Core.MODSystem.instance.modTextureController.SetArtStyle(Assets.Scripts.Core.AssetManagement.AssetManager.Instance.CurrentArtsetIndex, showInfoToast: false); + if (showInfoToast) { UI.MODToaster.Show($"Preset: Custom"); } + } + + public static void EnableCustomGraphicsPreset(bool showInfoToast = true) + { + BurikoMemory.Instance.GetCustomFlagPresetInstance().EnablePreset(restorePresetFromMemory: false); + if (showInfoToast) { UI.MODToaster.Show($"Preset: Custom"); } + } + + public static void SetArtStyle(int artStyle, bool showInfoToast) + { + Core.MODSystem.instance.modTextureController.SetArtStyle(artStyle, showInfoToast); + SwitchToCustomPresetIfPresetModified(showInfoToast); + } + + public static void SetTextWindowAppearance(ModPreset setting, bool showInfoToast = true) + { + SetTextWindowAppearanceInternal(setting, new MODMainUIController(), showInfoToast); + SwitchToCustomPresetIfPresetModified(showInfoToast); + } + + private static void SetTextWindowAppearanceInternal(ModPreset setting, MODMainUIController MODMainUIController, bool showInfoToast = true) + { + if (setting == ModPreset.Console) + { + BurikoMemory.Instance.SetGlobalFlag("GRyukishiMode", 0); + BurikoMemory.Instance.SetGlobalFlag("GADVMode", 1); + BurikoMemory.Instance.SetGlobalFlag("GLinemodeSp", 0); TryRedrawTextWindowBackground(WindowFilterType.ADV); - mODMainUIController.WideGuiPositionStore(); - mODMainUIController.ADVModeSettingStore(); + MODMainUIController.WideGuiPositionStore(); + MODMainUIController.ADVModeSettingStore(); string feedbackString = $"Set ADV Mode"; int toastDuration = 3; - Core.MODSystem.instance.modTextureController.SetArtStyle(0, showInfoToast); bool is_nvl_in_adv_region = BurikoMemory.Instance.GetFlag("NVL_in_ADV").IntValue() == 1; if (is_nvl_in_adv_region) { @@ -106,44 +171,43 @@ public static void SetAndSaveADV(ModPreset setting, bool showInfoToast = true) } if (is_nvl_in_adv_region || showInfoToast) { MODToaster.Show(feedbackString, isEnable: true, toastDuration: toastDuration); } } - else if (setting == ModPreset.NVL) + else if (setting == ModPreset.MangaGamer) { + BurikoMemory.Instance.SetGlobalFlag("GRyukishiMode", 0); BurikoMemory.Instance.SetGlobalFlag("GADVMode", 0); BurikoMemory.Instance.SetGlobalFlag("GLinemodeSp", 2); - BurikoMemory.Instance.SetGlobalFlag("GRyukishiMode", 0); - BurikoMemory.Instance.SetGlobalFlag("GHideCG", 0); - BurikoMemory.Instance.SetGlobalFlag("GStretchBackgrounds", 0); TryRedrawTextWindowBackground(WindowFilterType.Normal); - mODMainUIController.WideGuiPositionStore(); - mODMainUIController.NVLModeSettingStore(); - Core.MODSystem.instance.modTextureController.SetArtStyle(0, showInfoToast); + MODMainUIController.WideGuiPositionStore(); + MODMainUIController.NVLModeSettingStore(); if (showInfoToast) { MODToaster.Show($"Set NVL Mode", isEnable: false); } } else if (setting == ModPreset.OG) { + BurikoMemory.Instance.SetGlobalFlag("GRyukishiMode", 1); BurikoMemory.Instance.SetGlobalFlag("GADVMode", 0); BurikoMemory.Instance.SetGlobalFlag("GLinemodeSp", 2); - BurikoMemory.Instance.SetGlobalFlag("GRyukishiMode", 1); - BurikoMemory.Instance.SetGlobalFlag("GHideCG", 1); - BurikoMemory.Instance.SetGlobalFlag("GStretchBackgrounds", 0); TryRedrawTextWindowBackground(WindowFilterType.OG); - mODMainUIController.RyukishiGuiPositionStore(); - mODMainUIController.RyukishiModeSettingStore(); - Core.MODSystem.instance.modTextureController.SetArtStyle(2, showInfoToast); + MODMainUIController.RyukishiGuiPositionStore(); + MODMainUIController.RyukishiModeSettingStore(); if (showInfoToast) { MODToaster.Show($"Set OG Mode", isEnable: false); } } } + public static int GetADVNVLRyukishiModeFromFlags() => GetADVNVLRyukishiModeFromFlags(out bool _); + // This expressions for 'presetModified' should be updated each time SetAndSaveADV() above is changed, // so that the player knows when the flags have changed from their default values for the current preset public static int GetADVNVLRyukishiModeFromFlags(out bool presetModified) { + // If background override is enabled on any preset, the preset has been modified - presetModified = BurikoMemory.Instance.GetGlobalFlag("GBackgroundSet").IntValue() != 0; + presetModified = false; if (BurikoMemory.Instance.GetGlobalFlag("GRyukishiMode").IntValue() == 1) { + // Original/Ryukishi Preset presetModified = presetModified || + BurikoMemory.Instance.GetGlobalFlag("GBackgroundSet").IntValue() != 1 || BurikoMemory.Instance.GetGlobalFlag("GArtStyle").IntValue() != 2 || BurikoMemory.Instance.GetGlobalFlag("GADVMode").IntValue() != 0 || BurikoMemory.Instance.GetGlobalFlag("GLinemodeSp").IntValue() != 2 || @@ -155,7 +219,10 @@ public static int GetADVNVLRyukishiModeFromFlags(out bool presetModified) } else if (BurikoMemory.Instance.GetGlobalFlag("GADVMode").IntValue() == 1) { + // Console Preset presetModified = presetModified || + BurikoMemory.Instance.GetGlobalFlag("GLipSync").IntValue() != 1 || + BurikoMemory.Instance.GetGlobalFlag("GBackgroundSet").IntValue() != 0 || BurikoMemory.Instance.GetGlobalFlag("GArtStyle").IntValue() != 0 || BurikoMemory.Instance.GetGlobalFlag("GADVMode").IntValue() != 1 || BurikoMemory.Instance.GetGlobalFlag("GLinemodeSp").IntValue() != 0 || @@ -167,8 +234,10 @@ public static int GetADVNVLRyukishiModeFromFlags(out bool presetModified) } else { + // Mangagamer Preset presetModified = presetModified || - BurikoMemory.Instance.GetGlobalFlag("GArtStyle").IntValue() != 0 || + BurikoMemory.Instance.GetGlobalFlag("GBackgroundSet").IntValue() != 0 || + BurikoMemory.Instance.GetGlobalFlag("GArtStyle").IntValue() != 1 || BurikoMemory.Instance.GetGlobalFlag("GADVMode").IntValue() != 0 || BurikoMemory.Instance.GetGlobalFlag("GLinemodeSp").IntValue() != 2 || BurikoMemory.Instance.GetGlobalFlag("GRyukishiMode").IntValue() != 0 || @@ -179,6 +248,15 @@ public static int GetADVNVLRyukishiModeFromFlags(out bool presetModified) } } + public static void SwitchToCustomPresetIfPresetModified(bool showInfoToast) + { + GetADVNVLRyukishiModeFromFlags(out bool presetModified); + if(presetModified && !BurikoMemory.Instance.GetCustomFlagPresetInstance().Enabled) + { + EnableCustomGraphicsPreset(showInfoToast: showInfoToast); + } + } + public static void EnableNVLModeINADVMode() { BurikoMemory.Instance.SetFlag("NVL_in_ADV", 1); @@ -279,10 +357,17 @@ static int _IncrementFlagWithRollover(string flagName, int minValueInclusive, in public static bool ToggleFlagAndSave(string flagName) { int newValue = (BurikoMemory.Instance.GetGlobalFlag(flagName).IntValue() + 1) % 2; - BurikoMemory.Instance.SetGlobalFlag(flagName, newValue); + SetFlagFromUserInput(flagName, newValue, showInfoToast: false); return newValue == 1; } + + public static void SetFlagFromUserInput(string flagName, int newValue, bool showInfoToast) + { + BurikoMemory.Instance.SetGlobalFlag(flagName, newValue); + SwitchToCustomPresetIfPresetModified(showInfoToast); + } + public static string VideoOpeningDescription(int videoOpeningValue) { switch (videoOpeningValue) diff --git a/MOD.Scripts.UI/MODCustomFlagPreset.cs b/MOD.Scripts.UI/MODCustomFlagPreset.cs new file mode 100644 index 00000000..7748a1f4 --- /dev/null +++ b/MOD.Scripts.UI/MODCustomFlagPreset.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MOD.Scripts.UI +{ + /// + /// This class aids in enabling/disabling a group of flags at once + /// + class MODCustomFlagPreset + { + /// + /// This special flag/key will be used to determine if the custom preset is enabled or not + /// It should start with a $ sign, to avoid conflicting with normal flags. + /// + const string ENABLED_KEY = "$customPresetEnabled"; + + /// + /// This list contains flags which will be saved/restored with the custom preset + /// + List FLAGS_TO_SERIALIZE = new List + { + "GLipSync", + "GBackgroundSet", + "GArtStyle", + "GADVMode", + "GLinemodeSp", + "GRyukishiMode", + "GHideCG", + "GStretchBackgrounds" + }; + + // All of this class's state is kept in this Dictionary, for easy serialization. + // To restore the state, re-assign this variable with the new data. + public Dictionary Flags { get; set; } + + public MODCustomFlagPreset() + { + this.Flags = new Dictionary(); + } + + /// + /// This keeps track of whether the custom preset is enabled. + /// + public bool Enabled + { + get + { + if (Flags.TryGetValue(ENABLED_KEY, out int value)) + { + return value == 1; + } + else + { + return false; + } + } + } + + public void EnablePreset(bool restorePresetFromMemory) + { + if(restorePresetFromMemory) + { + RestorePresetFromMemory(); + } + + Flags[ENABLED_KEY] = 1; + } + + /// + /// Copy global flags to preset every time preset mod changes from enabled -> disabled. + // This ensure flags are saved when you switch to another preset. + // This doesn't handle the case when exiting the game, which must be handled manually using SavePresetToMemory(). + /// + public void DisablePresetAndSavePresetToMemory() + { + // Save all preset-related flags to memory + if(Enabled) + { + SavePresetToMemory(); + } + + // Set the special "enabled" key to false in memory + Flags[ENABLED_KEY] = 0; + } + + public void SavePresetToMemory() + { + foreach (string flagName in FLAGS_TO_SERIALIZE) + { + Flags[flagName] = Assets.Scripts.Core.Buriko.BurikoMemory.Instance.GetGlobalFlag(flagName).IntValue(); + } + } + + private void RestorePresetFromMemory() + { + foreach (KeyValuePair pair in Flags) + { + if (pair.Key.StartsWith("$")) + { + continue; + } + + Assets.Scripts.Core.Buriko.BurikoMemory.Instance.SetGlobalFlag(pair.Key, pair.Value); + } + } + } +} diff --git a/MOD.Scripts.UI/MODMenu.cs b/MOD.Scripts.UI/MODMenu.cs index 06b897f6..00f8c008 100644 --- a/MOD.Scripts.UI/MODMenu.cs +++ b/MOD.Scripts.UI/MODMenu.cs @@ -13,7 +13,7 @@ namespace MOD.Scripts.UI { - public enum ModMenuMode + public enum ModSubMenu { Normal, AudioSetup, @@ -30,7 +30,6 @@ public class MODMenu private MODSimpleTimer defaultToolTipTimer; private MODSimpleTimer startupWatchdogTimer; private bool startupFailed; - private bool anyButtonPressed; Vector2 scrollPosition; Vector2 leftDebugColumnScrollPosition; private Rect debugWindowRect; @@ -86,6 +85,9 @@ public void Update() public void LateUpdate() { + // Hide the menu on right-click + // This must be done in LateUpdate() rather than Update(), + // otherwise the right-click event will also open the in-game right-click menu if (Input.GetMouseButtonDown(1)) { this.UserHide(); @@ -147,7 +149,8 @@ private void OnGUIDebugWindow(int windowID) } /// - /// Must be called from an OnGUI() + /// This function MUST be called from an OnGUI(), otherwise Unity won't work + /// properly when the immediate mode GUI functions are called. /// public void OnGUIFragment() { @@ -172,12 +175,14 @@ public void OnGUIFragment() } } + // Assume that the game started up correctly if the flow.txt game script was reached if(BurikoScriptSystem.Instance.FlowWasReached) { this.startupFailed = false; } - // Button to open the Mod Menu on the Config Screen + // This adds an "Open Mod Menu" button to the Config Screen + // (the normal settings screen that comes with the stock game) if (gameSystem.GameState == GameState.ConfigScreen) { if (gameSystem.ConfigManager() != null) @@ -200,9 +205,10 @@ public void OnGUIFragment() GUILayout.EndArea(); } + // If you need to initialize things just once before the menu opens, rather than every frame + // you can do it in the OnBeforeMenuVisible() function below. if (visible && !lastMenuVisibleStatus) { - // Executes just before menu becomes visible currentMenu.OnBeforeMenuVisible(); } lastMenuVisibleStatus = visible; @@ -210,7 +216,7 @@ public void OnGUIFragment() if (visible) { - float totalAreaWidth = styleManager.Group.menuWidth; // areaWidth + toolTipWidth; + float totalAreaWidth = styleManager.Group.menuWidth; float areaWidth = Mathf.Round(totalAreaWidth * 9/16); float toolTipWidth = Mathf.Round(totalAreaWidth * 7/16); @@ -227,19 +233,24 @@ public void OnGUIFragment() float innerMargin = 4f; - // Radio buttons + // This contains the the entire menu, and draws a white border around it GUILayout.BeginArea(new Rect(areaPosX, areaPosY, areaWidth + toolTipWidth, areaHeight), styleManager.modMenuAreaStyle); + + // This displays the left hand side of the menu which contains the option buttons and headings { GUILayout.BeginArea(new Rect(innerMargin, innerMargin, areaWidth-innerMargin, areaHeight-innerMargin), styleManager.modGUIBackgroundTextureTransparent); // Note: GUILayout.Height is adjusted to be slightly smaller, otherwise not all content is visible/scroll bar is slightly cut off. scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Width(areaWidth), GUILayout.Height(areaHeight-10)); + // 'currentMenu' is reassigned to switch to different sub-menus + // please see the SetSubMenu() function currentMenu.OnGUI(); GUILayout.EndScrollView(); GUILayout.EndArea(); } + // This displays the right hand side of the menu which contains descriptions of each option. // Descriptions for each button are shown on hover, like a tooltip { GUILayout.BeginArea(new Rect(toolTipPosX, innerMargin, toolTipWidth- innerMargin, areaHeight-innerMargin), styleManager.modGUIBackgroundTextureTransparent); @@ -292,35 +303,43 @@ public void OnGUIFragment() } GUILayout.EndArea(); - if(MODRadio.anyRadioPressed || anyButtonPressed) + if(MODRadio.anyRadioPressed || MODMenuCommon.anyButtonPressed) { GameSystem.Instance.AudioController.PlaySystemSound(MODSound.GetSoundPathFromEnum(buttonClickSound)); MODRadio.anyRadioPressed = false; - anyButtonPressed = false; + MODMenuCommon.anyButtonPressed = false; } } } + // This temporarily changes button click sound for one frame. + // The button click sound will revert to the default click sound on the next frame. public void OverrideClickSound(GUISound sound) { buttonClickSound = sound; } - public void SetMode(ModMenuMode menuMode) + // The mod menu has different sub-menus, which can be switched between by calling this function. + // If the sub-menus have any state, it will be retained during switching, and even if the menu is closed and reopened. + public void SetSubMenu(ModSubMenu subMenu) { - switch (menuMode) + switch (subMenu) { - case ModMenuMode.AudioSetup: + case ModSubMenu.AudioSetup: currentMenu = audioSetupMenu; break; - case ModMenuMode.Normal: + case ModSubMenu.Normal: default: currentMenu = normalMenu; break; } } + // This function attempts to show the menu, but please note: + // - on the Save / Load screen, it will instead tell you to close the Save/Load screen + // - the menu might open after a short delay, due to using a delegate to close + // the currently open menu. Please keep this in mind if you're relying on the menu opening immediately. public void Show() { void ForceShow() @@ -349,8 +368,8 @@ void ForceShow() } /// - /// This function should be called when the user has initiated the hiding of the menu. - /// This function call might be ignored if the menu disallows closing - call + /// This function hides the menu if the menu allows it. + /// Use ForceHide() to forcibly hide the menu. /// public void UserHide() { @@ -360,6 +379,10 @@ public void UserHide() } } + /// + /// This function toggles the menu on/off if the menu allows it. + /// Use ForceToggleVisibility() to forcibly toggle the menu. + /// public void UserToggleVisibility() { if (currentMenu.UserCanClose()) @@ -368,6 +391,9 @@ public void UserToggleVisibility() } } + /// + /// This function hides the menu, even if the if the menu prefers not to be hidden. + /// public void ForceHide() { this.visible = false; @@ -375,6 +401,9 @@ public void ForceHide() gameSystem.ShowUIControls(); } + /// + /// This function toggles the menu, even if the if the menu prefers not to be toggled. + /// public void ForceToggleVisibility() { if (this.visible) diff --git a/MOD.Scripts.UI/MODMenuAudioSetup.cs b/MOD.Scripts.UI/MODMenuAudioSetup.cs index 9f3c1ef6..df96c873 100644 --- a/MOD.Scripts.UI/MODMenuAudioSetup.cs +++ b/MOD.Scripts.UI/MODMenuAudioSetup.cs @@ -33,7 +33,7 @@ public void OnGUI() if (GetGlobal("GAudioSet") != 0 && Button(new GUIContent("Click here when you're finished."))) { - modMenu.SetMode(ModMenuMode.Normal); + modMenu.SetSubMenu(ModSubMenu.Normal); modMenu.ForceHide(); } } diff --git a/MOD.Scripts.UI/MODMenuCommon.cs b/MOD.Scripts.UI/MODMenuCommon.cs index 44e84108..7747acf1 100644 --- a/MOD.Scripts.UI/MODMenuCommon.cs +++ b/MOD.Scripts.UI/MODMenuCommon.cs @@ -8,6 +8,7 @@ namespace MOD.Scripts.UI { static class MODMenuCommon { + public static bool anyButtonPressed; public static void Label(string label) { GUILayout.Label(label, MODStyleManager.OnGUIInstance.Group.label); @@ -20,7 +21,15 @@ public static void HeadingLabel(string label) public static bool Button(GUIContent guiContent, bool selected = false) { - return GUILayout.Button(guiContent, selected ? MODStyleManager.OnGUIInstance.Group.selectedButton : MODStyleManager.OnGUIInstance.Group.button); + if(GUILayout.Button(guiContent, selected ? MODStyleManager.OnGUIInstance.Group.selectedButton : MODStyleManager.OnGUIInstance.Group.button)) + { + anyButtonPressed = true; + return true; + } + else + { + return false; + } } public static int GetGlobal(string flagName) => BurikoMemory.Instance.GetGlobalFlag(flagName).IntValue(); diff --git a/MOD.Scripts.UI/MODMenuNormal.cs b/MOD.Scripts.UI/MODMenuNormal.cs index 8f97945a..01adcbeb 100644 --- a/MOD.Scripts.UI/MODMenuNormal.cs +++ b/MOD.Scripts.UI/MODMenuNormal.cs @@ -23,6 +23,14 @@ class MODMenuNormal : MODMenuModuleInterface private readonly MODRadio radioHideCG; private readonly MODRadio radioBackgrounds; private readonly MODRadio radioArtSet; + private readonly MODRadio radioStretchBackgrounds; + private readonly MODRadio radioTextWindowModeAndCrop; + + private readonly MODTabControl tabControl; + + private readonly MODCustomFlagPreset customFlagPreset; + + private bool showDeveloperSubmenu; public MODMenuNormal(MODMenu modMenu, MODMenuAudioOptions audioOptionsMenu) { @@ -33,10 +41,9 @@ public MODMenuNormal(MODMenu modMenu, MODMenuAudioOptions audioOptionsMenu) hasOGBackgrounds = MODActions.HasOGBackgrounds(); defaultArtsetDescriptions = new GUIContent[] { - new GUIContent("Console", "Use the Console sprites and backgrounds"), - new GUIContent("Remake", "Use Mangagamer's remake sprites with Console backgrounds"), - new GUIContent("Original", "Use Original/Ryukishi sprites and backgrounds (if available - OG backgrounds not available for Console Arcs)\n\n" + - "Warning: Most users should use the Original/Ryukishi preset at the top of this menu!"), + new GUIContent("Console", "Use the Console sprites"), + new GUIContent("MangaGamer", "Use Mangagamer's remake sprites"), + new GUIContent("Original", "Use Original/Ryukishi sprites"), }; string baseCensorshipDescription = @" @@ -80,155 +87,207 @@ Sets the script censorship level new GUIContent("Hide CGs", "Disables all CGs (mainly for use with the Original/Ryukishi preset)"), }); - radioBackgrounds = new MODRadio("Override Art Set Backgrounds", new GUIContent[]{ - new GUIContent("Default BGs", "Use the default backgrounds for the current artset"), - new GUIContent("Console BGs", "Force Console backgrounds, regardless of the artset"), - new GUIContent("Original BGs", "Force Original/Ryukishi backgrounds, regardless of the artset"), - new GUIContent("Original Stretched", "Force Original/Ryukishi backgrounds, stretched to fit, regardless of the artset\n\n" + - "WARNING: When using this option, you should have ADV/NVL mode selected, otherwise sprites will be cut off, and UI will appear in the wrong place"), + radioBackgrounds = new MODRadio("Background Style", new GUIContent[]{ + new GUIContent("Console BGs", "Use Console backgrounds"), + new GUIContent("Original BGs", "Use Original/Ryukishi backgrounds."), + }, itemsPerRow: 2); + + radioStretchBackgrounds = new MODRadio("Background Stretching", new GUIContent[] + { + new GUIContent("BG Stretching Off", "Makes backgrounds as big as possible without any stretching (Keep Aspect Ratio)"), + new GUIContent("BG Stretching On", "Stretches backgrounds to fit the screen (Ignore Aspect Ratio)\n\n" + + "Mainly for use with the Original BGs, which are in 4:3 aspect ratio."), + }); + + radioArtSet = new MODRadio("Sprite Style", defaultArtsetDescriptions, itemsPerRow: 3); + + radioTextWindowModeAndCrop = new MODRadio("Text Window Appearance", new GUIContent[]{ + new GUIContent("ADV Mode", "This option:\n" + + "- Makes text show at the bottom of the screen in a textbox\n" + + "- Shows the name of the current character on the textbox\n"), + new GUIContent("NVL Mode", "This option:\n" + + "- Makes text show across the whole screen\n"), + new GUIContent("Original", "This option:\n" + + "- Darkens the whole screen to emulate the original game\n" + + "- Makes text show only in a 4:3 section of the screen (narrower than NVL mode)\n"), }, itemsPerRow: 2); - radioArtSet = new MODRadio("Choose Art Set", defaultArtsetDescriptions, itemsPerRow: 3); + tabControl = new MODTabControl(new List + { + new MODTabControl.TabProperties("Gameplay", "Voice Matching and Opening Videos", GameplayTabOnGUI), + new MODTabControl.TabProperties("Graphics", "Sprites, Backgrounds, CGs, Resolution", GraphicsTabOnGUI), + new MODTabControl.TabProperties("Audio", "BGM and SE options", AudioTabOnGUI), + new MODTabControl.TabProperties("Troubleshooting", "Tools to help you if something goes wrong", TroubleShootingTabOnGUI), + }); + + customFlagPreset = Assets.Scripts.Core.Buriko.BurikoMemory.Instance.GetCustomFlagPresetInstance(); } public void OnGUI() { - HeadingLabel("Basic Options"); + tabControl.OnGUI(); + } + + public void OnBeforeMenuVisible() { + // Update the artset radio buttons/descriptions, as these are set by ModAddArtset() calls in init.txt at runtime + // Technically only need to do this once after init.txt has been called, but it's easier to just do it each time menu is opened + GUIContent[] descriptions = Core.MODSystem.instance.modTextureController.GetArtStyleDescriptions(); + for (int i = 0; i < descriptions.Length; i++) + { + if (i < this.defaultArtsetDescriptions.Length) + { + descriptions[i] = this.defaultArtsetDescriptions[i]; + } + } + this.radioArtSet.SetContents(descriptions); + resolutionMenu.OnBeforeMenuVisible(); + audioOptionsMenu.OnBeforeMenuVisible(); + } + + private void GraphicsTabOnGUI() + { Label("Graphics Presets (Hotkey: F1)"); { GUILayout.BeginHorizontal(); int advNVLRyukishiMode = MODActions.GetADVNVLRyukishiModeFromFlags(out bool presetModified); - if (Button(new GUIContent(advNVLRyukishiMode == 0 && presetModified ? "ADV (custom)" : "ADV", "This preset:\n" + + if (Button(new GUIContent("Console", "This preset:\n" + "- Makes text show at the bottom of the screen in a textbox\n" + "- Shows the name of the current character on the textbox\n" + - "- Uses the console sprites and backgrounds\n" + + "- Uses the console sprites (with lipsync) and console backgrounds\n" + "- Displays in 16:9 widescreen\n\n" + - "Note that sprites and backgrounds can be overridden by setting the 'Choose Art Set' & 'Override Art Set Backgrounds' options under 'Advanced Options', if available"), selected: advNVLRyukishiMode == 0)) + "Note that sprites and backgrounds can be overridden by setting the 'Choose Art Set' & 'Override Art Set Backgrounds' options under 'Advanced Options', if available"), selected: !customFlagPreset.Enabled && !presetModified && advNVLRyukishiMode == 0)) { - MODActions.SetAndSaveADV(MODActions.ModPreset.ADV, showInfoToast: false); + MODActions.SetGraphicsPreset(MODActions.ModPreset.Console, showInfoToast: false); } - if (Button(new GUIContent(advNVLRyukishiMode == 1 && presetModified ? "NVL (custom)" : "NVL", "This preset:\n" + + if (Button(new GUIContent("MangaGamer", "This preset:\n" + "- Makes text show across the whole screen\n" + "- Uses the console sprites and backgrounds\n" + "- Displays in 16:9 widescreen\n\n" + - "Note that sprites and backgrounds can be overridden by setting the 'Choose Art Set' & 'Override Art Set Backgrounds' options under 'Advanced Options', if available"), selected: advNVLRyukishiMode == 1)) + "Note that sprites and backgrounds can be overridden by setting the 'Choose Art Set' & 'Override Art Set Backgrounds' options under 'Advanced Options', if available"), selected: !customFlagPreset.Enabled && !presetModified && advNVLRyukishiMode == 1)) { - MODActions.SetAndSaveADV(MODActions.ModPreset.NVL, showInfoToast: false); + MODActions.SetGraphicsPreset(MODActions.ModPreset.MangaGamer, showInfoToast: false); } if (this.hasOGBackgrounds && - Button(new GUIContent(advNVLRyukishiMode == 2 && presetModified ? "Original/Ryukishi (custom)" : "Original/Ryukishi", "This preset makes the game behave similarly to the unmodded game:\n" + + Button(new GUIContent("Original/Ryukishi", "This preset makes the game behave similarly to the unmodded game:\n" + "- Displays backgrounds in 4:3 'standard' aspect\n" + "- CGs are disabled (Can be re-enabled, see 'Show/Hide CGs')\n" + "- Switches to original sprites and backgrounds\n\n" + - "Note that sprites, backgrounds, and CG hiding can be overridden by setting the 'Choose Art Set' & 'Override Art Set Backgrounds' options under 'Advanced Options', if available"), selected: advNVLRyukishiMode == 2)) + "Note that sprites, backgrounds, and CG hiding can be overridden by setting the 'Choose Art Set' & 'Override Art Set Backgrounds' options under 'Advanced Options', if available"), selected: !customFlagPreset.Enabled && !presetModified && advNVLRyukishiMode == 2)) { - MODActions.SetAndSaveADV(MODActions.ModPreset.OG, showInfoToast: false); + MODActions.SetGraphicsPreset(MODActions.ModPreset.OG, showInfoToast: false); + } + + if (Button(new GUIContent("Custom", "Your own custom preset, using the options below.\n\n" + + "This custom preset will be saved, even when you switch to the other presets."), selected: customFlagPreset.Enabled)) + { + MODActions.LoadCustomGraphicsPreset(showInfoToast: false); } GUILayout.EndHorizontal(); } - if (this.radioCensorshipLevel.OnGUIFragment(GetGlobal("GCensor")) is int censorLevel) - { - SetGlobal("GCensor", censorLevel); - }; + HeadingLabel("Advanced Options"); if (this.radioLipSync.OnGUIFragment(GetGlobal("GLipSync")) is int lipSyncEnabled) { - SetGlobal("GLipSync", lipSyncEnabled); + MODActions.SetFlagFromUserInput("GLipSync", lipSyncEnabled, showInfoToast: false); }; - if (this.radioOpenings.OnGUIFragment(GetGlobal("GVideoOpening") - 1) is int openingVideoLevelZeroIndexed) - { - SetGlobal("GVideoOpening", openingVideoLevelZeroIndexed + 1); - }; - - this.audioOptionsMenu.OnGUI(); - - HeadingLabel("Advanced Options"); - - this.audioOptionsMenu.AdvancedOnGUI(); - if (this.radioHideCG.OnGUIFragment(GetGlobal("GHideCG")) is int hideCG) { - SetGlobal("GHideCG", hideCG); + MODActions.SetFlagFromUserInput("GHideCG", hideCG, showInfoToast: false); }; if (this.radioArtSet.OnGUIFragment(Core.MODSystem.instance.modTextureController.GetArtStyle()) is int artStyle) { - SetGlobal("GStretchBackgrounds", 0); - Core.MODSystem.instance.modTextureController.SetArtStyle(artStyle, showInfoToast: false); + MODActions.SetArtStyle(artStyle, showInfoToast: false); } if (this.hasOGBackgrounds) { - int currentBackground = GetGlobal("GBackgroundSet"); - if (currentBackground == 2) + if (this.radioBackgrounds.OnGUIFragment(GetGlobal("GBackgroundSet")) is int background) { - if (GetGlobal("GStretchBackgrounds") == 1) - { - currentBackground = 3; - } + MODActions.SetFlagFromUserInput("GBackgroundSet", background, showInfoToast: false); + GameSystem.Instance.SceneController.ReloadAllImages(); } - if (this.radioBackgrounds.OnGUIFragment(currentBackground) is int background) + + if (this.radioStretchBackgrounds.OnGUIFragment(GetGlobal("GStretchBackgrounds")) is int stretchBackgrounds) { - if (background == 3) - { - SetGlobal("GStretchBackgrounds", 1); - SetGlobal("GBackgroundSet", 2); - } - else - { - SetGlobal("GStretchBackgrounds", 0); - SetGlobal("GBackgroundSet", background); - } + MODActions.SetFlagFromUserInput("GStretchBackgrounds", stretchBackgrounds, showInfoToast: false); GameSystem.Instance.SceneController.ReloadAllImages(); } } + if (this.radioTextWindowModeAndCrop.OnGUIFragment(MODActions.GetADVNVLRyukishiModeFromFlags()) is int windowMode) + { + MODActions.SetTextWindowAppearance((MODActions.ModPreset) windowMode, showInfoToast: false); + GameSystem.Instance.SceneController.ReloadAllImages(); + } + + HeadingLabel("Resolution"); + resolutionMenu.OnGUI(); + } + + private void GameplayTabOnGUI() + { + if (this.radioCensorshipLevel.OnGUIFragment(GetGlobal("GCensor")) is int censorLevel) + { + SetGlobal("GCensor", censorLevel); + }; + + if (this.radioOpenings.OnGUIFragment(GetGlobal("GVideoOpening") - 1) is int openingVideoLevelZeroIndexed) + { + SetGlobal("GVideoOpening", openingVideoLevelZeroIndexed + 1); + }; + } - GUILayout.Space(10); - OnGUIRestoreSettings(); + private void AudioTabOnGUI() + { + this.audioOptionsMenu.OnGUI(); - HeadingLabel("Troubleshooting"); + HeadingLabel("Advanced Options"); + + this.audioOptionsMenu.AdvancedOnGUI(); + } + + + private void TroubleShootingTabOnGUI() + { Label("Save Files and Log Files"); MODMenuSupport.ShowSupportButtons(content => Button(content)); - Label("Developer"); - GUILayout.BeginHorizontal(); - if (Button(new GUIContent("Toggle debug menu (Shift-F9)", "Toggle the debug menu"))) - { - modMenu.ToggleDebugMenu(); - } - if (Button(new GUIContent("Toggle flag menu (Shift-F10)", "Toggle the flag menu. Toggle Multiple times for more options.\n\nNote: 3rd and 4th panels are only shown if GMOD_DEBUG_MODE is true."))) + HeadingLabel("Developer Tools"); + + if(showDeveloperSubmenu) { - MODActions.ToggleFlagMenu(); - } - GUILayout.EndHorizontal(); - } + OnGUIRestoreSettings(); - public void OnBeforeMenuVisible() { - // Update the artset radio buttons/descriptions, as these are set by ModAddArtset() calls in init.txt at runtime - // Technically only need to do this once after init.txt has been called, but it's easier to just do it each time menu is opened - GUIContent[] descriptions = Core.MODSystem.instance.modTextureController.GetArtStyleDescriptions(); - for (int i = 0; i < descriptions.Length; i++) + Label("Developer Debug Menu"); + GUILayout.BeginHorizontal(); + if (Button(new GUIContent("Toggle debug menu (Shift-F9)", "Toggle the debug menu"))) + { + modMenu.ToggleDebugMenu(); + } + if (Button(new GUIContent("Toggle flag menu (Shift-F10)", "Toggle the flag menu. Toggle Multiple times for more options.\n\nNote: 3rd and 4th panels are only shown if GMOD_DEBUG_MODE is true."))) + { + MODActions.ToggleFlagMenu(); + } + GUILayout.EndHorizontal(); + } + else { - if (i < this.defaultArtsetDescriptions.Length) + if (Button(new GUIContent("Show Developer Tools: Only click if asked by developers", "Show the Developer Tools.\n\nOnly click this button if you're asked to by the developers."))) { - descriptions[i] = this.defaultArtsetDescriptions[i]; + showDeveloperSubmenu = true; } } - this.radioArtSet.SetContents(descriptions); - - resolutionMenu.OnBeforeMenuVisible(); - audioOptionsMenu.OnBeforeMenuVisible(); } private void OnGUIRestoreSettings() diff --git a/MOD.Scripts.UI/MODMenuSupport.cs b/MOD.Scripts.UI/MODMenuSupport.cs index 625435c3..9a10bc7a 100644 --- a/MOD.Scripts.UI/MODMenuSupport.cs +++ b/MOD.Scripts.UI/MODMenuSupport.cs @@ -52,7 +52,7 @@ public static void ShowSupportButtons(Func buttonRenderer) } if (buttonRenderer(new GUIContent("Show Saves", "Clearing your save files can fix some issues with game startup, and resets all mod flags.\n\n" + - "- WARNING: Steam sync will restore your saves if you manually delete them! Therefore, remember to disable steam sync, otherwise your saves will magically reappear!\n" + + "- WARNING: Steam cloud will restore your saves if you manually delete them! Therefore, remember to disable steam cloud, otherwise your saves will magically reappear!\n" + "- The 'global.dat' file stores your global unlock process and mod flags\n" + "- The 'qsaveX.dat' and 'saveXXX.dat' files contain individual save files. Note that these becoming corrupted can break your game\n" + "- It's recommended to take a backup of all your saves before you modify them"))) diff --git a/MOD.Scripts.UI/MODTabControl.cs b/MOD.Scripts.UI/MODTabControl.cs new file mode 100644 index 00000000..a8c52743 --- /dev/null +++ b/MOD.Scripts.UI/MODTabControl.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using static MOD.Scripts.UI.MODMenuCommon; + +namespace MOD.Scripts.UI +{ + class MODTabControl + { + public class TabProperties + { + public string name; + public string description; + public Action tabRenderFunction; + + public TabProperties(string name, string description, Action tabRenderFunction) + { + this.name = name; + this.description = description; + this.tabRenderFunction = tabRenderFunction; + } + } + + private int currentTab; + private List tabProperties; + private readonly MODRadio radioTabs; + + public MODTabControl(List tabProperties) + { + this.tabProperties = tabProperties; + this.radioTabs = new MODRadio("", tabProperties.Select(x => new GUIContent(x.name, x.description)).ToArray()); + } + + public void OnGUI() + { + if (radioTabs.OnGUIFragment(this.currentTab, hideLabel: true) is int tab) + { + this.currentTab = tab; + } + + // Probably not possible, but restrict the current tab to be within limits + currentTab = currentTab % tabProperties.Count; + + HeadingLabel(tabProperties[currentTab].name); + + // Render the current tab + tabProperties[currentTab].tabRenderFunction(); + } + } +}