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; } } }