Vault Re-Alignment
This commit is contained in:
@@ -62,6 +62,20 @@ namespace ProjectM.Client
|
||||
if (GUILayout.Button("Go Expedition")) DebugCommandSendSystem.Teleport(RegionId.Expedition);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(6);
|
||||
GUILayout.Label("- Telemetry (MC-0) -");
|
||||
if (DevTelemetryReadout.HasData)
|
||||
{
|
||||
var t = DevTelemetryReadout.Latest;
|
||||
GUILayout.Label($"tick {t.LastSampleTick} husks {t.LiveEnemyCount}");
|
||||
GUILayout.Label($"dash neg {t.DashIFrameNegatedHits} / wasted {t.DashesWasted}");
|
||||
GUILayout.Label($"whiff open {t.ChargerWhiffWindowsOpened} / punish {t.ChargerWhiffPunishesLanded}");
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("(waiting for server telemetry...)");
|
||||
}
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#if UNITY_EDITOR
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// MC-0 — EDITOR-ONLY client receiver for the periodic <see cref="ProjectM.Simulation.DebugTelemetryReport"/>.
|
||||
/// Drains the snapshot into <see cref="DevTelemetryReadout"/> (a plain static) so the IMGUI <c>DebugOverlay</c>
|
||||
/// reads NO ECS state directly (the job-safety rule for presentation). Plain client
|
||||
/// <see cref="SimulationSystemGroup"/>; non-Burst (touches a managed static).
|
||||
/// </summary>
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
|
||||
[UpdateInGroup(typeof(SimulationSystemGroup))]
|
||||
public partial struct DevTelemetryReceiveSystem : ISystem
|
||||
{
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
var builder = new EntityQueryBuilder(Allocator.Temp)
|
||||
.WithAll<ProjectM.Simulation.DebugTelemetryReport, ReceiveRpcCommandRequest>();
|
||||
state.RequireForUpdate(state.GetEntityQuery(builder));
|
||||
}
|
||||
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
foreach (var (report, reqEntity) in
|
||||
SystemAPI.Query<RefRO<ProjectM.Simulation.DebugTelemetryReport>>()
|
||||
.WithAll<ReceiveRpcCommandRequest>().WithEntityAccess())
|
||||
{
|
||||
var r = report.ValueRO;
|
||||
DevTelemetryReadout.Latest = new DevTelemetryReadout.Snapshot
|
||||
{
|
||||
DashIFrameNegatedHits = r.DashIFrameNegatedHits,
|
||||
DashesWasted = r.DashesWasted,
|
||||
ChargerWhiffWindowsOpened = r.ChargerWhiffWindowsOpened,
|
||||
ChargerWhiffPunishesLanded = r.ChargerWhiffPunishesLanded,
|
||||
LiveEnemyCount = r.LiveEnemyCount,
|
||||
LastSampleTick = r.LastSampleTick,
|
||||
};
|
||||
DevTelemetryReadout.HasData = true;
|
||||
ecb.DestroyEntity(reqEntity);
|
||||
}
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MC-0 — static bridge from the ECS telemetry receiver to the IMGUI <c>DebugOverlay</c> (so the overlay reads
|
||||
/// a plain struct, never ECS state). Reset on play-enter so a fast-enter-playmode reload can't show stale data.
|
||||
/// </summary>
|
||||
public static class DevTelemetryReadout
|
||||
{
|
||||
public struct Snapshot
|
||||
{
|
||||
public uint DashIFrameNegatedHits;
|
||||
public uint DashesWasted;
|
||||
public uint ChargerWhiffWindowsOpened;
|
||||
public uint ChargerWhiffPunishesLanded;
|
||||
public uint LiveEnemyCount;
|
||||
public uint LastSampleTick;
|
||||
}
|
||||
|
||||
public static Snapshot Latest;
|
||||
public static bool HasData;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void Reset()
|
||||
{
|
||||
Latest = default;
|
||||
HasData = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 685c34765eac3434aadf08c13fce1aa5
|
||||
@@ -77,6 +77,7 @@ namespace ProjectM.Client
|
||||
var gamepad = UnityEngine.InputSystem.Gamepad.current;
|
||||
var mouse = UnityEngine.InputSystem.Mouse.current;
|
||||
var keyboard = UnityEngine.InputSystem.Keyboard.current;
|
||||
bool dashPressed = ((keyboard != null && keyboard.leftShiftKey.wasPressedThisFrame) || (gamepad != null && gamepad.buttonEast.wasPressedThisFrame)) && !BuildPaletteState.Active;
|
||||
|
||||
float2 rightStick = float2.zero;
|
||||
bool gamepadActive = false;
|
||||
@@ -102,7 +103,7 @@ namespace ProjectM.Client
|
||||
kbmActive =
|
||||
keyboard.wKey.isPressed || keyboard.aKey.isPressed || keyboard.sKey.isPressed || keyboard.dKey.isPressed ||
|
||||
keyboard.upArrowKey.isPressed || keyboard.downArrowKey.isPressed ||
|
||||
keyboard.leftArrowKey.isPressed || keyboard.rightArrowKey.isPressed || keyboard.spaceKey.isPressed;
|
||||
keyboard.leftArrowKey.isPressed || keyboard.rightArrowKey.isPressed || keyboard.spaceKey.isPressed || keyboard.leftShiftKey.isPressed;
|
||||
}
|
||||
|
||||
if (gamepadActive && kbmActive)
|
||||
@@ -160,6 +161,9 @@ namespace ProjectM.Client
|
||||
input.ValueRW.Fire = default;
|
||||
if (firePressed)
|
||||
input.ValueRW.Fire.Set();
|
||||
input.ValueRW.Dash = default;
|
||||
if (dashPressed)
|
||||
input.ValueRW.Dash.Set();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user