Phase 1 A/C/D: void room fixed, authored portal + spawn FX, verbs finally taught
SLICE A - rooms become places: ExpeditionBiome duplicated to sub-slot 1 (X+1500) - ground, 205 decor children, rim cliffs, ambience; collider ring + 3 landmark colliders duplicated in the subscene; NEW in-room cover at BOTH slots (4 collidable rocks each: Environment-layer box colliders in the subscene + matching Synty rock meshes in Game.unity, placed clear of the landing point, portal spot, spawn ring and boss spawn). Half of all rooms previously played on a literal void. SLICE C - owned FX wired: VFXConfig gains Portal + EnemySpawn slots (FX_Portal_Round_01 / FX_Dust_Big_01 from PolygonParticleFX); the portal beacon prefers the authored effect (procedural pillar stays as fallback); enemies get a spawn-emerge cue on the ghost add-edge (primed-scan guard skips the connect flood). SLICE D (teaching) - the two undiscoverable verbs are now taught: dash on the Move step, class ability + attack on the Rooms step (scheme-aware glyphs); How-to-Play Controls adds Class ability + Dash rows and fixes the wrong RT glyph; Loop step 1 mentions class pick + prep; the stale '50 starting Ore' now interpolates Tuning.StartingOre. 456/456 EditMode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,8 @@ namespace ProjectM.Client
|
||||
struct FxCache { public float Hp; public float MaxHp; public float3 Pos; public bool IsEnemy; public uint Windup; }
|
||||
|
||||
readonly Dictionary<Entity, FxCache> _cache = new();
|
||||
bool _scanPrimed; // Phase 1: first health-scan completed -> new cache entries are true spawns, not the connect flood
|
||||
GameObject _portalFx; // Phase 1: authored portal effect (VFXConfig.Portal) replacing the procedural pillar when wired
|
||||
readonly HashSet<Entity> _seen = new();
|
||||
readonly List<Entity> _stale = new();
|
||||
readonly List<FloatingNumber> _numbers = new();
|
||||
@@ -294,9 +296,19 @@ namespace ProjectM.Client
|
||||
PrototypeCameraRig.AddShake(isLocalPlayer ? FeelConfig.PlayerDeathShake : FeelConfig.RemotePlayerDeathShake);
|
||||
}
|
||||
}
|
||||
else if (isEnemy && _scanPrimed && cur >= health.ValueRO.Max - 0.001f)
|
||||
{
|
||||
// Phase 1 spawn-emerge cue: FIRST sighting of a fresh (full-HP) enemy ghost = its spawn
|
||||
// (rooms drip-spawn AFTER the player lands, so the add-edge IS the spawn moment). The primed
|
||||
// guard skips the initial-connect flood; a relevancy re-entry of an undamaged enemy may
|
||||
// rarely re-cue - accepted juice noise, never gameplay.
|
||||
Burst(_hitFx, cfg != null ? cfg.EnemySpawn : null, (Vector3)p + Vector3.up * 0.15f, 8);
|
||||
PlayClip(_telegraphClip, (Vector3)p, 0.25f);
|
||||
}
|
||||
|
||||
_cache[entity] = new FxCache { Hp = cur, MaxHp = health.ValueRO.Max, Pos = p, IsEnemy = isEnemy, Windup = windup };
|
||||
}
|
||||
_scanPrimed = true; // Phase 1: everything cached after the first scan; later add-edges are true spawns
|
||||
|
||||
// Prune despawned ghosts. A Husk that vanished was killed -> death VFX at its last position.
|
||||
if (_cache.Count != _seen.Count)
|
||||
@@ -1003,9 +1015,25 @@ void TriggerSlash(Vector3 pos, float2 facing, float range, float halfAngle, int
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user