diff --git a/Assets/_Project/Scripts/Server/Combat/PlayerRespawnSystem.cs b/Assets/_Project/Scripts/Server/Combat/PlayerRespawnSystem.cs index e9342cb54..dfd5bc3ea 100644 --- a/Assets/_Project/Scripts/Server/Combat/PlayerRespawnSystem.cs +++ b/Assets/_Project/Scripts/Server/Combat/PlayerRespawnSystem.cs @@ -26,7 +26,6 @@ namespace ProjectM.Server public void OnCreate(ref SystemState state) { state.RequireForUpdate(); - state.RequireForUpdate(); } [BurstCompile] @@ -37,14 +36,20 @@ namespace ProjectM.Server return; uint now = serverTick.TickIndexForValidTick; - var spawner = SystemAPI.GetSingleton(); - float3 center = spawner.SpawnPoint; - if (SystemAPI.TryGetSingleton(out var baseAnchor)) + // Resilient spawn reference: prefer the BaseAnchor plot center, fall back to the PlayerSpawner. NEVER + // hard-require PlayerSpawner (a transiently-missing singleton must not strand dead players downed forever). + bool haveSpawner = SystemAPI.TryGetSingleton(out var spawner); + bool haveAnchor = SystemAPI.TryGetSingleton(out var baseAnchor); + if (!haveSpawner && !haveAnchor) + return; // no spawn reference at all this tick — re-try next tick rather than mis-place + float3 center = haveAnchor ? BaseGridMath.PlotCenter(baseAnchor) : spawner.SpawnPoint; + float ringRadius = haveSpawner ? spawner.SpawnRingRadius : 2f; + int ringSlots = haveSpawner ? spawner.RingSlots : 4; center = BaseGridMath.PlotCenter(baseAnchor); - foreach (var (health, respawn, invuln, xform, owner, eff) in + foreach (var (health, respawn, invuln, xform, region, owner, eff) in SystemAPI.Query, RefRW, RefRW, RefRW, - RefRO, RefRO>() + RefRW, RefRO, RefRO>() .WithAll()) { if (health.ValueRO.Current > 0f) @@ -66,8 +71,12 @@ namespace ProjectM.Server health.ValueRW.Current = maxHealth; float3 pos = center + PlayerSpawnMath.SpawnOffset( - owner.ValueRO.NetworkId, spawner.SpawnRingRadius, spawner.RingSlots); + owner.ValueRO.NetworkId, ringRadius, ringSlots); xform.ValueRW.Position = pos; + // Death fix: respawn is at BASE, so the player's server-only RegionTag MUST return to Base too + // (every other mover flips RegionTag + Position together). Dying on an expedition otherwise leaves + // you at base coords still tagged Expedition -> RegionRelevancy hides all base ghosts (soft-brick). + region.ValueRW.Region = RegionId.Base; // Grant brief post-respawn damage immunity so the swarm can't instantly re-kill. invuln.ValueRW.UntilTick = TickUtil.NonZero(now + (uint)math.max(0, respawn.ValueRO.InvulnTicks)); diff --git a/Assets/_Project/Tests/EditMode/PlayerRespawnSystemTests.cs b/Assets/_Project/Tests/EditMode/PlayerRespawnSystemTests.cs index c95e2639d..ce3e093e9 100644 --- a/Assets/_Project/Tests/EditMode/PlayerRespawnSystemTests.cs +++ b/Assets/_Project/Tests/EditMode/PlayerRespawnSystemTests.cs @@ -44,6 +44,7 @@ namespace ProjectM.Tests em.AddComponentData(e, new GhostOwner { NetworkId = networkId }); em.AddComponentData(e, new EffectiveCharacterStats { MaxHealth = maxHealth }); em.AddComponent(e); + em.AddComponentData(e, new RegionTag { Region = RegionId.Base }); return e; } @@ -103,5 +104,27 @@ namespace ProjectM.Tests Assert.AreEqual(50f, em.GetComponentData(player).Current, 1e-4f, "Alive health is untouched."); } } + + [Test] + public void Expedition_Death_Respawns_At_Base_And_Resets_RegionTag() + { + // Death-fix regression: a player who dies ON an expedition (RegionTag=Expedition) must respawn at base + // AND have its RegionTag reset to Base, or it soft-bricks (RegionRelevancy hides all base ghosts). + var (world, group) = MakeWorld("RespawnExpedition", serverTick: 200); + using (world) + { + var em = world.EntityManager; + var player = MakePlayer(em, health: 0f, maxHealth: 100f, respawnTick: 160, + delayTicks: 60, invulnTicks: 120, pos: new float3(1005, 1, 3), networkId: 1); + em.SetComponentData(player, new RegionTag { Region = RegionId.Expedition }); // died out on the sortie + + group.Update(); + + Assert.AreEqual(RegionId.Base, em.GetComponentData(player).Region, + "Death fix: respawn resets RegionTag to Base (else the player is stranded tagged Expedition)."); + Assert.Less(em.GetComponentData(player).Position.x, 100f, + "And is repositioned from the expedition (x~1005) back to the base ring."); + } + } } }