diff --git a/Assets/_Project/Scripts/Client/Presentation/DynamicLightConfig.cs b/Assets/_Project/Scripts/Client/Presentation/DynamicLightConfig.cs index 46e4b4bcf..989dec089 100644 --- a/Assets/_Project/Scripts/Client/Presentation/DynamicLightConfig.cs +++ b/Assets/_Project/Scripts/Client/Presentation/DynamicLightConfig.cs @@ -19,6 +19,8 @@ namespace ProjectM.Client [Header("Projectile lights (pooled, follow every live projectile ghost)")] [Min(0)] public int MaxProjectileLights = 24; public Color ProjectileColor = new Color(0.45f, 0.85f, 1f, 1f); + [Tooltip("Un-owned (enemy) projectiles — Spitter spit reads toxic green vs the player's cyan.")] + public Color EnemyProjectileColor = new Color(0.55f, 1f, 0.35f, 1f); [Min(0f)] public float ProjectileIntensity = 2.6f; [Min(0.5f)] public float ProjectileRange = 7f; diff --git a/Assets/_Project/Scripts/Client/Presentation/DynamicLightSystem.cs b/Assets/_Project/Scripts/Client/Presentation/DynamicLightSystem.cs index 59e5cf612..73d8d52a9 100644 --- a/Assets/_Project/Scripts/Client/Presentation/DynamicLightSystem.cs +++ b/Assets/_Project/Scripts/Client/Presentation/DynamicLightSystem.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using ProjectM.Simulation; using Unity.Entities; +using Unity.NetCode; using Unity.Transforms; using UnityEngine; @@ -74,6 +75,7 @@ namespace ProjectM.Client Color projColor = cfg != null ? cfg.ProjectileColor : new Color(0.45f, 0.85f, 1f, 1f); float projIntensity = cfg != null ? cfg.ProjectileIntensity : 2.6f; float projRange = cfg != null ? cfg.ProjectileRange : 7f; + Color enemyProjColor = cfg != null ? cfg.EnemyProjectileColor : new Color(0.55f, 1f, 0.35f, 1f); _seen.Clear(); foreach (var (lt, entity) in SystemAPI.Query>().WithAll().WithEntityAccess()) { @@ -84,7 +86,8 @@ namespace ProjectM.Client light = Rent(); _projectileLights[entity] = light; } - light.color = projColor; + // Attack-distinctness: owned shots glow the player cyan; un-owned (enemy spit) glows toxic green. + light.color = EntityManager.HasComponent(entity) ? projColor : enemyProjColor; light.intensity = projIntensity; light.range = projRange; var p = lt.ValueRO.Position; diff --git a/Assets/_Project/Scripts/Client/Presentation/EnemyDangerTelegraphSystem.cs b/Assets/_Project/Scripts/Client/Presentation/EnemyDangerTelegraphSystem.cs index f4af6dac8..64e76bad3 100644 --- a/Assets/_Project/Scripts/Client/Presentation/EnemyDangerTelegraphSystem.cs +++ b/Assets/_Project/Scripts/Client/Presentation/EnemyDangerTelegraphSystem.cs @@ -35,11 +35,22 @@ namespace ProjectM.Client readonly Dictionary _prevWindup = new(); // self-detect the windup-onset edge (was the core _cache.Windup) readonly HashSet _enemySeen = new(); // ALL enemies this frame (prunes _pulseStart/_strikeBeeped/_prevWindup) AudioClip _strikeBeepClip; // near-impact "dodge NOW" beep + AudioClip[] _kindGrowls; // per-kind windup voice (attack distinctness) + Material[] _kindMats; // per-kind telegraph colours (attack distinctness) Entity _localPlayer = Entity.Null; protected override void OnCreate() { _strikeBeepClip = MakeClip("strike", 1150f, 1500f, 0.05f, 0.30f, noise: false); // near-impact beep + // Attack-distinctness: per-kind windup VOICES so kinds read by EAR at onset (0 grunt thud, + // 1 charger rising roar, 2 spitter wet hiss, 3 swarmer chitter). + _kindGrowls = new AudioClip[] + { + MakeClip("growl_grunt", 180f, 110f, 0.14f, 0.30f, noise: false), + MakeClip("growl_charger", 90f, 420f, 0.32f, 0.34f, noise: false), + MakeClip("growl_spitter", 1900f, 500f, 0.18f, 0.26f, noise: true), + MakeClip("growl_swarmer", 1500f, 2100f, 0.07f, 0.22f, noise: false), + }; } protected override void OnStartRunning() @@ -48,13 +59,32 @@ namespace ProjectM.Client _fxRoot = new GameObject("~EnemyDangerFX").transform; _dangerMat = MakeParticleMaterial(); _dangerMat.name = "EnemyDanger"; - _dangerMat.color = new Color(3.2f, 0.28f, 0.18f, 1f); // HDR red (per-zone intensity carried in vertex alpha) + _dangerMat.color = new Color(3.2f, 0.28f, 0.18f, 1f); // HDR red fallback (per-zone intensity in vertex alpha) + // Attack-distinctness: per-kind telegraph COLORS (0 grunt orange, 1 charger crimson, + // 2 spitter toxic green, 3 swarmer yellow; the boss shares the charger crimson — shapes differ). + _kindMats = new Material[4]; + var kindColors = new Color[] + { + new Color(3.2f, 0.85f, 0.15f, 1f), + new Color(3.2f, 0.18f, 0.12f, 1f), + new Color(0.55f, 2.9f, 0.35f, 1f), + new Color(2.9f, 2.3f, 0.22f, 1f), + }; + for (int i = 0; i < 4; i++) + { + _kindMats[i] = MakeParticleMaterial(); + _kindMats[i].name = "EnemyDanger_K" + i; + _kindMats[i].color = kindColors[i]; + } } protected override void OnDestroy() { if (_fxRoot != null) Object.Destroy(_fxRoot.gameObject); if (_dangerMat != null) Object.Destroy(_dangerMat); + if (_kindMats != null) + for (int i = 0; i < _kindMats.Length; i++) + if (_kindMats[i] != null) Object.Destroy(_kindMats[i]); foreach (var kv in _dangerZones) if (kv.Value != null) { var mf = kv.Value.GetComponent(); if (mf != null && mf.sharedMesh != null) Object.Destroy(mf.sharedMesh); } } @@ -107,7 +137,18 @@ namespace ProjectM.Client // transition of WindUpUntilTick arms the anticipation scale-pulse (Feature C). Requires a prior 0 // record so a mid-windup relevancy re-entry doesn't spuriously pulse (matches the old prev.Windup==0). bool hadPrev = _prevWindup.TryGetValue(entity, out var pw); - if (until != 0u && hadPrev && pw == 0u) _pulseStart[entity] = (float)SystemAPI.Time.ElapsedTime; + if (until != 0u && hadPrev && pw == 0u) + { + _pulseStart[entity] = (float)SystemAPI.Time.ElapsedTime; + // Attack-distinctness: the per-kind windup VOICE at onset (same gate family as the strike beep). + if (FeelConfig.StrikeBeepEnabled && _localPlayer != Entity.Null && _kindGrowls != null + && math.distancesq(xf.ValueRO.Position, localPos) <= FeelConfig.StrikeBeepMaxDistSq) + { + byte gk = tele.ValueRO.Kind; + if (gk < _kindGrowls.Length && _kindGrowls[gk] != null) + PlayClip(_kindGrowls[gk], (Vector3)xf.ValueRO.Position, 0.22f); + } + } _prevWindup[entity] = until; // Feature D: a committed Charger lunge keeps the cue ALIVE past windup (AttackWindup zeroes at commit). @@ -163,7 +204,8 @@ namespace ProjectM.Client go.transform.SetParent(_fxRoot, false); go.AddComponent().sharedMesh = new Mesh { name = "EnemyDanger" }; var mr = go.AddComponent(); - mr.sharedMaterial = _dangerMat; + mr.sharedMaterial = _kindMats != null && tele.ValueRO.Kind < _kindMats.Length && _kindMats[tele.ValueRO.Kind] != null + ? _kindMats[tele.ValueRO.Kind] : _dangerMat; // per-kind telegraph colour (kind is fixed per entity) mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; mr.receiveShadows = false; _dangerZones[entity] = go;