Reset-based respawn: reuse+heal the mech, not create-a-new-one (task #52)
The respawn glitches (2 mechs, on-fire respawn, camera-inside, can't-control, wreck-never-disappears) all traced to one architectural divergence: our respawn SEVERED playerVehicle on death and CREATED a new mech, leaving the old as a permanent wreck and building a duplicate viewpoint whose old render tree was never torn down. The authentic engine (FUN_0049fb74 + RPPlayer) REUSES the same mech entity: on respawn Mech::Reset heals it and moves it in place. Implemented faithfully, adapted to our layout (the 1995 raw offsets map to different 2007 engine fields, so reset the equivalent named members, not the offsets): - Mech::Reset (real, was a reposition-only stub): reposition + kill dead-reckon (projectedOrigin/projectedVelocity/updateVelocity + our relocated gait accumulators) so the replicant stops lerping to the death spot; clear the death latch (movementMode=1, graphicAlarm=0); Heal every damage zone (new Mech__DamageZone::Heal: full structure, intact skin); DeathReset (vtable+0x28) every subsystem; ForceUpdate to broadcast. - btplayer.cpp: VehicleDead no longer severs playerVehicle; the respawn re-post gates on the mech still being dead; DropZoneReply resets the EXISTING mech in place (heal+move) instead of creating a new one, then fires the warp. Warp moved to the shared placement (initial drop-in + respawn). Verified 2-node: mech entity ID stays 3:22 across 3 deaths (reused, not a new 3:32); each Reset logs alive=1, 20 zones healed, 33 subsystems reset; A sees ONE mech (no wreck+new pair). Warp fires each respawn. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
efa7aeb683
commit
247e51e1e1
@@ -321,17 +321,25 @@ void
|
||||
if (message->deathCount != -1)
|
||||
{
|
||||
//
|
||||
// The 5s respawn re-post (>=0) or the engine's 2s probe (-2). A
|
||||
// vehicle in hand means the drop zone was acquired -> nothing to do.
|
||||
// (This branch also kept the old solo bring-up honest: the initial
|
||||
// spawn's -2 probes land here and are eaten.)
|
||||
// The 5s respawn re-post (>=0) or the engine's 2s probe (-2). RESET-BASED
|
||||
// respawn: the mech STAYS as playerVehicle through death (we no longer
|
||||
// sever it), so "has a vehicle" no longer means "acquired a drop zone".
|
||||
// Gate on whether the mech is still DEAD -- if it's alive again (already
|
||||
// reset by a prior reply), this re-post is moot; if still dead, fall
|
||||
// through to the drop-zone hunt so it gets a spot to reset into.
|
||||
//
|
||||
if (playerVehicle != 0)
|
||||
{
|
||||
deathPending = 0; // this+0x290
|
||||
suppressConsole = 0; // this+0x258
|
||||
Check_Fpu();
|
||||
return;
|
||||
Mech *mech = (Mech *)playerVehicle;
|
||||
Logical mech_dead = (mech != 0
|
||||
&& mech->GetClassID() == Mech::MechClassID
|
||||
&& mech->IsMechDestroyed());
|
||||
if (!mech_dead)
|
||||
{
|
||||
deathPending = 0; // this+0x290
|
||||
suppressConsole = 0; // this+0x258
|
||||
Check_Fpu();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (getenv("BT_SCORE_LOG"))
|
||||
@@ -411,13 +419,15 @@ void
|
||||
killerName = SelfDestructName; // this+0x1d0
|
||||
|
||||
//
|
||||
// Sever the vehicle link. @004c05c4 powers the roster down through
|
||||
// @0049fe0c (the slot-11 DeathShutdown broadcast -- our mech's own death
|
||||
// transition already runs that pass) and clears this+0x1fc. The wreck
|
||||
// stays registered in the world; only the player's claim on it ends --
|
||||
// which is what lets DropZoneReply create the replacement.
|
||||
// RESET-BASED respawn: do NOT sever playerVehicle. The authentic respawn
|
||||
// REUSES the same mech entity -- it stays our vehicle (a dead wreck) and is
|
||||
// later healed + moved in place by Mech::Reset (DropZoneReply). Severing it
|
||||
// (the old model) orphaned the wreck as a second entity and made the reply
|
||||
// build a NEW viewpoint mech, which is what produced the "2 mechs / camera-
|
||||
// inside / on-fire respawn" glitches. The ghost-drive is already gated by
|
||||
// the input zero on IsMechDestroyed (mech4.cpp), so keeping the pointer is
|
||||
// safe. (The dead mech keeps ticking as a frozen wreck until reset.)
|
||||
//
|
||||
playerVehicle = 0; // this+0x1fc
|
||||
|
||||
//
|
||||
// If lives remain (solo/dev: no role -> always), re-post to ourselves at
|
||||
@@ -828,6 +838,28 @@ void
|
||||
HostManager *host_manager = application->GetHostManager();
|
||||
Check(host_manager);
|
||||
|
||||
// TEST HOOK (BT_SPAWN_XZ="x,z"): force the spawn/respawn to a fixed ground
|
||||
// position (X/Z; Y stays the drop-zone ground height, the ground model
|
||||
// snaps it). The map's drop zones are picked ~randomly and land the two
|
||||
// -net instances ~1500u apart -- hard to find each other. Set the same
|
||||
// base on both pods with a small offset so they spawn side by side.
|
||||
{
|
||||
const char *xz = getenv("BT_SPAWN_XZ");
|
||||
if (xz != 0)
|
||||
{
|
||||
float sx = 0.0f, sz = 0.0f;
|
||||
if (sscanf(xz, "%f,%f", &sx, &sz) == 2)
|
||||
{
|
||||
mech_location.linearPosition.x = sx;
|
||||
mech_location.linearPosition.z = sz;
|
||||
if (getenv("BT_MP_LOG"))
|
||||
DEBUG_STREAM << "[spawn] BT_SPAWN_XZ override -> ("
|
||||
<< sx << ", " << mech_location.linearPosition.y
|
||||
<< ", " << sz << ")\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Make any MUNGA-level vehicle (camera ship for an observer player).
|
||||
//
|
||||
@@ -1020,6 +1052,25 @@ void
|
||||
<< " deathCount=" << deathCount << " msgDeath=" << message->deathCount
|
||||
<< " state=" << (int)GetSimulationState() << "\n" << std::flush;
|
||||
|
||||
// TEST HOOK (BT_SPAWN_XZ="x,z"): force the drop-zone X/Z so two -net instances
|
||||
// land in the same area (map zones are ~random, ~1500u apart -- hard to find
|
||||
// each other). Applied to the MESSAGE so BOTH the CreatePlayerVehicle make
|
||||
// AND the Mech::Reset placement below use it (Reset re-places at
|
||||
// message->dropZoneLocation, which would otherwise undo the override). Y
|
||||
// stays the drop-zone ground height; the ground model snaps it.
|
||||
{
|
||||
const char *xz = getenv("BT_SPAWN_XZ");
|
||||
if (xz != 0)
|
||||
{
|
||||
float sx = 0.0f, sz = 0.0f;
|
||||
if (sscanf(xz, "%f,%f", &sx, &sz) == 2)
|
||||
{
|
||||
message->dropZoneLocation.linearPosition.x = sx;
|
||||
message->dropZoneLocation.linearPosition.z = sz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!playerVehicle) // param_1[0x7f] == 0
|
||||
{
|
||||
CreatePlayerVehicle(message->dropZoneLocation); // (**vtable+0x40)(origin)
|
||||
@@ -1047,19 +1098,7 @@ void
|
||||
|
||||
AlwaysExecute(); // param_1[10] &= ~2 (run every frame)
|
||||
deathCount = 0; // param_1[0x80]
|
||||
|
||||
//
|
||||
// WARP (task #52): fire the translocation sphere at our reinsertion.
|
||||
// Self-contained render one-shot at the drop-zone position -- it does NOT
|
||||
// touch the player's SimulationState (that dial drives the camera/POV +
|
||||
// targeting in our reconstruction; pulsing it for the sphere trigger
|
||||
// regressed all of those). The effect plays its own collapse->expand.
|
||||
//
|
||||
{
|
||||
const Point3D &p = message->dropZoneLocation.linearPosition;
|
||||
extern void BTStartWarpEffect(float x, float y, float z);
|
||||
BTStartWarpEffect((float)p.x, (float)p.y, (float)p.z);
|
||||
}
|
||||
// (warp fired in the shared placement below -- initial drop-in + respawn)
|
||||
}
|
||||
else if (deathCount == message->deathCount) // param_2[0xe] == param_1[0x80]
|
||||
{
|
||||
@@ -1068,15 +1107,23 @@ void
|
||||
Check_Fpu();
|
||||
return;
|
||||
}
|
||||
if (GetSimulationState() != DropZoneAcquiredState) // state != 1
|
||||
//
|
||||
// RESPAWN (reset-based): the mech STAYS ours through death; this reply
|
||||
// (deathCount matches) resets the EXISTING dead mech in place -- fall
|
||||
// through to the placement below (Mech::Reset heals + moves it). Guard
|
||||
// against a duplicate reply / the 2s probe: if the mech is already alive
|
||||
// again, there is nothing to reset. (The old path pulsed
|
||||
// DropZoneAcquiredState and looped through the dead handler to build a
|
||||
// NEW mech -- removed; that state drives our camera/targeting.)
|
||||
//
|
||||
Mech *mech = (Mech *)playerVehicle;
|
||||
if (!(mech != 0 && mech->GetClassID() == Mech::MechClassID
|
||||
&& mech->IsMechDestroyed()))
|
||||
{
|
||||
//
|
||||
// Reset-after-death: BT routes this through the death handler
|
||||
// (FUN_004bffd0 calls FUN_004c012c here).
|
||||
//
|
||||
VehicleDeadMessageHandler((VehicleDeadMessage *)message);
|
||||
Check_Fpu();
|
||||
return;
|
||||
}
|
||||
// else: still dead -> fall through to placement (reset + warp)
|
||||
}
|
||||
else // stale / old message
|
||||
{
|
||||
@@ -1084,14 +1131,24 @@ void
|
||||
}
|
||||
|
||||
//
|
||||
// Place (or re-place) the mech at the drop-zone origin and raise the
|
||||
// "translocated" cockpit alarm.
|
||||
// Place the mech at the drop-zone origin: heal + move the SAME entity
|
||||
// (Mech::Reset), raise the "translocated" cockpit alarm, and fire the
|
||||
// translocation-sphere warp. Runs for the initial drop-in (fresh mech) and
|
||||
// for a respawn (dead mech healed) alike.
|
||||
//
|
||||
Set_Alarm_Level((char *)this + 0x2c, 2); // FUN_0041bbd8(this+0x2c, 2)
|
||||
if (playerVehicle->GetClassID() == Mech::MechClassID) // 0xbb9
|
||||
{
|
||||
Mech *mech = (Mech *)playerVehicle;
|
||||
mech->Reset(message->dropZoneLocation, 1 /* True */); // FUN_0049fb74
|
||||
mech->Reset(message->dropZoneLocation, 1 /* True */); // FUN_0049fb74 (heal+move)
|
||||
|
||||
// WARP (task #52): the translocation sphere at our reinsertion. A
|
||||
// self-contained render one-shot -- it does NOT touch SimulationState
|
||||
// (that dial drives the camera/POV + targeting; pulsing it regressed all
|
||||
// of those). The effect plays its own collapse->expand.
|
||||
const Point3D &p = message->dropZoneLocation.linearPosition;
|
||||
extern void BTStartWarpEffect(float x, float y, float z);
|
||||
BTStartWarpEffect((float)p.x, (float)p.y, (float)p.z);
|
||||
}
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user