Files
CydandClaude Fable 5 570eb3aceb Single-binary race loop: menu -> race -> menu in one process
WinMain now wraps the engine block in a loop: when a front-end-launched
mission ends under the local console, the setup screen comes back in the
same process instead of exiting (the arcade relaunch-per-mission model).
Replaces the CreateProcess self-respawn - required for Steam, where the
lobby and sockets must survive across races.

Second-cycle re-init crash fixed: d3d_OBJECT kept a static texture cache
keyed by filename, so race 2 got IDirect3DTexture9 pointers created on
race 1 destroyed device and died at first draw (DrawMesh AV). The cache
is now flushed in ~DPLRenderer before the device is released, and
ParticleEngine::Initialize drops particles left over from the previous
mission. Verified: three consecutive 30s races in one PID, each stopped
on time by the console with final scores collected.

Also: L4CONSOLELEN env override for test-length races, and the console
exposes MissionCompleted() for the loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 18:48:56 -05:00

156 lines
4.0 KiB
C++

#pragma once
#include <D3DX9.h>
#include <hash_map>
#include <string>
#define L4VERTEX_FVF (D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_DIFFUSE | D3DFVF_NORMAL)
#define L4POINTVERTEX_FVF (D3DFVF_XYZ)
#define L4VERTEX_2D_FVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)
#define L4VERTEX_2D_TEX_FVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1)
#define PASS_OPAQUE 0
#define PASS_DECAL 1
#define PASS_ALPHABLEND 2
#define PASS_SPHERE 3
#define PASS_SKY 4
#define PASS_TOTAL_COUNT 5
struct L4VERTEX
{
float x, y, z;
float nx, ny, nz;
DWORD color;
float u, v;
};
struct L4POINTVERTEX
{
float x, y, z;
};
struct L4VERTEX_2D
{
float x, y, z, rhw;
DWORD color;
};
struct L4VERTEX_2D_TEX
{
float x, y, z, rhw;
DWORD color;
float u, v;
};
struct L4TEXOP
{
enum WrapType
{
REPEAT,
CLAMP,
SELECT
} wrap_u, wrap_v;
LPDIRECT3DTEXTURE9 texture;
bool doScroll;
float scrollUDelta;
float scrollVDelta;
};
struct L4RAMP
{
float r0, g0, b0;
float r1, g1, b1;
};
struct L4DRAWOP
{
D3DMATERIAL9 material;
L4TEXOP texture;
bool alphaTest;
bool drawAsDecal;
bool drawAsSky;
};
class d3d_OBJECT
{
public:
d3d_OBJECT(LPDIRECT3DDEVICE9 device, int vertCount);
d3d_OBJECT(LPDIRECT3DDEVICE9 device, LPD3DXMESH mesh, DWORD *adjacencyBuffer, int drawOpCount);
~d3d_OBJECT();
inline LPDIRECT3DDEVICE9 GetDevice() { return mDevice; }
inline int GetVertCount() { return mVertCount; }
inline int GetDrawOpCount() { return mDrawOpCount; }
inline L4DRAWOP* GetDrawOp(int index) { return &(mDrawOps[index]); }
void Draw(int pass, const D3DXMATRIX *viewTransform, Time targetRenderFrame);
inline void SetRadius(float radius) { mRadius = radius; }
inline void SetOrigin(L4POINTVERTEX origin) { mOrigin = origin; }
inline void SetImmune(bool immune) { mImmune = immune; }
inline void SetLocalToWorld(D3DXMATRIX localToWorld) { mLocalToWorld = localToWorld; }
inline float GetRadius() { return mRadius; }
inline L4POINTVERTEX GetOrigin() { return mOrigin; }
inline bool GetImmune() { return mImmune; }
inline D3DXMATRIX GetLocalToWorld() { return mLocalToWorld; }
inline LPD3DXMESH GetMesh() { return mMesh; }
inline d3d_OBJECT* GetNext(int pass) { return mNext[pass + 1]; }
inline d3d_OBJECT* GetPrevious( int pass) { return mPrev[pass + 1]; }
inline void SetNext(d3d_OBJECT *next, int pass) { mNext[pass + 1] = next; }
inline void SetPrevious(d3d_OBJECT *previous, int pass) { mPrev[pass + 1] = previous; }
static void ResetState(LPDIRECT3DDEVICE9 device);
static d3d_OBJECT* LoadObject(LPDIRECT3DDEVICE9 device, char *fileName);
static L4TEXOP LoadTexture(LPDIRECT3DDEVICE9 device, const char *fileName);
// Cached textures are bound to the device that created them - the
// cache MUST be flushed when that device goes away (the single-binary
// race loop tears the renderer down between missions).
static void FlushTextureCache();
private:
static d3d_OBJECT* LoadSpheres(LPDIRECT3DDEVICE9 device, char *fileName);
LPDIRECT3DDEVICE9 mDevice;
inline void SetVB(LPDIRECT3DVERTEXBUFFER9 buffer) { mVB = buffer; }
void DrawMesh(int pass, const D3DXMATRIX *viewTransform, Time targetRenderFrame);
void DrawSpheres(int pass, const D3DXMATRIX *viewTransform);
long mID;
// vertex based
int mVertCount;
LPDIRECT3DVERTEXBUFFER9 mVB;
// mesh based
LPD3DXMESH mMesh;
int mDrawOpCount;
L4DRAWOP* mDrawOps;
bool mImmune;
D3DXMATRIX mLocalToWorld;
L4POINTVERTEX mOrigin;
float mRadius;
d3d_OBJECT *mNext[PASS_TOTAL_COUNT + 1];
d3d_OBJECT *mPrev[PASS_TOTAL_COUNT + 1];
void SetTextureScrolling(const L4TEXOP *texture, Time targetRenderFrame);
void SetTextureAddressing(L4TEXOP::WrapType wrap_u, L4TEXOP::WrapType wrap_v);
void SetTexture(LPDIRECT3DTEXTURE9 texture);
// state information for optimizing renderstate changes
static bool mLastTextureScrollState;
static L4TEXOP::WrapType mLastWrapU;
static L4TEXOP::WrapType mLastWrapV;
static bool mLastTexturingState;
static long mNextID;
static stdext::hash_map< std::string , L4TEXOP > mTextureCache;
};
extern int gNumBatches;