diff --git a/Data/RenderConfig.cs b/Data/RenderConfig.cs index 4830535..5241e14 100644 --- a/Data/RenderConfig.cs +++ b/Data/RenderConfig.cs @@ -115,6 +115,12 @@ public class AdvancedConfig /// public bool DisableTileCulling { get; set; } = true; + /// + /// When true, hides placeholder decorations (default tree-in-circle sprite) that failed to load or are empty. + /// Default false. + /// + public bool HideFailedDecorations { get; set; } = false; + /// File extension of the default output file name, without the dot (e.g. "mkv", "mp4"); selects the container. public string FileExtension { get; set; } = "mkv"; @@ -143,6 +149,7 @@ public class AdvancedConfig HighResBloomCorrection = source.HighResBloomCorrection; FixCustomFrameRate = source.FixCustomFrameRate; DisableTileCulling = source.DisableTileCulling; + HideFailedDecorations = source.HideFailedDecorations; FileExtension = source.FileExtension; firePlanetColor = source.firePlanetColor; icePlanetColor = source.icePlanetColor; diff --git a/Localization/Locale/en.json b/Localization/Locale/en.json index 218a31a..344ecc8 100644 --- a/Localization/Locale/en.json +++ b/Localization/Locale/en.json @@ -45,6 +45,7 @@ "ui.render_config.high_res_bloom_correction": "High-Resolution Bloom Correction", "ui.render_config.fix_custom_frame_rate": "Fix Custom FPS Render Timing", "ui.render_config.disable_tile_culling": "Disable Tile Culling", + "ui.render_config.hide_failed_decorations": "Hide Failed Decorations", "ui.render_config.random_seed": "Random Seed (Optional)", "ui.render_config.validation_title": "Invalid Settings", "ui.render_config.validation_error": "Please check your input values:\n- Width and Height must be integers > 0\n- Render FPS and Video FPS must be > 0\n- Delay After End must be >= 0", diff --git a/Localization/Locale/ko.json b/Localization/Locale/ko.json index dde6e4f..152f838 100644 --- a/Localization/Locale/ko.json +++ b/Localization/Locale/ko.json @@ -45,6 +45,7 @@ "ui.render_config.high_res_bloom_correction": "고해상도 블룸 보정", "ui.render_config.fix_custom_frame_rate": "커스텀 FPS 렌더 타이밍 보정", "ui.render_config.disable_tile_culling": "타일 컬링 비활성화", + "ui.render_config.hide_failed_decorations": "로딩 실패한 장식 숨기기", "ui.render_config.random_seed": "랜덤 시드 (선택 사항)", "ui.render_config.validation_title": "설정 오류", "ui.render_config.validation_error": "입력값을 확인해 주세요:\n- 너비와 높이는 0보다 큰 정수여야 합니다.\n- 렌더 FPS와 영상 FPS는 0보다 커야 합니다.\n- 종료 후 대기 시간은 0 이상이어야 합니다.", diff --git a/Patch/Game/DecorationPatch.cs b/Patch/Game/DecorationPatch.cs new file mode 100644 index 0000000..8ebce33 --- /dev/null +++ b/Patch/Game/DecorationPatch.cs @@ -0,0 +1,93 @@ +using ADOFAIRenderer.Core; +using HarmonyLib; +using UnityEngine; + +namespace ADOFAIRenderer.Patch.Game; + +/// +/// Patches decoration rendering to hide decorations that failed to load (or are empty) and fell back to the default +/// placeholder sprite (tree inside a circle) during a render when AdvancedConfig.HideFailedDecorations is enabled. +/// +public static class DecorationPatch +{ + /// + /// True while a render is loading or active and HideFailedDecorations is enabled in the configuration. + /// + public static bool IsEnabled => + RenderManager.Instance && + RenderManager.Instance.stage >= RenderStage.Load && + RenderManager.Instance.Configuration?.AdvancedConfig?.HideFailedDecorations == true; + + /// + /// Checks whether the given sprite is the default placeholder or not-found sprite of the decoration manager. + /// + public static bool IsPlaceholderSprite(Sprite? sprite, scrDecorationManager? manager = null) + { + if (!sprite) return false; + manager ??= scrDecorationManager.instance; + if (!manager) return false; + + return sprite == manager.defaultSprite || (manager.notFoundSprite && sprite == manager.notFoundSprite); + } + + /// + /// Sweeps all visual decorations in scrDecorationManager.instance.allDecorations and clears any placeholder sprites. + /// Called from when scnGame.Play runs. + /// + public static void ProcessDecorations() + { + if (!IsEnabled) return; + var manager = scrDecorationManager.instance; + if (!manager || manager.allDecorations == null) return; + + foreach (var dec in manager.allDecorations) + { + if (dec is scrVisualDecoration visualDec && visualDec.spriteRenderer) + { + if (IsPlaceholderSprite(visualDec.spriteRenderer.sprite, manager)) + { + visualDec.SetSprite((Sprite)null!); + } + } + } + } + + /// + /// Prefix of scrVisualDecoration.SetSprite(Sprite): replaces placeholder default/not-found sprites with null + /// when is true. + /// + [HarmonyPatch(typeof(scrVisualDecoration), nameof(scrVisualDecoration.SetSprite), typeof(Sprite))] + // ReSharper disable once InconsistentNaming + public static class VisualDecorationSetSpritePatch + { + [HarmonyPrefix] + public static void Prefix(scrVisualDecoration __instance, ref Sprite? sprite) + { + if (!IsEnabled || !sprite) return; + + if (IsPlaceholderSprite(sprite, __instance.manager)) + { + sprite = null; + } + } + } + + /// + /// Prefix of scrDecorationManager.ShowEmptyDecorations(bool): suppresses showing placeholder default sprites + /// during render when is true. + /// + [HarmonyPatch(typeof(scrDecorationManager), nameof(scrDecorationManager.ShowEmptyDecorations))] + public static class ShowEmptyDecorationsPatch + { + [HarmonyPrefix] + public static bool Prefix(bool show) + { + if (show && IsEnabled) + { + return false; + } + + return true; + } + } +} diff --git a/Patch/Game/scnGamePatch.cs b/Patch/Game/scnGamePatch.cs index dafd347..34936d5 100644 --- a/Patch/Game/scnGamePatch.cs +++ b/Patch/Game/scnGamePatch.cs @@ -40,6 +40,8 @@ public static class scnGamePatch } } + DecorationPatch.ProcessDecorations(); + var nonTile = RenderManager.Instance && RenderManager.Instance.stage >= RenderStage.Load && RenderManager.Instance.Configuration?.AdvancedConfig?.NonTile == true; diff --git a/UI/Window/RenderConfigWindow.cs b/UI/Window/RenderConfigWindow.cs index 4aae461..1ce74a2 100644 --- a/UI/Window/RenderConfigWindow.cs +++ b/UI/Window/RenderConfigWindow.cs @@ -142,6 +142,7 @@ internal static class RenderConfigWindow private static O5Toggle? _highResBloomCorrectionToggle; private static O5Toggle? _fixCustomFrameRateToggle; private static O5Toggle? _disableTileCullingToggle; + private static O5Toggle? _hideFailedDecorationsToggle; // Planet color overrides: the toggle enables the override, the picker holds the color. A disabled toggle stores // null in AdvancedConfig (no override is applied by PersistencePatch) and blocks the picker. @@ -824,6 +825,22 @@ internal static class RenderConfigWindow }, generateLabel: false ); + + // Hide Failed Decorations (AdvancedConfig.HideFailedDecorations): hides placeholder tree decorations when decoration images fail to load + _hideFailedDecorationsToggle = CreateToggleField( + ctx, CreateFieldSlot(advRight), + labelKey: "", + defaultValue: false, + value: config.AdvancedConfig.HideFailedDecorations, + toggleTextKey: "ui.render_config.hide_failed_decorations", + id: "toggle_hide_failed_decorations", + onChanged: val => + { + config.AdvancedConfig.HideFailedDecorations = val; + MarkConfigModified(); + }, + generateLabel: false + ); // 7-1. Planet Color Settings (Optional). // For each planet: the toggle turns the override on/off (off stores null and blocks/collapses the picker); @@ -1036,6 +1053,7 @@ internal static class RenderConfigWindow _highResBloomCorrectionToggle?.Set(config.AdvancedConfig.HighResBloomCorrection, false); _fixCustomFrameRateToggle?.Set(config.AdvancedConfig.FixCustomFrameRate, false); _disableTileCullingToggle?.Set(config.AdvancedConfig.DisableTileCulling, false); + _hideFailedDecorationsToggle?.Set(config.AdvancedConfig.HideFailedDecorations, false); // Planet colors: toggle on when an override is stored; the picker shows the stored color or the default and is // blocked without an override @@ -1220,6 +1238,7 @@ internal static class RenderConfigWindow if (_highResBloomCorrectionToggle != null) target.AdvancedConfig.HighResBloomCorrection = _highResBloomCorrectionToggle.Value; if (_fixCustomFrameRateToggle != null) target.AdvancedConfig.FixCustomFrameRate = _fixCustomFrameRateToggle.Value; if (_disableTileCullingToggle != null) target.AdvancedConfig.DisableTileCulling = _disableTileCullingToggle.Value; + if (_hideFailedDecorationsToggle != null) target.AdvancedConfig.HideFailedDecorations = _hideFailedDecorationsToggle.Value; // Planet Colors target.AdvancedConfig.firePlanetColor = (_firePlanetToggle?.Value == true && _firePlanetPicker != null)