From 17bb37cb926b89d536de3ecbf1a495c2a7f02be3 Mon Sep 17 00:00:00 2001 From: MelekTaus Moribundus Date: Thu, 2 Jul 2026 20:09:11 +0200 Subject: [PATCH] Fix stuck conversion render-gate on mid-settle disable/disconnect The freshly converted hunter's body was hidden for the settle window via a WaitForSeconds coroutine. If the object was disabled or the converting owner disconnected mid-settle, the coroutine was stranded and _renderGate stayed true forever, leaving the avatar invisible on every remote. Replace it with a frame-driven Time.time deadline (_renderGateUntil) lifted in UpdateVisibility, which runs every frame the component is active and can never get stuck. Remove the coroutine and the now-unused System.Collections using. Visible behaviour in the normal case is unchanged. Co-Authored-By: Claude Opus 4.8 --- Assets/Scripts/Network/NetworkPlayerSetup.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Assets/Scripts/Network/NetworkPlayerSetup.cs b/Assets/Scripts/Network/NetworkPlayerSetup.cs index 81bee3a..d9c305d 100644 --- a/Assets/Scripts/Network/NetworkPlayerSetup.cs +++ b/Assets/Scripts/Network/NetworkPlayerSetup.cs @@ -1,4 +1,3 @@ -using System.Collections; using Unity.Netcode; using UnityEngine; @@ -105,6 +104,7 @@ public class NetworkPlayerSetup : NetworkBehaviour private bool _visibilityApplied; private bool _lastVisible = true; private bool _renderGate; // conversion: body force-hidden until the fresh hunter has settled into place + private float _renderGateUntil; // Time.time deadline that lifts _renderGate; frame-driven (see UpdateVisibility) so a mid-settle disable/despawn can't strand the body hidden forever private MeshPainter _painter; // owns the per-avatar material instance carrying _XRayColor private ConcealmentMeter _concealment; // owner-only embedded-percent source (disabled on remotes) @@ -170,8 +170,8 @@ public class NetworkPlayerSetup : NetworkBehaviour if (!float.IsNaN(_spawnOverride.Value.x)) { _renderGate = true; + _renderGateUntil = Time.time + conversionRevealDelay; SetBodyRenderers(false); // hide immediately, before the first UpdateVisibility runs - StartCoroutine(RevealAfterSettle()); } } @@ -262,6 +262,11 @@ public class NetworkPlayerSetup : NetworkBehaviour // never fights the FirstPersonController's own-body self-hide. private void UpdateVisibility() { + // Frame-driven gate expiry (replaces a WaitForSeconds coroutine): a coroutine is stranded if the + // object is disabled or the converting owner disconnects mid-settle, leaving the body hidden forever. + // Time.time lifts it here every frame the component runs, so the gate can never get stuck. + if (_renderGate && Time.time >= _renderGateUntil) _renderGate = false; + bool found = _isFound.Value; if (IsOwner && !found) return; @@ -281,14 +286,6 @@ public class NetworkPlayerSetup : NetworkBehaviour _visibilityApplied = true; } - // Conversion: drop the render gate after the settle delay so the next visibility pass reveals the hunter - // at its final, replicated position. Per-client and cosmetic (each client reveals on its own clock). - private IEnumerator RevealAfterSettle() - { - yield return new WaitForSeconds(conversionRevealDelay); - _renderGate = false; - } - // Decides whether THIS avatar's body is rendered for the local viewer. Hunters and non-match avatars are // always visible; a Classic-found hider is always hidden; Resolution reveals everyone. The rest is the // hider-visibility matrix per mode: