Camera-seat ranking window LIVE: the 1995 broadcast overlay reimplemented
The stubbed dpl-instance ranking display is reborn as screen-space
quads in CameraShipHUDRenderable::Render:
- followed-player callsign banner (bottom center; the old direct
index was correct -- Execute already converts to the 0-based
texture slot)
- the RANKING WINDOW: one row per scoring player, [ordinal][callsign]
in rank order right of center; visibility = the Director's
authentic flash logic (10s on / 15s off, solid final 30s); rows
follow LIVE playerRank pointers so they re-sort as scores change
- discovery: each 128x32 ordinal bitmap packs TWO ordinals side by
side ('1st|2nd', '3rd|4th' -- why 4 bitmaps serve 8 players);
draw = texture rank/2 with a u-half selected by rank parity
- alpha-blended A4R4G4B4 white-on-transparent textures x green
diffuse = the authentic green look
BT_SHOT moved AFTER the 2D pass -- it captured the backbuffer pre-HUD,
so overlays were on screen but invisible to screenshots (cost one
false debugging round). GetOrdinalTexture accessor added.
Screenshot-verified: '1st MAVERICK' standings + MAVERICK banner over
live auto-directed coverage; 2-node mech smoke PASS (un-regressed;
the new render path only executes when a CameraDirector HUD exists).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4459262b9c
commit
0a77d8e54b
+31
-28
@@ -7431,6 +7431,9 @@ void
|
||||
Check_Pointer(display_rank_window);
|
||||
|
||||
mCamShipHUD = new CameraShipHUDRenderable(entity, CameraShipHUDRenderable::Dynamic, player_index, display_rank_window);
|
||||
if (getenv("BT_CAM_LOG"))
|
||||
DEBUG_STREAM << "[cam] CameraShipHUDRenderable created\n"
|
||||
<< std::flush;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -8518,34 +8521,6 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
BTDrawTranslocationSpheres(mDevice, &viewTransform, (float)dT, mTargetRenderTime);
|
||||
}
|
||||
|
||||
// DIAG (off by default): BT_SHOT=<path> dumps this instance's backbuffer (the rendered
|
||||
// WORLD, before the 2D HUD overlay) to a PNG once ~180 frames in, for non-disruptive
|
||||
// per-instance frame capture without foregrounding the window. Each instance sets its
|
||||
// own path. Built for the -net render glitch (task #53) but general.
|
||||
{
|
||||
const char *shotPath = getenv("BT_SHOT");
|
||||
if (shotPath)
|
||||
{
|
||||
static int s_btShotN = 0;
|
||||
++s_btShotN;
|
||||
// dump (overwriting) every 90 frames once past 90, so the file always holds a
|
||||
// recent frame regardless of load timing / debug fps.
|
||||
if (s_btShotN >= 90 && (s_btShotN % 90) == 0)
|
||||
{
|
||||
IDirect3DSurface9 *bb = 0;
|
||||
if (SUCCEEDED(mDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &bb)) && bb)
|
||||
{
|
||||
HRESULT hr = D3DXSaveSurfaceToFileA(shotPath, D3DXIFF_PNG, bb, 0, 0);
|
||||
bb->Release();
|
||||
DEBUG_STREAM << "[btshot] frame " << s_btShotN << " -> " << shotPath
|
||||
<< " hr=0x" << std::hex << (unsigned)hr << std::dec << "\n" << std::flush;
|
||||
}
|
||||
else
|
||||
DEBUG_STREAM << "[btshot] GetBackBuffer FAILED frame " << s_btShotN << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The BT targeting reticle / weapon pips (2D screen space, cockpit view
|
||||
// only -- the dpl2d layer; see game/reconstructed/dpl2d.cpp).
|
||||
{
|
||||
@@ -8588,6 +8563,34 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
if (mCamShipHUD)
|
||||
mCamShipHUD->Render(0, &viewTransform);
|
||||
|
||||
// DIAG (off by default): BT_SHOT=<path> dumps this instance's backbuffer to a
|
||||
// PNG every 90 frames -- non-disruptive per-instance frame capture without
|
||||
// foregrounding the window. Built for the -net render glitch (task #53);
|
||||
// moved AFTER the 2D pass (2026-07-18, camera-seat bring-up) so captures
|
||||
// include the HUD overlays (reticle, camera-ship ranking window) the screen
|
||||
// actually shows.
|
||||
{
|
||||
const char *shotPath = getenv("BT_SHOT");
|
||||
if (shotPath)
|
||||
{
|
||||
static int s_btShotN = 0;
|
||||
++s_btShotN;
|
||||
if (s_btShotN >= 90 && (s_btShotN % 90) == 0)
|
||||
{
|
||||
IDirect3DSurface9 *bb = 0;
|
||||
if (SUCCEEDED(mDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &bb)) && bb)
|
||||
{
|
||||
HRESULT hr = D3DXSaveSurfaceToFileA(shotPath, D3DXIFF_PNG, bb, 0, 0);
|
||||
bb->Release();
|
||||
DEBUG_STREAM << "[btshot] frame " << s_btShotN << " -> " << shotPath
|
||||
<< " hr=0x" << std::hex << (unsigned)hr << std::dec << "\n" << std::flush;
|
||||
}
|
||||
else
|
||||
DEBUG_STREAM << "[btshot] GetBackBuffer FAILED frame " << s_btShotN << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DEV-COMPOSITE: in DOCKED mode (BT_DEV_GAUGES_DOCK) blit the 6-surface gauge panel
|
||||
// into this window as the LAST draw before EndScene (no-op otherwise / off pod).
|
||||
extern void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device);
|
||||
|
||||
@@ -379,6 +379,7 @@ public:
|
||||
void SetCurrentFogLimits(float fogNear, float fogFar);
|
||||
|
||||
LPDIRECT3DTEXTURE9 GetNameTexture(int playerIndex) { return mNameTextures[playerIndex]; }
|
||||
LPDIRECT3DTEXTURE9 GetOrdinalTexture(int rank) { return mOrdinalTextures[rank]; }
|
||||
|
||||
inline LPDIRECT3DDEVICE9 GetDevice() { return mDevice; }
|
||||
inline LPD3DXMATRIXSTACK GetMatrixStack() { return m_MatrixStack; }
|
||||
|
||||
@@ -3045,17 +3045,124 @@ void CameraShipHUDRenderable::Execute()
|
||||
VideoRenderable::Execute();
|
||||
}
|
||||
|
||||
//
|
||||
// BT port (camera-seat bring-up, 2026-07-18): draw one screen-space textured
|
||||
// quad (u/v 0..1) -- shared by the followed-callsign banner and the ranking
|
||||
// window rows. The name/ordinal bitmaps load black-on-transparent
|
||||
// (LoadBitSliceTexture, A4R4G4B4), so alpha blending keys them over the scene.
|
||||
//
|
||||
static void CameraHUDDrawQuad(
|
||||
LPDIRECT3DDEVICE9 device,
|
||||
LPDIRECT3DTEXTURE9 texture,
|
||||
float x, float y, float w, float h,
|
||||
float u0 = 0.0f, float u1 = 1.0f)
|
||||
{
|
||||
if (texture == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
L4VERTEX_2D_TEX quad[4];
|
||||
memset(quad, 0, sizeof(quad));
|
||||
const DWORD color = D3DCOLOR_XRGB(0, 128, 0);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
quad[i].z = 0.0f;
|
||||
quad[i].rhw = 1.0f;
|
||||
quad[i].color = color;
|
||||
}
|
||||
quad[0].x = x + w; quad[0].y = y; quad[0].u = u1; quad[0].v = 0.0f;
|
||||
quad[1].x = x; quad[1].y = y; quad[1].u = u0; quad[1].v = 0.0f;
|
||||
quad[2].x = x + w; quad[2].y = y + h; quad[2].u = u1; quad[2].v = 1.0f;
|
||||
quad[3].x = x; quad[3].y = y + h; quad[3].u = u0; quad[3].v = 1.0f;
|
||||
device->SetTexture(0, texture);
|
||||
device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, quad,
|
||||
sizeof(L4VERTEX_2D_TEX));
|
||||
}
|
||||
|
||||
void CameraShipHUDRenderable::Render(int pass, const D3DXMATRIX *viewTransform)
|
||||
{
|
||||
if (application->GetApplicationState() == Application::RunningMission && oldFollowedPlayerIndex >= 0 && oldFollowedPlayerIndex < MAX_PLAYER_NAMES)
|
||||
if (application->GetApplicationState() != Application::RunningMission)
|
||||
{
|
||||
LPDIRECT3DDEVICE9 device = myRenderer->GetDevice();
|
||||
return;
|
||||
}
|
||||
LPDIRECT3DDEVICE9 device = myRenderer->GetDevice();
|
||||
device->SetFVF(L4VERTEX_2D_TEX_FVF);
|
||||
device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
|
||||
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
|
||||
device->SetFVF(L4VERTEX_2D_TEX_FVF);
|
||||
//
|
||||
// The followed player's callsign banner (bottom center). Execute already
|
||||
// converts the 1-based goalPlayerIndex to the 0-based texture slot.
|
||||
//
|
||||
if (oldFollowedPlayerIndex >= 0 && oldFollowedPlayerIndex < MAX_PLAYER_NAMES)
|
||||
{
|
||||
device->SetStreamSource(0, mVB, 0, sizeof(L4VERTEX_2D_TEX));
|
||||
device->SetTexture(0, myRenderer->GetNameTexture(oldFollowedPlayerIndex));
|
||||
device->SetTexture(0,
|
||||
myRenderer->GetNameTexture(oldFollowedPlayerIndex));
|
||||
device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
|
||||
}
|
||||
|
||||
//
|
||||
// THE RANKING WINDOW (the un-stubbed 1995 display): one row per scoring
|
||||
// player -- [1st/2nd/... ordinal][callsign] -- in RANK order, right of
|
||||
// center (the old DCS placement: window at x .22 y .16, ordinal left of
|
||||
// the name). Visibility is the Director's flash logic (10s on / 15s off,
|
||||
// solid for the final 30s). playerRank[bmp-1] -> &playerRanking (LIVE
|
||||
// pointers captured at ctor), so the rows re-sort as the score changes.
|
||||
//
|
||||
if (getenv("BT_CAM_LOG"))
|
||||
{
|
||||
static int s_rlog = 0;
|
||||
if ((s_rlog++ % 300) == 0)
|
||||
DEBUG_STREAM << "[cam] HUD render: followed="
|
||||
<< oldFollowedPlayerIndex
|
||||
<< " rankwin=" << (displayRankingWindow
|
||||
? (int)*displayRankingWindow : -1)
|
||||
<< " count=" << playerCount << "\n" << std::flush;
|
||||
}
|
||||
if (displayRankingWindow != NULL && *displayRankingWindow
|
||||
&& playerRank != NULL && playerCount > 0)
|
||||
{
|
||||
const float width = myRenderer->GetWidth();
|
||||
const float height = myRenderer->GetHeight();
|
||||
const float nameW = width * 0.20f;
|
||||
const float ordW = width * 0.10f;
|
||||
const float rowH = nameW * (32.0f / 128.0f); // bitmap aspect
|
||||
const float pad = width * 0.01f;
|
||||
const float x0 = width * 0.55f;
|
||||
const float y0 = height * 0.16f;
|
||||
|
||||
for (int bmp = 0; bmp < playerCount && bmp < MAX_PLAYER_NAMES; ++bmp)
|
||||
{
|
||||
if (playerRank[bmp] == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int rank = *playerRank[bmp];
|
||||
if (getenv("BT_CAM_LOG"))
|
||||
{
|
||||
static int s_rowlog = 0;
|
||||
if ((s_rowlog++ % 300) == 0)
|
||||
DEBUG_STREAM << "[cam] rank row: bmp=" << bmp
|
||||
<< " rank=" << rank << "\n" << std::flush;
|
||||
}
|
||||
if (rank < 0 || rank >= MAX_PLAYER_NAMES)
|
||||
{
|
||||
continue; // non-scoring (director/camera)
|
||||
}
|
||||
const float y = y0 + rank * (rowH * 1.15f);
|
||||
// each 128x32 ordinal bitmap packs TWO ordinals side by side
|
||||
// ("1st|2nd", "3rd|4th", ...) -- texture rank/2, u-half by parity
|
||||
CameraHUDDrawQuad(device, myRenderer->GetOrdinalTexture(rank / 2),
|
||||
x0, y, ordW, rowH,
|
||||
(rank & 1) ? 0.5f : 0.0f, (rank & 1) ? 1.0f : 0.5f);
|
||||
CameraHUDDrawQuad(device, myRenderer->GetNameTexture(bmp),
|
||||
x0 + ordW + pad, y, nameW, rowH);
|
||||
}
|
||||
}
|
||||
|
||||
device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Reference in New Issue
Block a user