From f1d56f5f9d14df778bf7cc676b588d4eeabd9cb58587835502bc87a0cf9ac57c Mon Sep 17 00:00:00 2001 From: copecone Date: Thu, 8 Oct 2026 18:32:44 +0900 Subject: [PATCH] Add Planet Color Config --- Localization/Locale/en.json | 4 + Localization/Locale/ko.json | 4 + Patch/Game/PersistencePatch.cs | 16 ++ Patch/Game/PlanetRendererPatch.cs | 30 ---- Patch/Game/PlanetarySystemPatch.cs | 35 +++++ UI/Window/RenderConfigWindow.cs | 245 +++++++++++++++++++++++++++-- 6 files changed, 291 insertions(+), 43 deletions(-) delete mode 100644 Patch/Game/PlanetRendererPatch.cs create mode 100644 Patch/Game/PlanetarySystemPatch.cs diff --git a/Localization/Locale/en.json b/Localization/Locale/en.json index 089dda0..e3af036 100644 --- a/Localization/Locale/en.json +++ b/Localization/Locale/en.json @@ -32,5 +32,9 @@ "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", "ui.render_config.invalid_path": "Output directory path is invalid:\n{0}", + "ui.render_config.planet_colors": "Planet Colors (Optional)", + "ui.render_config.fire_planet": "Fire Planet", + "ui.render_config.ice_planet": "Ice Planet", + "ui.render_config.wind_planet": "Wind Planet", "ui.render_config.expired": "(Expired)" } diff --git a/Localization/Locale/ko.json b/Localization/Locale/ko.json index 4bfcb93..1ac92e3 100644 --- a/Localization/Locale/ko.json +++ b/Localization/Locale/ko.json @@ -32,5 +32,9 @@ "ui.render_config.validation_title": "설정 오류", "ui.render_config.validation_error": "입력값을 확인해 주세요:\n- 너비와 높이는 0보다 큰 정수여야 합니다.\n- 렌더 FPS와 영상 FPS는 0보다 커야 합니다.\n- 종료 후 대기 시간은 0 이상이어야 합니다.", "ui.render_config.invalid_path": "출력 디렉토리 경로가 올바르지 않습니다:\n{0}", + "ui.render_config.planet_colors": "행성 색상 설정 (선택 사항)", + "ui.render_config.fire_planet": "불 행성", + "ui.render_config.ice_planet": "얼음 행성", + "ui.render_config.wind_planet": "바람 행성", "ui.render_config.expired": "(만료됨)" } diff --git a/Patch/Game/PersistencePatch.cs b/Patch/Game/PersistencePatch.cs index 72106d5..e27f468 100644 --- a/Patch/Game/PersistencePatch.cs +++ b/Patch/Game/PersistencePatch.cs @@ -18,4 +18,20 @@ public static class PersistencePatch __result = SkipIntroBehavior.Off; } } + + [HarmonyPostfix] + [HarmonyPatch("GetPlayerColor")] + // ReSharper disable once InconsistentNaming + public static void OverridePlayerColor(bool red, ref PlanetColor __result) + { + if (!RenderManager.Instance || RenderManager.Instance.stage == RenderStage.None) return; + var advConf = RenderManager.Instance.Configuration.AdvancedConfig; + + __result = red switch + { + true when advConf.firePlanetColor.HasValue => new PlanetColor(advConf.firePlanetColor.Value), + false when advConf.icePlanetColor.HasValue => new PlanetColor(advConf.icePlanetColor.Value), + _ => __result + }; + } } \ No newline at end of file diff --git a/Patch/Game/PlanetRendererPatch.cs b/Patch/Game/PlanetRendererPatch.cs deleted file mode 100644 index dee017d..0000000 --- a/Patch/Game/PlanetRendererPatch.cs +++ /dev/null @@ -1,30 +0,0 @@ -using ADOFAIRenderer.Core; -using HarmonyLib; -using UnityEngine; - -namespace ADOFAIRenderer.Patch.Game; - -[HarmonyPatch(typeof(PlanetRenderer))] -public static class PlanetRendererPatch -{ - [HarmonyPrefix] - [HarmonyPatch("SetColor")] - // ReSharper disable once InconsistentNaming - private static void OverrideColor(PlanetRenderer __instance, ref PlanetColor planetColor) - { - if (RenderManager.Instance.stage == RenderStage.None) return; - - var advConf = RenderManager.Instance.Configuration.AdvancedConfig; - ApplyColor(ref planetColor, scrController.instance.planetRed, advConf.firePlanetColor); - ApplyColor(ref planetColor, scrController.instance.planetBlue, advConf.icePlanetColor); - ApplyColor(ref planetColor, scrController.instance.planetGreen, advConf.windPlanetColor); - - return; - - void ApplyColor(ref PlanetColor planetColor, scrPlanet? targetPlanet, Color? customColor) - { - if (targetPlanet != null && __instance == targetPlanet.planetRenderer && customColor != null) - planetColor = new PlanetColor(customColor.Value); - } - } -} \ No newline at end of file diff --git a/Patch/Game/PlanetarySystemPatch.cs b/Patch/Game/PlanetarySystemPatch.cs new file mode 100644 index 0000000..1189c27 --- /dev/null +++ b/Patch/Game/PlanetarySystemPatch.cs @@ -0,0 +1,35 @@ +using ADOFAIRenderer.Core; +using HarmonyLib; + +namespace ADOFAIRenderer.Patch.Game; + +[HarmonyPatch(typeof(PlanetarySystem))] +public static class PlanetarySystemPatch +{ + [HarmonyPostfix] + [HarmonyPatch("Init")] + // ReSharper disable once InconsistentNaming + public static void OnInit(PlanetarySystem __instance) + { + ApplyWindPlanetColor(__instance); + } + + [HarmonyPostfix] + [HarmonyPatch("ApplyMultiplanetColors")] + // ReSharper disable once InconsistentNaming + public static void OnApplyMultiplanetColors(PlanetarySystem __instance) + { + ApplyWindPlanetColor(__instance); + } + + private static void ApplyWindPlanetColor(PlanetarySystem planetarySystem) + { + if (RenderManager.Instance == null || RenderManager.Instance.stage == RenderStage.None) return; + + var advConf = RenderManager.Instance.Configuration.AdvancedConfig; + if (advConf.windPlanetColor.HasValue && planetarySystem.planetGreen) + { + planetarySystem.planetGreen.planetRenderer.SetColor(new PlanetColor(advConf.windPlanetColor.Value)); + } + } +} diff --git a/UI/Window/RenderConfigWindow.cs b/UI/Window/RenderConfigWindow.cs index d56d8dc..cf5d5e7 100644 --- a/UI/Window/RenderConfigWindow.cs +++ b/UI/Window/RenderConfigWindow.cs @@ -8,8 +8,6 @@ using ADOFAIRenderer.Core.Data; using ADOFAIRenderer.Data; using ADOFAIRenderer.Localization; using ADOFAIRenderer.Util; -using ADOFAIRenderer.Util.FFmpeg; -using HarmonyLib; using MelonLoader; using O5Kit.Control; using O5Kit.Core; @@ -60,6 +58,17 @@ internal static class RenderConfigWindow private static O5Toggle? _processAudioToggle; private static O5Toggle? _nonTileToggle; + private static O5Toggle? _firePlanetToggle; + private static O5ColorPicker? _firePlanetPicker; + private static O5Toggle? _icePlanetToggle; + private static O5ColorPicker? _icePlanetPicker; + private static O5Toggle? _windPlanetToggle; + private static O5ColorPicker? _windPlanetPicker; + + private static readonly Color DefaultFirePlanetColor = new(1f, 0f, 0f, 1f); // #FF0000 + private static readonly Color DefaultIcePlanetColor = new(0f, 0f, 1f, 1f); // #0000FF + private static readonly Color DefaultWindPlanetColor = new(0.3f, 0.7f, 0f, 1f); // #4DB300 + public static void CreateWindow() { if (_configWindow == null) @@ -74,6 +83,7 @@ internal static class RenderConfigWindow _configWindow.CloseRequested += win => { + CloseAllPickers(); win.Rect.gameObject.SetActive(false); }; @@ -449,7 +459,107 @@ internal static class RenderConfigWindow generateLabel: false ); - // TODO: Fire Planet, Ice Planet, Wind Planet Color Settings (Each Optional) + // 7-1. Planet Color Settings (Optional) + var planetColorsLabel = O5Factory.ControlText(ctx, advContent, 13f, noPad: true); + planetColorsLabel.text = LocalizationManager.Get("ui.render_config.planet_colors"); + planetColorsLabel.color = ctx.Theme.TextDim; + // ReSharper disable once InconsistentNaming + var planetLabelLE = planetColorsLabel.gameObject.AddComponent(); + planetLabelLE.preferredHeight = 18f; + planetLabelLE.minHeight = 18f; + + var planetCols = CreateMultiColumnPanel(advContent, 3); + var (fireCol, iceCol, windCol) = (planetCols[0], planetCols[1], planetCols[2]); + + var fireSlot = CreateFieldSlot(fireCol); + _firePlanetToggle = CreateToggleField( + ctx, fireSlot, "", + false, config.AdvancedConfig.firePlanetColor.HasValue, + LocalizationManager.Get("ui.render_config.fire_planet"), + "toggle_fire_planet", + val => + { + _firePlanetPicker?.SetBlocked(!val); + config.AdvancedConfig.firePlanetColor = val ? _firePlanetPicker?.Value : null; + if (!val) _firePlanetPicker?.SetExpanded(false); + }, + generateLabel: false + ); + _firePlanetPicker = CreateColorPickerField( + ctx, fireSlot, + DefaultFirePlanetColor, + config.AdvancedConfig.firePlanetColor ?? DefaultFirePlanetColor, + "cp_fire_planet", + val => + { + if (_firePlanetToggle?.Value == true) + config.AdvancedConfig.firePlanetColor = val; + } + ); + if (!config.AdvancedConfig.firePlanetColor.HasValue) + { + _firePlanetPicker.SetBlocked(true, true); + } + + var iceSlot = CreateFieldSlot(iceCol); + _icePlanetToggle = CreateToggleField( + ctx, iceSlot, "", + false, config.AdvancedConfig.icePlanetColor.HasValue, + LocalizationManager.Get("ui.render_config.ice_planet"), + "toggle_ice_planet", + val => + { + _icePlanetPicker?.SetBlocked(!val); + config.AdvancedConfig.icePlanetColor = val ? _icePlanetPicker?.Value : null; + if (!val) _icePlanetPicker?.SetExpanded(false); + }, + generateLabel: false + ); + _icePlanetPicker = CreateColorPickerField( + ctx, iceSlot, + DefaultIcePlanetColor, + config.AdvancedConfig.icePlanetColor ?? DefaultIcePlanetColor, + "cp_ice_planet", + val => + { + if (_icePlanetToggle?.Value == true) + config.AdvancedConfig.icePlanetColor = val; + } + ); + if (!config.AdvancedConfig.icePlanetColor.HasValue) + { + _icePlanetPicker.SetBlocked(true, true); + } + + var windSlot = CreateFieldSlot(windCol); + _windPlanetToggle = CreateToggleField( + ctx, windSlot, "", + false, config.AdvancedConfig.windPlanetColor.HasValue, + LocalizationManager.Get("ui.render_config.wind_planet"), + "toggle_wind_planet", + val => + { + _windPlanetPicker?.SetBlocked(!val); + config.AdvancedConfig.windPlanetColor = val ? _windPlanetPicker?.Value : null; + if (!val) _windPlanetPicker?.SetExpanded(false); + }, + generateLabel: false + ); + _windPlanetPicker = CreateColorPickerField( + ctx, windSlot, + DefaultWindPlanetColor, + config.AdvancedConfig.windPlanetColor ?? DefaultWindPlanetColor, + "cp_wind_planet", + val => + { + if (_windPlanetToggle?.Value == true) + config.AdvancedConfig.windPlanetColor = val; + } + ); + if (!config.AdvancedConfig.windPlanetColor.HasValue) + { + _windPlanetPicker.SetBlocked(true, true); + } // 8. Go Render Button O5Factory.Button( @@ -519,6 +629,21 @@ internal static class RenderConfigWindow _randomSeedInput?.Set(config.AdvancedConfig.RandomSeed?.ToString() ?? "", false); _processAudioToggle?.Set(config.AdvancedConfig.ProcessAudio, false); _nonTileToggle?.Set(config.AdvancedConfig.NonTile, false); + + var fireColor = config.AdvancedConfig.firePlanetColor; + _firePlanetToggle?.Set(fireColor.HasValue, false); + _firePlanetPicker?.Set(fireColor ?? DefaultFirePlanetColor, false); + _firePlanetPicker?.SetBlocked(!fireColor.HasValue, true); + + var iceColor = config.AdvancedConfig.icePlanetColor; + _icePlanetToggle?.Set(iceColor.HasValue, false); + _icePlanetPicker?.Set(iceColor ?? DefaultIcePlanetColor, false); + _icePlanetPicker?.SetBlocked(!iceColor.HasValue, true); + + var windColor = config.AdvancedConfig.windPlanetColor; + _windPlanetToggle?.Set(windColor.HasValue, false); + _windPlanetPicker?.Set(windColor ?? DefaultWindPlanetColor, false); + _windPlanetPicker?.SetBlocked(!windColor.HasValue, true); UpdateVideoCodecUI(config.VideoConfig.Codec, config); UpdateRateControlUI(config.VideoConfig.RateControlMode, noAnimate: true); @@ -555,6 +680,17 @@ internal static class RenderConfigWindow } } + // Sync Planet Colors + config.AdvancedConfig.firePlanetColor = (_firePlanetToggle?.Value == true && _firePlanetPicker != null) + ? _firePlanetPicker.Value + : null; + config.AdvancedConfig.icePlanetColor = (_icePlanetToggle?.Value == true && _icePlanetPicker != null) + ? _icePlanetPicker.Value + : null; + config.AdvancedConfig.windPlanetColor = (_windPlanetToggle?.Value == true && _windPlanetPicker != null) + ? _windPlanetPicker.Value + : null; + // Sync Output Directory if (_outputDirInput != null) { @@ -599,16 +735,18 @@ internal static class RenderConfigWindow ); } - // 카드(또는 상단 섹션)당 하나의 좌/우 컨테이너를 만들어 - // 가로 한 줄 두 필드 사이 중앙이 행마다 어긋나지 않도록 한다. - // - 패널 1개 = 중앙선 1개 (행마다 HLG를 만들던 기존 방식의 P1-P2 어긋남 방지) - // - 좌/우 컬럼은 preferredWidth=0 + flexibleWidth=1 로 항상 50:50 + // 카드(또는 상단 섹션)당 하나의 다열 컨테이너를 만들어 + // 가로 한 줄 각 필드 사이 중앙이 행마다 어긋나지 않도록 한다. + // - 패널 1개 = 균등 분할 (행마다 HLG를 만들던 기존 방식의 어긋남 방지) + // - 각 컬럼은 preferredWidth=0 + flexibleWidth=1 로 항상 1:1:...:1 균등 분할 // - 각 필드는 Field 슬롯(label 18 + 간격 3 + 컨트롤 50)으로 높이를 고정해 // 좌/우 스택을 써도 행 단위 Y 정렬이 유지되도록 한다. - private static (RectTransform left, RectTransform right) CreateTwoColumnPanel( - Transform parent, float spacing = 8f, float columnSpacing = 8f) + private static RectTransform[] CreateMultiColumnPanel( + Transform parent, int columnCount, float spacing = 8f, float columnSpacing = 8f) { - var panelObj = new GameObject("TwoColPanel"); + if (columnCount < 1) throw new ArgumentOutOfRangeException(nameof(columnCount), "Column count must be at least 1."); + + var panelObj = new GameObject($"{columnCount}ColPanel"); panelObj.transform.SetParent(parent, false); panelObj.AddComponent(); @@ -626,10 +764,20 @@ internal static class RenderConfigWindow var panelLE = panelObj.AddComponent(); panelLE.flexibleWidth = 1f; - var left = CreateColumn(panelObj.transform, "LeftCol", columnSpacing); - var right = CreateColumn(panelObj.transform, "RightCol", columnSpacing); + var columns = new RectTransform[columnCount]; + for (var i = 0; i < columnCount; i++) + { + columns[i] = CreateColumn(panelObj.transform, $"Col{i + 1}", columnSpacing); + } - return (left, right); + return columns; + } + + private static (RectTransform left, RectTransform right) CreateTwoColumnPanel( + Transform parent, float spacing = 8f, float columnSpacing = 8f) + { + var cols = CreateMultiColumnPanel(parent, 2, spacing, columnSpacing); + return (cols[0], cols[1]); } private static RectTransform CreateColumn(Transform parent, string name, float spacing) @@ -895,8 +1043,79 @@ internal static class RenderConfigWindow ); } + private static O5ColorPicker CreateColorPickerField( + O5Context ctx, + Transform colParent, + Color? defaultValue, + Color value, + string id, + Action? onChanged = null) + { + var cpContainer = new GameObject($"CP_{id}"); + cpContainer.transform.SetParent(colParent, false); + + // ReSharper disable once InconsistentNaming + var cpLE = cpContainer.AddComponent(); + var height = ctx.Theme.ControlHeightFor(ctx.Theme.FontSizeBody); + cpLE.preferredHeight = height; + cpLE.minHeight = height; + + var canvasRect = RendererUIManager.Instance.WindowManager.Canvas.Root.GetComponent(); + + var picker = O5Factory.ColorPicker( + ctx: ctx, + parent: cpContainer.transform, + canvasRect: canvasRect, + canvasCamera: null, + defaultValue: defaultValue, + value: value, + onChanged: onChanged, + onComplete: onChanged, + id: id + ); + + // 3열 등 다열 레이아웃 환경에 맞춰 O5ColorPicker 헤더 내부 비율 및 폰트 크기 조정 + // O5Kit 기본값(Preview: 0~0.5, HexInput: 0.62~1.0, fontSize: 19)은 너비가 좁을 때 + // Hex 문자열(8자리 RGBA) 앞부분이 잘려 보이는 현상이 발생하므로 공간을 재분배합니다. + if (picker.Rect.Find("Header") is RectTransform header) + { + if (header.Find("Preview") is RectTransform preview) + { + preview.anchorMax = new Vector2(0.28f, 1f); + } + + if (header.Find("ToggleArea") is RectTransform toggleArea) + { + toggleArea.anchorMax = new Vector2(0.30f, 1f); + } + + if (header.Find("HexInput") is RectTransform hexInput) + { + hexInput.anchorMin = new Vector2(0.32f, 0f); + var tmpInput = hexInput.GetComponentInChildren(); + if (tmpInput != null && tmpInput.textComponent != null) + { + tmpInput.textComponent.fontSize = 15f; + } + } + } + + return picker; + } + private static void SetWindowActive(bool enabled) { + if (!enabled) + { + CloseAllPickers(); + } _configWindow?.Rect.gameObject.SetActive(enabled); } + + private static void CloseAllPickers() + { + _firePlanetPicker?.SetExpanded(false, true); + _icePlanetPicker?.SetExpanded(false, true); + _windPlanetPicker?.SetExpanded(false, true); + } } \ No newline at end of file