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)