Respawn visuals: warp was in the wrong render pass; render never un-wrecked
Two render bugs behind "warp never appears" + "respawned mech stays a sunk wreck": 1. Warp invisible: the tsphere was drawn in PASS_ALPHABLEND, but DrawMesh skips any op whose alphaTest != (pass==PASS_ALPHABLEND) (L4D3D.cpp:1045) and tsphere's op is opaque -> every op skipped, nothing drawn. Flag the sphere's ops for the alpha pass, and draw it as an explicit translucent-blue ADDITIVE glow (state saved+restored) so it reads as a warp shimmer, not an opaque ball or (as shipped) nothing. Tint via BT_WARP_COLOR. 2. Render stays wrecked on respawn: SwapToWreck hides the body + hangs a sinking dbr hulk with render_tree.wrecked a one-way latch; Mech::Reset healed the sim but nothing reversed the render, so the reborn mech kept rendering as the sunk hulk. Add RebuildMechRenderables (the heal direction of RemakeEntity): drop the hulk/debris, clear wrecked, restore every body segment to its now-intact mesh. Mech::Reset calls it via BTRebuildMechModel. Smoke-verified: tsphere loads with its op alpha-flagged; on each respawn "[BTrender] respawn: rebuilt intact model (12 segs restored, hulk dropped)"; no crash. Both are local render (the respawning node's own screen); remote-observer replication of the warp+un-wreck rides the deferred WriteUpdateRecord death path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
247e51e1e1
commit
d07dd0bf4e
@@ -770,6 +770,103 @@ void
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// RebuildMechRenderables (the HEAL direction of RemakeEntity -- respawn)
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
// SwapToWreck hides every body segment and hangs a sinking dbr hulk on the root,
|
||||||
|
// latching render_tree.wrecked (one-way). On respawn Mech::Reset heals the sim
|
||||||
|
// state, but the render stays the sunk hulk unless we reverse the swap: drop the
|
||||||
|
// hulk/debris and restore every segment to its now-intact mesh (the zones are
|
||||||
|
// healed, so GetGraphicState() == Exists). This is the same in-place mesh swap
|
||||||
|
// as RemakeEntityRenderables, forced (RemakeEntity early-returns while wrecked).
|
||||||
|
//
|
||||||
|
int BTTakePendingWreck(Entity *entity); // defined below (SwapToWreck section)
|
||||||
|
|
||||||
|
void
|
||||||
|
BTL4VideoRenderer::RebuildMechRenderables(Entity *entity)
|
||||||
|
{
|
||||||
|
std::map<Entity*, MechRenderTree>::iterator tree_it =
|
||||||
|
mMechRenderTrees.find(entity);
|
||||||
|
if (tree_it == mMechRenderTrees.end())
|
||||||
|
{
|
||||||
|
BTTakePendingWreck(entity); // clear a queued (never-applied) wreck
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MechRenderTree &render_tree = tree_it->second;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Drop the wreck hulk + strewn debris (the renderables leak -- the component
|
||||||
|
// dtor does not cascade, same as the wreck swap -- but hiding them removes
|
||||||
|
// them from the draw and stops TickWreck from sinking a nulled tree).
|
||||||
|
//
|
||||||
|
if (render_tree.wreckHulk != NULL) render_tree.wreckHulk->SetDrawObj(NULL);
|
||||||
|
if (render_tree.wreckDebris != NULL) render_tree.wreckDebris->SetDrawObj(NULL);
|
||||||
|
render_tree.wreckHulk = NULL;
|
||||||
|
render_tree.wreckDebris = NULL;
|
||||||
|
render_tree.wrecked = 0;
|
||||||
|
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).
|
||||||
|
//
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getenv("BT_DEATH_LOG"))
|
||||||
|
DEBUG_STREAM << "[BTrender] respawn: rebuilt intact model ("
|
||||||
|
<< restored << " segs restored, hulk dropped)\n" << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
//#############################################################################
|
//#############################################################################
|
||||||
// BTRemakeMechModel (sim-side bridge -- see btl4vid.hpp)
|
// BTRemakeMechModel (sim-side bridge -- see btl4vid.hpp)
|
||||||
@@ -790,6 +887,18 @@ void BTRemakeMechModel(Entity *entity)
|
|||||||
renderer->RemakeEntityRenderables(entity);
|
renderer->RemakeEntityRenderables(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sim-side bridge for the respawn render un-wreck (Mech::Reset calls this to
|
||||||
|
// restore the intact model after healing).
|
||||||
|
void BTRebuildMechModel(Entity *entity)
|
||||||
|
{
|
||||||
|
if (entity == NULL || application == NULL)
|
||||||
|
return;
|
||||||
|
BTL4VideoRenderer *renderer =
|
||||||
|
(BTL4VideoRenderer *)application->GetVideoRenderer();
|
||||||
|
if (renderer != NULL)
|
||||||
|
renderer->RebuildMechRenderables(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
//#############################################################################
|
//#############################################################################
|
||||||
@@ -2184,9 +2293,20 @@ void
|
|||||||
{
|
{
|
||||||
gTLocSphereTried = 1;
|
gTLocSphereTried = 1;
|
||||||
gTLocSphere = d3d_OBJECT::LoadObject(device, "tsphere.bgf");
|
gTLocSphere = d3d_OBJECT::LoadObject(device, "tsphere.bgf");
|
||||||
|
if (gTLocSphere != 0)
|
||||||
|
{
|
||||||
|
// Route the sphere's (opaque) draw-ops INTO the alpha-blend pass.
|
||||||
|
// DrawMesh skips any op whose alphaTest != (pass==PASS_ALPHABLEND)
|
||||||
|
// (L4D3D.cpp:1045), so an un-flagged tsphere renders NOTHING in the
|
||||||
|
// alpha pass -- the warp was invisible for exactly this reason.
|
||||||
|
for (int op = 0; op < gTLocSphere->GetDrawOpCount(); ++op)
|
||||||
|
gTLocSphere->GetDrawOp(op)->alphaTest = true;
|
||||||
|
}
|
||||||
if (getenv("BT_TLOC_LOG"))
|
if (getenv("BT_TLOC_LOG"))
|
||||||
DEBUG_STREAM << "[tloc] tsphere.bgf load "
|
DEBUG_STREAM << "[tloc] tsphere.bgf load "
|
||||||
<< (gTLocSphere ? "OK" : "FAILED") << "\n" << std::flush;
|
<< (gTLocSphere ? "OK" : "FAILED")
|
||||||
|
<< " ops=" << (gTLocSphere ? gTLocSphere->GetDrawOpCount() : 0)
|
||||||
|
<< "\n" << std::flush;
|
||||||
}
|
}
|
||||||
if (gTLocSphere == 0)
|
if (gTLocSphere == 0)
|
||||||
{
|
{
|
||||||
@@ -2229,7 +2349,51 @@ void
|
|||||||
m._41 = gWarpX; m._42 = gWarpY; m._43 = gWarpZ; m._44 = 1.0f;
|
m._41 = gWarpX; m._42 = gWarpY; m._43 = gWarpZ; m._44 = 1.0f;
|
||||||
|
|
||||||
gTLocSphere->SetLocalToWorld(m);
|
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;
|
||||||
|
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_ALPHAOP, &sAOp);
|
||||||
|
device->GetTextureStageState(0, D3DTSS_ALPHAARG1, &sAA1);
|
||||||
|
|
||||||
|
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||||
|
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE); // additive glow
|
||||||
|
device->SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||||
|
{
|
||||||
|
// ARGB blue tint + intensity; 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;
|
||||||
|
}
|
||||||
|
device->SetRenderState(D3DRS_TEXTUREFACTOR, s_warpColor);
|
||||||
|
}
|
||||||
|
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||||
|
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR);
|
||||||
|
device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||||
|
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TFACTOR);
|
||||||
|
|
||||||
gTLocSphere->Draw(PASS_ALPHABLEND, view, frame_time);
|
gTLocSphere->Draw(PASS_ALPHABLEND, view, frame_time);
|
||||||
|
|
||||||
|
device->SetRenderState(D3DRS_SRCBLEND, sSrc);
|
||||||
|
device->SetRenderState(D3DRS_DESTBLEND, sDst);
|
||||||
|
device->SetRenderState(D3DRS_LIGHTING, sLight);
|
||||||
|
device->SetRenderState(D3DRS_TEXTUREFACTOR, sTFactor);
|
||||||
|
device->SetTextureStageState(0, D3DTSS_COLOROP, sCOp);
|
||||||
|
device->SetTextureStageState(0, D3DTSS_COLORARG1, sCA1);
|
||||||
|
device->SetTextureStageState(0, D3DTSS_ALPHAOP, sAOp);
|
||||||
|
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, sAA1);
|
||||||
}
|
}
|
||||||
|
|
||||||
//===========================================================================//
|
//===========================================================================//
|
||||||
|
|||||||
@@ -648,6 +648,12 @@ extern void BTDrawReticle(struct IDirect3DDevice9 *device);
|
|||||||
void
|
void
|
||||||
RemakeEntityRenderables(Entity *entity);
|
RemakeEntityRenderables(Entity *entity);
|
||||||
|
|
||||||
|
// The HEAL direction of RemakeEntity: on respawn (Mech::Reset) drop the
|
||||||
|
// wreck hulk/debris + restore every body segment to its now-intact mesh
|
||||||
|
// (the wreck swap, SwapToWreck, is one-way; this reverses it).
|
||||||
|
void
|
||||||
|
RebuildMechRenderables(Entity *entity);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
//
|
//
|
||||||
// Per-mech render-tree bookkeeping so RemakeEntityRenderables can find each
|
// Per-mech render-tree bookkeeping so RemakeEntityRenderables can find each
|
||||||
|
|||||||
@@ -1066,6 +1066,14 @@ void
|
|||||||
SetPreRunFlag();
|
SetPreRunFlag();
|
||||||
if (interestCount == 0) interestCount = 1;
|
if (interestCount == 0) interestCount = 1;
|
||||||
|
|
||||||
|
// --- RENDER un-wreck: drop the sunk dbr hulk + restore the intact body ---
|
||||||
|
// (the wreck swap is one-way on the render side; Reset heals the sim but
|
||||||
|
// the mech keeps rendering as the sunk hulk without this).
|
||||||
|
{
|
||||||
|
extern void BTRebuildMechModel(Entity *entity);
|
||||||
|
BTRebuildMechModel((Entity *)this);
|
||||||
|
}
|
||||||
|
|
||||||
// --- broadcast the reset state to replicants (FUN_004a4c54(this, 0x1f)) ---
|
// --- broadcast the reset state to replicants (FUN_004a4c54(this, 0x1f)) ---
|
||||||
ForceUpdate();
|
ForceUpdate();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user