using ProjectM.Simulation;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using static ProjectM.Client.FeedbackFx;
namespace ProjectM.Client
{
///
/// DR-046 — client-only, observe-only presentation of the room-exit PORTAL made visible. A managed
/// in that OBSERVES replicated
/// and never mutates the sim. During the loot window it shows a glowing cyan
/// pillar (or the authored effect when wired) at the client-derived portal position
/// so the player has an unmistakable "go here to continue" target; hidden whenever the run isn't in RoomExplore.
/// Position resolves through the SAME authority the HUD prompt uses, so
/// the beacon and the "PRESS E" range always agree. Extracted from CombatFeedbackSystem (owns its own FX-root +
/// beacon material); no Entity-keyed cache.
///
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[UpdateInGroup(typeof(PresentationSystemGroup))]
public partial class RoomPortalBeaconSystem : SystemBase
{
Transform _fxRoot;
GameObject _portalFx; // Phase 1: authored portal effect (VFXConfig.Portal) replacing the procedural pillar when wired
Material _portalMat; // DR-046: room-exit portal beacon glow (mutated for the pulse; beacon-only mat)
GameObject _portalBeacon; // DR-046: pooled world-space "go here" pillar, shown only during RoomExplore
protected override void OnStartRunning()
{
if (_fxRoot != null) return;
_fxRoot = new GameObject("~RoomPortalBeaconFX").transform;
_portalMat = MakeParticleMaterial();
_portalMat.name = "RoomPortal";
_portalMat.color = new Color(0.25f, 1.2f, 1.55f, 0.85f); // DR-046: HDR cyan portal glow (Phase 0: tamed — 2.6/3.4 bloomed to a white blob)
}
protected override void OnDestroy()
{
if (_fxRoot != null) Object.Destroy(_fxRoot.gameObject);
if (_portalMat != null) Object.Destroy(_portalMat);
}
protected override void OnUpdate()
{
UpdatePortalBeacon();
}
// DR-046: the room-exit PORTAL made VISIBLE. During the RoomExplore loot window a glowing cyan pillar marks the
// client-derived portal position so the player has an unmistakable "go here to continue" target — the HUD prompt
// alone left the exit invisible, so players waited out the ~30s grace timeout ("nothing happens for a while").
// Client-only, observe-only; one pooled GameObject, hidden whenever the run isn't in RoomExplore. Position
// resolves through the SAME RegionMath.ExpeditionPortalPos authority the HUD prompt uses -> beacon + "PRESS E"
// range always agree.
void UpdatePortalBeacon()
{
if (_fxRoot == null || _portalMat == null) return;
bool inExplore = SystemAPI.TryGetSingleton(out var ri) && ri.Lifecycle == RunLifecycle.RoomExplore;
if (!inExplore || !SystemAPI.TryGetSingleton(out var anchor))
{
if (_portalBeacon != null && _portalBeacon.activeSelf) _portalBeacon.SetActive(false);
if (_portalFx != null && _portalFx.activeSelf) _portalFx.SetActive(false);
return;
}
float3 pos = RegionMath.ExpeditionPortalPos(BaseGridMath.PlotCenter(anchor), (byte)(ri.CurrentRoom & 1));
// Phase 1: prefer the authored portal effect (VFXConfig.Portal, PolygonParticleFX) over the
// procedural pillar; the pillar remains the asset-free fallback.
var vfx = VFXConfig.Instance;
if (vfx != null && vfx.Portal != null)
{
if (_portalFx == null)
{
_portalFx = Object.Instantiate(vfx.Portal, _fxRoot, false);
_portalFx.name = "~RoomPortalFx";
}
_portalFx.transform.position = new Vector3(pos.x, 0f, pos.z); // terrain y=0 (pos.y is the capsule plane)
if (!_portalFx.activeSelf) _portalFx.SetActive(true);
if (_portalBeacon != null && _portalBeacon.activeSelf) _portalBeacon.SetActive(false);
return;
}
if (_portalBeacon == null)
{
_portalBeacon = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
_portalBeacon.name = "~RoomPortalBeacon";
var col = _portalBeacon.GetComponent(); if (col != null) Object.Destroy(col); // cosmetic only
_portalBeacon.transform.SetParent(_fxRoot, false);
var mr = _portalBeacon.GetComponent();
mr.sharedMaterial = _portalMat;
mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
mr.receiveShadows = false;
}
if (!_portalBeacon.activeSelf) _portalBeacon.SetActive(true);
float t = (float)SystemAPI.Time.ElapsedTime;
float breathe = 0.5f + 0.5f * math.sin(t * 3.5f);
var tr = _portalBeacon.transform;
// Cylinder is 2u tall in local space -> scale.y=2.2 gives a 4.4u pillar; lift the centre so the base sits
// on the TERRAIN (y=0) — pos.y is the CC capsule-center plane (GridOrigin.y=1), 1 u above the ground.
tr.position = new Vector3(pos.x, 2.2f, pos.z);
tr.localScale = new Vector3(0.9f + 0.12f * breathe, 2.2f, 0.9f + 0.12f * breathe);
_portalMat.color = new Color(0.25f, 1.2f, 1.55f, 0.45f + 0.3f * breathe); // glow throb (beacon-only mat; Phase 0: tamed + slimmed — the fat 6u pillar bloomed to a white egg swallowing the prompt)
}
}
}