6379f5d897
Extract 7 sibling PresentationSystemGroup systems (client-only, observe-only), faithfully relocating methods+fields so behavior is preserved by construction: - HudSystem (2129->1588L): BoonModalHudSystem, RouteMapHudSystem, MetaShopHudSystem, ClassPrepPortalHudSystem — each owns its own runtime UIDocument (MenuUi.LoadPanelSettings + own sortingOrder + EnsureEventSystem), the proven EnemyMarkerSystem/OnboardingSystem pattern; no shared root, no new static bridge. - CombatFeedbackSystem (1300->914L): RoomPortalBeaconSystem, EnemyHealthBarSystem, EnemyDangerTelegraphSystem — each owns its FX-root + mats (via FeedbackFx), self-queries enemies + self-detects its edge (health-bar LastHp; danger _prevWindup), prunes its caches each frame. Verified: compiles clean (0 errors), 466/466 EditMode tests pass, Play world-creation clean (no ComponentSystemSorter cycle, no OnCreate exception, 0 console errors). NOTE: the final VISUAL smoke (panels appear at the right lifecycle; enemy health bars / danger telegraphs / portal beacon render; buttons live) needs a FOCUSED Play pass — the play-mode transition throttles while Unity is unfocused, so I could not drive live frames headlessly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
103 lines
5.9 KiB
C#
103 lines
5.9 KiB
C#
using ProjectM.Simulation;
|
|
using Unity.Entities;
|
|
using Unity.Mathematics;
|
|
using UnityEngine;
|
|
using static ProjectM.Client.FeedbackFx;
|
|
|
|
namespace ProjectM.Client
|
|
{
|
|
/// <summary>
|
|
/// DR-046 — client-only, observe-only presentation of the room-exit PORTAL made visible. A managed
|
|
/// <see cref="SystemBase"/> in <see cref="PresentationSystemGroup"/> that OBSERVES replicated <see cref="RunInfo"/>
|
|
/// and never mutates the sim. During the <see cref="RunLifecycle.RoomExplore"/> loot window it shows a glowing cyan
|
|
/// pillar (or the authored <see cref="VFXConfig.Portal"/> 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 <see cref="RegionMath.ExpeditionPortalPos"/> 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.
|
|
/// </summary>
|
|
[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<RunInfo>(out var ri) && ri.Lifecycle == RunLifecycle.RoomExplore;
|
|
if (!inExplore || !SystemAPI.TryGetSingleton<BaseAnchor>(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<Collider>(); if (col != null) Object.Destroy(col); // cosmetic only
|
|
_portalBeacon.transform.SetParent(_fxRoot, false);
|
|
var mr = _portalBeacon.GetComponent<MeshRenderer>();
|
|
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)
|
|
}
|
|
}
|
|
}
|