Respawn: swirl warp, cockpit-not-black on respawn, observer sees peer un-wreck
Three respawn-visual bugs the user saw, each grounded in the engine/decomp:
1. Warp = flat blue BLOB, not the demo's swirly blue/light-blue/white shimmer.
Ground truth (content/VIDEO/MAT/DAY/BTFX.VMF): tsphere_mtl = a SCROLLING bintA
texture (tsphere_scr_tex, SPECIAL SCROLL) + EMISSIVE {0.7,0.5,1} + RAMP "sky"
(dark-blue->white). The swirl IS the scrolling texture; the colour is the
emissive/ramp. Our draw did COLOROP=SELECTARG1/ARG1=TFACTOR, which REPLACES
every texel with one flat colour -> the blob. Fix: MODULATE the (bound,
scrolling) texture by a TFACTOR set to the authentic EMISSIVE hue (0xB380FF)
so the swirl survives and reads blue-white. DrawMesh's cached SetTexture
(L4D3D.cpp:1215) leaves our MODULATE ops standing since textured meshes drew
first. Additive glow, all state saved/restored.
2. First-person view BLACK on the dying/respawning mech until V. SetViewInside's
body-hide + '_cop' canopy suppression + viewSkeleton update were gated !wrecked,
and RebuildMechRenderables (respawn un-wreck) restored the full OUTSIDE torso
with no '_cop' rule -> the cockpit eyepoint ended up wrapped in opaque geometry.
Fix: factor the per-segment mesh selection into ApplyViewSkeleton(viewpoint,
inside) shared by SetViewInside AND RebuildMechRenderables, so respawn re-asserts
the inside skeleton + '_cop' hide; record viewSkeleton even while wrecked. Only
the mech the local camera views FROM gets the inside treatment (a replicant is
always outside).
3. OBSERVER never saw the peer respawn -- the peer's wreck sat forever. The wreck
appears incidentally via rising damage-zone replication -> MechDeathHandler::Tick
-> BTRemakeMechModel (one-way). The un-wreck+warp ran master-only in Mech::Reset.
Fix (reuses the existing damage channel, no stream-framing change): Mech::Reset
also ForceUpdate(DamageZoneUpdateModelFlag) so healed zones cross; Tick handles
the FALLING edge -- on a ReplicantInstance whose zone heals from destroyed, call
BTRebuildMechModel + BTStartWarpEffect once (wasWrecked latch). This is the port
analog of the binary's type-0 graphic-state -> ResetPose un-wreck hook
(Mech::ReadUpdateRecord case 0); the full Mech::WriteUpdateRecord death record
(type 6) is deferred (would touch the netcode framing).
Smoke-verified headless (2-node): victim respawns intact + warp; observer logs
"replicant un-wrecked + warp" at the peer's spawn point; no crash.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d07dd0bf4e
commit
160b78e38d
+128
-120
@@ -809,57 +809,17 @@ void
|
||||
render_tree.wreckAge = 0.0f;
|
||||
|
||||
//
|
||||
// Restore every body segment to its now-intact mesh (mirrors the load in
|
||||
// RemakeEntityRenderables / MakeMechRenderables, for the healed graphic state).
|
||||
// Restore every body segment to its now-intact mesh via the shared view-skeleton
|
||||
// applier, which ALSO re-asserts the inside-view rules (SkeletonType_A + '_cop'
|
||||
// canopy suppression). Without this, respawning while in cockpit view rebuilt
|
||||
// the full OUTSIDE torso around the eyepoint -> the eye ended up inside opaque
|
||||
// geometry -> black viewport until the next V-toggle. Only the mech the local
|
||||
// camera views FROM (the player's own) gets the inside treatment; a replicant
|
||||
// (a peer's mech) is always drawn with its outside skeleton.
|
||||
//
|
||||
JointedMover *jointed_mover = (JointedMover *)entity;
|
||||
EntitySegment::SkeletonType skeletonType =
|
||||
(EntitySegment::SkeletonType)render_tree.viewSkeleton;
|
||||
EntitySegment::SegmentTableIterator segment_iterator(jointed_mover->segmentTable);
|
||||
EntitySegment *segment;
|
||||
int restored = 0;
|
||||
|
||||
while ((segment = segment_iterator.ReadAndNext()) != NULL)
|
||||
{
|
||||
if (segment->IsSiteSegment() != 0)
|
||||
continue;
|
||||
int segment_slot = segment->GetIndex();
|
||||
std::map<int, HierarchicalDrawComponent*>::iterator r =
|
||||
render_tree.segRenderable.find(segment_slot);
|
||||
if (r == render_tree.segRenderable.end() || r->second == NULL)
|
||||
continue;
|
||||
|
||||
Enumeration seg_gstate = 0; // ExistsGraphicState (healed)
|
||||
int zone_index = segment->GetPrimaryDamageZone();
|
||||
if (zone_index >= 0 && zone_index < entity->damageZoneCount
|
||||
&& entity->damageZones[zone_index] != 0)
|
||||
seg_gstate = entity->damageZones[zone_index]->GetGraphicState();
|
||||
|
||||
CString *object_name = segment->GetVideoObjectName(skeletonType, seg_gstate);
|
||||
d3d_OBJECT *new_object = NULL;
|
||||
if (object_name != NULL)
|
||||
{
|
||||
char filename[44];
|
||||
strcpy(filename, (const char *)*object_name);
|
||||
int len = (int)strlen(filename);
|
||||
if (len >= 4)
|
||||
filename[len - 4] = '\0';
|
||||
strcat(filename, ".bgf");
|
||||
new_object = d3d_OBJECT::LoadObject(GetDevice(), filename);
|
||||
if (new_object != NULL && strstr(filename, "tshd") != NULL)
|
||||
{
|
||||
new_object->SetIsShadow(1);
|
||||
for (int op = 0; op < new_object->GetDrawOpCount(); ++op)
|
||||
new_object->GetDrawOp(op)->alphaTest = true;
|
||||
}
|
||||
}
|
||||
if (new_object != NULL)
|
||||
{
|
||||
r->second->SetDrawObj(new_object);
|
||||
++restored;
|
||||
}
|
||||
render_tree.segGState[segment_slot] = (int)seg_gstate;
|
||||
}
|
||||
Entity *viewpoint = (application != 0) ? application->GetViewpointEntity() : 0;
|
||||
int inside = (entity == viewpoint) ? mViewInside : 0;
|
||||
int restored = ApplyViewSkeleton(entity, inside);
|
||||
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[BTrender] respawn: rebuilt intact model ("
|
||||
@@ -2077,6 +2037,86 @@ BTL4VideoRenderer::BTL4VideoRenderer(
|
||||
mViewInside = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// ApplyViewSkeleton -- select + load each body segment's displayed mesh for the
|
||||
// given view (inside = SkeletonType_A / outside = the mech's own skeletonType),
|
||||
// honoring the live damage graphic state, the inside-view '_cop' canopy
|
||||
// suppression, and the shadow (tshd) flagging. Shared by SetViewInside (the live
|
||||
// V-toggle) and RebuildMechRenderables (the respawn un-wreck) so the cockpit view
|
||||
// stays self-consistent ACROSS the death/respawn transition -- the respawn path
|
||||
// used to restore the full OUTSIDE torso around the cockpit eyepoint (no '_cop'
|
||||
// suppression, and viewSkeleton left stale), which enclosed the eye in opaque
|
||||
// geometry -> the "black viewport until you press V" bug. Records viewSkeleton so
|
||||
// a later rebuild restores the RIGHT skeleton. Returns the count of shown meshes.
|
||||
//
|
||||
int
|
||||
BTL4VideoRenderer::ApplyViewSkeleton(Entity *viewpoint, int inside)
|
||||
{
|
||||
std::map<Entity*, MechRenderTree>::iterator tree_it =
|
||||
mMechRenderTrees.find(viewpoint);
|
||||
if (tree_it == mMechRenderTrees.end())
|
||||
return 0;
|
||||
MechRenderTree &render_tree = tree_it->second;
|
||||
render_tree.viewSkeleton = inside
|
||||
? (int)EntitySegment::SkeletonType_A
|
||||
: render_tree.skeletonType;
|
||||
|
||||
JointedMover *jm = (JointedMover *)viewpoint;
|
||||
EntitySegment::SegmentTableIterator it(jm->segmentTable);
|
||||
EntitySegment *segment;
|
||||
int shown = 0, hidden = 0;
|
||||
while ((segment = it.ReadAndNext()) != NULL)
|
||||
{
|
||||
if (segment->IsSiteSegment() != 0)
|
||||
continue;
|
||||
int slot = segment->GetIndex();
|
||||
std::map<int, HierarchicalDrawComponent*>::iterator r =
|
||||
render_tree.segRenderable.find(slot);
|
||||
if (r == render_tree.segRenderable.end() || r->second == NULL)
|
||||
continue;
|
||||
// Live graphic state from the zone (healed=Exists / damaged / gone) -- read
|
||||
// fresh so a just-healed respawn shows the intact mesh, not a stale cache.
|
||||
Enumeration gstate = 0;
|
||||
int zone_index = segment->GetPrimaryDamageZone();
|
||||
if (zone_index >= 0 && zone_index < viewpoint->damageZoneCount
|
||||
&& viewpoint->damageZones[zone_index] != 0)
|
||||
gstate = viewpoint->damageZones[zone_index]->GetGraphicState();
|
||||
CString *nm = segment->GetVideoObjectName(
|
||||
(EntitySegment::SkeletonType)render_tree.viewSkeleton, gstate);
|
||||
// The cockpit interior shell (blx_cop -- the canopy frame around the
|
||||
// eyepoint) currently renders as a black enclosure; keep it hidden until
|
||||
// its interior rendering is sorted (BT_INSIDE_COCKPIT=1 shows it).
|
||||
if (inside && nm != NULL && strstr((const char *)*nm, "_cop") != NULL
|
||||
&& !getenv("BT_INSIDE_COCKPIT"))
|
||||
nm = NULL;
|
||||
d3d_OBJECT *obj = NULL;
|
||||
if (nm != NULL)
|
||||
{
|
||||
char filename[44];
|
||||
strcpy(filename, (const char *)*nm);
|
||||
int len = (int)strlen(filename);
|
||||
if (len >= 4)
|
||||
filename[len - 4] = '\0';
|
||||
strcat(filename, ".bgf");
|
||||
obj = d3d_OBJECT::LoadObject(GetDevice(), filename);
|
||||
if (obj != NULL && strstr(filename, "tshd") != NULL)
|
||||
{
|
||||
obj->SetIsShadow(1);
|
||||
for (int op = 0; op < obj->GetDrawOpCount(); ++op)
|
||||
obj->GetDrawOp(op)->alphaTest = true;
|
||||
}
|
||||
}
|
||||
r->second->SetDrawObj(obj);
|
||||
render_tree.segGState[slot] = (int)gstate;
|
||||
if (obj) ++shown; else ++hidden;
|
||||
}
|
||||
DEBUG_STREAM << "[view] skeleton "
|
||||
<< (inside ? "A (inside)" : "N (outside)") << ": "
|
||||
<< shown << " segment mesh(es) shown, " << hidden
|
||||
<< " hidden\n" << std::flush;
|
||||
return shown;
|
||||
}
|
||||
|
||||
//
|
||||
// The V-key view toggle: switch the live camera between the authentic cockpit
|
||||
// eyepoint and the port's external chase camera. A missing eye (e.g. the
|
||||
@@ -2098,68 +2138,21 @@ void
|
||||
// the authentic pod view worked exactly this way), the chase view restores
|
||||
// the full outside set. Damage graphic states are respected per segment.
|
||||
//
|
||||
// While WRECKED the body is the sinking hulk, so skip the live mesh swap --
|
||||
// but still RECORD the chosen skeleton so the respawn rebuild restores the
|
||||
// right one (RebuildMechRenderables re-applies these same rules on un-wreck).
|
||||
//
|
||||
Entity *viewpoint = (application != 0) ? application->GetViewpointEntity() : 0;
|
||||
std::map<Entity*, MechRenderTree>::iterator tree_it =
|
||||
mMechRenderTrees.find(viewpoint);
|
||||
if (tree_it != mMechRenderTrees.end() && !tree_it->second.wrecked)
|
||||
if (tree_it != mMechRenderTrees.end())
|
||||
{
|
||||
MechRenderTree &render_tree = tree_it->second;
|
||||
render_tree.viewSkeleton = inside
|
||||
? (int)EntitySegment::SkeletonType_A
|
||||
: render_tree.skeletonType;
|
||||
|
||||
JointedMover *jm = (JointedMover *)viewpoint;
|
||||
EntitySegment::SegmentTableIterator it(jm->segmentTable);
|
||||
EntitySegment *segment;
|
||||
int shown = 0, hidden = 0;
|
||||
while ((segment = it.ReadAndNext()) != NULL)
|
||||
{
|
||||
if (segment->IsSiteSegment() != 0)
|
||||
continue;
|
||||
int slot = segment->GetIndex();
|
||||
std::map<int, HierarchicalDrawComponent*>::iterator r =
|
||||
render_tree.segRenderable.find(slot);
|
||||
if (r == render_tree.segRenderable.end() || r->second == NULL)
|
||||
continue;
|
||||
int gstate = 0;
|
||||
std::map<int, int>::iterator g = render_tree.segGState.find(slot);
|
||||
if (g != render_tree.segGState.end())
|
||||
gstate = g->second;
|
||||
CString *nm = segment->GetVideoObjectName(
|
||||
(EntitySegment::SkeletonType)render_tree.viewSkeleton,
|
||||
(Enumeration)gstate);
|
||||
// The cockpit interior shell (blx_cop -- the canopy frame around the
|
||||
// eyepoint) currently renders as a black enclosure; keep it hidden
|
||||
// until its interior rendering is sorted (BT_INSIDE_COCKPIT=1 shows
|
||||
// it for that investigation).
|
||||
if (inside && nm != NULL && strstr((const char *)*nm, "_cop") != NULL
|
||||
&& !getenv("BT_INSIDE_COCKPIT"))
|
||||
nm = NULL;
|
||||
d3d_OBJECT *obj = NULL;
|
||||
if (nm != NULL)
|
||||
{
|
||||
char filename[44];
|
||||
strcpy(filename, (const char *)*nm);
|
||||
int len = (int)strlen(filename);
|
||||
if (len >= 4)
|
||||
filename[len - 4] = '\0';
|
||||
strcat(filename, ".bgf");
|
||||
obj = d3d_OBJECT::LoadObject(GetDevice(), filename);
|
||||
}
|
||||
r->second->SetDrawObj(obj);
|
||||
if (obj)
|
||||
{
|
||||
++shown;
|
||||
DEBUG_STREAM << "[view] shown: seg '"
|
||||
<< (const char *)segment->GetName() << "' mesh '"
|
||||
<< (nm ? (const char *)*nm : "?") << "'\n" << std::flush;
|
||||
}
|
||||
else ++hidden;
|
||||
}
|
||||
DEBUG_STREAM << "[view] skeleton "
|
||||
<< (inside ? "A (inside)" : "N (outside)") << ": "
|
||||
<< shown << " segment mesh(es) shown, " << hidden
|
||||
<< " hidden\n" << std::flush;
|
||||
if (!tree_it->second.wrecked)
|
||||
ApplyViewSkeleton(viewpoint, inside);
|
||||
else
|
||||
tree_it->second.viewSkeleton = inside
|
||||
? (int)EntitySegment::SkeletonType_A
|
||||
: tree_it->second.skeletonType;
|
||||
}
|
||||
|
||||
DEBUG_STREAM << "[view] " << (inside ? "COCKPIT eyepoint" : "external chase")
|
||||
@@ -2350,18 +2343,30 @@ void
|
||||
|
||||
gTLocSphere->SetLocalToWorld(m);
|
||||
|
||||
// Draw as a translucent BLUE additive glow -- a shimmering warp, not an
|
||||
// opaque view-blocking ball. We are inside the alpha pass (ALPHABLENDENABLE
|
||||
// + ZWRITE off already set, L4VIDEO.cpp:7762), but the blend factors + colour
|
||||
// are indeterminate here (beams/pfx drew first), so set them explicitly and
|
||||
// RESTORE every state we touch (a leaked LIGHTING/TFACTOR corrupts the scene).
|
||||
DWORD sSrc, sDst, sLight, sTFactor, sCOp, sCA1, sAOp, sAA1;
|
||||
// Draw the AUTHENTIC translocation-sphere material as a shimmering swirl, not
|
||||
// a flat blob. Ground truth (content/VIDEO/MAT/DAY/BTFX.VMF): tsphere_mtl =
|
||||
// TEXTURE{tsphere_scr_tex -> MAP bintA, SCROLL 0.0 0.0 0.1 0.5} EMISSIVE{0.7,
|
||||
// 0.5,1} RAMP{sky:(0,0,0.6)->(.99,.99,.99)}. The swirl IS the scrolling bintA
|
||||
// texture; the blue/light-blue/white is the EMISSIVE colour (+ the sky ramp).
|
||||
// The old code did COLOROP=SELECTARG1/ARG1=TFACTOR, which REPLACES every texel
|
||||
// with one flat colour -> the "opaque blue blob" the user saw. Instead
|
||||
// MODULATE the (bound, scrolling) texture by a TFACTOR tint set to the authentic
|
||||
// EMISSIVE hue, so the swirl survives and reads blue-white. DrawMesh binds
|
||||
// bintA + animates the scroll (SetTexture/SetTextureScrolling, L4D3D.cpp:1093,
|
||||
// 1220); its SetTexture only rewrites the stage ops on a texturing on/off
|
||||
// TRANSITION (cached mLastTexturingState, :1215) -- textured meshes drew first
|
||||
// so the cache is 'on' and our MODULATE ops stand. We are in the alpha pass
|
||||
// (ALPHABLENDENABLE + ZWRITE off, L4VIDEO.cpp:7762) but the blend/colour are
|
||||
// indeterminate here (beams/pfx drew first): set them explicitly + RESTORE
|
||||
// every state (a leaked LIGHTING/TFACTOR/stage-op corrupts the scene).
|
||||
DWORD sSrc, sDst, sLight, sTFactor, sCOp, sCA1, sCA2, sAOp, sAA1;
|
||||
device->GetRenderState(D3DRS_SRCBLEND, &sSrc);
|
||||
device->GetRenderState(D3DRS_DESTBLEND, &sDst);
|
||||
device->GetRenderState(D3DRS_LIGHTING, &sLight);
|
||||
device->GetRenderState(D3DRS_TEXTUREFACTOR, &sTFactor);
|
||||
device->GetTextureStageState(0, D3DTSS_COLOROP, &sCOp);
|
||||
device->GetTextureStageState(0, D3DTSS_COLORARG1, &sCA1);
|
||||
device->GetTextureStageState(0, D3DTSS_COLORARG2, &sCA2);
|
||||
device->GetTextureStageState(0, D3DTSS_ALPHAOP, &sAOp);
|
||||
device->GetTextureStageState(0, D3DTSS_ALPHAARG1, &sAA1);
|
||||
|
||||
@@ -2369,20 +2374,22 @@ void
|
||||
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE); // additive glow
|
||||
device->SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
{
|
||||
// ARGB blue tint + intensity; BT_WARP_COLOR=AARRGGBB (hex) overrides.
|
||||
// ARGB tint = the authentic EMISSIVE {0.7,0.5,1} -> R=0xB3 G=0x80 B=0xFF,
|
||||
// full-intensity alpha; BT_WARP_COLOR=AARRGGBB (hex) overrides.
|
||||
static DWORD s_warpColor = 0;
|
||||
if (s_warpColor == 0)
|
||||
{
|
||||
const char *wc = getenv("BT_WARP_COLOR");
|
||||
s_warpColor = wc ? (DWORD)strtoul(wc, 0, 16) : 0x9040A0FF;
|
||||
if (s_warpColor == 0) s_warpColor = 0x9040A0FF;
|
||||
s_warpColor = wc ? (DWORD)strtoul(wc, 0, 16) : 0xFFB380FF;
|
||||
if (s_warpColor == 0) s_warpColor = 0xFFB380FF;
|
||||
}
|
||||
device->SetRenderState(D3DRS_TEXTUREFACTOR, s_warpColor);
|
||||
}
|
||||
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR);
|
||||
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); // bintA swirl
|
||||
device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR); // blue tint
|
||||
device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TFACTOR);
|
||||
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TFACTOR); // uniform intensity
|
||||
|
||||
gTLocSphere->Draw(PASS_ALPHABLEND, view, frame_time);
|
||||
|
||||
@@ -2392,6 +2399,7 @@ void
|
||||
device->SetRenderState(D3DRS_TEXTUREFACTOR, sTFactor);
|
||||
device->SetTextureStageState(0, D3DTSS_COLOROP, sCOp);
|
||||
device->SetTextureStageState(0, D3DTSS_COLORARG1, sCA1);
|
||||
device->SetTextureStageState(0, D3DTSS_COLORARG2, sCA2);
|
||||
device->SetTextureStageState(0, D3DTSS_ALPHAOP, sAOp);
|
||||
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, sAA1);
|
||||
}
|
||||
|
||||
@@ -654,6 +654,14 @@ extern void BTDrawReticle(struct IDirect3DDevice9 *device);
|
||||
void
|
||||
RebuildMechRenderables(Entity *entity);
|
||||
|
||||
// Select/load each body segment's displayed mesh for the given view
|
||||
// (inside=SkeletonType_A + '_cop' suppression / outside=full skeleton),
|
||||
// honoring live damage graphic state. Shared by SetViewInside (V-toggle)
|
||||
// and RebuildMechRenderables (respawn) so the cockpit view stays consistent
|
||||
// across death/respawn. Returns the count of shown meshes.
|
||||
int
|
||||
ApplyViewSkeleton(Entity *viewpoint, int inside);
|
||||
|
||||
protected:
|
||||
//
|
||||
// Per-mech render-tree bookkeeping so RemakeEntityRenderables can find each
|
||||
|
||||
@@ -1076,6 +1076,13 @@ void
|
||||
|
||||
// --- broadcast the reset state to replicants (FUN_004a4c54(this, 0x1f)) ---
|
||||
ForceUpdate();
|
||||
// Also re-broadcast the (now-healed) damage zones so a REPLICANT observer sees
|
||||
// the zone levels FALL from destroyed back to intact -- the falling edge that
|
||||
// MechDeathHandler::Tick uses to un-wreck + warp the peer on the other screen
|
||||
// (the master's Reset above un-wrecks + warps LOCALLY; without this the observer
|
||||
// has no signal that the peer respawned and its wreck sits forever). Same
|
||||
// channel the damage path uses (mechdmg.cpp:1016).
|
||||
ForceUpdate(Entity::DamageZoneUpdateModelFlag);
|
||||
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[respawn] Mech::Reset " << GetEntityID()
|
||||
|
||||
@@ -949,7 +949,7 @@ extern void BTSpawnDamageEffect(Mech *mech, int effect_resource, int segment_ind
|
||||
extern void BTRemakeMechModel(Entity *entity);
|
||||
|
||||
MechDeathHandler::MechDeathHandler(Mech *mech) // @0042a984
|
||||
: owner(mech)
|
||||
: owner(mech), wasWrecked(0)
|
||||
{
|
||||
// per-zone last-damage cache, zeroed (binary this[0x10], size mech+0x11c).
|
||||
int count = (mech != 0 && mech->damageZoneCount > 0) ? mech->damageZoneCount : 0;
|
||||
@@ -981,13 +981,47 @@ void
|
||||
|
||||
Scalar level = zone->GetStructureDamageLevel();
|
||||
Scalar prev = lastLevel[i];
|
||||
if (level <= prev) // no new damage on this zone
|
||||
|
||||
// FALLING edge = the master's Mech::Reset healed + re-broadcast this zone
|
||||
// (ForceUpdate(DamageZoneUpdateModelFlag)). The rising-damage swap below is
|
||||
// one-way; on a REPLICANT observer, reverse it here -- drop the destroyed
|
||||
// segment meshes (BTRebuildMechModel) and play the recovery warp -- so the
|
||||
// peer's wreck un-wrecks + warps back on the observer's screen. Gated to
|
||||
// ReplicantInstance: the master already un-wrecks + warps locally in
|
||||
// Mech::Reset (mech4.cpp), so it must NOT double-fire here. The authentic
|
||||
// engine drives this off the type-0 graphic-state transition in
|
||||
// Mech::ReadUpdateRecord (mech.cpp case 0 -> ResetPose); our port has no
|
||||
// ported RemakeEntity state, so we react to the replicated zone level.
|
||||
if (level < prev)
|
||||
{
|
||||
lastLevel[i] = level;
|
||||
if (wasWrecked && prev >= 1.0f && level < 1.0f)
|
||||
{
|
||||
wasWrecked = 0; // one un-wreck per death->respawn
|
||||
if (owner->GetInstance() == Entity::ReplicantInstance)
|
||||
{
|
||||
extern void BTRebuildMechModel(Entity *entity);
|
||||
extern void BTStartWarpEffect(float x, float y, float z);
|
||||
BTRebuildMechModel((Entity *)owner);
|
||||
BTStartWarpEffect((float)owner->localOrigin.linearPosition.x,
|
||||
(float)owner->localOrigin.linearPosition.y,
|
||||
(float)owner->localOrigin.linearPosition.z);
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[respawn] replicant un-wrecked + warp at ("
|
||||
<< owner->localOrigin.linearPosition.x << ","
|
||||
<< owner->localOrigin.linearPosition.z << ")\n" << std::flush;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (level <= prev) // unchanged -- no new damage
|
||||
continue;
|
||||
lastLevel[i] = level;
|
||||
|
||||
const Mech__DamageZone::DamageDescriptor *d = zone->DescriptorForLevel(level);
|
||||
if (level >= 1.0f && prev < 1.0f) // just destroyed -> the Destroyed descriptor
|
||||
{
|
||||
wasWrecked = 1; // latch: a wreck the observer must later reverse
|
||||
const Mech__DamageZone::DamageDescriptor *dd =
|
||||
zone->DescriptorForGraphicState(DamageZone::DestroyedGraphicState); // enum 1
|
||||
if (dd != 0)
|
||||
|
||||
@@ -357,6 +357,12 @@ class MechDeathHandler
|
||||
private:
|
||||
Mech *owner; // @0x14
|
||||
std::vector<Scalar> lastLevel; // @0x10 per-zone last damageLevel cache
|
||||
// PORT (respawn replication): latched when any zone is destroyed, so a
|
||||
// REPLICANT observer can reverse the one-way wreck swap when the master's
|
||||
// Mech::Reset heals + re-broadcasts the zones (falling damage edge). Not a
|
||||
// binary field -- the authentic un-wreck rides the type-0 graphic-state hook
|
||||
// (Mech::ReadUpdateRecord case 0), which our port drives via the zone levels.
|
||||
Logical wasWrecked;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user