From ccc0ac40726e4dc8abc97b88bae9ce7c4fec272ebd31f6cf8c94f1b5b6170b9a Mon Sep 17 00:00:00 2001 From: koren <131921956+kkorenn@users.noreply.github.com> Date: Sat, 8 Aug 2026 22:22:40 +0900 Subject: [PATCH 1/4] non-tile addition --- Core/NonTileHider.cs | 166 ++++++++++++++++++++++++++ Patch/Game/scrPortalParticlesPatch.cs | 13 ++ 2 files changed, 179 insertions(+) create mode 100644 Core/NonTileHider.cs diff --git a/Core/NonTileHider.cs b/Core/NonTileHider.cs new file mode 100644 index 0000000..024d09c --- /dev/null +++ b/Core/NonTileHider.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using MelonLoader; +using UnityEngine; + +namespace ADOFAIRenderer.Core +{ + /// + /// 논타일(BGA) 모드: 렌더링 중 타일과 행성을 숨겨 배경/장식만 영상에 담습니다. + /// Quartz 에디터 모듈의 BGA 모드 로직을 이식한 것으로, 게임 코드가 렌더러를 + /// 다시 켜는 경우(행성 전환, 타일 등장 애니메이션 등)를 상쇄하기 위해 + /// 매 프레임 재적용(Reconcile)하는 방식을 사용합니다. + /// + public static class NonTileHider + { + private static bool _applied; + private static readonly Dictionary ParticleRendererCache = new(); + + /// + /// 논타일 렌더링 세션 여부. 레벨 로딩 중(OverrideLoading)에도 참이어야 + /// 마지막 타일의 종료 포탈 생성(scrFloor.SpawnPortalParticles)을 막을 수 있습니다. + /// + public static bool SuppressPortal => + RenderManager.Instance + && RenderManager.Instance.Configuration != null + && RenderManager.Instance.Configuration.AdvancedConfig.NonTile + && (RenderManager.IsActive || RenderManager.Instance.OverrideLoading); + + /// + /// 렌더링 중 매 프레임 호출해 타일/행성을 숨긴 상태로 유지합니다. + /// 게임의 타일 컬링이 카메라 근처 타일의 렌더러를 매번 다시 켜므로 + /// (첫/마지막 타일만 검사하는 방식으로는 감지 불가) 전체 타일을 매 프레임 재적용합니다. + /// Renderer.enabled는 값이 바뀔 때만 쓰므로 반복 비용은 순회뿐입니다. + /// + public static void Reconcile() + { + try + { + // 포탈 생성 자체는 Harmony 패치로 막지만, 패치 적용 전(또는 설정 확정 전)에 + // 이미 생성된 종료 포탈이 있을 수 있으므로 최초 적용 시 한 번 정리합니다. + if (!_applied) SetPortalsVisible(false); + + SetPlanetsVisible(false); + + var floors = ADOBase.lm ? ADOBase.lm.listFloors : null; + if (floors != null && floors.Count > 0) + SetFloorsVisible(floors, false); + + _applied = true; + } + catch (Exception e) + { + Melon.Logger.Warning($"[NonTile] 타일/행성 숨김 처리 실패: {e.Message}"); + } + } + + /// 렌더 종료 시 호출해 숨겼던 타일/행성을 복원합니다. + public static void Restore() + { + if (!_applied) return; + try + { + var floors = ADOBase.lm ? ADOBase.lm.listFloors : null; + if (floors != null) SetFloorsVisible(floors, true); + SetPlanetsVisible(true); + SetPortalsVisible(true); + } + catch (Exception e) + { + Melon.Logger.Warning($"[NonTile] 타일/행성 복원 실패: {e.Message}"); + } + _applied = false; + ParticleRendererCache.Clear(); + } + + private static void SetFloorsVisible(List floors, bool visible) + { + foreach (var floor in floors) + { + if (!floor) continue; + if (floor.floorRenderer != null) Set(floor.floorRenderer.renderer, visible); + Set(floor.legacyFloorSpriteRenderer, visible); + Set(floor.iconsprite, visible); + Set(floor.outlineSprite, visible); + Set(floor.topGlow, visible); + Set(floor.bottomGlow, visible); + Set(floor.multiplanetLine, visible); + if (floor.holdRenderer != null) Set(floor.holdRenderer.m_meshRenderer, visible); + + // 멀티플래닛 타일에 붙는 더미 행성도 함께 숨깁니다. + var floorDummies = floor.dummyPlanets; + if (floorDummies != null) + { + foreach (var dummy in floorDummies) + SetPlanetRendererVisible(dummy, visible); + } + } + } + + private static void SetPlanetsVisible(bool visible) + { + var pm = scrPlayerManager.instance; + var players = pm ? pm.players : null; + if (players != null) + { + foreach (var player in players) + { + var planets = player ? player.planetarySystem?.planetList : null; + if (planets == null) continue; + foreach (var planet in planets) + if (planet != null) SetPlanetRendererVisible(planet.planetRenderer, visible); + } + } + + var dummies = ADOBase.controller ? ADOBase.controller.dummyPlanets : null; + if (dummies != null) + { + foreach (var dummy in dummies) + SetPlanetRendererVisible(dummy, visible); + } + } + + private static void SetPlanetRendererVisible(PlanetRenderer? pr, bool visible) + { + if (pr == null) return; + Set(pr.sprite != null ? pr.sprite.meshRenderer : null, visible); + Set(ParticleRenderer(pr.coreParticles), visible); + Set(ParticleRenderer(pr.tailParticles), visible); + Set(ParticleRenderer(pr.tailParticlesCoop), visible); + Set(ParticleRenderer(pr.sparks), visible); + Set(pr.ring, visible); + Set(pr.glow, visible); + Set(pr.faceSprite, visible); + Set(pr.faceDetails, visible); + Set(pr.samuraiSprite, visible); + } + + // 마지막 타일의 종료 포탈(animatedPortalPrefab 인스턴스)은 타일의 자식 오브젝트라 + // scrFloor 필드 순회로는 잡히지 않으므로 컴포넌트 검색으로 처리합니다. + // 신규 생성은 scrFloorPortalPatch가 막으므로 최초 적용/복원 시에만 호출됩니다. + private static void SetPortalsVisible(bool visible) + { + foreach (var portal in UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None)) + { + if (portal == null) continue; + foreach (var r in portal.GetComponentsInChildren(true)) + Set(r, visible); + } + } + + private static Renderer? ParticleRenderer(ParticleSystem? ps) + { + if (ps == null) return null; + var id = ps.GetInstanceID(); + if (ParticleRendererCache.TryGetValue(id, out var cached) && cached != null) return cached; + var r = ps.GetComponent(); + if (r != null) ParticleRendererCache[id] = r; + return r; + } + + private static void Set(Renderer? r, bool visible) + { + if (r != null && r.enabled != visible) r.enabled = visible; + } + } +} diff --git a/Patch/Game/scrPortalParticlesPatch.cs b/Patch/Game/scrPortalParticlesPatch.cs index 1a07347..865a8a6 100644 --- a/Patch/Game/scrPortalParticlesPatch.cs +++ b/Patch/Game/scrPortalParticlesPatch.cs @@ -3,6 +3,19 @@ using UnityEngine; namespace ADOFAIRenderer.Patch.Game { + [HarmonyPatch(typeof(scrFloor))] + // ReSharper disable once InconsistentNaming + public static class scrFloorPortalPatch + { + [HarmonyPrefix] + [HarmonyPatch("SpawnPortalParticles")] + public static bool SkipPortalInNonTileRender() + { + // 논타일 렌더링 중에는 마지막 타일의 종료 포탈을 아예 생성하지 않습니다. + return !Core.NonTileHider.SuppressPortal; + } + } + [HarmonyPatch(typeof(scrPortalParticles))] // ReSharper disable once InconsistentNaming public static class scrPortalParticlesPatch -- 2.47.3 From 548b594eaf1cd3485599952eb254f0ed9ca19ee405a7374d03082bfc43420a14 Mon Sep 17 00:00:00 2001 From: koren <131921956+kkorenn@users.noreply.github.com> Date: Sat, 8 Aug 2026 22:22:53 +0900 Subject: [PATCH 2/4] Update FFmpegVideoBGPlayer.cs --- Util/FFmpegVideoBGPlayer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Util/FFmpegVideoBGPlayer.cs b/Util/FFmpegVideoBGPlayer.cs index 6cbf54e..2bd66ec 100644 --- a/Util/FFmpegVideoBGPlayer.cs +++ b/Util/FFmpegVideoBGPlayer.cs @@ -133,7 +133,9 @@ namespace ADOFAIRenderer.Util break; default: - throw new ArgumentOutOfRangeException(); + // NV12/YUV420P 외 포맷(10비트, 4:2:2 등)은 GPU 경로 미지원 — + // 예외를 던지면 초기화 전체가 실패하므로 CPU RGBA 폴백으로 진행합니다. + break; } } -- 2.47.3 From 23a6801aed4509d319c465806f8252bc4de3f80485a0478aaf29a209746574c1 Mon Sep 17 00:00:00 2001 From: koren <131921956+kkorenn@users.noreply.github.com> Date: Sat, 8 Aug 2026 22:23:05 +0900 Subject: [PATCH 3/4] Update scnEditorPatch.cs --- Patch/Game/scnEditorPatch.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Patch/Game/scnEditorPatch.cs b/Patch/Game/scnEditorPatch.cs index d73286e..6ee3a66 100644 --- a/Patch/Game/scnEditorPatch.cs +++ b/Patch/Game/scnEditorPatch.cs @@ -16,8 +16,12 @@ namespace ADOFAIRenderer.Patch.Game { if (RenderMod.IsFeatureDisabled) return; - var cloneTarget = __instance.buttonExit!.gameObject; - var buttonContainer = cloneTarget.transform.parent!; + // 장식용 UI 주입이므로 실패해도 scnEditor.Start를 중단시키면 안 됩니다. + if (!__instance || !__instance.buttonExit) return; + + var cloneTarget = __instance.buttonExit.gameObject; + var buttonContainer = cloneTarget.transform.parent; + if (buttonContainer == null) return; var buttonObj = Object.Instantiate(cloneTarget, buttonContainer)!; buttonObj.name = "buttonRender"; -- 2.47.3 From 27e48638582d229a06c6c6475ee81bd4221fa990ed7d0b8bea4d6b28268f4f2a Mon Sep 17 00:00:00 2001 From: koren <131921956+kkorenn@users.noreply.github.com> Date: Sat, 8 Aug 2026 22:23:11 +0900 Subject: [PATCH 4/4] Update scrPortalParticlesPatch.cs --- Patch/Game/scrPortalParticlesPatch.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Patch/Game/scrPortalParticlesPatch.cs b/Patch/Game/scrPortalParticlesPatch.cs index 865a8a6..1a18d9f 100644 --- a/Patch/Game/scrPortalParticlesPatch.cs +++ b/Patch/Game/scrPortalParticlesPatch.cs @@ -25,6 +25,9 @@ namespace ADOFAIRenderer.Patch.Game // ReSharper disable once InconsistentNaming public static void ForceScaledTime(scrPortalParticles __instance) { + // 렌더링 중에만 필요한 조치입니다. 일반 플레이에서 적용하면 + // 일시정지(timeScale=0) 시 바닐라가 unscaled로 만든 파티클까지 얼어붙습니다. + if (!Core.RenderManager.IsActive) return; if (__instance == null) return; var particleSystems = __instance.GetComponentsInChildren(true); -- 2.47.3