Hunter shot: replicated laser beam + X-ray hit flash; snapshot apply in-place reseed

- Laser.shader + HunterLaserBeam: additive HDR beam, fade in/out over opacity
  and thickness, code-driven LineRenderer, tunable emission multiplier.
- FirstPersonMode: TryShoot computes world endpoint, fires beam with lateral
  muzzle offset (right) so it isn't dead-center from hunter view.
- NetworkPlayerSetup: shot relay RPCs (owner->server->all) + hit-flash RPC
  (server->all), flash OR-branch in ComputeXRay.
- MatchManager: hit reveal broadcasts X-ray flash first, delays hider removal
  by hitRevealDelay so the glow is visible before the body vanishes.
- MeshPainter reseed: in-place fast path avoids full GPU backend teardown on
  snapshot apply (no ~70MB RT churn), fixes spawn/paint-load hitch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-07-04 14:18:55 +02:00
1 parent 5b95cb7f56
commit 9290ef650b
10 files changed
+403 -23

No files matched your search

+43 -4
View File
@@ -115,6 +115,12 @@ public class MatchManager : MonoBehaviour
[Min(0f)]
[SerializeField] private float shootCooldown = 0.4f;
[Tooltip("Seconds a shot hider stays lit with the X-ray glow before it is actually removed from play " +
"(Classic: hidden as found; Conversion: respawned as a hunter). Gives the just-shot flash time to " +
"be seen. The hider stays a live target during this window; 0 = remove instantly (no visible flash).")]
[Min(0f)]
[SerializeField] private float hitRevealDelay = 0.35f;
[Header("Concealment reveal")]
[Tooltip("A hider whose ConcealmentMeter EmbeddedPercent reaches this value (while a match is running) is " +
"revealed through walls with the material's X-ray glow, so nobody can hide by burying itself deep " +
@@ -192,6 +198,9 @@ public class MatchManager : MonoBehaviour
// empties. _phaseRoutine is the running Hide->Seek->Resolution coroutine (stopped if the match is ended
// early via the authority's Back-to-Lobby).
private readonly HashSet<ulong> _pendingHiders = new HashSet<ulong>();
// Server-only. Hiders that were hit and are in their X-ray flash window, not yet removed. Kept out of the
// seek-end check by staying in _pendingHiders; this set only dedupes a second shot during the flash.
private readonly HashSet<ulong> _revealingHiders = new HashSet<ulong>();
// Server-only. Live NGO clientId -> persistent per-install id, reported by each client on connect. The
// clientId is ephemeral (reused after a disconnect); the value is stable across that client's restarts.
private readonly Dictionary<ulong, string> _persistentIds = new Dictionary<ulong, string>();
@@ -398,6 +407,7 @@ public class MatchManager : MonoBehaviour
// A hider that leaves mid-match can never be found - drop it so "all hiders found" can still trigger.
_pendingHiders.Remove(clientId);
_revealingHiders.Remove(clientId); // and out of the in-flight flash set (its resolve coroutine no-ops)
// Free the ephemeral clientId -> identity mapping (the clientId will be reused by a later joiner).
_persistentIds.Remove(clientId);
@@ -517,6 +527,7 @@ public class MatchManager : MonoBehaviour
RespawnAll(id => hunterIds.Contains(id) ? hunterPrefab : hiderPrefab, matchSpawnCenter);
_pendingHiders.Clear();
_revealingHiders.Clear();
foreach (ulong id in _net.ConnectedClientsIds)
if (!hunterIds.Contains(id)) _pendingHiders.Add(id);
@@ -656,13 +667,40 @@ public class MatchManager : MonoBehaviour
MarkHiderFound(targetClientId);
}
// Server-only. Resolves a hit on a hider for the active mode, and drops it from the pending set; the seek
// loop ends the phase early once that empties (no hiders left to find/convert). Classic flags the avatar
// found (replicated by its NetworkPlayerSetup -> free-cam); Conversion respawns it as a Hunter.
// Server-only. Resolves a hit on a hider: flashes its X-ray glow on every client, then (after hitRevealDelay
// so the flash is actually seen) drops it from the pending set and applies the mode outcome - Classic flags
// the avatar found (replicated by its NetworkPlayerSetup -> free-cam); Conversion respawns it as a Hunter.
// The hider stays in _pendingHiders during the flash so seek doesn't end early on the last hit; the seek
// loop ends the phase once _pendingHiders empties (no hiders left to find/convert).
private void MarkHiderFound(ulong hiderClientId)
{
_pendingHiders.Remove(hiderClientId);
if (_revealingHiders.Contains(hiderClientId)) return; // already flashing / being resolved
if (_net.ConnectedClients.TryGetValue(hiderClientId, out var c) && c.PlayerObject)
{
var setup = c.PlayerObject.GetComponent<NetworkPlayerSetup>();
if (setup) setup.FlashHitReveal(hitRevealDelay); // light it up on every client, now
}
_revealingHiders.Add(hiderClientId);
StartCoroutine(ResolveHitAfterReveal(hiderClientId));
}
// Server-only. Waits out the reveal flash, then removes the hider and applies the mode outcome. Split from
// MarkHiderFound so the glow is visible before the avatar leaves play (Classic hide / Conversion despawn).
private IEnumerator ResolveHitAfterReveal(ulong hiderClientId)
{
if (hitRevealDelay > 0f) yield return new WaitForSeconds(hitRevealDelay);
_revealingHiders.Remove(hiderClientId);
if (_net == null || !_net.IsServer) yield break;
_pendingHiders.Remove(hiderClientId);
// A Back-to-Lobby (EndMatch) during the flash tears the match down and respawns everyone; don't then
// convert/found a stale avatar.
if (Phase == MatchPhase.InMatch &&
_net.ConnectedClients.TryGetValue(hiderClientId, out var c) && c.PlayerObject)
{
if (matchMode == MatchMode.Conversion)
ConvertToHunter(hiderClientId, c.PlayerObject);
@@ -672,6 +710,7 @@ public class MatchManager : MonoBehaviour
if (setup) setup.ServerMarkFound();
}
}
string verb = matchMode == MatchMode.Conversion ? "converted to hunter" : "found";
Debug.Log($"[MatchManager] Hider {hiderClientId} {verb} - {_pendingHiders.Count} hider(s) left.", this);
}