#87: mech armour PANELS now darken with damage -- the .DZM material system was loaded and unused

Players: "the actual enemy mech in external view is not showing darkened armor
panels".  We swapped destroyed LIMB meshes but never darkened a panel.

The 1995 game darkens armour through the MATERIALS.  MakeMechRenderables
(FUN_004cef28) built, per (damage zone, material), a watcher
FUN_004573e4(material, &zone->damageLevel, 0.1f) that snapshotted the materials
This commit is contained in:
Joe DiPrima
2026-07-31 19:52:55 -05:00
parent 5410371b0c
commit 137c151951
14 changed files with 589 additions and 1 deletions
+45
View File
@@ -393,6 +393,17 @@ d3d_OBJECT* d3d_OBJECT::LoadObjectBGF(LPDIRECT3DDEVICE9 device, char *fileName)
object->mDrawOps[i].lodNear = data.batches[i].lodNear;
object->mDrawOps[i].lodFar = data.batches[i].lodFar;
object->mDrawOps[i].lodDepthBias = data.batches[i].lodBias;
// MECH ARMOUR DAMAGE (issue #87): carry the authored material name so the
// mech's .DZM zone->material lists can drive this batch's darkening.
{
const std::string &mn = data.batches[i].matName;
size_t n = mn.size();
if (n >= sizeof(object->mDrawOps[i].dzMatName))
n = sizeof(object->mDrawOps[i].dzMatName) - 1;
memcpy(object->mDrawOps[i].dzMatName, mn.c_str(), n);
object->mDrawOps[i].dzMatName[n] = '\0';
object->mDrawOps[i].dzDamageLevel = 0.0f;
}
const bool useRamp = (s_ramp && data.batches[i].hasRamp);
uint32_t c = data.batches[i].color;
@@ -1198,6 +1209,33 @@ void d3d_OBJECT::DrawMesh(int pass, const D3DXMATRIX *viewTransform, Time target
mDevice->SetMaterial(&drawOp->material);
// MECH ARMOUR DAMAGE (issue #87): the 1995 renderer kept a per-material
// watcher that lerped the material's colour terms from the authored values
// toward 0.1x as the owning damage zone's damageLevel ran 0->1
// (FUN_004573e4 precomputed pristine*0.1; FUN_00457784 re-pushed
// lerp(pristine, damaged, level) on every change, constant @0x4579a4 = 1.0).
//
// We cannot express that by scaling this op's D3DMATERIAL9: D3D9 defaults
// DIFFUSEMATERIALSOURCE to D3DMCS_COLOR1, and every BGF vertex carries a
// baked colour, so the material's Diffuse is never consulted for this
// geometry -- scaling it renders identically (measured: 0 changed pixels).
// Instead modulate the FINAL colour by k on texture stage 1, which darkens
// the result whatever the diffuse source was, and equally covers the
// ramp-baked-texture path (the mech's own case) and pure-emissive batches.
const bool damageTint = (drawOp->dzDamageLevel > 0.0f);
if (damageTint)
{
float d = drawOp->dzDamageLevel;
if (d > 1.0f) d = 1.0f;
const float k = 1.0f - (1.0f - kDamagedMaterialScale) * d; // lerp(1, 0.1, d)
mDevice->SetRenderState(D3DRS_TEXTUREFACTOR, D3DCOLOR_COLORVALUE(k, k, k, 1.0f));
mDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE);
mDevice->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
mDevice->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TFACTOR);
mDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
mDevice->SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT);
}
#ifndef RP3_EMULATE
SetTextureScrolling(&(drawOp->texture), targetRenderFrame);
SetTexture(drawOp->texture.texture);
@@ -1337,6 +1375,13 @@ void d3d_OBJECT::DrawMesh(int pass, const D3DXMATRIX *viewTransform, Time target
mDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, sVAop);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, sVAa2);
}
if (damageTint)
{
// stage 1 is otherwise unused in this draw path -- put it back to
// DISABLE so the next (undamaged) op is not tinted too.
mDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
mDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
}
if (drawOp->lodDepthBias != 0.0f)
mDevice->SetRenderState(D3DRS_DEPTHBIAS, sDB);
}
+7
View File
@@ -619,6 +619,9 @@ struct Builder {
uint32_t currentColor = 0xFFB0B0B8u;
bool currentHasDiffuse = false; // material carried an explicit diffuse tag
std::string currentTex;
// authored (pre-substitution) "library:material_mtl" of the material in force --
// the .DZM armour-damage lists key on this (issue #87)
std::string currentMatName;
int currentTexChannel = 0; // BSL bit-slice (BMF tag 0x18)
bool currentTexScroll = false; // TEXTURE SPECIAL " SCROLL" (tag 0x2037)
float currentTexScrollU = 0.0f, currentTexScrollV = 0.0f;
@@ -872,6 +875,7 @@ struct Builder {
batch.color = pureEmissive ? currentColor // keep BLACK: L4D3D's
: (useRamp ? rampTint : currentColor); // pure-emissive test keys on it
batch.texPath = currentTex;
batch.matName = currentMatName; // .DZM armour-damage key (issue #87)
batch.texChannel = currentTexChannel;
batch.texScroll = currentTexScroll;
batch.texScrollU = currentTexScrollU;
@@ -1185,6 +1189,9 @@ struct Builder {
size_t maxn = ch.len - 1, n = 0;
while (n < maxn && s[n]) ++n;
std::string full(s, n);
// remember the AUTHORED name (before the paint callback rewrites
// it) -- the .DZM zone material lists are authored against it
currentMatName = full;
// baked ground-shadow material (basev:shadow_mtl etc.)
currentShadowMat = false;
for (size_t si = 0; si + 6 <= full.size(); ++si)
+11
View File
@@ -106,6 +106,17 @@ struct BgfDrawBatch {
// routed through the mech-shadow pipeline (translucent dark, depth-biased,
// no z-write) instead.
bool shadowMat = false;
// AUTHORED MATERIAL NAME ("library:material_mtl", e.g. "avaskin:avat2_dz_ltorso_mtl"),
// recorded PRE-substitution -- the per-pilot paint callback rewrites names, but the
// .DZM damage-zone material lists are authored against these base names.
//
// This is the key the mech ARMOUR-DARKENING system matches on (issue #87): the 1995
// BTL4VideoRenderer built one material-damage watcher per (zone, material) pair
// (FUN_004573e4) that lerped the material's colour terms from pristine toward 0.1x
// as that zone's damageLevel ran 0->1. Our draw path bakes colour into a
// D3DMATERIAL9 at load, so the equivalent modulation is applied per draw op --
// see L4D3D d3d_OBJECT::SetDamageScaleForMaterials.
std::string matName;
};
struct BgfData {
+18
View File
@@ -71,6 +71,12 @@ struct L4RAMP
float r1, g1, b1;
};
// MECH ARMOUR DAMAGE (issue #87): the factor the 1995 material-damage watcher
// (FUN_004573e4) precomputed as the FULLY-DAMAGED colour -- pristine x 0.1, the
// literal 0x3dcccccd passed at every construction site. A zone at damageLevel 1
// renders its panels at 10% of the authored brightness.
const float kDamagedMaterialScale = 0.1f;
struct L4DRAWOP
{
D3DMATERIAL9 material;
@@ -112,6 +118,18 @@ struct L4DRAWOP
// coplanar duplicated surfaces resolve to the detail layer instead of
// venetian-blind z-fighting (the board's submission-order rule, in D3D terms).
float lodDepthBias;
// MECH ARMOUR DAMAGE (issue #87). The 1995 BTL4VideoRenderer bound one
// material-damage watcher per (damage zone, material) pair -- FUN_004573e4 --
// which lerped that material's colour terms from their authored values toward
// 0.1x as the zone's damageLevel ran 0->1 (FUN_00457784 re-pushed them whenever
// the level changed). The zone->material lists come from the per-mech .DZM
// files, already parsed into DamageZone::materialTable (MUNGA DAMAGE.cpp).
//
// Our draw path bakes colour into this D3DMATERIAL9 at load, so the equivalent
// modulation is applied at SetMaterial time from the level cached here.
// Both fields are memset-0 safe: empty name = not zone-mapped, level 0 = pristine.
char dzMatName[64]; // authored "library:material_mtl" for this batch
float dzDamageLevel; // owning zone's damageLevel [0..1] (0 = undamaged)
};
class d3d_OBJECT