Vault Re-Alignment

This commit is contained in:
2026-06-09 23:26:20 -07:00
parent a7405c3f38
commit da522efe7a
63 changed files with 119048 additions and 15 deletions
@@ -52,14 +52,18 @@ namespace ProjectM.Client
ParticleSystem _hitFx;
ParticleSystem _deathFx;
ParticleSystem _muzzleFx;
ParticleSystem _dashFx;
AudioClip _hitClip;
AudioClip _deathClip;
AudioClip _fireClip;
AudioClip _telegraphClip;
AudioClip _dashClip;
Entity _localPlayer = Entity.Null;
uint _lastLocalFireTick;
bool _fireTickInit;
uint _lastLocalDashTick;
bool _dashTickInit;
const int NumberPoolSize = 32;
const int MaxActiveVfx = 40; // bound one-shot VFX GameObject churn under sustained combat
@@ -72,6 +76,7 @@ namespace ProjectM.Client
_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);
_dashClip = MakeClip("dash", 950f, 240f, 0.12f, 0.50f, noise: false);
}
protected override void OnStartRunning()
@@ -83,6 +88,7 @@ namespace ProjectM.Client
_hitFx = MakeBurst("HitSparks", mat, new Color(3f, 2.2f, 0.6f), 0.13f, 7f, 0.32f, 256);
_deathFx = MakeBurst("DeathBurst", mat, new Color(3.2f, 0.7f, 0.25f), 0.22f, 9f, 0.55f, 512);
_muzzleFx = MakeBurst("Muzzle", mat, new Color(0.6f, 2.4f, 3.2f), 0.12f, 5f, 0.20f, 128);
_dashFx = MakeBurst("DashWhoosh", mat, new Color(0.7f, 2.6f, 3.0f), 0.16f, 4f, 0.30f, 256);
for (int i = 0; i < NumberPoolSize; i++)
_numbers.Add(CreateNumber());
@@ -104,6 +110,8 @@ namespace ProjectM.Client
EntityManager.CompleteDependencyBeforeRO<Health>();
EntityManager.CompleteDependencyBeforeRO<LocalTransform>();
EntityManager.CompleteDependencyBeforeRO<AttackWindup>();
EntityManager.CompleteDependencyBeforeRO<DashState>();
EntityManager.CompleteDependencyBeforeRO<DashCooldown>();
// Resolve the local player (for hit colouring + fire feedback).
_localPlayer = Entity.Null;
@@ -115,6 +123,17 @@ namespace ProjectM.Client
localPos = xf.ValueRO.Position;
}
// Client-derived dash window of the LOCAL player (DashSystem runs in the client prediction loop
// too): drives the i-frame shimmer + the hit-feedback suppression below. Observe-only.
bool localIFrameActive = false;
if (_localPlayer != Entity.Null && EntityManager.HasComponent<DashState>(_localPlayer)
&& SystemAPI.TryGetSingleton<NetworkTime>(out var dashNetTime) && dashNetTime.ServerTick.IsValid)
{
var localDash = EntityManager.GetComponentData<DashState>(_localPlayer);
localIFrameActive = localDash.IFrameUntilTick != 0u
&& new NetworkTick(localDash.IFrameUntilTick).IsNewerThan(dashNetTime.ServerTick);
}
// Edge-detect Health on every damageable ghost (players + Husks).
_seen.Clear();
foreach (var (health, xf, entity) in
@@ -136,7 +155,9 @@ namespace ProjectM.Client
PlayClip(_telegraphClip, (Vector3)p, 0.5f);
}
if (cur < prev.Hp - 0.001f)
// Local hit feedback is SUPPRESSED while the local i-frame window is active: the server
// negates the hit; any transient Health dip is reconciliation flicker, not a real hit.
if (cur < prev.Hp - 0.001f && !(isLocalPlayer && localIFrameActive && FeelConfig.DashHitSuppress))
{
SpawnNumber(prev.Hp - cur, (Vector3)p, isLocalPlayer, cam);
Burst(_hitFx, cfg != null ? cfg.Hit : null, (Vector3)p + Vector3.up * 0.8f, FeelConfig.HitBurstCount);
@@ -201,6 +222,26 @@ namespace ProjectM.Client
_fireTickInit = true;
}
// Local-player dash feedback (MC-1): DashCooldown.NextTick advances exactly once per dash
// (replicated [GhostField], predicted both sides; raw uint edge like the muzzle flash — cosmetic
// only). Whoosh + afterimage burst + camera punch on start, shimmer trail while i-frames last.
if (_localPlayer != Entity.Null && EntityManager.HasComponent<DashCooldown>(_localPlayer))
{
uint nextDash = EntityManager.GetComponentData<DashCooldown>(_localPlayer).NextTick;
if (_dashTickInit && nextDash != 0 && nextDash != _lastLocalDashTick)
{
EmitAt(_dashFx, (Vector3)localPos + Vector3.up * 0.6f, FeelConfig.DashBurstCount);
PlayClip(_dashClip, (Vector3)localPos, FeelConfig.DashSfxVolume);
PrototypeCameraRig.AddShake(FeelConfig.DashShake);
PrototypeCameraRig.PunchFov(FeelConfig.DashFovKick, FeelConfig.HitStopDurationMs);
}
_lastLocalDashTick = nextDash;
_dashTickInit = true;
if (localIFrameActive) // i-frame shimmer trail while the local window is active
EmitAt(_dashFx, (Vector3)localPos + Vector3.up * 0.7f, FeelConfig.DashShimmerPerFrame);
}
UpdateProjectileTrails(cfg);
PruneVfx();
AnimateNumbers(dt, cam);
@@ -74,6 +74,21 @@ namespace ProjectM.Client
/// <summary>Tether line width (world units).</summary>
public static float LockOnLineWidth;
// ---- Feature 5 (MC-1): dash juice ----
/// <summary>Camera shake on the local player's dash start.</summary>
public static float DashShake;
/// <summary>Transient FOV punch (degrees) on dash start — the "lurch" read (camera punch, never Time.timeScale).</summary>
public static float DashFovKick;
/// <summary>Afterimage/whoosh particle burst count at dash start.</summary>
public static int DashBurstCount;
/// <summary>Dash whoosh SFX volume.</summary>
public static float DashSfxVolume;
/// <summary>Particles emitted per frame while the local i-frame window is active (the shimmer trail).</summary>
public static int DashShimmerPerFrame;
/// <summary>Suppress local hit-feedback during the local i-frame window (masks the prediction-reconciliation
/// Health flicker on a clean dodge — the documented acceptable-not-a-bug interaction).</summary>
public static bool DashHitSuppress;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
public static void ResetDefaults()
{
@@ -108,6 +123,14 @@ namespace ProjectM.Client
LockOnArcDegrees = 60f;
LockOnLineColor = new Color(0.55f, 0.9f, 1f, 0.35f);
LockOnLineWidth = 0.05f;
// Feature 5 dash (MC-1)
DashShake = 0.18f;
DashFovKick = 1.2f;
DashBurstCount = 14;
DashSfxVolume = 0.55f;
DashShimmerPerFrame = 2;
DashHitSuppress = true;
}
}
}