Gitea #44: the reticle TARGET NAME PLATE (PNAME1-8) -- the target's callsign under the crosshair
Reconstructed the last deferred piece of the 1995 reticle, decoded from the Execute disassembly @004cdcf0 [T1]: - selection: target_mech+0x190 (owning BTPlayer) -> player+0x1e0 (playerBitmapIndex), 1-based into the mesh table at this+0x2e8 - placement: re-placed every frame the aim moves -- scale 0.12 uniform, translate (K*retPos.x, K*retPos.y - 0.08, -1.0), K = 1/2.8 (the x87 long double @0x4cee64) -- so the label TRACKS THE AIM POINT, not screen centre - visibility: the LOCK attribute (this+0x184, cached +0x188) AND an owning player; reticle-off / simple-X also hide it - art: the plate quad UV-addresses a shared 128x64 bitslice texmap whose texels the pod OVERWROTE each mission with the egg's callsign rasters (original source commented out at L4VIDEO.cpp:5682-5710) -- the baked 'PLAYER n' art in BMAP.BSL is only the shipped fallback. Material colour is a neutral (0.5,0.5,0.5): grey-white, not phosphor green. Port: same geometry in reticle units (0.224 below the aim point, 0.336x0.084) drawn as the target's egg callsign texture through the reticle's own MapX/MapY mapping, so it follows the world view in every layout and BT_SHOT captures it. New: dpl2d_DrawTexturedRect, BTReticleTargetPlate, BTGetPlayerNameTexture. Also: kRetCaret corrected 0.02f -> 0.025f (_DAT_004cd7f4 read from the binary; the old value was a T3 guess, 20% small). KB: gauges-hud + open-questions corrected (the chain was filed as deferred and mis-attributed to 'the 3D marker'); TWO PNAME consumers now distinguished (this plate, and CameraShipHUDRenderable's ranking window which shipped 2026-07-18); new bgf-format section on the plate atlas. Rig-verified: 2-node relay, BT_GOTO=enemy -> '[hud] name plate: bmp=2 tex=1' and 'Boreas' rendered under the crosshair on the locked target. Solo clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0166KTsC7ADm7VXEi1HF1jNg
This commit is contained in:
co-authored by
Claude Opus 5
parent
b7107b1020
commit
35c750dc7c
@@ -1386,7 +1386,9 @@ static const float kRetTickMajor = 0.016f; // [0x82]
|
||||
static const float kRetBotX = -0.25f; // [0x84] bottom heading-ladder x0
|
||||
static const float kRetBotY = 0.35f; // [0x85] heading ladder y
|
||||
static const float kRetBotSpan = 0.5f; // [0x86]
|
||||
static const float kRetCaret = 0.02f; // _DAT_004cd7f4 (caret triangle size) [T3]
|
||||
static const float kRetCaret = 0.025f; // _DAT_004cd7f4 (caret triangle size)
|
||||
// [T1] read from the binary 2026-07-24
|
||||
// (was a 0.02f guess -- 20% small)
|
||||
static const int kRetTicksR = 13; // [9] right ladder tick count
|
||||
static const int kRetTicksB = 21; // [0xb] bottom ladder tick count
|
||||
static const float kRetMaxRange = 1200.0f; // ctor param 11 (0x44960000)
|
||||
@@ -1789,9 +1791,10 @@ void
|
||||
|
||||
// the LOCK RING [0xb7] + its SPIN [0x9d] (Execute @4cebf9-4cee54 [T1]):
|
||||
// while a target is locked the ring+cross (subB9) draws at the reticle
|
||||
// frame centre, spinning 4 degrees per frame; unlocked it clears. (The
|
||||
// binary also hangs the PNAMEx.bgf player-name mesh on the 3D marker here
|
||||
// -- the 3D marker chain remains deferred.)
|
||||
// frame centre, spinning 4 degrees per frame; unlocked it clears. The SAME
|
||||
// lock-change block drives the PNAMEx.bgf player NAME PLATE (the target's
|
||||
// callsign under the crosshair) -- reconstructed at the end of this
|
||||
// function, no longer deferred.
|
||||
{
|
||||
extern int gBTHudLockState;
|
||||
const int locked = (gBTHudLockState == 2); // the Lock attr rule, not just a target
|
||||
@@ -1856,6 +1859,93 @@ void
|
||||
extern int gBTHudPrimary;
|
||||
dpl2d_ExecuteList(gBTHudPrimary ? masterList : simpleXList, device);
|
||||
}
|
||||
|
||||
//
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// THE TARGET NAME PLATE (PNAME1-8.bgf) -- the floating callsign under the
|
||||
// crosshair. Reconstructed from the reticle Execute disassembly
|
||||
// (reference/decomp/reticle_execute_004cdcf0.disasm.txt) [T1]:
|
||||
//
|
||||
// @004cdede, every frame while the full HUD is up:
|
||||
// dpl_IdentityDCS (plate);
|
||||
// dpl_ScaleDCS (plate, 0.12f, 0.12f, 0.12f);
|
||||
// dpl_TranslateDCS(plate, K*reticlePos.x + -0.0f,
|
||||
// K*reticlePos.y + -0.08f, -1.0f);
|
||||
// dpl_FlushDCS (plate);
|
||||
// K = the x87 long double @0x4cee64 = 0.35714286 = 1/2.8 -- the SAME
|
||||
// 2.8 projection constant the hotbox uses, i.e. K converts the reticle's
|
||||
// -1..+1 screen space into eye-space extents at z = -1.
|
||||
// @004cec98: the plate's MESH is the target player's (see
|
||||
// BTReticleTargetPlate in mech4.cpp), visibility 1; no owning player
|
||||
// -> visibility 0.
|
||||
// @004cdd75 / @004cddc1: reticle Off, or On-but-simple-X (PrimaryHudOn
|
||||
// clear) -> visibility 0 and the cached target cleared.
|
||||
// @004cebf9-@004cec47 -- THE LOCK GATE: the block is entered on a change
|
||||
// of EITHER the target (Reticle+0x1c) or the LOCK attribute (the
|
||||
// Scalar* at this+0x184, cached at +0x188), and when that lock value
|
||||
// reads 0 it branches to the hide path (@004ced87). So the plate needs
|
||||
// a real fire-control LOCK, not merely something under the crosshair --
|
||||
// the same `gBTHudLockState == 2` rule the lock RING uses below.
|
||||
//
|
||||
// So the plate TRACKS THE AIM POINT (it is not fixed under screen centre),
|
||||
// sits 0.08 eye-space units below it, and shows the TARGET'S CALLSIGN.
|
||||
//
|
||||
// PORT: identical placement expressed in reticle units -- dividing the
|
||||
// eye-space offsets by K (i.e. x2.8) puts the plate centre at
|
||||
// (reticlePos.x, reticlePos.y +/- 0.08*2.8 = 0.224)
|
||||
// spanning the plate quad's own 1.0 x 0.25 extents (content/VIDEO/GEO/
|
||||
// PNAME1.BGF, verified) scaled by 0.12 and back-converted: 0.336 x 0.084.
|
||||
// The art is the player's callsign raster, exactly as the 1995 plate's
|
||||
// `bmap:name12_mtl` material supplied it -- here the egg's 128x32 name
|
||||
// texture (4:1 == the quad's aspect) via BTGetPlayerNameTexture.
|
||||
//
|
||||
{
|
||||
extern int gBTHudPrimary;
|
||||
extern int gBTHudLockState; // 2 == authentic LOCK
|
||||
extern int BTReticleTargetPlate(void); // mech4.cpp
|
||||
extern void *BTGetPlayerNameTexture(int index0); // L4VIDEO.cpp
|
||||
extern void dpl2d_DrawTexturedRect(struct IDirect3DDevice9 *device,
|
||||
void *texture, float cx, float cy, float w, float h,
|
||||
unsigned long argb);
|
||||
extern float gBTAimX, gBTAimY;
|
||||
|
||||
const float kPlateScale = 0.12f; // dpl_ScaleDCS, uniform
|
||||
const float kPlateW = 1.0f; // the PNAMEx quad's x extent
|
||||
const float kPlateH = 0.25f; // ... and its y extent (4:1)
|
||||
const float kInvK = 2.8f; // 1/K -- eye-space -> reticle
|
||||
const float kPlateDY = 0.08f * kInvK; // = 0.224 below the aim point
|
||||
// The plate's own material colour: content/VIDEO/MAT/BMAP.BMF gives
|
||||
// `name12_mtl` (and each sibling) a NEUTRAL (0.5, 0.5, 0.5) -- the pod's
|
||||
// plate is a grey-white label, NOT phosphor green like the reticle
|
||||
// glyphs. 0.5 * 255 = 128.
|
||||
const unsigned long kPlateARGB = 0xFF808080;
|
||||
|
||||
const int plate = (gBTHudPrimary && gBTHudLockState == 2)
|
||||
? BTReticleTargetPlate() : 0;
|
||||
if (plate > 0)
|
||||
{
|
||||
void *texture = BTGetPlayerNameTexture(plate - 1);
|
||||
if (texture != 0)
|
||||
{
|
||||
// reticle y is +DOWN on screen (dpl2d MapY), eye-space y is
|
||||
// +UP, so the binary's -0.08 eye offset is +kPlateDY here.
|
||||
dpl2d_DrawTexturedRect(device, texture,
|
||||
gBTAimX, gBTAimY + kPlateDY,
|
||||
kPlateW * kPlateScale * kInvK,
|
||||
kPlateH * kPlateScale * kInvK,
|
||||
kPlateARGB);
|
||||
}
|
||||
if (getenv("BT_HUD_LOG"))
|
||||
{
|
||||
static int s_plog = 0;
|
||||
if ((s_plog++ % 120) == 0)
|
||||
DEBUG_STREAM << "[hud] name plate: bmp=" << plate
|
||||
<< " tex=" << (texture != 0)
|
||||
<< " at (" << gBTAimX << "," << (gBTAimY + kPlateDY)
|
||||
<< ")\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -572,3 +572,108 @@ void dpl2d_ExecuteList(dpl2d_DISPLAY *list, IDirect3DDevice9 *device)
|
||||
device->SetRenderState(D3DRS_SRCBLEND, oldSrc);
|
||||
device->SetRenderState(D3DRS_DESTBLEND, oldDst);
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// dpl2d_DrawTexturedRect -- a TEXTURED quad in RETICLE coordinates
|
||||
//#############################################################################
|
||||
//
|
||||
// The reticle's target NAME PLATE (the 1995 PNAMEx.bgf plate) is a UV-mapped
|
||||
// quad, so it cannot ride the line/polyline display lists above -- but it must
|
||||
// land in the SAME space, because the binary places it from the same
|
||||
// `Reticle::reticlePosition` that translates the aim group (reticle Execute
|
||||
// @004cdede [T1 disasm]). So it reuses this TU's authentic mapping: MapX's
|
||||
// aspect-corrected x unit + MapY's half-height y unit, in the CURRENT viewport
|
||||
// (which is the world sub-rect under the cockpit-surround layout, not the whole
|
||||
// backbuffer -- so the plate follows the view like the rest of the reticle).
|
||||
//
|
||||
// cx/cy = quad CENTRE, w/h = full extents, all in reticle units. The texture
|
||||
// arrives as void* because the caller (game/reconstructed/btl4vid.cpp) carries
|
||||
// no d3d9 types. State is saved/restored exactly as dpl2d_ExecuteList does
|
||||
// (leaking a texture stage or the alpha state into the next frame's 3D pass is
|
||||
// the "floating rocks" class of bug).
|
||||
//
|
||||
void dpl2d_DrawTexturedRect(
|
||||
IDirect3DDevice9 *device,
|
||||
void *texture,
|
||||
float cx,
|
||||
float cy,
|
||||
float w,
|
||||
float h,
|
||||
unsigned long argb)
|
||||
{
|
||||
if (device == 0 || texture == 0)
|
||||
return;
|
||||
|
||||
D3DVIEWPORT9 vp;
|
||||
if (FAILED(device->GetViewport(&vp)))
|
||||
return;
|
||||
|
||||
const float ox = (float)vp.X, oy = (float)vp.Y;
|
||||
const float vw = (float)vp.Width, vh = (float)vp.Height;
|
||||
const float xunit = XUnit(vw, vh); // aspect-corrected, as MapX
|
||||
const float yunit = vh * 0.5f; // as MapY
|
||||
|
||||
const float px = MapX(cx, ox, vw, vh);
|
||||
const float py = MapY(cy, oy, vh);
|
||||
const float hw = (w * 0.5f) * xunit;
|
||||
const float hh = (h * 0.5f) * yunit;
|
||||
|
||||
struct VertexTex
|
||||
{
|
||||
float x, y, z, rhw;
|
||||
DWORD color;
|
||||
float u, v;
|
||||
};
|
||||
const DWORD kVertexTexFVF =
|
||||
(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1);
|
||||
|
||||
VertexTex quad[4];
|
||||
memset(quad, 0, sizeof(quad));
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
quad[i].z = 0.0f;
|
||||
quad[i].rhw = 1.0f;
|
||||
quad[i].color = (DWORD)argb; // modulates the plate art (its material colour)
|
||||
}
|
||||
// tri-strip order: TR, TL, BR, BL
|
||||
quad[0].x = px + hw; quad[0].y = py - hh; quad[0].u = 1.0f; quad[0].v = 0.0f;
|
||||
quad[1].x = px - hw; quad[1].y = py - hh; quad[1].u = 0.0f; quad[1].v = 0.0f;
|
||||
quad[2].x = px + hw; quad[2].y = py + hh; quad[2].u = 1.0f; quad[2].v = 1.0f;
|
||||
quad[3].x = px - hw; quad[3].y = py + hh; quad[3].u = 0.0f; quad[3].v = 1.0f;
|
||||
|
||||
DWORD oldLighting = 0, oldZ = 0, oldCull = 0;
|
||||
DWORD oldAlpha = 0, oldSrc = 0, oldDst = 0, oldFog = 0;
|
||||
device->GetRenderState(D3DRS_LIGHTING, &oldLighting);
|
||||
device->GetRenderState(D3DRS_ZENABLE, &oldZ);
|
||||
device->GetRenderState(D3DRS_CULLMODE, &oldCull);
|
||||
device->GetRenderState(D3DRS_ALPHABLENDENABLE, &oldAlpha);
|
||||
device->GetRenderState(D3DRS_SRCBLEND, &oldSrc);
|
||||
device->GetRenderState(D3DRS_DESTBLEND, &oldDst);
|
||||
device->GetRenderState(D3DRS_FOGENABLE, &oldFog);
|
||||
IDirect3DBaseTexture9 *oldTex = 0;
|
||||
device->GetTexture(0, &oldTex);
|
||||
|
||||
device->SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
device->SetRenderState(D3DRS_ZENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_FOGENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
||||
device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
|
||||
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
device->SetFVF(kVertexTexFVF);
|
||||
device->SetTexture(0, (IDirect3DTexture9 *)texture);
|
||||
|
||||
device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, quad, sizeof(VertexTex));
|
||||
|
||||
device->SetTexture(0, oldTex);
|
||||
if (oldTex) oldTex->Release();
|
||||
device->SetFVF(kVertex2DFVF);
|
||||
device->SetRenderState(D3DRS_LIGHTING, oldLighting);
|
||||
device->SetRenderState(D3DRS_ZENABLE, oldZ);
|
||||
device->SetRenderState(D3DRS_FOGENABLE, oldFog);
|
||||
device->SetRenderState(D3DRS_CULLMODE, oldCull);
|
||||
device->SetRenderState(D3DRS_ALPHABLENDENABLE, oldAlpha);
|
||||
device->SetRenderState(D3DRS_SRCBLEND, oldSrc);
|
||||
device->SetRenderState(D3DRS_DESTBLEND, oldDst);
|
||||
}
|
||||
|
||||
@@ -7103,3 +7103,57 @@ void BTCommitLookState(void *mech_v, int look_state)
|
||||
<< " rearFiring=" << m->rearFiring
|
||||
<< " yaw=" << yaw << " pitch=" << pitch << "\n" << std::flush;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// BTReticleTargetPlate -- the reticle NAME PLATE's player index (Gitea #43-b)
|
||||
//#############################################################################
|
||||
//
|
||||
// The 1995 reticle renderable (BTReticleRenderable, ctor @004cc40c) loads
|
||||
// PNAME1-8.bgf into a mesh table at this+0x2e8 and, every frame in Execute,
|
||||
// picks one for the plate hung under the crosshair (disasm @004cec98 [T1]):
|
||||
//
|
||||
// edx = [this + 0x1e0] // the reticle's TARGET entity
|
||||
// eax = edx ? [edx + 0x190] : 0 // mech+0x190 == the owning player
|
||||
// if (eax) { // a piloted target
|
||||
// edx = [eax + 0x1e0] // player+0x1e0 == playerBitmapIndex
|
||||
// mesh = [this + edx*4 + 0x2e8] // 1-BASED: index 1 -> +0x2ec = PNAME1
|
||||
// dpl_SetInstanceObject(plate, mesh);
|
||||
// dpl_SetInstanceVisibility(plate, 1); dpl_FlushInstance(plate);
|
||||
// } else if (showing) { visibility 0; flush; }
|
||||
//
|
||||
// So the plate identifies the TARGET by its egg `bitmapindex` -- the same
|
||||
// 1-based key the Comm pilot list and the radar labels use. This bridge lives
|
||||
// in a complete-Mech TU (Reticle + Mech + Player all complete) and returns the
|
||||
// 1-based index, or 0 for the binary's "hide the plate" case.
|
||||
//
|
||||
// GUARDED DEVIATION [T2, marked]: the binary reads `target+0x190` on ANY target
|
||||
// entity; our compiled layouts differ per class (the databinding rule), so the
|
||||
// mech test is explicit -- the same `IsDerivedFrom(Mech::ClassDerivations)`
|
||||
// gate emitter.cpp uses. Our world-pick can target terrain and structures,
|
||||
// which the 1995 pick could not, so this guard is load-bearing here.
|
||||
//
|
||||
int BTReticleTargetPlate(void)
|
||||
{
|
||||
if (application == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
Entity *view = application->GetViewpointEntity();
|
||||
if (view == 0 || !view->IsDerivedFrom(*Mech::GetClassDerivations()))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
Entity *target = ((Mech *)view)->targetReticle.targetEntity;
|
||||
if (target == 0 || !target->IsDerivedFrom(*Mech::GetClassDerivations()))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
Player *owner = ((Mech *)target)->GetPlayerLink();
|
||||
if (owner == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int index = owner->playerBitmapIndex;
|
||||
return (index > 0 && index <= 8) ? index : 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user