From d07dd0bf4e03eda8b3c3bcc6cac5bfd95a2a11a8 Mon Sep 17 00:00:00 2001 From: arcattack Date: Fri, 10 Jul 2026 07:26:48 -0500 Subject: [PATCH] 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 --- game/reconstructed/btl4vid.cpp | 166 ++++++++++++++++++++++++++++++++- game/reconstructed/btl4vid.hpp | 6 ++ game/reconstructed/mech4.cpp | 8 ++ 3 files changed, 179 insertions(+), 1 deletion(-) diff --git a/game/reconstructed/btl4vid.cpp b/game/reconstructed/btl4vid.cpp index 471b6d0..13cda26 100644 --- a/game/reconstructed/btl4vid.cpp +++ b/game/reconstructed/btl4vid.cpp @@ -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::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::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) @@ -790,6 +887,18 @@ void BTRemakeMechModel(Entity *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; 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")) 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) { @@ -2229,7 +2349,51 @@ void m._41 = gWarpX; m._42 = gWarpY; m._43 = gWarpZ; m._44 = 1.0f; 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); + + 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); } //===========================================================================// diff --git a/game/reconstructed/btl4vid.hpp b/game/reconstructed/btl4vid.hpp index d161894..7eb7c2c 100644 --- a/game/reconstructed/btl4vid.hpp +++ b/game/reconstructed/btl4vid.hpp @@ -648,6 +648,12 @@ extern void BTDrawReticle(struct IDirect3DDevice9 *device); void 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: // // Per-mech render-tree bookkeeping so RemakeEntityRenderables can find each diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index b8bbb47..49b8dd1 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -1066,6 +1066,14 @@ void SetPreRunFlag(); 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)) --- ForceUpdate();