Fix: dying on an expedition soft-bricked the player — respawn now resets RegionTag to Base
PlayerRespawnSystem teleported a recovered player to base coords but never reset its server-only RegionTag (every other region-mover flips RegionTag + Position together). So dying ON an expedition left you at base still tagged Expedition: GhostRelevancy hid all base ghosts from you, base enemies ignored you, and the expedition field/zone-director kept counting you as "still out there" (waves never stopped). No self-recovery. - PlayerRespawnSystem: add RefRW<RegionTag> to the recovery query + set Region=Base alongside the reposition. - Harden: drop the hard RequireForUpdate<PlayerSpawner> (a transiently-missing spawner could strand dead players downed forever) -> TryGetSingleton with a BaseAnchor fallback, early-return only if both are absent. - PlayerRespawnSystemTests: add RegionTag to the harness + a regression (expedition death -> respawn at base with RegionTag reset to Base). 390/390 EditMode. Investigation: combat-overhaul workflow wf_c6c87dc5-9c3 (death lane). Base-death case was already correct. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,6 @@ namespace ProjectM.Server
|
|||||||
public void OnCreate(ref SystemState state)
|
public void OnCreate(ref SystemState state)
|
||||||
{
|
{
|
||||||
state.RequireForUpdate<NetworkTime>();
|
state.RequireForUpdate<NetworkTime>();
|
||||||
state.RequireForUpdate<PlayerSpawner>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BurstCompile]
|
[BurstCompile]
|
||||||
@@ -37,14 +36,20 @@ namespace ProjectM.Server
|
|||||||
return;
|
return;
|
||||||
uint now = serverTick.TickIndexForValidTick;
|
uint now = serverTick.TickIndexForValidTick;
|
||||||
|
|
||||||
var spawner = SystemAPI.GetSingleton<PlayerSpawner>();
|
// Resilient spawn reference: prefer the BaseAnchor plot center, fall back to the PlayerSpawner. NEVER
|
||||||
float3 center = spawner.SpawnPoint;
|
// hard-require PlayerSpawner (a transiently-missing singleton must not strand dead players downed forever).
|
||||||
if (SystemAPI.TryGetSingleton<BaseAnchor>(out var baseAnchor))
|
bool haveSpawner = SystemAPI.TryGetSingleton<PlayerSpawner>(out var spawner);
|
||||||
|
bool haveAnchor = SystemAPI.TryGetSingleton<BaseAnchor>(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);
|
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<Health>, RefRW<RespawnState>, RefRW<RespawnInvuln>, RefRW<LocalTransform>,
|
SystemAPI.Query<RefRW<Health>, RefRW<RespawnState>, RefRW<RespawnInvuln>, RefRW<LocalTransform>,
|
||||||
RefRO<GhostOwner>, RefRO<EffectiveCharacterStats>>()
|
RefRW<RegionTag>, RefRO<GhostOwner>, RefRO<EffectiveCharacterStats>>()
|
||||||
.WithAll<PlayerTag>())
|
.WithAll<PlayerTag>())
|
||||||
{
|
{
|
||||||
if (health.ValueRO.Current > 0f)
|
if (health.ValueRO.Current > 0f)
|
||||||
@@ -66,8 +71,12 @@ namespace ProjectM.Server
|
|||||||
health.ValueRW.Current = maxHealth;
|
health.ValueRW.Current = maxHealth;
|
||||||
|
|
||||||
float3 pos = center + PlayerSpawnMath.SpawnOffset(
|
float3 pos = center + PlayerSpawnMath.SpawnOffset(
|
||||||
owner.ValueRO.NetworkId, spawner.SpawnRingRadius, spawner.RingSlots);
|
owner.ValueRO.NetworkId, ringRadius, ringSlots);
|
||||||
xform.ValueRW.Position = pos;
|
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.
|
// 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));
|
invuln.ValueRW.UntilTick = TickUtil.NonZero(now + (uint)math.max(0, respawn.ValueRO.InvulnTicks));
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ namespace ProjectM.Tests
|
|||||||
em.AddComponentData(e, new GhostOwner { NetworkId = networkId });
|
em.AddComponentData(e, new GhostOwner { NetworkId = networkId });
|
||||||
em.AddComponentData(e, new EffectiveCharacterStats { MaxHealth = maxHealth });
|
em.AddComponentData(e, new EffectiveCharacterStats { MaxHealth = maxHealth });
|
||||||
em.AddComponent<PlayerTag>(e);
|
em.AddComponent<PlayerTag>(e);
|
||||||
|
em.AddComponentData(e, new RegionTag { Region = RegionId.Base });
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,5 +104,27 @@ namespace ProjectM.Tests
|
|||||||
Assert.AreEqual(50f, em.GetComponentData<Health>(player).Current, 1e-4f, "Alive health is untouched.");
|
Assert.AreEqual(50f, em.GetComponentData<Health>(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<RegionTag>(player).Region,
|
||||||
|
"Death fix: respawn resets RegionTag to Base (else the player is stranded tagged Expedition).");
|
||||||
|
Assert.Less(em.GetComponentData<LocalTransform>(player).Position.x, 100f,
|
||||||
|
"And is repositioned from the expedition (x~1005) back to the base ring.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user