diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj index e2352eb8..4f1833a7 100644 --- a/Assembly-CSharp.csproj +++ b/Assembly-CSharp.csproj @@ -4,9 +4,7 @@ {0BA774AA-B2B4-468C-B002-CAA0652C466E} Debug AnyCPU - Library 7.1 - Assembly-CSharp v2.0 4 True @@ -26,7 +24,13 @@ pdbonly true - + + + + Library + Assembly-CSharp + + DLLs\UnityEngine.dll @@ -51,8 +55,6 @@ DLLs\UnityEngine.UI.dll - - @@ -192,6 +194,7 @@ + @@ -241,6 +244,7 @@ + @@ -614,6 +618,52 @@ - + + + + + Exe + HigurashiScriptCompiler + STANDALONE_SCRIPT_COMPILER + + bin\ScriptCompiler\ + true + pdbonly + true + + + + DLLs\System.dll + + + DLLs\System.Core.dll + + + DLLs\Antlr3.Runtime.dll + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Assembly-CSharp.sln b/Assembly-CSharp.sln index c0336b00..22493eb2 100644 --- a/Assembly-CSharp.sln +++ b/Assembly-CSharp.sln @@ -9,12 +9,15 @@ Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU + ScriptCompiler|Any CPU = ScriptCompiler|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {0BA774AA-B2B4-468C-B002-CAA0652C466E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0BA774AA-B2B4-468C-B002-CAA0652C466E}.Debug|Any CPU.Build.0 = Debug|Any CPU {0BA774AA-B2B4-468C-B002-CAA0652C466E}.Release|Any CPU.ActiveCfg = Release|Any CPU {0BA774AA-B2B4-468C-B002-CAA0652C466E}.Release|Any CPU.Build.0 = Release|Any CPU + {0BA774AA-B2B4-468C-B002-CAA0652C466E}.ScriptCompiler|Any CPU.ActiveCfg = ScriptCompiler|Any CPU + {0BA774AA-B2B4-468C-B002-CAA0652C466E}.ScriptCompiler|Any CPU.Build.0 = ScriptCompiler|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Assets.Scripts.Core.Buriko/BurikoScriptFile.cs b/Assets.Scripts.Core.Buriko/BurikoScriptFile.cs index 66ba188c..21bdf9e1 100644 --- a/Assets.Scripts.Core.Buriko/BurikoScriptFile.cs +++ b/Assets.Scripts.Core.Buriko/BurikoScriptFile.cs @@ -2801,8 +2801,8 @@ public BurikoVariable OperationMODGenericCall() string[] lipSyncParams = callParameters.Split(','); if(lipSyncParams.Length == 3 && - float.TryParse(lipSyncParams[0].Trim(), out float thresh1) && - float.TryParse(lipSyncParams[1].Trim(), out float thresh2) && + MODUtility.TryParseInvariantCulture(lipSyncParams[0].Trim(), out float thresh1) && + MODUtility.TryParseInvariantCulture(lipSyncParams[1].Trim(), out float thresh2) && bool.TryParse(lipSyncParams[2].Trim(), out bool forceComputedLipsync)) { GameSystem.Instance.SceneController.MODSetExpressionThresholds(thresh1, thresh2); @@ -2816,7 +2816,7 @@ public BurikoVariable OperationMODGenericCall() case "LipSyncCacheSettings": string[] lipSyncCacheParams = callParameters.Split(','); - if (lipSyncCacheParams.Length == 1 && int.TryParse(lipSyncCacheParams[0].Trim(), out int maxAge)) + if (lipSyncCacheParams.Length == 1 && MODUtility.TryParseInvariantCulture(lipSyncCacheParams[0].Trim(), out int maxAge)) { MODLipsyncCache.SetMaxTextureAge(maxAge); } @@ -2827,21 +2827,21 @@ public BurikoVariable OperationMODGenericCall() break; case "NormalFontWeight": - if(int.TryParse(callParameters, out int normalFontWeightPercent)) + if(MODUtility.TryParseInvariantCulture(callParameters, out int normalFontWeightPercent)) { MODFontAdjuster.SetNormalFontWeight(normalFontWeightPercent / 100.0f); } break; case "BoldFontWeight": - if (int.TryParse(callParameters, out int boldFontWeightPercent)) + if (MODUtility.TryParseInvariantCulture(callParameters, out int boldFontWeightPercent)) { MODFontAdjuster.SetBoldFontWeight(boldFontWeightPercent / 100.0f); } break; case "FaceDilate": - if (int.TryParse(callParameters, out int faceDilate)) + if (MODUtility.TryParseInvariantCulture(callParameters, out int faceDilate)) { MODFontAdjuster.SetFaceDilation(faceDilate / 100.0f); } diff --git a/Assets.Scripts.Core.State/StateNormal.cs b/Assets.Scripts.Core.State/StateNormal.cs index 4cad5553..d025ffa6 100644 --- a/Assets.Scripts.Core.State/StateNormal.cs +++ b/Assets.Scripts.Core.State/StateNormal.cs @@ -191,26 +191,6 @@ public bool InputHandler() gameSystem.IsSkipping = !gameSystem.IsSkipping; } - // Fullscreen - if (Input.GetKeyDown(KeyCode.F)) - { - if (GameSystem.Instance.IsFullscreen) - { - int num14 = PlayerPrefs.GetInt("width"); - int num15 = PlayerPrefs.GetInt("height"); - if (num14 == 0 || num15 == 0) - { - num14 = 640; - num15 = 480; - } - GameSystem.Instance.DeFullscreen(width: num14, height: num15); - } - else - { - GameSystem.Instance.GoFullscreen(); - } - } - // Toggle Language if (Input.GetKeyDown(KeyCode.L)) { diff --git a/Assets.Scripts.Core/GameSystem.cs b/Assets.Scripts.Core/GameSystem.cs index 599bfa2f..d5ecdcf9 100644 --- a/Assets.Scripts.Core/GameSystem.cs +++ b/Assets.Scripts.Core/GameSystem.cs @@ -897,14 +897,18 @@ private void LateUpdate() if (screenModeSet == -1) { screenModeSet = 0; + string source = "Screen.currentResolution"; fullscreenResolution = Screen.currentResolution; if (PlayerPrefs.HasKey("fullscreen_width") && PlayerPrefs.HasKey("fullscreen_height") && Screen.fullScreen) { + source = "PlayerPrefs[fullscreen_width] and [fullscreen_height]"; fullscreenResolution.width = PlayerPrefs.GetInt("fullscreen_width"); fullscreenResolution.height = PlayerPrefs.GetInt("fullscreen_height"); } - Debug.Log("Fullscreen Resolution: " + fullscreenResolution.width + ", " + fullscreenResolution.height); + Debug.Log($"LateUpdate Fullscreen Resolution: [{fullscreenResolution.width}, {fullscreenResolution.height}] Source: [{source}]"); } + + MOD.Scripts.Core.MODResolutionMonitor.Update(); } private bool CheckInitialization() @@ -1068,6 +1072,15 @@ public T ChooseJapaneseEnglish(T japanese, T english) } } + public bool MODWindowedResolutionValid(int width, int height) + { + Resolution fullScreenResolution = GetFullscreenResolution(); + return width > 320 && + height > 240 && + width <= fullScreenResolution.width && + height <= fullScreenResolution.height; + } + public Resolution GetFullscreenResolution(bool useOverride = true, bool doLogging = true) { Resolution resolution = new Resolution(); @@ -1102,7 +1115,14 @@ public Resolution GetFullscreenResolution(bool useOverride = true, bool doLoggin // If it's bigger than that, then switch over // Note that this (from what I can tell) gives you the biggest resolution of any of your monitors, // not just the one the game is running under, so it could *also* be wrong, which is why we check both methods - if (Screen.resolutions.Length > 0) + // + // NOTE: On the Higurashi Rei (Ep9) and Hou+ (Ep10) versions of Unity (2019.4.36), Screen.resolutions doesn't work correctly. + // The unmodded game for Ep9 and Ep10 works correctly on my laptop, implying the 'stock' fullscreen resolution is correct. + // As a result the below "best resolution" code has been disabled for Ep9 and Ep10 (and future chapters too) and the LateUpdate() resolution is used instead. + // + // See: https://github.com/07th-mod/hou-plus/issues/13 + const bool doBestResolutionScanning = true; + if (doBestResolutionScanning && Screen.resolutions.Length > 0) { int index = 0; Resolution best = Screen.resolutions[0]; diff --git a/Assets.Scripts.UI.Config/ScreenSwitcherButton.cs b/Assets.Scripts.UI.Config/ScreenSwitcherButton.cs index 0a54159e..0b2dad85 100644 --- a/Assets.Scripts.UI.Config/ScreenSwitcherButton.cs +++ b/Assets.Scripts.UI.Config/ScreenSwitcherButton.cs @@ -1,4 +1,6 @@ using Assets.Scripts.Core; +using MOD.Scripts.Core; +using MOD.Scripts.UI; using UnityEngine; namespace Assets.Scripts.UI.Config @@ -11,7 +13,7 @@ public class ScreenSwitcherButton : MonoBehaviour private UIButton button; - private int Height() + private int? Height() { // Old resolutions (which the assetbundle button widths are based on) were 640x480, 800x600, and 1024x768 // New resolutions are 1280x720, 1920x1080, and 2560x1440 @@ -24,8 +26,7 @@ private int Height() case 1024: return 1440; default: - Debug.LogWarning("Found unexpected width button " + Width); - return Mathf.RoundToInt(Width / GameSystem.Instance.AspectRatio); + return null; } } @@ -34,22 +35,43 @@ private void OnClick() if (IsFullscreen) { GameSystem.Instance.GoFullscreen(); + return; } - else + + // Check the button's Width is valid. I've seen error messages where the above Height() switch statement states that Width is 0. + int? maybe_height = Height(); + if(maybe_height == null) + { + MODToaster.Show($"Button has invalid width: [{Width}]"); + return; + } + + int height = maybe_height.Value; + int width = Mathf.RoundToInt(height * GameSystem.Instance.AspectRatio); + + // Check the windowed resolution would actually fit in the monitor + if (!GameSystem.Instance.MODWindowedResolutionValid(width, height)) { - int height = Height(); - int width = Mathf.RoundToInt(height * GameSystem.Instance.AspectRatio); - GameSystem.Instance.DeFullscreen(width: width, height: height); - PlayerPrefs.SetInt("width", width); - PlayerPrefs.SetInt("height", height); + MODToaster.Show($"Resolution Too Big/Small: [{width}x{height}]"); + return; } + + GameSystem.Instance.DeFullscreen(width: width, height: height); + PlayerPrefs.SetInt("width", width); + PlayerPrefs.SetInt("height", height); + + // Check if the resolution was set correctly. If not, revert to fullscreen resolution. + // See MODResolutionMonitor for details + MODResolutionMonitor.RevertToFullscreenIfResolutionChangeFails(new ResolutionChangeInfo(width, height, delayBeforeResolutionCheck: 10)); + + Debug.Log($"Attempted to set Windowed resolution {width}x{height}"); } private bool ShouldBeDown() { - if (IsFullscreen) + if (GameSystem.Instance.IsFullscreen) { - return GameSystem.Instance.IsFullscreen; + return IsFullscreen; } return Screen.height == Height(); } diff --git a/BGICompiler.Compiler/BGIParameters.cs b/BGICompiler.Compiler/BGIParameters.cs index 02d36ec3..de657261 100644 --- a/BGICompiler.Compiler/BGIParameters.cs +++ b/BGICompiler.Compiler/BGIParameters.cs @@ -1,7 +1,7 @@ using Antlr.Runtime.Tree; using Assets.Scripts.Core.Buriko; using System.Collections.Generic; -using UnityEngine; +using BGICompiler.Compiler.Logger; namespace BGICompiler.Compiler { diff --git a/BGICompiler.Compiler/BGItoMG.cs b/BGICompiler.Compiler/BGItoMG.cs index 62cacd20..fafbad8e 100644 --- a/BGICompiler.Compiler/BGItoMG.cs +++ b/BGICompiler.Compiler/BGItoMG.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using UnityEngine; +using BGICompiler.Compiler.Logger; namespace BGICompiler.Compiler { diff --git a/BGICompiler.Compiler/LoggerWrapper.cs b/BGICompiler.Compiler/LoggerWrapper.cs new file mode 100644 index 00000000..49e9239f --- /dev/null +++ b/BGICompiler.Compiler/LoggerWrapper.cs @@ -0,0 +1,44 @@ +using System; + +namespace BGICompiler.Compiler.Logger +{ + class Debug + { +#if STANDALONE_SCRIPT_COMPILER + public static void Log(object message) + { + print($"INFO", message); + } + + public static void LogWarning(object message) + { + print("WARN", message); + } + + public static void LogError(object message) + { + print("ERROR", message); + } + + private static void print(string level, object message) + { + Console.WriteLine($"[{level}] {message}", message); + } +#else + public static void Log(object message) + { + UnityEngine.Debug.Log(message); + } + + public static void LogWarning(object message) + { + UnityEngine.Debug.LogWarning(message); + } + + public static void LogError(object message) + { + UnityEngine.Debug.Log(message); + } +#endif + } +} diff --git a/BGICompiler.Compiler/NoUnityScriptCompiler.cs b/BGICompiler.Compiler/NoUnityScriptCompiler.cs new file mode 100644 index 00000000..6cbb4797 --- /dev/null +++ b/BGICompiler.Compiler/NoUnityScriptCompiler.cs @@ -0,0 +1,90 @@ +#if STANDALONE_SCRIPT_COMPILER + +using System; +using System.IO; +using BGICompiler.Compiler.Logger; + +namespace BGICompiler.Compiler +{ + internal class NoUnityScriptCompiler + { + private static bool SaveCompileStatus(int numTotal, int numPass, int numFail) + { + bool allCompiledOK = numPass == numTotal; + bool atLeastOneCompiled = numPass != 0; + bool pass = allCompiledOK && atLeastOneCompiled; + + // Also consider compilation a failure if no scripts were compiled + string statusString = pass ? "Compile OK" : "FAIL"; + statusString += $" | {numPass}/{numTotal} compiled and {numFail} failed"; + File.WriteAllText("higu_script_compile_status.txt", statusString); + + return pass; + } + + private static bool CompileFolder(string srcDir, string destDir) + { + string[] txtPaths = Directory.GetFiles(srcDir, "*.txt"); + int numTotal = txtPaths.Length; + + int numPass = 0; + int numFail = 0; + int progress = 0; + foreach (string txtPath in txtPaths) + { + progress++; + + try + { + string txtPathNoExt = Path.GetFileNameWithoutExtension(txtPath); + string mgPath = Path.Combine(destDir, txtPathNoExt) + ".mg"; + Debug.Log($"Compiling [{progress}/{numTotal}] {txtPath} -> {mgPath}..."); + new BGItoMG(txtPath, mgPath); + numPass++; + } + catch (Exception e) + { + Debug.LogWarning($"Failed to compile script {txtPath}!\r\n{e}"); + numFail++; + } + } + + return SaveCompileStatus(numTotal, numPass, numFail); + } + + public static int Main(string[] args) + { + if(args.Length < 2) + { + Debug.LogError($"Got {args.Length} args but need at least two args:\n" + + $" 1. path to the 'Update' (.txt) folder\n" + + $" 2. path to the 'CompiledUpdateScripts' folder (.mg)\n" + + $"Example: [HigurashiScriptCompiler Update CompiledUpdateScripts] when called from inside the StreamingAssets folder."); + return -1; + } + + Debug.Log($"Got {args.Length} args"); + + string txtFolderPath = args[0]; + string mgFolderPath = args[1]; + + Debug.Log($"Compiling Scripts In {txtFolderPath} -> {mgFolderPath}"); + + if(!Directory.Exists(txtFolderPath)) + { + Debug.LogError($"Source .txt script folder does not exist at [{txtFolderPath}]"); + return -1; + } + + if (!Directory.Exists(mgFolderPath)) + { + Debug.LogError($"Destination .mg script folder does not exist at [{mgFolderPath}]"); + return -1; + } + + return CompileFolder(txtFolderPath, mgFolderPath) ? 0 : -1; + } + } +} + +#endif diff --git a/BGICompiler.Compiler/OperationHandler.cs b/BGICompiler.Compiler/OperationHandler.cs index 4fcf4b1e..13454b42 100644 --- a/BGICompiler.Compiler/OperationHandler.cs +++ b/BGICompiler.Compiler/OperationHandler.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.IO; -using UnityEngine; +using BGICompiler.Compiler.Logger; namespace BGICompiler.Compiler { diff --git a/MOD.Scripts.Core/MODResolutionMonitor.cs b/MOD.Scripts.Core/MODResolutionMonitor.cs new file mode 100644 index 00000000..f82893bb --- /dev/null +++ b/MOD.Scripts.Core/MODResolutionMonitor.cs @@ -0,0 +1,68 @@ +using Assets.Scripts.Core; +using MOD.Scripts.UI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace MOD.Scripts.Core +{ + public class ResolutionChangeInfo + { + public int targetWidth; + public int targetHeight; + public int framesSinceResolutionChange; + public int delayBeforeResolutionCheck; + + public ResolutionChangeInfo(int targetWidth, int targetHeight, int delayBeforeResolutionCheck) + { + this.targetHeight = targetHeight; + this.targetWidth = targetWidth; + this.framesSinceResolutionChange = 0; + this.delayBeforeResolutionCheck = delayBeforeResolutionCheck; + } + } + + /// + /// This class monitors the game's resolution, to see if it was set correctly, and reverts + /// to fullscreen if it was not set correctly after some number of frames. + /// + /// I noticed that on Ubuntu, on Ch9 and Ch10 if you set a windowed resolution greater than your screen's resolution, the + /// window manager would resize the window back, but this would break something internally. If you then + /// tried to set the same resolution again, the mouse cursor would no longer align with the screen. + /// + /// The below code tries to catch this condition, and revert to fullscreen as a 'safe' fallback. + /// + /// Currently (2024-08-12) the below is only run when you use the main menu config buttons + /// to set the windowed resolution. The F10 mod menu resolution buttons will forcibly set the resolution. + /// + static class MODResolutionMonitor + { + private static ResolutionChangeInfo lastChangeInfo = null; + public static void RevertToFullscreenIfResolutionChangeFails(ResolutionChangeInfo info) => lastChangeInfo = info; + + public static void Update() + { + //This can bug out if you click the resolution change buttons in less than "delayBeforeResolutionCheck" + if (lastChangeInfo is ResolutionChangeInfo info) + { + if (info.framesSinceResolutionChange >= info.delayBeforeResolutionCheck) + { + if (Screen.width == info.targetWidth && Screen.height == info.targetHeight) + { + MODToaster.Show($"Successfuly set resolution: [{Screen.width}x{Screen.height}]"); + } + else + { + MODToaster.Show($"Failed to set Windowed Resolution properly [detected {Screen.width}x{Screen.height}] - reverting to fullscreen"); + GameSystem.Instance.GoFullscreen(); + } + lastChangeInfo = null; + } + + info.framesSinceResolutionChange++; + } + } + } +} diff --git a/MOD.Scripts.Core/MODUtility.cs b/MOD.Scripts.Core/MODUtility.cs index 4bb75cdb..bbdbc435 100644 --- a/MOD.Scripts.Core/MODUtility.cs +++ b/MOD.Scripts.Core/MODUtility.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; @@ -457,4 +458,14 @@ public static bool PatchWindowResizeFunction() return false; } } + + static public bool TryParseInvariantCulture(string s, out float value) + { + return float.TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out value); + } + + static public bool TryParseInvariantCulture(string s, out int value) + { + return int.TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out value); + } } diff --git a/MOD.Scripts.UI/MODKeyboardShortcuts.cs b/MOD.Scripts.UI/MODKeyboardShortcuts.cs index b1439c95..8cc7671c 100644 --- a/MOD.Scripts.UI/MODKeyboardShortcuts.cs +++ b/MOD.Scripts.UI/MODKeyboardShortcuts.cs @@ -72,10 +72,24 @@ enum Action DebugMode, RestoreSettings, ToggleAudioSet, + ToggleFullscreen, } private static Action? GetUserAction() { + // On Windows, a Windows specific key hook is setup in KeyHook.cs such that ALT+ENTER toggles fullscreen + // However on Linux and Mac this doesn't work, so use the below Unity keyboard shortcut to toggle fullscreen with ALT+ENTER + if (Application.platform != RuntimePlatform.WindowsPlayer) + { + if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)) + { + if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter)) + { + return Action.ToggleFullscreen; + } + } + } + // These take priority over the non-shift key buttons if (Input.GetKey(KeyCode.LeftShift)) { @@ -168,6 +182,10 @@ enum Action { return Action.VoiceVolumeDown; } + else if(Input.GetKeyDown(KeyCode.F)) + { + return Action.ToggleFullscreen; + } return null; } @@ -320,6 +338,24 @@ private static void ModHandleUserAction(Action action) } break; + case Action.ToggleFullscreen: + if (GameSystem.Instance.IsFullscreen) + { + int num14 = PlayerPrefs.GetInt("width"); + int num15 = PlayerPrefs.GetInt("height"); + if (num14 == 0 || num15 == 0) + { + num14 = 640; + num15 = 480; + } + GameSystem.Instance.DeFullscreen(width: num14, height: num15); + } + else + { + GameSystem.Instance.GoFullscreen(); + } + break; + default: Assets.Scripts.Core.Logger.Log($"Warning: Unknown mod action {action} was requested to be executed"); break; @@ -334,20 +370,29 @@ public static bool ModInputHandler() { if (GetUserAction() is Action action) { - if (action == Action.ModMenu) - { - GameSystem.Instance.MainUIController.modMenu.UserToggleVisibility(); - } - else + switch(action) { - if (!ModInputHandlingAllowed()) - { - MODToaster.Show($"Please let animation finish first and/or close the menu"); - } - else - { + case Action.ModMenu: + GameSystem.Instance.MainUIController.modMenu.UserToggleVisibility(); + break; + + // These actions can execute at any time + case Action.ToggleFullscreen: ModHandleUserAction(action); - } + break; + + // These actions can only run at reasonable times (eg. when not in a menu) + // to prevent UI and graphical bugs + default: + if (!ModInputHandlingAllowed()) + { + MODToaster.Show($"Please let animation finish first and/or close the menu"); + } + else + { + ModHandleUserAction(action); + } + break; } }