Further Tests & Progress

This commit is contained in:
2026-06-04 11:35:57 -07:00
parent 5c11ff4fad
commit 51401d2c2b
65 changed files with 2784 additions and 45 deletions
@@ -35,7 +35,7 @@ namespace ProjectM.Client
[UpdateInGroup(typeof(PresentationSystemGroup))]
public partial class CombatFeedbackSystem : SystemBase
{
struct FxCache { public float Hp; public float3 Pos; public bool IsEnemy; }
struct FxCache { public float Hp; public float3 Pos; public bool IsEnemy; public uint Windup; }
readonly Dictionary<Entity, FxCache> _cache = new();
readonly HashSet<Entity> _seen = new();
@@ -55,6 +55,7 @@ namespace ProjectM.Client
AudioClip _hitClip;
AudioClip _deathClip;
AudioClip _fireClip;
AudioClip _telegraphClip;
Entity _localPlayer = Entity.Null;
uint _lastLocalFireTick;
@@ -70,6 +71,7 @@ namespace ProjectM.Client
_hitClip = MakeClip("husk_hit", 640f, 180f, 0.10f, 0.5f, noise: true);
_deathClip = MakeClip("husk_death", 320f, 50f, 0.34f, 0.55f, noise: false);
_fireClip = MakeClip("fire", 880f, 1500f, 0.07f, 0.30f, noise: false);
_telegraphClip = MakeClip("telegraph", 680f, 1020f, 0.12f, 0.35f, noise: false);
}
protected override void OnStartRunning()
@@ -101,6 +103,7 @@ namespace ProjectM.Client
// Make sure predicted/physics jobs writing these are done before this main-thread read.
EntityManager.CompleteDependencyBeforeRO<Health>();
EntityManager.CompleteDependencyBeforeRO<LocalTransform>();
EntityManager.CompleteDependencyBeforeRO<AttackWindup>();
// Resolve the local player (for hit colouring + fire feedback).
_localPlayer = Entity.Null;
@@ -121,28 +124,45 @@ namespace ProjectM.Client
float cur = health.ValueRO.Current;
float3 p = xf.ValueRO.Position;
bool isEnemy = SystemAPI.HasComponent<EnemyTag>(entity);
uint windup = isEnemy && SystemAPI.HasComponent<AttackWindup>(entity) ? SystemAPI.GetComponent<AttackWindup>(entity).WindUpUntilTick : 0u;
bool isLocalPlayer = entity == _localPlayer;
if (_cache.TryGetValue(entity, out var prev))
{
if (isEnemy && windup != 0 && prev.Windup == 0)
{
// Attack telegraph: the wind-up just began -> warn the player ~0.3s before the strike lands.
Burst(_hitFx, null, (Vector3)p + Vector3.up * 1.2f, 6);
PlayClip(_telegraphClip, (Vector3)p, 0.5f);
}
if (cur < prev.Hp - 0.001f)
{
SpawnNumber(prev.Hp - cur, (Vector3)p, isLocalPlayer, cam);
Burst(_hitFx, cfg != null ? cfg.Hit : null, (Vector3)p + Vector3.up * 0.8f, 10);
PlayClip(_hitClip, (Vector3)p, 0.7f);
PrototypeCameraRig.AddShake(isLocalPlayer ? 0.32f : 0.10f);
Burst(_hitFx, cfg != null ? cfg.Hit : null, (Vector3)p + Vector3.up * 0.8f, FeelConfig.HitBurstCount);
PlayClip(_hitClip, (Vector3)p, FeelConfig.HitSfxVolume);
PrototypeCameraRig.AddShake(isLocalPlayer ? FeelConfig.HitShakeLocal : FeelConfig.HitShakeRemote);
if (isLocalPlayer) PrototypeCameraRig.PunchFov(FeelConfig.HitStopFovKick, FeelConfig.HitStopDurationMs);
}
// Respawn recovery: the LOCAL player's Health rising from <=0 back to positive. No healing
// mechanic exists, so a 0 -> positive edge is unambiguously a respawn (observer-only).
if (isLocalPlayer && FeelConfig.RespawnShimmerEnabled && cur > prev.Hp + 0.001f && prev.Hp <= 0f)
{
Burst(_muzzleFx, null, (Vector3)p + Vector3.up * 0.6f, FeelConfig.RespawnShimmerBurst);
PrototypeCameraRig.AddShake(FeelConfig.RespawnShimmerShake);
}
// Player death (players don't despawn — they respawn; Husk death is handled on prune).
if (!isEnemy && cur <= 0f && prev.Hp > 0f)
{
Burst(_deathFx, PlayerDeathPrefab(cfg), (Vector3)p + Vector3.up * 0.5f, 28);
Burst(_deathFx, PlayerDeathPrefab(cfg), (Vector3)p + Vector3.up * 0.5f, FeelConfig.DeathBurstCount);
PlayClip(_deathClip, (Vector3)p, 0.7f);
PrototypeCameraRig.AddShake(isLocalPlayer ? 0.5f : 0.25f);
PrototypeCameraRig.AddShake(isLocalPlayer ? FeelConfig.PlayerDeathShake : FeelConfig.RemotePlayerDeathShake);
}
}
_cache[entity] = new FxCache { Hp = cur, Pos = p, IsEnemy = isEnemy };
_cache[entity] = new FxCache { Hp = cur, Pos = p, IsEnemy = isEnemy, Windup = windup };
}
// Prune despawned ghosts. A Husk that vanished was killed -> death VFX at its last position.
@@ -157,9 +177,10 @@ namespace ProjectM.Client
var c = _cache[_stale[i]];
if (c.IsEnemy)
{
Burst(_deathFx, cfg != null ? cfg.EnemyDeath : null, (Vector3)c.Pos + Vector3.up * 0.5f, 28);
PlayClip(_deathClip, (Vector3)c.Pos, 0.65f);
PrototypeCameraRig.AddShake(0.16f);
Burst(_deathFx, cfg != null ? cfg.EnemyDeath : null, (Vector3)c.Pos + Vector3.up * 0.5f, Mathf.Max(1, Mathf.RoundToInt(FeelConfig.DeathBurstCount * FeelConfig.KillBurstScale)));
PlayClip(_deathClip, (Vector3)c.Pos, FeelConfig.KillSfxVolume);
PrototypeCameraRig.AddShake(FeelConfig.KillShake);
PrototypeCameraRig.PunchFov(FeelConfig.KillFovKick, FeelConfig.HitStopDurationMs);
}
_cache.Remove(_stale[i]);
}